From af19446a67445e96624dfdb9f1fa6b4de77545c8 Mon Sep 17 00:00:00 2001 From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com> Date: Mon, 8 Apr 2024 16:04:30 +0400 Subject: [PATCH 001/102] fix: Use multidict 6.0.5, add setup tools as dev dependency --- poetry.lock | 1317 ++++++++++++++++++++++++++-------------------- pyproject.toml | 3 + requirements.txt | 2 +- 3 files changed, 749 insertions(+), 573 deletions(-) diff --git a/poetry.lock b/poetry.lock index ff84b97..590e15e 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,14 +1,14 @@ -# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. [[package]] name = "aiodns" -version = "3.1.1" +version = "3.2.0" description = "Simple DNS resolver for asyncio" optional = false python-versions = "*" files = [ - {file = "aiodns-3.1.1-py3-none-any.whl", hash = "sha256:a387b63da4ced6aad35b1dda2d09620ad608a1c7c0fb71efa07ebb4cd511928d"}, - {file = "aiodns-3.1.1.tar.gz", hash = "sha256:1073eac48185f7a4150cad7f96a5192d6911f12b4fb894de80a088508c9b3a99"}, + {file = "aiodns-3.2.0-py3-none-any.whl", hash = "sha256:e443c0c27b07da3174a109fd9e736d69058d808f144d3c9d56dbd1776964c5f5"}, + {file = "aiodns-3.2.0.tar.gz", hash = "sha256:62869b23409349c21b072883ec8998316b234c9a9e36675756e8e317e8768f72"}, ] [package.dependencies] @@ -27,87 +27,87 @@ files = [ [[package]] name = "aiohttp" -version = "3.9.1" +version = "3.9.3" description = "Async http client/server framework (asyncio)" optional = false python-versions = ">=3.8" files = [ - {file = "aiohttp-3.9.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e1f80197f8b0b846a8d5cf7b7ec6084493950d0882cc5537fb7b96a69e3c8590"}, - {file = "aiohttp-3.9.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c72444d17777865734aa1a4d167794c34b63e5883abb90356a0364a28904e6c0"}, - {file = "aiohttp-3.9.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9b05d5cbe9dafcdc733262c3a99ccf63d2f7ce02543620d2bd8db4d4f7a22f83"}, - {file = "aiohttp-3.9.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c4fa235d534b3547184831c624c0b7c1e262cd1de847d95085ec94c16fddcd5"}, - {file = "aiohttp-3.9.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:289ba9ae8e88d0ba16062ecf02dd730b34186ea3b1e7489046fc338bdc3361c4"}, - {file = "aiohttp-3.9.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bff7e2811814fa2271be95ab6e84c9436d027a0e59665de60edf44e529a42c1f"}, - {file = "aiohttp-3.9.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81b77f868814346662c96ab36b875d7814ebf82340d3284a31681085c051320f"}, - {file = "aiohttp-3.9.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3b9c7426923bb7bd66d409da46c41e3fb40f5caf679da624439b9eba92043fa6"}, - {file = "aiohttp-3.9.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:8d44e7bf06b0c0a70a20f9100af9fcfd7f6d9d3913e37754c12d424179b4e48f"}, - {file = "aiohttp-3.9.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:22698f01ff5653fe66d16ffb7658f582a0ac084d7da1323e39fd9eab326a1f26"}, - {file = "aiohttp-3.9.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:ca7ca5abfbfe8d39e653870fbe8d7710be7a857f8a8386fc9de1aae2e02ce7e4"}, - {file = "aiohttp-3.9.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:8d7f98fde213f74561be1d6d3fa353656197f75d4edfbb3d94c9eb9b0fc47f5d"}, - {file = "aiohttp-3.9.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5216b6082c624b55cfe79af5d538e499cd5f5b976820eac31951fb4325974501"}, - {file = "aiohttp-3.9.1-cp310-cp310-win32.whl", hash = "sha256:0e7ba7ff228c0d9a2cd66194e90f2bca6e0abca810b786901a569c0de082f489"}, - {file = "aiohttp-3.9.1-cp310-cp310-win_amd64.whl", hash = "sha256:c7e939f1ae428a86e4abbb9a7c4732bf4706048818dfd979e5e2839ce0159f23"}, - {file = "aiohttp-3.9.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:df9cf74b9bc03d586fc53ba470828d7b77ce51b0582d1d0b5b2fb673c0baa32d"}, - {file = "aiohttp-3.9.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ecca113f19d5e74048c001934045a2b9368d77b0b17691d905af18bd1c21275e"}, - {file = "aiohttp-3.9.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8cef8710fb849d97c533f259103f09bac167a008d7131d7b2b0e3a33269185c0"}, - {file = "aiohttp-3.9.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bea94403a21eb94c93386d559bce297381609153e418a3ffc7d6bf772f59cc35"}, - {file = "aiohttp-3.9.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91c742ca59045dce7ba76cab6e223e41d2c70d79e82c284a96411f8645e2afff"}, - {file = "aiohttp-3.9.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6c93b7c2e52061f0925c3382d5cb8980e40f91c989563d3d32ca280069fd6a87"}, - {file = "aiohttp-3.9.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee2527134f95e106cc1653e9ac78846f3a2ec1004cf20ef4e02038035a74544d"}, - {file = "aiohttp-3.9.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:11ff168d752cb41e8492817e10fb4f85828f6a0142b9726a30c27c35a1835f01"}, - {file = "aiohttp-3.9.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b8c3a67eb87394386847d188996920f33b01b32155f0a94f36ca0e0c635bf3e3"}, - {file = "aiohttp-3.9.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c7b5d5d64e2a14e35a9240b33b89389e0035e6de8dbb7ffa50d10d8b65c57449"}, - {file = "aiohttp-3.9.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:69985d50a2b6f709412d944ffb2e97d0be154ea90600b7a921f95a87d6f108a2"}, - {file = "aiohttp-3.9.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:c9110c06eaaac7e1f5562caf481f18ccf8f6fdf4c3323feab28a93d34cc646bd"}, - {file = "aiohttp-3.9.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d737e69d193dac7296365a6dcb73bbbf53bb760ab25a3727716bbd42022e8d7a"}, - {file = "aiohttp-3.9.1-cp311-cp311-win32.whl", hash = "sha256:4ee8caa925aebc1e64e98432d78ea8de67b2272252b0a931d2ac3bd876ad5544"}, - {file = "aiohttp-3.9.1-cp311-cp311-win_amd64.whl", hash = "sha256:a34086c5cc285be878622e0a6ab897a986a6e8bf5b67ecb377015f06ed316587"}, - {file = "aiohttp-3.9.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f800164276eec54e0af5c99feb9494c295118fc10a11b997bbb1348ba1a52065"}, - {file = "aiohttp-3.9.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:500f1c59906cd142d452074f3811614be04819a38ae2b3239a48b82649c08821"}, - {file = "aiohttp-3.9.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0b0a6a36ed7e164c6df1e18ee47afbd1990ce47cb428739d6c99aaabfaf1b3af"}, - {file = "aiohttp-3.9.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69da0f3ed3496808e8cbc5123a866c41c12c15baaaead96d256477edf168eb57"}, - {file = "aiohttp-3.9.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:176df045597e674fa950bf5ae536be85699e04cea68fa3a616cf75e413737eb5"}, - {file = "aiohttp-3.9.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b796b44111f0cab6bbf66214186e44734b5baab949cb5fb56154142a92989aeb"}, - {file = "aiohttp-3.9.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f27fdaadce22f2ef950fc10dcdf8048407c3b42b73779e48a4e76b3c35bca26c"}, - {file = "aiohttp-3.9.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bcb6532b9814ea7c5a6a3299747c49de30e84472fa72821b07f5a9818bce0f66"}, - {file = "aiohttp-3.9.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:54631fb69a6e44b2ba522f7c22a6fb2667a02fd97d636048478db2fd8c4e98fe"}, - {file = "aiohttp-3.9.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:4b4c452d0190c5a820d3f5c0f3cd8a28ace48c54053e24da9d6041bf81113183"}, - {file = "aiohttp-3.9.1-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:cae4c0c2ca800c793cae07ef3d40794625471040a87e1ba392039639ad61ab5b"}, - {file = "aiohttp-3.9.1-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:565760d6812b8d78d416c3c7cfdf5362fbe0d0d25b82fed75d0d29e18d7fc30f"}, - {file = "aiohttp-3.9.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:54311eb54f3a0c45efb9ed0d0a8f43d1bc6060d773f6973efd90037a51cd0a3f"}, - {file = "aiohttp-3.9.1-cp312-cp312-win32.whl", hash = "sha256:85c3e3c9cb1d480e0b9a64c658cd66b3cfb8e721636ab8b0e746e2d79a7a9eed"}, - {file = "aiohttp-3.9.1-cp312-cp312-win_amd64.whl", hash = "sha256:11cb254e397a82efb1805d12561e80124928e04e9c4483587ce7390b3866d213"}, - {file = "aiohttp-3.9.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:8a22a34bc594d9d24621091d1b91511001a7eea91d6652ea495ce06e27381f70"}, - {file = "aiohttp-3.9.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:598db66eaf2e04aa0c8900a63b0101fdc5e6b8a7ddd805c56d86efb54eb66672"}, - {file = "aiohttp-3.9.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2c9376e2b09895c8ca8b95362283365eb5c03bdc8428ade80a864160605715f1"}, - {file = "aiohttp-3.9.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41473de252e1797c2d2293804e389a6d6986ef37cbb4a25208de537ae32141dd"}, - {file = "aiohttp-3.9.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9c5857612c9813796960c00767645cb5da815af16dafb32d70c72a8390bbf690"}, - {file = "aiohttp-3.9.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ffcd828e37dc219a72c9012ec44ad2e7e3066bec6ff3aaa19e7d435dbf4032ca"}, - {file = "aiohttp-3.9.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:219a16763dc0294842188ac8a12262b5671817042b35d45e44fd0a697d8c8361"}, - {file = "aiohttp-3.9.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f694dc8a6a3112059258a725a4ebe9acac5fe62f11c77ac4dcf896edfa78ca28"}, - {file = "aiohttp-3.9.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:bcc0ea8d5b74a41b621ad4a13d96c36079c81628ccc0b30cfb1603e3dfa3a014"}, - {file = "aiohttp-3.9.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:90ec72d231169b4b8d6085be13023ece8fa9b1bb495e4398d847e25218e0f431"}, - {file = "aiohttp-3.9.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:cf2a0ac0615842b849f40c4d7f304986a242f1e68286dbf3bd7a835e4f83acfd"}, - {file = "aiohttp-3.9.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:0e49b08eafa4f5707ecfb321ab9592717a319e37938e301d462f79b4e860c32a"}, - {file = "aiohttp-3.9.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2c59e0076ea31c08553e868cec02d22191c086f00b44610f8ab7363a11a5d9d8"}, - {file = "aiohttp-3.9.1-cp38-cp38-win32.whl", hash = "sha256:4831df72b053b1eed31eb00a2e1aff6896fb4485301d4ccb208cac264b648db4"}, - {file = "aiohttp-3.9.1-cp38-cp38-win_amd64.whl", hash = "sha256:3135713c5562731ee18f58d3ad1bf41e1d8883eb68b363f2ffde5b2ea4b84cc7"}, - {file = "aiohttp-3.9.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:cfeadf42840c1e870dc2042a232a8748e75a36b52d78968cda6736de55582766"}, - {file = "aiohttp-3.9.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:70907533db712f7aa791effb38efa96f044ce3d4e850e2d7691abd759f4f0ae0"}, - {file = "aiohttp-3.9.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cdefe289681507187e375a5064c7599f52c40343a8701761c802c1853a504558"}, - {file = "aiohttp-3.9.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7481f581251bb5558ba9f635db70908819caa221fc79ee52a7f58392778c636"}, - {file = "aiohttp-3.9.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:49f0c1b3c2842556e5de35f122fc0f0b721334ceb6e78c3719693364d4af8499"}, - {file = "aiohttp-3.9.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0d406b01a9f5a7e232d1b0d161b40c05275ffbcbd772dc18c1d5a570961a1ca4"}, - {file = "aiohttp-3.9.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d8e4450e7fe24d86e86b23cc209e0023177b6d59502e33807b732d2deb6975f"}, - {file = "aiohttp-3.9.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c0266cd6f005e99f3f51e583012de2778e65af6b73860038b968a0a8888487a"}, - {file = "aiohttp-3.9.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ab221850108a4a063c5b8a70f00dd7a1975e5a1713f87f4ab26a46e5feac5a0e"}, - {file = "aiohttp-3.9.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:c88a15f272a0ad3d7773cf3a37cc7b7d077cbfc8e331675cf1346e849d97a4e5"}, - {file = "aiohttp-3.9.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:237533179d9747080bcaad4d02083ce295c0d2eab3e9e8ce103411a4312991a0"}, - {file = "aiohttp-3.9.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:02ab6006ec3c3463b528374c4cdce86434e7b89ad355e7bf29e2f16b46c7dd6f"}, - {file = "aiohttp-3.9.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04fa38875e53eb7e354ece1607b1d2fdee2d175ea4e4d745f6ec9f751fe20c7c"}, - {file = "aiohttp-3.9.1-cp39-cp39-win32.whl", hash = "sha256:82eefaf1a996060602f3cc1112d93ba8b201dbf5d8fd9611227de2003dddb3b7"}, - {file = "aiohttp-3.9.1-cp39-cp39-win_amd64.whl", hash = "sha256:9b05d33ff8e6b269e30a7957bd3244ffbce2a7a35a81b81c382629b80af1a8bf"}, - {file = "aiohttp-3.9.1.tar.gz", hash = "sha256:8fc49a87ac269d4529da45871e2ffb6874e87779c3d0e2ccd813c0899221239d"}, + {file = "aiohttp-3.9.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:939677b61f9d72a4fa2a042a5eee2a99a24001a67c13da113b2e30396567db54"}, + {file = "aiohttp-3.9.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1f5cd333fcf7590a18334c90f8c9147c837a6ec8a178e88d90a9b96ea03194cc"}, + {file = "aiohttp-3.9.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:82e6aa28dd46374f72093eda8bcd142f7771ee1eb9d1e223ff0fa7177a96b4a5"}, + {file = "aiohttp-3.9.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f56455b0c2c7cc3b0c584815264461d07b177f903a04481dfc33e08a89f0c26b"}, + {file = "aiohttp-3.9.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bca77a198bb6e69795ef2f09a5f4c12758487f83f33d63acde5f0d4919815768"}, + {file = "aiohttp-3.9.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e083c285857b78ee21a96ba1eb1b5339733c3563f72980728ca2b08b53826ca5"}, + {file = "aiohttp-3.9.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab40e6251c3873d86ea9b30a1ac6d7478c09277b32e14745d0d3c6e76e3c7e29"}, + {file = "aiohttp-3.9.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:df822ee7feaaeffb99c1a9e5e608800bd8eda6e5f18f5cfb0dc7eeb2eaa6bbec"}, + {file = "aiohttp-3.9.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:acef0899fea7492145d2bbaaaec7b345c87753168589cc7faf0afec9afe9b747"}, + {file = "aiohttp-3.9.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:cd73265a9e5ea618014802ab01babf1940cecb90c9762d8b9e7d2cc1e1969ec6"}, + {file = "aiohttp-3.9.3-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:a78ed8a53a1221393d9637c01870248a6f4ea5b214a59a92a36f18151739452c"}, + {file = "aiohttp-3.9.3-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:6b0e029353361f1746bac2e4cc19b32f972ec03f0f943b390c4ab3371840aabf"}, + {file = "aiohttp-3.9.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7cf5c9458e1e90e3c390c2639f1017a0379a99a94fdfad3a1fd966a2874bba52"}, + {file = "aiohttp-3.9.3-cp310-cp310-win32.whl", hash = "sha256:3e59c23c52765951b69ec45ddbbc9403a8761ee6f57253250c6e1536cacc758b"}, + {file = "aiohttp-3.9.3-cp310-cp310-win_amd64.whl", hash = "sha256:055ce4f74b82551678291473f66dc9fb9048a50d8324278751926ff0ae7715e5"}, + {file = "aiohttp-3.9.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6b88f9386ff1ad91ace19d2a1c0225896e28815ee09fc6a8932fded8cda97c3d"}, + {file = "aiohttp-3.9.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c46956ed82961e31557b6857a5ca153c67e5476972e5f7190015018760938da2"}, + {file = "aiohttp-3.9.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:07b837ef0d2f252f96009e9b8435ec1fef68ef8b1461933253d318748ec1acdc"}, + {file = "aiohttp-3.9.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad46e6f620574b3b4801c68255492e0159d1712271cc99d8bdf35f2043ec266"}, + {file = "aiohttp-3.9.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5ed3e046ea7b14938112ccd53d91c1539af3e6679b222f9469981e3dac7ba1ce"}, + {file = "aiohttp-3.9.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:039df344b45ae0b34ac885ab5b53940b174530d4dd8a14ed8b0e2155b9dddccb"}, + {file = "aiohttp-3.9.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7943c414d3a8d9235f5f15c22ace69787c140c80b718dcd57caaade95f7cd93b"}, + {file = "aiohttp-3.9.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:84871a243359bb42c12728f04d181a389718710129b36b6aad0fc4655a7647d4"}, + {file = "aiohttp-3.9.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:5eafe2c065df5401ba06821b9a054d9cb2848867f3c59801b5d07a0be3a380ae"}, + {file = "aiohttp-3.9.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:9d3c9b50f19704552f23b4eaea1fc082fdd82c63429a6506446cbd8737823da3"}, + {file = "aiohttp-3.9.3-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:f033d80bc6283092613882dfe40419c6a6a1527e04fc69350e87a9df02bbc283"}, + {file = "aiohttp-3.9.3-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:2c895a656dd7e061b2fd6bb77d971cc38f2afc277229ce7dd3552de8313a483e"}, + {file = "aiohttp-3.9.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1f5a71d25cd8106eab05f8704cd9167b6e5187bcdf8f090a66c6d88b634802b4"}, + {file = "aiohttp-3.9.3-cp311-cp311-win32.whl", hash = "sha256:50fca156d718f8ced687a373f9e140c1bb765ca16e3d6f4fe116e3df7c05b2c5"}, + {file = "aiohttp-3.9.3-cp311-cp311-win_amd64.whl", hash = "sha256:5fe9ce6c09668063b8447f85d43b8d1c4e5d3d7e92c63173e6180b2ac5d46dd8"}, + {file = "aiohttp-3.9.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:38a19bc3b686ad55804ae931012f78f7a534cce165d089a2059f658f6c91fa60"}, + {file = "aiohttp-3.9.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:770d015888c2a598b377bd2f663adfd947d78c0124cfe7b959e1ef39f5b13869"}, + {file = "aiohttp-3.9.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ee43080e75fc92bf36219926c8e6de497f9b247301bbf88c5c7593d931426679"}, + {file = "aiohttp-3.9.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:52df73f14ed99cee84865b95a3d9e044f226320a87af208f068ecc33e0c35b96"}, + {file = "aiohttp-3.9.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc9b311743a78043b26ffaeeb9715dc360335e5517832f5a8e339f8a43581e4d"}, + {file = "aiohttp-3.9.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b955ed993491f1a5da7f92e98d5dad3c1e14dc175f74517c4e610b1f2456fb11"}, + {file = "aiohttp-3.9.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:504b6981675ace64c28bf4a05a508af5cde526e36492c98916127f5a02354d53"}, + {file = "aiohttp-3.9.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a6fe5571784af92b6bc2fda8d1925cccdf24642d49546d3144948a6a1ed58ca5"}, + {file = "aiohttp-3.9.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ba39e9c8627edc56544c8628cc180d88605df3892beeb2b94c9bc857774848ca"}, + {file = "aiohttp-3.9.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:e5e46b578c0e9db71d04c4b506a2121c0cb371dd89af17a0586ff6769d4c58c1"}, + {file = "aiohttp-3.9.3-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:938a9653e1e0c592053f815f7028e41a3062e902095e5a7dc84617c87267ebd5"}, + {file = "aiohttp-3.9.3-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:c3452ea726c76e92f3b9fae4b34a151981a9ec0a4847a627c43d71a15ac32aa6"}, + {file = "aiohttp-3.9.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ff30218887e62209942f91ac1be902cc80cddb86bf00fbc6783b7a43b2bea26f"}, + {file = "aiohttp-3.9.3-cp312-cp312-win32.whl", hash = "sha256:38f307b41e0bea3294a9a2a87833191e4bcf89bb0365e83a8be3a58b31fb7f38"}, + {file = "aiohttp-3.9.3-cp312-cp312-win_amd64.whl", hash = "sha256:b791a3143681a520c0a17e26ae7465f1b6f99461a28019d1a2f425236e6eedb5"}, + {file = "aiohttp-3.9.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0ed621426d961df79aa3b963ac7af0d40392956ffa9be022024cd16297b30c8c"}, + {file = "aiohttp-3.9.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7f46acd6a194287b7e41e87957bfe2ad1ad88318d447caf5b090012f2c5bb528"}, + {file = "aiohttp-3.9.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:feeb18a801aacb098220e2c3eea59a512362eb408d4afd0c242044c33ad6d542"}, + {file = "aiohttp-3.9.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f734e38fd8666f53da904c52a23ce517f1b07722118d750405af7e4123933511"}, + {file = "aiohttp-3.9.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b40670ec7e2156d8e57f70aec34a7216407848dfe6c693ef131ddf6e76feb672"}, + {file = "aiohttp-3.9.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fdd215b7b7fd4a53994f238d0f46b7ba4ac4c0adb12452beee724ddd0743ae5d"}, + {file = "aiohttp-3.9.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:017a21b0df49039c8f46ca0971b3a7fdc1f56741ab1240cb90ca408049766168"}, + {file = "aiohttp-3.9.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e99abf0bba688259a496f966211c49a514e65afa9b3073a1fcee08856e04425b"}, + {file = "aiohttp-3.9.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:648056db9a9fa565d3fa851880f99f45e3f9a771dd3ff3bb0c048ea83fb28194"}, + {file = "aiohttp-3.9.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8aacb477dc26797ee089721536a292a664846489c49d3ef9725f992449eda5a8"}, + {file = "aiohttp-3.9.3-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:522a11c934ea660ff8953eda090dcd2154d367dec1ae3c540aff9f8a5c109ab4"}, + {file = "aiohttp-3.9.3-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:5bce0dc147ca85caa5d33debc4f4d65e8e8b5c97c7f9f660f215fa74fc49a321"}, + {file = "aiohttp-3.9.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:4b4af9f25b49a7be47c0972139e59ec0e8285c371049df1a63b6ca81fdd216a2"}, + {file = "aiohttp-3.9.3-cp38-cp38-win32.whl", hash = "sha256:298abd678033b8571995650ccee753d9458dfa0377be4dba91e4491da3f2be63"}, + {file = "aiohttp-3.9.3-cp38-cp38-win_amd64.whl", hash = "sha256:69361bfdca5468c0488d7017b9b1e5ce769d40b46a9f4a2eed26b78619e9396c"}, + {file = "aiohttp-3.9.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:0fa43c32d1643f518491d9d3a730f85f5bbaedcbd7fbcae27435bb8b7a061b29"}, + {file = "aiohttp-3.9.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:835a55b7ca49468aaaac0b217092dfdff370e6c215c9224c52f30daaa735c1c1"}, + {file = "aiohttp-3.9.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:06a9b2c8837d9a94fae16c6223acc14b4dfdff216ab9b7202e07a9a09541168f"}, + {file = "aiohttp-3.9.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:abf151955990d23f84205286938796c55ff11bbfb4ccfada8c9c83ae6b3c89a3"}, + {file = "aiohttp-3.9.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59c26c95975f26e662ca78fdf543d4eeaef70e533a672b4113dd888bd2423caa"}, + {file = "aiohttp-3.9.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f95511dd5d0e05fd9728bac4096319f80615aaef4acbecb35a990afebe953b0e"}, + {file = "aiohttp-3.9.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:595f105710293e76b9dc09f52e0dd896bd064a79346234b521f6b968ffdd8e58"}, + {file = "aiohttp-3.9.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7c8b816c2b5af5c8a436df44ca08258fc1a13b449393a91484225fcb7545533"}, + {file = "aiohttp-3.9.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f1088fa100bf46e7b398ffd9904f4808a0612e1d966b4aa43baa535d1b6341eb"}, + {file = "aiohttp-3.9.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f59dfe57bb1ec82ac0698ebfcdb7bcd0e99c255bd637ff613760d5f33e7c81b3"}, + {file = "aiohttp-3.9.3-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:361a1026c9dd4aba0109e4040e2aecf9884f5cfe1b1b1bd3d09419c205e2e53d"}, + {file = "aiohttp-3.9.3-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:363afe77cfcbe3a36353d8ea133e904b108feea505aa4792dad6585a8192c55a"}, + {file = "aiohttp-3.9.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8e2c45c208c62e955e8256949eb225bd8b66a4c9b6865729a786f2aa79b72e9d"}, + {file = "aiohttp-3.9.3-cp39-cp39-win32.whl", hash = "sha256:f7217af2e14da0856e082e96ff637f14ae45c10a5714b63c77f26d8884cf1051"}, + {file = "aiohttp-3.9.3-cp39-cp39-win_amd64.whl", hash = "sha256:27468897f628c627230dba07ec65dc8d0db566923c48f29e084ce382119802bc"}, + {file = "aiohttp-3.9.3.tar.gz", hash = "sha256:90842933e5d1ff760fae6caca4b2b3edba53ba8f4b71e95dacf2818a2aca06f7"}, ] [package.dependencies] @@ -196,18 +196,18 @@ tests = ["pytest"] [[package]] name = "asyncstdlib" -version = "3.12.0" +version = "3.12.2" description = "The missing async toolbox" optional = false -python-versions = "~=3.6" +python-versions = "~=3.8" files = [ - {file = "asyncstdlib-3.12.0-py3-none-any.whl", hash = "sha256:68b8dc92d91193fe977825e2dc965b74ae6624d1994bcd5958aa7414ba9d4eaa"}, - {file = "asyncstdlib-3.12.0.tar.gz", hash = "sha256:83d620553fea73ee924a986e69135bcacb655f6cdc6f2f9e67030a1e259d8be5"}, + {file = "asyncstdlib-3.12.2-py3-none-any.whl", hash = "sha256:343b4ae330a76a8ed05fc83a03e2756dcaa7bede0cf4c7f0ba4aa7112feddec4"}, + {file = "asyncstdlib-3.12.2.tar.gz", hash = "sha256:3fea1eef246b68022c5bbaf6661db59f640ef54dd05fa1d0ac987e49b86a5529"}, ] [package.extras] doc = ["sphinx", "sphinxcontrib-trio"] -test = ["black", "coverage", "flake8", "flake8-2020", "flake8-bugbear", "mypy", "pytest", "pytest-cov"] +test = ["black", "coverage", "flake8", "flake8-2020", "flake8-bugbear", "mypy", "pytest", "pytest-cov", "typing-extensions"] [[package]] name = "attrs" @@ -230,19 +230,22 @@ tests-no-zope = ["attrs[tests-mypy]", "cloudpickle", "hypothesis", "pympler", "p [[package]] name = "beautifulsoup4" -version = "4.12.2" +version = "4.12.3" description = "Screen-scraping library" optional = false python-versions = ">=3.6.0" files = [ - {file = "beautifulsoup4-4.12.2-py3-none-any.whl", hash = "sha256:bd2520ca0d9d7d12694a53d44ac482d181b4ec1888909b035a3dbf40d0f57d4a"}, - {file = "beautifulsoup4-4.12.2.tar.gz", hash = "sha256:492bbc69dca35d12daac71c4db1bfff0c876c00ef4a2ffacce226d4638eb72da"}, + {file = "beautifulsoup4-4.12.3-py3-none-any.whl", hash = "sha256:b80878c9f40111313e55da8ba20bdba06d8fa3969fc68304167741bbf9e082ed"}, + {file = "beautifulsoup4-4.12.3.tar.gz", hash = "sha256:74e3d1928edc070d21748185c46e3fb33490f22f52a3addee9aee0f4f7781051"}, ] [package.dependencies] soupsieve = ">1.2" [package.extras] +cchardet = ["cchardet"] +chardet = ["chardet"] +charset-normalizer = ["charset-normalizer"] html5lib = ["html5lib"] lxml = ["lxml"] @@ -393,13 +396,13 @@ six = ">=1.9.0" [[package]] name = "certifi" -version = "2023.11.17" +version = "2024.2.2" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" files = [ - {file = "certifi-2023.11.17-py3-none-any.whl", hash = "sha256:e036ab49d5b79556f99cfc2d9320b34cfbe5be05c5871b51de9329f0603b0474"}, - {file = "certifi-2023.11.17.tar.gz", hash = "sha256:9b469f3a900bf28dc19b8cfbf8019bf47f7fdd1a65a1d4ffb98fc14166beb4d1"}, + {file = "certifi-2024.2.2-py3-none-any.whl", hash = "sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1"}, + {file = "certifi-2024.2.2.tar.gz", hash = "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f"}, ] [[package]] @@ -513,104 +516,115 @@ test-randomorder = ["pytest-randomly"] [[package]] name = "cytoolz" -version = "0.12.2" +version = "0.12.3" description = "Cython implementation of Toolz: High performance functional utilities" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" files = [ - {file = "cytoolz-0.12.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4bff49986c9bae127928a2f9fd6313146a342bfae8292f63e562f872bd01b871"}, - {file = "cytoolz-0.12.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:908c13f305d34322e11b796de358edaeea47dd2d115c33ca22909c5e8fb036fd"}, - {file = "cytoolz-0.12.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:735147aa41b8eeb104da186864b55e2a6623c758000081d19c93d759cd9523e3"}, - {file = "cytoolz-0.12.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7d352d4de060604e605abdc5c8a5d0429d5f156cb9866609065d3003454d4cea"}, - {file = "cytoolz-0.12.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:89247ac220031a4f9f689688bcee42b38fd770d4cce294e5d914afc53b630abe"}, - {file = "cytoolz-0.12.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9070ae35c410d644e6df98a8f69f3ed2807e657d0df2a26b2643127cbf6944a5"}, - {file = "cytoolz-0.12.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:843500cd3e4884b92fd4037912bc42d5f047108d2c986d36352e880196d465b0"}, - {file = "cytoolz-0.12.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6a93644d7996fd696ab7f1f466cd75d718d0a00d5c8118b9fe8c64231dc1f85e"}, - {file = "cytoolz-0.12.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:96796594c770bc6587376e74ddc7d9c982d68f47116bb69d90873db5e0ea88b6"}, - {file = "cytoolz-0.12.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:48425107fbb1af3f0f2410c004f16be10ffc9374358e5600b57fa543f46f8def"}, - {file = "cytoolz-0.12.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:cde6dbb788a4cbc4a80a72aa96386ba4c2b17bdfff3ace0709799adbe16d6476"}, - {file = "cytoolz-0.12.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:68ae7091cc73a752f0b938f15bb193de80ca5edf5ae2ea6360d93d3e9228357b"}, - {file = "cytoolz-0.12.2-cp310-cp310-win32.whl", hash = "sha256:997b7e0960072f6bb445402da162f964ea67387b9f18bda2361edcc026e13597"}, - {file = "cytoolz-0.12.2-cp310-cp310-win_amd64.whl", hash = "sha256:663911786dcde3e4a5d88215c722c531c7548903dc07d418418c0d1c768072c0"}, - {file = "cytoolz-0.12.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4a7d8b869ded171f6cdf584fc2fc6ae03b30a0e1e37a9daf213a59857a62ed90"}, - {file = "cytoolz-0.12.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9b28787eaf2174e68f0acb3c66f9c6b98bdfeb0930c0d0b08e1941c7aedc8d27"}, - {file = "cytoolz-0.12.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:00547da587f124b32b072ce52dd5e4b37cf199fedcea902e33c67548523e4678"}, - {file = "cytoolz-0.12.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:275d53fd769df2102d6c9fc98e553bd8a9a38926f54d6b20cf29f0dd00bf3b75"}, - {file = "cytoolz-0.12.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5556acde785a61d4cf8b8534ae109b023cbd2f9df65ee2afbe070be47c410f8c"}, - {file = "cytoolz-0.12.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b41a85b9b9a2530b72b0d3d10e383fc3c2647ae88169d557d5e216f881860318"}, - {file = "cytoolz-0.12.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:673d6e9e3aa86949343b46ac2b7be266c36e07ce77fa1d40f349e6987a814d6e"}, - {file = "cytoolz-0.12.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:81e6a9a8fda78a2f4901d2915b25bf620f372997ca1f20a14f7cefef5ad6f6f4"}, - {file = "cytoolz-0.12.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:fa44215bc31675a6380cd896dadb7f2054a7b94cfb87e53e52af844c65406a54"}, - {file = "cytoolz-0.12.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:a08b4346350660799d81d4016e748bcb134a9083301d41f9618f64a6077f89f2"}, - {file = "cytoolz-0.12.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:2fb740482794a72e2e5fec58e4d9b00dcd5a60a8cef68431ff12f2ba0e0d9a7e"}, - {file = "cytoolz-0.12.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9007bb1290c79402be6b84bcf9e7a622a073859d61fcee146dc7bc47afe328f3"}, - {file = "cytoolz-0.12.2-cp311-cp311-win32.whl", hash = "sha256:a973f5286758f76824ecf19ae1999f6697371a9121c8f163295d181d19a819d7"}, - {file = "cytoolz-0.12.2-cp311-cp311-win_amd64.whl", hash = "sha256:1ce324d1b413636ea5ee929f79637821f13c9e55e9588f38228947294944d2ed"}, - {file = "cytoolz-0.12.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:c08094b9e5d1b6dfb0845a0253cc2655ca64ce70d15162dfdb102e28c8993493"}, - {file = "cytoolz-0.12.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:baf020f4b708f800b353259cd7575e335a79f1ac912d9dda55b2aa0bf3616e42"}, - {file = "cytoolz-0.12.2-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4416ee86a87180b6a28e7483102c92debc077bec59c67eda8cc63fc52a218ac0"}, - {file = "cytoolz-0.12.2-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c6ee222671eed5c5b16a5ad2aea07f0a715b8b199ee534834bc1dd2798f1ade7"}, - {file = "cytoolz-0.12.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad92e37be0b106fdbc575a3a669b43b364a5ef334495c9764de4c2d7541f7a99"}, - {file = "cytoolz-0.12.2-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:460c05238fbfe6d848141669d17a751a46c923f9f0c9fd8a3a462ab737623a44"}, - {file = "cytoolz-0.12.2-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:9e5075e30be626ef0f9bedf7a15f55ed4d7209e832bc314fdc232dbd61dcbf44"}, - {file = "cytoolz-0.12.2-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:03b58f843f09e73414e82e57f7e8d88f087eaabf8f276b866a40661161da6c51"}, - {file = "cytoolz-0.12.2-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:5e4e612b7ecc9596e7c859cd9e0cd085e6d0c576b4f0d917299595eb56bf9c05"}, - {file = "cytoolz-0.12.2-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:08a0e03f287e45eb694998bb55ac1643372199c659affa8319dfbbdec7f7fb3c"}, - {file = "cytoolz-0.12.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:b029bdd5a8b6c9a7c0e8fdbe4fc25ffaa2e09b77f6f3462314696e3a20511829"}, - {file = "cytoolz-0.12.2-cp36-cp36m-win32.whl", hash = "sha256:18580d060fa637ff01541640ecde6de832a248df02b8fb57e6dd578f189d62c7"}, - {file = "cytoolz-0.12.2-cp36-cp36m-win_amd64.whl", hash = "sha256:97cf514a9f3426228d8daf880f56488330e4b2948a6d183a106921217850d9eb"}, - {file = "cytoolz-0.12.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:18a0f838677f9510aef0330c0096778dd6406d21d4ff9504bf79d85235a18460"}, - {file = "cytoolz-0.12.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb081b2b02bf4405c804de1ece6f904916838ab0e057f1446e4ac12fac827960"}, - {file = "cytoolz-0.12.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:57233e1600560ceb719bed759dc78393edd541b9a3e7fefc3079abd83c26a6ea"}, - {file = "cytoolz-0.12.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0295289c4510efa41174850e75bc9188f82b72b1b54d0ea57d1781729c2924d5"}, - {file = "cytoolz-0.12.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a92aab8dd1d427ac9bc7480cfd3481dbab0ef024558f2f5a47de672d8a5ffaa"}, - {file = "cytoolz-0.12.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:51d3495235af09f21aa92a7cdd51504bda640b108b6be834448b774f52852c09"}, - {file = "cytoolz-0.12.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:f9c690b359f503f18bf1c46a6456370e4f6f3fc4320b8774ae69c4f85ecc6c94"}, - {file = "cytoolz-0.12.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:481e3129a76ea01adcc0e7097ccb8dbddab1cfc40b6f0e32c670153512957c0f"}, - {file = "cytoolz-0.12.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:55e94124af9c8fbb1df54195cc092688fdad0765641b738970b6f1d5ea72e776"}, - {file = "cytoolz-0.12.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:5616d386dfbfba7c39e9418ba668c734f6ceaacc0130877e8a100cad11e6838b"}, - {file = "cytoolz-0.12.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:732d08228fa8d366fec284f7032cc868d28a99fa81fc71e3adf7ecedbcf33a0f"}, - {file = "cytoolz-0.12.2-cp37-cp37m-win32.whl", hash = "sha256:f039c5373f7b314b151432c73219216857b19ab9cb834f0eb5d880f74fc7851c"}, - {file = "cytoolz-0.12.2-cp37-cp37m-win_amd64.whl", hash = "sha256:246368e983eaee9851b15d7755f82030eab4aa82098d2a34f6bef9c689d33fcc"}, - {file = "cytoolz-0.12.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:81074edf3c74bc9bd250d223408a5df0ff745d1f7a462597536cd26b9390e2d6"}, - {file = "cytoolz-0.12.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:960d85ebaa974ecea4e71fa56d098378fa51fd670ee744614cbb95bf95e28fc7"}, - {file = "cytoolz-0.12.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c8d0dff4865da54ae825d43e1721925721b19f3b9aca8e730c2ce73dee2c630"}, - {file = "cytoolz-0.12.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0a9d12436fd64937bd2c9609605f527af7f1a8db6e6637639b44121c0fe715d6"}, - {file = "cytoolz-0.12.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd461e402e24929d866f05061d2f8337e3a8456e75e21b72c125abff2477c7f7"}, - {file = "cytoolz-0.12.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0568d4da0a9ee9f9f5ab318f6501557f1cfe26d18c96c8e0dac7332ae04c6717"}, - {file = "cytoolz-0.12.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:101b5bd32badfc8b1f9c7be04ba3ae04fb47f9c8736590666ce9449bff76e0b1"}, - {file = "cytoolz-0.12.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8bb624dbaef4661f5e3625c1e39ad98ecceef281d1380e2774d8084ad0810275"}, - {file = "cytoolz-0.12.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:3e993804e6b04113d61fdb9541b6df2f096ec265a506dad7437517470919c90f"}, - {file = "cytoolz-0.12.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:ab911033e5937fc221a2c165acce7f66ae5ac9d3e54bec56f3c9c197a96be574"}, - {file = "cytoolz-0.12.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:e6de6a4bdfaee382c2de2a3580b3ae76fce6105da202bbd835e5efbeae6a9c6e"}, - {file = "cytoolz-0.12.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:9480b4b327be83c4d29cb88bcace761b11f5e30198ffe2287889455c6819e934"}, - {file = "cytoolz-0.12.2-cp38-cp38-win32.whl", hash = "sha256:4180b2785d1278e6abb36a72ac97c92432db53fa2df00ee943d2c15a33627d31"}, - {file = "cytoolz-0.12.2-cp38-cp38-win_amd64.whl", hash = "sha256:d0086ba8d41d73647b13087a3ca9c020f6bfec338335037e8f5172b4c7c8dce5"}, - {file = "cytoolz-0.12.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d29988bde28a90a00367edcf92afa1a2f7ecf43ea3ae383291b7da6d380ccc25"}, - {file = "cytoolz-0.12.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:24c0d71e9ac91f4466b1bd280f7de43aa4d94682daaf34d85d867a9b479b87cc"}, - {file = "cytoolz-0.12.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fa436abd4ac9ca71859baf5794614e6ec8fa27362f0162baedcc059048da55f7"}, - {file = "cytoolz-0.12.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:45c7b4eac7571707269ebc2893facdf87e359cd5c7cfbfa9e6bd8b33fb1079c5"}, - {file = "cytoolz-0.12.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:294d24edc747ef4e1b28e54365f713becb844e7898113fafbe3e9165dc44aeea"}, - {file = "cytoolz-0.12.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:478051e5ef8278b2429864c8d148efcebdc2be948a61c9a44757cd8c816c98f5"}, - {file = "cytoolz-0.12.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:14108cafb140dd68fdda610c2bbc6a37bf052cd48cfebf487ed44145f7a2b67f"}, - {file = "cytoolz-0.12.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5fef7b602ccf8a3c77ab483479ccd7a952a8c5bb1c263156671ba7aaa24d1035"}, - {file = "cytoolz-0.12.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9bf51354e15520715f068853e6ab8190e77139940e8b8b633bdb587956a08fb0"}, - {file = "cytoolz-0.12.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:388f840fd911d61a96e9e595eaf003f9dc39e847c9060b8e623ab29e556f009b"}, - {file = "cytoolz-0.12.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:a67f75cc51a2dc7229a8ac84291e4d61dc5abfc8940befcf37a2836d95873340"}, - {file = "cytoolz-0.12.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:63b31345e20afda2ae30dba246955517a4264464d75e071fc2fa641e88c763ec"}, - {file = "cytoolz-0.12.2-cp39-cp39-win32.whl", hash = "sha256:f6e86ac2b45a95f75c6f744147483e0fc9697ce7dfe1726083324c236f873f8b"}, - {file = "cytoolz-0.12.2-cp39-cp39-win_amd64.whl", hash = "sha256:5998f81bf6a2b28a802521efe14d9fc119f74b64e87b62ad1b0e7c3d8366d0c7"}, - {file = "cytoolz-0.12.2-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:593e89e2518eaf81e96edcc9ef2c5fca666e8fc922b03d5cb7a7b8964dbee336"}, - {file = "cytoolz-0.12.2-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ff451d614ca1d4227db0ffa627fb51df71968cf0d9baf0210528dad10fdbc3ab"}, - {file = "cytoolz-0.12.2-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ad9ea4a50d2948738351790047d45f2b1a023facc01bf0361988109b177e8b2f"}, - {file = "cytoolz-0.12.2-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cbe038bb78d599b5a29d09c438905defaa615a522bc7e12f8016823179439497"}, - {file = "cytoolz-0.12.2-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:d494befe648c13c98c0f3d56d05489c839c9228a32f58e9777305deb6c2c1cee"}, - {file = "cytoolz-0.12.2-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:c26805b6c8dc8565ed91045c44040bf6c0fe5cb5b390c78cd1d9400d08a6cd39"}, - {file = "cytoolz-0.12.2-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df4e32badb2ccf1773e1e74020b7e3b8caf9e92f842c6be7d14888ecdefc2c6c"}, - {file = "cytoolz-0.12.2-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ce7889dc3701826d519ede93cdff11940fb5567dbdc165dce0e78047eece02b7"}, - {file = "cytoolz-0.12.2-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c820608e7077416f766b148d75e158e454881961881b657cff808529d261dd24"}, - {file = "cytoolz-0.12.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:698da4fa1f7baeea0607738cb1f9877ed1ba50342b29891b0223221679d6f729"}, - {file = "cytoolz-0.12.2.tar.gz", hash = "sha256:31d4b0455d72d914645f803d917daf4f314d115c70de0578d3820deb8b101f66"}, + {file = "cytoolz-0.12.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bbe58e26c84b163beba0fbeacf6b065feabc8f75c6d3fe305550d33f24a2d346"}, + {file = "cytoolz-0.12.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c51b66ada9bfdb88cf711bf350fcc46f82b83a4683cf2413e633c31a64df6201"}, + {file = "cytoolz-0.12.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e70d9c615e5c9dc10d279d1e32e846085fe1fd6f08d623ddd059a92861f4e3dd"}, + {file = "cytoolz-0.12.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a83f4532707963ae1a5108e51fdfe1278cc8724e3301fee48b9e73e1316de64f"}, + {file = "cytoolz-0.12.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d028044524ee2e815f36210a793c414551b689d4f4eda28f8bbb0883ad78bf5f"}, + {file = "cytoolz-0.12.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c2875bcd1397d0627a09a4f9172fa513185ad302c63758efc15b8eb33cc2a98"}, + {file = "cytoolz-0.12.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:131ff4820e5d64a25d7ad3c3556f2d8aa65c66b3f021b03f8a8e98e4180dd808"}, + {file = "cytoolz-0.12.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:04afa90d9d9d18394c40d9bed48c51433d08b57c042e0e50c8c0f9799735dcbd"}, + {file = "cytoolz-0.12.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:dc1ca9c610425f9854323669a671fc163300b873731584e258975adf50931164"}, + {file = "cytoolz-0.12.3-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:bfa3f8e01bc423a933f2e1c510cbb0632c6787865b5242857cc955cae220d1bf"}, + {file = "cytoolz-0.12.3-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:f702e295dddef5f8af4a456db93f114539b8dc2a7a9bc4de7c7e41d169aa6ec3"}, + {file = "cytoolz-0.12.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0fbad1fb9bb47e827d00e01992a099b0ba79facf5e5aa453be066033232ac4b5"}, + {file = "cytoolz-0.12.3-cp310-cp310-win32.whl", hash = "sha256:8587c3c3dbe78af90c5025288766ac10dc2240c1e76eb0a93a4e244c265ccefd"}, + {file = "cytoolz-0.12.3-cp310-cp310-win_amd64.whl", hash = "sha256:9e45803d9e75ef90a2f859ef8f7f77614730f4a8ce1b9244375734567299d239"}, + {file = "cytoolz-0.12.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3ac4f2fb38bbc67ff1875b7d2f0f162a247f43bd28eb7c9d15e6175a982e558d"}, + {file = "cytoolz-0.12.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0cf1e1e96dd86829a0539baf514a9c8473a58fbb415f92401a68e8e52a34ecd5"}, + {file = "cytoolz-0.12.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08a438701c6141dd34eaf92e9e9a1f66e23a22f7840ef8a371eba274477de85d"}, + {file = "cytoolz-0.12.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c6b6f11b0d7ed91be53166aeef2a23a799e636625675bb30818f47f41ad31821"}, + {file = "cytoolz-0.12.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a7fde09384d23048a7b4ac889063761e44b89a0b64015393e2d1d21d5c1f534a"}, + {file = "cytoolz-0.12.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d3bfe45173cc8e6c76206be3a916d8bfd2214fb2965563e288088012f1dabfc"}, + {file = "cytoolz-0.12.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:27513a5d5b6624372d63313574381d3217a66e7a2626b056c695179623a5cb1a"}, + {file = "cytoolz-0.12.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d294e5e81ff094fe920fd545052ff30838ea49f9e91227a55ecd9f3ca19774a0"}, + {file = "cytoolz-0.12.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:727b01a2004ddb513496507a695e19b5c0cfebcdfcc68349d3efd92a1c297bf4"}, + {file = "cytoolz-0.12.3-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:fe1e1779a39dbe83f13886d2b4b02f8c4b10755e3c8d9a89b630395f49f4f406"}, + {file = "cytoolz-0.12.3-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:de74ef266e2679c3bf8b5fc20cee4fc0271ba13ae0d9097b1491c7a9bcadb389"}, + {file = "cytoolz-0.12.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9e04d22049233394e0b08193aca9737200b4a2afa28659d957327aa780ddddf2"}, + {file = "cytoolz-0.12.3-cp311-cp311-win32.whl", hash = "sha256:20d36430d8ac809186736fda735ee7d595b6242bdb35f69b598ef809ebfa5605"}, + {file = "cytoolz-0.12.3-cp311-cp311-win_amd64.whl", hash = "sha256:780c06110f383344d537f48d9010d79fa4f75070d214fc47f389357dd4f010b6"}, + {file = "cytoolz-0.12.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:86923d823bd19ce35805953b018d436f6b862edd6a7c8b747a13d52b39ed5716"}, + {file = "cytoolz-0.12.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a3e61acfd029bfb81c2c596249b508dfd2b4f72e31b7b53b62e5fb0507dd7293"}, + {file = "cytoolz-0.12.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd728f4e6051af6af234651df49319da1d813f47894d4c3c8ab7455e01703a37"}, + {file = "cytoolz-0.12.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fe8c6267caa7ec67bcc37e360f0d8a26bc3bdce510b15b97f2f2e0143bdd3673"}, + {file = "cytoolz-0.12.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:99462abd8323c52204a2a0ce62454ce8fa0f4e94b9af397945c12830de73f27e"}, + {file = "cytoolz-0.12.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da125221b1fa25c690fcd030a54344cecec80074df018d906fc6a99f46c1e3a6"}, + {file = "cytoolz-0.12.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1c18e351956f70db9e2d04ff02f28e9a41839250d3f936a4c8a1eabd1c3094d2"}, + {file = "cytoolz-0.12.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:921e6d2440ac758c4945c587b1d1d9b781b72737ac0c0ca5d5e02ca1db8bded2"}, + {file = "cytoolz-0.12.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:1651a9bd591a8326329ce1d6336f3129161a36d7061a4d5ea9e5377e033364cf"}, + {file = "cytoolz-0.12.3-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:8893223b87c2782bd59f9c4bd5c7bf733edd8728b523c93efb91d7468b486528"}, + {file = "cytoolz-0.12.3-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:e4d2961644153c5ae186db964aa9f6109da81b12df0f1d3494b4e5cf2c332ee2"}, + {file = "cytoolz-0.12.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:71b6eb97f6695f7ba8ce69c49b707a351c5f46fd97f5aeb5f6f2fb0d6e72b887"}, + {file = "cytoolz-0.12.3-cp312-cp312-win32.whl", hash = "sha256:cee3de65584e915053412cd178729ff510ad5f8f585c21c5890e91028283518f"}, + {file = "cytoolz-0.12.3-cp312-cp312-win_amd64.whl", hash = "sha256:9eef0d23035fa4dcfa21e570961e86c375153a7ee605cdd11a8b088c24f707f6"}, + {file = "cytoolz-0.12.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d9a38332cfad2a91e89405b7c18b3f00e2edc951c225accbc217597d3e4e9fde"}, + {file = "cytoolz-0.12.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f501ae1353071fa5d6677437bbeb1aeb5622067dce0977cedc2c5ec5843b202"}, + {file = "cytoolz-0.12.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:56f899758146a52e2f8cfb3fb6f4ca19c1e5814178c3d584de35f9e4d7166d91"}, + {file = "cytoolz-0.12.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:800f0526adf9e53d3c6acda748f4def1f048adaa780752f154da5cf22aa488a2"}, + {file = "cytoolz-0.12.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d0976a3fcb81d065473173e9005848218ce03ddb2ec7d40dd6a8d2dba7f1c3ae"}, + {file = "cytoolz-0.12.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c835eab01466cb67d0ce6290601ebef2d82d8d0d0a285ed0d6e46989e4a7a71a"}, + {file = "cytoolz-0.12.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:4fba0616fcd487e34b8beec1ad9911d192c62e758baa12fcb44448b9b6feae22"}, + {file = "cytoolz-0.12.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:6f6e8207d732651e0204779e1ba5a4925c93081834570411f959b80681f8d333"}, + {file = "cytoolz-0.12.3-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:8119bf5961091cfe644784d0bae214e273b3b3a479f93ee3baab97bbd995ccfe"}, + {file = "cytoolz-0.12.3-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:7ad1331cb68afeec58469c31d944a2100cee14eac221553f0d5218ace1a0b25d"}, + {file = "cytoolz-0.12.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:92c53d508fb8a4463acc85b322fa24734efdc66933a5c8661bdc862103a3373d"}, + {file = "cytoolz-0.12.3-cp37-cp37m-win32.whl", hash = "sha256:2c6dd75dae3d84fa8988861ab8b1189d2488cb8a9b8653828f9cd6126b5e7abd"}, + {file = "cytoolz-0.12.3-cp37-cp37m-win_amd64.whl", hash = "sha256:caf07a97b5220e6334dd32c8b6d8b2bd255ca694eca5dfe914bb5b880ee66cdb"}, + {file = "cytoolz-0.12.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ed0cfb9326747759e2ad81cb6e45f20086a273b67ac3a4c00b19efcbab007c60"}, + {file = "cytoolz-0.12.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:96a5a0292575c3697121f97cc605baf2fd125120c7dcdf39edd1a135798482ca"}, + {file = "cytoolz-0.12.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b76f2f50a789c44d6fd7f773ec43d2a8686781cd52236da03f7f7d7998989bee"}, + {file = "cytoolz-0.12.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2905fdccacc64b4beba37f95cab9d792289c80f4d70830b70de2fc66c007ec01"}, + {file = "cytoolz-0.12.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f1ebe23028eac51251f22ba01dba6587d30aa9c320372ca0c14eeab67118ec3f"}, + {file = "cytoolz-0.12.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96c715404a3825e37fe3966fe84c5f8a1f036e7640b2a02dbed96cac0c933451"}, + {file = "cytoolz-0.12.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bac0adffc1b6b6a4c5f1fd1dd2161afb720bcc771a91016dc6bdba59af0a5d3"}, + {file = "cytoolz-0.12.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:37441bf4a2a4e2e0fe9c3b0ea5e72db352f5cca03903977ffc42f6f6c5467be9"}, + {file = "cytoolz-0.12.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:f04037302049cb30033f7fa4e1d0e44afe35ed6bfcf9b380fc11f2a27d3ed697"}, + {file = "cytoolz-0.12.3-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:f37b60e66378e7a116931d7220f5352186abfcc950d64856038aa2c01944929c"}, + {file = "cytoolz-0.12.3-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:ec9be3e4b6f86ea8b294d34c990c99d2ba6c526ef1e8f46f1d52c263d4f32cd7"}, + {file = "cytoolz-0.12.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:0e9199c9e3fbf380a92b8042c677eb9e7ed4bccb126de5e9c0d26f5888d96788"}, + {file = "cytoolz-0.12.3-cp38-cp38-win32.whl", hash = "sha256:18cd61e078bd6bffe088e40f1ed02001387c29174750abce79499d26fa57f5eb"}, + {file = "cytoolz-0.12.3-cp38-cp38-win_amd64.whl", hash = "sha256:765b8381d4003ceb1a07896a854eee2c31ebc950a4ae17d1e7a17c2a8feb2a68"}, + {file = "cytoolz-0.12.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b4a52dd2a36b0a91f7aa50ca6c8509057acc481a24255f6cb07b15d339a34e0f"}, + {file = "cytoolz-0.12.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:581f1ce479769fe7eeb9ae6d87eadb230df8c7c5fff32138162cdd99d7fb8fc3"}, + {file = "cytoolz-0.12.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:46f505d4c6eb79585c8ad0b9dc140ef30a138c880e4e3b40230d642690e36366"}, + {file = "cytoolz-0.12.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59276021619b432a5c21c01cda8320b9cc7dbc40351ffc478b440bfccd5bbdd3"}, + {file = "cytoolz-0.12.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e44f4c25e1e7cf6149b499c74945a14649c8866d36371a2c2d2164e4649e7755"}, + {file = "cytoolz-0.12.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c64f8e60c1dd69e4d5e615481f2d57937746f4a6be2d0f86e9e7e3b9e2243b5e"}, + {file = "cytoolz-0.12.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:33c63186f3bf9d7ef1347bc0537bb9a0b4111a0d7d6e619623cabc18fef0dc3b"}, + {file = "cytoolz-0.12.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:fdddb9d988405f24035234f1e8d1653ab2e48cc2404226d21b49a129aefd1d25"}, + {file = "cytoolz-0.12.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6986632d8a969ea1e720990c818dace1a24c11015fd7c59b9fea0b65ef71f726"}, + {file = "cytoolz-0.12.3-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:0ba1cbc4d9cd7571c917f88f4a069568e5121646eb5d82b2393b2cf84712cf2a"}, + {file = "cytoolz-0.12.3-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:7d267ffc9a36c0a9a58c7e0adc9fa82620f22e4a72533e15dd1361f57fc9accf"}, + {file = "cytoolz-0.12.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:95e878868a172a41fbf6c505a4b967309e6870e22adc7b1c3b19653d062711fa"}, + {file = "cytoolz-0.12.3-cp39-cp39-win32.whl", hash = "sha256:8e21932d6d260996f7109f2a40b2586070cb0a0cf1d65781e156326d5ebcc329"}, + {file = "cytoolz-0.12.3-cp39-cp39-win_amd64.whl", hash = "sha256:0d8edfbc694af6c9bda4db56643fb8ed3d14e47bec358c2f1417de9a12d6d1fb"}, + {file = "cytoolz-0.12.3-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:55f9bd1ae6c2a27eda5abe2a0b65a83029d2385c5a1da7b8ef47af5905d7e905"}, + {file = "cytoolz-0.12.3-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d2d271393c378282727f1231d40391ae93b93ddc0997448acc21dd0cb6a1e56d"}, + {file = "cytoolz-0.12.3-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee98968d6a66ee83a8ceabf31182189ab5d8598998c8ce69b6d5843daeb2db60"}, + {file = "cytoolz-0.12.3-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:01cfb8518828c1189200c02a5010ea404407fb18fd5589e29c126e84bbeadd36"}, + {file = "cytoolz-0.12.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:456395d7aec01db32bf9e6db191d667347c78d8d48e77234521fa1078f60dabb"}, + {file = "cytoolz-0.12.3-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:cd88028bb897fba99ddd84f253ca6bef73ecb7bdf3f3cf25bc493f8f97d3c7c5"}, + {file = "cytoolz-0.12.3-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59b19223e7f7bd7a73ec3aa6fdfb73b579ff09c2bc0b7d26857eec2d01a58c76"}, + {file = "cytoolz-0.12.3-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a79d72b08048a0980a59457c239555f111ac0c8bdc140c91a025f124104dbb4"}, + {file = "cytoolz-0.12.3-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1dd70141b32b717696a72b8876e86bc9c6f8eff995c1808e299db3541213ff82"}, + {file = "cytoolz-0.12.3-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:a1445c91009eb775d479e88954c51d0b4cf9a1e8ce3c503c2672d17252882647"}, + {file = "cytoolz-0.12.3-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ca6a9a9300d5bda417d9090107c6d2b007683efc59d63cc09aca0e7930a08a85"}, + {file = "cytoolz-0.12.3-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be6feb903d2a08a4ba2e70e950e862fd3be9be9a588b7c38cee4728150a52918"}, + {file = "cytoolz-0.12.3-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:92b6f43f086e5a965d33d62a145ae121b4ccb6e0789ac0acc895ce084fec8c65"}, + {file = "cytoolz-0.12.3-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:534fa66db8564d9b13872d81d54b6b09ae592c585eb826aac235bd6f1830f8ad"}, + {file = "cytoolz-0.12.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:fea649f979def23150680de1bd1d09682da3b54932800a0f90f29fc2a6c98ba8"}, + {file = "cytoolz-0.12.3-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a447247ed312dd64e3a8d9483841ecc5338ee26d6e6fbd29cd373ed030db0240"}, + {file = "cytoolz-0.12.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba3f843aa89f35467b38c398ae5b980a824fdbdb94065adc6ec7c47a0a22f4c7"}, + {file = "cytoolz-0.12.3-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:582c22f97a380211fb36a7b65b1beeb84ea11d82015fa84b054be78580390082"}, + {file = "cytoolz-0.12.3-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47feb089506fc66e1593cd9ade3945693a9d089a445fbe9a11385cab200b9f22"}, + {file = "cytoolz-0.12.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:ba9002d2f043943744a9dc8e50a47362bcb6e6f360dc0a1abcb19642584d87bb"}, + {file = "cytoolz-0.12.3.tar.gz", hash = "sha256:4503dc59f4ced53a54643272c61dc305d1dbbfbd7d6bdf296948de9f34c3a282"}, ] [package.dependencies] @@ -879,18 +893,18 @@ files = [ [[package]] name = "importlib-resources" -version = "6.1.1" +version = "6.4.0" description = "Read resources from Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "importlib_resources-6.1.1-py3-none-any.whl", hash = "sha256:e8bf90d8213b486f428c9c39714b920041cb02c184686a3dee24905aaa8105d6"}, - {file = "importlib_resources-6.1.1.tar.gz", hash = "sha256:3893a00122eafde6894c59914446a512f728a0c1a45f9bb9b63721b6bacf0b4a"}, + {file = "importlib_resources-6.4.0-py3-none-any.whl", hash = "sha256:50d10f043df931902d4194ea07ec57960f66a80449ff867bfe782b4c486ba78c"}, + {file = "importlib_resources-6.4.0.tar.gz", hash = "sha256:cdb2b453b8046ca4e3798eb1d84f3cce1446a0e8e7b5ef4efb600f19fc398145"}, ] [package.extras] docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] -testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-ruff", "zipp (>=3.17)"] +testing = ["jaraco.test (>=5.4)", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-ruff (>=0.2.1)", "zipp (>=3.17)"] [[package]] name = "iso8601" @@ -922,19 +936,19 @@ i18n = ["Babel (>=2.7)"] [[package]] name = "limits" -version = "3.7.0" +version = "3.10.1" description = "Rate limiting utilities" optional = false python-versions = ">=3.7" files = [ - {file = "limits-3.7.0-py3-none-any.whl", hash = "sha256:c528817b7fc15f3e86ad091ba3e40231f6430a91b753db864767684cda8a7f2e"}, - {file = "limits-3.7.0.tar.gz", hash = "sha256:124c6a04d2f4b20990fb1de019eec9474d6c1346c70d8fd0561609b86998b64a"}, + {file = "limits-3.10.1-py3-none-any.whl", hash = "sha256:446242f5a6f7b8c7744e286a70793264ed81bca97860f94b821347284d14fbe9"}, + {file = "limits-3.10.1.tar.gz", hash = "sha256:1ee31d169d498da267a1b72183ae5940afc64b17b4ed4dfd977f6ea5607c2cfb"}, ] [package.dependencies] deprecated = ">=1.2" importlib-resources = ">=1.3" -packaging = ">=21,<24" +packaging = ">=21,<25" typing-extensions = "*" [package.extras] @@ -951,283 +965,377 @@ rediscluster = ["redis (>=4.2.0,!=4.5.2,!=4.5.3)"] [[package]] name = "lxml" -version = "5.1.0" +version = "5.2.1" description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API." optional = false python-versions = ">=3.6" files = [ - {file = "lxml-5.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:704f5572ff473a5f897745abebc6df40f22d4133c1e0a1f124e4f2bd3330ff7e"}, - {file = "lxml-5.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9d3c0f8567ffe7502d969c2c1b809892dc793b5d0665f602aad19895f8d508da"}, - {file = "lxml-5.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5fcfbebdb0c5d8d18b84118842f31965d59ee3e66996ac842e21f957eb76138c"}, - {file = "lxml-5.1.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2f37c6d7106a9d6f0708d4e164b707037b7380fcd0b04c5bd9cae1fb46a856fb"}, - {file = "lxml-5.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2befa20a13f1a75c751f47e00929fb3433d67eb9923c2c0b364de449121f447c"}, - {file = "lxml-5.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22b7ee4c35f374e2c20337a95502057964d7e35b996b1c667b5c65c567d2252a"}, - {file = "lxml-5.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:bf8443781533b8d37b295016a4b53c1494fa9a03573c09ca5104550c138d5c05"}, - {file = "lxml-5.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:82bddf0e72cb2af3cbba7cec1d2fd11fda0de6be8f4492223d4a268713ef2147"}, - {file = "lxml-5.1.0-cp310-cp310-win32.whl", hash = "sha256:b66aa6357b265670bb574f050ffceefb98549c721cf28351b748be1ef9577d93"}, - {file = "lxml-5.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:4946e7f59b7b6a9e27bef34422f645e9a368cb2be11bf1ef3cafc39a1f6ba68d"}, - {file = "lxml-5.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:14deca1460b4b0f6b01f1ddc9557704e8b365f55c63070463f6c18619ebf964f"}, - {file = "lxml-5.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ed8c3d2cd329bf779b7ed38db176738f3f8be637bb395ce9629fc76f78afe3d4"}, - {file = "lxml-5.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:436a943c2900bb98123b06437cdd30580a61340fbdb7b28aaf345a459c19046a"}, - {file = "lxml-5.1.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:acb6b2f96f60f70e7f34efe0c3ea34ca63f19ca63ce90019c6cbca6b676e81fa"}, - {file = "lxml-5.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:af8920ce4a55ff41167ddbc20077f5698c2e710ad3353d32a07d3264f3a2021e"}, - {file = "lxml-5.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7cfced4a069003d8913408e10ca8ed092c49a7f6cefee9bb74b6b3e860683b45"}, - {file = "lxml-5.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:9e5ac3437746189a9b4121db2a7b86056ac8786b12e88838696899328fc44bb2"}, - {file = "lxml-5.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f4c9bda132ad108b387c33fabfea47866af87f4ea6ffb79418004f0521e63204"}, - {file = "lxml-5.1.0-cp311-cp311-win32.whl", hash = "sha256:bc64d1b1dab08f679fb89c368f4c05693f58a9faf744c4d390d7ed1d8223869b"}, - {file = "lxml-5.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:a5ab722ae5a873d8dcee1f5f45ddd93c34210aed44ff2dc643b5025981908cda"}, - {file = "lxml-5.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:9aa543980ab1fbf1720969af1d99095a548ea42e00361e727c58a40832439114"}, - {file = "lxml-5.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6f11b77ec0979f7e4dc5ae081325a2946f1fe424148d3945f943ceaede98adb8"}, - {file = "lxml-5.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a36c506e5f8aeb40680491d39ed94670487ce6614b9d27cabe45d94cd5d63e1e"}, - {file = "lxml-5.1.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f643ffd2669ffd4b5a3e9b41c909b72b2a1d5e4915da90a77e119b8d48ce867a"}, - {file = "lxml-5.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:16dd953fb719f0ffc5bc067428fc9e88f599e15723a85618c45847c96f11f431"}, - {file = "lxml-5.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:16018f7099245157564d7148165132c70adb272fb5a17c048ba70d9cc542a1a1"}, - {file = "lxml-5.1.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:82cd34f1081ae4ea2ede3d52f71b7be313756e99b4b5f829f89b12da552d3aa3"}, - {file = "lxml-5.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:19a1bc898ae9f06bccb7c3e1dfd73897ecbbd2c96afe9095a6026016e5ca97b8"}, - {file = "lxml-5.1.0-cp312-cp312-win32.whl", hash = "sha256:13521a321a25c641b9ea127ef478b580b5ec82aa2e9fc076c86169d161798b01"}, - {file = "lxml-5.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:1ad17c20e3666c035db502c78b86e58ff6b5991906e55bdbef94977700c72623"}, - {file = "lxml-5.1.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:24ef5a4631c0b6cceaf2dbca21687e29725b7c4e171f33a8f8ce23c12558ded1"}, - {file = "lxml-5.1.0-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8d2900b7f5318bc7ad8631d3d40190b95ef2aa8cc59473b73b294e4a55e9f30f"}, - {file = "lxml-5.1.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:601f4a75797d7a770daed8b42b97cd1bb1ba18bd51a9382077a6a247a12aa38d"}, - {file = "lxml-5.1.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b4b68c961b5cc402cbd99cca5eb2547e46ce77260eb705f4d117fd9c3f932b95"}, - {file = "lxml-5.1.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:afd825e30f8d1f521713a5669b63657bcfe5980a916c95855060048b88e1adb7"}, - {file = "lxml-5.1.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:262bc5f512a66b527d026518507e78c2f9c2bd9eb5c8aeeb9f0eb43fcb69dc67"}, - {file = "lxml-5.1.0-cp36-cp36m-win32.whl", hash = "sha256:e856c1c7255c739434489ec9c8aa9cdf5179785d10ff20add308b5d673bed5cd"}, - {file = "lxml-5.1.0-cp36-cp36m-win_amd64.whl", hash = "sha256:c7257171bb8d4432fe9d6fdde4d55fdbe663a63636a17f7f9aaba9bcb3153ad7"}, - {file = "lxml-5.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b9e240ae0ba96477682aa87899d94ddec1cc7926f9df29b1dd57b39e797d5ab5"}, - {file = "lxml-5.1.0-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a96f02ba1bcd330807fc060ed91d1f7a20853da6dd449e5da4b09bfcc08fdcf5"}, - {file = "lxml-5.1.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e3898ae2b58eeafedfe99e542a17859017d72d7f6a63de0f04f99c2cb125936"}, - {file = "lxml-5.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61c5a7edbd7c695e54fca029ceb351fc45cd8860119a0f83e48be44e1c464862"}, - {file = "lxml-5.1.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:3aeca824b38ca78d9ee2ab82bd9883083d0492d9d17df065ba3b94e88e4d7ee6"}, - {file = "lxml-5.1.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8f52fe6859b9db71ee609b0c0a70fea5f1e71c3462ecf144ca800d3f434f0764"}, - {file = "lxml-5.1.0-cp37-cp37m-win32.whl", hash = "sha256:d42e3a3fc18acc88b838efded0e6ec3edf3e328a58c68fbd36a7263a874906c8"}, - {file = "lxml-5.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:eac68f96539b32fce2c9b47eb7c25bb2582bdaf1bbb360d25f564ee9e04c542b"}, - {file = "lxml-5.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ae15347a88cf8af0949a9872b57a320d2605ae069bcdf047677318bc0bba45b1"}, - {file = "lxml-5.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c26aab6ea9c54d3bed716b8851c8bfc40cb249b8e9880e250d1eddde9f709bf5"}, - {file = "lxml-5.1.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:342e95bddec3a698ac24378d61996b3ee5ba9acfeb253986002ac53c9a5f6f84"}, - {file = "lxml-5.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:725e171e0b99a66ec8605ac77fa12239dbe061482ac854d25720e2294652eeaa"}, - {file = "lxml-5.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d184e0d5c918cff04cdde9dbdf9600e960161d773666958c9d7b565ccc60c45"}, - {file = "lxml-5.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:98f3f020a2b736566c707c8e034945c02aa94e124c24f77ca097c446f81b01f1"}, - {file = "lxml-5.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6d48fc57e7c1e3df57be5ae8614bab6d4e7b60f65c5457915c26892c41afc59e"}, - {file = "lxml-5.1.0-cp38-cp38-win32.whl", hash = "sha256:7ec465e6549ed97e9f1e5ed51c657c9ede767bc1c11552f7f4d022c4df4a977a"}, - {file = "lxml-5.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:b21b4031b53d25b0858d4e124f2f9131ffc1530431c6d1321805c90da78388d1"}, - {file = "lxml-5.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:52427a7eadc98f9e62cb1368a5079ae826f94f05755d2d567d93ee1bc3ceb354"}, - {file = "lxml-5.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6a2a2c724d97c1eb8cf966b16ca2915566a4904b9aad2ed9a09c748ffe14f969"}, - {file = "lxml-5.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:843b9c835580d52828d8f69ea4302537337a21e6b4f1ec711a52241ba4a824f3"}, - {file = "lxml-5.1.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9b99f564659cfa704a2dd82d0684207b1aadf7d02d33e54845f9fc78e06b7581"}, - {file = "lxml-5.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f8b0c78e7aac24979ef09b7f50da871c2de2def043d468c4b41f512d831e912"}, - {file = "lxml-5.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9bcf86dfc8ff3e992fed847c077bd875d9e0ba2fa25d859c3a0f0f76f07f0c8d"}, - {file = "lxml-5.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:49a9b4af45e8b925e1cd6f3b15bbba2c81e7dba6dce170c677c9cda547411e14"}, - {file = "lxml-5.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:280f3edf15c2a967d923bcfb1f8f15337ad36f93525828b40a0f9d6c2ad24890"}, - {file = "lxml-5.1.0-cp39-cp39-win32.whl", hash = "sha256:ed7326563024b6e91fef6b6c7a1a2ff0a71b97793ac33dbbcf38f6005e51ff6e"}, - {file = "lxml-5.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:8d7b4beebb178e9183138f552238f7e6613162a42164233e2bda00cb3afac58f"}, - {file = "lxml-5.1.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:9bd0ae7cc2b85320abd5e0abad5ccee5564ed5f0cc90245d2f9a8ef330a8deae"}, - {file = "lxml-5.1.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8c1d679df4361408b628f42b26a5d62bd3e9ba7f0c0e7969f925021554755aa"}, - {file = "lxml-5.1.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:2ad3a8ce9e8a767131061a22cd28fdffa3cd2dc193f399ff7b81777f3520e372"}, - {file = "lxml-5.1.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:304128394c9c22b6569eba2a6d98392b56fbdfbad58f83ea702530be80d0f9df"}, - {file = "lxml-5.1.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d74fcaf87132ffc0447b3c685a9f862ffb5b43e70ea6beec2fb8057d5d2a1fea"}, - {file = "lxml-5.1.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:8cf5877f7ed384dabfdcc37922c3191bf27e55b498fecece9fd5c2c7aaa34c33"}, - {file = "lxml-5.1.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:877efb968c3d7eb2dad540b6cabf2f1d3c0fbf4b2d309a3c141f79c7e0061324"}, - {file = "lxml-5.1.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f14a4fb1c1c402a22e6a341a24c1341b4a3def81b41cd354386dcb795f83897"}, - {file = "lxml-5.1.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:25663d6e99659544ee8fe1b89b1a8c0aaa5e34b103fab124b17fa958c4a324a6"}, - {file = "lxml-5.1.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:8b9f19df998761babaa7f09e6bc169294eefafd6149aaa272081cbddc7ba4ca3"}, - {file = "lxml-5.1.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e53d7e6a98b64fe54775d23a7c669763451340c3d44ad5e3a3b48a1efbdc96f"}, - {file = "lxml-5.1.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:c3cd1fc1dc7c376c54440aeaaa0dcc803d2126732ff5c6b68ccd619f2e64be4f"}, - {file = "lxml-5.1.0.tar.gz", hash = "sha256:3eea6ed6e6c918e468e693c41ef07f3c3acc310b70ddd9cc72d9ef84bc9564ca"}, + {file = "lxml-5.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1f7785f4f789fdb522729ae465adcaa099e2a3441519df750ebdccc481d961a1"}, + {file = "lxml-5.2.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6cc6ee342fb7fa2471bd9b6d6fdfc78925a697bf5c2bcd0a302e98b0d35bfad3"}, + {file = "lxml-5.2.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:794f04eec78f1d0e35d9e0c36cbbb22e42d370dda1609fb03bcd7aeb458c6377"}, + {file = "lxml-5.2.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c817d420c60a5183953c783b0547d9eb43b7b344a2c46f69513d5952a78cddf3"}, + {file = "lxml-5.2.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2213afee476546a7f37c7a9b4ad4d74b1e112a6fafffc9185d6d21f043128c81"}, + {file = "lxml-5.2.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b070bbe8d3f0f6147689bed981d19bbb33070225373338df755a46893528104a"}, + {file = "lxml-5.2.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e02c5175f63effbd7c5e590399c118d5db6183bbfe8e0d118bdb5c2d1b48d937"}, + {file = "lxml-5.2.1-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:3dc773b2861b37b41a6136e0b72a1a44689a9c4c101e0cddb6b854016acc0aa8"}, + {file = "lxml-5.2.1-cp310-cp310-manylinux_2_28_ppc64le.whl", hash = "sha256:d7520db34088c96cc0e0a3ad51a4fd5b401f279ee112aa2b7f8f976d8582606d"}, + {file = "lxml-5.2.1-cp310-cp310-manylinux_2_28_s390x.whl", hash = "sha256:bcbf4af004f98793a95355980764b3d80d47117678118a44a80b721c9913436a"}, + {file = "lxml-5.2.1-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:a2b44bec7adf3e9305ce6cbfa47a4395667e744097faed97abb4728748ba7d47"}, + {file = "lxml-5.2.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:1c5bb205e9212d0ebddf946bc07e73fa245c864a5f90f341d11ce7b0b854475d"}, + {file = "lxml-5.2.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2c9d147f754b1b0e723e6afb7ba1566ecb162fe4ea657f53d2139bbf894d050a"}, + {file = "lxml-5.2.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:3545039fa4779be2df51d6395e91a810f57122290864918b172d5dc7ca5bb433"}, + {file = "lxml-5.2.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a91481dbcddf1736c98a80b122afa0f7296eeb80b72344d7f45dc9f781551f56"}, + {file = "lxml-5.2.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:2ddfe41ddc81f29a4c44c8ce239eda5ade4e7fc305fb7311759dd6229a080052"}, + {file = "lxml-5.2.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:a7baf9ffc238e4bf401299f50e971a45bfcc10a785522541a6e3179c83eabf0a"}, + {file = "lxml-5.2.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:31e9a882013c2f6bd2f2c974241bf4ba68c85eba943648ce88936d23209a2e01"}, + {file = "lxml-5.2.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:0a15438253b34e6362b2dc41475e7f80de76320f335e70c5528b7148cac253a1"}, + {file = "lxml-5.2.1-cp310-cp310-win32.whl", hash = "sha256:6992030d43b916407c9aa52e9673612ff39a575523c5f4cf72cdef75365709a5"}, + {file = "lxml-5.2.1-cp310-cp310-win_amd64.whl", hash = "sha256:da052e7962ea2d5e5ef5bc0355d55007407087392cf465b7ad84ce5f3e25fe0f"}, + {file = "lxml-5.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:70ac664a48aa64e5e635ae5566f5227f2ab7f66a3990d67566d9907edcbbf867"}, + {file = "lxml-5.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1ae67b4e737cddc96c99461d2f75d218bdf7a0c3d3ad5604d1f5e7464a2f9ffe"}, + {file = "lxml-5.2.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f18a5a84e16886898e51ab4b1d43acb3083c39b14c8caeb3589aabff0ee0b270"}, + {file = "lxml-5.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c6f2c8372b98208ce609c9e1d707f6918cc118fea4e2c754c9f0812c04ca116d"}, + {file = "lxml-5.2.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:394ed3924d7a01b5bd9a0d9d946136e1c2f7b3dc337196d99e61740ed4bc6fe1"}, + {file = "lxml-5.2.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5d077bc40a1fe984e1a9931e801e42959a1e6598edc8a3223b061d30fbd26bbc"}, + {file = "lxml-5.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:764b521b75701f60683500d8621841bec41a65eb739b8466000c6fdbc256c240"}, + {file = "lxml-5.2.1-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:3a6b45da02336895da82b9d472cd274b22dc27a5cea1d4b793874eead23dd14f"}, + {file = "lxml-5.2.1-cp311-cp311-manylinux_2_28_ppc64le.whl", hash = "sha256:5ea7b6766ac2dfe4bcac8b8595107665a18ef01f8c8343f00710b85096d1b53a"}, + {file = "lxml-5.2.1-cp311-cp311-manylinux_2_28_s390x.whl", hash = "sha256:e196a4ff48310ba62e53a8e0f97ca2bca83cdd2fe2934d8b5cb0df0a841b193a"}, + {file = "lxml-5.2.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:200e63525948e325d6a13a76ba2911f927ad399ef64f57898cf7c74e69b71095"}, + {file = "lxml-5.2.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:dae0ed02f6b075426accbf6b2863c3d0a7eacc1b41fb40f2251d931e50188dad"}, + {file = "lxml-5.2.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:ab31a88a651039a07a3ae327d68ebdd8bc589b16938c09ef3f32a4b809dc96ef"}, + {file = "lxml-5.2.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:df2e6f546c4df14bc81f9498bbc007fbb87669f1bb707c6138878c46b06f6510"}, + {file = "lxml-5.2.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5dd1537e7cc06efd81371f5d1a992bd5ab156b2b4f88834ca852de4a8ea523fa"}, + {file = "lxml-5.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9b9ec9c9978b708d488bec36b9e4c94d88fd12ccac3e62134a9d17ddba910ea9"}, + {file = "lxml-5.2.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:8e77c69d5892cb5ba71703c4057091e31ccf534bd7f129307a4d084d90d014b8"}, + {file = "lxml-5.2.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:a8d5c70e04aac1eda5c829a26d1f75c6e5286c74743133d9f742cda8e53b9c2f"}, + {file = "lxml-5.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c94e75445b00319c1fad60f3c98b09cd63fe1134a8a953dcd48989ef42318534"}, + {file = "lxml-5.2.1-cp311-cp311-win32.whl", hash = "sha256:4951e4f7a5680a2db62f7f4ab2f84617674d36d2d76a729b9a8be4b59b3659be"}, + {file = "lxml-5.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:5c670c0406bdc845b474b680b9a5456c561c65cf366f8db5a60154088c92d102"}, + {file = "lxml-5.2.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:abc25c3cab9ec7fcd299b9bcb3b8d4a1231877e425c650fa1c7576c5107ab851"}, + {file = "lxml-5.2.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6935bbf153f9a965f1e07c2649c0849d29832487c52bb4a5c5066031d8b44fd5"}, + {file = "lxml-5.2.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d793bebb202a6000390a5390078e945bbb49855c29c7e4d56a85901326c3b5d9"}, + {file = "lxml-5.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afd5562927cdef7c4f5550374acbc117fd4ecc05b5007bdfa57cc5355864e0a4"}, + {file = "lxml-5.2.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0e7259016bc4345a31af861fdce942b77c99049d6c2107ca07dc2bba2435c1d9"}, + {file = "lxml-5.2.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:530e7c04f72002d2f334d5257c8a51bf409db0316feee7c87e4385043be136af"}, + {file = "lxml-5.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59689a75ba8d7ffca577aefd017d08d659d86ad4585ccc73e43edbfc7476781a"}, + {file = "lxml-5.2.1-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:f9737bf36262046213a28e789cc82d82c6ef19c85a0cf05e75c670a33342ac2c"}, + {file = "lxml-5.2.1-cp312-cp312-manylinux_2_28_ppc64le.whl", hash = "sha256:3a74c4f27167cb95c1d4af1c0b59e88b7f3e0182138db2501c353555f7ec57f4"}, + {file = "lxml-5.2.1-cp312-cp312-manylinux_2_28_s390x.whl", hash = "sha256:68a2610dbe138fa8c5826b3f6d98a7cfc29707b850ddcc3e21910a6fe51f6ca0"}, + {file = "lxml-5.2.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:f0a1bc63a465b6d72569a9bba9f2ef0334c4e03958e043da1920299100bc7c08"}, + {file = "lxml-5.2.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c2d35a1d047efd68027817b32ab1586c1169e60ca02c65d428ae815b593e65d4"}, + {file = "lxml-5.2.1-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:79bd05260359170f78b181b59ce871673ed01ba048deef4bf49a36ab3e72e80b"}, + {file = "lxml-5.2.1-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:865bad62df277c04beed9478fe665b9ef63eb28fe026d5dedcb89b537d2e2ea6"}, + {file = "lxml-5.2.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:44f6c7caff88d988db017b9b0e4ab04934f11e3e72d478031efc7edcac6c622f"}, + {file = "lxml-5.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:71e97313406ccf55d32cc98a533ee05c61e15d11b99215b237346171c179c0b0"}, + {file = "lxml-5.2.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:057cdc6b86ab732cf361f8b4d8af87cf195a1f6dc5b0ff3de2dced242c2015e0"}, + {file = "lxml-5.2.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:f3bbbc998d42f8e561f347e798b85513ba4da324c2b3f9b7969e9c45b10f6169"}, + {file = "lxml-5.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:491755202eb21a5e350dae00c6d9a17247769c64dcf62d8c788b5c135e179dc4"}, + {file = "lxml-5.2.1-cp312-cp312-win32.whl", hash = "sha256:8de8f9d6caa7f25b204fc861718815d41cbcf27ee8f028c89c882a0cf4ae4134"}, + {file = "lxml-5.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:f2a9efc53d5b714b8df2b4b3e992accf8ce5bbdfe544d74d5c6766c9e1146a3a"}, + {file = "lxml-5.2.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:70a9768e1b9d79edca17890175ba915654ee1725975d69ab64813dd785a2bd5c"}, + {file = "lxml-5.2.1-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c38d7b9a690b090de999835f0443d8aa93ce5f2064035dfc48f27f02b4afc3d0"}, + {file = "lxml-5.2.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5670fb70a828663cc37552a2a85bf2ac38475572b0e9b91283dc09efb52c41d1"}, + {file = "lxml-5.2.1-cp36-cp36m-manylinux_2_28_x86_64.whl", hash = "sha256:958244ad566c3ffc385f47dddde4145088a0ab893504b54b52c041987a8c1863"}, + {file = "lxml-5.2.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:b6241d4eee5f89453307c2f2bfa03b50362052ca0af1efecf9fef9a41a22bb4f"}, + {file = "lxml-5.2.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:2a66bf12fbd4666dd023b6f51223aed3d9f3b40fef06ce404cb75bafd3d89536"}, + {file = "lxml-5.2.1-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:9123716666e25b7b71c4e1789ec829ed18663152008b58544d95b008ed9e21e9"}, + {file = "lxml-5.2.1-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:0c3f67e2aeda739d1cc0b1102c9a9129f7dc83901226cc24dd72ba275ced4218"}, + {file = "lxml-5.2.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:5d5792e9b3fb8d16a19f46aa8208987cfeafe082363ee2745ea8b643d9cc5b45"}, + {file = "lxml-5.2.1-cp36-cp36m-musllinux_1_2_aarch64.whl", hash = "sha256:88e22fc0a6684337d25c994381ed8a1580a6f5ebebd5ad41f89f663ff4ec2885"}, + {file = "lxml-5.2.1-cp36-cp36m-musllinux_1_2_ppc64le.whl", hash = "sha256:21c2e6b09565ba5b45ae161b438e033a86ad1736b8c838c766146eff8ceffff9"}, + {file = "lxml-5.2.1-cp36-cp36m-musllinux_1_2_s390x.whl", hash = "sha256:afbbdb120d1e78d2ba8064a68058001b871154cc57787031b645c9142b937a62"}, + {file = "lxml-5.2.1-cp36-cp36m-musllinux_1_2_x86_64.whl", hash = "sha256:627402ad8dea044dde2eccde4370560a2b750ef894c9578e1d4f8ffd54000461"}, + {file = "lxml-5.2.1-cp36-cp36m-win32.whl", hash = "sha256:e89580a581bf478d8dcb97d9cd011d567768e8bc4095f8557b21c4d4c5fea7d0"}, + {file = "lxml-5.2.1-cp36-cp36m-win_amd64.whl", hash = "sha256:59565f10607c244bc4c05c0c5fa0c190c990996e0c719d05deec7030c2aa8289"}, + {file = "lxml-5.2.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:857500f88b17a6479202ff5fe5f580fc3404922cd02ab3716197adf1ef628029"}, + {file = "lxml-5.2.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:56c22432809085b3f3ae04e6e7bdd36883d7258fcd90e53ba7b2e463efc7a6af"}, + {file = "lxml-5.2.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a55ee573116ba208932e2d1a037cc4b10d2c1cb264ced2184d00b18ce585b2c0"}, + {file = "lxml-5.2.1-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:6cf58416653c5901e12624e4013708b6e11142956e7f35e7a83f1ab02f3fe456"}, + {file = "lxml-5.2.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:64c2baa7774bc22dd4474248ba16fe1a7f611c13ac6123408694d4cc93d66dbd"}, + {file = "lxml-5.2.1-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:74b28c6334cca4dd704e8004cba1955af0b778cf449142e581e404bd211fb619"}, + {file = "lxml-5.2.1-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:7221d49259aa1e5a8f00d3d28b1e0b76031655ca74bb287123ef56c3db92f213"}, + {file = "lxml-5.2.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:3dbe858ee582cbb2c6294dc85f55b5f19c918c2597855e950f34b660f1a5ede6"}, + {file = "lxml-5.2.1-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:04ab5415bf6c86e0518d57240a96c4d1fcfc3cb370bb2ac2a732b67f579e5a04"}, + {file = "lxml-5.2.1-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:6ab833e4735a7e5533711a6ea2df26459b96f9eec36d23f74cafe03631647c41"}, + {file = "lxml-5.2.1-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:f443cdef978430887ed55112b491f670bba6462cea7a7742ff8f14b7abb98d75"}, + {file = "lxml-5.2.1-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:9e2addd2d1866fe112bc6f80117bcc6bc25191c5ed1bfbcf9f1386a884252ae8"}, + {file = "lxml-5.2.1-cp37-cp37m-win32.whl", hash = "sha256:f51969bac61441fd31f028d7b3b45962f3ecebf691a510495e5d2cd8c8092dbd"}, + {file = "lxml-5.2.1-cp37-cp37m-win_amd64.whl", hash = "sha256:b0b58fbfa1bf7367dde8a557994e3b1637294be6cf2169810375caf8571a085c"}, + {file = "lxml-5.2.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:3e183c6e3298a2ed5af9d7a356ea823bccaab4ec2349dc9ed83999fd289d14d5"}, + {file = "lxml-5.2.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:804f74efe22b6a227306dd890eecc4f8c59ff25ca35f1f14e7482bbce96ef10b"}, + {file = "lxml-5.2.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:08802f0c56ed150cc6885ae0788a321b73505d2263ee56dad84d200cab11c07a"}, + {file = "lxml-5.2.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0f8c09ed18ecb4ebf23e02b8e7a22a05d6411911e6fabef3a36e4f371f4f2585"}, + {file = "lxml-5.2.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e3d30321949861404323c50aebeb1943461a67cd51d4200ab02babc58bd06a86"}, + {file = "lxml-5.2.1-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:b560e3aa4b1d49e0e6c847d72665384db35b2f5d45f8e6a5c0072e0283430533"}, + {file = "lxml-5.2.1-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:058a1308914f20784c9f4674036527e7c04f7be6fb60f5d61353545aa7fcb739"}, + {file = "lxml-5.2.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:adfb84ca6b87e06bc6b146dc7da7623395db1e31621c4785ad0658c5028b37d7"}, + {file = "lxml-5.2.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:417d14450f06d51f363e41cace6488519038f940676ce9664b34ebf5653433a5"}, + {file = "lxml-5.2.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:a2dfe7e2473f9b59496247aad6e23b405ddf2e12ef0765677b0081c02d6c2c0b"}, + {file = "lxml-5.2.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:bf2e2458345d9bffb0d9ec16557d8858c9c88d2d11fed53998512504cd9df49b"}, + {file = "lxml-5.2.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:58278b29cb89f3e43ff3e0c756abbd1518f3ee6adad9e35b51fb101c1c1daaec"}, + {file = "lxml-5.2.1-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:64641a6068a16201366476731301441ce93457eb8452056f570133a6ceb15fca"}, + {file = "lxml-5.2.1-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:78bfa756eab503673991bdcf464917ef7845a964903d3302c5f68417ecdc948c"}, + {file = "lxml-5.2.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:11a04306fcba10cd9637e669fd73aa274c1c09ca64af79c041aa820ea992b637"}, + {file = "lxml-5.2.1-cp38-cp38-win32.whl", hash = "sha256:66bc5eb8a323ed9894f8fa0ee6cb3e3fb2403d99aee635078fd19a8bc7a5a5da"}, + {file = "lxml-5.2.1-cp38-cp38-win_amd64.whl", hash = "sha256:9676bfc686fa6a3fa10cd4ae6b76cae8be26eb5ec6811d2a325636c460da1806"}, + {file = "lxml-5.2.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:cf22b41fdae514ee2f1691b6c3cdeae666d8b7fa9434de445f12bbeee0cf48dd"}, + {file = "lxml-5.2.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ec42088248c596dbd61d4ae8a5b004f97a4d91a9fd286f632e42e60b706718d7"}, + {file = "lxml-5.2.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cd53553ddad4a9c2f1f022756ae64abe16da1feb497edf4d9f87f99ec7cf86bd"}, + {file = "lxml-5.2.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:feaa45c0eae424d3e90d78823f3828e7dc42a42f21ed420db98da2c4ecf0a2cb"}, + {file = "lxml-5.2.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ddc678fb4c7e30cf830a2b5a8d869538bc55b28d6c68544d09c7d0d8f17694dc"}, + {file = "lxml-5.2.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:853e074d4931dbcba7480d4dcab23d5c56bd9607f92825ab80ee2bd916edea53"}, + {file = "lxml-5.2.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc4691d60512798304acb9207987e7b2b7c44627ea88b9d77489bbe3e6cc3bd4"}, + {file = "lxml-5.2.1-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:beb72935a941965c52990f3a32d7f07ce869fe21c6af8b34bf6a277b33a345d3"}, + {file = "lxml-5.2.1-cp39-cp39-manylinux_2_28_ppc64le.whl", hash = "sha256:6588c459c5627fefa30139be4d2e28a2c2a1d0d1c265aad2ba1935a7863a4913"}, + {file = "lxml-5.2.1-cp39-cp39-manylinux_2_28_s390x.whl", hash = "sha256:588008b8497667f1ddca7c99f2f85ce8511f8f7871b4a06ceede68ab62dff64b"}, + {file = "lxml-5.2.1-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:b6787b643356111dfd4032b5bffe26d2f8331556ecb79e15dacb9275da02866e"}, + {file = "lxml-5.2.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7c17b64b0a6ef4e5affae6a3724010a7a66bda48a62cfe0674dabd46642e8b54"}, + {file = "lxml-5.2.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:27aa20d45c2e0b8cd05da6d4759649170e8dfc4f4e5ef33a34d06f2d79075d57"}, + {file = "lxml-5.2.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:d4f2cc7060dc3646632d7f15fe68e2fa98f58e35dd5666cd525f3b35d3fed7f8"}, + {file = "lxml-5.2.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff46d772d5f6f73564979cd77a4fffe55c916a05f3cb70e7c9c0590059fb29ef"}, + {file = "lxml-5.2.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:96323338e6c14e958d775700ec8a88346014a85e5de73ac7967db0367582049b"}, + {file = "lxml-5.2.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:52421b41ac99e9d91934e4d0d0fe7da9f02bfa7536bb4431b4c05c906c8c6919"}, + {file = "lxml-5.2.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:7a7efd5b6d3e30d81ec68ab8a88252d7c7c6f13aaa875009fe3097eb4e30b84c"}, + {file = "lxml-5.2.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:0ed777c1e8c99b63037b91f9d73a6aad20fd035d77ac84afcc205225f8f41188"}, + {file = "lxml-5.2.1-cp39-cp39-win32.whl", hash = "sha256:644df54d729ef810dcd0f7732e50e5ad1bd0a135278ed8d6bcb06f33b6b6f708"}, + {file = "lxml-5.2.1-cp39-cp39-win_amd64.whl", hash = "sha256:9ca66b8e90daca431b7ca1408cae085d025326570e57749695d6a01454790e95"}, + {file = "lxml-5.2.1-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:9b0ff53900566bc6325ecde9181d89afadc59c5ffa39bddf084aaedfe3b06a11"}, + {file = "lxml-5.2.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fd6037392f2d57793ab98d9e26798f44b8b4da2f2464388588f48ac52c489ea1"}, + {file = "lxml-5.2.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b9c07e7a45bb64e21df4b6aa623cb8ba214dfb47d2027d90eac197329bb5e94"}, + {file = "lxml-5.2.1-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:3249cc2989d9090eeac5467e50e9ec2d40704fea9ab72f36b034ea34ee65ca98"}, + {file = "lxml-5.2.1-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:f42038016852ae51b4088b2862126535cc4fc85802bfe30dea3500fdfaf1864e"}, + {file = "lxml-5.2.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:533658f8fbf056b70e434dff7e7aa611bcacb33e01f75de7f821810e48d1bb66"}, + {file = "lxml-5.2.1-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:622020d4521e22fb371e15f580d153134bfb68d6a429d1342a25f051ec72df1c"}, + {file = "lxml-5.2.1-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:efa7b51824aa0ee957ccd5a741c73e6851de55f40d807f08069eb4c5a26b2baa"}, + {file = "lxml-5.2.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c6ad0fbf105f6bcc9300c00010a2ffa44ea6f555df1a2ad95c88f5656104817"}, + {file = "lxml-5.2.1-pp37-pypy37_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:e233db59c8f76630c512ab4a4daf5a5986da5c3d5b44b8e9fc742f2a24dbd460"}, + {file = "lxml-5.2.1-pp37-pypy37_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:6a014510830df1475176466b6087fc0c08b47a36714823e58d8b8d7709132a96"}, + {file = "lxml-5.2.1-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:d38c8f50ecf57f0463399569aa388b232cf1a2ffb8f0a9a5412d0db57e054860"}, + {file = "lxml-5.2.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:5aea8212fb823e006b995c4dda533edcf98a893d941f173f6c9506126188860d"}, + {file = "lxml-5.2.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ff097ae562e637409b429a7ac958a20aab237a0378c42dabaa1e3abf2f896e5f"}, + {file = "lxml-5.2.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f5d65c39f16717a47c36c756af0fb36144069c4718824b7533f803ecdf91138"}, + {file = "lxml-5.2.1-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:3d0c3dd24bb4605439bf91068598d00c6370684f8de4a67c2992683f6c309d6b"}, + {file = "lxml-5.2.1-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e32be23d538753a8adb6c85bd539f5fd3b15cb987404327c569dfc5fd8366e85"}, + {file = "lxml-5.2.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:cc518cea79fd1e2f6c90baafa28906d4309d24f3a63e801d855e7424c5b34144"}, + {file = "lxml-5.2.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a0af35bd8ebf84888373630f73f24e86bf016642fb8576fba49d3d6b560b7cbc"}, + {file = "lxml-5.2.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8aca2e3a72f37bfc7b14ba96d4056244001ddcc18382bd0daa087fd2e68a354"}, + {file = "lxml-5.2.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ca1e8188b26a819387b29c3895c47a5e618708fe6f787f3b1a471de2c4a94d9"}, + {file = "lxml-5.2.1-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c8ba129e6d3b0136a0f50345b2cb3db53f6bda5dd8c7f5d83fbccba97fb5dcb5"}, + {file = "lxml-5.2.1-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e998e304036198b4f6914e6a1e2b6f925208a20e2042563d9734881150c6c246"}, + {file = "lxml-5.2.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:d3be9b2076112e51b323bdf6d5a7f8a798de55fb8d95fcb64bd179460cdc0704"}, + {file = "lxml-5.2.1.tar.gz", hash = "sha256:3f7765e69bbce0906a7c74d5fe46d2c7a7596147318dbc08e4a2431f3060e306"}, ] [package.extras] cssselect = ["cssselect (>=0.7)"] +html-clean = ["lxml-html-clean"] html5 = ["html5lib"] htmlsoup = ["BeautifulSoup4"] -source = ["Cython (>=3.0.7)"] +source = ["Cython (>=3.0.10)"] [[package]] name = "markupsafe" -version = "2.1.3" +version = "2.1.5" description = "Safely add untrusted strings to HTML/XML markup." optional = false python-versions = ">=3.7" files = [ - {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-win32.whl", hash = "sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-win_amd64.whl", hash = "sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-win32.whl", hash = "sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-win32.whl", hash = "sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-win_amd64.whl", hash = "sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-win32.whl", hash = "sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-win_amd64.whl", hash = "sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-win32.whl", hash = "sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-win_amd64.whl", hash = "sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba"}, - {file = "MarkupSafe-2.1.3.tar.gz", hash = "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-win32.whl", hash = "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-win_amd64.whl", hash = "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-win32.whl", hash = "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-win_amd64.whl", hash = "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-win32.whl", hash = "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl", hash = "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-win32.whl", hash = "sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-win_amd64.whl", hash = "sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-win32.whl", hash = "sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-win_amd64.whl", hash = "sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-win32.whl", hash = "sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-win_amd64.whl", hash = "sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5"}, + {file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"}, ] [[package]] name = "multidict" -version = "6.0.4" +version = "6.0.5" description = "multidict implementation" optional = false python-versions = ">=3.7" files = [ - {file = "multidict-6.0.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0b1a97283e0c85772d613878028fec909f003993e1007eafa715b24b377cb9b8"}, - {file = "multidict-6.0.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:eeb6dcc05e911516ae3d1f207d4b0520d07f54484c49dfc294d6e7d63b734171"}, - {file = "multidict-6.0.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d6d635d5209b82a3492508cf5b365f3446afb65ae7ebd755e70e18f287b0adf7"}, - {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c048099e4c9e9d615545e2001d3d8a4380bd403e1a0578734e0d31703d1b0c0b"}, - {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ea20853c6dbbb53ed34cb4d080382169b6f4554d394015f1bef35e881bf83547"}, - {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:16d232d4e5396c2efbbf4f6d4df89bfa905eb0d4dc5b3549d872ab898451f569"}, - {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36c63aaa167f6c6b04ef2c85704e93af16c11d20de1d133e39de6a0e84582a93"}, - {file = "multidict-6.0.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:64bdf1086b6043bf519869678f5f2757f473dee970d7abf6da91ec00acb9cb98"}, - {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:43644e38f42e3af682690876cff722d301ac585c5b9e1eacc013b7a3f7b696a0"}, - {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7582a1d1030e15422262de9f58711774e02fa80df0d1578995c76214f6954988"}, - {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:ddff9c4e225a63a5afab9dd15590432c22e8057e1a9a13d28ed128ecf047bbdc"}, - {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:ee2a1ece51b9b9e7752e742cfb661d2a29e7bcdba2d27e66e28a99f1890e4fa0"}, - {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a2e4369eb3d47d2034032a26c7a80fcb21a2cb22e1173d761a162f11e562caa5"}, - {file = "multidict-6.0.4-cp310-cp310-win32.whl", hash = "sha256:574b7eae1ab267e5f8285f0fe881f17efe4b98c39a40858247720935b893bba8"}, - {file = "multidict-6.0.4-cp310-cp310-win_amd64.whl", hash = "sha256:4dcbb0906e38440fa3e325df2359ac6cb043df8e58c965bb45f4e406ecb162cc"}, - {file = "multidict-6.0.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0dfad7a5a1e39c53ed00d2dd0c2e36aed4650936dc18fd9a1826a5ae1cad6f03"}, - {file = "multidict-6.0.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:64da238a09d6039e3bd39bb3aee9c21a5e34f28bfa5aa22518581f910ff94af3"}, - {file = "multidict-6.0.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ff959bee35038c4624250473988b24f846cbeb2c6639de3602c073f10410ceba"}, - {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:01a3a55bd90018c9c080fbb0b9f4891db37d148a0a18722b42f94694f8b6d4c9"}, - {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c5cb09abb18c1ea940fb99360ea0396f34d46566f157122c92dfa069d3e0e982"}, - {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:666daae833559deb2d609afa4490b85830ab0dfca811a98b70a205621a6109fe"}, - {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11bdf3f5e1518b24530b8241529d2050014c884cf18b6fc69c0c2b30ca248710"}, - {file = "multidict-6.0.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7d18748f2d30f94f498e852c67d61261c643b349b9d2a581131725595c45ec6c"}, - {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:458f37be2d9e4c95e2d8866a851663cbc76e865b78395090786f6cd9b3bbf4f4"}, - {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:b1a2eeedcead3a41694130495593a559a668f382eee0727352b9a41e1c45759a"}, - {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7d6ae9d593ef8641544d6263c7fa6408cc90370c8cb2bbb65f8d43e5b0351d9c"}, - {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:5979b5632c3e3534e42ca6ff856bb24b2e3071b37861c2c727ce220d80eee9ed"}, - {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:dcfe792765fab89c365123c81046ad4103fcabbc4f56d1c1997e6715e8015461"}, - {file = "multidict-6.0.4-cp311-cp311-win32.whl", hash = "sha256:3601a3cece3819534b11d4efc1eb76047488fddd0c85a3948099d5da4d504636"}, - {file = "multidict-6.0.4-cp311-cp311-win_amd64.whl", hash = "sha256:81a4f0b34bd92df3da93315c6a59034df95866014ac08535fc819f043bfd51f0"}, - {file = "multidict-6.0.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:67040058f37a2a51ed8ea8f6b0e6ee5bd78ca67f169ce6122f3e2ec80dfe9b78"}, - {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:853888594621e6604c978ce2a0444a1e6e70c8d253ab65ba11657659dcc9100f"}, - {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:39ff62e7d0f26c248b15e364517a72932a611a9b75f35b45be078d81bdb86603"}, - {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:af048912e045a2dc732847d33821a9d84ba553f5c5f028adbd364dd4765092ac"}, - {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1e8b901e607795ec06c9e42530788c45ac21ef3aaa11dbd0c69de543bfb79a9"}, - {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62501642008a8b9871ddfccbf83e4222cf8ac0d5aeedf73da36153ef2ec222d2"}, - {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:99b76c052e9f1bc0721f7541e5e8c05db3941eb9ebe7b8553c625ef88d6eefde"}, - {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:509eac6cf09c794aa27bcacfd4d62c885cce62bef7b2c3e8b2e49d365b5003fe"}, - {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:21a12c4eb6ddc9952c415f24eef97e3e55ba3af61f67c7bc388dcdec1404a067"}, - {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:5cad9430ab3e2e4fa4a2ef4450f548768400a2ac635841bc2a56a2052cdbeb87"}, - {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ab55edc2e84460694295f401215f4a58597f8f7c9466faec545093045476327d"}, - {file = "multidict-6.0.4-cp37-cp37m-win32.whl", hash = "sha256:5a4dcf02b908c3b8b17a45fb0f15b695bf117a67b76b7ad18b73cf8e92608775"}, - {file = "multidict-6.0.4-cp37-cp37m-win_amd64.whl", hash = "sha256:6ed5f161328b7df384d71b07317f4d8656434e34591f20552c7bcef27b0ab88e"}, - {file = "multidict-6.0.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5fc1b16f586f049820c5c5b17bb4ee7583092fa0d1c4e28b5239181ff9532e0c"}, - {file = "multidict-6.0.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1502e24330eb681bdaa3eb70d6358e818e8e8f908a22a1851dfd4e15bc2f8161"}, - {file = "multidict-6.0.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b692f419760c0e65d060959df05f2a531945af31fda0c8a3b3195d4efd06de11"}, - {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45e1ecb0379bfaab5eef059f50115b54571acfbe422a14f668fc8c27ba410e7e"}, - {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ddd3915998d93fbcd2566ddf9cf62cdb35c9e093075f862935573d265cf8f65d"}, - {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:59d43b61c59d82f2effb39a93c48b845efe23a3852d201ed2d24ba830d0b4cf2"}, - {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc8e1d0c705233c5dd0c5e6460fbad7827d5d36f310a0fadfd45cc3029762258"}, - {file = "multidict-6.0.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d6aa0418fcc838522256761b3415822626f866758ee0bc6632c9486b179d0b52"}, - {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6748717bb10339c4760c1e63da040f5f29f5ed6e59d76daee30305894069a660"}, - {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:4d1a3d7ef5e96b1c9e92f973e43aa5e5b96c659c9bc3124acbbd81b0b9c8a951"}, - {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4372381634485bec7e46718edc71528024fcdc6f835baefe517b34a33c731d60"}, - {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:fc35cb4676846ef752816d5be2193a1e8367b4c1397b74a565a9d0389c433a1d"}, - {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:4b9d9e4e2b37daddb5c23ea33a3417901fa7c7b3dee2d855f63ee67a0b21e5b1"}, - {file = "multidict-6.0.4-cp38-cp38-win32.whl", hash = "sha256:e41b7e2b59679edfa309e8db64fdf22399eec4b0b24694e1b2104fb789207779"}, - {file = "multidict-6.0.4-cp38-cp38-win_amd64.whl", hash = "sha256:d6c254ba6e45d8e72739281ebc46ea5eb5f101234f3ce171f0e9f5cc86991480"}, - {file = "multidict-6.0.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:16ab77bbeb596e14212e7bab8429f24c1579234a3a462105cda4a66904998664"}, - {file = "multidict-6.0.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bc779e9e6f7fda81b3f9aa58e3a6091d49ad528b11ed19f6621408806204ad35"}, - {file = "multidict-6.0.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4ceef517eca3e03c1cceb22030a3e39cb399ac86bff4e426d4fc6ae49052cc60"}, - {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:281af09f488903fde97923c7744bb001a9b23b039a909460d0f14edc7bf59706"}, - {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:52f2dffc8acaba9a2f27174c41c9e57f60b907bb9f096b36b1a1f3be71c6284d"}, - {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b41156839806aecb3641f3208c0dafd3ac7775b9c4c422d82ee2a45c34ba81ca"}, - {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5e3fc56f88cc98ef8139255cf8cd63eb2c586531e43310ff859d6bb3a6b51f1"}, - {file = "multidict-6.0.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8316a77808c501004802f9beebde51c9f857054a0c871bd6da8280e718444449"}, - {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f70b98cd94886b49d91170ef23ec5c0e8ebb6f242d734ed7ed677b24d50c82cf"}, - {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bf6774e60d67a9efe02b3616fee22441d86fab4c6d335f9d2051d19d90a40063"}, - {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:e69924bfcdda39b722ef4d9aa762b2dd38e4632b3641b1d9a57ca9cd18f2f83a"}, - {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:6b181d8c23da913d4ff585afd1155a0e1194c0b50c54fcfe286f70cdaf2b7176"}, - {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:52509b5be062d9eafc8170e53026fbc54cf3b32759a23d07fd935fb04fc22d95"}, - {file = "multidict-6.0.4-cp39-cp39-win32.whl", hash = "sha256:27c523fbfbdfd19c6867af7346332b62b586eed663887392cff78d614f9ec313"}, - {file = "multidict-6.0.4-cp39-cp39-win_amd64.whl", hash = "sha256:33029f5734336aa0d4c0384525da0387ef89148dc7191aae00ca5fb23d7aafc2"}, - {file = "multidict-6.0.4.tar.gz", hash = "sha256:3666906492efb76453c0e7b97f2cf459b0682e7402c0489a95484965dbc1da49"}, + {file = "multidict-6.0.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:228b644ae063c10e7f324ab1ab6b548bdf6f8b47f3ec234fef1093bc2735e5f9"}, + {file = "multidict-6.0.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:896ebdcf62683551312c30e20614305f53125750803b614e9e6ce74a96232604"}, + {file = "multidict-6.0.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:411bf8515f3be9813d06004cac41ccf7d1cd46dfe233705933dd163b60e37600"}, + {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d147090048129ce3c453f0292e7697d333db95e52616b3793922945804a433c"}, + {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:215ed703caf15f578dca76ee6f6b21b7603791ae090fbf1ef9d865571039ade5"}, + {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c6390cf87ff6234643428991b7359b5f59cc15155695deb4eda5c777d2b880f"}, + {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21fd81c4ebdb4f214161be351eb5bcf385426bf023041da2fd9e60681f3cebae"}, + {file = "multidict-6.0.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3cc2ad10255f903656017363cd59436f2111443a76f996584d1077e43ee51182"}, + {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6939c95381e003f54cd4c5516740faba40cf5ad3eeff460c3ad1d3e0ea2549bf"}, + {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:220dd781e3f7af2c2c1053da9fa96d9cf3072ca58f057f4c5adaaa1cab8fc442"}, + {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:766c8f7511df26d9f11cd3a8be623e59cca73d44643abab3f8c8c07620524e4a"}, + {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:fe5d7785250541f7f5019ab9cba2c71169dc7d74d0f45253f8313f436458a4ef"}, + {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c1c1496e73051918fcd4f58ff2e0f2f3066d1c76a0c6aeffd9b45d53243702cc"}, + {file = "multidict-6.0.5-cp310-cp310-win32.whl", hash = "sha256:7afcdd1fc07befad18ec4523a782cde4e93e0a2bf71239894b8d61ee578c1319"}, + {file = "multidict-6.0.5-cp310-cp310-win_amd64.whl", hash = "sha256:99f60d34c048c5c2fabc766108c103612344c46e35d4ed9ae0673d33c8fb26e8"}, + {file = "multidict-6.0.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f285e862d2f153a70586579c15c44656f888806ed0e5b56b64489afe4a2dbfba"}, + {file = "multidict-6.0.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:53689bb4e102200a4fafa9de9c7c3c212ab40a7ab2c8e474491914d2305f187e"}, + {file = "multidict-6.0.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:612d1156111ae11d14afaf3a0669ebf6c170dbb735e510a7438ffe2369a847fd"}, + {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7be7047bd08accdb7487737631d25735c9a04327911de89ff1b26b81745bd4e3"}, + {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de170c7b4fe6859beb8926e84f7d7d6c693dfe8e27372ce3b76f01c46e489fcf"}, + {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:04bde7a7b3de05732a4eb39c94574db1ec99abb56162d6c520ad26f83267de29"}, + {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85f67aed7bb647f93e7520633d8f51d3cbc6ab96957c71272b286b2f30dc70ed"}, + {file = "multidict-6.0.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:425bf820055005bfc8aa9a0b99ccb52cc2f4070153e34b701acc98d201693733"}, + {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d3eb1ceec286eba8220c26f3b0096cf189aea7057b6e7b7a2e60ed36b373b77f"}, + {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:7901c05ead4b3fb75113fb1dd33eb1253c6d3ee37ce93305acd9d38e0b5f21a4"}, + {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:e0e79d91e71b9867c73323a3444724d496c037e578a0e1755ae159ba14f4f3d1"}, + {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:29bfeb0dff5cb5fdab2023a7a9947b3b4af63e9c47cae2a10ad58394b517fddc"}, + {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e030047e85cbcedbfc073f71836d62dd5dadfbe7531cae27789ff66bc551bd5e"}, + {file = "multidict-6.0.5-cp311-cp311-win32.whl", hash = "sha256:2f4848aa3baa109e6ab81fe2006c77ed4d3cd1e0ac2c1fbddb7b1277c168788c"}, + {file = "multidict-6.0.5-cp311-cp311-win_amd64.whl", hash = "sha256:2faa5ae9376faba05f630d7e5e6be05be22913782b927b19d12b8145968a85ea"}, + {file = "multidict-6.0.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:51d035609b86722963404f711db441cf7134f1889107fb171a970c9701f92e1e"}, + {file = "multidict-6.0.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:cbebcd5bcaf1eaf302617c114aa67569dd3f090dd0ce8ba9e35e9985b41ac35b"}, + {file = "multidict-6.0.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2ffc42c922dbfddb4a4c3b438eb056828719f07608af27d163191cb3e3aa6cc5"}, + {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ceb3b7e6a0135e092de86110c5a74e46bda4bd4fbfeeb3a3bcec79c0f861e450"}, + {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:79660376075cfd4b2c80f295528aa6beb2058fd289f4c9252f986751a4cd0496"}, + {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e4428b29611e989719874670fd152b6625500ad6c686d464e99f5aaeeaca175a"}, + {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d84a5c3a5f7ce6db1f999fb9438f686bc2e09d38143f2d93d8406ed2dd6b9226"}, + {file = "multidict-6.0.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:76c0de87358b192de7ea9649beb392f107dcad9ad27276324c24c91774ca5271"}, + {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:79a6d2ba910adb2cbafc95dad936f8b9386e77c84c35bc0add315b856d7c3abb"}, + {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:92d16a3e275e38293623ebf639c471d3e03bb20b8ebb845237e0d3664914caef"}, + {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:fb616be3538599e797a2017cccca78e354c767165e8858ab5116813146041a24"}, + {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:14c2976aa9038c2629efa2c148022ed5eb4cb939e15ec7aace7ca932f48f9ba6"}, + {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:435a0984199d81ca178b9ae2c26ec3d49692d20ee29bc4c11a2a8d4514c67eda"}, + {file = "multidict-6.0.5-cp312-cp312-win32.whl", hash = "sha256:9fe7b0653ba3d9d65cbe7698cca585bf0f8c83dbbcc710db9c90f478e175f2d5"}, + {file = "multidict-6.0.5-cp312-cp312-win_amd64.whl", hash = "sha256:01265f5e40f5a17f8241d52656ed27192be03bfa8764d88e8220141d1e4b3556"}, + {file = "multidict-6.0.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:19fe01cea168585ba0f678cad6f58133db2aa14eccaf22f88e4a6dccadfad8b3"}, + {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6bf7a982604375a8d49b6cc1b781c1747f243d91b81035a9b43a2126c04766f5"}, + {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:107c0cdefe028703fb5dafe640a409cb146d44a6ae201e55b35a4af8e95457dd"}, + {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:403c0911cd5d5791605808b942c88a8155c2592e05332d2bf78f18697a5fa15e"}, + {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aeaf541ddbad8311a87dd695ed9642401131ea39ad7bc8cf3ef3967fd093b626"}, + {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e4972624066095e52b569e02b5ca97dbd7a7ddd4294bf4e7247d52635630dd83"}, + {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d946b0a9eb8aaa590df1fe082cee553ceab173e6cb5b03239716338629c50c7a"}, + {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b55358304d7a73d7bdf5de62494aaf70bd33015831ffd98bc498b433dfe5b10c"}, + {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:a3145cb08d8625b2d3fee1b2d596a8766352979c9bffe5d7833e0503d0f0b5e5"}, + {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:d65f25da8e248202bd47445cec78e0025c0fe7582b23ec69c3b27a640dd7a8e3"}, + {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:c9bf56195c6bbd293340ea82eafd0071cb3d450c703d2c93afb89f93b8386ccc"}, + {file = "multidict-6.0.5-cp37-cp37m-win32.whl", hash = "sha256:69db76c09796b313331bb7048229e3bee7928eb62bab5e071e9f7fcc4879caee"}, + {file = "multidict-6.0.5-cp37-cp37m-win_amd64.whl", hash = "sha256:fce28b3c8a81b6b36dfac9feb1de115bab619b3c13905b419ec71d03a3fc1423"}, + {file = "multidict-6.0.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:76f067f5121dcecf0d63a67f29080b26c43c71a98b10c701b0677e4a065fbd54"}, + {file = "multidict-6.0.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b82cc8ace10ab5bd93235dfaab2021c70637005e1ac787031f4d1da63d493c1d"}, + {file = "multidict-6.0.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5cb241881eefd96b46f89b1a056187ea8e9ba14ab88ba632e68d7a2ecb7aadf7"}, + {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8e94e6912639a02ce173341ff62cc1201232ab86b8a8fcc05572741a5dc7d93"}, + {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:09a892e4a9fb47331da06948690ae38eaa2426de97b4ccbfafbdcbe5c8f37ff8"}, + {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55205d03e8a598cfc688c71ca8ea5f66447164efff8869517f175ea632c7cb7b"}, + {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37b15024f864916b4951adb95d3a80c9431299080341ab9544ed148091b53f50"}, + {file = "multidict-6.0.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f2a1dee728b52b33eebff5072817176c172050d44d67befd681609b4746e1c2e"}, + {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:edd08e6f2f1a390bf137080507e44ccc086353c8e98c657e666c017718561b89"}, + {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:60d698e8179a42ec85172d12f50b1668254628425a6bd611aba022257cac1386"}, + {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:3d25f19500588cbc47dc19081d78131c32637c25804df8414463ec908631e453"}, + {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:4cc0ef8b962ac7a5e62b9e826bd0cd5040e7d401bc45a6835910ed699037a461"}, + {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:eca2e9d0cc5a889850e9bbd68e98314ada174ff6ccd1129500103df7a94a7a44"}, + {file = "multidict-6.0.5-cp38-cp38-win32.whl", hash = "sha256:4a6a4f196f08c58c59e0b8ef8ec441d12aee4125a7d4f4fef000ccb22f8d7241"}, + {file = "multidict-6.0.5-cp38-cp38-win_amd64.whl", hash = "sha256:0275e35209c27a3f7951e1ce7aaf93ce0d163b28948444bec61dd7badc6d3f8c"}, + {file = "multidict-6.0.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e7be68734bd8c9a513f2b0cfd508802d6609da068f40dc57d4e3494cefc92929"}, + {file = "multidict-6.0.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1d9ea7a7e779d7a3561aade7d596649fbecfa5c08a7674b11b423783217933f9"}, + {file = "multidict-6.0.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ea1456df2a27c73ce51120fa2f519f1bea2f4a03a917f4a43c8707cf4cbbae1a"}, + {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf590b134eb70629e350691ecca88eac3e3b8b3c86992042fb82e3cb1830d5e1"}, + {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5c0631926c4f58e9a5ccce555ad7747d9a9f8b10619621f22f9635f069f6233e"}, + {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dce1c6912ab9ff5f179eaf6efe7365c1f425ed690b03341911bf4939ef2f3046"}, + {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0868d64af83169e4d4152ec612637a543f7a336e4a307b119e98042e852ad9c"}, + {file = "multidict-6.0.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:141b43360bfd3bdd75f15ed811850763555a251e38b2405967f8e25fb43f7d40"}, + {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7df704ca8cf4a073334e0427ae2345323613e4df18cc224f647f251e5e75a527"}, + {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6214c5a5571802c33f80e6c84713b2c79e024995b9c5897f794b43e714daeec9"}, + {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:cd6c8fca38178e12c00418de737aef1261576bd1b6e8c6134d3e729a4e858b38"}, + {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:e02021f87a5b6932fa6ce916ca004c4d441509d33bbdbeca70d05dff5e9d2479"}, + {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ebd8d160f91a764652d3e51ce0d2956b38efe37c9231cd82cfc0bed2e40b581c"}, + {file = "multidict-6.0.5-cp39-cp39-win32.whl", hash = "sha256:04da1bb8c8dbadf2a18a452639771951c662c5ad03aefe4884775454be322c9b"}, + {file = "multidict-6.0.5-cp39-cp39-win_amd64.whl", hash = "sha256:d6f6d4f185481c9669b9447bf9d9cf3b95a0e9df9d169bbc17e363b7d5487755"}, + {file = "multidict-6.0.5-py3-none-any.whl", hash = "sha256:0d63c74e3d7ab26de115c49bffc92cc77ed23395303d496eae515d4204a625e7"}, + {file = "multidict-6.0.5.tar.gz", hash = "sha256:f7e301075edaf50500f0b341543c41194d8df3ae5caf4702f2095f3ca73dd8da"}, ] [[package]] name = "mypy" -version = "1.8.0" +version = "1.9.0" description = "Optional static typing for Python" optional = false python-versions = ">=3.8" files = [ - {file = "mypy-1.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:485a8942f671120f76afffff70f259e1cd0f0cfe08f81c05d8816d958d4577d3"}, - {file = "mypy-1.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:df9824ac11deaf007443e7ed2a4a26bebff98d2bc43c6da21b2b64185da011c4"}, - {file = "mypy-1.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2afecd6354bbfb6e0160f4e4ad9ba6e4e003b767dd80d85516e71f2e955ab50d"}, - {file = "mypy-1.8.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8963b83d53ee733a6e4196954502b33567ad07dfd74851f32be18eb932fb1cb9"}, - {file = "mypy-1.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:e46f44b54ebddbeedbd3d5b289a893219065ef805d95094d16a0af6630f5d410"}, - {file = "mypy-1.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:855fe27b80375e5c5878492f0729540db47b186509c98dae341254c8f45f42ae"}, - {file = "mypy-1.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4c886c6cce2d070bd7df4ec4a05a13ee20c0aa60cb587e8d1265b6c03cf91da3"}, - {file = "mypy-1.8.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d19c413b3c07cbecf1f991e2221746b0d2a9410b59cb3f4fb9557f0365a1a817"}, - {file = "mypy-1.8.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9261ed810972061388918c83c3f5cd46079d875026ba97380f3e3978a72f503d"}, - {file = "mypy-1.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:51720c776d148bad2372ca21ca29256ed483aa9a4cdefefcef49006dff2a6835"}, - {file = "mypy-1.8.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:52825b01f5c4c1c4eb0db253ec09c7aa17e1a7304d247c48b6f3599ef40db8bd"}, - {file = "mypy-1.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f5ac9a4eeb1ec0f1ccdc6f326bcdb464de5f80eb07fb38b5ddd7b0de6bc61e55"}, - {file = "mypy-1.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afe3fe972c645b4632c563d3f3eff1cdca2fa058f730df2b93a35e3b0c538218"}, - {file = "mypy-1.8.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:42c6680d256ab35637ef88891c6bd02514ccb7e1122133ac96055ff458f93fc3"}, - {file = "mypy-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:720a5ca70e136b675af3af63db533c1c8c9181314d207568bbe79051f122669e"}, - {file = "mypy-1.8.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:028cf9f2cae89e202d7b6593cd98db6759379f17a319b5faf4f9978d7084cdc6"}, - {file = "mypy-1.8.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4e6d97288757e1ddba10dd9549ac27982e3e74a49d8d0179fc14d4365c7add66"}, - {file = "mypy-1.8.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f1478736fcebb90f97e40aff11a5f253af890c845ee0c850fe80aa060a267c6"}, - {file = "mypy-1.8.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:42419861b43e6962a649068a61f4a4839205a3ef525b858377a960b9e2de6e0d"}, - {file = "mypy-1.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:2b5b6c721bd4aabaadead3a5e6fa85c11c6c795e0c81a7215776ef8afc66de02"}, - {file = "mypy-1.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5c1538c38584029352878a0466f03a8ee7547d7bd9f641f57a0f3017a7c905b8"}, - {file = "mypy-1.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4ef4be7baf08a203170f29e89d79064463b7fc7a0908b9d0d5114e8009c3a259"}, - {file = "mypy-1.8.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7178def594014aa6c35a8ff411cf37d682f428b3b5617ca79029d8ae72f5402b"}, - {file = "mypy-1.8.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ab3c84fa13c04aeeeabb2a7f67a25ef5d77ac9d6486ff33ded762ef353aa5592"}, - {file = "mypy-1.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:99b00bc72855812a60d253420d8a2eae839b0afa4938f09f4d2aa9bb4654263a"}, - {file = "mypy-1.8.0-py3-none-any.whl", hash = "sha256:538fd81bb5e430cc1381a443971c0475582ff9f434c16cd46d2c66763ce85d9d"}, - {file = "mypy-1.8.0.tar.gz", hash = "sha256:6ff8b244d7085a0b425b56d327b480c3b29cafbd2eff27316a004f9a7391ae07"}, + {file = "mypy-1.9.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f8a67616990062232ee4c3952f41c779afac41405806042a8126fe96e098419f"}, + {file = "mypy-1.9.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d357423fa57a489e8c47b7c85dfb96698caba13d66e086b412298a1a0ea3b0ed"}, + {file = "mypy-1.9.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49c87c15aed320de9b438ae7b00c1ac91cd393c1b854c2ce538e2a72d55df150"}, + {file = "mypy-1.9.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:48533cdd345c3c2e5ef48ba3b0d3880b257b423e7995dada04248725c6f77374"}, + {file = "mypy-1.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:4d3dbd346cfec7cb98e6cbb6e0f3c23618af826316188d587d1c1bc34f0ede03"}, + {file = "mypy-1.9.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:653265f9a2784db65bfca694d1edd23093ce49740b2244cde583aeb134c008f3"}, + {file = "mypy-1.9.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3a3c007ff3ee90f69cf0a15cbcdf0995749569b86b6d2f327af01fd1b8aee9dc"}, + {file = "mypy-1.9.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2418488264eb41f69cc64a69a745fad4a8f86649af4b1041a4c64ee61fc61129"}, + {file = "mypy-1.9.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:68edad3dc7d70f2f17ae4c6c1b9471a56138ca22722487eebacfd1eb5321d612"}, + {file = "mypy-1.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:85ca5fcc24f0b4aeedc1d02f93707bccc04733f21d41c88334c5482219b1ccb3"}, + {file = "mypy-1.9.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aceb1db093b04db5cd390821464504111b8ec3e351eb85afd1433490163d60cd"}, + {file = "mypy-1.9.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0235391f1c6f6ce487b23b9dbd1327b4ec33bb93934aa986efe8a9563d9349e6"}, + {file = "mypy-1.9.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d4d5ddc13421ba3e2e082a6c2d74c2ddb3979c39b582dacd53dd5d9431237185"}, + {file = "mypy-1.9.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:190da1ee69b427d7efa8aa0d5e5ccd67a4fb04038c380237a0d96829cb157913"}, + {file = "mypy-1.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:fe28657de3bfec596bbeef01cb219833ad9d38dd5393fc649f4b366840baefe6"}, + {file = "mypy-1.9.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e54396d70be04b34f31d2edf3362c1edd023246c82f1730bbf8768c28db5361b"}, + {file = "mypy-1.9.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5e6061f44f2313b94f920e91b204ec600982961e07a17e0f6cd83371cb23f5c2"}, + {file = "mypy-1.9.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81a10926e5473c5fc3da8abb04119a1f5811a236dc3a38d92015cb1e6ba4cb9e"}, + {file = "mypy-1.9.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b685154e22e4e9199fc95f298661deea28aaede5ae16ccc8cbb1045e716b3e04"}, + {file = "mypy-1.9.0-cp38-cp38-win_amd64.whl", hash = "sha256:5d741d3fc7c4da608764073089e5f58ef6352bedc223ff58f2f038c2c4698a89"}, + {file = "mypy-1.9.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:587ce887f75dd9700252a3abbc9c97bbe165a4a630597845c61279cf32dfbf02"}, + {file = "mypy-1.9.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f88566144752999351725ac623471661c9d1cd8caa0134ff98cceeea181789f4"}, + {file = "mypy-1.9.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61758fabd58ce4b0720ae1e2fea5cfd4431591d6d590b197775329264f86311d"}, + {file = "mypy-1.9.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e49499be624dead83927e70c756970a0bc8240e9f769389cdf5714b0784ca6bf"}, + {file = "mypy-1.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:571741dc4194b4f82d344b15e8837e8c5fcc462d66d076748142327626a1b6e9"}, + {file = "mypy-1.9.0-py3-none-any.whl", hash = "sha256:a260627a570559181a9ea5de61ac6297aa5af202f06fd7ab093ce74e7181e43e"}, + {file = "mypy-1.9.0.tar.gz", hash = "sha256:3cc5da0127e6a478cddd906068496a97a7618a21ce9b54bde5bf7e539c7af974"}, ] [package.dependencies] @@ -1253,13 +1361,13 @@ files = [ [[package]] name = "packaging" -version = "23.2" +version = "24.0" description = "Core utilities for Python packages" optional = false python-versions = ">=3.7" files = [ - {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"}, - {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, + {file = "packaging-24.0-py3-none-any.whl", hash = "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5"}, + {file = "packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"}, ] [[package]] @@ -1279,6 +1387,40 @@ bcrypt = ["bcrypt (>=3.1.0)"] build-docs = ["cloud-sptheme (>=1.10.1)", "sphinx (>=1.6)", "sphinxcontrib-fulltoc (>=1.2.0)"] totp = ["cryptography"] +[[package]] +name = "pendulum" +version = "2.1.2" +description = "Python datetimes made easy" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ + {file = "pendulum-2.1.2-cp27-cp27m-macosx_10_15_x86_64.whl", hash = "sha256:b6c352f4bd32dff1ea7066bd31ad0f71f8d8100b9ff709fb343f3b86cee43efe"}, + {file = "pendulum-2.1.2-cp27-cp27m-win_amd64.whl", hash = "sha256:318f72f62e8e23cd6660dbafe1e346950281a9aed144b5c596b2ddabc1d19739"}, + {file = "pendulum-2.1.2-cp35-cp35m-macosx_10_15_x86_64.whl", hash = "sha256:0731f0c661a3cb779d398803655494893c9f581f6488048b3fb629c2342b5394"}, + {file = "pendulum-2.1.2-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:3481fad1dc3f6f6738bd575a951d3c15d4b4ce7c82dce37cf8ac1483fde6e8b0"}, + {file = "pendulum-2.1.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:9702069c694306297ed362ce7e3c1ef8404ac8ede39f9b28b7c1a7ad8c3959e3"}, + {file = "pendulum-2.1.2-cp35-cp35m-win_amd64.whl", hash = "sha256:fb53ffa0085002ddd43b6ca61a7b34f2d4d7c3ed66f931fe599e1a531b42af9b"}, + {file = "pendulum-2.1.2-cp36-cp36m-macosx_10_15_x86_64.whl", hash = "sha256:c501749fdd3d6f9e726086bf0cd4437281ed47e7bca132ddb522f86a1645d360"}, + {file = "pendulum-2.1.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:c807a578a532eeb226150d5006f156632df2cc8c5693d778324b43ff8c515dd0"}, + {file = "pendulum-2.1.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:2d1619a721df661e506eff8db8614016f0720ac171fe80dda1333ee44e684087"}, + {file = "pendulum-2.1.2-cp36-cp36m-win_amd64.whl", hash = "sha256:f888f2d2909a414680a29ae74d0592758f2b9fcdee3549887779cd4055e975db"}, + {file = "pendulum-2.1.2-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:e95d329384717c7bf627bf27e204bc3b15c8238fa8d9d9781d93712776c14002"}, + {file = "pendulum-2.1.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:4c9c689747f39d0d02a9f94fcee737b34a5773803a64a5fdb046ee9cac7442c5"}, + {file = "pendulum-2.1.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:1245cd0075a3c6d889f581f6325dd8404aca5884dea7223a5566c38aab94642b"}, + {file = "pendulum-2.1.2-cp37-cp37m-win_amd64.whl", hash = "sha256:db0a40d8bcd27b4fb46676e8eb3c732c67a5a5e6bfab8927028224fbced0b40b"}, + {file = "pendulum-2.1.2-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:f5e236e7730cab1644e1b87aca3d2ff3e375a608542e90fe25685dae46310116"}, + {file = "pendulum-2.1.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:de42ea3e2943171a9e95141f2eecf972480636e8e484ccffaf1e833929e9e052"}, + {file = "pendulum-2.1.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7c5ec650cb4bec4c63a89a0242cc8c3cebcec92fcfe937c417ba18277d8560be"}, + {file = "pendulum-2.1.2-cp38-cp38-win_amd64.whl", hash = "sha256:33fb61601083f3eb1d15edeb45274f73c63b3c44a8524703dc143f4212bf3269"}, + {file = "pendulum-2.1.2-cp39-cp39-manylinux1_i686.whl", hash = "sha256:29c40a6f2942376185728c9a0347d7c0f07905638c83007e1d262781f1e6953a"}, + {file = "pendulum-2.1.2-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:94b1fc947bfe38579b28e1cccb36f7e28a15e841f30384b5ad6c5e31055c85d7"}, + {file = "pendulum-2.1.2.tar.gz", hash = "sha256:b06a0ca1bfe41c990bbf0c029f0b6501a7f2ec4e38bfec730712015e8860f207"}, +] + +[package.dependencies] +python-dateutil = ">=2.6,<3.0" +pytzdata = ">=2020.1" + [[package]] name = "pendulum" version = "3.0.0" @@ -1457,13 +1599,13 @@ idna = ["idna (>=2.1)"] [[package]] name = "pycparser" -version = "2.21" +version = "2.22" description = "C parser in Python" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=3.8" files = [ - {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"}, - {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, + {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"}, + {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, ] [[package]] @@ -1509,47 +1651,47 @@ files = [ [[package]] name = "pydantic" -version = "1.10.13" +version = "1.10.15" description = "Data validation and settings management using python type hints" optional = false python-versions = ">=3.7" files = [ - {file = "pydantic-1.10.13-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:efff03cc7a4f29d9009d1c96ceb1e7a70a65cfe86e89d34e4a5f2ab1e5693737"}, - {file = "pydantic-1.10.13-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3ecea2b9d80e5333303eeb77e180b90e95eea8f765d08c3d278cd56b00345d01"}, - {file = "pydantic-1.10.13-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1740068fd8e2ef6eb27a20e5651df000978edce6da6803c2bef0bc74540f9548"}, - {file = "pydantic-1.10.13-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:84bafe2e60b5e78bc64a2941b4c071a4b7404c5c907f5f5a99b0139781e69ed8"}, - {file = "pydantic-1.10.13-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:bc0898c12f8e9c97f6cd44c0ed70d55749eaf783716896960b4ecce2edfd2d69"}, - {file = "pydantic-1.10.13-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:654db58ae399fe6434e55325a2c3e959836bd17a6f6a0b6ca8107ea0571d2e17"}, - {file = "pydantic-1.10.13-cp310-cp310-win_amd64.whl", hash = "sha256:75ac15385a3534d887a99c713aa3da88a30fbd6204a5cd0dc4dab3d770b9bd2f"}, - {file = "pydantic-1.10.13-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c553f6a156deb868ba38a23cf0df886c63492e9257f60a79c0fd8e7173537653"}, - {file = "pydantic-1.10.13-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5e08865bc6464df8c7d61439ef4439829e3ab62ab1669cddea8dd00cd74b9ffe"}, - {file = "pydantic-1.10.13-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e31647d85a2013d926ce60b84f9dd5300d44535a9941fe825dc349ae1f760df9"}, - {file = "pydantic-1.10.13-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:210ce042e8f6f7c01168b2d84d4c9eb2b009fe7bf572c2266e235edf14bacd80"}, - {file = "pydantic-1.10.13-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:8ae5dd6b721459bfa30805f4c25880e0dd78fc5b5879f9f7a692196ddcb5a580"}, - {file = "pydantic-1.10.13-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f8e81fc5fb17dae698f52bdd1c4f18b6ca674d7068242b2aff075f588301bbb0"}, - {file = "pydantic-1.10.13-cp311-cp311-win_amd64.whl", hash = "sha256:61d9dce220447fb74f45e73d7ff3b530e25db30192ad8d425166d43c5deb6df0"}, - {file = "pydantic-1.10.13-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4b03e42ec20286f052490423682016fd80fda830d8e4119f8ab13ec7464c0132"}, - {file = "pydantic-1.10.13-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f59ef915cac80275245824e9d771ee939133be38215555e9dc90c6cb148aaeb5"}, - {file = "pydantic-1.10.13-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5a1f9f747851338933942db7af7b6ee8268568ef2ed86c4185c6ef4402e80ba8"}, - {file = "pydantic-1.10.13-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:97cce3ae7341f7620a0ba5ef6cf043975cd9d2b81f3aa5f4ea37928269bc1b87"}, - {file = "pydantic-1.10.13-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:854223752ba81e3abf663d685f105c64150873cc6f5d0c01d3e3220bcff7d36f"}, - {file = "pydantic-1.10.13-cp37-cp37m-win_amd64.whl", hash = "sha256:b97c1fac8c49be29486df85968682b0afa77e1b809aff74b83081cc115e52f33"}, - {file = "pydantic-1.10.13-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c958d053453a1c4b1c2062b05cd42d9d5c8eb67537b8d5a7e3c3032943ecd261"}, - {file = "pydantic-1.10.13-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4c5370a7edaac06daee3af1c8b1192e305bc102abcbf2a92374b5bc793818599"}, - {file = "pydantic-1.10.13-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d6f6e7305244bddb4414ba7094ce910560c907bdfa3501e9db1a7fd7eaea127"}, - {file = "pydantic-1.10.13-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d3a3c792a58e1622667a2837512099eac62490cdfd63bd407993aaf200a4cf1f"}, - {file = "pydantic-1.10.13-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:c636925f38b8db208e09d344c7aa4f29a86bb9947495dd6b6d376ad10334fb78"}, - {file = "pydantic-1.10.13-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:678bcf5591b63cc917100dc50ab6caebe597ac67e8c9ccb75e698f66038ea953"}, - {file = "pydantic-1.10.13-cp38-cp38-win_amd64.whl", hash = "sha256:6cf25c1a65c27923a17b3da28a0bdb99f62ee04230c931d83e888012851f4e7f"}, - {file = "pydantic-1.10.13-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8ef467901d7a41fa0ca6db9ae3ec0021e3f657ce2c208e98cd511f3161c762c6"}, - {file = "pydantic-1.10.13-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:968ac42970f57b8344ee08837b62f6ee6f53c33f603547a55571c954a4225691"}, - {file = "pydantic-1.10.13-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9849f031cf8a2f0a928fe885e5a04b08006d6d41876b8bbd2fc68a18f9f2e3fd"}, - {file = "pydantic-1.10.13-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:56e3ff861c3b9c6857579de282ce8baabf443f42ffba355bf070770ed63e11e1"}, - {file = "pydantic-1.10.13-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f00790179497767aae6bcdc36355792c79e7bbb20b145ff449700eb076c5f96"}, - {file = "pydantic-1.10.13-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:75b297827b59bc229cac1a23a2f7a4ac0031068e5be0ce385be1462e7e17a35d"}, - {file = "pydantic-1.10.13-cp39-cp39-win_amd64.whl", hash = "sha256:e70ca129d2053fb8b728ee7d1af8e553a928d7e301a311094b8a0501adc8763d"}, - {file = "pydantic-1.10.13-py3-none-any.whl", hash = "sha256:b87326822e71bd5f313e7d3bfdc77ac3247035ac10b0c0618bd99dcf95b1e687"}, - {file = "pydantic-1.10.13.tar.gz", hash = "sha256:32c8b48dcd3b2ac4e78b0ba4af3a2c2eb6048cb75202f0ea7b34feb740efc340"}, + {file = "pydantic-1.10.15-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:22ed12ee588b1df028a2aa5d66f07bf8f8b4c8579c2e96d5a9c1f96b77f3bb55"}, + {file = "pydantic-1.10.15-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:75279d3cac98186b6ebc2597b06bcbc7244744f6b0b44a23e4ef01e5683cc0d2"}, + {file = "pydantic-1.10.15-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50f1666a9940d3d68683c9d96e39640f709d7a72ff8702987dab1761036206bb"}, + {file = "pydantic-1.10.15-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82790d4753ee5d00739d6cb5cf56bceb186d9d6ce134aca3ba7befb1eedbc2c8"}, + {file = "pydantic-1.10.15-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:d207d5b87f6cbefbdb1198154292faee8017d7495a54ae58db06762004500d00"}, + {file = "pydantic-1.10.15-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e49db944fad339b2ccb80128ffd3f8af076f9f287197a480bf1e4ca053a866f0"}, + {file = "pydantic-1.10.15-cp310-cp310-win_amd64.whl", hash = "sha256:d3b5c4cbd0c9cb61bbbb19ce335e1f8ab87a811f6d589ed52b0254cf585d709c"}, + {file = "pydantic-1.10.15-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c3d5731a120752248844676bf92f25a12f6e45425e63ce22e0849297a093b5b0"}, + {file = "pydantic-1.10.15-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c365ad9c394f9eeffcb30a82f4246c0006417f03a7c0f8315d6211f25f7cb654"}, + {file = "pydantic-1.10.15-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3287e1614393119c67bd4404f46e33ae3be3ed4cd10360b48d0a4459f420c6a3"}, + {file = "pydantic-1.10.15-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:be51dd2c8596b25fe43c0a4a59c2bee4f18d88efb8031188f9e7ddc6b469cf44"}, + {file = "pydantic-1.10.15-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:6a51a1dd4aa7b3f1317f65493a182d3cff708385327c1c82c81e4a9d6d65b2e4"}, + {file = "pydantic-1.10.15-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4e316e54b5775d1eb59187f9290aeb38acf620e10f7fd2f776d97bb788199e53"}, + {file = "pydantic-1.10.15-cp311-cp311-win_amd64.whl", hash = "sha256:0d142fa1b8f2f0ae11ddd5e3e317dcac060b951d605fda26ca9b234b92214986"}, + {file = "pydantic-1.10.15-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7ea210336b891f5ea334f8fc9f8f862b87acd5d4a0cbc9e3e208e7aa1775dabf"}, + {file = "pydantic-1.10.15-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3453685ccd7140715e05f2193d64030101eaad26076fad4e246c1cc97e1bb30d"}, + {file = "pydantic-1.10.15-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bea1f03b8d4e8e86702c918ccfd5d947ac268f0f0cc6ed71782e4b09353b26f"}, + {file = "pydantic-1.10.15-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:005655cabc29081de8243126e036f2065bd7ea5b9dff95fde6d2c642d39755de"}, + {file = "pydantic-1.10.15-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:af9850d98fc21e5bc24ea9e35dd80a29faf6462c608728a110c0a30b595e58b7"}, + {file = "pydantic-1.10.15-cp37-cp37m-win_amd64.whl", hash = "sha256:d31ee5b14a82c9afe2bd26aaa405293d4237d0591527d9129ce36e58f19f95c1"}, + {file = "pydantic-1.10.15-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5e09c19df304b8123938dc3c53d3d3be6ec74b9d7d0d80f4f4b5432ae16c2022"}, + {file = "pydantic-1.10.15-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7ac9237cd62947db00a0d16acf2f3e00d1ae9d3bd602b9c415f93e7a9fc10528"}, + {file = "pydantic-1.10.15-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:584f2d4c98ffec420e02305cf675857bae03c9d617fcfdc34946b1160213a948"}, + {file = "pydantic-1.10.15-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bbc6989fad0c030bd70a0b6f626f98a862224bc2b1e36bfc531ea2facc0a340c"}, + {file = "pydantic-1.10.15-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:d573082c6ef99336f2cb5b667b781d2f776d4af311574fb53d908517ba523c22"}, + {file = "pydantic-1.10.15-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6bd7030c9abc80134087d8b6e7aa957e43d35714daa116aced57269a445b8f7b"}, + {file = "pydantic-1.10.15-cp38-cp38-win_amd64.whl", hash = "sha256:3350f527bb04138f8aff932dc828f154847fbdc7a1a44c240fbfff1b57f49a12"}, + {file = "pydantic-1.10.15-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:51d405b42f1b86703555797270e4970a9f9bd7953f3990142e69d1037f9d9e51"}, + {file = "pydantic-1.10.15-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a980a77c52723b0dc56640ced396b73a024d4b74f02bcb2d21dbbac1debbe9d0"}, + {file = "pydantic-1.10.15-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67f1a1fb467d3f49e1708a3f632b11c69fccb4e748a325d5a491ddc7b5d22383"}, + {file = "pydantic-1.10.15-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:676ed48f2c5bbad835f1a8ed8a6d44c1cd5a21121116d2ac40bd1cd3619746ed"}, + {file = "pydantic-1.10.15-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:92229f73400b80c13afcd050687f4d7e88de9234d74b27e6728aa689abcf58cc"}, + {file = "pydantic-1.10.15-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2746189100c646682eff0bce95efa7d2e203420d8e1c613dc0c6b4c1d9c1fde4"}, + {file = "pydantic-1.10.15-cp39-cp39-win_amd64.whl", hash = "sha256:394f08750bd8eaad714718812e7fab615f873b3cdd0b9d84e76e51ef3b50b6b7"}, + {file = "pydantic-1.10.15-py3-none-any.whl", hash = "sha256:28e552a060ba2740d0d2aabe35162652c1459a0b9069fe0db7f4ee0e18e74d58"}, + {file = "pydantic-1.10.15.tar.gz", hash = "sha256:ca832e124eda231a60a041da4f013e3ff24949d94a01154b137fc2f2a43c3ffb"}, ] [package.dependencies] @@ -1598,18 +1740,29 @@ docs = ["Sphinx[docs] (>=6,<8)", "sphinx-autodoc-typehints[docs] (>=1.21.0,<2.0. [[package]] name = "python-dateutil" -version = "2.8.2" +version = "2.9.0.post0" description = "Extensions to the standard Python datetime module" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" files = [ - {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, - {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, + {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, + {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, ] [package.dependencies] six = ">=1.5" +[[package]] +name = "pytzdata" +version = "2020.1" +description = "The Olson timezone database for Python." +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "pytzdata-2020.1-py2.py3-none-any.whl", hash = "sha256:e1e14750bcf95016381e4d472bad004eef710f2d6417240904070b3d6654485f"}, + {file = "pytzdata-2020.1.tar.gz", hash = "sha256:3efa13b335a00a8de1d345ae41ec78dd11c9f8807f522d39850f2dd828681540"}, +] + [[package]] name = "pyyaml" version = "6.0.1" @@ -1635,6 +1788,7 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, @@ -1703,27 +1857,27 @@ test = ["bandit", "beautifulsoup4", "chardet (==3.*)", "coverage", "docutils", " [[package]] name = "sanic-beskar" -version = "2.3.2" +version = "2.3.3.post1" description = "Strong, Simple, (now async!) and Precise security for Sanic APIs" optional = false -python-versions = ">=3.8,<3.13" +python-versions = ">=3.9,<4.0" files = [ - {file = "sanic_beskar-2.3.2-py3-none-any.whl", hash = "sha256:745c4c74b49fa3ac142a1bd31f88b2918aff710a655da677e912ce056e4697c5"}, - {file = "sanic_beskar-2.3.2.tar.gz", hash = "sha256:1266fec8523ddddb98ed46a8eab374d6e86ccd0795d2c86530c195a3afcd598b"}, + {file = "sanic_beskar-2.3.3.post1-py3-none-any.whl", hash = "sha256:25b25fcc8dbd9d5214ada9fd613d6d41f02a76f001e919648b122bf4c6689bb1"}, + {file = "sanic_beskar-2.3.3.post1.tar.gz", hash = "sha256:31a5a09b943772555634eb627d97e3e374160fdcfe5dde57dabc4f5e3e9ea012"}, ] [package.dependencies] -cryptography = ">=39.0.0,<42.0" +cryptography = ">=39.0.0" jinja2 = ">=3.1.2" passlib = ">=1.7" pendulum = [ - {version = ">=2.1,<=3.1", markers = "python_version >= \"3.8\" and python_version < \"3.12\""}, + {version = ">=2.1,<3.0", markers = "python_version >= \"3.9\" and python_version < \"3.12\""}, {version = "==3.0.*", markers = "python_version >= \"3.12\""}, ] -py-buzz = ">3.2.0,<5.0" +py-buzz = ">3.2.0" pyjwt = ">=2.6" pyseto = ">=1.6.9" -sanic = ">=22.6.0,<=24.0" +sanic = ">=22.6.0" [[package]] name = "sanic-ext" @@ -1744,7 +1898,7 @@ dev = ["Jinja2", "black (>=21.4b2)", "coverage", "flake8 (>=3.7.7)", "isort (>=5 test = ["Jinja2", "coverage", "pytest", "pytest-asyncio", "pytest-cov", "sanic-testing (>=22.9.0)", "tox"] [[package]] -name = "sanic-limiter" +name = "sanic_limiter" version = "0.1.4" description = "Provides rate limiting features for Sanic. Supports in-memory, redis and memcache as storage." optional = false @@ -1776,13 +1930,13 @@ files = [ [[package]] name = "sentry-sdk" -version = "1.39.2" +version = "1.44.1" description = "Python client for Sentry (https://sentry.io)" optional = false python-versions = "*" files = [ - {file = "sentry-sdk-1.39.2.tar.gz", hash = "sha256:24c83b0b41c887d33328a9166f5950dc37ad58f01c9f2fbff6b87a6f1094170c"}, - {file = "sentry_sdk-1.39.2-py2.py3-none-any.whl", hash = "sha256:acaf597b30258fc7663063b291aa99e58f3096e91fe1e6634f4b79f9c1943e8e"}, + {file = "sentry-sdk-1.44.1.tar.gz", hash = "sha256:24e6a53eeabffd2f95d952aa35ca52f0f4201d17f820ac9d3ff7244c665aaf68"}, + {file = "sentry_sdk-1.44.1-py2.py3-none-any.whl", hash = "sha256:5f75eb91d8ab6037c754a87b8501cc581b2827e923682f593bed3539ce5b3999"}, ] [package.dependencies] @@ -1797,6 +1951,7 @@ asyncpg = ["asyncpg (>=0.23)"] beam = ["apache-beam (>=2.12)"] bottle = ["bottle (>=0.12.13)"] celery = ["celery (>=3)"] +celery-redbeat = ["celery-redbeat (>=2)"] chalice = ["chalice (>=1.16.0)"] clickhouse-driver = ["clickhouse-driver (>=0.2.0)"] django = ["django (>=1.8)"] @@ -1807,9 +1962,10 @@ grpcio = ["grpcio (>=1.21.1)"] httpx = ["httpx (>=0.16.0)"] huey = ["huey (>=2)"] loguru = ["loguru (>=0.5)"] +openai = ["openai (>=1.0.0)", "tiktoken (>=0.3.0)"] opentelemetry = ["opentelemetry-distro (>=0.35b0)"] opentelemetry-experimental = ["opentelemetry-distro (>=0.40b0,<1.0)", "opentelemetry-instrumentation-aiohttp-client (>=0.40b0,<1.0)", "opentelemetry-instrumentation-django (>=0.40b0,<1.0)", "opentelemetry-instrumentation-fastapi (>=0.40b0,<1.0)", "opentelemetry-instrumentation-flask (>=0.40b0,<1.0)", "opentelemetry-instrumentation-requests (>=0.40b0,<1.0)", "opentelemetry-instrumentation-sqlite3 (>=0.40b0,<1.0)", "opentelemetry-instrumentation-urllib (>=0.40b0,<1.0)"] -pure-eval = ["asttokens", "executing", "pure_eval"] +pure-eval = ["asttokens", "executing", "pure-eval"] pymongo = ["pymongo (>=3.1)"] pyspark = ["pyspark (>=2.4.4)"] quart = ["blinker (>=1.1)", "quart (>=0.16.1)"] @@ -1820,6 +1976,22 @@ starlette = ["starlette (>=0.19.1)"] starlite = ["starlite (>=1.48)"] tornado = ["tornado (>=5)"] +[[package]] +name = "setuptools" +version = "69.2.0" +description = "Easily download, build, install, upgrade, and uninstall Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "setuptools-69.2.0-py3-none-any.whl", hash = "sha256:c21c49fb1042386df081cb5d86759792ab89efca84cf114889191cd09aacc80c"}, + {file = "setuptools-69.2.0.tar.gz", hash = "sha256:0ff4183f8f42cd8fa3acea16c45205521a4ef28f73c6391d8a25e92893134f2e"}, +] + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "mypy (==1.9)", "packaging (>=23.2)", "pip (>=19.1)", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff (>=0.2.1)", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.2)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] + [[package]] name = "six" version = "1.16.0" @@ -1844,60 +2016,60 @@ files = [ [[package]] name = "sqlalchemy" -version = "2.0.25" +version = "2.0.29" description = "Database Abstraction Library" optional = false python-versions = ">=3.7" files = [ - {file = "SQLAlchemy-2.0.25-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4344d059265cc8b1b1be351bfb88749294b87a8b2bbe21dfbe066c4199541ebd"}, - {file = "SQLAlchemy-2.0.25-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6f9e2e59cbcc6ba1488404aad43de005d05ca56e069477b33ff74e91b6319735"}, - {file = "SQLAlchemy-2.0.25-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:84daa0a2055df9ca0f148a64fdde12ac635e30edbca80e87df9b3aaf419e144a"}, - {file = "SQLAlchemy-2.0.25-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc8b7dabe8e67c4832891a5d322cec6d44ef02f432b4588390017f5cec186a84"}, - {file = "SQLAlchemy-2.0.25-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:f5693145220517b5f42393e07a6898acdfe820e136c98663b971906120549da5"}, - {file = "SQLAlchemy-2.0.25-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:db854730a25db7c956423bb9fb4bdd1216c839a689bf9cc15fada0a7fb2f4570"}, - {file = "SQLAlchemy-2.0.25-cp310-cp310-win32.whl", hash = "sha256:14a6f68e8fc96e5e8f5647ef6cda6250c780612a573d99e4d881581432ef1669"}, - {file = "SQLAlchemy-2.0.25-cp310-cp310-win_amd64.whl", hash = "sha256:87f6e732bccd7dcf1741c00f1ecf33797383128bd1c90144ac8adc02cbb98643"}, - {file = "SQLAlchemy-2.0.25-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:342d365988ba88ada8af320d43df4e0b13a694dbd75951f537b2d5e4cb5cd002"}, - {file = "SQLAlchemy-2.0.25-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f37c0caf14b9e9b9e8f6dbc81bc56db06acb4363eba5a633167781a48ef036ed"}, - {file = "SQLAlchemy-2.0.25-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa9373708763ef46782d10e950b49d0235bfe58facebd76917d3f5cbf5971aed"}, - {file = "SQLAlchemy-2.0.25-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d24f571990c05f6b36a396218f251f3e0dda916e0c687ef6fdca5072743208f5"}, - {file = "SQLAlchemy-2.0.25-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:75432b5b14dc2fff43c50435e248b45c7cdadef73388e5610852b95280ffd0e9"}, - {file = "SQLAlchemy-2.0.25-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:884272dcd3ad97f47702965a0e902b540541890f468d24bd1d98bcfe41c3f018"}, - {file = "SQLAlchemy-2.0.25-cp311-cp311-win32.whl", hash = "sha256:e607cdd99cbf9bb80391f54446b86e16eea6ad309361942bf88318bcd452363c"}, - {file = "SQLAlchemy-2.0.25-cp311-cp311-win_amd64.whl", hash = "sha256:7d505815ac340568fd03f719446a589162d55c52f08abd77ba8964fbb7eb5b5f"}, - {file = "SQLAlchemy-2.0.25-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:0dacf67aee53b16f365c589ce72e766efaabd2b145f9de7c917777b575e3659d"}, - {file = "SQLAlchemy-2.0.25-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b801154027107461ee992ff4b5c09aa7cc6ec91ddfe50d02bca344918c3265c6"}, - {file = "SQLAlchemy-2.0.25-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59a21853f5daeb50412d459cfb13cb82c089ad4c04ec208cd14dddd99fc23b39"}, - {file = "SQLAlchemy-2.0.25-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:29049e2c299b5ace92cbed0c1610a7a236f3baf4c6b66eb9547c01179f638ec5"}, - {file = "SQLAlchemy-2.0.25-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b64b183d610b424a160b0d4d880995e935208fc043d0302dd29fee32d1ee3f95"}, - {file = "SQLAlchemy-2.0.25-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4f7a7d7fcc675d3d85fbf3b3828ecd5990b8d61bd6de3f1b260080b3beccf215"}, - {file = "SQLAlchemy-2.0.25-cp312-cp312-win32.whl", hash = "sha256:cf18ff7fc9941b8fc23437cc3e68ed4ebeff3599eec6ef5eebf305f3d2e9a7c2"}, - {file = "SQLAlchemy-2.0.25-cp312-cp312-win_amd64.whl", hash = "sha256:91f7d9d1c4dd1f4f6e092874c128c11165eafcf7c963128f79e28f8445de82d5"}, - {file = "SQLAlchemy-2.0.25-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:bb209a73b8307f8fe4fe46f6ad5979649be01607f11af1eb94aa9e8a3aaf77f0"}, - {file = "SQLAlchemy-2.0.25-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:798f717ae7c806d67145f6ae94dc7c342d3222d3b9a311a784f371a4333212c7"}, - {file = "SQLAlchemy-2.0.25-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5fdd402169aa00df3142149940b3bf9ce7dde075928c1886d9a1df63d4b8de62"}, - {file = "SQLAlchemy-2.0.25-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:0d3cab3076af2e4aa5693f89622bef7fa770c6fec967143e4da7508b3dceb9b9"}, - {file = "SQLAlchemy-2.0.25-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:74b080c897563f81062b74e44f5a72fa44c2b373741a9ade701d5f789a10ba23"}, - {file = "SQLAlchemy-2.0.25-cp37-cp37m-win32.whl", hash = "sha256:87d91043ea0dc65ee583026cb18e1b458d8ec5fc0a93637126b5fc0bc3ea68c4"}, - {file = "SQLAlchemy-2.0.25-cp37-cp37m-win_amd64.whl", hash = "sha256:75f99202324383d613ddd1f7455ac908dca9c2dd729ec8584c9541dd41822a2c"}, - {file = "SQLAlchemy-2.0.25-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:420362338681eec03f53467804541a854617faed7272fe71a1bfdb07336a381e"}, - {file = "SQLAlchemy-2.0.25-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7c88f0c7dcc5f99bdb34b4fd9b69b93c89f893f454f40219fe923a3a2fd11625"}, - {file = "SQLAlchemy-2.0.25-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3be4987e3ee9d9a380b66393b77a4cd6d742480c951a1c56a23c335caca4ce3"}, - {file = "SQLAlchemy-2.0.25-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2a159111a0f58fb034c93eeba211b4141137ec4b0a6e75789ab7a3ef3c7e7e3"}, - {file = "SQLAlchemy-2.0.25-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8b8cb63d3ea63b29074dcd29da4dc6a97ad1349151f2d2949495418fd6e48db9"}, - {file = "SQLAlchemy-2.0.25-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:736ea78cd06de6c21ecba7416499e7236a22374561493b456a1f7ffbe3f6cdb4"}, - {file = "SQLAlchemy-2.0.25-cp38-cp38-win32.whl", hash = "sha256:10331f129982a19df4284ceac6fe87353ca3ca6b4ca77ff7d697209ae0a5915e"}, - {file = "SQLAlchemy-2.0.25-cp38-cp38-win_amd64.whl", hash = "sha256:c55731c116806836a5d678a70c84cb13f2cedba920212ba7dcad53260997666d"}, - {file = "SQLAlchemy-2.0.25-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:605b6b059f4b57b277f75ace81cc5bc6335efcbcc4ccb9066695e515dbdb3900"}, - {file = "SQLAlchemy-2.0.25-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:665f0a3954635b5b777a55111ababf44b4fc12b1f3ba0a435b602b6387ffd7cf"}, - {file = "SQLAlchemy-2.0.25-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ecf6d4cda1f9f6cb0b45803a01ea7f034e2f1aed9475e883410812d9f9e3cfcf"}, - {file = "SQLAlchemy-2.0.25-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c51db269513917394faec5e5c00d6f83829742ba62e2ac4fa5c98d58be91662f"}, - {file = "SQLAlchemy-2.0.25-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:790f533fa5c8901a62b6fef5811d48980adeb2f51f1290ade8b5e7ba990ba3de"}, - {file = "SQLAlchemy-2.0.25-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1b1180cda6df7af84fe72e4530f192231b1f29a7496951db4ff38dac1687202d"}, - {file = "SQLAlchemy-2.0.25-cp39-cp39-win32.whl", hash = "sha256:555651adbb503ac7f4cb35834c5e4ae0819aab2cd24857a123370764dc7d7e24"}, - {file = "SQLAlchemy-2.0.25-cp39-cp39-win_amd64.whl", hash = "sha256:dc55990143cbd853a5d038c05e79284baedf3e299661389654551bd02a6a68d7"}, - {file = "SQLAlchemy-2.0.25-py3-none-any.whl", hash = "sha256:a86b4240e67d4753dc3092d9511886795b3c2852abe599cffe108952f7af7ac3"}, - {file = "SQLAlchemy-2.0.25.tar.gz", hash = "sha256:a2c69a7664fb2d54b8682dd774c3b54f67f84fa123cf84dda2a5f40dcaa04e08"}, + {file = "SQLAlchemy-2.0.29-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4c142852ae192e9fe5aad5c350ea6befe9db14370b34047e1f0f7cf99e63c63b"}, + {file = "SQLAlchemy-2.0.29-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:99a1e69d4e26f71e750e9ad6fdc8614fbddb67cfe2173a3628a2566034e223c7"}, + {file = "SQLAlchemy-2.0.29-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ef3fbccb4058355053c51b82fd3501a6e13dd808c8d8cd2561e610c5456013c"}, + {file = "SQLAlchemy-2.0.29-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d6753305936eddc8ed190e006b7bb33a8f50b9854823485eed3a886857ab8d1"}, + {file = "SQLAlchemy-2.0.29-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0f3ca96af060a5250a8ad5a63699180bc780c2edf8abf96c58af175921df847a"}, + {file = "SQLAlchemy-2.0.29-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c4520047006b1d3f0d89e0532978c0688219857eb2fee7c48052560ae76aca1e"}, + {file = "SQLAlchemy-2.0.29-cp310-cp310-win32.whl", hash = "sha256:b2a0e3cf0caac2085ff172c3faacd1e00c376e6884b5bc4dd5b6b84623e29e4f"}, + {file = "SQLAlchemy-2.0.29-cp310-cp310-win_amd64.whl", hash = "sha256:01d10638a37460616708062a40c7b55f73e4d35eaa146781c683e0fa7f6c43fb"}, + {file = "SQLAlchemy-2.0.29-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:308ef9cb41d099099fffc9d35781638986870b29f744382904bf9c7dadd08513"}, + {file = "SQLAlchemy-2.0.29-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:296195df68326a48385e7a96e877bc19aa210e485fa381c5246bc0234c36c78e"}, + {file = "SQLAlchemy-2.0.29-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a13b917b4ffe5a0a31b83d051d60477819ddf18276852ea68037a144a506efb9"}, + {file = "SQLAlchemy-2.0.29-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f6d971255d9ddbd3189e2e79d743ff4845c07f0633adfd1de3f63d930dbe673"}, + {file = "SQLAlchemy-2.0.29-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:61405ea2d563407d316c63a7b5271ae5d274a2a9fbcd01b0aa5503635699fa1e"}, + {file = "SQLAlchemy-2.0.29-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:de7202ffe4d4a8c1e3cde1c03e01c1a3772c92858837e8f3879b497158e4cb44"}, + {file = "SQLAlchemy-2.0.29-cp311-cp311-win32.whl", hash = "sha256:b5d7ed79df55a731749ce65ec20d666d82b185fa4898430b17cb90c892741520"}, + {file = "SQLAlchemy-2.0.29-cp311-cp311-win_amd64.whl", hash = "sha256:205f5a2b39d7c380cbc3b5dcc8f2762fb5bcb716838e2d26ccbc54330775b003"}, + {file = "SQLAlchemy-2.0.29-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d96710d834a6fb31e21381c6d7b76ec729bd08c75a25a5184b1089141356171f"}, + {file = "SQLAlchemy-2.0.29-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:52de4736404e53c5c6a91ef2698c01e52333988ebdc218f14c833237a0804f1b"}, + {file = "SQLAlchemy-2.0.29-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c7b02525ede2a164c5fa5014915ba3591730f2cc831f5be9ff3b7fd3e30958e"}, + {file = "SQLAlchemy-2.0.29-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0dfefdb3e54cd15f5d56fd5ae32f1da2d95d78319c1f6dfb9bcd0eb15d603d5d"}, + {file = "SQLAlchemy-2.0.29-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:a88913000da9205b13f6f195f0813b6ffd8a0c0c2bd58d499e00a30eb508870c"}, + {file = "SQLAlchemy-2.0.29-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fecd5089c4be1bcc37c35e9aa678938d2888845a134dd016de457b942cf5a758"}, + {file = "SQLAlchemy-2.0.29-cp312-cp312-win32.whl", hash = "sha256:8197d6f7a3d2b468861ebb4c9f998b9df9e358d6e1cf9c2a01061cb9b6cf4e41"}, + {file = "SQLAlchemy-2.0.29-cp312-cp312-win_amd64.whl", hash = "sha256:9b19836ccca0d321e237560e475fd99c3d8655d03da80c845c4da20dda31b6e1"}, + {file = "SQLAlchemy-2.0.29-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:87a1d53a5382cdbbf4b7619f107cc862c1b0a4feb29000922db72e5a66a5ffc0"}, + {file = "SQLAlchemy-2.0.29-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2a0732dffe32333211801b28339d2a0babc1971bc90a983e3035e7b0d6f06b93"}, + {file = "SQLAlchemy-2.0.29-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90453597a753322d6aa770c5935887ab1fc49cc4c4fdd436901308383d698b4b"}, + {file = "SQLAlchemy-2.0.29-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:ea311d4ee9a8fa67f139c088ae9f905fcf0277d6cd75c310a21a88bf85e130f5"}, + {file = "SQLAlchemy-2.0.29-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:5f20cb0a63a3e0ec4e169aa8890e32b949c8145983afa13a708bc4b0a1f30e03"}, + {file = "SQLAlchemy-2.0.29-cp37-cp37m-win32.whl", hash = "sha256:e5bbe55e8552019c6463709b39634a5fc55e080d0827e2a3a11e18eb73f5cdbd"}, + {file = "SQLAlchemy-2.0.29-cp37-cp37m-win_amd64.whl", hash = "sha256:c2f9c762a2735600654c654bf48dad388b888f8ce387b095806480e6e4ff6907"}, + {file = "SQLAlchemy-2.0.29-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7e614d7a25a43a9f54fcce4675c12761b248547f3d41b195e8010ca7297c369c"}, + {file = "SQLAlchemy-2.0.29-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:471fcb39c6adf37f820350c28aac4a7df9d3940c6548b624a642852e727ea586"}, + {file = "SQLAlchemy-2.0.29-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:988569c8732f54ad3234cf9c561364221a9e943b78dc7a4aaf35ccc2265f1930"}, + {file = "SQLAlchemy-2.0.29-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dddaae9b81c88083e6437de95c41e86823d150f4ee94bf24e158a4526cbead01"}, + {file = "SQLAlchemy-2.0.29-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:334184d1ab8f4c87f9652b048af3f7abea1c809dfe526fb0435348a6fef3d380"}, + {file = "SQLAlchemy-2.0.29-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:38b624e5cf02a69b113c8047cf7f66b5dfe4a2ca07ff8b8716da4f1b3ae81567"}, + {file = "SQLAlchemy-2.0.29-cp38-cp38-win32.whl", hash = "sha256:bab41acf151cd68bc2b466deae5deeb9e8ae9c50ad113444151ad965d5bf685b"}, + {file = "SQLAlchemy-2.0.29-cp38-cp38-win_amd64.whl", hash = "sha256:52c8011088305476691b8750c60e03b87910a123cfd9ad48576d6414b6ec2a1d"}, + {file = "SQLAlchemy-2.0.29-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3071ad498896907a5ef756206b9dc750f8e57352113c19272bdfdc429c7bd7de"}, + {file = "SQLAlchemy-2.0.29-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:dba622396a3170974f81bad49aacebd243455ec3cc70615aeaef9e9613b5bca5"}, + {file = "SQLAlchemy-2.0.29-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b184e3de58009cc0bf32e20f137f1ec75a32470f5fede06c58f6c355ed42a72"}, + {file = "SQLAlchemy-2.0.29-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c37f1050feb91f3d6c32f864d8e114ff5545a4a7afe56778d76a9aec62638ba"}, + {file = "SQLAlchemy-2.0.29-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bda7ce59b06d0f09afe22c56714c65c957b1068dee3d5e74d743edec7daba552"}, + {file = "SQLAlchemy-2.0.29-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:25664e18bef6dc45015b08f99c63952a53a0a61f61f2e48a9e70cec27e55f699"}, + {file = "SQLAlchemy-2.0.29-cp39-cp39-win32.whl", hash = "sha256:77d29cb6c34b14af8a484e831ab530c0f7188f8efed1c6a833a2c674bf3c26ec"}, + {file = "SQLAlchemy-2.0.29-cp39-cp39-win_amd64.whl", hash = "sha256:04c487305ab035a9548f573763915189fc0fe0824d9ba28433196f8436f1449c"}, + {file = "SQLAlchemy-2.0.29-py3-none-any.whl", hash = "sha256:dc4ee2d4ee43251905f88637d5281a8d52e916a021384ec10758826f5cbae305"}, + {file = "SQLAlchemy-2.0.29.tar.gz", hash = "sha256:bd9566b8e58cabd700bc367b60e90d9349cd16f0984973f98a9a09f9c64e86f0"}, ] [package.dependencies] @@ -1931,13 +2103,13 @@ sqlcipher = ["sqlcipher3_binary"] [[package]] name = "toolz" -version = "0.12.0" +version = "0.12.1" description = "List processing tools and functional utilities" optional = false -python-versions = ">=3.5" +python-versions = ">=3.7" files = [ - {file = "toolz-0.12.0-py3-none-any.whl", hash = "sha256:2059bd4148deb1884bb0eb770a3cde70e7f954cfbbdc2285f1f2de01fd21eb6f"}, - {file = "toolz-0.12.0.tar.gz", hash = "sha256:88c570861c440ee3f2f6037c4654613228ff40c93a6c25e0eba70d17282c6194"}, + {file = "toolz-0.12.1-py3-none-any.whl", hash = "sha256:d22731364c07d72eea0a0ad45bafb2c2937ab6fd38a3507bf55eae8744aa7d85"}, + {file = "toolz-0.12.1.tar.gz", hash = "sha256:ecca342664893f177a13dac0e6b41cbd8ac25a358e5f215316d43e2100224f4d"}, ] [[package]] @@ -1956,24 +2128,24 @@ html5tagger = ">=1.2.1" [[package]] name = "types-aiofiles" -version = "23.2.0.20240106" +version = "23.2.0.20240403" description = "Typing stubs for aiofiles" optional = false python-versions = ">=3.8" files = [ - {file = "types-aiofiles-23.2.0.20240106.tar.gz", hash = "sha256:ef4fa3072441c58beaadbd0d07ba18e89beff49c71648dd223e2ca861f3dac53"}, - {file = "types_aiofiles-23.2.0.20240106-py3-none-any.whl", hash = "sha256:7324f9a9f7200c1f4986a9e40a42b548290f707b967709f30b280e99fdacbd99"}, + {file = "types-aiofiles-23.2.0.20240403.tar.gz", hash = "sha256:1ffcf8f5f72b81f71139f754ea2610ab0017f27ba4fd771e187b07840ee49c0f"}, + {file = "types_aiofiles-23.2.0.20240403-py3-none-any.whl", hash = "sha256:adeeb4b999f19fda2dfe91c07857ff54701b6ee9b227b523a5a7be92125a2c5f"}, ] [[package]] name = "types-beautifulsoup4" -version = "4.12.0.20240106" +version = "4.12.0.20240229" description = "Typing stubs for beautifulsoup4" optional = false python-versions = ">=3.8" files = [ - {file = "types-beautifulsoup4-4.12.0.20240106.tar.gz", hash = "sha256:98d628985b71b140bd3bc22a8cb0ab603c2f2d08f20d37925965eb4a21739be8"}, - {file = "types_beautifulsoup4-4.12.0.20240106-py3-none-any.whl", hash = "sha256:cbdd60ab8aeac737ac014431b6e921b43e84279c0405fdd25a6900bb0e71da5b"}, + {file = "types-beautifulsoup4-4.12.0.20240229.tar.gz", hash = "sha256:e37e4cfa11b03b01775732e56d2c010cb24ee107786277bae6bc0fa3e305b686"}, + {file = "types_beautifulsoup4-4.12.0.20240229-py3-none-any.whl", hash = "sha256:000cdddb8aee4effb45a04be95654de8629fb8594a4f2f1231cff81108977324"}, ] [package.dependencies] @@ -1981,13 +2153,13 @@ types-html5lib = "*" [[package]] name = "types-html5lib" -version = "1.1.11.20240106" +version = "1.1.11.20240228" description = "Typing stubs for html5lib" optional = false python-versions = ">=3.8" files = [ - {file = "types-html5lib-1.1.11.20240106.tar.gz", hash = "sha256:fc3a1b18eb601b3eeaf92c900bd67675c0a4fa1dd1d2a2893ebdb46923547ee9"}, - {file = "types_html5lib-1.1.11.20240106-py3-none-any.whl", hash = "sha256:61993cb89220107481e0f1da65c388ff8cf3d8c5f6e8483c97559639a596b697"}, + {file = "types-html5lib-1.1.11.20240228.tar.gz", hash = "sha256:22736b7299e605ec4ba539d48691e905fd0c61c3ea610acc59922232dc84cede"}, + {file = "types_html5lib-1.1.11.20240228-py3-none-any.whl", hash = "sha256:af5de0125cb0fe5667543b158db83849b22e25c0e36c9149836b095548bf1020"}, ] [[package]] @@ -2003,24 +2175,24 @@ files = [ [[package]] name = "typing-extensions" -version = "4.9.0" +version = "4.11.0" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" files = [ - {file = "typing_extensions-4.9.0-py3-none-any.whl", hash = "sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd"}, - {file = "typing_extensions-4.9.0.tar.gz", hash = "sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783"}, + {file = "typing_extensions-4.11.0-py3-none-any.whl", hash = "sha256:c1f94d72897edaf4ce775bb7558d5b79d8126906a14ea5ed1635921406c0387a"}, + {file = "typing_extensions-4.11.0.tar.gz", hash = "sha256:83f085bd5ca59c80295fc2a82ab5dac679cbe02b9f33f7d83af68e241bea51b0"}, ] [[package]] name = "tzdata" -version = "2023.4" +version = "2024.1" description = "Provider of IANA time zone data" optional = false python-versions = ">=2" files = [ - {file = "tzdata-2023.4-py2.py3-none-any.whl", hash = "sha256:aa3ace4329eeacda5b7beb7ea08ece826c28d761cda36e747cfbf97996d39bf3"}, - {file = "tzdata-2023.4.tar.gz", hash = "sha256:dd54c94f294765522c77399649b4fefd95522479a664a0cec87f41bebc6148c9"}, + {file = "tzdata-2024.1-py2.py3-none-any.whl", hash = "sha256:9068bc196136463f5245e51efda838afa15aaeca9903f49050dfa2679db4d252"}, + {file = "tzdata-2024.1.tar.gz", hash = "sha256:2674120f8d891909751c38abcdfd386ac0a5a1127954fbc332af6b5ceae07efd"}, ] [[package]] @@ -2099,17 +2271,18 @@ files = [ [[package]] name = "urllib3" -version = "2.1.0" +version = "2.2.1" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.8" files = [ - {file = "urllib3-2.1.0-py3-none-any.whl", hash = "sha256:55901e917a5896a349ff771be919f8bd99aff50b79fe58fec595eb37bbc56bb3"}, - {file = "urllib3-2.1.0.tar.gz", hash = "sha256:df7aa8afb0148fa78488e7899b2c59b5f4ffcfa82e6c54ccb9dd37c1d7b52d54"}, + {file = "urllib3-2.2.1-py3-none-any.whl", hash = "sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d"}, + {file = "urllib3-2.2.1.tar.gz", hash = "sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19"}, ] [package.extras] brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +h2 = ["h2 (>=4,<5)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] @@ -2423,4 +2596,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = ">=3.11,<3.13" -content-hash = "adb47607ce6269e06fd1e7ef3b34f6f66df3dbc730d4609980a97a4e60977add" +content-hash = "674baa9bbe0b000407c2864d512462b9fa77f42ed81da2fea0fc8e8b944e403e" diff --git a/pyproject.toml b/pyproject.toml index 2ea4099..a6261c8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,6 +30,9 @@ types-ujson = "^5.9.0.0" types-aiofiles = "^23.2.0.20240106" types-beautifulsoup4 = "^4.12.0.20240106" +[tool.poetry.group.dev.dependencies] +setuptools = "^69.2.0" + [tool.pytest.ini_options] asyncio_mode = "auto" filterwarnings = [ diff --git a/requirements.txt b/requirements.txt index c93aedb..c90036c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -27,7 +27,7 @@ jinja2==3.1.3 ; python_version >= "3.11" and python_version < "3.13" limits==3.7.0 ; python_version >= "3.11" and python_version < "3.13" lxml==5.1.0 ; python_version >= "3.11" and python_version < "3.13" markupsafe==2.1.3 ; python_version >= "3.11" and python_version < "3.13" -multidict==6.0.4 ; python_version >= "3.11" and python_version < "3.13" +multidict==6.0.5 ; python_version >= "3.11" and python_version < "3.13" packaging==23.2 ; python_version >= "3.11" and python_version < "3.13" passlib==1.7.4 ; python_version >= "3.11" and python_version < "3.13" pendulum==3.0.0 ; python_version >= "3.11" and python_version < "3.13" From 6929056ccb2ba9635a9d1ee15c13704ac11367ed Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 8 Apr 2024 12:07:46 +0000 Subject: [PATCH 002/102] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index c90036c..62ff52d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,8 +2,8 @@ aiodns==3.1.1 ; (sys_platform == "linux" or sys_platform == "darwin") and python aiofiles==23.2.1 ; python_version >= "3.11" and python_version < "3.13" aiohttp[speedups]==3.9.1 ; python_version >= "3.11" and python_version < "3.13" aiosignal==1.3.1 ; python_version >= "3.11" and python_version < "3.13" -argon2-cffi-bindings==21.2.0 ; python_version >= "3.11" and python_version < "3.13" argon2-cffi==23.1.0 ; python_version >= "3.11" and python_version < "3.13" +argon2-cffi-bindings==21.2.0 ; python_version >= "3.11" and python_version < "3.13" asyncstdlib==3.12.0 ; python_version >= "3.11" and python_version < "3.13" attrs==23.2.0 ; python_version >= "3.11" and python_version < "3.13" beautifulsoup4==4.12.2 ; python_version >= "3.11" and python_version < "3.13" @@ -40,11 +40,11 @@ pyjwt==2.8.0 ; python_version >= "3.11" and python_version < "3.13" pyseto==1.7.7 ; python_version >= "3.11" and python_version < "3.13" python-dateutil==2.8.2 ; python_version >= "3.11" and python_version < "3.13" pyyaml==6.0.1 ; python_version >= "3.11" and python_version < "3.13" +sanic==23.12.1 ; python_version >= "3.11" and python_version < "3.13" sanic-beskar==2.3.2 ; python_version >= "3.11" and python_version < "3.13" sanic-ext==23.12.0 ; python_version >= "3.11" and python_version < "3.13" sanic-limiter @ git+https://github.com/Omegastick/sanic-limiter@843e13144aa21d843ce212a7c1db31b72ce8a103 ; python_version >= "3.11" and python_version < "3.13" sanic-routing==23.12.0 ; python_version >= "3.11" and python_version < "3.13" -sanic==23.12.1 ; python_version >= "3.11" and python_version < "3.13" sanic[ext]==23.12.1 ; python_version >= "3.11" and python_version < "3.13" sentry-sdk[sanic]==1.39.2 ; python_version >= "3.11" and python_version < "3.13" six==1.16.0 ; python_version >= "3.11" and python_version < "3.13" From 090111b3982905631b3d647266f5b7cf4287d5f8 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 8 Apr 2024 21:03:21 +0000 Subject: [PATCH 003/102] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/pre-commit-hooks: v4.5.0 → v4.6.0](https://github.com/pre-commit/pre-commit-hooks/compare/v4.5.0...v4.6.0) - [github.com/psf/black: 23.12.1 → 24.3.0](https://github.com/psf/black/compare/23.12.1...24.3.0) --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0412b79..952f021 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.5.0 + rev: v4.6.0 hooks: - id: trailing-whitespace - id: end-of-file-fixer @@ -12,7 +12,7 @@ repos: - id: check-toml - id: check-merge-conflict - repo: https://github.com/psf/black - rev: 23.12.1 + rev: 24.3.0 hooks: - id: black language_version: python3.11 From 0538e69018e4f7fc087a99e1ebcffb1b677b66f7 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 8 Apr 2024 21:03:28 +0000 Subject: [PATCH 004/102] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- api/backends/github.py | 26 ++++++++++++++++++-------- api/compat.py | 1 + api/models/announcements.py | 19 +++++++++++-------- app.py | 6 +++--- requirements.txt | 4 ++-- 5 files changed, 35 insertions(+), 21 deletions(-) diff --git a/api/backends/github.py b/api/backends/github.py index f4b0750..82a5c45 100644 --- a/api/backends/github.py +++ b/api/backends/github.py @@ -108,9 +108,9 @@ class Github(Backend): ) if team_view: - filter_contributor[ - "keys" - ] = f"{base_url.replace('api.', '')}/{filter_contributor['login']}.gpg" + filter_contributor["keys"] = ( + f"{base_url.replace('api.', '')}/{filter_contributor['login']}.gpg" + ) return Contributor(**filter_contributor) @@ -136,7 +136,9 @@ class Github(Backend): Returns: list[Release]: A list of Release objects. """ - list_releases_endpoint: str = f"{self.repositories_rest_endpoint}/{repository.owner}/{repository.name}/releases?per_page={per_page}&page={page}" + list_releases_endpoint: str = ( + f"{self.repositories_rest_endpoint}/{repository.owner}/{repository.name}/releases?per_page={per_page}&page={page}" + ) response: ClientResponse = await http_get( headers=self.headers, url=list_releases_endpoint ) @@ -162,7 +164,9 @@ class Github(Backend): Returns: Release: The Release object representing the retrieved release. """ - release_by_tag_endpoint: str = f"{self.repositories_rest_endpoint}/{repository.owner}/{repository.name}/releases/tags/{tag_name}" + release_by_tag_endpoint: str = ( + f"{self.repositories_rest_endpoint}/{repository.owner}/{repository.name}/releases/tags/{tag_name}" + ) response: ClientResponse = await http_get( headers=self.headers, url=release_by_tag_endpoint ) @@ -181,7 +185,9 @@ class Github(Backend): Returns: Release: The latest release for the given repository. """ - latest_release_endpoint: str = f"{self.repositories_rest_endpoint}/{repository.owner}/{repository.name}/releases/latest" + latest_release_endpoint: str = ( + f"{self.repositories_rest_endpoint}/{repository.owner}/{repository.name}/releases/latest" + ) response: ClientResponse = await http_get( headers=self.headers, url=latest_release_endpoint ) @@ -200,7 +206,9 @@ class Github(Backend): Returns: Release: The latest pre-release for the given repository. """ - list_releases_endpoint: str = f"{self.repositories_rest_endpoint}/{repository.owner}/{repository.name}/releases?per_page=10&page=1" + list_releases_endpoint: str = ( + f"{self.repositories_rest_endpoint}/{repository.owner}/{repository.name}/releases?per_page=10&page=1" + ) response: ClientResponse = await http_get( headers=self.headers, url=list_releases_endpoint ) @@ -223,7 +231,9 @@ class Github(Backend): list[Contributor]: A list of contributors for the given repository. """ - contributors_endpoint: str = f"{self.repositories_rest_endpoint}/{repository.owner}/{repository.name}/contributors" + contributors_endpoint: str = ( + f"{self.repositories_rest_endpoint}/{repository.owner}/{repository.name}/contributors" + ) response: ClientResponse = await http_get( headers=self.headers, url=contributors_endpoint ) diff --git a/api/compat.py b/api/compat.py index 993e02a..9520729 100644 --- a/api/compat.py +++ b/api/compat.py @@ -9,6 +9,7 @@ Routes: - GET /patches/: Retrieve a list of patches for a given release tag. """ + import os from sanic import Blueprint, Request from sanic.response import JSONResponse, json diff --git a/api/models/announcements.py b/api/models/announcements.py index 8604721..fd5f3df 100644 --- a/api/models/announcements.py +++ b/api/models/announcements.py @@ -21,14 +21,17 @@ class AnnouncementResponseModel(dict): id=announcement.id, author=announcement.author, title=announcement.title, - content=ContentFields( - message=announcement.message, - attachment_urls=[ - attachment.attachment_url for attachment in announcement.attachments - ], - ) - if announcement.message or announcement.attachments - else None, + content=( + ContentFields( + message=announcement.message, + attachment_urls=[ + attachment.attachment_url + for attachment in announcement.attachments + ], + ) + if announcement.message or announcement.attachments + else None + ), channel=announcement.channel, created_at=str(announcement.created_at), level=announcement.level, diff --git a/app.py b/app.py index af8a7a7..a699c84 100644 --- a/app.py +++ b/app.py @@ -75,9 +75,9 @@ async def add_cache_control(_, response): @app.on_response async def add_csp(_, response): - response.headers[ - "Content-Security-Policy" - ] = "default-src * 'unsafe-inline' 'unsafe-eval' data: blob:;" + response.headers["Content-Security-Policy"] = ( + "default-src * 'unsafe-inline' 'unsafe-eval' data: blob:;" + ) app.static( diff --git a/requirements.txt b/requirements.txt index c93aedb..9efe91f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,8 +2,8 @@ aiodns==3.1.1 ; (sys_platform == "linux" or sys_platform == "darwin") and python aiofiles==23.2.1 ; python_version >= "3.11" and python_version < "3.13" aiohttp[speedups]==3.9.1 ; python_version >= "3.11" and python_version < "3.13" aiosignal==1.3.1 ; python_version >= "3.11" and python_version < "3.13" -argon2-cffi-bindings==21.2.0 ; python_version >= "3.11" and python_version < "3.13" argon2-cffi==23.1.0 ; python_version >= "3.11" and python_version < "3.13" +argon2-cffi-bindings==21.2.0 ; python_version >= "3.11" and python_version < "3.13" asyncstdlib==3.12.0 ; python_version >= "3.11" and python_version < "3.13" attrs==23.2.0 ; python_version >= "3.11" and python_version < "3.13" beautifulsoup4==4.12.2 ; python_version >= "3.11" and python_version < "3.13" @@ -40,11 +40,11 @@ pyjwt==2.8.0 ; python_version >= "3.11" and python_version < "3.13" pyseto==1.7.7 ; python_version >= "3.11" and python_version < "3.13" python-dateutil==2.8.2 ; python_version >= "3.11" and python_version < "3.13" pyyaml==6.0.1 ; python_version >= "3.11" and python_version < "3.13" +sanic==23.12.1 ; python_version >= "3.11" and python_version < "3.13" sanic-beskar==2.3.2 ; python_version >= "3.11" and python_version < "3.13" sanic-ext==23.12.0 ; python_version >= "3.11" and python_version < "3.13" sanic-limiter @ git+https://github.com/Omegastick/sanic-limiter@843e13144aa21d843ce212a7c1db31b72ce8a103 ; python_version >= "3.11" and python_version < "3.13" sanic-routing==23.12.0 ; python_version >= "3.11" and python_version < "3.13" -sanic==23.12.1 ; python_version >= "3.11" and python_version < "3.13" sanic[ext]==23.12.1 ; python_version >= "3.11" and python_version < "3.13" sentry-sdk[sanic]==1.39.2 ; python_version >= "3.11" and python_version < "3.13" six==1.16.0 ; python_version >= "3.11" and python_version < "3.13" From 8ae50b543eb78f2173c66acc2e9b676a7026f80a Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Thu, 1 Feb 2024 04:12:05 +0100 Subject: [PATCH 005/102] feat: Initialize project --- .devcontainer/Dockerfile | 46 - .devcontainer/devcontainer.json | 65 - .env.example | 2 + .github/dependabot.yml | 25 - .github/workflows/codeql.yml | 54 - .github/workflows/dev.yml | 69 - .github/workflows/main.yml | 67 - .gitignore | 191 +- .pre-commit-config.yaml | 20 - .vscode/settings.json | 10 - Dockerfile | 19 - LICENSE | 661 ---- README.md | 44 - SECURITY.md | 11 - api/__init__.py | 27 - api/announcements.py | 242 -- api/backends/__init__.py | 0 api/backends/backend.py | 91 - api/backends/entities.py | 173 -- api/backends/github.py | 455 --- api/compat.py | 88 - api/donations.py | 38 - api/github.py | 220 -- api/info.py | 32 - api/login.py | 52 - api/manager.py | 61 - api/models/__init__.py | 0 api/models/announcements.py | 40 - api/models/compat.py | 49 - api/models/donations.py | 39 - api/models/github.py | 129 - api/models/info.py | 40 - api/models/manager.py | 32 - api/models/socials.py | 23 - api/ping.py | 24 - api/robots.py | 11 - api/socials.py | 32 - api/utils/__init__.py | 0 api/utils/auth.py | 40 - api/utils/http_utils.py | 26 - api/utils/limiter.py | 7 - api/utils/versioning.py | 8 - app.py | 87 - build.gradle.kts | 47 + config.py | 152 - data/database.py | 6 - data/models.py | 77 - docker-compose.yml | 16 - docs/.swagger-codegen-ignore | 23 + docs/.swagger-codegen/VERSION | 1 + docs/index.html | 2752 +++++++++++++++++ gradle.properties | 4 + gradle/libs.versions.toml | 41 + gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 59536 bytes gradle/wrapper/gradle-wrapper.properties | 5 + gradlew | 234 ++ gradlew.bat | 89 + mypy.ini | 30 - persistence/.gitkeep | 0 poetry.lock | 2599 ---------------- pyproject.toml | 45 - requirements.txt | 62 - settings.gradle.kts | 7 + .../kotlin/app/revanced/api/Application.kt | 24 + .../app/revanced/api/backend/Backend.kt | 140 + .../api/backend/github/GitHubBackend.kt | 116 + .../api/backend/github/api/RequestResource.kt | 26 + .../api/backend/github/api/ResponseSchema.kt | 52 + .../app/revanced/api/plugins/Databases.kt | 49 + .../app/revanced/api/plugins/Dependencies.kt | 21 + .../kotlin/app/revanced/api/plugins/HTTP.kt | 37 + .../app/revanced/api/plugins/Routing.kt | 45 + .../app/revanced/api/plugins/Security.kt | 30 + .../app/revanced/api/plugins/Serialization.kt | 11 + .../app/revanced/api/plugins/UsersSchema.kt | 59 + src/main/resources/logback.xml | 12 + src/main/resources/openapi/documentation.yaml | 23 + src/main/resources/static/about.json | 99 + src/main/resources/static/robots.txt | 2 + .../kotlin/app/revanced/ApplicationTest.kt | 21 + static/img/favicon.ico | Bin 20239 -> 0 bytes 81 files changed, 4005 insertions(+), 6302 deletions(-) delete mode 100644 .devcontainer/Dockerfile delete mode 100644 .devcontainer/devcontainer.json create mode 100644 .env.example delete mode 100644 .github/dependabot.yml delete mode 100644 .github/workflows/codeql.yml delete mode 100644 .github/workflows/dev.yml delete mode 100644 .github/workflows/main.yml delete mode 100644 .pre-commit-config.yaml delete mode 100644 .vscode/settings.json delete mode 100644 Dockerfile delete mode 100644 LICENSE delete mode 100644 README.md delete mode 100644 SECURITY.md delete mode 100644 api/__init__.py delete mode 100644 api/announcements.py delete mode 100644 api/backends/__init__.py delete mode 100644 api/backends/backend.py delete mode 100644 api/backends/entities.py delete mode 100644 api/backends/github.py delete mode 100644 api/compat.py delete mode 100644 api/donations.py delete mode 100644 api/github.py delete mode 100644 api/info.py delete mode 100644 api/login.py delete mode 100644 api/manager.py delete mode 100644 api/models/__init__.py delete mode 100644 api/models/announcements.py delete mode 100644 api/models/compat.py delete mode 100644 api/models/donations.py delete mode 100644 api/models/github.py delete mode 100644 api/models/info.py delete mode 100644 api/models/manager.py delete mode 100644 api/models/socials.py delete mode 100644 api/ping.py delete mode 100644 api/robots.py delete mode 100644 api/socials.py delete mode 100644 api/utils/__init__.py delete mode 100644 api/utils/auth.py delete mode 100644 api/utils/http_utils.py delete mode 100644 api/utils/limiter.py delete mode 100644 api/utils/versioning.py delete mode 100644 app.py create mode 100644 build.gradle.kts delete mode 100644 config.py delete mode 100644 data/database.py delete mode 100644 data/models.py delete mode 100644 docker-compose.yml create mode 100644 docs/.swagger-codegen-ignore create mode 100644 docs/.swagger-codegen/VERSION create mode 100644 docs/index.html create mode 100644 gradle.properties create mode 100644 gradle/libs.versions.toml create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100644 gradlew create mode 100644 gradlew.bat delete mode 100644 mypy.ini delete mode 100644 persistence/.gitkeep delete mode 100644 poetry.lock delete mode 100644 pyproject.toml delete mode 100644 requirements.txt create mode 100644 settings.gradle.kts create mode 100644 src/main/kotlin/app/revanced/api/Application.kt create mode 100644 src/main/kotlin/app/revanced/api/backend/Backend.kt create mode 100644 src/main/kotlin/app/revanced/api/backend/github/GitHubBackend.kt create mode 100644 src/main/kotlin/app/revanced/api/backend/github/api/RequestResource.kt create mode 100644 src/main/kotlin/app/revanced/api/backend/github/api/ResponseSchema.kt create mode 100644 src/main/kotlin/app/revanced/api/plugins/Databases.kt create mode 100644 src/main/kotlin/app/revanced/api/plugins/Dependencies.kt create mode 100644 src/main/kotlin/app/revanced/api/plugins/HTTP.kt create mode 100644 src/main/kotlin/app/revanced/api/plugins/Routing.kt create mode 100644 src/main/kotlin/app/revanced/api/plugins/Security.kt create mode 100644 src/main/kotlin/app/revanced/api/plugins/Serialization.kt create mode 100644 src/main/kotlin/app/revanced/api/plugins/UsersSchema.kt create mode 100644 src/main/resources/logback.xml create mode 100644 src/main/resources/openapi/documentation.yaml create mode 100644 src/main/resources/static/about.json create mode 100644 src/main/resources/static/robots.txt create mode 100644 src/test/kotlin/app/revanced/ApplicationTest.kt delete mode 100644 static/img/favicon.ico diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile deleted file mode 100644 index 7ef7d02..0000000 --- a/.devcontainer/Dockerfile +++ /dev/null @@ -1,46 +0,0 @@ -FROM mcr.microsoft.com/devcontainers/base:jammy -# FROM mcr.microsoft.com/devcontainers/base:jammy - -ARG DEBIAN_FRONTEND=noninteractive -ARG USER=vscode - -RUN DEBIAN_FRONTEND=noninteractive \ - && apt-get update \ - && apt-get install -y build-essential --no-install-recommends make \ - ca-certificates \ - git \ - libssl-dev \ - zlib1g-dev \ - libbz2-dev \ - libreadline-dev \ - libsqlite3-dev \ - wget \ - curl \ - llvm \ - libncurses5-dev \ - xz-utils \ - tk-dev \ - libxml2-dev \ - libxmlsec1-dev \ - libffi-dev \ - liblzma-dev \ - && rm -rf /var/lib/apt/lists/* - -# Python and poetry installation -USER $USER -ARG HOME="/home/$USER" -ARG PYTHON_VERSION=3.11 -# ARG PYTHON_VERSION=3.10 - -ENV PYENV_ROOT="${HOME}/.pyenv" -ENV PATH="${PYENV_ROOT}/shims:${PYENV_ROOT}/bin:${HOME}/.local/bin:$PATH" - -RUN echo "done 0" \ - && curl https://pyenv.run | bash \ - && echo "done 1" \ - && pyenv install ${PYTHON_VERSION} \ - && echo "done 2" \ - && pyenv global ${PYTHON_VERSION} \ - && echo "done 3" \ - && curl -sSL https://install.python-poetry.org | python3 - \ - && poetry config virtualenvs.in-project true diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json deleted file mode 100644 index 0890542..0000000 --- a/.devcontainer/devcontainer.json +++ /dev/null @@ -1,65 +0,0 @@ -{ - "name": "poetry3-poetry-pyenv", - "build": { - "dockerfile": "Dockerfile" - }, - - // 👇 Features to add to the Dev Container. More info: https://containers.dev/implementors/features. - // "features": {}, - - // 👇 Use 'forwardPorts' to make a list of ports inside the container available locally. - // "forwardPorts": [], - - // 👇 Use 'postCreateCommand' to run commands after the container is created. - // "postCreateCommand": "", - - // 👇 Configure tool-specific properties. - "customizations": { - "vscode": { - "extensions": [ - "ms-python.python", - "njpwerner.autodocstring", - "ms-azuretools.vscode-docker", - "github.copilot-labs", - "github.copilot-nightly", - "eamodio.gitlens", - "visualstudioexptteam.intellicode-api-usage-examples", - "ms-python.isort", - "ms-vsliveshare.vsliveshare", - "matangover.mypy", - "ms-python.vscode-pylance", - "mgesbert.python-path", - "zeshuaro.vscode-python-poetry", - "njqdev.vscode-python-typehint", - "ms-python.black-formatter" - ] - } - }, - "features": { - "ghcr.io/devcontainers/features/docker-outside-of-docker:1": {}, - "ghcr.io/devcontainers/features/github-cli:1": {}, - "ghcr.io/devcontainers/features/sshd:1": {}, - "ghcr.io/devcontainers-contrib/features/black:2": {}, - "ghcr.io/devcontainers-contrib/features/curl-apt-get:1": {}, - "ghcr.io/devcontainers-contrib/features/ffmpeg-apt-get:1": {}, - "ghcr.io/devcontainers-contrib/features/micro:1": {}, - "ghcr.io/devcontainers-contrib/features/mosh-apt-get:1": {}, - "ghcr.io/devcontainers-contrib/features/mypy:2": {}, - "ghcr.io/devcontainers-contrib/features/poetry:2": {}, - "ghcr.io/devcontainers-contrib/features/wget-apt-get:1": {}, - "ghcr.io/stuartleeks/dev-container-features/shell-history:0": {}, - "ghcr.io/jckimble/devcontainer-features/ngrok:3": {}, - "ghcr.io/devcontainers/features/common-utils:2": { - "installZsh": true, - "configureZshAsDefaultShell": true, - "installOhMyZsh": true, - "upgradePackages": true, - "username": "codespace", - "userUid": "automatic", - "userGid": "automatic" - } - } - - // 👇 Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. - // "remoteUser": "root" -} diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..6129bbb --- /dev/null +++ b/.env.example @@ -0,0 +1,2 @@ +GITHUB_TOKEN= +API_VERSION= \ No newline at end of file diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index f02748c..0000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,25 +0,0 @@ -version: 2 -updates: - - package-ecosystem: "github-actions" - directory: "/" - schedule: - interval: "daily" - target-branch: "dev" - assignees: - - "alexandreteles" - - - package-ecosystem: "pip" - directory: "/" - schedule: - interval: "daily" - target-branch: "dev" - assignees: - - "alexandreteles" - - - package-ecosystem: "docker" - directory: "/" - schedule: - interval: "daily" - target-branch: "dev" - assignees: - - "alexandreteles" diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml deleted file mode 100644 index 5373d7e..0000000 --- a/.github/workflows/codeql.yml +++ /dev/null @@ -1,54 +0,0 @@ -name: "CodeQL" - -on: - push: - branches: [dev] - pull_request: - types: [opened, reopened, edited, synchronize] - schedule: - - cron: "29 5 * * 5" - workflow_dispatch: - -jobs: - analyze: - name: Analyze - runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }} - timeout-minutes: ${{ (matrix.language == 'swift' && 120) || 360 }} - permissions: - actions: read - contents: read - security-events: write - strategy: - fail-fast: false - matrix: - language: ["python"] - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: "3.11.7" - - - name: Install project dependencies - run: | - python -m pip install --upgrade pip - if [ -f requirements.txt ]; - then pip install -r requirements.txt; - fi - - - name: Initialize CodeQL - uses: github/codeql-action/init@v3 - with: - languages: ${{ matrix.language }} - queries: security-and-quality - - - name: Autobuild - uses: github/codeql-action/autobuild@v3 - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3 - with: - category: "/language:${{matrix.language}}" diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml deleted file mode 100644 index 91b030c..0000000 --- a/.github/workflows/dev.yml +++ /dev/null @@ -1,69 +0,0 @@ -name: Build dev branch - -on: - push: - branches: [ "dev" ] - schedule: - - cron: '24 9 * * 6' - workflow_dispatch: - -env: - REGISTRY: ghcr.io - IMAGE_NAME: ${{ github.repository }} - IMAGE_TAG: ${{ github.sha }} - -jobs: - security_checks: - runs-on: ubuntu-latest - name: Security check - steps: - - uses: actions/checkout@v4 - - name: Security Checks (PyCharm Security) - uses: tonybaloney/pycharm-security@master - with: - path: . - - build: - needs: security_checks - runs-on: ubuntu-latest - permissions: - contents: read - packages: write - - steps: - - name: Checkout Dockerfile - id: checkout - uses: actions/checkout@v4 - with: - ref: ${{ inputs.branch }} - - - name: Setup QEMU - id: qemu - uses: docker/setup-qemu-action@v3 - with: - image: tonistiigi/binfmt:latest - platforms: all - - - name: Setup Docker Buildx - id: buildx - uses: docker/setup-buildx-action@v3 - - - name: Extract metadata (tags, labels) for Docker - id: meta - uses: docker/metadata-action@v5 - with: - images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} - flavor: | - latest=${{ startsWith(github.ref, 'refs/heads/main') }} - suffix=-${{ github.sha }} - - - name: Build Docker image - id: build - uses: docker/build-push-action@v5 - with: - context: . - file: ./Dockerfile - platforms: linux/amd64,linux/arm64/v8 - push: false - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml deleted file mode 100644 index 886e0ac..0000000 --- a/.github/workflows/main.yml +++ /dev/null @@ -1,67 +0,0 @@ -name: Build and Publish Docker Image - -on: - push: - branches: [main] - schedule: - - cron: "24 9 * * 6" - workflow_dispatch: - -env: - REGISTRY: ghcr.io - IMAGE_NAME: ${{ github.repository }} - IMAGE_TAG: ${{ github.sha }} - -jobs: - build: - runs-on: ubuntu-latest - permissions: - contents: read - packages: write - - steps: - - name: Checkout Dockerfile - id: checkout - uses: actions/checkout@v4 - - - name: Setup QEMU - id: qemu - uses: docker/setup-qemu-action@v3 - with: - image: tonistiigi/binfmt:latest - platforms: all - - - name: Setup Docker Buildx - id: buildx - uses: docker/setup-buildx-action@v3 - - - name: Login to GitHub Container Registry - id: ghcr - uses: docker/login-action@v3 - with: - registry: ${{ env.REGISTRY }} - username: ${{ github.repository_owner }} - password: ${{ secrets.GH_TOKEN }} - - - name: Extract metadata (tags, labels) for Docker - id: meta - uses: docker/metadata-action@v5 - with: - images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} - flavor: | - latest=${{ startsWith(github.ref, 'refs/heads/main') }} - suffix=-${{ github.sha }} - - - name: Build and push main Docker image - id: build - uses: docker/build-push-action@v5 - with: - build-args: GH_TOKEN=${{ secrets.GH_TOKEN }} - context: . - file: ./Dockerfile - platforms: linux/amd64,linux/arm64/v8 - cache-to: type=gha,mode=max,ignore-error=true - cache-from: type=gha - push: true - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} diff --git a/.gitignore b/.gitignore index b8beef6..d16fa9e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,164 +1,39 @@ -# Byte-compiled / optimized / DLL files -__pycache__/ -*.py[cod] -*$py.class - -# C extensions -*.so - -# Distribution / packaging -.Python +.gradle build/ -develop-eggs/ -dist/ -downloads/ -eggs/ -.eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -wheels/ -share/python-wheels/ -*.egg-info/ -.installed.cfg -*.egg -MANIFEST +!gradle/wrapper/gradle-wrapper.jar +!**/src/main/**/build/ +!**/src/test/**/build/ -# PyInstaller -# Usually these files are written by a python script from a template -# before PyInstaller builds the exe, so as to inject date/other infos into it. -*.manifest -*.spec +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache +bin/ +!**/src/main/**/bin/ +!**/src/test/**/bin/ -# Installer logs -pip-log.txt -pip-delete-this-directory.txt +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr +out/ +!**/src/main/**/out/ +!**/src/test/**/out/ -# Unit test / coverage reports -htmlcov/ -.tox/ -.nox/ -.coverage -.coverage.* -.cache -nosetests.xml -coverage.xml -*.cover -*.py,cover -.hypothesis/ -.pytest_cache/ -cover/ +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ -# Translations -*.mo -*.pot +### VS Code ### +.vscode/ -# Django stuff: -*.log -local_settings.py -db.sqlite3 -db.sqlite3-journal - -# Flask stuff: -instance/ -.webassets-cache - -# Scrapy stuff: -.scrapy - -# Sphinx documentation -docs/_build/ - -# PyBuilder -.pybuilder/ -target/ - -# Jupyter Notebook -.ipynb_checkpoints - -# IPython -profile_default/ -ipython_config.py - -# pyenv -# For a library or package, you might want to ignore these files since the code is -# intended to run in multiple environments; otherwise, check them in: -# .python-version - -# pipenv -# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. -# However, in case of collaboration, if having platform-specific dependencies or dependencies -# having no cross-platform support, pipenv may install dependencies that don't work, or not -# install all needed dependencies. -#Pipfile.lock - -# poetry -# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. -# This is especially recommended for binary packages to ensure reproducibility, and is more -# commonly ignored for libraries. -# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control -#poetry.lock - -# pdm -# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. -#pdm.lock -# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it -# in version control. -# https://pdm.fming.dev/#use-with-ide -.pdm.toml - -# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm -__pypackages__/ - -# Celery stuff -celerybeat-schedule -celerybeat.pid - -# SageMath parsed files -*.sage.py - -# Environments -.env -.venv -env/ -venv/ -ENV/ -env.bak/ -venv.bak/ - -# Spyder project settings -.spyderproject -.spyproject - -# Rope project settings -.ropeproject - -# mkdocs documentation -/site - -# mypy -.mypy_cache/ -.dmypy.json -dmypy.json - -# Pyre type checker -.pyre/ - -# pytype static type analyzer -.pytype/ - -# Cython debug symbols -cython_debug/ - -# PyCharm -# JetBrains specific template is maintained in a separate JetBrains.gitignore that can -# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore -# and can be added to the global gitignore or merged into this file. For a more nuclear -# option (not recommended) you can uncomment the following to ignore the entire idea folder. -#.idea/ - -# custom -env.sh -persistence/database.db +### Project ### +.env \ No newline at end of file diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml deleted file mode 100644 index 952f021..0000000 --- a/.pre-commit-config.yaml +++ /dev/null @@ -1,20 +0,0 @@ -repos: - - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.6.0 - hooks: - - id: trailing-whitespace - - id: end-of-file-fixer - - id: check-yaml - - id: check-added-large-files - - id: check-docstring-first - - id: debug-statements - - id: requirements-txt-fixer - - id: check-toml - - id: check-merge-conflict - - repo: https://github.com/psf/black - rev: 24.3.0 - hooks: - - id: black - language_version: python3.11 -ci: - autoupdate_branch: "dev" diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index ad9bb99..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "python.analysis.typeCheckingMode": "off", - "spellright.language": [ - "pt" - ], - "spellright.documentTypes": [ - "markdown", - "latex" - ] -} diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 8c044a7..0000000 --- a/Dockerfile +++ /dev/null @@ -1,19 +0,0 @@ -FROM python:3.11-slim - -ARG GITHUB_TOKEN -ARG SENTRY_DSN - -ENV GITHUB_TOKEN $GITHUB_TOKEN -ENV SENTRY_DSN $SENTRY_DSN - -WORKDIR /usr/src/app - -COPY . . - -RUN apt update && \ - apt-get install git build-essential libffi-dev libssl-dev openssl --no-install-recommends -y \ - && pip install --no-cache-dir -r requirements.txt - -VOLUME persistence - -CMD [ "python3", "-m" , "sanic", "app:app", "--fast", "--access-logs", "--motd", "--noisy-exceptions", "-H", "0.0.0.0"] diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 0ad25db..0000000 --- a/LICENSE +++ /dev/null @@ -1,661 +0,0 @@ - GNU AFFERO GENERAL PUBLIC LICENSE - Version 3, 19 November 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU Affero General Public License is a free, copyleft license for -software and other kinds of works, specifically designed to ensure -cooperation with the community in the case of network server software. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -our General Public Licenses are intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - Developers that use our General Public Licenses protect your rights -with two steps: (1) assert copyright on the software, and (2) offer -you this License which gives you legal permission to copy, distribute -and/or modify the software. - - A secondary benefit of defending all users' freedom is that -improvements made in alternate versions of the program, if they -receive widespread use, become available for other developers to -incorporate. Many developers of free software are heartened and -encouraged by the resulting cooperation. However, in the case of -software used on network servers, this result may fail to come about. -The GNU General Public License permits making a modified version and -letting the public access it on a server without ever releasing its -source code to the public. - - The GNU Affero General Public License is designed specifically to -ensure that, in such cases, the modified source code becomes available -to the community. It requires the operator of a network server to -provide the source code of the modified version running there to the -users of that server. Therefore, public use of a modified version, on -a publicly accessible server, gives the public access to the source -code of the modified version. - - An older license, called the Affero General Public License and -published by Affero, was designed to accomplish similar goals. This is -a different license, not a version of the Affero GPL, but Affero has -released a new version of the Affero GPL which permits relicensing under -this license. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU Affero General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Remote Network Interaction; Use with the GNU General Public License. - - Notwithstanding any other provision of this License, if you modify the -Program, your modified version must prominently offer all users -interacting with it remotely through a computer network (if your version -supports such interaction) an opportunity to receive the Corresponding -Source of your version by providing access to the Corresponding Source -from a network server at no charge, through some standard or customary -means of facilitating copying of software. This Corresponding Source -shall include the Corresponding Source for any work covered by version 3 -of the GNU General Public License that is incorporated pursuant to the -following paragraph. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the work with which it is combined will remain governed by version -3 of the GNU General Public License. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU Affero General Public License from time to time. Such new versions -will be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU Affero General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU Affero General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU Affero General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - - If your software can interact with users remotely through a computer -network, you should also make sure that it provides a way for users to -get its source. For example, if your program is a web application, its -interface could display a "Source" link that leads users to an archive -of the code. There are many ways you could offer source, and different -solutions will be better for different programs; see section 13 for the -specific requirements. - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU AGPL, see -. diff --git a/README.md b/README.md deleted file mode 100644 index aaa86ee..0000000 --- a/README.md +++ /dev/null @@ -1,44 +0,0 @@ -# ReVanced Releases API - ---- - -![License: AGPLv3](https://img.shields.io/github/license/revanced/revanced-api) -[![Build and Publish Docker Image](https://github.com/revanced/revanced-api/actions/workflows/main.yml/badge.svg)](https://github.com/revanced/revanced-api/actions/workflows/main.yml) - ---- - -This is a simple API that proxies requests needed to feed the ReVanced Manager and website with data. - -## Usage - -To run this API, you need Python 3.11.x. You can install the dependencies with poetry: - -```shell -poetry install -``` - -Create the following environment variables: - -- `GITHUB_TOKEN` with a valid GitHub token with read access to public repositories -- `SECRET_KEY` to salt login sessions -- `USERNAME` & `PASSWORD` to initialize the database with a user to login with to authenticated endpoints - -Then, you can run the API in development mode with: - -```shell -poetry run sanic app:app --dev -``` - -or in production mode with: - -```shell -poetry run sanic app:app --fast -``` - -## Contributing - -If you want to contribute to this project, feel free to open a pull request or an issue. We don't do much here, so it's pretty easy to contribute. - -## License - -This project is licensed under the AGPLv3 License - see the [LICENSE](LICENSE) file for details. diff --git a/SECURITY.md b/SECURITY.md deleted file mode 100644 index a96b0b4..0000000 --- a/SECURITY.md +++ /dev/null @@ -1,11 +0,0 @@ -# Security Policy - -## Supported Tags - -| Tag | ReVanced Version | -| ------- | ------------------ | -| latest | latest upstream | - -## Reporting a Vulnerability - -To report a vulnerability, please open an Issue in our issue tracker here on GitHub. diff --git a/api/__init__.py b/api/__init__.py deleted file mode 100644 index 1ce029b..0000000 --- a/api/__init__.py +++ /dev/null @@ -1,27 +0,0 @@ -# api/__init__.py -from sanic import Blueprint -import importlib -import pkgutil -from api.utils.versioning import get_version - -# Dynamically import all modules in the 'api' package, excluding subdirectories -versioned_blueprints: dict[str, list] = {} -for finder, module_name, ispkg in pkgutil.iter_modules(["api"]): - if not ispkg: - # Import the module - module = importlib.import_module(f"api.{module_name}") - - # Add the module's blueprint to the versioned list, if it exists - if hasattr(module, module_name): - blueprint = getattr(module, module_name) - version = get_version(module_name) - versioned_blueprints.setdefault(version, []).append(blueprint) - -# Create Blueprint groups for each version -api = [] -for version, blueprints in versioned_blueprints.items(): - if version == "old" or version == "v0": - group = Blueprint.group(*blueprints, url_prefix="/") - else: - group = Blueprint.group(*blueprints, version=version, url_prefix="/") - api.append(group) diff --git a/api/announcements.py b/api/announcements.py deleted file mode 100644 index bcdc4c0..0000000 --- a/api/announcements.py +++ /dev/null @@ -1,242 +0,0 @@ -""" -This module provides a blueprint for the announcements endpoint. - -Routes: - - GET /announcements: Get a list of announcements from all channels. - - GET /announcements/: Get a list of announcement from a channel. - - GET /announcements/latest: Get the latest announcement. - - GET /announcements//latest: Get the latest announcement from a channel. - - POST /announcements/: Create an announcement. - - DELETE /announcements/: Delete an announcement. -""" - -import os -import datetime -from sanic import Blueprint, Request -from sanic.response import JSONResponse, json -from sanic_ext import openapi -from data.database import Session -from data.models import AnnouncementDbModel, AttachmentDbModel - -import sanic_beskar - -from api.models.announcements import AnnouncementResponseModel -from api.utils.limiter import limiter - -announcements: Blueprint = Blueprint(os.path.basename(__file__).strip(".py")) - - -@announcements.get("/announcements") -@openapi.definition( - summary="Get a list of announcements", - response=[[AnnouncementResponseModel]], -) -async def get_announcements(request: Request) -> JSONResponse: - """ - Retrieve a list of announcements. - - **Returns:** - - JSONResponse: A Sanic JSONResponse object containing a list of announcements from all channels. - """ - - session = Session() - - announcements = [ - AnnouncementResponseModel.to_response(announcement) - for announcement in session.query(AnnouncementDbModel).all() - ] - - session.close() - - return json(announcements, status=200) - - -@announcements.get("/announcements/") -@openapi.definition( - summary="Get a list of announcements from a channel", - response=[[AnnouncementResponseModel]], -) -async def get_announcements_for_channel(request: Request, channel: str) -> JSONResponse: - """ - Retrieve a list of announcements from a channel. - - **Args:** - - channel (str): The channel to retrieve announcements from. - - **Returns:** - - JSONResponse: A Sanic JSONResponse object containing a list of announcements from a channel. - """ - - session = Session() - - announcements = [ - AnnouncementResponseModel.to_response(announcement) - for announcement in session.query(AnnouncementDbModel) - .filter_by(channel=channel) - .all() - ] - - session.close() - - return json(announcements, status=200) - - -@announcements.get("/announcements/latest") -@openapi.definition( - summary="Get the latest announcement", - response=AnnouncementResponseModel, -) -async def get_latest_announcement(request: Request) -> JSONResponse: - """ - Retrieve the latest announcement. - - **Returns:** - - JSONResponse: A Sanic JSONResponse object containing the latest announcement. - """ - - session = Session() - - announcement = ( - session.query(AnnouncementDbModel) - .order_by(AnnouncementDbModel.id.desc()) - .first() - ) - - if not announcement: - return json({"error": "No announcement found"}, status=404) - - announcement_response = AnnouncementResponseModel.to_response(announcement) - - session.close() - - return json(announcement_response, status=200) - - -# for specific channel - - -@announcements.get("/announcements//latest") -@openapi.definition( - summary="Get the latest announcement from a channel", - response=AnnouncementResponseModel, -) -async def get_latest_announcement_for_channel( - request: Request, channel: str -) -> JSONResponse: - """ - Retrieve the latest announcement from a channel. - - **Args:** - - channel (str): The channel to retrieve the latest announcement from. - - **Returns:** - - JSONResponse: A Sanic JSONResponse object containing the latest announcement from a channel. - """ - - session = Session() - - announcement = ( - session.query(AnnouncementDbModel) - .filter_by(channel=channel) - .order_by(AnnouncementDbModel.id.desc()) - .first() - ) - - if not announcement: - return json({"error": "No announcement found"}, status=404) - - announcement_response = AnnouncementResponseModel.to_response(announcement) - - session.close() - - return json(announcement_response, status=200) - - -@announcements.post("/announcements/") -@limiter.limit("16 per hour") -@sanic_beskar.auth_required -@openapi.definition( - summary="Create an announcement", - body=AnnouncementResponseModel, - response=AnnouncementResponseModel, -) -async def post_announcement(request: Request, channel: str) -> JSONResponse: - """ - Create an announcement. - - **Args:** - - author (str | None): The author of the announcement. - - title (str): The title of the announcement. - - content (ContentFields | None): The content of the announcement. - - channel (str): The channel to create the announcement in. - - nevel (int | None): The severity of the announcement. - """ - session = Session() - - if not request.json: - return json({"error": "Missing request body"}, status=400) - - content = request.json.get("content", None) - - author = request.json.get("author", None) - title = request.json.get("title") - message = content["message"] if content and "message" in content else None - attachments = ( - list( - map( - lambda url: AttachmentDbModel(attachment_url=url), - content["attachments"], - ) - ) - if content and "attachments" in content - else [] - ) - level = request.json.get("level", None) - created_at = datetime.datetime.now() - - announcement = AnnouncementDbModel( - author=author, - title=title, - message=message, - attachments=attachments, - channel=channel, - created_at=created_at, - level=level, - ) - - session.add(announcement) - session.commit() - session.close() - - return json({}, status=200) - - -@announcements.delete("/announcements/") -@sanic_beskar.auth_required -@openapi.definition( - summary="Delete an announcement", -) -async def delete_announcement(request: Request, announcement_id: int) -> JSONResponse: - """ - Delete an announcement. - - **Args:** - - announcement_id (int): The ID of the announcement to delete. - - **Exceptions:** - - 404: Announcement not found. - """ - session = Session() - - announcement = ( - session.query(AnnouncementDbModel).filter_by(id=announcement_id).first() - ) - - if not announcement: - return json({"error": "Announcement not found"}, status=404) - - session.delete(announcement) - session.commit() - session.close() - - return json({}, status=200) diff --git a/api/backends/__init__.py b/api/backends/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/api/backends/backend.py b/api/backends/backend.py deleted file mode 100644 index 24dfc63..0000000 --- a/api/backends/backend.py +++ /dev/null @@ -1,91 +0,0 @@ -from abc import abstractmethod -from typing import Any, Protocol - -from api.backends.entities import * - - -class Backend(Protocol): - """Interface for a generic backend. - - Attributes: - name (str): Name of the backend. - base_url (str): Base URL of the backend. - - Methods: - list_releases: Retrieve a list of releases. - get_release_by_tag_name: Retrieve a release by its tag name. - get_latest_release: Retrieve the latest release. - get_latest_pre_release: Retrieve the latest pre-release. - get_release_notes: Retrieve the release notes of a specific release. - get_contributors: Retrieve the list of contributors. - get_patches: Retrieve the patches of a specific release. - """ - - name: str - base_url: str - - def __init__(self, name: str, base_url: str): - self.name = name - self.base_url = base_url - - @abstractmethod - async def list_releases(self, *args: Any, **kwargs: Any) -> list[Release]: - raise NotImplementedError - - @abstractmethod - async def get_release_by_tag_name(self, *args: Any, **kwargs: Any) -> Release: - raise NotImplementedError - - @abstractmethod - async def get_latest_release(self, *args: Any, **kwargs: Any) -> Release: - raise NotImplementedError - - @abstractmethod - async def get_latest_pre_release(self, *args: Any, **kwargs: Any) -> Release: - raise NotImplementedError - - @abstractmethod - async def get_contributors(self, *args: Any, **kwargs: Any) -> list[Contributor]: - raise NotImplementedError - - @abstractmethod - async def get_patches(self, *args: Any, **kwargs: Any) -> list[dict]: - raise NotImplementedError - - @abstractmethod - async def get_team_members(self, *args: Any, **kwargs: Any) -> list[Contributor]: - raise NotImplementedError - - -class Repository: - """A repository that communicates with a specific backend. - - Attributes: - backend (Backend): The backend instance used to communicate with the repository. - """ - - def __init__(self, backend: Backend): - self.backend = backend - - -class AppInfoProvider(Protocol): - """Interface for a generic app info provider. - - Attributes: - name (str): Name of the app info provider. - base_url (str): Base URL of the app info provider. - - Methods: - get_app_info: Retrieve information about an app. - """ - - name: str - base_url: str - - def __init__(self, name: str, base_url: str): - self.name = name - self.base_url = base_url - - @abstractmethod - async def get_app_info(self, *args: Any, **kwargs: Any) -> AppInfo: - raise NotImplementedError diff --git a/api/backends/entities.py b/api/backends/entities.py deleted file mode 100644 index 1cb022f..0000000 --- a/api/backends/entities.py +++ /dev/null @@ -1,173 +0,0 @@ -from typing import Optional -from dataclasses import dataclass - - -@dataclass -class Metadata(dict): - """ - Represents the metadata of a release. - - Attributes: - - tag_name (str): The name of the release tag. - - name (str): The name of the release. - - body (str): The body of the release. - - draft (bool): Whether the release is a draft. - - prerelease (bool): Whether the release is a prerelease. - - created_at (str): The creation date of the release. - - published_at (str): The publication date of the release. - """ - - def __init__( - self, - tag_name: str, - name: str, - draft: bool, - prerelease: bool, - created_at: str, - published_at: str, - body: str, - repository: Optional[str] = None, - ): - dict.__init__( - self, - tag_name=tag_name, - name=name, - draft=draft, - prerelease=prerelease, - created_at=created_at, - published_at=published_at, - body=body, - repository=repository, - ) - - -@dataclass -class Asset(dict): - """ - Represents an asset in a release. - - Attributes: - - name (str): The name of the asset. - - content_type (str): The MIME type of the asset content. - - download_count (int): The number of times the asset has been downloaded. - - download_url (str): The URL to download the asset. - """ - - def __init__( - self, - name: str, - content_type: str, - download_count: int, - browser_download_url: str, - ): - dict.__init__( - self, - name=name, - content_type=content_type, - download_count=download_count, - browser_download_url=browser_download_url, - ) - - -@dataclass -class Release(dict): - """ - Represents a release. - - Attributes: - - metadata (Metadata): The metadata of the release. - - assets (list[Asset]): The assets of the release. - """ - - def __init__(self, metadata: Metadata, assets: list[Asset]): - dict.__init__(self, metadata=metadata, assets=assets) - - -@dataclass -class Contributor(dict): - """ - Represents a contributor to a repository. - - Attributes: - - login (str): The GitHub username of the contributor. - - avatar_url (str): The URL to the contributor's avatar image. - - html_url (str): The URL to the contributor's GitHub profile. - - contributions (Optional[int]): The number of contributions the contributor has made to the repository. - """ - - def __init__( - self, - login: str, - avatar_url: str, - html_url: str, - contributions: Optional[int] = None, - bio: Optional[str] = None, - keys: Optional[str] = None, - ): - match contributions, bio, keys: - case None, None, None: - dict.__init__( - self, - login=login, - avatar_url=avatar_url, - html_url=html_url, - bio=bio, - keys=keys, - ) - case int(_), None, None: - dict.__init__( - self, - login=login, - avatar_url=avatar_url, - html_url=html_url, - contributions=contributions, - ) - case None, str(_), None: - dict.__init__( - self, - login=login, - avatar_url=avatar_url, - html_url=html_url, - bio=bio, - ) - case int(_), str(_), str(_): - dict.__init__( - self, - login=login, - avatar_url=avatar_url, - html_url=html_url, - contributions=contributions, - bio=bio, - keys=keys, - ) - case None, str(_), str(_): - dict.__init__( - self, - login=login, - avatar_url=avatar_url, - html_url=html_url, - bio=bio, - keys=keys, - ) - case _: - raise ValueError("Invalid arguments") - - -@dataclass -class AppInfo(dict): - """ - Represents the information of an app. - - Attributes: - - name (str): The name of the app. - - category (str): The app category. - - logo (str): The base64 enconded app logo. - """ - - def __init__(self, name: str, category: str, logo: str): - dict.__init__( - self, - name=name, - category=category, - logo=logo, - ) diff --git a/api/backends/github.py b/api/backends/github.py deleted file mode 100644 index 82a5c45..0000000 --- a/api/backends/github.py +++ /dev/null @@ -1,455 +0,0 @@ -import asyncio -import os -from operator import eq -from typing import Any, Optional - -import ujson -from aiohttp import ClientResponse -from sanic import SanicException -from cytoolz import filter, map, partial, curry, pipe -from cytoolz.dicttoolz import get_in, keyfilter -from cytoolz.itertoolz import mapcat, pluck - -from api.backends.backend import Backend, Repository -from api.backends.entities import * -from api.backends.entities import Contributor -from api.utils.http_utils import http_get - -repo_name: str = "github" -base_url: str = "https://api.github.com" - - -class GithubRepository(Repository): - """ - A repository class that represents a GitHub repository. - - Args: - owner (str): The username of the owner of the GitHub repository. - name (str): The name of the GitHub repository. - """ - - def __init__(self, owner: str, name: str): - """ - Initializes a new instance of the GithubRepository class. - - Args: - owner (str): The username of the owner of the GitHub repository. - name (str): The name of the GitHub repository. - """ - super().__init__(Github()) - self.owner = owner - self.name = name - - -class Github(Backend): - """ - A backend class that interacts with the GitHub API. - - Attributes: - name (str): The name of the GitHub backend. - base_url (str): The base URL of the GitHub API. - token (str): The GitHub access token used for authentication. - headers (dict[str, str]): The HTTP headers to be sent with each request to the GitHub API. - """ - - def __init__(self): - """ - Initializes a new instance of the GitHub class. - """ - super().__init__(repo_name, base_url) - self.token: Optional[str] = os.getenv("GITHUB_TOKEN") - self.headers: dict[str, str] = { - "Authorization": f"Bearer {self.token}", - "Accept": "application/vnd.github+json", - "X-GitHub-Api-Version": "2022-11-28", - } - self.repositories_rest_endpoint: str = f"{base_url}/repos" - - @staticmethod - async def __assemble_release(release: dict) -> Release: - async def __assemble_asset(asset: dict) -> Asset: - asset_data: dict = keyfilter( - lambda key: key - in {"name", "content_type", "download_count", "browser_download_url"}, - asset, - ) - return Asset(**asset_data) - - filter_metadata = keyfilter( - lambda key: key - in { - "tag_name", - "name", - "draft", - "prerelease", - "created_at", - "published_at", - "body", - }, - release, - ) - metadata = Metadata(**filter_metadata) - assets = await asyncio.gather(*map(__assemble_asset, release["assets"])) - return Release(metadata=metadata, assets=assets) - - @staticmethod - async def __assemble_contributor( - contributor: dict, team_view: bool = False - ) -> Contributor: - match team_view: - case True: - keys = {"login", "avatar_url", "html_url", "bio"} - case _: - keys = {"login", "avatar_url", "html_url", "contributions"} - - filter_contributor = keyfilter( - lambda key: key in keys, - contributor, - ) - - if team_view: - filter_contributor["keys"] = ( - f"{base_url.replace('api.', '')}/{filter_contributor['login']}.gpg" - ) - - return Contributor(**filter_contributor) - - @staticmethod - async def __validate_request(_response: ClientResponse) -> None: - if _response.status != 200: - raise SanicException( - context=await _response.json(loads=ujson.loads), - status_code=_response.status, - ) - - async def list_releases( - self, repository: GithubRepository, per_page: int = 30, page: int = 1 - ) -> list[Release]: - """ - Returns a list of Release objects for a given GitHub repository. - - Args: - repository (GithubRepository): The GitHub repository for which to retrieve the releases. - per_page (int): The number of releases to return per page. - page (int): The page number of the releases to return. - - Returns: - list[Release]: A list of Release objects. - """ - list_releases_endpoint: str = ( - f"{self.repositories_rest_endpoint}/{repository.owner}/{repository.name}/releases?per_page={per_page}&page={page}" - ) - response: ClientResponse = await http_get( - headers=self.headers, url=list_releases_endpoint - ) - await self.__validate_request(response) - releases: list[Release] = await asyncio.gather( - *map( - lambda release: self.__assemble_release(release), - await response.json(loads=ujson.loads), - ) - ) - return releases - - async def get_release_by_tag_name( - self, repository: GithubRepository, tag_name: str - ) -> Release: - """ - Retrieves a specific release for a given GitHub repository by its tag name. - - Args: - repository (GithubRepository): The GitHub repository for which to retrieve the release. - tag_name (str): The tag name of the release to retrieve. - - Returns: - Release: The Release object representing the retrieved release. - """ - release_by_tag_endpoint: str = ( - f"{self.repositories_rest_endpoint}/{repository.owner}/{repository.name}/releases/tags/{tag_name}" - ) - response: ClientResponse = await http_get( - headers=self.headers, url=release_by_tag_endpoint - ) - await self.__validate_request(response) - return await self.__assemble_release(await response.json(loads=ujson.loads)) - - async def get_latest_release( - self, - repository: GithubRepository, - ) -> Release: - """Get the latest release for a given repository. - - Args: - repository (GithubRepository): The GitHub repository for which to retrieve the release. - - Returns: - Release: The latest release for the given repository. - """ - latest_release_endpoint: str = ( - f"{self.repositories_rest_endpoint}/{repository.owner}/{repository.name}/releases/latest" - ) - response: ClientResponse = await http_get( - headers=self.headers, url=latest_release_endpoint - ) - await self.__validate_request(response) - return await self.__assemble_release(await response.json(loads=ujson.loads)) - - async def get_latest_pre_release( - self, - repository: GithubRepository, - ) -> Release: - """Get the latest pre-release for a given repository. - - Args: - repository (GithubRepository): The GitHub repository for which to retrieve the release. - - Returns: - Release: The latest pre-release for the given repository. - """ - list_releases_endpoint: str = ( - f"{self.repositories_rest_endpoint}/{repository.owner}/{repository.name}/releases?per_page=10&page=1" - ) - response: ClientResponse = await http_get( - headers=self.headers, url=list_releases_endpoint - ) - await self.__validate_request(response) - latest_pre_release = next( - filter( - lambda release: release["prerelease"], - await response.json(loads=ujson.loads), - ) - ) - return await self.__assemble_release(latest_pre_release) - - async def get_contributors(self, repository: GithubRepository) -> list[Contributor]: - """Get a list of contributors for a given repository. - - Args: - repository (GithubRepository): The repository for which to retrieve contributors. - - Returns: - list[Contributor]: A list of contributors for the given repository. - """ - - contributors_endpoint: str = ( - f"{self.repositories_rest_endpoint}/{repository.owner}/{repository.name}/contributors" - ) - response: ClientResponse = await http_get( - headers=self.headers, url=contributors_endpoint - ) - await self.__validate_request(response) - contributors: list[Contributor] = await asyncio.gather( - *map(self.__assemble_contributor, await response.json(loads=ujson.loads)) - ) - - return contributors - - async def get_patches( - self, repository: GithubRepository, tag_name: str = "latest", dev: bool = False - ) -> list[dict]: - """Get a dictionary of patch URLs for a given repository. - - Args: - repository (GithubRepository): The repository for which to retrieve patches. - tag_name (str): The name of the release tag. - dev (bool): If we should get the latest pre-release instead. - - Returns: - list[dict]: A JSON object containing the patches. - """ - - async def __fetch_download_url(_release: Release) -> str: - asset = get_in(["assets"], _release) - patch_asset = next( - filter(lambda x: eq(get_in(["name"], x), "patches.json"), asset), None - ) - return get_in(["browser_download_url"], patch_asset) - - match tag_name: - case "latest": - match dev: - case True: - release = await self.get_latest_pre_release(repository) - case _: - release = await self.get_latest_release(repository) - case _: - release = await self.get_release_by_tag_name( - repository=repository, tag_name=tag_name - ) - - response: ClientResponse = await http_get( - headers=self.headers, - url=await __fetch_download_url(_release=release), - ) - await self.__validate_request(response) - return ujson.loads(await response.read()) - - async def get_team_members(self, repository: GithubRepository) -> list[Contributor]: - """Get the list of team members from the owner organization of a given repository. - - Args: - repository (GithubRepository): The repository for which to retrieve team members in the owner organization. - - Returns: - list[Contributor]: A list of members in the owner organization. - """ - team_members_endpoint: str = f"{self.base_url}/orgs/{repository.owner}/members" - user_info_endpoint: str = f"{self.base_url}/users/" - response: ClientResponse = await http_get( - headers=self.headers, url=team_members_endpoint - ) - await self.__validate_request(response) - logins: list[str] = list(pluck("login", await response.json())) - _http_get = partial(http_get, headers=self.headers) - user_data_response: list[dict] = await asyncio.gather( - *map( - lambda login: _http_get(url=f"{user_info_endpoint}{login}"), - logins, - ) - ) - user_data = await asyncio.gather( - *map( - lambda _response: _response.json(loads=ujson.loads), - user_data_response, - ) - ) - team_members: list[Contributor] = await asyncio.gather( - *map( - lambda member: self.__assemble_contributor(member, team_view=True), - list(user_data), - ) - ) - - return team_members - - async def compat_get_tools( - self, repositories: list[GithubRepository], dev: bool - ) -> list: - """Get the latest releases for a set of repositories (v1 compat). - - Args: - repositories (set[GithubRepository]): The repositories for which to retrieve releases. - dev: If we should get the latest pre-release instead. - - Returns: - list[dict[str, str]]: A JSON object containing the releases. - """ - - def transform(data: dict, repository: GithubRepository): - """Transforms a dictionary from the input list into a list of dictionaries with the desired structure. - - Args: - data(dict): A dictionary from the input list. - repository(GithubRepository): The repository for which to retrieve releases. - - Returns: - _[list]: A list of dictionaries with the desired structure. - """ - - def process_asset(asset: dict) -> dict: - """Transforms an asset dictionary into a new dictionary with the desired structure. - - Args: - asset(dict): An asset dictionary. - - Returns: - _[dict]: A new dictionary with the desired structure. - """ - return { - "repository": f"{repository.owner}/{repository.name}", - "version": data["metadata"]["tag_name"], - "timestamp": data["metadata"]["published_at"], - "name": asset["name"], - "browser_download_url": asset["browser_download_url"], - "content_type": asset["content_type"], - } - - return map(process_asset, data["assets"]) - - results = await asyncio.gather( - *map( - lambda release: self.get_latest_release(release), - repositories, - ) - ) - - return list(mapcat(lambda pair: transform(*pair), zip(results, repositories))) - - async def compat_get_contributors( - self, repositories: list[GithubRepository] - ) -> list: - """Get the contributors for a set of repositories (v1 compat). - - Args: - repositories (set[GithubRepository]): The repositories for which to retrieve contributors. - - Returns: - list[dict[str, str]]: A JSON object containing the contributors. - """ - - def transform(data: dict, repository: GithubRepository) -> dict[str, Any]: - """Transforms a dictionary from the input list into a list of dictionaries with the desired structure. - - Args: - data(dict): A dictionary from the input list. - repository(GithubRepository): The repository for which to retrieve contributors. - - Returns: - _[list]: A list of dictionaries with the desired structure. - """ - return { - "name": f"{repository.owner}/{repository.name}", - "contributors": data, - } - - results = await asyncio.gather( - *map( - lambda repository: self.get_contributors(repository), - repositories, - ) - ) - - return list(map(lambda pair: transform(*pair), zip(results, repositories))) - - async def generate_custom_sources( - self, repositories: list[GithubRepository], dev: bool - ) -> dict[str, dict[str, str]]: - """Generate a custom sources dictionary for a set of repositories. - - Args: - repositories (list[GithubRepository]): The repositories for which to generate the sources. - dev (bool): If we should get the latest pre-release instead. - - Returns: - dict[str, dict[str, str]]: A dictionary containing the custom sources. - """ - - # Helper functions - filter_by_name = curry(lambda name, item: name in item["name"]) - filter_patches_jar = curry( - lambda item: "patches" in item["name"] and item["name"].endswith(".jar") - ) - get_fields = curry( - lambda fields, item: {field: item[field] for field in fields} - ) - rename_key = curry( - lambda old, new, d: {new if k == old else k: v for k, v in d.items()} - ) - - sources = await self.compat_get_tools(repositories, dev) - - patches = pipe( - sources, - lambda items: next(filter(filter_patches_jar, items), None), - get_fields(["version", "browser_download_url"]), - rename_key("browser_download_url", "url"), - ) - - integrations = pipe( - sources, - lambda items: next(filter(filter_by_name("integrations"), items), None), - get_fields(["version", "browser_download_url"]), - rename_key("browser_download_url", "url"), - ) - - return {"patches": patches, "integrations": integrations} diff --git a/api/compat.py b/api/compat.py deleted file mode 100644 index 9520729..0000000 --- a/api/compat.py +++ /dev/null @@ -1,88 +0,0 @@ -""" -This module provides endpoints for compatibility with the old API. - -Routes: - - GET //releases: Retrieve a list of releases for a GitHub repository. - - GET //releases/latest: Retrieve the latest release for a GitHub repository. - - GET //releases/tag/: Retrieve a specific release for a GitHub repository by its tag name. - - GET //contributors: Retrieve a list of contributors for a GitHub repository. - - GET /patches/: Retrieve a list of patches for a given release tag. - -""" - -import os -from sanic import Blueprint, Request -from sanic.response import JSONResponse, json -from sanic_ext import openapi - -from api.backends.github import Github, GithubRepository -from api.models.github import * -from api.models.compat import ToolsResponseModel, ContributorsResponseModel -from config import compat_repositories, owner - -compat: Blueprint = Blueprint(os.path.basename(__file__).strip(".py")) - -github_backend: Github = Github() - - -@compat.get("/tools") -@openapi.definition( - summary="Get patching tools' latest version.", response=[ToolsResponseModel] -) -async def tools(request: Request) -> JSONResponse: - """ - Retrieve a list of releases for a GitHub repository. - - **Args:** - - repo (str): The name of the GitHub repository to retrieve releases for. - - **Query Parameters:** - - per_page (int): The number of releases to retrieve per page. - - page (int): The page number of the releases to retrieve. - - **Returns:** - - JSONResponse: A Sanic JSONResponse object containing the list of releases. - - **Raises:** - - HTTPException: If there is an error retrieving the releases. - """ - - data: dict[str, list] = { - "tools": await github_backend.compat_get_tools( - repositories=[ - GithubRepository(owner=owner, name=repo) - for repo in compat_repositories - if repo - not in ["revanced-api", "revanced-releases-api", "revanced-website"] - ], - dev=True if request.args.get("dev") else False, - ) - } - - return json(data, status=200) - - -@compat.get("/contributors") -@openapi.definition( - summary="Get organization-wise contributors.", response=[ContributorsResponseModel] -) -async def contributors(request: Request) -> JSONResponse: - """ - Retrieve a list of releases for a GitHub repository. - - **Returns:** - - JSONResponse: A Sanic JSONResponse object containing the list of releases. - - **Raises:** - - HTTPException: If there is an error retrieving the releases. - """ - - data: dict[str, list] = { - "repositories": await github_backend.compat_get_contributors( - repositories=[ - GithubRepository(owner=owner, name=repo) for repo in compat_repositories - ] - ) - } - - return json(data, status=200) diff --git a/api/donations.py b/api/donations.py deleted file mode 100644 index 25ae875..0000000 --- a/api/donations.py +++ /dev/null @@ -1,38 +0,0 @@ -""" -This module provides a blueprint for the donations endpoint. - -Routes: - - GET /donations: Get ReVanced donation links and wallets. -""" - -import os - -from sanic import Blueprint, Request -from sanic.response import JSONResponse, json -from sanic_ext import openapi - -from api.models.donations import DonationsResponseModel -from config import wallets, links - -donations: Blueprint = Blueprint(os.path.basename(__file__).strip(".py")) - - -@donations.get("/donations") -@openapi.definition( - summary="Get ReVanced donation links and wallets", - response=[DonationsResponseModel], -) -async def root(request: Request) -> JSONResponse: - """ - Returns a JSONResponse with a dictionary containing ReVanced donation links and wallets. - - **Returns:** - - JSONResponse: A Sanic JSONResponse instance containing a dictionary with the donation links and wallets. - """ - data: dict[str, dict] = { - "donations": { - "wallets": wallets, - "links": links, - } - } - return json(data, status=200) diff --git a/api/github.py b/api/github.py deleted file mode 100644 index aaf98bc..0000000 --- a/api/github.py +++ /dev/null @@ -1,220 +0,0 @@ -""" -This module provides endpoints for interacting with the GitHub API. - -Routes: - - GET //releases: Retrieve a list of releases for a GitHub repository. - - GET //releases/latest: Retrieve the latest release for a GitHub repository. - - GET //releases/tag/: Retrieve a specific release for a GitHub repository by its tag name. - - GET //contributors: Retrieve a list of contributors for a GitHub repository. - - GET /patches/: Retrieve a list of patches for a given release tag. - -""" - -import os -from sanic import Blueprint, Request -from sanic.response import JSONResponse, json -from sanic_ext import openapi - -from api.backends.entities import Release, Contributor -from api.backends.github import Github, GithubRepository -from api.models.github import * -from config import owner, default_repository - -github: Blueprint = Blueprint(os.path.basename(__file__).strip(".py")) - -github_backend: Github = Github() - - -@github.get("//releases") -@openapi.definition( - summary="Get releases for a repository", response=[ReleaseListResponseModel] -) -async def list_releases(request: Request, repo: str) -> JSONResponse: - """ - Retrieve a list of releases for a GitHub repository. - - **Args:** - - repo (str): The name of the GitHub repository to retrieve releases for. - - **Query Parameters:** - - per_page (int): The number of releases to retrieve per page. - - page (int): The page number of the releases to retrieve. - - **Returns:** - - JSONResponse: A Sanic JSONResponse object containing the list of releases. - - **Raises:** - - HTTPException: If there is an error retrieving the releases. - """ - - per_page = int(request.args.get("per_page")) if request.args.get("per_page") else 30 - page = int(request.args.get("page")) if request.args.get("page") else 1 - - data: dict[str, list[Release]] = { - "releases": await github_backend.list_releases( - repository=GithubRepository(owner=owner, name=repo), - per_page=per_page, - page=page, - ) - } - - return json(data, status=200) - - -@github.get("//releases/latest") -@openapi.definition( - summary="Get the latest release for a repository", - response=SingleReleaseResponseModel, -) -async def latest_release(request: Request, repo: str) -> JSONResponse: - """ - Retrieve the latest release for a GitHub repository. - - **Args:** - - repo (str): The name of the GitHub repository to retrieve the release for. - - **Query Parameters:** - - dev (bool): Whether or not to retrieve the latest development release. - - **Returns:** - - JSONResponse: A Sanic JSONResponse object containing the release. - - **Raises:** - - HTTPException: If there is an error retrieving the releases. - """ - - data: dict[str, Release] - - match request.args.get("dev"): - case "true": - data = { - "release": await github_backend.get_latest_pre_release( - repository=GithubRepository(owner=owner, name=repo) - ) - } - case _: - data = { - "release": await github_backend.get_latest_release( - repository=GithubRepository(owner=owner, name=repo) - ) - } - - return json(data, status=200) - - -@github.get("//releases/tag/") -@openapi.definition( - summary="Retrieve a release for a GitHub repository by its tag name.", - response=SingleReleaseResponseModel, -) -async def get_release_by_tag_name( - request: Request, repo: str, tag: str -) -> JSONResponse: - """ - Retrieve a release for a GitHub repository by its tag name. - - **Args:** - - repo (str): The name of the GitHub repository to retrieve the release for. - - tag (str): The tag for the release to be retrieved. - - **Returns:** - - JSONResponse: A Sanic JSONResponse object containing the release. - - **Raises:** - - HTTPException: If there is an error retrieving the releases. - """ - - data: dict[str, Release] = { - "release": await github_backend.get_release_by_tag_name( - repository=GithubRepository(owner=owner, name=repo), tag_name=tag - ) - } - - return json(data, status=200) - - -@github.get("//contributors") -@openapi.definition( - summary="Retrieve a list of contributors for a repository.", - response=ContributorsModel, -) -async def get_contributors(request: Request, repo: str) -> JSONResponse: - """ - Retrieve a list of contributors for a repository. - - **Args:** - - repo (str): The name of the GitHub repository to retrieve the contributors for. - - **Returns:** - - JSONResponse: A Sanic JSONResponse object containing the list of contributors. - - **Raises:** - - HTTPException: If there is an error retrieving the contributors. - """ - - data: dict[str, list[Contributor]] = { - "contributors": await github_backend.get_contributors( - repository=GithubRepository(owner=owner, name=repo) - ) - } - - return json(data, status=200) - - -@github.get("/patches/") -@openapi.definition( - summary="Retrieve a list of patches for a release.", response=PatchesModel -) -async def get_patches(request: Request, tag: str) -> JSONResponse: - """ - Retrieve a list of patches for a release. - - **Args:** - - tag (str): The tag for the patches to be retrieved. - - **Query Parameters:** - - dev (bool): Whether or not to retrieve the latest development release. - - **Returns:** - - JSONResponse: A Sanic JSONResponse object containing the list of patches. - - **Raises:** - - HTTPException: If there is an error retrieving the patches. - """ - - repo: str = "revanced-patches" - - dev: bool = bool(request.args.get("dev")) - - data: dict[str, list[dict]] = { - "patches": await github_backend.get_patches( - repository=GithubRepository(owner=owner, name=repo), tag_name=tag, dev=dev - ) - } - - return json(data, status=200) - - -@github.get("/team/members") -@openapi.definition( - summary="Retrieve a list of team members for the Revanced organization.", - response=TeamMembersModel, -) -async def get_team_members(request: Request) -> JSONResponse: - """ - Retrieve a list of team members for the Revanced organization. - - **Returns:** - - JSONResponse: A Sanic JSONResponse object containing the list of team members. - - **Raises:** - - HTTPException: If there is an error retrieving the team members. - """ - - data: dict[str, list[Contributor]] = { - "members": await github_backend.get_team_members( - repository=GithubRepository(owner=owner, name=default_repository) - ) - } - - return json(data, status=200) diff --git a/api/info.py b/api/info.py deleted file mode 100644 index 60fbeac..0000000 --- a/api/info.py +++ /dev/null @@ -1,32 +0,0 @@ -""" -This module provides a blueprint for the info endpoint. - -Routes: - - GET /info: Get info about the owner of the API. -""" - -import os -from sanic import Blueprint, Request -from sanic.response import JSONResponse, json -from sanic_ext import openapi - -from api.models.info import InfoResponseModel -from config import default_info - -info: Blueprint = Blueprint(os.path.basename(__file__).strip(".py")) - - -@info.get("/info") -@openapi.definition( - summary="Information about the API", - response=[InfoResponseModel], -) -async def root(request: Request) -> JSONResponse: - """ - Returns a JSONResponse with a dictionary containing info about the owner of the API. - - **Returns:** - - JSONResponse: A Sanic JSONResponse instance containing a dictionary with the info about the owner of the API. - """ - data: dict[str, dict] = {"info": default_info} - return json(data, status=200) diff --git a/api/login.py b/api/login.py deleted file mode 100644 index 939960b..0000000 --- a/api/login.py +++ /dev/null @@ -1,52 +0,0 @@ -""" -This module provides a blueprint for the login endpoint. - -Routes: - - POST /login: Login to the API -""" - -import os -from sanic import Blueprint, Request -from sanic.response import JSONResponse, json -from sanic_ext import openapi -from sanic_beskar.exceptions import AuthenticationError - -from api.utils.auth import beskar -from api.utils.limiter import limiter - -login: Blueprint = Blueprint(os.path.basename(__file__).strip(".py")) - - -@login.post("/login") -@openapi.definition( - summary="Login to the API", -) -@limiter.limit("3 per hour") -async def login_user(request: Request) -> JSONResponse: - """ - Login to the API. - - **Args:** - - username (str): The username of the user to login. - - password (str): The password of the user to login. - - **Returns:** - - JSONResponse: A Sanic JSONResponse object containing the access token. - """ - - req = request.json - username = req.get("username", None) - password = req.get("password", None) - if not username or not password: - return json({"error": "Missing username or password"}, status=400) - - try: - user = await beskar.authenticate(username, password) - except AuthenticationError: - return json({"error": "Invalid username or password"}, status=403) - - if not user: - return json({"error": "Invalid username or password"}, status=403) - - ret = {"access_token": await beskar.encode_token(user)} - return json(ret, status=200) diff --git a/api/manager.py b/api/manager.py deleted file mode 100644 index 87a3e60..0000000 --- a/api/manager.py +++ /dev/null @@ -1,61 +0,0 @@ -""" -This module provides ReVanced Manager specific endpoints. - -Routes: - - GET /manager/bootstrap: Get a list of the main ReVanced tools. - - GET /manager/sources: Get a list of ReVanced sources. -""" - -import os -from sanic import Blueprint, Request -from sanic.response import JSONResponse, json -from sanic_ext import openapi - -from api.backends.github import GithubRepository, Github - -from api.models.manager import BootsrapResponseModel, CustomSourcesResponseModel -from config import compat_repositories, owner - -manager: Blueprint = Blueprint(os.path.basename(__file__).strip(".py")) - -github_backend: Github = Github() - - -@manager.get("/manager/bootstrap") -@openapi.definition( - summary="Get a list of the main ReVanced tools", - response=[BootsrapResponseModel], -) -async def bootstrap(request: Request) -> JSONResponse: - """ - Returns a JSONResponse with a list of the main ReVanced tools. - - **Returns:** - - JSONResponse: A Sanic JSONResponse instance containing a list with the tool names. - """ - data: dict[str, dict] = {"tools": compat_repositories} - return json(data, status=200) - - -@manager.get("/manager/custom-source") -@openapi.definition( - summary="Get a list of ReVanced sources", - response=[CustomSourcesResponseModel], -) -async def custom_sources(request: Request) -> JSONResponse: - """ - Returns a JSONResponse with a list of the main ReVanced sources. - - **Returns:** - - JSONResponse: A Sanic JSONResponse instance containing a list with the source names. - """ - data = await github_backend.generate_custom_sources( - repositories=[ - GithubRepository(owner=owner, name=repo) - for repo in compat_repositories - if "patches" in repo or "integrations" in repo - ], - dev=True if request.args.get("dev") else False, - ) - - return json(data, status=200) diff --git a/api/models/__init__.py b/api/models/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/api/models/announcements.py b/api/models/announcements.py deleted file mode 100644 index fd5f3df..0000000 --- a/api/models/announcements.py +++ /dev/null @@ -1,40 +0,0 @@ -from data.models import AnnouncementDbModel - - -class ContentFields(dict): - message: str | None - attachment_urls: list[str] | None - - -class AnnouncementResponseModel(dict): - id: int - author: str | None - title: str - content: ContentFields | None - channel: str - created_at: str - level: int | None - - @staticmethod - def to_response(announcement: AnnouncementDbModel): - response = AnnouncementResponseModel( - id=announcement.id, - author=announcement.author, - title=announcement.title, - content=( - ContentFields( - message=announcement.message, - attachment_urls=[ - attachment.attachment_url - for attachment in announcement.attachments - ], - ) - if announcement.message or announcement.attachments - else None - ), - channel=announcement.channel, - created_at=str(announcement.created_at), - level=announcement.level, - ) - - return response diff --git a/api/models/compat.py b/api/models/compat.py deleted file mode 100644 index b187613..0000000 --- a/api/models/compat.py +++ /dev/null @@ -1,49 +0,0 @@ -from pydantic import BaseModel -from api.models.github import ContributorsFields - - -class ToolsResponseFields(BaseModel): - """Implements the fields for the /tools endpoint. - - Args: - BaseModel (pydantic.BaseModel): BaseModel from pydantic - """ - - repository: str - version: str - timestamp: str - name: str - size: str | None = None - browser_download_url: str - content_type: str - - -class ToolsResponseModel(BaseModel): - """Implements the JSON response model for the /tools endpoint. - - Args: - BaseModel (pydantic.BaseModel): BaseModel from pydantic - """ - - tools: list[ToolsResponseFields] - - -class ContributorsResponseFields(BaseModel): - """Implements the fields for the /contributors endpoint. - - Args: - BaseModel (pydantic.BaseModel): BaseModel from pydantic - """ - - name: str - contributors: list[ContributorsFields] - - -class ContributorsResponseModel(BaseModel): - """Implements the JSON response model for the /contributors endpoint. - - Args: - BaseModel (pydantic.BaseModel): BaseModel from pydantic - """ - - repositories: list[ContributorsResponseFields] diff --git a/api/models/donations.py b/api/models/donations.py deleted file mode 100644 index 12ad18b..0000000 --- a/api/models/donations.py +++ /dev/null @@ -1,39 +0,0 @@ -from pydantic import BaseModel - - -class WalletFields(BaseModel): - """ - Implements the fields for a crypto wallet. - """ - - network: str - currency_code: str - address: str - preferred: bool - - -class LinkFields(BaseModel): - """ - Implements the fields for a donation link. - """ - - name: str - url: str - preferred: bool - - -class DonationFields(BaseModel): - """ - A Pydantic BaseModel that represents all the donation links and wallets. - """ - - wallets: list[WalletFields] - links: list[LinkFields] - - -class DonationsResponseModel(BaseModel): - """ - A Pydantic BaseModel that represents a dictionary of donation links. - """ - - donations: DonationFields diff --git a/api/models/github.py b/api/models/github.py deleted file mode 100644 index e2f246a..0000000 --- a/api/models/github.py +++ /dev/null @@ -1,129 +0,0 @@ -from typing import Any, Optional -from pydantic import BaseModel - - -class MetadataFields(BaseModel): - """ - Metadata fields for a GitHub release. - """ - - tag_name: str - name: str - draft: bool - prerelease: bool - created_at: str - published_at: str - body: str - - -class AssetFields(BaseModel): - """ - Asset fields for a GitHub release. - """ - - name: str - content_type: str - download_count: int - browser_download_url: str - - -class ReleaseResponseModel(BaseModel): - """ - Response model for a GitHub release. - """ - - metadata: MetadataFields - assets: list[AssetFields] - - -class SingleReleaseResponseModel(BaseModel): - """ - Response model for a GitHub release. - """ - - release: ReleaseResponseModel - - -class ReleaseListResponseModel(BaseModel): - """ - Response model for a list of GitHub releases. - """ - - releases: list[ReleaseResponseModel] - - -class CompatiblePackagesResponseFields(BaseModel): - """ - Implements the fields for compatible packages in the PatchesResponseFields class. - """ - - name: str - versions: list[str] | None - - -class PatchesOptionsResponseFields(BaseModel): - key: str - title: str - description: str - required: bool - choices: list[Any] | None - - -class PatchesResponseFields(BaseModel): - """ - Implements the fields for the /patches endpoint. - """ - - name: str - description: str - version: str - excluded: bool - dependencies: list[str] | None - options: list[PatchesOptionsResponseFields] | None - compatiblePackages: list[CompatiblePackagesResponseFields] - - -class PatchesModel(BaseModel): - """ - Response model for a list of GitHub releases. - """ - - patches: list[PatchesResponseFields] - - -class ContributorsFields(BaseModel): - """ - Implements the fields for a contributor. - """ - - login: str - avatar_url: str - html_url: str - contributions: Optional[int] - - -class ContributorsModel(BaseModel): - """ - Response model for a list of contributors. - """ - - contributors: list[ContributorsFields] - - -class TeamMemberFields(BaseModel): - """ - Implements the fields for a team member. - """ - - login: str - avatar_url: str - html_url: str - bio: Optional[str] - - -class TeamMembersModel(BaseModel): - """ - Responde model for a list of team members. - """ - - members: list[TeamMemberFields] diff --git a/api/models/info.py b/api/models/info.py deleted file mode 100644 index 06b102c..0000000 --- a/api/models/info.py +++ /dev/null @@ -1,40 +0,0 @@ -from api.models.donations import DonationFields -from api.models.socials import SocialFields -from pydantic import BaseModel - - -class ContactFields(BaseModel): - """ - Implements the fields for the API owner contact info. - """ - - email: str - - -class BrandingFields(BaseModel): - """ - Implements the fields for the API owner branding info. - """ - - logo: str - - -class InfoFields(BaseModel): - """ - Implements the fields for the API owner info. - """ - - name: str - about: str - branding: BrandingFields - contact: ContactFields - socials: list[SocialFields] - donations: DonationFields - - -class InfoResponseModel(BaseModel): - """ - A Pydantic BaseModel that represents a dictionary of info. - """ - - info: InfoFields diff --git a/api/models/manager.py b/api/models/manager.py deleted file mode 100644 index c7e39b3..0000000 --- a/api/models/manager.py +++ /dev/null @@ -1,32 +0,0 @@ -from pydantic import BaseModel - - -class BootsrapResponseModel(BaseModel): - """ - A Pydantic BaseModel that represents a list of available tools. - """ - - tools: list[str] - """ - A list of available tools. - """ - - -class CustomSourcesFields(BaseModel): - """ - Implements the fields for a source. - """ - - url: str - preferred: bool - - -class CustomSourcesResponseModel(BaseModel): - """ - A Pydantic BaseModel that represents a list of available sources. - """ - - _: dict[str, CustomSourcesFields] - """ - A list of available sources. - """ diff --git a/api/models/socials.py b/api/models/socials.py deleted file mode 100644 index 45edad0..0000000 --- a/api/models/socials.py +++ /dev/null @@ -1,23 +0,0 @@ -from pydantic import BaseModel - - -class SocialFields(BaseModel): - """ - Implements the fields for a social network link. - """ - - name: str - url: str - preferred: bool - - -class SocialsResponseModel(BaseModel): - """ - A Pydantic BaseModel that represents a dictionary of social links. - """ - - socials: list[SocialFields] - """ - A dictionary where the keys are the names of the social networks, and - the values are the links to the profiles or pages. - """ diff --git a/api/ping.py b/api/ping.py deleted file mode 100644 index 09bce09..0000000 --- a/api/ping.py +++ /dev/null @@ -1,24 +0,0 @@ -""" -This module provides endpoints for pinging the API. - -Routes: - - GET /ping: Ping the API. -""" - -import os -from sanic import Blueprint, HTTPResponse, Request, response -from sanic_ext import openapi - -ping: Blueprint = Blueprint(os.path.basename(__file__).rstrip(".py")) - - -@ping.get("/ping") -@openapi.summary("Ping the API") -async def root(request: Request) -> HTTPResponse: - """ - Endpoint for pinging the API. - - **Returns:** - - Empty response with status code 204. - """ - return response.empty(status=204) diff --git a/api/robots.py b/api/robots.py deleted file mode 100644 index 10460c3..0000000 --- a/api/robots.py +++ /dev/null @@ -1,11 +0,0 @@ -import os -from sanic import Blueprint -from sanic.response import text - - -robots: Blueprint = Blueprint(os.path.basename(__file__).strip(".py")) - - -@robots.get("/robots.txt") -async def robots_txt(request): - return text("User-agent: *\nDisallow: /", content_type="text/plain") diff --git a/api/socials.py b/api/socials.py deleted file mode 100644 index f58dcc6..0000000 --- a/api/socials.py +++ /dev/null @@ -1,32 +0,0 @@ -""" -This module provides a blueprint for the socials endpoint. - -Routes: - - GET /socials: Get ReVanced socials. -""" - -import os -from sanic import Blueprint, Request -from sanic.response import JSONResponse, json -from sanic_ext import openapi - -from api.models.socials import SocialsResponseModel -from config import social_links - -socials: Blueprint = Blueprint(os.path.basename(__file__).strip(".py")) - - -@socials.get("/socials") -@openapi.definition( - summary="Get ReVanced socials", - response=[SocialsResponseModel], -) -async def root(request: Request) -> JSONResponse: - """ - Returns a JSONResponse with a dictionary containing ReVanced social links. - - **Returns:** - - JSONResponse: A Sanic JSONResponse instance containing a dictionary with the social links. - """ - data: dict[str, dict] = {"socials": social_links} - return json(data, status=200) diff --git a/api/utils/__init__.py b/api/utils/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/api/utils/auth.py b/api/utils/auth.py deleted file mode 100644 index f336a0f..0000000 --- a/api/utils/auth.py +++ /dev/null @@ -1,40 +0,0 @@ -import os -import secrets -import string -from data.database import Session - -from sanic_beskar import Beskar - -from data.models import UserDbModel - -beskar = Beskar() - - -def configure_auth(app): - app.config.SECRET_KEY = os.environ.get("SECRET_KEY").join( - secrets.choice(string.ascii_letters) for i in range(15) - ) - app.config["TOKEN_ACCESS_LIFESPAN"] = {"hours": 24} - app.config["TOKEN_REFRESH_LIFESPAN"] = {"days": 30} - beskar.init_app(app, UserDbModel) - - _init_default_user() - - -def _init_default_user(): - username = os.environ.get("USERNAME") - password = os.environ.get("PASSWORD") - - if not username or not password: - raise Exception("Missing USERNAME or PASSWORD environment variables") - - session = Session() - - existing_user = session.query(UserDbModel).filter_by(username=username).first() - if not existing_user: - session.add( - UserDbModel(username=username, password=beskar.hash_password(password)) - ) - session.commit() - - session.close() diff --git a/api/utils/http_utils.py b/api/utils/http_utils.py deleted file mode 100644 index 98c9e30..0000000 --- a/api/utils/http_utils.py +++ /dev/null @@ -1,26 +0,0 @@ -from typing import Optional - -import ujson -from aiohttp import ClientSession - -_client: Optional[ClientSession] = None - - -async def http_get(headers, url): - """ - Performs a GET HTTP request to a given URL with the provided headers. - - Args: - headers (dict): A dictionary containing headers to be included in the HTTP request. - url (str): The URL to which the HTTP request will be made. - - Returns: - The HTTP response returned by the server. - """ - global _client - if _client is None: - _client = ClientSession(json_serialize=ujson.dumps) - return await _client.get(url, headers=headers) - else: - assert isinstance(_client, ClientSession) - return await _client.get(url, headers=headers) diff --git a/api/utils/limiter.py b/api/utils/limiter.py deleted file mode 100644 index 5198ca2..0000000 --- a/api/utils/limiter.py +++ /dev/null @@ -1,7 +0,0 @@ -from sanic_limiter import Limiter, get_remote_address - -limiter = Limiter(key_func=get_remote_address) - - -def configure_limiter(app): - limiter.init_app(app) diff --git a/api/utils/versioning.py b/api/utils/versioning.py deleted file mode 100644 index ad69dd4..0000000 --- a/api/utils/versioning.py +++ /dev/null @@ -1,8 +0,0 @@ -from cytoolz import keyfilter -from config import api_versions - - -def get_version(value: str) -> str: - result = keyfilter(lambda key: value in api_versions[key], api_versions) - - return list(result.keys())[0] if result else "v0" diff --git a/app.py b/app.py deleted file mode 100644 index a699c84..0000000 --- a/app.py +++ /dev/null @@ -1,87 +0,0 @@ -# app.py - -import os -from typing import Any - -from sanic import HTTPResponse, Sanic -import sanic.response -from sanic_ext import Config - -from api import api -from config import openapi_title, openapi_version, openapi_description, hostnames - -from api.utils.limiter import configure_limiter -from api.utils.auth import configure_auth - -import sentry_sdk - -if os.environ.get("SENTRY_DSN"): - sentry_sdk.init( - dsn=os.environ["SENTRY_DSN"], - enable_tracing=True, - traces_sample_rate=1.0, - ) -else: - print("WARNING: Sentry DSN not set, not enabling Sentry") - -REDIRECTS = { - "/": "/docs/swagger", -} - -app = Sanic("revanced-api") -app.extend(config=Config(oas_ignore_head=False)) -app.ext.openapi.describe( - title=openapi_title, - version=openapi_version, - description=openapi_description, -) -app.config.CORS_ALWAYS_SEND = True -app.config.CORS_AUTOMATIC_OPTIONS = True -app.config.CORS_VARY_HEADER = True -app.config.CORS_METHODS = ["GET", "HEAD", "OPTIONS"] -app.config.CORS_SUPPORTS_CREDENTIALS = True -app.config.CORS_SEND_WILDCARD = True -app.config.CORS_ORIGINS = "*" - -# sanic-beskar -configure_auth(app) - -# sanic-limiter -configure_limiter(app) - -app.blueprint(api) - -# https://sanic.dev/en/guide/how-to/static-redirects.html - - -def get_static_function(value) -> Any: - return lambda *_, **__: value - - -for src, dest in REDIRECTS.items(): - app.route(src)(get_static_function(sanic.response.redirect(dest))) - - -@app.on_request -async def domain_check(request) -> HTTPResponse: - if request.host not in hostnames: - return sanic.response.redirect(f"https://api.revanced.app/{request.path}") - - -@app.on_response -async def add_cache_control(_, response): - response.headers["Cache-Control"] = "public, max-age=300" - - -@app.on_response -async def add_csp(_, response): - response.headers["Content-Security-Policy"] = ( - "default-src * 'unsafe-inline' 'unsafe-eval' data: blob:;" - ) - - -app.static( - "/favicon.ico", - "static/img/favicon.ico", - name="favicon", -) diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000..822a45f --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,47 @@ +plugins { + alias(libs.plugins.kotlin) + alias(libs.plugins.ktor) + alias(libs.plugins.serilization) +} + +group = "app.revanced" + +application { + mainClass.set("app.revanced.api.ApplicationKt") + + val isDevelopment: Boolean = project.ext.has("development") + applicationDefaultJvmArgs = listOf("-Dio.ktor.development=$isDevelopment") +} + +repositories { + mavenCentral() +} + +dependencies { + implementation(libs.ktor.client.core) + implementation(libs.ktor.client.cio) + implementation(libs.ktor.client.okhttp) + implementation(libs.ktor.client.auth) + implementation(libs.ktor.client.resources) + implementation(libs.ktor.client.content.negotiation) + implementation(libs.ktor.server.core) + implementation(libs.ktor.server.content.negotiation) + implementation(libs.ktor.server.auth) + implementation(libs.ktor.server.auth.jwt) + implementation(libs.ktor.server.swagger) + implementation(libs.ktor.server.openapi) + implementation(libs.ktor.server.cors) + implementation(libs.ktor.server.caching.headers) + implementation(libs.ktor.server.host.common) + implementation(libs.ktor.server.netty) + implementation(libs.ktor.server.conditional.headers) + implementation(libs.ktor.serialization.kotlinx.json) + implementation(libs.koin.ktor) + implementation(libs.h2) + implementation(libs.logback.classic) + implementation(libs.exposed.core) + implementation(libs.exposed.jdbc) + implementation(libs.dotenv.kotlin) + testImplementation(libs.ktor.server.tests) + testImplementation(libs.kotlin.test.junit) +} diff --git a/config.py b/config.py deleted file mode 100644 index aee8610..0000000 --- a/config.py +++ /dev/null @@ -1,152 +0,0 @@ -# API Configuration - -from typing import Any - - -backend: str = "github" -redis: dict[str, str | int] = {"host": "localhost", "port": 6379} -hostnames: list[str] = [ - "api.revanced.app", - "deimos.revanced.app", - "localhost:8000", - "127.0.0.1:8000", -] - -# GitHub Backend Configuration - -owner: str = "revanced" -default_repository: str = ".github" - -# API Versioning - -api_versions: dict[str, list[str]] = { - "old": ["compat"], - "v2": [ - "announcements", - "donations", - "github", - "info", - "login", - "ping", - "socials", - "manager", - ], -} - -api_version: str = "v2" -openapi_version: str = "2.0.0" -openapi_title: str = "ReVanced API" -openapi_description: str = """ -## The official JSON API for ReVanced Releases 🚀 - -### Links - -- [Changelogs](https://github.com/revanced/) -- [Official links to ReVanced](https://revanced.app) - -### Important Information - -* Rate Limiting - 60 requests per minute -* Cache - 5 minutes - -### Additional Notes - -1. Breaking changes are to be expected -2. Client side caching is advised to avoid unnecessary requests -3. Abuse of the API will result in IP blocks -""" - -# Testing Configuration - -github_testing_repository: str = "revanced-patches" -github_testing_tag: str = "v2.173.0" -apkdl_testing_package: str = "com.google.android.youtube" - -# Old API Configuration - -compat_api_version: str = "v1" -compat_repositories: list = [ - "revanced-patcher", - "revanced-patches", - "revanced-integrations", - "revanced-manager", - "revanced-cli", - "revanced-website", - "revanced-api", - "revanced-releases-api", -] - -# Social Links - -social_links: list[dict[str, str | bool]] = [ - {"name": "Website", "url": "https://revanced.app", "preferred": True}, - {"name": "GitHub", "url": "https://github.com/revanced", "preferred": False}, - {"name": "Twitter", "url": "https://twitter.com/revancedapp", "preferred": False}, - {"name": "Discord", "url": "https://revanced.app/discord", "preferred": True}, - { - "name": "Reddit", - "url": "https://www.reddit.com/r/revancedapp", - "preferred": False, - }, - {"name": "Telegram", "url": "https://t.me/app_revanced", "preferred": False}, - {"name": "YouTube", "url": "https://www.youtube.com/@ReVanced", "preferred": False}, -] - -# Donation info - -wallets: list[dict[str, str | bool]] = [ - { - "network": "Bitcoin", - "currency_code": "BTC", - "address": "bc1q4x8j6mt27y5gv0q625t8wkr87ruy8fprpy4v3f", - "preferred": False, - }, - { - "network": "Dogecoin", - "currency_code": "DOGE", - "address": "D8GH73rNjudgi6bS2krrXWEsU9KShedLXp", - "preferred": True, - }, - { - "network": "Ethereum", - "currency_code": "ETH", - "address": "0x7ab4091e00363654bf84B34151225742cd92FCE5", - "preferred": False, - }, - { - "network": "Litecoin", - "currency_code": "LTC", - "address": "LbJi8EuoDcwaZvykcKmcrM74jpjde23qJ2", - "preferred": False, - }, - { - "network": "Monero", - "currency_code": "XMR", - "address": "46YwWDbZD6jVptuk5mLHsuAmh1BnUMSjSNYacozQQEraWSQ93nb2yYVRHoMR6PmFYWEHsLHg9tr1cH5M8Rtn7YaaGQPCjSh", - "preferred": False, - }, -] - -links: list[dict[str, str | bool]] = [ - { - "name": "Open Collective", - "url": "https://opencollective.com/revanced", - "preferred": True, - }, - { - "name": "GitHub Sponsors", - "url": "https://github.com/sponsors/ReVanced", - "preferred": False, - }, -] - -default_info: dict[str, Any] = { - "name": "ReVanced", - "about": "ReVanced was born out of Vanced's discontinuation and it is our goal to continue the legacy of what Vanced left behind. Thanks to ReVanced Patcher, it's possible to create long-lasting patches for nearly any Android app. ReVanced's patching system is designed to allow patches to work on new versions of the apps automatically with bare minimum maintenance.", - "branding": { - "logo": "https://raw.githubusercontent.com/ReVanced/revanced-branding/main/assets/revanced-logo/revanced-logo.svg" - }, - "contact": {"email": "contact@revanced.app"}, - "socials": social_links, - "donations": {"wallets": wallets, "links": links}, -} diff --git a/data/database.py b/data/database.py deleted file mode 100644 index ad93c56..0000000 --- a/data/database.py +++ /dev/null @@ -1,6 +0,0 @@ -from sqlalchemy import create_engine -from sqlalchemy.orm import sessionmaker - -engine = create_engine("sqlite:///persistence/database.db", pool_size=20) - -Session = sessionmaker(bind=engine) diff --git a/data/models.py b/data/models.py deleted file mode 100644 index 36621ba..0000000 --- a/data/models.py +++ /dev/null @@ -1,77 +0,0 @@ -from sqlalchemy import Column, Integer, String, DateTime -from sqlalchemy.ext.declarative import declarative_base -from sqlalchemy.orm import relationship -from sqlalchemy import ForeignKey - -from data.database import Session, engine - -Base = declarative_base() - - -class AnnouncementDbModel(Base): - __tablename__ = "announcements" - - id = Column(Integer, primary_key=True, autoincrement=True) - author = Column(String, nullable=True) - title = Column(String, nullable=False) - message = Column(String, nullable=True) - attachments = relationship("AttachmentDbModel", back_populates="announcements") - channel = Column(String, nullable=True) - created_at = Column(DateTime, nullable=False) - level = Column(Integer, nullable=True) - - -class AttachmentDbModel(Base): - __tablename__ = "attachments" - - id = Column(Integer, primary_key=True, autoincrement=True) - announcement_id = Column(Integer, ForeignKey("announcements.id")) - attachment_url = Column(String, nullable=False) - - announcements = relationship("AnnouncementDbModel", back_populates="attachments") - - -class UserDbModel(Base): - __tablename__ = "users" - - id = Column(Integer, primary_key=True, autoincrement=True) - username = Column(String, nullable=False) - password = Column(String, nullable=False) - - # Required by sanic-beskar - @property - def rolenames(self): - return [] - - @classmethod - async def lookup(cls, username=None): - try: - session = Session() - - user = session.query(UserDbModel).filter_by(username=username).first() - - session.close() - - return user - except: - return None - - @classmethod - async def identify(cls, id): - try: - session = Session() - - user = session.query(UserDbModel).filter_by(id=id).first() - - session.close() - - return user - except: - return None - - @property - def identity(self): - return self.id - - -Base.metadata.create_all(engine) diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index 02d487c..0000000 --- a/docker-compose.yml +++ /dev/null @@ -1,16 +0,0 @@ -version: "3.8" - -services: - revanced-api: - container_name: revanced-api - image: ghcr.io/revanced/revanced-api:latest - volumes: - - /data/revanced-api:/usr/src/app/persistence - environment: - - GITHUB_TOKEN=YOUR_GITHUB_TOKEN - - SECRET_KEY=YOUR_SECRET_KEY - - USERNAME=YOUR_USERNAME - - PASSWORD=YOUR_PASSWORD - ports: - - 127.0.0.1:7934:8000 - restart: unless-stopped diff --git a/docs/.swagger-codegen-ignore b/docs/.swagger-codegen-ignore new file mode 100644 index 0000000..c5fa491 --- /dev/null +++ b/docs/.swagger-codegen-ignore @@ -0,0 +1,23 @@ +# Swagger Codegen Ignore +# Generated by swagger-codegen https://github.com/swagger-api/swagger-codegen + +# Use this file to prevent files from being overwritten by the generator. +# The patterns follow closely to .gitignore or .dockerignore. + +# As an example, the C# client generator defines ApiClient.cs. +# You can make changes and tell Swagger Codgen to ignore just this file by uncommenting the following line: +#ApiClient.cs + +# You can match any string of characters against a directory, file or extension with a single asterisk (*): +#foo/*/qux +# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux + +# You can recursively match patterns against a directory, file or extension with a double asterisk (**): +#foo/**/qux +# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux + +# You can also negate patterns with an exclamation (!). +# For example, you can ignore all files in a docs folder with the file extension .md: +#docs/*.md +# Then explicitly reverse the ignore rule for a single file: +#!docs/README.md diff --git a/docs/.swagger-codegen/VERSION b/docs/.swagger-codegen/VERSION new file mode 100644 index 0000000..a254f0a --- /dev/null +++ b/docs/.swagger-codegen/VERSION @@ -0,0 +1 @@ +3.0.41 \ No newline at end of file diff --git a/docs/index.html b/docs/index.html new file mode 100644 index 0000000..af14f14 --- /dev/null +++ b/docs/index.html @@ -0,0 +1,2752 @@ + + + + + Application API + + + + + + + + + + + + + +
+
+
+ +
+
+
+
+

Application API

+
+
+
+ +
+
+

Default

+
+
+
+

rootGet

+

+
+
+
+

+

Hello World!

+

+
+
/
+

+

Usage and SDK Samples

+

+ + +
+
+
curl -X GET\
+-H "Accept: text/plain"\
+"http://0.0.0.0:8080/"
+
+
+
import io.swagger.client.*;
+import io.swagger.client.auth.*;
+import io.swagger.client.model.*;
+import io.swagger.client.api.DefaultApi;
+
+import java.io.File;
+import java.util.*;
+
+public class DefaultApiExample {
+
+    public static void main(String[] args) {
+        
+        DefaultApi apiInstance = new DefaultApi();
+        try {
+            'String' result = apiInstance.rootGet();
+            System.out.println(result);
+        } catch (ApiException e) {
+            System.err.println("Exception when calling DefaultApi#rootGet");
+            e.printStackTrace();
+        }
+    }
+}
+
+ +
+
import io.swagger.client.api.DefaultApi;
+
+public class DefaultApiExample {
+
+    public static void main(String[] args) {
+        DefaultApi apiInstance = new DefaultApi();
+        try {
+            'String' result = apiInstance.rootGet();
+            System.out.println(result);
+        } catch (ApiException e) {
+            System.err.println("Exception when calling DefaultApi#rootGet");
+            e.printStackTrace();
+        }
+    }
+}
+
+ +
+

+DefaultApi *apiInstance = [[DefaultApi alloc] init];
+
+[apiInstance rootGetWithCompletionHandler: 
+              ^('String' output, NSError* error) {
+                            if (output) {
+                                NSLog(@"%@", output);
+                            }
+                            if (error) {
+                                NSLog(@"Error: %@", error);
+                            }
+                        }];
+
+
+ +
+
var ApplicationApi = require('application_api');
+
+var api = new ApplicationApi.DefaultApi()
+var callback = function(error, data, response) {
+  if (error) {
+    console.error(error);
+  } else {
+    console.log('API called successfully. Returned data: ' + data);
+  }
+};
+api.rootGet(callback);
+
+
+ + +
+
using System;
+using System.Diagnostics;
+using IO.Swagger.Api;
+using IO.Swagger.Client;
+using IO.Swagger.Model;
+
+namespace Example
+{
+    public class rootGetExample
+    {
+        public void main()
+        {
+
+            var apiInstance = new DefaultApi();
+
+            try
+            {
+                'String' result = apiInstance.rootGet();
+                Debug.WriteLine(result);
+            }
+            catch (Exception e)
+            {
+                Debug.Print("Exception when calling DefaultApi.rootGet: " + e.Message );
+            }
+        }
+    }
+}
+
+
+ +
+
<?php
+require_once(__DIR__ . '/vendor/autoload.php');
+
+$api_instance = new Swagger\Client\ApiDefaultApi();
+
+try {
+    $result = $api_instance->rootGet();
+    print_r($result);
+} catch (Exception $e) {
+    echo 'Exception when calling DefaultApi->rootGet: ', $e->getMessage(), PHP_EOL;
+}
+?>
+
+ +
+
use Data::Dumper;
+use WWW::SwaggerClient::Configuration;
+use WWW::SwaggerClient::DefaultApi;
+
+my $api_instance = WWW::SwaggerClient::DefaultApi->new();
+
+eval { 
+    my $result = $api_instance->rootGet();
+    print Dumper($result);
+};
+if ($@) {
+    warn "Exception when calling DefaultApi->rootGet: $@\n";
+}
+
+ +
+
from __future__ import print_statement
+import time
+import swagger_client
+from swagger_client.rest import ApiException
+from pprint import pprint
+
+# create an instance of the API class
+api_instance = swagger_client.DefaultApi()
+
+try: 
+    api_response = api_instance.root_get()
+    pprint(api_response)
+except ApiException as e:
+    print("Exception when calling DefaultApi->rootGet: %s\n" % e)
+
+
+ +

Parameters

+ + + + + + +

Responses

+

Status: 200 - OK

+ + + +
+
+
+ +
+ +
+
+ +
+
+
+
+
+ +
+
+
+ + + + + + + + + diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..859e808 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,4 @@ +org.gradle.parallel = true +org.gradle.caching = true +kotlin.code.style = official +version = 0.0.1 \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml new file mode 100644 index 0000000..ae96f6a --- /dev/null +++ b/gradle/libs.versions.toml @@ -0,0 +1,41 @@ +[versions] +kotlin="1.9.22" +logback="1.4.14" +exposed="0.41.1" +h2="2.1.214" +koin="3.5.3" +dotenv="6.4.1" +ktor = "2.3.7" + +[libraries] +ktor-client-core = { module = "io.ktor:ktor-client-core" } +ktor-client-cio = { module = "io.ktor:ktor-client-cio" } +ktor-client-okhttp = { module = "io.ktor:ktor-client-okhttp" } +ktor-client-resources = { module = "io.ktor:ktor-client-resources" } +ktor-client-auth = { module = "io.ktor:ktor-client-auth" } +ktor-client-content-negotiation = { module = "io.ktor:ktor-client-content-negotiation" } +ktor-server-conditional-headers = { module = "io.ktor:ktor-server-conditional-headers" } +ktor-server-core = { module = "io.ktor:ktor-server-core" } +ktor-server-content-negotiation = { module = "io.ktor:ktor-server-content-negotiation" } +ktor-server-auth = { module = "io.ktor:ktor-server-auth" } +ktor-server-auth-jwt = { module = "io.ktor:ktor-server-auth-jwt" } +ktor-server-swagger = { module = "io.ktor:ktor-server-swagger" } +ktor-server-openapi = { module = "io.ktor:ktor-server-openapi" } +ktor-server-cors = { module = "io.ktor:ktor-server-cors" } +ktor-server-caching-headers = { module = "io.ktor:ktor-server-caching-headers" } +ktor-server-host-common = { module = "io.ktor:ktor-server-host-common" } +ktor-server-netty = { module = "io.ktor:ktor-server-netty" } +ktor-serialization-kotlinx-json = { module = "io.ktor:ktor-serialization-kotlinx-json" } +koin-ktor = { module = "io.insert-koin:koin-ktor", version.ref = "koin" } +h2 = { module = "com.h2database:h2", version.ref = "h2" } +logback-classic = { module = "ch.qos.logback:logback-classic", version.ref = "logback" } +exposed-core = { module = "org.jetbrains.exposed:exposed-core", version.ref = "exposed" } +exposed-jdbc = { module = "org.jetbrains.exposed:exposed-jdbc", version.ref = "exposed" } +dotenv-kotlin = { module = "io.github.cdimascio:dotenv-kotlin", version.ref = "dotenv" } +ktor-server-tests = { module = "io.ktor:ktor-server-tests" } +kotlin-test-junit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kotlin" } + +[plugins] +serilization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } +ktor = { id = "io.ktor.plugin", version.ref = "ktor" } +kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..7454180f2ae8848c63b8b4dea2cb829da983f2fa GIT binary patch literal 59536 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 QuEJmfE|#`Dqx|px0L@K;Y5)KL literal 0 HcmV?d00001 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..e411586 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100644 index 0000000..1b6c787 --- /dev/null +++ b/gradlew @@ -0,0 +1,234 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +APP_NAME="Gradle" +APP_BASE_NAME=${0##*/} + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..107acd3 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/mypy.ini b/mypy.ini deleted file mode 100644 index 9009c16..0000000 --- a/mypy.ini +++ /dev/null @@ -1,30 +0,0 @@ -[mypy] -python_version = 3.11 -pretty = true -follow_imports = normal -namespace_packages = true -show_column_numbers = true -show_error_codes = true -allow_redefinition = false -check_untyped_defs = true -implicit_reexport = false -strict_optional = true -strict_equality = true -warn_no_return = true -warn_redundant_casts = true -warn_unused_configs = true -warn_unused_ignores = true -warn_unreachable = true -plugins = pydantic.mypy - -[mypy-toolz.*] -ignore_missing_imports = True - -[mypy-sanic_testing.*] -ignore_missing_imports = True - -[mypy-fire.*] -ignore_missing_imports = True - -[mypy-cytoolz.*] -ignore_missing_imports = True diff --git a/persistence/.gitkeep b/persistence/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/poetry.lock b/poetry.lock deleted file mode 100644 index 590e15e..0000000 --- a/poetry.lock +++ /dev/null @@ -1,2599 +0,0 @@ -# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. - -[[package]] -name = "aiodns" -version = "3.2.0" -description = "Simple DNS resolver for asyncio" -optional = false -python-versions = "*" -files = [ - {file = "aiodns-3.2.0-py3-none-any.whl", hash = "sha256:e443c0c27b07da3174a109fd9e736d69058d808f144d3c9d56dbd1776964c5f5"}, - {file = "aiodns-3.2.0.tar.gz", hash = "sha256:62869b23409349c21b072883ec8998316b234c9a9e36675756e8e317e8768f72"}, -] - -[package.dependencies] -pycares = ">=4.0.0" - -[[package]] -name = "aiofiles" -version = "23.2.1" -description = "File support for asyncio." -optional = false -python-versions = ">=3.7" -files = [ - {file = "aiofiles-23.2.1-py3-none-any.whl", hash = "sha256:19297512c647d4b27a2cf7c34caa7e405c0d60b5560618a29a9fe027b18b0107"}, - {file = "aiofiles-23.2.1.tar.gz", hash = "sha256:84ec2218d8419404abcb9f0c02df3f34c6e0a68ed41072acfb1cef5cbc29051a"}, -] - -[[package]] -name = "aiohttp" -version = "3.9.3" -description = "Async http client/server framework (asyncio)" -optional = false -python-versions = ">=3.8" -files = [ - {file = "aiohttp-3.9.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:939677b61f9d72a4fa2a042a5eee2a99a24001a67c13da113b2e30396567db54"}, - {file = "aiohttp-3.9.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1f5cd333fcf7590a18334c90f8c9147c837a6ec8a178e88d90a9b96ea03194cc"}, - {file = "aiohttp-3.9.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:82e6aa28dd46374f72093eda8bcd142f7771ee1eb9d1e223ff0fa7177a96b4a5"}, - {file = "aiohttp-3.9.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f56455b0c2c7cc3b0c584815264461d07b177f903a04481dfc33e08a89f0c26b"}, - {file = "aiohttp-3.9.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bca77a198bb6e69795ef2f09a5f4c12758487f83f33d63acde5f0d4919815768"}, - {file = "aiohttp-3.9.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e083c285857b78ee21a96ba1eb1b5339733c3563f72980728ca2b08b53826ca5"}, - {file = "aiohttp-3.9.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab40e6251c3873d86ea9b30a1ac6d7478c09277b32e14745d0d3c6e76e3c7e29"}, - {file = "aiohttp-3.9.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:df822ee7feaaeffb99c1a9e5e608800bd8eda6e5f18f5cfb0dc7eeb2eaa6bbec"}, - {file = "aiohttp-3.9.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:acef0899fea7492145d2bbaaaec7b345c87753168589cc7faf0afec9afe9b747"}, - {file = "aiohttp-3.9.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:cd73265a9e5ea618014802ab01babf1940cecb90c9762d8b9e7d2cc1e1969ec6"}, - {file = "aiohttp-3.9.3-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:a78ed8a53a1221393d9637c01870248a6f4ea5b214a59a92a36f18151739452c"}, - {file = "aiohttp-3.9.3-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:6b0e029353361f1746bac2e4cc19b32f972ec03f0f943b390c4ab3371840aabf"}, - {file = "aiohttp-3.9.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7cf5c9458e1e90e3c390c2639f1017a0379a99a94fdfad3a1fd966a2874bba52"}, - {file = "aiohttp-3.9.3-cp310-cp310-win32.whl", hash = "sha256:3e59c23c52765951b69ec45ddbbc9403a8761ee6f57253250c6e1536cacc758b"}, - {file = "aiohttp-3.9.3-cp310-cp310-win_amd64.whl", hash = "sha256:055ce4f74b82551678291473f66dc9fb9048a50d8324278751926ff0ae7715e5"}, - {file = "aiohttp-3.9.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6b88f9386ff1ad91ace19d2a1c0225896e28815ee09fc6a8932fded8cda97c3d"}, - {file = "aiohttp-3.9.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c46956ed82961e31557b6857a5ca153c67e5476972e5f7190015018760938da2"}, - {file = "aiohttp-3.9.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:07b837ef0d2f252f96009e9b8435ec1fef68ef8b1461933253d318748ec1acdc"}, - {file = "aiohttp-3.9.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad46e6f620574b3b4801c68255492e0159d1712271cc99d8bdf35f2043ec266"}, - {file = "aiohttp-3.9.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5ed3e046ea7b14938112ccd53d91c1539af3e6679b222f9469981e3dac7ba1ce"}, - {file = "aiohttp-3.9.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:039df344b45ae0b34ac885ab5b53940b174530d4dd8a14ed8b0e2155b9dddccb"}, - {file = "aiohttp-3.9.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7943c414d3a8d9235f5f15c22ace69787c140c80b718dcd57caaade95f7cd93b"}, - {file = "aiohttp-3.9.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:84871a243359bb42c12728f04d181a389718710129b36b6aad0fc4655a7647d4"}, - {file = "aiohttp-3.9.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:5eafe2c065df5401ba06821b9a054d9cb2848867f3c59801b5d07a0be3a380ae"}, - {file = "aiohttp-3.9.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:9d3c9b50f19704552f23b4eaea1fc082fdd82c63429a6506446cbd8737823da3"}, - {file = "aiohttp-3.9.3-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:f033d80bc6283092613882dfe40419c6a6a1527e04fc69350e87a9df02bbc283"}, - {file = "aiohttp-3.9.3-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:2c895a656dd7e061b2fd6bb77d971cc38f2afc277229ce7dd3552de8313a483e"}, - {file = "aiohttp-3.9.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1f5a71d25cd8106eab05f8704cd9167b6e5187bcdf8f090a66c6d88b634802b4"}, - {file = "aiohttp-3.9.3-cp311-cp311-win32.whl", hash = "sha256:50fca156d718f8ced687a373f9e140c1bb765ca16e3d6f4fe116e3df7c05b2c5"}, - {file = "aiohttp-3.9.3-cp311-cp311-win_amd64.whl", hash = "sha256:5fe9ce6c09668063b8447f85d43b8d1c4e5d3d7e92c63173e6180b2ac5d46dd8"}, - {file = "aiohttp-3.9.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:38a19bc3b686ad55804ae931012f78f7a534cce165d089a2059f658f6c91fa60"}, - {file = "aiohttp-3.9.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:770d015888c2a598b377bd2f663adfd947d78c0124cfe7b959e1ef39f5b13869"}, - {file = "aiohttp-3.9.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ee43080e75fc92bf36219926c8e6de497f9b247301bbf88c5c7593d931426679"}, - {file = "aiohttp-3.9.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:52df73f14ed99cee84865b95a3d9e044f226320a87af208f068ecc33e0c35b96"}, - {file = "aiohttp-3.9.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc9b311743a78043b26ffaeeb9715dc360335e5517832f5a8e339f8a43581e4d"}, - {file = "aiohttp-3.9.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b955ed993491f1a5da7f92e98d5dad3c1e14dc175f74517c4e610b1f2456fb11"}, - {file = "aiohttp-3.9.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:504b6981675ace64c28bf4a05a508af5cde526e36492c98916127f5a02354d53"}, - {file = "aiohttp-3.9.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a6fe5571784af92b6bc2fda8d1925cccdf24642d49546d3144948a6a1ed58ca5"}, - {file = "aiohttp-3.9.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ba39e9c8627edc56544c8628cc180d88605df3892beeb2b94c9bc857774848ca"}, - {file = "aiohttp-3.9.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:e5e46b578c0e9db71d04c4b506a2121c0cb371dd89af17a0586ff6769d4c58c1"}, - {file = "aiohttp-3.9.3-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:938a9653e1e0c592053f815f7028e41a3062e902095e5a7dc84617c87267ebd5"}, - {file = "aiohttp-3.9.3-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:c3452ea726c76e92f3b9fae4b34a151981a9ec0a4847a627c43d71a15ac32aa6"}, - {file = "aiohttp-3.9.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ff30218887e62209942f91ac1be902cc80cddb86bf00fbc6783b7a43b2bea26f"}, - {file = "aiohttp-3.9.3-cp312-cp312-win32.whl", hash = "sha256:38f307b41e0bea3294a9a2a87833191e4bcf89bb0365e83a8be3a58b31fb7f38"}, - {file = "aiohttp-3.9.3-cp312-cp312-win_amd64.whl", hash = "sha256:b791a3143681a520c0a17e26ae7465f1b6f99461a28019d1a2f425236e6eedb5"}, - {file = "aiohttp-3.9.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0ed621426d961df79aa3b963ac7af0d40392956ffa9be022024cd16297b30c8c"}, - {file = "aiohttp-3.9.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7f46acd6a194287b7e41e87957bfe2ad1ad88318d447caf5b090012f2c5bb528"}, - {file = "aiohttp-3.9.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:feeb18a801aacb098220e2c3eea59a512362eb408d4afd0c242044c33ad6d542"}, - {file = "aiohttp-3.9.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f734e38fd8666f53da904c52a23ce517f1b07722118d750405af7e4123933511"}, - {file = "aiohttp-3.9.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b40670ec7e2156d8e57f70aec34a7216407848dfe6c693ef131ddf6e76feb672"}, - {file = "aiohttp-3.9.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fdd215b7b7fd4a53994f238d0f46b7ba4ac4c0adb12452beee724ddd0743ae5d"}, - {file = "aiohttp-3.9.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:017a21b0df49039c8f46ca0971b3a7fdc1f56741ab1240cb90ca408049766168"}, - {file = "aiohttp-3.9.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e99abf0bba688259a496f966211c49a514e65afa9b3073a1fcee08856e04425b"}, - {file = "aiohttp-3.9.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:648056db9a9fa565d3fa851880f99f45e3f9a771dd3ff3bb0c048ea83fb28194"}, - {file = "aiohttp-3.9.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8aacb477dc26797ee089721536a292a664846489c49d3ef9725f992449eda5a8"}, - {file = "aiohttp-3.9.3-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:522a11c934ea660ff8953eda090dcd2154d367dec1ae3c540aff9f8a5c109ab4"}, - {file = "aiohttp-3.9.3-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:5bce0dc147ca85caa5d33debc4f4d65e8e8b5c97c7f9f660f215fa74fc49a321"}, - {file = "aiohttp-3.9.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:4b4af9f25b49a7be47c0972139e59ec0e8285c371049df1a63b6ca81fdd216a2"}, - {file = "aiohttp-3.9.3-cp38-cp38-win32.whl", hash = "sha256:298abd678033b8571995650ccee753d9458dfa0377be4dba91e4491da3f2be63"}, - {file = "aiohttp-3.9.3-cp38-cp38-win_amd64.whl", hash = "sha256:69361bfdca5468c0488d7017b9b1e5ce769d40b46a9f4a2eed26b78619e9396c"}, - {file = "aiohttp-3.9.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:0fa43c32d1643f518491d9d3a730f85f5bbaedcbd7fbcae27435bb8b7a061b29"}, - {file = "aiohttp-3.9.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:835a55b7ca49468aaaac0b217092dfdff370e6c215c9224c52f30daaa735c1c1"}, - {file = "aiohttp-3.9.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:06a9b2c8837d9a94fae16c6223acc14b4dfdff216ab9b7202e07a9a09541168f"}, - {file = "aiohttp-3.9.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:abf151955990d23f84205286938796c55ff11bbfb4ccfada8c9c83ae6b3c89a3"}, - {file = "aiohttp-3.9.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59c26c95975f26e662ca78fdf543d4eeaef70e533a672b4113dd888bd2423caa"}, - {file = "aiohttp-3.9.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f95511dd5d0e05fd9728bac4096319f80615aaef4acbecb35a990afebe953b0e"}, - {file = "aiohttp-3.9.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:595f105710293e76b9dc09f52e0dd896bd064a79346234b521f6b968ffdd8e58"}, - {file = "aiohttp-3.9.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7c8b816c2b5af5c8a436df44ca08258fc1a13b449393a91484225fcb7545533"}, - {file = "aiohttp-3.9.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f1088fa100bf46e7b398ffd9904f4808a0612e1d966b4aa43baa535d1b6341eb"}, - {file = "aiohttp-3.9.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f59dfe57bb1ec82ac0698ebfcdb7bcd0e99c255bd637ff613760d5f33e7c81b3"}, - {file = "aiohttp-3.9.3-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:361a1026c9dd4aba0109e4040e2aecf9884f5cfe1b1b1bd3d09419c205e2e53d"}, - {file = "aiohttp-3.9.3-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:363afe77cfcbe3a36353d8ea133e904b108feea505aa4792dad6585a8192c55a"}, - {file = "aiohttp-3.9.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8e2c45c208c62e955e8256949eb225bd8b66a4c9b6865729a786f2aa79b72e9d"}, - {file = "aiohttp-3.9.3-cp39-cp39-win32.whl", hash = "sha256:f7217af2e14da0856e082e96ff637f14ae45c10a5714b63c77f26d8884cf1051"}, - {file = "aiohttp-3.9.3-cp39-cp39-win_amd64.whl", hash = "sha256:27468897f628c627230dba07ec65dc8d0db566923c48f29e084ce382119802bc"}, - {file = "aiohttp-3.9.3.tar.gz", hash = "sha256:90842933e5d1ff760fae6caca4b2b3edba53ba8f4b71e95dacf2818a2aca06f7"}, -] - -[package.dependencies] -aiodns = {version = "*", optional = true, markers = "(sys_platform == \"linux\" or sys_platform == \"darwin\") and extra == \"speedups\""} -aiosignal = ">=1.1.2" -attrs = ">=17.3.0" -Brotli = {version = "*", optional = true, markers = "platform_python_implementation == \"CPython\" and extra == \"speedups\""} -brotlicffi = {version = "*", optional = true, markers = "platform_python_implementation != \"CPython\" and extra == \"speedups\""} -frozenlist = ">=1.1.1" -multidict = ">=4.5,<7.0" -yarl = ">=1.0,<2.0" - -[package.extras] -speedups = ["Brotli", "aiodns", "brotlicffi"] - -[[package]] -name = "aiosignal" -version = "1.3.1" -description = "aiosignal: a list of registered asynchronous callbacks" -optional = false -python-versions = ">=3.7" -files = [ - {file = "aiosignal-1.3.1-py3-none-any.whl", hash = "sha256:f8376fb07dd1e86a584e4fcdec80b36b7f81aac666ebc724e2c090300dd83b17"}, - {file = "aiosignal-1.3.1.tar.gz", hash = "sha256:54cd96e15e1649b75d6c87526a6ff0b6c1b0dd3459f43d9ca11d48c339b68cfc"}, -] - -[package.dependencies] -frozenlist = ">=1.1.0" - -[[package]] -name = "argon2-cffi" -version = "23.1.0" -description = "Argon2 for Python" -optional = false -python-versions = ">=3.7" -files = [ - {file = "argon2_cffi-23.1.0-py3-none-any.whl", hash = "sha256:c670642b78ba29641818ab2e68bd4e6a78ba53b7eff7b4c3815ae16abf91c7ea"}, - {file = "argon2_cffi-23.1.0.tar.gz", hash = "sha256:879c3e79a2729ce768ebb7d36d4609e3a78a4ca2ec3a9f12286ca057e3d0db08"}, -] - -[package.dependencies] -argon2-cffi-bindings = "*" - -[package.extras] -dev = ["argon2-cffi[tests,typing]", "tox (>4)"] -docs = ["furo", "myst-parser", "sphinx", "sphinx-copybutton", "sphinx-notfound-page"] -tests = ["hypothesis", "pytest"] -typing = ["mypy"] - -[[package]] -name = "argon2-cffi-bindings" -version = "21.2.0" -description = "Low-level CFFI bindings for Argon2" -optional = false -python-versions = ">=3.6" -files = [ - {file = "argon2-cffi-bindings-21.2.0.tar.gz", hash = "sha256:bb89ceffa6c791807d1305ceb77dbfacc5aa499891d2c55661c6459651fc39e3"}, - {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ccb949252cb2ab3a08c02024acb77cfb179492d5701c7cbdbfd776124d4d2367"}, - {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9524464572e12979364b7d600abf96181d3541da11e23ddf565a32e70bd4dc0d"}, - {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b746dba803a79238e925d9046a63aa26bf86ab2a2fe74ce6b009a1c3f5c8f2ae"}, - {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:58ed19212051f49a523abb1dbe954337dc82d947fb6e5a0da60f7c8471a8476c"}, - {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:bd46088725ef7f58b5a1ef7ca06647ebaf0eb4baff7d1d0d177c6cc8744abd86"}, - {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_i686.whl", hash = "sha256:8cd69c07dd875537a824deec19f978e0f2078fdda07fd5c42ac29668dda5f40f"}, - {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:f1152ac548bd5b8bcecfb0b0371f082037e47128653df2e8ba6e914d384f3c3e"}, - {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-win32.whl", hash = "sha256:603ca0aba86b1349b147cab91ae970c63118a0f30444d4bc80355937c950c082"}, - {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-win_amd64.whl", hash = "sha256:b2ef1c30440dbbcba7a5dc3e319408b59676e2e039e2ae11a8775ecf482b192f"}, - {file = "argon2_cffi_bindings-21.2.0-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:e415e3f62c8d124ee16018e491a009937f8cf7ebf5eb430ffc5de21b900dad93"}, - {file = "argon2_cffi_bindings-21.2.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:3e385d1c39c520c08b53d63300c3ecc28622f076f4c2b0e6d7e796e9f6502194"}, - {file = "argon2_cffi_bindings-21.2.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c3e3cc67fdb7d82c4718f19b4e7a87123caf8a93fde7e23cf66ac0337d3cb3f"}, - {file = "argon2_cffi_bindings-21.2.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6a22ad9800121b71099d0fb0a65323810a15f2e292f2ba450810a7316e128ee5"}, - {file = "argon2_cffi_bindings-21.2.0-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f9f8b450ed0547e3d473fdc8612083fd08dd2120d6ac8f73828df9b7d45bb351"}, - {file = "argon2_cffi_bindings-21.2.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:93f9bf70084f97245ba10ee36575f0c3f1e7d7724d67d8e5b08e61787c320ed7"}, - {file = "argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:3b9ef65804859d335dc6b31582cad2c5166f0c3e7975f324d9ffaa34ee7e6583"}, - {file = "argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4966ef5848d820776f5f562a7d45fdd70c2f330c961d0d745b784034bd9f48d"}, - {file = "argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:20ef543a89dee4db46a1a6e206cd015360e5a75822f76df533845c3cbaf72670"}, - {file = "argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ed2937d286e2ad0cc79a7087d3c272832865f779430e0cc2b4f3718d3159b0cb"}, - {file = "argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:5e00316dabdaea0b2dd82d141cc66889ced0cdcbfa599e8b471cf22c620c329a"}, -] - -[package.dependencies] -cffi = ">=1.0.1" - -[package.extras] -dev = ["cogapp", "pre-commit", "pytest", "wheel"] -tests = ["pytest"] - -[[package]] -name = "asyncstdlib" -version = "3.12.2" -description = "The missing async toolbox" -optional = false -python-versions = "~=3.8" -files = [ - {file = "asyncstdlib-3.12.2-py3-none-any.whl", hash = "sha256:343b4ae330a76a8ed05fc83a03e2756dcaa7bede0cf4c7f0ba4aa7112feddec4"}, - {file = "asyncstdlib-3.12.2.tar.gz", hash = "sha256:3fea1eef246b68022c5bbaf6661db59f640ef54dd05fa1d0ac987e49b86a5529"}, -] - -[package.extras] -doc = ["sphinx", "sphinxcontrib-trio"] -test = ["black", "coverage", "flake8", "flake8-2020", "flake8-bugbear", "mypy", "pytest", "pytest-cov", "typing-extensions"] - -[[package]] -name = "attrs" -version = "23.2.0" -description = "Classes Without Boilerplate" -optional = false -python-versions = ">=3.7" -files = [ - {file = "attrs-23.2.0-py3-none-any.whl", hash = "sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1"}, - {file = "attrs-23.2.0.tar.gz", hash = "sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30"}, -] - -[package.extras] -cov = ["attrs[tests]", "coverage[toml] (>=5.3)"] -dev = ["attrs[tests]", "pre-commit"] -docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope-interface"] -tests = ["attrs[tests-no-zope]", "zope-interface"] -tests-mypy = ["mypy (>=1.6)", "pytest-mypy-plugins"] -tests-no-zope = ["attrs[tests-mypy]", "cloudpickle", "hypothesis", "pympler", "pytest (>=4.3.0)", "pytest-xdist[psutil]"] - -[[package]] -name = "beautifulsoup4" -version = "4.12.3" -description = "Screen-scraping library" -optional = false -python-versions = ">=3.6.0" -files = [ - {file = "beautifulsoup4-4.12.3-py3-none-any.whl", hash = "sha256:b80878c9f40111313e55da8ba20bdba06d8fa3969fc68304167741bbf9e082ed"}, - {file = "beautifulsoup4-4.12.3.tar.gz", hash = "sha256:74e3d1928edc070d21748185c46e3fb33490f22f52a3addee9aee0f4f7781051"}, -] - -[package.dependencies] -soupsieve = ">1.2" - -[package.extras] -cchardet = ["cchardet"] -chardet = ["chardet"] -charset-normalizer = ["charset-normalizer"] -html5lib = ["html5lib"] -lxml = ["lxml"] - -[[package]] -name = "brotli" -version = "1.1.0" -description = "Python bindings for the Brotli compression library" -optional = false -python-versions = "*" -files = [ - {file = "Brotli-1.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e1140c64812cb9b06c922e77f1c26a75ec5e3f0fb2bf92cc8c58720dec276752"}, - {file = "Brotli-1.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c8fd5270e906eef71d4a8d19b7c6a43760c6abcfcc10c9101d14eb2357418de9"}, - {file = "Brotli-1.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1ae56aca0402a0f9a3431cddda62ad71666ca9d4dc3a10a142b9dce2e3c0cda3"}, - {file = "Brotli-1.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:43ce1b9935bfa1ede40028054d7f48b5469cd02733a365eec8a329ffd342915d"}, - {file = "Brotli-1.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:7c4855522edb2e6ae7fdb58e07c3ba9111e7621a8956f481c68d5d979c93032e"}, - {file = "Brotli-1.1.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:38025d9f30cf4634f8309c6874ef871b841eb3c347e90b0851f63d1ded5212da"}, - {file = "Brotli-1.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e6a904cb26bfefc2f0a6f240bdf5233be78cd2488900a2f846f3c3ac8489ab80"}, - {file = "Brotli-1.1.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a37b8f0391212d29b3a91a799c8e4a2855e0576911cdfb2515487e30e322253d"}, - {file = "Brotli-1.1.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:e84799f09591700a4154154cab9787452925578841a94321d5ee8fb9a9a328f0"}, - {file = "Brotli-1.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f66b5337fa213f1da0d9000bc8dc0cb5b896b726eefd9c6046f699b169c41b9e"}, - {file = "Brotli-1.1.0-cp310-cp310-win32.whl", hash = "sha256:be36e3d172dc816333f33520154d708a2657ea63762ec16b62ece02ab5e4daf2"}, - {file = "Brotli-1.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:0c6244521dda65ea562d5a69b9a26120769b7a9fb3db2fe9545935ed6735b128"}, - {file = "Brotli-1.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a3daabb76a78f829cafc365531c972016e4aa8d5b4bf60660ad8ecee19df7ccc"}, - {file = "Brotli-1.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c8146669223164fc87a7e3de9f81e9423c67a79d6b3447994dfb9c95da16e2d6"}, - {file = "Brotli-1.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:30924eb4c57903d5a7526b08ef4a584acc22ab1ffa085faceb521521d2de32dd"}, - {file = "Brotli-1.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ceb64bbc6eac5a140ca649003756940f8d6a7c444a68af170b3187623b43bebf"}, - {file = "Brotli-1.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a469274ad18dc0e4d316eefa616d1d0c2ff9da369af19fa6f3daa4f09671fd61"}, - {file = "Brotli-1.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:524f35912131cc2cabb00edfd8d573b07f2d9f21fa824bd3fb19725a9cf06327"}, - {file = "Brotli-1.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:5b3cc074004d968722f51e550b41a27be656ec48f8afaeeb45ebf65b561481dd"}, - {file = "Brotli-1.1.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:19c116e796420b0cee3da1ccec3b764ed2952ccfcc298b55a10e5610ad7885f9"}, - {file = "Brotli-1.1.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:510b5b1bfbe20e1a7b3baf5fed9e9451873559a976c1a78eebaa3b86c57b4265"}, - {file = "Brotli-1.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a1fd8a29719ccce974d523580987b7f8229aeace506952fa9ce1d53a033873c8"}, - {file = "Brotli-1.1.0-cp311-cp311-win32.whl", hash = "sha256:39da8adedf6942d76dc3e46653e52df937a3c4d6d18fdc94a7c29d263b1f5b50"}, - {file = "Brotli-1.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:aac0411d20e345dc0920bdec5548e438e999ff68d77564d5e9463a7ca9d3e7b1"}, - {file = "Brotli-1.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:316cc9b17edf613ac76b1f1f305d2a748f1b976b033b049a6ecdfd5612c70409"}, - {file = "Brotli-1.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:caf9ee9a5775f3111642d33b86237b05808dafcd6268faa492250e9b78046eb2"}, - {file = "Brotli-1.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:70051525001750221daa10907c77830bc889cb6d865cc0b813d9db7fefc21451"}, - {file = "Brotli-1.1.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7f4bf76817c14aa98cc6697ac02f3972cb8c3da93e9ef16b9c66573a68014f91"}, - {file = "Brotli-1.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d0c5516f0aed654134a2fc936325cc2e642f8a0e096d075209672eb321cff408"}, - {file = "Brotli-1.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6c3020404e0b5eefd7c9485ccf8393cfb75ec38ce75586e046573c9dc29967a0"}, - {file = "Brotli-1.1.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:4ed11165dd45ce798d99a136808a794a748d5dc38511303239d4e2363c0695dc"}, - {file = "Brotli-1.1.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:4093c631e96fdd49e0377a9c167bfd75b6d0bad2ace734c6eb20b348bc3ea180"}, - {file = "Brotli-1.1.0-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:7e4c4629ddad63006efa0ef968c8e4751c5868ff0b1c5c40f76524e894c50248"}, - {file = "Brotli-1.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:861bf317735688269936f755fa136a99d1ed526883859f86e41a5d43c61d8966"}, - {file = "Brotli-1.1.0-cp312-cp312-win32.whl", hash = "sha256:5f4d5ea15c9382135076d2fb28dde923352fe02951e66935a9efaac8f10e81b0"}, - {file = "Brotli-1.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:906bc3a79de8c4ae5b86d3d75a8b77e44404b0f4261714306e3ad248d8ab0951"}, - {file = "Brotli-1.1.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:a090ca607cbb6a34b0391776f0cb48062081f5f60ddcce5d11838e67a01928d1"}, - {file = "Brotli-1.1.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2de9d02f5bda03d27ede52e8cfe7b865b066fa49258cbab568720aa5be80a47d"}, - {file = "Brotli-1.1.0-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2333e30a5e00fe0fe55903c8832e08ee9c3b1382aacf4db26664a16528d51b4b"}, - {file = "Brotli-1.1.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4d4a848d1837973bf0f4b5e54e3bec977d99be36a7895c61abb659301b02c112"}, - {file = "Brotli-1.1.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:fdc3ff3bfccdc6b9cc7c342c03aa2400683f0cb891d46e94b64a197910dc4064"}, - {file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:5eeb539606f18a0b232d4ba45adccde4125592f3f636a6182b4a8a436548b914"}, - {file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:fd5f17ff8f14003595ab414e45fce13d073e0762394f957182e69035c9f3d7c2"}, - {file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:069a121ac97412d1fe506da790b3e69f52254b9df4eb665cd42460c837193354"}, - {file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:e93dfc1a1165e385cc8239fab7c036fb2cd8093728cbd85097b284d7b99249a2"}, - {file = "Brotli-1.1.0-cp36-cp36m-win32.whl", hash = "sha256:a599669fd7c47233438a56936988a2478685e74854088ef5293802123b5b2460"}, - {file = "Brotli-1.1.0-cp36-cp36m-win_amd64.whl", hash = "sha256:d143fd47fad1db3d7c27a1b1d66162e855b5d50a89666af46e1679c496e8e579"}, - {file = "Brotli-1.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:11d00ed0a83fa22d29bc6b64ef636c4552ebafcef57154b4ddd132f5638fbd1c"}, - {file = "Brotli-1.1.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f733d788519c7e3e71f0855c96618720f5d3d60c3cb829d8bbb722dddce37985"}, - {file = "Brotli-1.1.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:929811df5462e182b13920da56c6e0284af407d1de637d8e536c5cd00a7daf60"}, - {file = "Brotli-1.1.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0b63b949ff929fbc2d6d3ce0e924c9b93c9785d877a21a1b678877ffbbc4423a"}, - {file = "Brotli-1.1.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:d192f0f30804e55db0d0e0a35d83a9fead0e9a359a9ed0285dbacea60cc10a84"}, - {file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:f296c40e23065d0d6650c4aefe7470d2a25fffda489bcc3eb66083f3ac9f6643"}, - {file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:919e32f147ae93a09fe064d77d5ebf4e35502a8df75c29fb05788528e330fe74"}, - {file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:23032ae55523cc7bccb4f6a0bf368cd25ad9bcdcc1990b64a647e7bbcce9cb5b"}, - {file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:224e57f6eac61cc449f498cc5f0e1725ba2071a3d4f48d5d9dffba42db196438"}, - {file = "Brotli-1.1.0-cp37-cp37m-win32.whl", hash = "sha256:587ca6d3cef6e4e868102672d3bd9dc9698c309ba56d41c2b9c85bbb903cdb95"}, - {file = "Brotli-1.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:2954c1c23f81c2eaf0b0717d9380bd348578a94161a65b3a2afc62c86467dd68"}, - {file = "Brotli-1.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:efa8b278894b14d6da122a72fefcebc28445f2d3f880ac59d46c90f4c13be9a3"}, - {file = "Brotli-1.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:03d20af184290887bdea3f0f78c4f737d126c74dc2f3ccadf07e54ceca3bf208"}, - {file = "Brotli-1.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6172447e1b368dcbc458925e5ddaf9113477b0ed542df258d84fa28fc45ceea7"}, - {file = "Brotli-1.1.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a743e5a28af5f70f9c080380a5f908d4d21d40e8f0e0c8901604d15cfa9ba751"}, - {file = "Brotli-1.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0541e747cce78e24ea12d69176f6a7ddb690e62c425e01d31cc065e69ce55b48"}, - {file = "Brotli-1.1.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:cdbc1fc1bc0bff1cef838eafe581b55bfbffaed4ed0318b724d0b71d4d377619"}, - {file = "Brotli-1.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:890b5a14ce214389b2cc36ce82f3093f96f4cc730c1cffdbefff77a7c71f2a97"}, - {file = "Brotli-1.1.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1ab4fbee0b2d9098c74f3057b2bc055a8bd92ccf02f65944a241b4349229185a"}, - {file = "Brotli-1.1.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:141bd4d93984070e097521ed07e2575b46f817d08f9fa42b16b9b5f27b5ac088"}, - {file = "Brotli-1.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fce1473f3ccc4187f75b4690cfc922628aed4d3dd013d047f95a9b3919a86596"}, - {file = "Brotli-1.1.0-cp38-cp38-win32.whl", hash = "sha256:db85ecf4e609a48f4b29055f1e144231b90edc90af7481aa731ba2d059226b1b"}, - {file = "Brotli-1.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:3d7954194c36e304e1523f55d7042c59dc53ec20dd4e9ea9d151f1b62b4415c0"}, - {file = "Brotli-1.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5fb2ce4b8045c78ebbc7b8f3c15062e435d47e7393cc57c25115cfd49883747a"}, - {file = "Brotli-1.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7905193081db9bfa73b1219140b3d315831cbff0d8941f22da695832f0dd188f"}, - {file = "Brotli-1.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a77def80806c421b4b0af06f45d65a136e7ac0bdca3c09d9e2ea4e515367c7e9"}, - {file = "Brotli-1.1.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8dadd1314583ec0bf2d1379f7008ad627cd6336625d6679cf2f8e67081b83acf"}, - {file = "Brotli-1.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:901032ff242d479a0efa956d853d16875d42157f98951c0230f69e69f9c09bac"}, - {file = "Brotli-1.1.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:22fc2a8549ffe699bfba2256ab2ed0421a7b8fadff114a3d201794e45a9ff578"}, - {file = "Brotli-1.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ae15b066e5ad21366600ebec29a7ccbc86812ed267e4b28e860b8ca16a2bc474"}, - {file = "Brotli-1.1.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:949f3b7c29912693cee0afcf09acd6ebc04c57af949d9bf77d6101ebb61e388c"}, - {file = "Brotli-1.1.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:89f4988c7203739d48c6f806f1e87a1d96e0806d44f0fba61dba81392c9e474d"}, - {file = "Brotli-1.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:de6551e370ef19f8de1807d0a9aa2cdfdce2e85ce88b122fe9f6b2b076837e59"}, - {file = "Brotli-1.1.0-cp39-cp39-win32.whl", hash = "sha256:f0d8a7a6b5983c2496e364b969f0e526647a06b075d034f3297dc66f3b360c64"}, - {file = "Brotli-1.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:cdad5b9014d83ca68c25d2e9444e28e967ef16e80f6b436918c700c117a85467"}, - {file = "Brotli-1.1.0.tar.gz", hash = "sha256:81de08ac11bcb85841e440c13611c00b67d3bf82698314928d0b676362546724"}, -] - -[[package]] -name = "brotlicffi" -version = "1.1.0.0" -description = "Python CFFI bindings to the Brotli library" -optional = false -python-versions = ">=3.7" -files = [ - {file = "brotlicffi-1.1.0.0-cp37-abi3-macosx_10_9_x86_64.whl", hash = "sha256:9b7ae6bd1a3f0df532b6d67ff674099a96d22bc0948955cb338488c31bfb8851"}, - {file = "brotlicffi-1.1.0.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19ffc919fa4fc6ace69286e0a23b3789b4219058313cf9b45625016bf7ff996b"}, - {file = "brotlicffi-1.1.0.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9feb210d932ffe7798ee62e6145d3a757eb6233aa9a4e7db78dd3690d7755814"}, - {file = "brotlicffi-1.1.0.0-cp37-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:84763dbdef5dd5c24b75597a77e1b30c66604725707565188ba54bab4f114820"}, - {file = "brotlicffi-1.1.0.0-cp37-abi3-win32.whl", hash = "sha256:1b12b50e07c3911e1efa3a8971543e7648100713d4e0971b13631cce22c587eb"}, - {file = "brotlicffi-1.1.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:994a4f0681bb6c6c3b0925530a1926b7a189d878e6e5e38fae8efa47c5d9c613"}, - {file = "brotlicffi-1.1.0.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:2e4aeb0bd2540cb91b069dbdd54d458da8c4334ceaf2d25df2f4af576d6766ca"}, - {file = "brotlicffi-1.1.0.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b7b0033b0d37bb33009fb2fef73310e432e76f688af76c156b3594389d81391"}, - {file = "brotlicffi-1.1.0.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:54a07bb2374a1eba8ebb52b6fafffa2afd3c4df85ddd38fcc0511f2bb387c2a8"}, - {file = "brotlicffi-1.1.0.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7901a7dc4b88f1c1475de59ae9be59799db1007b7d059817948d8e4f12e24e35"}, - {file = "brotlicffi-1.1.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ce01c7316aebc7fce59da734286148b1d1b9455f89cf2c8a4dfce7d41db55c2d"}, - {file = "brotlicffi-1.1.0.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:246f1d1a90279bb6069de3de8d75a8856e073b8ff0b09dcca18ccc14cec85979"}, - {file = "brotlicffi-1.1.0.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc4bc5d82bc56ebd8b514fb8350cfac4627d6b0743382e46d033976a5f80fab6"}, - {file = "brotlicffi-1.1.0.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37c26ecb14386a44b118ce36e546ce307f4810bc9598a6e6cb4f7fca725ae7e6"}, - {file = "brotlicffi-1.1.0.0-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca72968ae4eaf6470498d5c2887073f7efe3b1e7d7ec8be11a06a79cc810e990"}, - {file = "brotlicffi-1.1.0.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:add0de5b9ad9e9aa293c3aa4e9deb2b61e99ad6c1634e01d01d98c03e6a354cc"}, - {file = "brotlicffi-1.1.0.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:9b6068e0f3769992d6b622a1cd2e7835eae3cf8d9da123d7f51ca9c1e9c333e5"}, - {file = "brotlicffi-1.1.0.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8557a8559509b61e65083f8782329188a250102372576093c88930c875a69838"}, - {file = "brotlicffi-1.1.0.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2a7ae37e5d79c5bdfb5b4b99f2715a6035e6c5bf538c3746abc8e26694f92f33"}, - {file = "brotlicffi-1.1.0.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:391151ec86bb1c683835980f4816272a87eaddc46bb91cbf44f62228b84d8cca"}, - {file = "brotlicffi-1.1.0.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:2f3711be9290f0453de8eed5275d93d286abe26b08ab4a35d7452caa1fef532f"}, - {file = "brotlicffi-1.1.0.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:1a807d760763e398bbf2c6394ae9da5815901aa93ee0a37bca5efe78d4ee3171"}, - {file = "brotlicffi-1.1.0.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fa8ca0623b26c94fccc3a1fdd895be1743b838f3917300506d04aa3346fd2a14"}, - {file = "brotlicffi-1.1.0.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3de0cf28a53a3238b252aca9fed1593e9d36c1d116748013339f0949bfc84112"}, - {file = "brotlicffi-1.1.0.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6be5ec0e88a4925c91f3dea2bb0013b3a2accda6f77238f76a34a1ea532a1cb0"}, - {file = "brotlicffi-1.1.0.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:d9eb71bb1085d996244439154387266fd23d6ad37161f6f52f1cd41dd95a3808"}, - {file = "brotlicffi-1.1.0.0.tar.gz", hash = "sha256:b77827a689905143f87915310b93b273ab17888fd43ef350d4832c4a71083c13"}, -] - -[package.dependencies] -cffi = ">=1.0.0" - -[[package]] -name = "bson" -version = "0.5.10" -description = "BSON codec for Python" -optional = false -python-versions = "*" -files = [ - {file = "bson-0.5.10.tar.gz", hash = "sha256:d6511b2ab051139a9123c184de1a04227262173ad593429d21e443d6462d6590"}, -] - -[package.dependencies] -python-dateutil = ">=2.4.0" -six = ">=1.9.0" - -[[package]] -name = "certifi" -version = "2024.2.2" -description = "Python package for providing Mozilla's CA Bundle." -optional = false -python-versions = ">=3.6" -files = [ - {file = "certifi-2024.2.2-py3-none-any.whl", hash = "sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1"}, - {file = "certifi-2024.2.2.tar.gz", hash = "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f"}, -] - -[[package]] -name = "cffi" -version = "1.16.0" -description = "Foreign Function Interface for Python calling C code." -optional = false -python-versions = ">=3.8" -files = [ - {file = "cffi-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6b3d6606d369fc1da4fd8c357d026317fbb9c9b75d36dc16e90e84c26854b088"}, - {file = "cffi-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ac0f5edd2360eea2f1daa9e26a41db02dd4b0451b48f7c318e217ee092a213e9"}, - {file = "cffi-1.16.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e61e3e4fa664a8588aa25c883eab612a188c725755afff6289454d6362b9673"}, - {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a72e8961a86d19bdb45851d8f1f08b041ea37d2bd8d4fd19903bc3083d80c896"}, - {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b50bf3f55561dac5438f8e70bfcdfd74543fd60df5fa5f62d94e5867deca684"}, - {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7651c50c8c5ef7bdb41108b7b8c5a83013bfaa8a935590c5d74627c047a583c7"}, - {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4108df7fe9b707191e55f33efbcb2d81928e10cea45527879a4749cbe472614"}, - {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:32c68ef735dbe5857c810328cb2481e24722a59a2003018885514d4c09af9743"}, - {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:673739cb539f8cdaa07d92d02efa93c9ccf87e345b9a0b556e3ecc666718468d"}, - {file = "cffi-1.16.0-cp310-cp310-win32.whl", hash = "sha256:9f90389693731ff1f659e55c7d1640e2ec43ff725cc61b04b2f9c6d8d017df6a"}, - {file = "cffi-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:e6024675e67af929088fda399b2094574609396b1decb609c55fa58b028a32a1"}, - {file = "cffi-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b84834d0cf97e7d27dd5b7f3aca7b6e9263c56308ab9dc8aae9784abb774d404"}, - {file = "cffi-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b8ebc27c014c59692bb2664c7d13ce7a6e9a629be20e54e7271fa696ff2b417"}, - {file = "cffi-1.16.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee07e47c12890ef248766a6e55bd38ebfb2bb8edd4142d56db91b21ea68b7627"}, - {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8a9d3ebe49f084ad71f9269834ceccbf398253c9fac910c4fd7053ff1386936"}, - {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e70f54f1796669ef691ca07d046cd81a29cb4deb1e5f942003f401c0c4a2695d"}, - {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5bf44d66cdf9e893637896c7faa22298baebcd18d1ddb6d2626a6e39793a1d56"}, - {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b78010e7b97fef4bee1e896df8a4bbb6712b7f05b7ef630f9d1da00f6444d2e"}, - {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c6a164aa47843fb1b01e941d385aab7215563bb8816d80ff3a363a9f8448a8dc"}, - {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e09f3ff613345df5e8c3667da1d918f9149bd623cd9070c983c013792a9a62eb"}, - {file = "cffi-1.16.0-cp311-cp311-win32.whl", hash = "sha256:2c56b361916f390cd758a57f2e16233eb4f64bcbeee88a4881ea90fca14dc6ab"}, - {file = "cffi-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:db8e577c19c0fda0beb7e0d4e09e0ba74b1e4c092e0e40bfa12fe05b6f6d75ba"}, - {file = "cffi-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956"}, - {file = "cffi-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:68e7c44931cc171c54ccb702482e9fc723192e88d25a0e133edd7aff8fcd1f6e"}, - {file = "cffi-1.16.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abd808f9c129ba2beda4cfc53bde801e5bcf9d6e0f22f095e45327c038bfe68e"}, - {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88e2b3c14bdb32e440be531ade29d3c50a1a59cd4e51b1dd8b0865c54ea5d2e2"}, - {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357"}, - {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7be2d771cdba2942e13215c4e340bfd76398e9227ad10402a8767ab1865d2e6"}, - {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e715596e683d2ce000574bae5d07bd522c781a822866c20495e52520564f0969"}, - {file = "cffi-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2d92b25dbf6cae33f65005baf472d2c245c050b1ce709cc4588cdcdd5495b520"}, - {file = "cffi-1.16.0-cp312-cp312-win32.whl", hash = "sha256:b2ca4e77f9f47c55c194982e10f058db063937845bb2b7a86c84a6cfe0aefa8b"}, - {file = "cffi-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:68678abf380b42ce21a5f2abde8efee05c114c2fdb2e9eef2efdb0257fba1235"}, - {file = "cffi-1.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0c9ef6ff37e974b73c25eecc13952c55bceed9112be2d9d938ded8e856138bcc"}, - {file = "cffi-1.16.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a09582f178759ee8128d9270cd1344154fd473bb77d94ce0aeb2a93ebf0feaf0"}, - {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e760191dd42581e023a68b758769e2da259b5d52e3103c6060ddc02c9edb8d7b"}, - {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80876338e19c951fdfed6198e70bc88f1c9758b94578d5a7c4c91a87af3cf31c"}, - {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6a14b17d7e17fa0d207ac08642c8820f84f25ce17a442fd15e27ea18d67c59b"}, - {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6602bc8dc6f3a9e02b6c22c4fc1e47aa50f8f8e6d3f78a5e16ac33ef5fefa324"}, - {file = "cffi-1.16.0-cp38-cp38-win32.whl", hash = "sha256:131fd094d1065b19540c3d72594260f118b231090295d8c34e19a7bbcf2e860a"}, - {file = "cffi-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:31d13b0f99e0836b7ff893d37af07366ebc90b678b6664c955b54561fc36ef36"}, - {file = "cffi-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:582215a0e9adbe0e379761260553ba11c58943e4bbe9c36430c4ca6ac74b15ed"}, - {file = "cffi-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b29ebffcf550f9da55bec9e02ad430c992a87e5f512cd63388abb76f1036d8d2"}, - {file = "cffi-1.16.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dc9b18bf40cc75f66f40a7379f6a9513244fe33c0e8aa72e2d56b0196a7ef872"}, - {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cb4a35b3642fc5c005a6755a5d17c6c8b6bcb6981baf81cea8bfbc8903e8ba8"}, - {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b86851a328eedc692acf81fb05444bdf1891747c25af7529e39ddafaf68a4f3f"}, - {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c0f31130ebc2d37cdd8e44605fb5fa7ad59049298b3f745c74fa74c62fbfcfc4"}, - {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f8e709127c6c77446a8c0a8c8bf3c8ee706a06cd44b1e827c3e6a2ee6b8c098"}, - {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:748dcd1e3d3d7cd5443ef03ce8685043294ad6bd7c02a38d1bd367cfd968e000"}, - {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8895613bcc094d4a1b2dbe179d88d7fb4a15cee43c052e8885783fac397d91fe"}, - {file = "cffi-1.16.0-cp39-cp39-win32.whl", hash = "sha256:ed86a35631f7bfbb28e108dd96773b9d5a6ce4811cf6ea468bb6a359b256b1e4"}, - {file = "cffi-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:3686dffb02459559c74dd3d81748269ffb0eb027c39a6fc99502de37d501faa8"}, - {file = "cffi-1.16.0.tar.gz", hash = "sha256:bcb3ef43e58665bbda2fb198698fcae6776483e0c4a631aa5647806c25e02cc0"}, -] - -[package.dependencies] -pycparser = "*" - -[[package]] -name = "cryptography" -version = "41.0.7" -description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." -optional = false -python-versions = ">=3.7" -files = [ - {file = "cryptography-41.0.7-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:3c78451b78313fa81607fa1b3f1ae0a5ddd8014c38a02d9db0616133987b9cdf"}, - {file = "cryptography-41.0.7-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:928258ba5d6f8ae644e764d0f996d61a8777559f72dfeb2eea7e2fe0ad6e782d"}, - {file = "cryptography-41.0.7-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5a1b41bc97f1ad230a41657d9155113c7521953869ae57ac39ac7f1bb471469a"}, - {file = "cryptography-41.0.7-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:841df4caa01008bad253bce2a6f7b47f86dc9f08df4b433c404def869f590a15"}, - {file = "cryptography-41.0.7-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:5429ec739a29df2e29e15d082f1d9ad683701f0ec7709ca479b3ff2708dae65a"}, - {file = "cryptography-41.0.7-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:43f2552a2378b44869fe8827aa19e69512e3245a219104438692385b0ee119d1"}, - {file = "cryptography-41.0.7-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:af03b32695b24d85a75d40e1ba39ffe7db7ffcb099fe507b39fd41a565f1b157"}, - {file = "cryptography-41.0.7-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:49f0805fc0b2ac8d4882dd52f4a3b935b210935d500b6b805f321addc8177406"}, - {file = "cryptography-41.0.7-cp37-abi3-win32.whl", hash = "sha256:f983596065a18a2183e7f79ab3fd4c475205b839e02cbc0efbbf9666c4b3083d"}, - {file = "cryptography-41.0.7-cp37-abi3-win_amd64.whl", hash = "sha256:90452ba79b8788fa380dfb587cca692976ef4e757b194b093d845e8d99f612f2"}, - {file = "cryptography-41.0.7-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:079b85658ea2f59c4f43b70f8119a52414cdb7be34da5d019a77bf96d473b960"}, - {file = "cryptography-41.0.7-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:b640981bf64a3e978a56167594a0e97db71c89a479da8e175d8bb5be5178c003"}, - {file = "cryptography-41.0.7-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e3114da6d7f95d2dee7d3f4eec16dacff819740bbab931aff8648cb13c5ff5e7"}, - {file = "cryptography-41.0.7-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d5ec85080cce7b0513cfd233914eb8b7bbd0633f1d1703aa28d1dd5a72f678ec"}, - {file = "cryptography-41.0.7-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:7a698cb1dac82c35fcf8fe3417a3aaba97de16a01ac914b89a0889d364d2f6be"}, - {file = "cryptography-41.0.7-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:37a138589b12069efb424220bf78eac59ca68b95696fc622b6ccc1c0a197204a"}, - {file = "cryptography-41.0.7-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:68a2dec79deebc5d26d617bfdf6e8aab065a4f34934b22d3b5010df3ba36612c"}, - {file = "cryptography-41.0.7-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:09616eeaef406f99046553b8a40fbf8b1e70795a91885ba4c96a70793de5504a"}, - {file = "cryptography-41.0.7-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:48a0476626da912a44cc078f9893f292f0b3e4c739caf289268168d8f4702a39"}, - {file = "cryptography-41.0.7-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c7f3201ec47d5207841402594f1d7950879ef890c0c495052fa62f58283fde1a"}, - {file = "cryptography-41.0.7-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c5ca78485a255e03c32b513f8c2bc39fedb7f5c5f8535545bdc223a03b24f248"}, - {file = "cryptography-41.0.7-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:d6c391c021ab1f7a82da5d8d0b3cee2f4b2c455ec86c8aebbc84837a631ff309"}, - {file = "cryptography-41.0.7.tar.gz", hash = "sha256:13f93ce9bea8016c253b34afc6bd6a75993e5c40672ed5405a9c832f0d4a00bc"}, -] - -[package.dependencies] -cffi = ">=1.12" - -[package.extras] -docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.1.1)"] -docstest = ["pyenchant (>=1.6.11)", "sphinxcontrib-spelling (>=4.0.1)", "twine (>=1.12.0)"] -nox = ["nox"] -pep8test = ["black", "check-sdist", "mypy", "ruff"] -sdist = ["build"] -ssh = ["bcrypt (>=3.1.5)"] -test = ["pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] -test-randomorder = ["pytest-randomly"] - -[[package]] -name = "cytoolz" -version = "0.12.3" -description = "Cython implementation of Toolz: High performance functional utilities" -optional = false -python-versions = ">=3.7" -files = [ - {file = "cytoolz-0.12.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bbe58e26c84b163beba0fbeacf6b065feabc8f75c6d3fe305550d33f24a2d346"}, - {file = "cytoolz-0.12.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c51b66ada9bfdb88cf711bf350fcc46f82b83a4683cf2413e633c31a64df6201"}, - {file = "cytoolz-0.12.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e70d9c615e5c9dc10d279d1e32e846085fe1fd6f08d623ddd059a92861f4e3dd"}, - {file = "cytoolz-0.12.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a83f4532707963ae1a5108e51fdfe1278cc8724e3301fee48b9e73e1316de64f"}, - {file = "cytoolz-0.12.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d028044524ee2e815f36210a793c414551b689d4f4eda28f8bbb0883ad78bf5f"}, - {file = "cytoolz-0.12.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c2875bcd1397d0627a09a4f9172fa513185ad302c63758efc15b8eb33cc2a98"}, - {file = "cytoolz-0.12.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:131ff4820e5d64a25d7ad3c3556f2d8aa65c66b3f021b03f8a8e98e4180dd808"}, - {file = "cytoolz-0.12.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:04afa90d9d9d18394c40d9bed48c51433d08b57c042e0e50c8c0f9799735dcbd"}, - {file = "cytoolz-0.12.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:dc1ca9c610425f9854323669a671fc163300b873731584e258975adf50931164"}, - {file = "cytoolz-0.12.3-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:bfa3f8e01bc423a933f2e1c510cbb0632c6787865b5242857cc955cae220d1bf"}, - {file = "cytoolz-0.12.3-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:f702e295dddef5f8af4a456db93f114539b8dc2a7a9bc4de7c7e41d169aa6ec3"}, - {file = "cytoolz-0.12.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0fbad1fb9bb47e827d00e01992a099b0ba79facf5e5aa453be066033232ac4b5"}, - {file = "cytoolz-0.12.3-cp310-cp310-win32.whl", hash = "sha256:8587c3c3dbe78af90c5025288766ac10dc2240c1e76eb0a93a4e244c265ccefd"}, - {file = "cytoolz-0.12.3-cp310-cp310-win_amd64.whl", hash = "sha256:9e45803d9e75ef90a2f859ef8f7f77614730f4a8ce1b9244375734567299d239"}, - {file = "cytoolz-0.12.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3ac4f2fb38bbc67ff1875b7d2f0f162a247f43bd28eb7c9d15e6175a982e558d"}, - {file = "cytoolz-0.12.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0cf1e1e96dd86829a0539baf514a9c8473a58fbb415f92401a68e8e52a34ecd5"}, - {file = "cytoolz-0.12.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08a438701c6141dd34eaf92e9e9a1f66e23a22f7840ef8a371eba274477de85d"}, - {file = "cytoolz-0.12.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c6b6f11b0d7ed91be53166aeef2a23a799e636625675bb30818f47f41ad31821"}, - {file = "cytoolz-0.12.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a7fde09384d23048a7b4ac889063761e44b89a0b64015393e2d1d21d5c1f534a"}, - {file = "cytoolz-0.12.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d3bfe45173cc8e6c76206be3a916d8bfd2214fb2965563e288088012f1dabfc"}, - {file = "cytoolz-0.12.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:27513a5d5b6624372d63313574381d3217a66e7a2626b056c695179623a5cb1a"}, - {file = "cytoolz-0.12.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d294e5e81ff094fe920fd545052ff30838ea49f9e91227a55ecd9f3ca19774a0"}, - {file = "cytoolz-0.12.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:727b01a2004ddb513496507a695e19b5c0cfebcdfcc68349d3efd92a1c297bf4"}, - {file = "cytoolz-0.12.3-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:fe1e1779a39dbe83f13886d2b4b02f8c4b10755e3c8d9a89b630395f49f4f406"}, - {file = "cytoolz-0.12.3-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:de74ef266e2679c3bf8b5fc20cee4fc0271ba13ae0d9097b1491c7a9bcadb389"}, - {file = "cytoolz-0.12.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9e04d22049233394e0b08193aca9737200b4a2afa28659d957327aa780ddddf2"}, - {file = "cytoolz-0.12.3-cp311-cp311-win32.whl", hash = "sha256:20d36430d8ac809186736fda735ee7d595b6242bdb35f69b598ef809ebfa5605"}, - {file = "cytoolz-0.12.3-cp311-cp311-win_amd64.whl", hash = "sha256:780c06110f383344d537f48d9010d79fa4f75070d214fc47f389357dd4f010b6"}, - {file = "cytoolz-0.12.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:86923d823bd19ce35805953b018d436f6b862edd6a7c8b747a13d52b39ed5716"}, - {file = "cytoolz-0.12.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a3e61acfd029bfb81c2c596249b508dfd2b4f72e31b7b53b62e5fb0507dd7293"}, - {file = "cytoolz-0.12.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd728f4e6051af6af234651df49319da1d813f47894d4c3c8ab7455e01703a37"}, - {file = "cytoolz-0.12.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fe8c6267caa7ec67bcc37e360f0d8a26bc3bdce510b15b97f2f2e0143bdd3673"}, - {file = "cytoolz-0.12.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:99462abd8323c52204a2a0ce62454ce8fa0f4e94b9af397945c12830de73f27e"}, - {file = "cytoolz-0.12.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da125221b1fa25c690fcd030a54344cecec80074df018d906fc6a99f46c1e3a6"}, - {file = "cytoolz-0.12.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1c18e351956f70db9e2d04ff02f28e9a41839250d3f936a4c8a1eabd1c3094d2"}, - {file = "cytoolz-0.12.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:921e6d2440ac758c4945c587b1d1d9b781b72737ac0c0ca5d5e02ca1db8bded2"}, - {file = "cytoolz-0.12.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:1651a9bd591a8326329ce1d6336f3129161a36d7061a4d5ea9e5377e033364cf"}, - {file = "cytoolz-0.12.3-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:8893223b87c2782bd59f9c4bd5c7bf733edd8728b523c93efb91d7468b486528"}, - {file = "cytoolz-0.12.3-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:e4d2961644153c5ae186db964aa9f6109da81b12df0f1d3494b4e5cf2c332ee2"}, - {file = "cytoolz-0.12.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:71b6eb97f6695f7ba8ce69c49b707a351c5f46fd97f5aeb5f6f2fb0d6e72b887"}, - {file = "cytoolz-0.12.3-cp312-cp312-win32.whl", hash = "sha256:cee3de65584e915053412cd178729ff510ad5f8f585c21c5890e91028283518f"}, - {file = "cytoolz-0.12.3-cp312-cp312-win_amd64.whl", hash = "sha256:9eef0d23035fa4dcfa21e570961e86c375153a7ee605cdd11a8b088c24f707f6"}, - {file = "cytoolz-0.12.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d9a38332cfad2a91e89405b7c18b3f00e2edc951c225accbc217597d3e4e9fde"}, - {file = "cytoolz-0.12.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f501ae1353071fa5d6677437bbeb1aeb5622067dce0977cedc2c5ec5843b202"}, - {file = "cytoolz-0.12.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:56f899758146a52e2f8cfb3fb6f4ca19c1e5814178c3d584de35f9e4d7166d91"}, - {file = "cytoolz-0.12.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:800f0526adf9e53d3c6acda748f4def1f048adaa780752f154da5cf22aa488a2"}, - {file = "cytoolz-0.12.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d0976a3fcb81d065473173e9005848218ce03ddb2ec7d40dd6a8d2dba7f1c3ae"}, - {file = "cytoolz-0.12.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c835eab01466cb67d0ce6290601ebef2d82d8d0d0a285ed0d6e46989e4a7a71a"}, - {file = "cytoolz-0.12.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:4fba0616fcd487e34b8beec1ad9911d192c62e758baa12fcb44448b9b6feae22"}, - {file = "cytoolz-0.12.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:6f6e8207d732651e0204779e1ba5a4925c93081834570411f959b80681f8d333"}, - {file = "cytoolz-0.12.3-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:8119bf5961091cfe644784d0bae214e273b3b3a479f93ee3baab97bbd995ccfe"}, - {file = "cytoolz-0.12.3-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:7ad1331cb68afeec58469c31d944a2100cee14eac221553f0d5218ace1a0b25d"}, - {file = "cytoolz-0.12.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:92c53d508fb8a4463acc85b322fa24734efdc66933a5c8661bdc862103a3373d"}, - {file = "cytoolz-0.12.3-cp37-cp37m-win32.whl", hash = "sha256:2c6dd75dae3d84fa8988861ab8b1189d2488cb8a9b8653828f9cd6126b5e7abd"}, - {file = "cytoolz-0.12.3-cp37-cp37m-win_amd64.whl", hash = "sha256:caf07a97b5220e6334dd32c8b6d8b2bd255ca694eca5dfe914bb5b880ee66cdb"}, - {file = "cytoolz-0.12.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ed0cfb9326747759e2ad81cb6e45f20086a273b67ac3a4c00b19efcbab007c60"}, - {file = "cytoolz-0.12.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:96a5a0292575c3697121f97cc605baf2fd125120c7dcdf39edd1a135798482ca"}, - {file = "cytoolz-0.12.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b76f2f50a789c44d6fd7f773ec43d2a8686781cd52236da03f7f7d7998989bee"}, - {file = "cytoolz-0.12.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2905fdccacc64b4beba37f95cab9d792289c80f4d70830b70de2fc66c007ec01"}, - {file = "cytoolz-0.12.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f1ebe23028eac51251f22ba01dba6587d30aa9c320372ca0c14eeab67118ec3f"}, - {file = "cytoolz-0.12.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96c715404a3825e37fe3966fe84c5f8a1f036e7640b2a02dbed96cac0c933451"}, - {file = "cytoolz-0.12.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bac0adffc1b6b6a4c5f1fd1dd2161afb720bcc771a91016dc6bdba59af0a5d3"}, - {file = "cytoolz-0.12.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:37441bf4a2a4e2e0fe9c3b0ea5e72db352f5cca03903977ffc42f6f6c5467be9"}, - {file = "cytoolz-0.12.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:f04037302049cb30033f7fa4e1d0e44afe35ed6bfcf9b380fc11f2a27d3ed697"}, - {file = "cytoolz-0.12.3-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:f37b60e66378e7a116931d7220f5352186abfcc950d64856038aa2c01944929c"}, - {file = "cytoolz-0.12.3-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:ec9be3e4b6f86ea8b294d34c990c99d2ba6c526ef1e8f46f1d52c263d4f32cd7"}, - {file = "cytoolz-0.12.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:0e9199c9e3fbf380a92b8042c677eb9e7ed4bccb126de5e9c0d26f5888d96788"}, - {file = "cytoolz-0.12.3-cp38-cp38-win32.whl", hash = "sha256:18cd61e078bd6bffe088e40f1ed02001387c29174750abce79499d26fa57f5eb"}, - {file = "cytoolz-0.12.3-cp38-cp38-win_amd64.whl", hash = "sha256:765b8381d4003ceb1a07896a854eee2c31ebc950a4ae17d1e7a17c2a8feb2a68"}, - {file = "cytoolz-0.12.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b4a52dd2a36b0a91f7aa50ca6c8509057acc481a24255f6cb07b15d339a34e0f"}, - {file = "cytoolz-0.12.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:581f1ce479769fe7eeb9ae6d87eadb230df8c7c5fff32138162cdd99d7fb8fc3"}, - {file = "cytoolz-0.12.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:46f505d4c6eb79585c8ad0b9dc140ef30a138c880e4e3b40230d642690e36366"}, - {file = "cytoolz-0.12.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59276021619b432a5c21c01cda8320b9cc7dbc40351ffc478b440bfccd5bbdd3"}, - {file = "cytoolz-0.12.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e44f4c25e1e7cf6149b499c74945a14649c8866d36371a2c2d2164e4649e7755"}, - {file = "cytoolz-0.12.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c64f8e60c1dd69e4d5e615481f2d57937746f4a6be2d0f86e9e7e3b9e2243b5e"}, - {file = "cytoolz-0.12.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:33c63186f3bf9d7ef1347bc0537bb9a0b4111a0d7d6e619623cabc18fef0dc3b"}, - {file = "cytoolz-0.12.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:fdddb9d988405f24035234f1e8d1653ab2e48cc2404226d21b49a129aefd1d25"}, - {file = "cytoolz-0.12.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6986632d8a969ea1e720990c818dace1a24c11015fd7c59b9fea0b65ef71f726"}, - {file = "cytoolz-0.12.3-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:0ba1cbc4d9cd7571c917f88f4a069568e5121646eb5d82b2393b2cf84712cf2a"}, - {file = "cytoolz-0.12.3-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:7d267ffc9a36c0a9a58c7e0adc9fa82620f22e4a72533e15dd1361f57fc9accf"}, - {file = "cytoolz-0.12.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:95e878868a172a41fbf6c505a4b967309e6870e22adc7b1c3b19653d062711fa"}, - {file = "cytoolz-0.12.3-cp39-cp39-win32.whl", hash = "sha256:8e21932d6d260996f7109f2a40b2586070cb0a0cf1d65781e156326d5ebcc329"}, - {file = "cytoolz-0.12.3-cp39-cp39-win_amd64.whl", hash = "sha256:0d8edfbc694af6c9bda4db56643fb8ed3d14e47bec358c2f1417de9a12d6d1fb"}, - {file = "cytoolz-0.12.3-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:55f9bd1ae6c2a27eda5abe2a0b65a83029d2385c5a1da7b8ef47af5905d7e905"}, - {file = "cytoolz-0.12.3-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d2d271393c378282727f1231d40391ae93b93ddc0997448acc21dd0cb6a1e56d"}, - {file = "cytoolz-0.12.3-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee98968d6a66ee83a8ceabf31182189ab5d8598998c8ce69b6d5843daeb2db60"}, - {file = "cytoolz-0.12.3-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:01cfb8518828c1189200c02a5010ea404407fb18fd5589e29c126e84bbeadd36"}, - {file = "cytoolz-0.12.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:456395d7aec01db32bf9e6db191d667347c78d8d48e77234521fa1078f60dabb"}, - {file = "cytoolz-0.12.3-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:cd88028bb897fba99ddd84f253ca6bef73ecb7bdf3f3cf25bc493f8f97d3c7c5"}, - {file = "cytoolz-0.12.3-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59b19223e7f7bd7a73ec3aa6fdfb73b579ff09c2bc0b7d26857eec2d01a58c76"}, - {file = "cytoolz-0.12.3-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a79d72b08048a0980a59457c239555f111ac0c8bdc140c91a025f124104dbb4"}, - {file = "cytoolz-0.12.3-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1dd70141b32b717696a72b8876e86bc9c6f8eff995c1808e299db3541213ff82"}, - {file = "cytoolz-0.12.3-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:a1445c91009eb775d479e88954c51d0b4cf9a1e8ce3c503c2672d17252882647"}, - {file = "cytoolz-0.12.3-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ca6a9a9300d5bda417d9090107c6d2b007683efc59d63cc09aca0e7930a08a85"}, - {file = "cytoolz-0.12.3-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be6feb903d2a08a4ba2e70e950e862fd3be9be9a588b7c38cee4728150a52918"}, - {file = "cytoolz-0.12.3-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:92b6f43f086e5a965d33d62a145ae121b4ccb6e0789ac0acc895ce084fec8c65"}, - {file = "cytoolz-0.12.3-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:534fa66db8564d9b13872d81d54b6b09ae592c585eb826aac235bd6f1830f8ad"}, - {file = "cytoolz-0.12.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:fea649f979def23150680de1bd1d09682da3b54932800a0f90f29fc2a6c98ba8"}, - {file = "cytoolz-0.12.3-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a447247ed312dd64e3a8d9483841ecc5338ee26d6e6fbd29cd373ed030db0240"}, - {file = "cytoolz-0.12.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba3f843aa89f35467b38c398ae5b980a824fdbdb94065adc6ec7c47a0a22f4c7"}, - {file = "cytoolz-0.12.3-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:582c22f97a380211fb36a7b65b1beeb84ea11d82015fa84b054be78580390082"}, - {file = "cytoolz-0.12.3-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47feb089506fc66e1593cd9ade3945693a9d089a445fbe9a11385cab200b9f22"}, - {file = "cytoolz-0.12.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:ba9002d2f043943744a9dc8e50a47362bcb6e6f360dc0a1abcb19642584d87bb"}, - {file = "cytoolz-0.12.3.tar.gz", hash = "sha256:4503dc59f4ced53a54643272c61dc305d1dbbfbd7d6bdf296948de9f34c3a282"}, -] - -[package.dependencies] -toolz = ">=0.8.0" - -[package.extras] -cython = ["cython"] - -[[package]] -name = "deprecated" -version = "1.2.14" -description = "Python @deprecated decorator to deprecate old python classes, functions or methods." -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -files = [ - {file = "Deprecated-1.2.14-py2.py3-none-any.whl", hash = "sha256:6fac8b097794a90302bdbb17b9b815e732d3c4720583ff1b198499d78470466c"}, - {file = "Deprecated-1.2.14.tar.gz", hash = "sha256:e5323eb936458dccc2582dc6f9c322c852a775a27065ff2b0c4970b9d53d01b3"}, -] - -[package.dependencies] -wrapt = ">=1.10,<2" - -[package.extras] -dev = ["PyTest", "PyTest-Cov", "bump2version (<1)", "sphinx (<2)", "tox"] - -[[package]] -name = "fastpbkdf2" -version = "0.2" -description = "A faster implementation of PBKDF2" -optional = false -python-versions = "*" -files = [ - {file = "fastpbkdf2-0.2.tar.gz", hash = "sha256:88fe92e9c3b6972421ba744ff4792334e4855b2d37534417f8c98c7cdba5ea9a"}, -] - -[package.dependencies] -cffi = ">=1.1" -six = ">=1.4.1" - -[[package]] -name = "frozenlist" -version = "1.4.1" -description = "A list-like structure which implements collections.abc.MutableSequence" -optional = false -python-versions = ">=3.8" -files = [ - {file = "frozenlist-1.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f9aa1878d1083b276b0196f2dfbe00c9b7e752475ed3b682025ff20c1c1f51ac"}, - {file = "frozenlist-1.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:29acab3f66f0f24674b7dc4736477bcd4bc3ad4b896f5f45379a67bce8b96868"}, - {file = "frozenlist-1.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:74fb4bee6880b529a0c6560885fce4dc95936920f9f20f53d99a213f7bf66776"}, - {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:590344787a90ae57d62511dd7c736ed56b428f04cd8c161fcc5e7232c130c69a"}, - {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:068b63f23b17df8569b7fdca5517edef76171cf3897eb68beb01341131fbd2ad"}, - {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5c849d495bf5154cd8da18a9eb15db127d4dba2968d88831aff6f0331ea9bd4c"}, - {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9750cc7fe1ae3b1611bb8cfc3f9ec11d532244235d75901fb6b8e42ce9229dfe"}, - {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9b2de4cf0cdd5bd2dee4c4f63a653c61d2408055ab77b151c1957f221cabf2a"}, - {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0633c8d5337cb5c77acbccc6357ac49a1770b8c487e5b3505c57b949b4b82e98"}, - {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:27657df69e8801be6c3638054e202a135c7f299267f1a55ed3a598934f6c0d75"}, - {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:f9a3ea26252bd92f570600098783d1371354d89d5f6b7dfd87359d669f2109b5"}, - {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:4f57dab5fe3407b6c0c1cc907ac98e8a189f9e418f3b6e54d65a718aaafe3950"}, - {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e02a0e11cf6597299b9f3bbd3f93d79217cb90cfd1411aec33848b13f5c656cc"}, - {file = "frozenlist-1.4.1-cp310-cp310-win32.whl", hash = "sha256:a828c57f00f729620a442881cc60e57cfcec6842ba38e1b19fd3e47ac0ff8dc1"}, - {file = "frozenlist-1.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:f56e2333dda1fe0f909e7cc59f021eba0d2307bc6f012a1ccf2beca6ba362439"}, - {file = "frozenlist-1.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a0cb6f11204443f27a1628b0e460f37fb30f624be6051d490fa7d7e26d4af3d0"}, - {file = "frozenlist-1.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b46c8ae3a8f1f41a0d2ef350c0b6e65822d80772fe46b653ab6b6274f61d4a49"}, - {file = "frozenlist-1.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fde5bd59ab5357e3853313127f4d3565fc7dad314a74d7b5d43c22c6a5ed2ced"}, - {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:722e1124aec435320ae01ee3ac7bec11a5d47f25d0ed6328f2273d287bc3abb0"}, - {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2471c201b70d58a0f0c1f91261542a03d9a5e088ed3dc6c160d614c01649c106"}, - {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c757a9dd70d72b076d6f68efdbb9bc943665ae954dad2801b874c8c69e185068"}, - {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f146e0911cb2f1da549fc58fc7bcd2b836a44b79ef871980d605ec392ff6b0d2"}, - {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f9c515e7914626b2a2e1e311794b4c35720a0be87af52b79ff8e1429fc25f19"}, - {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c302220494f5c1ebeb0912ea782bcd5e2f8308037b3c7553fad0e48ebad6ad82"}, - {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:442acde1e068288a4ba7acfe05f5f343e19fac87bfc96d89eb886b0363e977ec"}, - {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:1b280e6507ea8a4fa0c0a7150b4e526a8d113989e28eaaef946cc77ffd7efc0a"}, - {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:fe1a06da377e3a1062ae5fe0926e12b84eceb8a50b350ddca72dc85015873f74"}, - {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:db9e724bebd621d9beca794f2a4ff1d26eed5965b004a97f1f1685a173b869c2"}, - {file = "frozenlist-1.4.1-cp311-cp311-win32.whl", hash = "sha256:e774d53b1a477a67838a904131c4b0eef6b3d8a651f8b138b04f748fccfefe17"}, - {file = "frozenlist-1.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:fb3c2db03683b5767dedb5769b8a40ebb47d6f7f45b1b3e3b4b51ec8ad9d9825"}, - {file = "frozenlist-1.4.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:1979bc0aeb89b33b588c51c54ab0161791149f2461ea7c7c946d95d5f93b56ae"}, - {file = "frozenlist-1.4.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:cc7b01b3754ea68a62bd77ce6020afaffb44a590c2289089289363472d13aedb"}, - {file = "frozenlist-1.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c9c92be9fd329ac801cc420e08452b70e7aeab94ea4233a4804f0915c14eba9b"}, - {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c3894db91f5a489fc8fa6a9991820f368f0b3cbdb9cd8849547ccfab3392d86"}, - {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ba60bb19387e13597fb059f32cd4d59445d7b18b69a745b8f8e5db0346f33480"}, - {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8aefbba5f69d42246543407ed2461db31006b0f76c4e32dfd6f42215a2c41d09"}, - {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:780d3a35680ced9ce682fbcf4cb9c2bad3136eeff760ab33707b71db84664e3a"}, - {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9acbb16f06fe7f52f441bb6f413ebae6c37baa6ef9edd49cdd567216da8600cd"}, - {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:23b701e65c7b36e4bf15546a89279bd4d8675faabc287d06bbcfac7d3c33e1e6"}, - {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:3e0153a805a98f5ada7e09826255ba99fb4f7524bb81bf6b47fb702666484ae1"}, - {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:dd9b1baec094d91bf36ec729445f7769d0d0cf6b64d04d86e45baf89e2b9059b"}, - {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:1a4471094e146b6790f61b98616ab8e44f72661879cc63fa1049d13ef711e71e"}, - {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5667ed53d68d91920defdf4035d1cdaa3c3121dc0b113255124bcfada1cfa1b8"}, - {file = "frozenlist-1.4.1-cp312-cp312-win32.whl", hash = "sha256:beee944ae828747fd7cb216a70f120767fc9f4f00bacae8543c14a6831673f89"}, - {file = "frozenlist-1.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:64536573d0a2cb6e625cf309984e2d873979709f2cf22839bf2d61790b448ad5"}, - {file = "frozenlist-1.4.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:20b51fa3f588ff2fe658663db52a41a4f7aa6c04f6201449c6c7c476bd255c0d"}, - {file = "frozenlist-1.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:410478a0c562d1a5bcc2f7ea448359fcb050ed48b3c6f6f4f18c313a9bdb1826"}, - {file = "frozenlist-1.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c6321c9efe29975232da3bd0af0ad216800a47e93d763ce64f291917a381b8eb"}, - {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48f6a4533887e189dae092f1cf981f2e3885175f7a0f33c91fb5b7b682b6bab6"}, - {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6eb73fa5426ea69ee0e012fb59cdc76a15b1283d6e32e4f8dc4482ec67d1194d"}, - {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fbeb989b5cc29e8daf7f976b421c220f1b8c731cbf22b9130d8815418ea45887"}, - {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:32453c1de775c889eb4e22f1197fe3bdfe457d16476ea407472b9442e6295f7a"}, - {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:693945278a31f2086d9bf3df0fe8254bbeaef1fe71e1351c3bd730aa7d31c41b"}, - {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:1d0ce09d36d53bbbe566fe296965b23b961764c0bcf3ce2fa45f463745c04701"}, - {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:3a670dc61eb0d0eb7080890c13de3066790f9049b47b0de04007090807c776b0"}, - {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:dca69045298ce5c11fd539682cff879cc1e664c245d1c64da929813e54241d11"}, - {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:a06339f38e9ed3a64e4c4e43aec7f59084033647f908e4259d279a52d3757d09"}, - {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b7f2f9f912dca3934c1baec2e4585a674ef16fe00218d833856408c48d5beee7"}, - {file = "frozenlist-1.4.1-cp38-cp38-win32.whl", hash = "sha256:e7004be74cbb7d9f34553a5ce5fb08be14fb33bc86f332fb71cbe5216362a497"}, - {file = "frozenlist-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:5a7d70357e7cee13f470c7883a063aae5fe209a493c57d86eb7f5a6f910fae09"}, - {file = "frozenlist-1.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:bfa4a17e17ce9abf47a74ae02f32d014c5e9404b6d9ac7f729e01562bbee601e"}, - {file = "frozenlist-1.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b7e3ed87d4138356775346e6845cccbe66cd9e207f3cd11d2f0b9fd13681359d"}, - {file = "frozenlist-1.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c99169d4ff810155ca50b4da3b075cbde79752443117d89429595c2e8e37fed8"}, - {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:edb678da49d9f72c9f6c609fbe41a5dfb9a9282f9e6a2253d5a91e0fc382d7c0"}, - {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6db4667b187a6742b33afbbaf05a7bc551ffcf1ced0000a571aedbb4aa42fc7b"}, - {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55fdc093b5a3cb41d420884cdaf37a1e74c3c37a31f46e66286d9145d2063bd0"}, - {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82e8211d69a4f4bc360ea22cd6555f8e61a1bd211d1d5d39d3d228b48c83a897"}, - {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89aa2c2eeb20957be2d950b85974b30a01a762f3308cd02bb15e1ad632e22dc7"}, - {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9d3e0c25a2350080e9319724dede4f31f43a6c9779be48021a7f4ebde8b2d742"}, - {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7268252af60904bf52c26173cbadc3a071cece75f873705419c8681f24d3edea"}, - {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:0c250a29735d4f15321007fb02865f0e6b6a41a6b88f1f523ca1596ab5f50bd5"}, - {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:96ec70beabbd3b10e8bfe52616a13561e58fe84c0101dd031dc78f250d5128b9"}, - {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:23b2d7679b73fe0e5a4560b672a39f98dfc6f60df63823b0a9970525325b95f6"}, - {file = "frozenlist-1.4.1-cp39-cp39-win32.whl", hash = "sha256:a7496bfe1da7fb1a4e1cc23bb67c58fab69311cc7d32b5a99c2007b4b2a0e932"}, - {file = "frozenlist-1.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:e6a20a581f9ce92d389a8c7d7c3dd47c81fd5d6e655c8dddf341e14aa48659d0"}, - {file = "frozenlist-1.4.1-py3-none-any.whl", hash = "sha256:04ced3e6a46b4cfffe20f9ae482818e34eba9b5fb0ce4056e4cc9b6e212d09b7"}, - {file = "frozenlist-1.4.1.tar.gz", hash = "sha256:c037a86e8513059a2613aaba4d817bb90b9d9b6b69aace3ce9c877e8c8ed402b"}, -] - -[[package]] -name = "greenlet" -version = "3.0.3" -description = "Lightweight in-process concurrent programming" -optional = false -python-versions = ">=3.7" -files = [ - {file = "greenlet-3.0.3-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:9da2bd29ed9e4f15955dd1595ad7bc9320308a3b766ef7f837e23ad4b4aac31a"}, - {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d353cadd6083fdb056bb46ed07e4340b0869c305c8ca54ef9da3421acbdf6881"}, - {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dca1e2f3ca00b84a396bc1bce13dd21f680f035314d2379c4160c98153b2059b"}, - {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3ed7fb269f15dc662787f4119ec300ad0702fa1b19d2135a37c2c4de6fadfd4a"}, - {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd4f49ae60e10adbc94b45c0b5e6a179acc1736cf7a90160b404076ee283cf83"}, - {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:73a411ef564e0e097dbe7e866bb2dda0f027e072b04da387282b02c308807405"}, - {file = "greenlet-3.0.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:7f362975f2d179f9e26928c5b517524e89dd48530a0202570d55ad6ca5d8a56f"}, - {file = "greenlet-3.0.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:649dde7de1a5eceb258f9cb00bdf50e978c9db1b996964cd80703614c86495eb"}, - {file = "greenlet-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:68834da854554926fbedd38c76e60c4a2e3198c6fbed520b106a8986445caaf9"}, - {file = "greenlet-3.0.3-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:b1b5667cced97081bf57b8fa1d6bfca67814b0afd38208d52538316e9422fc61"}, - {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:52f59dd9c96ad2fc0d5724107444f76eb20aaccb675bf825df6435acb7703559"}, - {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:afaff6cf5200befd5cec055b07d1c0a5a06c040fe5ad148abcd11ba6ab9b114e"}, - {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fe754d231288e1e64323cfad462fcee8f0288654c10bdf4f603a39ed923bef33"}, - {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2797aa5aedac23af156bbb5a6aa2cd3427ada2972c828244eb7d1b9255846379"}, - {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7f009caad047246ed379e1c4dbcb8b020f0a390667ea74d2387be2998f58a22"}, - {file = "greenlet-3.0.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c5e1536de2aad7bf62e27baf79225d0d64360d4168cf2e6becb91baf1ed074f3"}, - {file = "greenlet-3.0.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:894393ce10ceac937e56ec00bb71c4c2f8209ad516e96033e4b3b1de270e200d"}, - {file = "greenlet-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:1ea188d4f49089fc6fb283845ab18a2518d279c7cd9da1065d7a84e991748728"}, - {file = "greenlet-3.0.3-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:70fb482fdf2c707765ab5f0b6655e9cfcf3780d8d87355a063547b41177599be"}, - {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4d1ac74f5c0c0524e4a24335350edad7e5f03b9532da7ea4d3c54d527784f2e"}, - {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:149e94a2dd82d19838fe4b2259f1b6b9957d5ba1b25640d2380bea9c5df37676"}, - {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:15d79dd26056573940fcb8c7413d84118086f2ec1a8acdfa854631084393efcc"}, - {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:881b7db1ebff4ba09aaaeae6aa491daeb226c8150fc20e836ad00041bcb11230"}, - {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fcd2469d6a2cf298f198f0487e0a5b1a47a42ca0fa4dfd1b6862c999f018ebbf"}, - {file = "greenlet-3.0.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1f672519db1796ca0d8753f9e78ec02355e862d0998193038c7073045899f305"}, - {file = "greenlet-3.0.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2516a9957eed41dd8f1ec0c604f1cdc86758b587d964668b5b196a9db5bfcde6"}, - {file = "greenlet-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:bba5387a6975598857d86de9eac14210a49d554a77eb8261cc68b7d082f78ce2"}, - {file = "greenlet-3.0.3-cp37-cp37m-macosx_11_0_universal2.whl", hash = "sha256:5b51e85cb5ceda94e79d019ed36b35386e8c37d22f07d6a751cb659b180d5274"}, - {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:daf3cb43b7cf2ba96d614252ce1684c1bccee6b2183a01328c98d36fcd7d5cb0"}, - {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:99bf650dc5d69546e076f413a87481ee1d2d09aaaaaca058c9251b6d8c14783f"}, - {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2dd6e660effd852586b6a8478a1d244b8dc90ab5b1321751d2ea15deb49ed414"}, - {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e3391d1e16e2a5a1507d83e4a8b100f4ee626e8eca43cf2cadb543de69827c4c"}, - {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e1f145462f1fa6e4a4ae3c0f782e580ce44d57c8f2c7aae1b6fa88c0b2efdb41"}, - {file = "greenlet-3.0.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1a7191e42732df52cb5f39d3527217e7ab73cae2cb3694d241e18f53d84ea9a7"}, - {file = "greenlet-3.0.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:0448abc479fab28b00cb472d278828b3ccca164531daab4e970a0458786055d6"}, - {file = "greenlet-3.0.3-cp37-cp37m-win32.whl", hash = "sha256:b542be2440edc2d48547b5923c408cbe0fc94afb9f18741faa6ae970dbcb9b6d"}, - {file = "greenlet-3.0.3-cp37-cp37m-win_amd64.whl", hash = "sha256:01bc7ea167cf943b4c802068e178bbf70ae2e8c080467070d01bfa02f337ee67"}, - {file = "greenlet-3.0.3-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:1996cb9306c8595335bb157d133daf5cf9f693ef413e7673cb07e3e5871379ca"}, - {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ddc0f794e6ad661e321caa8d2f0a55ce01213c74722587256fb6566049a8b04"}, - {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c9db1c18f0eaad2f804728c67d6c610778456e3e1cc4ab4bbd5eeb8e6053c6fc"}, - {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7170375bcc99f1a2fbd9c306f5be8764eaf3ac6b5cb968862cad4c7057756506"}, - {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b66c9c1e7ccabad3a7d037b2bcb740122a7b17a53734b7d72a344ce39882a1b"}, - {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:098d86f528c855ead3479afe84b49242e174ed262456c342d70fc7f972bc13c4"}, - {file = "greenlet-3.0.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:81bb9c6d52e8321f09c3d165b2a78c680506d9af285bfccbad9fb7ad5a5da3e5"}, - {file = "greenlet-3.0.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fd096eb7ffef17c456cfa587523c5f92321ae02427ff955bebe9e3c63bc9f0da"}, - {file = "greenlet-3.0.3-cp38-cp38-win32.whl", hash = "sha256:d46677c85c5ba00a9cb6f7a00b2bfa6f812192d2c9f7d9c4f6a55b60216712f3"}, - {file = "greenlet-3.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:419b386f84949bf0e7c73e6032e3457b82a787c1ab4a0e43732898a761cc9dbf"}, - {file = "greenlet-3.0.3-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:da70d4d51c8b306bb7a031d5cff6cc25ad253affe89b70352af5f1cb68e74b53"}, - {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:086152f8fbc5955df88382e8a75984e2bb1c892ad2e3c80a2508954e52295257"}, - {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d73a9fe764d77f87f8ec26a0c85144d6a951a6c438dfe50487df5595c6373eac"}, - {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7dcbe92cc99f08c8dd11f930de4d99ef756c3591a5377d1d9cd7dd5e896da71"}, - {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1551a8195c0d4a68fac7a4325efac0d541b48def35feb49d803674ac32582f61"}, - {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:64d7675ad83578e3fc149b617a444fab8efdafc9385471f868eb5ff83e446b8b"}, - {file = "greenlet-3.0.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b37eef18ea55f2ffd8f00ff8fe7c8d3818abd3e25fb73fae2ca3b672e333a7a6"}, - {file = "greenlet-3.0.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:77457465d89b8263bca14759d7c1684df840b6811b2499838cc5b040a8b5b113"}, - {file = "greenlet-3.0.3-cp39-cp39-win32.whl", hash = "sha256:57e8974f23e47dac22b83436bdcf23080ade568ce77df33159e019d161ce1d1e"}, - {file = "greenlet-3.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:c5ee858cfe08f34712f548c3c363e807e7186f03ad7a5039ebadb29e8c6be067"}, - {file = "greenlet-3.0.3.tar.gz", hash = "sha256:43374442353259554ce33599da8b692d5aa96f8976d567d4badf263371fbe491"}, -] - -[package.extras] -docs = ["Sphinx", "furo"] -test = ["objgraph", "psutil"] - -[[package]] -name = "html5tagger" -version = "1.3.0" -description = "Pythonic HTML generation/templating (no template files)" -optional = false -python-versions = ">=3.7" -files = [ - {file = "html5tagger-1.3.0-py3-none-any.whl", hash = "sha256:ce14313515edffec8ed8a36c5890d023922641171b4e6e5774ad1a74998f5351"}, - {file = "html5tagger-1.3.0.tar.gz", hash = "sha256:84fa3dfb49e5c83b79bbd856ab7b1de8e2311c3bb46a8be925f119e3880a8da9"}, -] - -[[package]] -name = "httptools" -version = "0.6.1" -description = "A collection of framework independent HTTP protocol utils." -optional = false -python-versions = ">=3.8.0" -files = [ - {file = "httptools-0.6.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d2f6c3c4cb1948d912538217838f6e9960bc4a521d7f9b323b3da579cd14532f"}, - {file = "httptools-0.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:00d5d4b68a717765b1fabfd9ca755bd12bf44105eeb806c03d1962acd9b8e563"}, - {file = "httptools-0.6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:639dc4f381a870c9ec860ce5c45921db50205a37cc3334e756269736ff0aac58"}, - {file = "httptools-0.6.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e57997ac7fb7ee43140cc03664de5f268813a481dff6245e0075925adc6aa185"}, - {file = "httptools-0.6.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0ac5a0ae3d9f4fe004318d64b8a854edd85ab76cffbf7ef5e32920faef62f142"}, - {file = "httptools-0.6.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:3f30d3ce413088a98b9db71c60a6ada2001a08945cb42dd65a9a9fe228627658"}, - {file = "httptools-0.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:1ed99a373e327f0107cb513b61820102ee4f3675656a37a50083eda05dc9541b"}, - {file = "httptools-0.6.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7a7ea483c1a4485c71cb5f38be9db078f8b0e8b4c4dc0210f531cdd2ddac1ef1"}, - {file = "httptools-0.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:85ed077c995e942b6f1b07583e4eb0a8d324d418954fc6af913d36db7c05a5a0"}, - {file = "httptools-0.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b0bb634338334385351a1600a73e558ce619af390c2b38386206ac6a27fecfc"}, - {file = "httptools-0.6.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d9ceb2c957320def533671fc9c715a80c47025139c8d1f3797477decbc6edd2"}, - {file = "httptools-0.6.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4f0f8271c0a4db459f9dc807acd0eadd4839934a4b9b892f6f160e94da309837"}, - {file = "httptools-0.6.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6a4f5ccead6d18ec072ac0b84420e95d27c1cdf5c9f1bc8fbd8daf86bd94f43d"}, - {file = "httptools-0.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:5cceac09f164bcba55c0500a18fe3c47df29b62353198e4f37bbcc5d591172c3"}, - {file = "httptools-0.6.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:75c8022dca7935cba14741a42744eee13ba05db00b27a4b940f0d646bd4d56d0"}, - {file = "httptools-0.6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:48ed8129cd9a0d62cf4d1575fcf90fb37e3ff7d5654d3a5814eb3d55f36478c2"}, - {file = "httptools-0.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f58e335a1402fb5a650e271e8c2d03cfa7cea46ae124649346d17bd30d59c90"}, - {file = "httptools-0.6.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93ad80d7176aa5788902f207a4e79885f0576134695dfb0fefc15b7a4648d503"}, - {file = "httptools-0.6.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9bb68d3a085c2174c2477eb3ffe84ae9fb4fde8792edb7bcd09a1d8467e30a84"}, - {file = "httptools-0.6.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:b512aa728bc02354e5ac086ce76c3ce635b62f5fbc32ab7082b5e582d27867bb"}, - {file = "httptools-0.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:97662ce7fb196c785344d00d638fc9ad69e18ee4bfb4000b35a52efe5adcc949"}, - {file = "httptools-0.6.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:8e216a038d2d52ea13fdd9b9c9c7459fb80d78302b257828285eca1c773b99b3"}, - {file = "httptools-0.6.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3e802e0b2378ade99cd666b5bffb8b2a7cc8f3d28988685dc300469ea8dd86cb"}, - {file = "httptools-0.6.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4bd3e488b447046e386a30f07af05f9b38d3d368d1f7b4d8f7e10af85393db97"}, - {file = "httptools-0.6.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe467eb086d80217b7584e61313ebadc8d187a4d95bb62031b7bab4b205c3ba3"}, - {file = "httptools-0.6.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:3c3b214ce057c54675b00108ac42bacf2ab8f85c58e3f324a4e963bbc46424f4"}, - {file = "httptools-0.6.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8ae5b97f690badd2ca27cbf668494ee1b6d34cf1c464271ef7bfa9ca6b83ffaf"}, - {file = "httptools-0.6.1-cp38-cp38-win_amd64.whl", hash = "sha256:405784577ba6540fa7d6ff49e37daf104e04f4b4ff2d1ac0469eaa6a20fde084"}, - {file = "httptools-0.6.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:95fb92dd3649f9cb139e9c56604cc2d7c7bf0fc2e7c8d7fbd58f96e35eddd2a3"}, - {file = "httptools-0.6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:dcbab042cc3ef272adc11220517278519adf8f53fd3056d0e68f0a6f891ba94e"}, - {file = "httptools-0.6.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cf2372e98406efb42e93bfe10f2948e467edfd792b015f1b4ecd897903d3e8d"}, - {file = "httptools-0.6.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:678fcbae74477a17d103b7cae78b74800d795d702083867ce160fc202104d0da"}, - {file = "httptools-0.6.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:e0b281cf5a125c35f7f6722b65d8542d2e57331be573e9e88bc8b0115c4a7a81"}, - {file = "httptools-0.6.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:95658c342529bba4e1d3d2b1a874db16c7cca435e8827422154c9da76ac4e13a"}, - {file = "httptools-0.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:7ebaec1bf683e4bf5e9fbb49b8cc36da482033596a415b3e4ebab5a4c0d7ec5e"}, - {file = "httptools-0.6.1.tar.gz", hash = "sha256:c6e26c30455600b95d94b1b836085138e82f177351454ee841c148f93a9bad5a"}, -] - -[package.extras] -test = ["Cython (>=0.29.24,<0.30.0)"] - -[[package]] -name = "idna" -version = "3.6" -description = "Internationalized Domain Names in Applications (IDNA)" -optional = false -python-versions = ">=3.5" -files = [ - {file = "idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"}, - {file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"}, -] - -[[package]] -name = "importlib-resources" -version = "6.4.0" -description = "Read resources from Python packages" -optional = false -python-versions = ">=3.8" -files = [ - {file = "importlib_resources-6.4.0-py3-none-any.whl", hash = "sha256:50d10f043df931902d4194ea07ec57960f66a80449ff867bfe782b4c486ba78c"}, - {file = "importlib_resources-6.4.0.tar.gz", hash = "sha256:cdb2b453b8046ca4e3798eb1d84f3cce1446a0e8e7b5ef4efb600f19fc398145"}, -] - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] -testing = ["jaraco.test (>=5.4)", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-ruff (>=0.2.1)", "zipp (>=3.17)"] - -[[package]] -name = "iso8601" -version = "2.1.0" -description = "Simple module to parse ISO 8601 dates" -optional = false -python-versions = ">=3.7,<4.0" -files = [ - {file = "iso8601-2.1.0-py3-none-any.whl", hash = "sha256:aac4145c4dcb66ad8b648a02830f5e2ff6c24af20f4f482689be402db2429242"}, - {file = "iso8601-2.1.0.tar.gz", hash = "sha256:6b1d3829ee8921c4301998c909f7829fa9ed3cbdac0d3b16af2d743aed1ba8df"}, -] - -[[package]] -name = "jinja2" -version = "3.1.3" -description = "A very fast and expressive template engine." -optional = false -python-versions = ">=3.7" -files = [ - {file = "Jinja2-3.1.3-py3-none-any.whl", hash = "sha256:7d6d50dd97d52cbc355597bd845fabfbac3f551e1f99619e39a35ce8c370b5fa"}, - {file = "Jinja2-3.1.3.tar.gz", hash = "sha256:ac8bd6544d4bb2c9792bf3a159e80bba8fda7f07e81bc3aed565432d5925ba90"}, -] - -[package.dependencies] -MarkupSafe = ">=2.0" - -[package.extras] -i18n = ["Babel (>=2.7)"] - -[[package]] -name = "limits" -version = "3.10.1" -description = "Rate limiting utilities" -optional = false -python-versions = ">=3.7" -files = [ - {file = "limits-3.10.1-py3-none-any.whl", hash = "sha256:446242f5a6f7b8c7744e286a70793264ed81bca97860f94b821347284d14fbe9"}, - {file = "limits-3.10.1.tar.gz", hash = "sha256:1ee31d169d498da267a1b72183ae5940afc64b17b4ed4dfd977f6ea5607c2cfb"}, -] - -[package.dependencies] -deprecated = ">=1.2" -importlib-resources = ">=1.3" -packaging = ">=21,<25" -typing-extensions = "*" - -[package.extras] -all = ["aetcd", "coredis (>=3.4.0,<5)", "emcache (>=0.6.1)", "emcache (>=1)", "etcd3", "motor (>=3,<4)", "pymemcache (>3,<5.0.0)", "pymongo (>4.1,<5)", "redis (>3,!=4.5.2,!=4.5.3,<6.0.0)", "redis (>=4.2.0,!=4.5.2,!=4.5.3)"] -async-etcd = ["aetcd"] -async-memcached = ["emcache (>=0.6.1)", "emcache (>=1)"] -async-mongodb = ["motor (>=3,<4)"] -async-redis = ["coredis (>=3.4.0,<5)"] -etcd = ["etcd3"] -memcached = ["pymemcache (>3,<5.0.0)"] -mongodb = ["pymongo (>4.1,<5)"] -redis = ["redis (>3,!=4.5.2,!=4.5.3,<6.0.0)"] -rediscluster = ["redis (>=4.2.0,!=4.5.2,!=4.5.3)"] - -[[package]] -name = "lxml" -version = "5.2.1" -description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API." -optional = false -python-versions = ">=3.6" -files = [ - {file = "lxml-5.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1f7785f4f789fdb522729ae465adcaa099e2a3441519df750ebdccc481d961a1"}, - {file = "lxml-5.2.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6cc6ee342fb7fa2471bd9b6d6fdfc78925a697bf5c2bcd0a302e98b0d35bfad3"}, - {file = "lxml-5.2.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:794f04eec78f1d0e35d9e0c36cbbb22e42d370dda1609fb03bcd7aeb458c6377"}, - {file = "lxml-5.2.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c817d420c60a5183953c783b0547d9eb43b7b344a2c46f69513d5952a78cddf3"}, - {file = "lxml-5.2.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2213afee476546a7f37c7a9b4ad4d74b1e112a6fafffc9185d6d21f043128c81"}, - {file = "lxml-5.2.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b070bbe8d3f0f6147689bed981d19bbb33070225373338df755a46893528104a"}, - {file = "lxml-5.2.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e02c5175f63effbd7c5e590399c118d5db6183bbfe8e0d118bdb5c2d1b48d937"}, - {file = "lxml-5.2.1-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:3dc773b2861b37b41a6136e0b72a1a44689a9c4c101e0cddb6b854016acc0aa8"}, - {file = "lxml-5.2.1-cp310-cp310-manylinux_2_28_ppc64le.whl", hash = "sha256:d7520db34088c96cc0e0a3ad51a4fd5b401f279ee112aa2b7f8f976d8582606d"}, - {file = "lxml-5.2.1-cp310-cp310-manylinux_2_28_s390x.whl", hash = "sha256:bcbf4af004f98793a95355980764b3d80d47117678118a44a80b721c9913436a"}, - {file = "lxml-5.2.1-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:a2b44bec7adf3e9305ce6cbfa47a4395667e744097faed97abb4728748ba7d47"}, - {file = "lxml-5.2.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:1c5bb205e9212d0ebddf946bc07e73fa245c864a5f90f341d11ce7b0b854475d"}, - {file = "lxml-5.2.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2c9d147f754b1b0e723e6afb7ba1566ecb162fe4ea657f53d2139bbf894d050a"}, - {file = "lxml-5.2.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:3545039fa4779be2df51d6395e91a810f57122290864918b172d5dc7ca5bb433"}, - {file = "lxml-5.2.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a91481dbcddf1736c98a80b122afa0f7296eeb80b72344d7f45dc9f781551f56"}, - {file = "lxml-5.2.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:2ddfe41ddc81f29a4c44c8ce239eda5ade4e7fc305fb7311759dd6229a080052"}, - {file = "lxml-5.2.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:a7baf9ffc238e4bf401299f50e971a45bfcc10a785522541a6e3179c83eabf0a"}, - {file = "lxml-5.2.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:31e9a882013c2f6bd2f2c974241bf4ba68c85eba943648ce88936d23209a2e01"}, - {file = "lxml-5.2.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:0a15438253b34e6362b2dc41475e7f80de76320f335e70c5528b7148cac253a1"}, - {file = "lxml-5.2.1-cp310-cp310-win32.whl", hash = "sha256:6992030d43b916407c9aa52e9673612ff39a575523c5f4cf72cdef75365709a5"}, - {file = "lxml-5.2.1-cp310-cp310-win_amd64.whl", hash = "sha256:da052e7962ea2d5e5ef5bc0355d55007407087392cf465b7ad84ce5f3e25fe0f"}, - {file = "lxml-5.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:70ac664a48aa64e5e635ae5566f5227f2ab7f66a3990d67566d9907edcbbf867"}, - {file = "lxml-5.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1ae67b4e737cddc96c99461d2f75d218bdf7a0c3d3ad5604d1f5e7464a2f9ffe"}, - {file = "lxml-5.2.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f18a5a84e16886898e51ab4b1d43acb3083c39b14c8caeb3589aabff0ee0b270"}, - {file = "lxml-5.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c6f2c8372b98208ce609c9e1d707f6918cc118fea4e2c754c9f0812c04ca116d"}, - {file = "lxml-5.2.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:394ed3924d7a01b5bd9a0d9d946136e1c2f7b3dc337196d99e61740ed4bc6fe1"}, - {file = "lxml-5.2.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5d077bc40a1fe984e1a9931e801e42959a1e6598edc8a3223b061d30fbd26bbc"}, - {file = "lxml-5.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:764b521b75701f60683500d8621841bec41a65eb739b8466000c6fdbc256c240"}, - {file = "lxml-5.2.1-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:3a6b45da02336895da82b9d472cd274b22dc27a5cea1d4b793874eead23dd14f"}, - {file = "lxml-5.2.1-cp311-cp311-manylinux_2_28_ppc64le.whl", hash = "sha256:5ea7b6766ac2dfe4bcac8b8595107665a18ef01f8c8343f00710b85096d1b53a"}, - {file = "lxml-5.2.1-cp311-cp311-manylinux_2_28_s390x.whl", hash = "sha256:e196a4ff48310ba62e53a8e0f97ca2bca83cdd2fe2934d8b5cb0df0a841b193a"}, - {file = "lxml-5.2.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:200e63525948e325d6a13a76ba2911f927ad399ef64f57898cf7c74e69b71095"}, - {file = "lxml-5.2.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:dae0ed02f6b075426accbf6b2863c3d0a7eacc1b41fb40f2251d931e50188dad"}, - {file = "lxml-5.2.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:ab31a88a651039a07a3ae327d68ebdd8bc589b16938c09ef3f32a4b809dc96ef"}, - {file = "lxml-5.2.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:df2e6f546c4df14bc81f9498bbc007fbb87669f1bb707c6138878c46b06f6510"}, - {file = "lxml-5.2.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5dd1537e7cc06efd81371f5d1a992bd5ab156b2b4f88834ca852de4a8ea523fa"}, - {file = "lxml-5.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9b9ec9c9978b708d488bec36b9e4c94d88fd12ccac3e62134a9d17ddba910ea9"}, - {file = "lxml-5.2.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:8e77c69d5892cb5ba71703c4057091e31ccf534bd7f129307a4d084d90d014b8"}, - {file = "lxml-5.2.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:a8d5c70e04aac1eda5c829a26d1f75c6e5286c74743133d9f742cda8e53b9c2f"}, - {file = "lxml-5.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c94e75445b00319c1fad60f3c98b09cd63fe1134a8a953dcd48989ef42318534"}, - {file = "lxml-5.2.1-cp311-cp311-win32.whl", hash = "sha256:4951e4f7a5680a2db62f7f4ab2f84617674d36d2d76a729b9a8be4b59b3659be"}, - {file = "lxml-5.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:5c670c0406bdc845b474b680b9a5456c561c65cf366f8db5a60154088c92d102"}, - {file = "lxml-5.2.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:abc25c3cab9ec7fcd299b9bcb3b8d4a1231877e425c650fa1c7576c5107ab851"}, - {file = "lxml-5.2.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6935bbf153f9a965f1e07c2649c0849d29832487c52bb4a5c5066031d8b44fd5"}, - {file = "lxml-5.2.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d793bebb202a6000390a5390078e945bbb49855c29c7e4d56a85901326c3b5d9"}, - {file = "lxml-5.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afd5562927cdef7c4f5550374acbc117fd4ecc05b5007bdfa57cc5355864e0a4"}, - {file = "lxml-5.2.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0e7259016bc4345a31af861fdce942b77c99049d6c2107ca07dc2bba2435c1d9"}, - {file = "lxml-5.2.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:530e7c04f72002d2f334d5257c8a51bf409db0316feee7c87e4385043be136af"}, - {file = "lxml-5.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59689a75ba8d7ffca577aefd017d08d659d86ad4585ccc73e43edbfc7476781a"}, - {file = "lxml-5.2.1-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:f9737bf36262046213a28e789cc82d82c6ef19c85a0cf05e75c670a33342ac2c"}, - {file = "lxml-5.2.1-cp312-cp312-manylinux_2_28_ppc64le.whl", hash = "sha256:3a74c4f27167cb95c1d4af1c0b59e88b7f3e0182138db2501c353555f7ec57f4"}, - {file = "lxml-5.2.1-cp312-cp312-manylinux_2_28_s390x.whl", hash = "sha256:68a2610dbe138fa8c5826b3f6d98a7cfc29707b850ddcc3e21910a6fe51f6ca0"}, - {file = "lxml-5.2.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:f0a1bc63a465b6d72569a9bba9f2ef0334c4e03958e043da1920299100bc7c08"}, - {file = "lxml-5.2.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c2d35a1d047efd68027817b32ab1586c1169e60ca02c65d428ae815b593e65d4"}, - {file = "lxml-5.2.1-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:79bd05260359170f78b181b59ce871673ed01ba048deef4bf49a36ab3e72e80b"}, - {file = "lxml-5.2.1-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:865bad62df277c04beed9478fe665b9ef63eb28fe026d5dedcb89b537d2e2ea6"}, - {file = "lxml-5.2.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:44f6c7caff88d988db017b9b0e4ab04934f11e3e72d478031efc7edcac6c622f"}, - {file = "lxml-5.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:71e97313406ccf55d32cc98a533ee05c61e15d11b99215b237346171c179c0b0"}, - {file = "lxml-5.2.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:057cdc6b86ab732cf361f8b4d8af87cf195a1f6dc5b0ff3de2dced242c2015e0"}, - {file = "lxml-5.2.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:f3bbbc998d42f8e561f347e798b85513ba4da324c2b3f9b7969e9c45b10f6169"}, - {file = "lxml-5.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:491755202eb21a5e350dae00c6d9a17247769c64dcf62d8c788b5c135e179dc4"}, - {file = "lxml-5.2.1-cp312-cp312-win32.whl", hash = "sha256:8de8f9d6caa7f25b204fc861718815d41cbcf27ee8f028c89c882a0cf4ae4134"}, - {file = "lxml-5.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:f2a9efc53d5b714b8df2b4b3e992accf8ce5bbdfe544d74d5c6766c9e1146a3a"}, - {file = "lxml-5.2.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:70a9768e1b9d79edca17890175ba915654ee1725975d69ab64813dd785a2bd5c"}, - {file = "lxml-5.2.1-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c38d7b9a690b090de999835f0443d8aa93ce5f2064035dfc48f27f02b4afc3d0"}, - {file = "lxml-5.2.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5670fb70a828663cc37552a2a85bf2ac38475572b0e9b91283dc09efb52c41d1"}, - {file = "lxml-5.2.1-cp36-cp36m-manylinux_2_28_x86_64.whl", hash = "sha256:958244ad566c3ffc385f47dddde4145088a0ab893504b54b52c041987a8c1863"}, - {file = "lxml-5.2.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:b6241d4eee5f89453307c2f2bfa03b50362052ca0af1efecf9fef9a41a22bb4f"}, - {file = "lxml-5.2.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:2a66bf12fbd4666dd023b6f51223aed3d9f3b40fef06ce404cb75bafd3d89536"}, - {file = "lxml-5.2.1-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:9123716666e25b7b71c4e1789ec829ed18663152008b58544d95b008ed9e21e9"}, - {file = "lxml-5.2.1-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:0c3f67e2aeda739d1cc0b1102c9a9129f7dc83901226cc24dd72ba275ced4218"}, - {file = "lxml-5.2.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:5d5792e9b3fb8d16a19f46aa8208987cfeafe082363ee2745ea8b643d9cc5b45"}, - {file = "lxml-5.2.1-cp36-cp36m-musllinux_1_2_aarch64.whl", hash = "sha256:88e22fc0a6684337d25c994381ed8a1580a6f5ebebd5ad41f89f663ff4ec2885"}, - {file = "lxml-5.2.1-cp36-cp36m-musllinux_1_2_ppc64le.whl", hash = "sha256:21c2e6b09565ba5b45ae161b438e033a86ad1736b8c838c766146eff8ceffff9"}, - {file = "lxml-5.2.1-cp36-cp36m-musllinux_1_2_s390x.whl", hash = "sha256:afbbdb120d1e78d2ba8064a68058001b871154cc57787031b645c9142b937a62"}, - {file = "lxml-5.2.1-cp36-cp36m-musllinux_1_2_x86_64.whl", hash = "sha256:627402ad8dea044dde2eccde4370560a2b750ef894c9578e1d4f8ffd54000461"}, - {file = "lxml-5.2.1-cp36-cp36m-win32.whl", hash = "sha256:e89580a581bf478d8dcb97d9cd011d567768e8bc4095f8557b21c4d4c5fea7d0"}, - {file = "lxml-5.2.1-cp36-cp36m-win_amd64.whl", hash = "sha256:59565f10607c244bc4c05c0c5fa0c190c990996e0c719d05deec7030c2aa8289"}, - {file = "lxml-5.2.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:857500f88b17a6479202ff5fe5f580fc3404922cd02ab3716197adf1ef628029"}, - {file = "lxml-5.2.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:56c22432809085b3f3ae04e6e7bdd36883d7258fcd90e53ba7b2e463efc7a6af"}, - {file = "lxml-5.2.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a55ee573116ba208932e2d1a037cc4b10d2c1cb264ced2184d00b18ce585b2c0"}, - {file = "lxml-5.2.1-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:6cf58416653c5901e12624e4013708b6e11142956e7f35e7a83f1ab02f3fe456"}, - {file = "lxml-5.2.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:64c2baa7774bc22dd4474248ba16fe1a7f611c13ac6123408694d4cc93d66dbd"}, - {file = "lxml-5.2.1-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:74b28c6334cca4dd704e8004cba1955af0b778cf449142e581e404bd211fb619"}, - {file = "lxml-5.2.1-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:7221d49259aa1e5a8f00d3d28b1e0b76031655ca74bb287123ef56c3db92f213"}, - {file = "lxml-5.2.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:3dbe858ee582cbb2c6294dc85f55b5f19c918c2597855e950f34b660f1a5ede6"}, - {file = "lxml-5.2.1-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:04ab5415bf6c86e0518d57240a96c4d1fcfc3cb370bb2ac2a732b67f579e5a04"}, - {file = "lxml-5.2.1-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:6ab833e4735a7e5533711a6ea2df26459b96f9eec36d23f74cafe03631647c41"}, - {file = "lxml-5.2.1-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:f443cdef978430887ed55112b491f670bba6462cea7a7742ff8f14b7abb98d75"}, - {file = "lxml-5.2.1-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:9e2addd2d1866fe112bc6f80117bcc6bc25191c5ed1bfbcf9f1386a884252ae8"}, - {file = "lxml-5.2.1-cp37-cp37m-win32.whl", hash = "sha256:f51969bac61441fd31f028d7b3b45962f3ecebf691a510495e5d2cd8c8092dbd"}, - {file = "lxml-5.2.1-cp37-cp37m-win_amd64.whl", hash = "sha256:b0b58fbfa1bf7367dde8a557994e3b1637294be6cf2169810375caf8571a085c"}, - {file = "lxml-5.2.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:3e183c6e3298a2ed5af9d7a356ea823bccaab4ec2349dc9ed83999fd289d14d5"}, - {file = "lxml-5.2.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:804f74efe22b6a227306dd890eecc4f8c59ff25ca35f1f14e7482bbce96ef10b"}, - {file = "lxml-5.2.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:08802f0c56ed150cc6885ae0788a321b73505d2263ee56dad84d200cab11c07a"}, - {file = "lxml-5.2.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0f8c09ed18ecb4ebf23e02b8e7a22a05d6411911e6fabef3a36e4f371f4f2585"}, - {file = "lxml-5.2.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e3d30321949861404323c50aebeb1943461a67cd51d4200ab02babc58bd06a86"}, - {file = "lxml-5.2.1-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:b560e3aa4b1d49e0e6c847d72665384db35b2f5d45f8e6a5c0072e0283430533"}, - {file = "lxml-5.2.1-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:058a1308914f20784c9f4674036527e7c04f7be6fb60f5d61353545aa7fcb739"}, - {file = "lxml-5.2.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:adfb84ca6b87e06bc6b146dc7da7623395db1e31621c4785ad0658c5028b37d7"}, - {file = "lxml-5.2.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:417d14450f06d51f363e41cace6488519038f940676ce9664b34ebf5653433a5"}, - {file = "lxml-5.2.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:a2dfe7e2473f9b59496247aad6e23b405ddf2e12ef0765677b0081c02d6c2c0b"}, - {file = "lxml-5.2.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:bf2e2458345d9bffb0d9ec16557d8858c9c88d2d11fed53998512504cd9df49b"}, - {file = "lxml-5.2.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:58278b29cb89f3e43ff3e0c756abbd1518f3ee6adad9e35b51fb101c1c1daaec"}, - {file = "lxml-5.2.1-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:64641a6068a16201366476731301441ce93457eb8452056f570133a6ceb15fca"}, - {file = "lxml-5.2.1-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:78bfa756eab503673991bdcf464917ef7845a964903d3302c5f68417ecdc948c"}, - {file = "lxml-5.2.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:11a04306fcba10cd9637e669fd73aa274c1c09ca64af79c041aa820ea992b637"}, - {file = "lxml-5.2.1-cp38-cp38-win32.whl", hash = "sha256:66bc5eb8a323ed9894f8fa0ee6cb3e3fb2403d99aee635078fd19a8bc7a5a5da"}, - {file = "lxml-5.2.1-cp38-cp38-win_amd64.whl", hash = "sha256:9676bfc686fa6a3fa10cd4ae6b76cae8be26eb5ec6811d2a325636c460da1806"}, - {file = "lxml-5.2.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:cf22b41fdae514ee2f1691b6c3cdeae666d8b7fa9434de445f12bbeee0cf48dd"}, - {file = "lxml-5.2.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ec42088248c596dbd61d4ae8a5b004f97a4d91a9fd286f632e42e60b706718d7"}, - {file = "lxml-5.2.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cd53553ddad4a9c2f1f022756ae64abe16da1feb497edf4d9f87f99ec7cf86bd"}, - {file = "lxml-5.2.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:feaa45c0eae424d3e90d78823f3828e7dc42a42f21ed420db98da2c4ecf0a2cb"}, - {file = "lxml-5.2.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ddc678fb4c7e30cf830a2b5a8d869538bc55b28d6c68544d09c7d0d8f17694dc"}, - {file = "lxml-5.2.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:853e074d4931dbcba7480d4dcab23d5c56bd9607f92825ab80ee2bd916edea53"}, - {file = "lxml-5.2.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc4691d60512798304acb9207987e7b2b7c44627ea88b9d77489bbe3e6cc3bd4"}, - {file = "lxml-5.2.1-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:beb72935a941965c52990f3a32d7f07ce869fe21c6af8b34bf6a277b33a345d3"}, - {file = "lxml-5.2.1-cp39-cp39-manylinux_2_28_ppc64le.whl", hash = "sha256:6588c459c5627fefa30139be4d2e28a2c2a1d0d1c265aad2ba1935a7863a4913"}, - {file = "lxml-5.2.1-cp39-cp39-manylinux_2_28_s390x.whl", hash = "sha256:588008b8497667f1ddca7c99f2f85ce8511f8f7871b4a06ceede68ab62dff64b"}, - {file = "lxml-5.2.1-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:b6787b643356111dfd4032b5bffe26d2f8331556ecb79e15dacb9275da02866e"}, - {file = "lxml-5.2.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7c17b64b0a6ef4e5affae6a3724010a7a66bda48a62cfe0674dabd46642e8b54"}, - {file = "lxml-5.2.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:27aa20d45c2e0b8cd05da6d4759649170e8dfc4f4e5ef33a34d06f2d79075d57"}, - {file = "lxml-5.2.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:d4f2cc7060dc3646632d7f15fe68e2fa98f58e35dd5666cd525f3b35d3fed7f8"}, - {file = "lxml-5.2.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff46d772d5f6f73564979cd77a4fffe55c916a05f3cb70e7c9c0590059fb29ef"}, - {file = "lxml-5.2.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:96323338e6c14e958d775700ec8a88346014a85e5de73ac7967db0367582049b"}, - {file = "lxml-5.2.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:52421b41ac99e9d91934e4d0d0fe7da9f02bfa7536bb4431b4c05c906c8c6919"}, - {file = "lxml-5.2.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:7a7efd5b6d3e30d81ec68ab8a88252d7c7c6f13aaa875009fe3097eb4e30b84c"}, - {file = "lxml-5.2.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:0ed777c1e8c99b63037b91f9d73a6aad20fd035d77ac84afcc205225f8f41188"}, - {file = "lxml-5.2.1-cp39-cp39-win32.whl", hash = "sha256:644df54d729ef810dcd0f7732e50e5ad1bd0a135278ed8d6bcb06f33b6b6f708"}, - {file = "lxml-5.2.1-cp39-cp39-win_amd64.whl", hash = "sha256:9ca66b8e90daca431b7ca1408cae085d025326570e57749695d6a01454790e95"}, - {file = "lxml-5.2.1-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:9b0ff53900566bc6325ecde9181d89afadc59c5ffa39bddf084aaedfe3b06a11"}, - {file = "lxml-5.2.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fd6037392f2d57793ab98d9e26798f44b8b4da2f2464388588f48ac52c489ea1"}, - {file = "lxml-5.2.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b9c07e7a45bb64e21df4b6aa623cb8ba214dfb47d2027d90eac197329bb5e94"}, - {file = "lxml-5.2.1-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:3249cc2989d9090eeac5467e50e9ec2d40704fea9ab72f36b034ea34ee65ca98"}, - {file = "lxml-5.2.1-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:f42038016852ae51b4088b2862126535cc4fc85802bfe30dea3500fdfaf1864e"}, - {file = "lxml-5.2.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:533658f8fbf056b70e434dff7e7aa611bcacb33e01f75de7f821810e48d1bb66"}, - {file = "lxml-5.2.1-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:622020d4521e22fb371e15f580d153134bfb68d6a429d1342a25f051ec72df1c"}, - {file = "lxml-5.2.1-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:efa7b51824aa0ee957ccd5a741c73e6851de55f40d807f08069eb4c5a26b2baa"}, - {file = "lxml-5.2.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c6ad0fbf105f6bcc9300c00010a2ffa44ea6f555df1a2ad95c88f5656104817"}, - {file = "lxml-5.2.1-pp37-pypy37_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:e233db59c8f76630c512ab4a4daf5a5986da5c3d5b44b8e9fc742f2a24dbd460"}, - {file = "lxml-5.2.1-pp37-pypy37_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:6a014510830df1475176466b6087fc0c08b47a36714823e58d8b8d7709132a96"}, - {file = "lxml-5.2.1-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:d38c8f50ecf57f0463399569aa388b232cf1a2ffb8f0a9a5412d0db57e054860"}, - {file = "lxml-5.2.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:5aea8212fb823e006b995c4dda533edcf98a893d941f173f6c9506126188860d"}, - {file = "lxml-5.2.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ff097ae562e637409b429a7ac958a20aab237a0378c42dabaa1e3abf2f896e5f"}, - {file = "lxml-5.2.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f5d65c39f16717a47c36c756af0fb36144069c4718824b7533f803ecdf91138"}, - {file = "lxml-5.2.1-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:3d0c3dd24bb4605439bf91068598d00c6370684f8de4a67c2992683f6c309d6b"}, - {file = "lxml-5.2.1-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e32be23d538753a8adb6c85bd539f5fd3b15cb987404327c569dfc5fd8366e85"}, - {file = "lxml-5.2.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:cc518cea79fd1e2f6c90baafa28906d4309d24f3a63e801d855e7424c5b34144"}, - {file = "lxml-5.2.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a0af35bd8ebf84888373630f73f24e86bf016642fb8576fba49d3d6b560b7cbc"}, - {file = "lxml-5.2.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8aca2e3a72f37bfc7b14ba96d4056244001ddcc18382bd0daa087fd2e68a354"}, - {file = "lxml-5.2.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ca1e8188b26a819387b29c3895c47a5e618708fe6f787f3b1a471de2c4a94d9"}, - {file = "lxml-5.2.1-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c8ba129e6d3b0136a0f50345b2cb3db53f6bda5dd8c7f5d83fbccba97fb5dcb5"}, - {file = "lxml-5.2.1-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e998e304036198b4f6914e6a1e2b6f925208a20e2042563d9734881150c6c246"}, - {file = "lxml-5.2.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:d3be9b2076112e51b323bdf6d5a7f8a798de55fb8d95fcb64bd179460cdc0704"}, - {file = "lxml-5.2.1.tar.gz", hash = "sha256:3f7765e69bbce0906a7c74d5fe46d2c7a7596147318dbc08e4a2431f3060e306"}, -] - -[package.extras] -cssselect = ["cssselect (>=0.7)"] -html-clean = ["lxml-html-clean"] -html5 = ["html5lib"] -htmlsoup = ["BeautifulSoup4"] -source = ["Cython (>=3.0.10)"] - -[[package]] -name = "markupsafe" -version = "2.1.5" -description = "Safely add untrusted strings to HTML/XML markup." -optional = false -python-versions = ">=3.7" -files = [ - {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-win32.whl", hash = "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-win_amd64.whl", hash = "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-win32.whl", hash = "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-win_amd64.whl", hash = "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-win32.whl", hash = "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl", hash = "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-win32.whl", hash = "sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-win_amd64.whl", hash = "sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-win32.whl", hash = "sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-win_amd64.whl", hash = "sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-win32.whl", hash = "sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-win_amd64.whl", hash = "sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5"}, - {file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"}, -] - -[[package]] -name = "multidict" -version = "6.0.5" -description = "multidict implementation" -optional = false -python-versions = ">=3.7" -files = [ - {file = "multidict-6.0.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:228b644ae063c10e7f324ab1ab6b548bdf6f8b47f3ec234fef1093bc2735e5f9"}, - {file = "multidict-6.0.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:896ebdcf62683551312c30e20614305f53125750803b614e9e6ce74a96232604"}, - {file = "multidict-6.0.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:411bf8515f3be9813d06004cac41ccf7d1cd46dfe233705933dd163b60e37600"}, - {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d147090048129ce3c453f0292e7697d333db95e52616b3793922945804a433c"}, - {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:215ed703caf15f578dca76ee6f6b21b7603791ae090fbf1ef9d865571039ade5"}, - {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c6390cf87ff6234643428991b7359b5f59cc15155695deb4eda5c777d2b880f"}, - {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21fd81c4ebdb4f214161be351eb5bcf385426bf023041da2fd9e60681f3cebae"}, - {file = "multidict-6.0.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3cc2ad10255f903656017363cd59436f2111443a76f996584d1077e43ee51182"}, - {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6939c95381e003f54cd4c5516740faba40cf5ad3eeff460c3ad1d3e0ea2549bf"}, - {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:220dd781e3f7af2c2c1053da9fa96d9cf3072ca58f057f4c5adaaa1cab8fc442"}, - {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:766c8f7511df26d9f11cd3a8be623e59cca73d44643abab3f8c8c07620524e4a"}, - {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:fe5d7785250541f7f5019ab9cba2c71169dc7d74d0f45253f8313f436458a4ef"}, - {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c1c1496e73051918fcd4f58ff2e0f2f3066d1c76a0c6aeffd9b45d53243702cc"}, - {file = "multidict-6.0.5-cp310-cp310-win32.whl", hash = "sha256:7afcdd1fc07befad18ec4523a782cde4e93e0a2bf71239894b8d61ee578c1319"}, - {file = "multidict-6.0.5-cp310-cp310-win_amd64.whl", hash = "sha256:99f60d34c048c5c2fabc766108c103612344c46e35d4ed9ae0673d33c8fb26e8"}, - {file = "multidict-6.0.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f285e862d2f153a70586579c15c44656f888806ed0e5b56b64489afe4a2dbfba"}, - {file = "multidict-6.0.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:53689bb4e102200a4fafa9de9c7c3c212ab40a7ab2c8e474491914d2305f187e"}, - {file = "multidict-6.0.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:612d1156111ae11d14afaf3a0669ebf6c170dbb735e510a7438ffe2369a847fd"}, - {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7be7047bd08accdb7487737631d25735c9a04327911de89ff1b26b81745bd4e3"}, - {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de170c7b4fe6859beb8926e84f7d7d6c693dfe8e27372ce3b76f01c46e489fcf"}, - {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:04bde7a7b3de05732a4eb39c94574db1ec99abb56162d6c520ad26f83267de29"}, - {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85f67aed7bb647f93e7520633d8f51d3cbc6ab96957c71272b286b2f30dc70ed"}, - {file = "multidict-6.0.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:425bf820055005bfc8aa9a0b99ccb52cc2f4070153e34b701acc98d201693733"}, - {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d3eb1ceec286eba8220c26f3b0096cf189aea7057b6e7b7a2e60ed36b373b77f"}, - {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:7901c05ead4b3fb75113fb1dd33eb1253c6d3ee37ce93305acd9d38e0b5f21a4"}, - {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:e0e79d91e71b9867c73323a3444724d496c037e578a0e1755ae159ba14f4f3d1"}, - {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:29bfeb0dff5cb5fdab2023a7a9947b3b4af63e9c47cae2a10ad58394b517fddc"}, - {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e030047e85cbcedbfc073f71836d62dd5dadfbe7531cae27789ff66bc551bd5e"}, - {file = "multidict-6.0.5-cp311-cp311-win32.whl", hash = "sha256:2f4848aa3baa109e6ab81fe2006c77ed4d3cd1e0ac2c1fbddb7b1277c168788c"}, - {file = "multidict-6.0.5-cp311-cp311-win_amd64.whl", hash = "sha256:2faa5ae9376faba05f630d7e5e6be05be22913782b927b19d12b8145968a85ea"}, - {file = "multidict-6.0.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:51d035609b86722963404f711db441cf7134f1889107fb171a970c9701f92e1e"}, - {file = "multidict-6.0.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:cbebcd5bcaf1eaf302617c114aa67569dd3f090dd0ce8ba9e35e9985b41ac35b"}, - {file = "multidict-6.0.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2ffc42c922dbfddb4a4c3b438eb056828719f07608af27d163191cb3e3aa6cc5"}, - {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ceb3b7e6a0135e092de86110c5a74e46bda4bd4fbfeeb3a3bcec79c0f861e450"}, - {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:79660376075cfd4b2c80f295528aa6beb2058fd289f4c9252f986751a4cd0496"}, - {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e4428b29611e989719874670fd152b6625500ad6c686d464e99f5aaeeaca175a"}, - {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d84a5c3a5f7ce6db1f999fb9438f686bc2e09d38143f2d93d8406ed2dd6b9226"}, - {file = "multidict-6.0.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:76c0de87358b192de7ea9649beb392f107dcad9ad27276324c24c91774ca5271"}, - {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:79a6d2ba910adb2cbafc95dad936f8b9386e77c84c35bc0add315b856d7c3abb"}, - {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:92d16a3e275e38293623ebf639c471d3e03bb20b8ebb845237e0d3664914caef"}, - {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:fb616be3538599e797a2017cccca78e354c767165e8858ab5116813146041a24"}, - {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:14c2976aa9038c2629efa2c148022ed5eb4cb939e15ec7aace7ca932f48f9ba6"}, - {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:435a0984199d81ca178b9ae2c26ec3d49692d20ee29bc4c11a2a8d4514c67eda"}, - {file = "multidict-6.0.5-cp312-cp312-win32.whl", hash = "sha256:9fe7b0653ba3d9d65cbe7698cca585bf0f8c83dbbcc710db9c90f478e175f2d5"}, - {file = "multidict-6.0.5-cp312-cp312-win_amd64.whl", hash = "sha256:01265f5e40f5a17f8241d52656ed27192be03bfa8764d88e8220141d1e4b3556"}, - {file = "multidict-6.0.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:19fe01cea168585ba0f678cad6f58133db2aa14eccaf22f88e4a6dccadfad8b3"}, - {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6bf7a982604375a8d49b6cc1b781c1747f243d91b81035a9b43a2126c04766f5"}, - {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:107c0cdefe028703fb5dafe640a409cb146d44a6ae201e55b35a4af8e95457dd"}, - {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:403c0911cd5d5791605808b942c88a8155c2592e05332d2bf78f18697a5fa15e"}, - {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aeaf541ddbad8311a87dd695ed9642401131ea39ad7bc8cf3ef3967fd093b626"}, - {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e4972624066095e52b569e02b5ca97dbd7a7ddd4294bf4e7247d52635630dd83"}, - {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d946b0a9eb8aaa590df1fe082cee553ceab173e6cb5b03239716338629c50c7a"}, - {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b55358304d7a73d7bdf5de62494aaf70bd33015831ffd98bc498b433dfe5b10c"}, - {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:a3145cb08d8625b2d3fee1b2d596a8766352979c9bffe5d7833e0503d0f0b5e5"}, - {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:d65f25da8e248202bd47445cec78e0025c0fe7582b23ec69c3b27a640dd7a8e3"}, - {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:c9bf56195c6bbd293340ea82eafd0071cb3d450c703d2c93afb89f93b8386ccc"}, - {file = "multidict-6.0.5-cp37-cp37m-win32.whl", hash = "sha256:69db76c09796b313331bb7048229e3bee7928eb62bab5e071e9f7fcc4879caee"}, - {file = "multidict-6.0.5-cp37-cp37m-win_amd64.whl", hash = "sha256:fce28b3c8a81b6b36dfac9feb1de115bab619b3c13905b419ec71d03a3fc1423"}, - {file = "multidict-6.0.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:76f067f5121dcecf0d63a67f29080b26c43c71a98b10c701b0677e4a065fbd54"}, - {file = "multidict-6.0.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b82cc8ace10ab5bd93235dfaab2021c70637005e1ac787031f4d1da63d493c1d"}, - {file = "multidict-6.0.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5cb241881eefd96b46f89b1a056187ea8e9ba14ab88ba632e68d7a2ecb7aadf7"}, - {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8e94e6912639a02ce173341ff62cc1201232ab86b8a8fcc05572741a5dc7d93"}, - {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:09a892e4a9fb47331da06948690ae38eaa2426de97b4ccbfafbdcbe5c8f37ff8"}, - {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55205d03e8a598cfc688c71ca8ea5f66447164efff8869517f175ea632c7cb7b"}, - {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37b15024f864916b4951adb95d3a80c9431299080341ab9544ed148091b53f50"}, - {file = "multidict-6.0.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f2a1dee728b52b33eebff5072817176c172050d44d67befd681609b4746e1c2e"}, - {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:edd08e6f2f1a390bf137080507e44ccc086353c8e98c657e666c017718561b89"}, - {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:60d698e8179a42ec85172d12f50b1668254628425a6bd611aba022257cac1386"}, - {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:3d25f19500588cbc47dc19081d78131c32637c25804df8414463ec908631e453"}, - {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:4cc0ef8b962ac7a5e62b9e826bd0cd5040e7d401bc45a6835910ed699037a461"}, - {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:eca2e9d0cc5a889850e9bbd68e98314ada174ff6ccd1129500103df7a94a7a44"}, - {file = "multidict-6.0.5-cp38-cp38-win32.whl", hash = "sha256:4a6a4f196f08c58c59e0b8ef8ec441d12aee4125a7d4f4fef000ccb22f8d7241"}, - {file = "multidict-6.0.5-cp38-cp38-win_amd64.whl", hash = "sha256:0275e35209c27a3f7951e1ce7aaf93ce0d163b28948444bec61dd7badc6d3f8c"}, - {file = "multidict-6.0.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e7be68734bd8c9a513f2b0cfd508802d6609da068f40dc57d4e3494cefc92929"}, - {file = "multidict-6.0.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1d9ea7a7e779d7a3561aade7d596649fbecfa5c08a7674b11b423783217933f9"}, - {file = "multidict-6.0.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ea1456df2a27c73ce51120fa2f519f1bea2f4a03a917f4a43c8707cf4cbbae1a"}, - {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf590b134eb70629e350691ecca88eac3e3b8b3c86992042fb82e3cb1830d5e1"}, - {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5c0631926c4f58e9a5ccce555ad7747d9a9f8b10619621f22f9635f069f6233e"}, - {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dce1c6912ab9ff5f179eaf6efe7365c1f425ed690b03341911bf4939ef2f3046"}, - {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0868d64af83169e4d4152ec612637a543f7a336e4a307b119e98042e852ad9c"}, - {file = "multidict-6.0.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:141b43360bfd3bdd75f15ed811850763555a251e38b2405967f8e25fb43f7d40"}, - {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7df704ca8cf4a073334e0427ae2345323613e4df18cc224f647f251e5e75a527"}, - {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6214c5a5571802c33f80e6c84713b2c79e024995b9c5897f794b43e714daeec9"}, - {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:cd6c8fca38178e12c00418de737aef1261576bd1b6e8c6134d3e729a4e858b38"}, - {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:e02021f87a5b6932fa6ce916ca004c4d441509d33bbdbeca70d05dff5e9d2479"}, - {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ebd8d160f91a764652d3e51ce0d2956b38efe37c9231cd82cfc0bed2e40b581c"}, - {file = "multidict-6.0.5-cp39-cp39-win32.whl", hash = "sha256:04da1bb8c8dbadf2a18a452639771951c662c5ad03aefe4884775454be322c9b"}, - {file = "multidict-6.0.5-cp39-cp39-win_amd64.whl", hash = "sha256:d6f6d4f185481c9669b9447bf9d9cf3b95a0e9df9d169bbc17e363b7d5487755"}, - {file = "multidict-6.0.5-py3-none-any.whl", hash = "sha256:0d63c74e3d7ab26de115c49bffc92cc77ed23395303d496eae515d4204a625e7"}, - {file = "multidict-6.0.5.tar.gz", hash = "sha256:f7e301075edaf50500f0b341543c41194d8df3ae5caf4702f2095f3ca73dd8da"}, -] - -[[package]] -name = "mypy" -version = "1.9.0" -description = "Optional static typing for Python" -optional = false -python-versions = ">=3.8" -files = [ - {file = "mypy-1.9.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f8a67616990062232ee4c3952f41c779afac41405806042a8126fe96e098419f"}, - {file = "mypy-1.9.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d357423fa57a489e8c47b7c85dfb96698caba13d66e086b412298a1a0ea3b0ed"}, - {file = "mypy-1.9.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49c87c15aed320de9b438ae7b00c1ac91cd393c1b854c2ce538e2a72d55df150"}, - {file = "mypy-1.9.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:48533cdd345c3c2e5ef48ba3b0d3880b257b423e7995dada04248725c6f77374"}, - {file = "mypy-1.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:4d3dbd346cfec7cb98e6cbb6e0f3c23618af826316188d587d1c1bc34f0ede03"}, - {file = "mypy-1.9.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:653265f9a2784db65bfca694d1edd23093ce49740b2244cde583aeb134c008f3"}, - {file = "mypy-1.9.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3a3c007ff3ee90f69cf0a15cbcdf0995749569b86b6d2f327af01fd1b8aee9dc"}, - {file = "mypy-1.9.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2418488264eb41f69cc64a69a745fad4a8f86649af4b1041a4c64ee61fc61129"}, - {file = "mypy-1.9.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:68edad3dc7d70f2f17ae4c6c1b9471a56138ca22722487eebacfd1eb5321d612"}, - {file = "mypy-1.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:85ca5fcc24f0b4aeedc1d02f93707bccc04733f21d41c88334c5482219b1ccb3"}, - {file = "mypy-1.9.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aceb1db093b04db5cd390821464504111b8ec3e351eb85afd1433490163d60cd"}, - {file = "mypy-1.9.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0235391f1c6f6ce487b23b9dbd1327b4ec33bb93934aa986efe8a9563d9349e6"}, - {file = "mypy-1.9.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d4d5ddc13421ba3e2e082a6c2d74c2ddb3979c39b582dacd53dd5d9431237185"}, - {file = "mypy-1.9.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:190da1ee69b427d7efa8aa0d5e5ccd67a4fb04038c380237a0d96829cb157913"}, - {file = "mypy-1.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:fe28657de3bfec596bbeef01cb219833ad9d38dd5393fc649f4b366840baefe6"}, - {file = "mypy-1.9.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e54396d70be04b34f31d2edf3362c1edd023246c82f1730bbf8768c28db5361b"}, - {file = "mypy-1.9.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5e6061f44f2313b94f920e91b204ec600982961e07a17e0f6cd83371cb23f5c2"}, - {file = "mypy-1.9.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81a10926e5473c5fc3da8abb04119a1f5811a236dc3a38d92015cb1e6ba4cb9e"}, - {file = "mypy-1.9.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b685154e22e4e9199fc95f298661deea28aaede5ae16ccc8cbb1045e716b3e04"}, - {file = "mypy-1.9.0-cp38-cp38-win_amd64.whl", hash = "sha256:5d741d3fc7c4da608764073089e5f58ef6352bedc223ff58f2f038c2c4698a89"}, - {file = "mypy-1.9.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:587ce887f75dd9700252a3abbc9c97bbe165a4a630597845c61279cf32dfbf02"}, - {file = "mypy-1.9.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f88566144752999351725ac623471661c9d1cd8caa0134ff98cceeea181789f4"}, - {file = "mypy-1.9.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61758fabd58ce4b0720ae1e2fea5cfd4431591d6d590b197775329264f86311d"}, - {file = "mypy-1.9.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e49499be624dead83927e70c756970a0bc8240e9f769389cdf5714b0784ca6bf"}, - {file = "mypy-1.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:571741dc4194b4f82d344b15e8837e8c5fcc462d66d076748142327626a1b6e9"}, - {file = "mypy-1.9.0-py3-none-any.whl", hash = "sha256:a260627a570559181a9ea5de61ac6297aa5af202f06fd7ab093ce74e7181e43e"}, - {file = "mypy-1.9.0.tar.gz", hash = "sha256:3cc5da0127e6a478cddd906068496a97a7618a21ce9b54bde5bf7e539c7af974"}, -] - -[package.dependencies] -mypy-extensions = ">=1.0.0" -typing-extensions = ">=4.1.0" - -[package.extras] -dmypy = ["psutil (>=4.0)"] -install-types = ["pip"] -mypyc = ["setuptools (>=50)"] -reports = ["lxml"] - -[[package]] -name = "mypy-extensions" -version = "1.0.0" -description = "Type system extensions for programs checked with the mypy type checker." -optional = false -python-versions = ">=3.5" -files = [ - {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, - {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, -] - -[[package]] -name = "packaging" -version = "24.0" -description = "Core utilities for Python packages" -optional = false -python-versions = ">=3.7" -files = [ - {file = "packaging-24.0-py3-none-any.whl", hash = "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5"}, - {file = "packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"}, -] - -[[package]] -name = "passlib" -version = "1.7.4" -description = "comprehensive password hashing framework supporting over 30 schemes" -optional = false -python-versions = "*" -files = [ - {file = "passlib-1.7.4-py2.py3-none-any.whl", hash = "sha256:aa6bca462b8d8bda89c70b382f0c298a20b5560af6cbfa2dce410c0a2fb669f1"}, - {file = "passlib-1.7.4.tar.gz", hash = "sha256:defd50f72b65c5402ab2c573830a6978e5f202ad0d984793c8dde2c4152ebe04"}, -] - -[package.extras] -argon2 = ["argon2-cffi (>=18.2.0)"] -bcrypt = ["bcrypt (>=3.1.0)"] -build-docs = ["cloud-sptheme (>=1.10.1)", "sphinx (>=1.6)", "sphinxcontrib-fulltoc (>=1.2.0)"] -totp = ["cryptography"] - -[[package]] -name = "pendulum" -version = "2.1.2" -description = "Python datetimes made easy" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -files = [ - {file = "pendulum-2.1.2-cp27-cp27m-macosx_10_15_x86_64.whl", hash = "sha256:b6c352f4bd32dff1ea7066bd31ad0f71f8d8100b9ff709fb343f3b86cee43efe"}, - {file = "pendulum-2.1.2-cp27-cp27m-win_amd64.whl", hash = "sha256:318f72f62e8e23cd6660dbafe1e346950281a9aed144b5c596b2ddabc1d19739"}, - {file = "pendulum-2.1.2-cp35-cp35m-macosx_10_15_x86_64.whl", hash = "sha256:0731f0c661a3cb779d398803655494893c9f581f6488048b3fb629c2342b5394"}, - {file = "pendulum-2.1.2-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:3481fad1dc3f6f6738bd575a951d3c15d4b4ce7c82dce37cf8ac1483fde6e8b0"}, - {file = "pendulum-2.1.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:9702069c694306297ed362ce7e3c1ef8404ac8ede39f9b28b7c1a7ad8c3959e3"}, - {file = "pendulum-2.1.2-cp35-cp35m-win_amd64.whl", hash = "sha256:fb53ffa0085002ddd43b6ca61a7b34f2d4d7c3ed66f931fe599e1a531b42af9b"}, - {file = "pendulum-2.1.2-cp36-cp36m-macosx_10_15_x86_64.whl", hash = "sha256:c501749fdd3d6f9e726086bf0cd4437281ed47e7bca132ddb522f86a1645d360"}, - {file = "pendulum-2.1.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:c807a578a532eeb226150d5006f156632df2cc8c5693d778324b43ff8c515dd0"}, - {file = "pendulum-2.1.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:2d1619a721df661e506eff8db8614016f0720ac171fe80dda1333ee44e684087"}, - {file = "pendulum-2.1.2-cp36-cp36m-win_amd64.whl", hash = "sha256:f888f2d2909a414680a29ae74d0592758f2b9fcdee3549887779cd4055e975db"}, - {file = "pendulum-2.1.2-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:e95d329384717c7bf627bf27e204bc3b15c8238fa8d9d9781d93712776c14002"}, - {file = "pendulum-2.1.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:4c9c689747f39d0d02a9f94fcee737b34a5773803a64a5fdb046ee9cac7442c5"}, - {file = "pendulum-2.1.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:1245cd0075a3c6d889f581f6325dd8404aca5884dea7223a5566c38aab94642b"}, - {file = "pendulum-2.1.2-cp37-cp37m-win_amd64.whl", hash = "sha256:db0a40d8bcd27b4fb46676e8eb3c732c67a5a5e6bfab8927028224fbced0b40b"}, - {file = "pendulum-2.1.2-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:f5e236e7730cab1644e1b87aca3d2ff3e375a608542e90fe25685dae46310116"}, - {file = "pendulum-2.1.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:de42ea3e2943171a9e95141f2eecf972480636e8e484ccffaf1e833929e9e052"}, - {file = "pendulum-2.1.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7c5ec650cb4bec4c63a89a0242cc8c3cebcec92fcfe937c417ba18277d8560be"}, - {file = "pendulum-2.1.2-cp38-cp38-win_amd64.whl", hash = "sha256:33fb61601083f3eb1d15edeb45274f73c63b3c44a8524703dc143f4212bf3269"}, - {file = "pendulum-2.1.2-cp39-cp39-manylinux1_i686.whl", hash = "sha256:29c40a6f2942376185728c9a0347d7c0f07905638c83007e1d262781f1e6953a"}, - {file = "pendulum-2.1.2-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:94b1fc947bfe38579b28e1cccb36f7e28a15e841f30384b5ad6c5e31055c85d7"}, - {file = "pendulum-2.1.2.tar.gz", hash = "sha256:b06a0ca1bfe41c990bbf0c029f0b6501a7f2ec4e38bfec730712015e8860f207"}, -] - -[package.dependencies] -python-dateutil = ">=2.6,<3.0" -pytzdata = ">=2020.1" - -[[package]] -name = "pendulum" -version = "3.0.0" -description = "Python datetimes made easy" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pendulum-3.0.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2cf9e53ef11668e07f73190c805dbdf07a1939c3298b78d5a9203a86775d1bfd"}, - {file = "pendulum-3.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fb551b9b5e6059377889d2d878d940fd0bbb80ae4810543db18e6f77b02c5ef6"}, - {file = "pendulum-3.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c58227ac260d5b01fc1025176d7b31858c9f62595737f350d22124a9a3ad82d"}, - {file = "pendulum-3.0.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:60fb6f415fea93a11c52578eaa10594568a6716602be8430b167eb0d730f3332"}, - {file = "pendulum-3.0.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b69f6b4dbcb86f2c2fe696ba991e67347bcf87fe601362a1aba6431454b46bde"}, - {file = "pendulum-3.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:138afa9c373ee450ede206db5a5e9004fd3011b3c6bbe1e57015395cd076a09f"}, - {file = "pendulum-3.0.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:83d9031f39c6da9677164241fd0d37fbfc9dc8ade7043b5d6d62f56e81af8ad2"}, - {file = "pendulum-3.0.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0c2308af4033fa534f089595bcd40a95a39988ce4059ccd3dc6acb9ef14ca44a"}, - {file = "pendulum-3.0.0-cp310-none-win_amd64.whl", hash = "sha256:9a59637cdb8462bdf2dbcb9d389518c0263799189d773ad5c11db6b13064fa79"}, - {file = "pendulum-3.0.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:3725245c0352c95d6ca297193192020d1b0c0f83d5ee6bb09964edc2b5a2d508"}, - {file = "pendulum-3.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6c035f03a3e565ed132927e2c1b691de0dbf4eb53b02a5a3c5a97e1a64e17bec"}, - {file = "pendulum-3.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:597e66e63cbd68dd6d58ac46cb7a92363d2088d37ccde2dae4332ef23e95cd00"}, - {file = "pendulum-3.0.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:99a0f8172e19f3f0c0e4ace0ad1595134d5243cf75985dc2233e8f9e8de263ca"}, - {file = "pendulum-3.0.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:77d8839e20f54706aed425bec82a83b4aec74db07f26acd039905d1237a5e1d4"}, - {file = "pendulum-3.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afde30e8146292b059020fbc8b6f8fd4a60ae7c5e6f0afef937bbb24880bdf01"}, - {file = "pendulum-3.0.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:660434a6fcf6303c4efd36713ca9212c753140107ee169a3fc6c49c4711c2a05"}, - {file = "pendulum-3.0.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:dee9e5a48c6999dc1106eb7eea3e3a50e98a50651b72c08a87ee2154e544b33e"}, - {file = "pendulum-3.0.0-cp311-none-win_amd64.whl", hash = "sha256:d4cdecde90aec2d67cebe4042fd2a87a4441cc02152ed7ed8fb3ebb110b94ec4"}, - {file = "pendulum-3.0.0-cp311-none-win_arm64.whl", hash = "sha256:773c3bc4ddda2dda9f1b9d51fe06762f9200f3293d75c4660c19b2614b991d83"}, - {file = "pendulum-3.0.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:409e64e41418c49f973d43a28afe5df1df4f1dd87c41c7c90f1a63f61ae0f1f7"}, - {file = "pendulum-3.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a38ad2121c5ec7c4c190c7334e789c3b4624798859156b138fcc4d92295835dc"}, - {file = "pendulum-3.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fde4d0b2024b9785f66b7f30ed59281bd60d63d9213cda0eb0910ead777f6d37"}, - {file = "pendulum-3.0.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4b2c5675769fb6d4c11238132962939b960fcb365436b6d623c5864287faa319"}, - {file = "pendulum-3.0.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8af95e03e066826f0f4c65811cbee1b3123d4a45a1c3a2b4fc23c4b0dff893b5"}, - {file = "pendulum-3.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2165a8f33cb15e06c67070b8afc87a62b85c5a273e3aaa6bc9d15c93a4920d6f"}, - {file = "pendulum-3.0.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ad5e65b874b5e56bd942546ea7ba9dd1d6a25121db1c517700f1c9de91b28518"}, - {file = "pendulum-3.0.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:17fe4b2c844bbf5f0ece69cfd959fa02957c61317b2161763950d88fed8e13b9"}, - {file = "pendulum-3.0.0-cp312-none-win_amd64.whl", hash = "sha256:78f8f4e7efe5066aca24a7a57511b9c2119f5c2b5eb81c46ff9222ce11e0a7a5"}, - {file = "pendulum-3.0.0-cp312-none-win_arm64.whl", hash = "sha256:28f49d8d1e32aae9c284a90b6bb3873eee15ec6e1d9042edd611b22a94ac462f"}, - {file = "pendulum-3.0.0-cp37-cp37m-macosx_10_12_x86_64.whl", hash = "sha256:d4e2512f4e1a4670284a153b214db9719eb5d14ac55ada5b76cbdb8c5c00399d"}, - {file = "pendulum-3.0.0-cp37-cp37m-macosx_11_0_arm64.whl", hash = "sha256:3d897eb50883cc58d9b92f6405245f84b9286cd2de6e8694cb9ea5cb15195a32"}, - {file = "pendulum-3.0.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e169cc2ca419517f397811bbe4589cf3cd13fca6dc38bb352ba15ea90739ebb"}, - {file = "pendulum-3.0.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f17c3084a4524ebefd9255513692f7e7360e23c8853dc6f10c64cc184e1217ab"}, - {file = "pendulum-3.0.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:826d6e258052715f64d05ae0fc9040c0151e6a87aae7c109ba9a0ed930ce4000"}, - {file = "pendulum-3.0.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2aae97087872ef152a0c40e06100b3665d8cb86b59bc8471ca7c26132fccd0f"}, - {file = "pendulum-3.0.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:ac65eeec2250d03106b5e81284ad47f0d417ca299a45e89ccc69e36130ca8bc7"}, - {file = "pendulum-3.0.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:a5346d08f3f4a6e9e672187faa179c7bf9227897081d7121866358af369f44f9"}, - {file = "pendulum-3.0.0-cp37-none-win_amd64.whl", hash = "sha256:235d64e87946d8f95c796af34818c76e0f88c94d624c268693c85b723b698aa9"}, - {file = "pendulum-3.0.0-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:6a881d9c2a7f85bc9adafcfe671df5207f51f5715ae61f5d838b77a1356e8b7b"}, - {file = "pendulum-3.0.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:d7762d2076b9b1cb718a6631ad6c16c23fc3fac76cbb8c454e81e80be98daa34"}, - {file = "pendulum-3.0.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e8e36a8130819d97a479a0e7bf379b66b3b1b520e5dc46bd7eb14634338df8c"}, - {file = "pendulum-3.0.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7dc843253ac373358ffc0711960e2dd5b94ab67530a3e204d85c6e8cb2c5fa10"}, - {file = "pendulum-3.0.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0a78ad3635d609ceb1e97d6aedef6a6a6f93433ddb2312888e668365908c7120"}, - {file = "pendulum-3.0.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b30a137e9e0d1f751e60e67d11fc67781a572db76b2296f7b4d44554761049d6"}, - {file = "pendulum-3.0.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:c95984037987f4a457bb760455d9ca80467be792236b69d0084f228a8ada0162"}, - {file = "pendulum-3.0.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d29c6e578fe0f893766c0d286adbf0b3c726a4e2341eba0917ec79c50274ec16"}, - {file = "pendulum-3.0.0-cp38-none-win_amd64.whl", hash = "sha256:deaba8e16dbfcb3d7a6b5fabdd5a38b7c982809567479987b9c89572df62e027"}, - {file = "pendulum-3.0.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:b11aceea5b20b4b5382962b321dbc354af0defe35daa84e9ff3aae3c230df694"}, - {file = "pendulum-3.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a90d4d504e82ad236afac9adca4d6a19e4865f717034fc69bafb112c320dcc8f"}, - {file = "pendulum-3.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:825799c6b66e3734227756fa746cc34b3549c48693325b8b9f823cb7d21b19ac"}, - {file = "pendulum-3.0.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad769e98dc07972e24afe0cff8d365cb6f0ebc7e65620aa1976fcfbcadc4c6f3"}, - {file = "pendulum-3.0.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6fc26907eb5fb8cc6188cc620bc2075a6c534d981a2f045daa5f79dfe50d512"}, - {file = "pendulum-3.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c717eab1b6d898c00a3e0fa7781d615b5c5136bbd40abe82be100bb06df7a56"}, - {file = "pendulum-3.0.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:3ddd1d66d1a714ce43acfe337190be055cdc221d911fc886d5a3aae28e14b76d"}, - {file = "pendulum-3.0.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:822172853d7a9cf6da95d7b66a16c7160cb99ae6df55d44373888181d7a06edc"}, - {file = "pendulum-3.0.0-cp39-none-win_amd64.whl", hash = "sha256:840de1b49cf1ec54c225a2a6f4f0784d50bd47f68e41dc005b7f67c7d5b5f3ae"}, - {file = "pendulum-3.0.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3b1f74d1e6ffe5d01d6023870e2ce5c2191486928823196f8575dcc786e107b1"}, - {file = "pendulum-3.0.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:729e9f93756a2cdfa77d0fc82068346e9731c7e884097160603872686e570f07"}, - {file = "pendulum-3.0.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e586acc0b450cd21cbf0db6bae386237011b75260a3adceddc4be15334689a9a"}, - {file = "pendulum-3.0.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22e7944ffc1f0099a79ff468ee9630c73f8c7835cd76fdb57ef7320e6a409df4"}, - {file = "pendulum-3.0.0-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:fa30af36bd8e50686846bdace37cf6707bdd044e5cb6e1109acbad3277232e04"}, - {file = "pendulum-3.0.0-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:440215347b11914ae707981b9a57ab9c7b6983ab0babde07063c6ee75c0dc6e7"}, - {file = "pendulum-3.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:314c4038dc5e6a52991570f50edb2f08c339debdf8cea68ac355b32c4174e820"}, - {file = "pendulum-3.0.0-pp37-pypy37_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5acb1d386337415f74f4d1955c4ce8d0201978c162927d07df8eb0692b2d8533"}, - {file = "pendulum-3.0.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a789e12fbdefaffb7b8ac67f9d8f22ba17a3050ceaaa635cd1cc4645773a4b1e"}, - {file = "pendulum-3.0.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:860aa9b8a888e5913bd70d819306749e5eb488e6b99cd6c47beb701b22bdecf5"}, - {file = "pendulum-3.0.0-pp37-pypy37_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:5ebc65ea033ef0281368217fbf59f5cb05b338ac4dd23d60959c7afcd79a60a0"}, - {file = "pendulum-3.0.0-pp37-pypy37_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:d9fef18ab0386ef6a9ac7bad7e43ded42c83ff7ad412f950633854f90d59afa8"}, - {file = "pendulum-3.0.0-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:1c134ba2f0571d0b68b83f6972e2307a55a5a849e7dac8505c715c531d2a8795"}, - {file = "pendulum-3.0.0-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:385680812e7e18af200bb9b4a49777418c32422d05ad5a8eb85144c4a285907b"}, - {file = "pendulum-3.0.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9eec91cd87c59fb32ec49eb722f375bd58f4be790cae11c1b70fac3ee4f00da0"}, - {file = "pendulum-3.0.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4386bffeca23c4b69ad50a36211f75b35a4deb6210bdca112ac3043deb7e494a"}, - {file = "pendulum-3.0.0-pp38-pypy38_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:dfbcf1661d7146d7698da4b86e7f04814221081e9fe154183e34f4c5f5fa3bf8"}, - {file = "pendulum-3.0.0-pp38-pypy38_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:04a1094a5aa1daa34a6b57c865b25f691848c61583fb22722a4df5699f6bf74c"}, - {file = "pendulum-3.0.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:5b0ec85b9045bd49dd3a3493a5e7ddfd31c36a2a60da387c419fa04abcaecb23"}, - {file = "pendulum-3.0.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:0a15b90129765b705eb2039062a6daf4d22c4e28d1a54fa260892e8c3ae6e157"}, - {file = "pendulum-3.0.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:bb8f6d7acd67a67d6fedd361ad2958ff0539445ef51cbe8cd288db4306503cd0"}, - {file = "pendulum-3.0.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fd69b15374bef7e4b4440612915315cc42e8575fcda2a3d7586a0d88192d0c88"}, - {file = "pendulum-3.0.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc00f8110db6898360c53c812872662e077eaf9c75515d53ecc65d886eec209a"}, - {file = "pendulum-3.0.0-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:83a44e8b40655d0ba565a5c3d1365d27e3e6778ae2a05b69124db9e471255c4a"}, - {file = "pendulum-3.0.0-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:1a3604e9fbc06b788041b2a8b78f75c243021e0f512447806a6d37ee5214905d"}, - {file = "pendulum-3.0.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:92c307ae7accebd06cbae4729f0ba9fa724df5f7d91a0964b1b972a22baa482b"}, - {file = "pendulum-3.0.0.tar.gz", hash = "sha256:5d034998dea404ec31fae27af6b22cff1708f830a1ed7353be4d1019bb9f584e"}, -] - -[package.dependencies] -python-dateutil = ">=2.6" -tzdata = ">=2020.1" - -[package.extras] -test = ["time-machine (>=2.6.0)"] - -[[package]] -name = "py-buzz" -version = "4.1.0" -description = "\"That's not flying, it's falling with style\": Exceptions with extras" -optional = false -python-versions = ">=3.8,<4.0" -files = [ - {file = "py_buzz-4.1.0-py3-none-any.whl", hash = "sha256:77dc0dc9c9923b6f8079dc2e2c3b4fbebd2308acaca1500f8eda2711cd308f97"}, - {file = "py_buzz-4.1.0.tar.gz", hash = "sha256:ac11dba4922b2af114126044597d2fcd15e8c6a74368bed91f3c6732c8f09812"}, -] - -[[package]] -name = "pycares" -version = "4.4.0" -description = "Python interface for c-ares" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pycares-4.4.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:24da119850841d16996713d9c3374ca28a21deee056d609fbbed29065d17e1f6"}, - {file = "pycares-4.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8f64cb58729689d4d0e78f0bfb4c25ce2f851d0274c0273ac751795c04b8798a"}, - {file = "pycares-4.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d33e2a1120887e89075f7f814ec144f66a6ce06a54f5722ccefc62fbeda83cff"}, - {file = "pycares-4.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c680fef1b502ee680f8f0b95a41af4ec2c234e50e16c0af5bbda31999d3584bd"}, - {file = "pycares-4.4.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fff16b09042ba077f7b8aa5868d1d22456f0002574d0ba43462b10a009331677"}, - {file = "pycares-4.4.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:229a1675eb33bc9afb1fc463e73ee334950ccc485bc83a43f6ae5839fb4d5fa3"}, - {file = "pycares-4.4.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:3aebc73e5ad70464f998f77f2da2063aa617cbd8d3e8174dd7c5b4518f967153"}, - {file = "pycares-4.4.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6ef64649eba56448f65e26546d85c860709844d2fc22ef14d324fe0b27f761a9"}, - {file = "pycares-4.4.0-cp310-cp310-win32.whl", hash = "sha256:4afc2644423f4eef97857a9fd61be9758ce5e336b4b0bd3d591238bb4b8b03e0"}, - {file = "pycares-4.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:5ed4e04af4012f875b78219d34434a6d08a67175150ac1b79eb70ab585d4ba8c"}, - {file = "pycares-4.4.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:bce8db2fc6f3174bd39b81405210b9b88d7b607d33e56a970c34a0c190da0490"}, - {file = "pycares-4.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9a0303428d013ccf5c51de59c83f9127aba6200adb7fd4be57eddb432a1edd2a"}, - {file = "pycares-4.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afb91792f1556f97be7f7acb57dc7756d89c5a87bd8b90363a77dbf9ea653817"}, - {file = "pycares-4.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b61579cecf1f4d616e5ea31a6e423a16680ab0d3a24a2ffe7bb1d4ee162477ff"}, - {file = "pycares-4.4.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b7af06968cbf6851566e806bf3e72825b0e6671832a2cbe840be1d2d65350710"}, - {file = "pycares-4.4.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ceb12974367b0a68a05d52f4162b29f575d241bd53de155efe632bf2c943c7f6"}, - {file = "pycares-4.4.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:2eeec144bcf6a7b6f2d74d6e70cbba7886a84dd373c886f06cb137a07de4954c"}, - {file = "pycares-4.4.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e3a6f7cfdfd11eb5493d6d632e582408c8f3b429f295f8799c584c108b28db6f"}, - {file = "pycares-4.4.0-cp311-cp311-win32.whl", hash = "sha256:34736a2ffaa9c08ca9c707011a2d7b69074bbf82d645d8138bba771479b2362f"}, - {file = "pycares-4.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:eb66c30eb11e877976b7ead13632082a8621df648c408b8e15cdb91a452dd502"}, - {file = "pycares-4.4.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:fd644505a8cfd7f6584d33a9066d4e3d47700f050ef1490230c962de5dfb28c6"}, - {file = "pycares-4.4.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:52084961262232ec04bd75f5043aed7e5d8d9695e542ff691dfef0110209f2d4"}, - {file = "pycares-4.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0c5368206057884cde18602580083aeaad9b860e2eac14fd253543158ce1e93"}, - {file = "pycares-4.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:112a4979c695b1c86f6782163d7dec58d57a3b9510536dcf4826550f9053dd9a"}, - {file = "pycares-4.4.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8d186dafccdaa3409194c0f94db93c1a5d191145a275f19da6591f9499b8e7b8"}, - {file = "pycares-4.4.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:64965dc19c578a683ea73487a215a8897276224e004d50eeb21f0bc7a0b63c88"}, - {file = "pycares-4.4.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:ed2a38e34bec6f2586435f6ff0bc5fe11d14bebd7ed492cf739a424e81681540"}, - {file = "pycares-4.4.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:94d6962db81541eb0396d2f0dfcbb18cdb8c8b251d165efc2d974ae652c547d4"}, - {file = "pycares-4.4.0-cp312-cp312-win32.whl", hash = "sha256:1168a48a834813aa80f412be2df4abaf630528a58d15c704857448b20b1675c0"}, - {file = "pycares-4.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:db24c4e7fea4a052c6e869cbf387dd85d53b9736cfe1ef5d8d568d1ca925e977"}, - {file = "pycares-4.4.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:21a5a0468861ec7df7befa69050f952da13db5427ae41ffe4713bc96291d1d95"}, - {file = "pycares-4.4.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:22c00bf659a9fa44d7b405cf1cd69b68b9d37537899898d8cbe5dffa4016b273"}, - {file = "pycares-4.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23aa3993a352491a47fcf17867f61472f32f874df4adcbb486294bd9fbe8abee"}, - {file = "pycares-4.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:813d661cbe2e37d87da2d16b7110a6860e93ddb11735c6919c8a3545c7b9c8d8"}, - {file = "pycares-4.4.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:77cf5a2fd5583c670de41a7f4a7b46e5cbabe7180d8029f728571f4d2e864084"}, - {file = "pycares-4.4.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:3eaa6681c0a3e3f3868c77aca14b7760fed35fdfda2fe587e15c701950e7bc69"}, - {file = "pycares-4.4.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ad58e284a658a8a6a84af2e0b62f2f961f303cedfe551854d7bd40c3cbb61912"}, - {file = "pycares-4.4.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:bfb89ca9e3d0a9b5332deeb666b2ede9d3469107742158f4aeda5ce032d003f4"}, - {file = "pycares-4.4.0-cp38-cp38-win32.whl", hash = "sha256:f36bdc1562142e3695555d2f4ac0cb69af165eddcefa98efc1c79495b533481f"}, - {file = "pycares-4.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:902461a92b6a80fd5041a2ec5235680c7cc35e43615639ec2a40e63fca2dfb51"}, - {file = "pycares-4.4.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7bddc6adba8f699728f7fc1c9ce8cef359817ad78e2ed52b9502cb5f8dc7f741"}, - {file = "pycares-4.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:cb49d5805cd347c404f928c5ae7c35e86ba0c58ffa701dbe905365e77ce7d641"}, - {file = "pycares-4.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56cf3349fa3a2e67ed387a7974c11d233734636fe19facfcda261b411af14d80"}, - {file = "pycares-4.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8bf2eaa83a5987e48fa63302f0fe7ce3275cfda87b34d40fef9ce703fb3ac002"}, - {file = "pycares-4.4.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82bba2ab77eb5addbf9758d514d9bdef3c1bfe7d1649a47bd9a0d55a23ef478b"}, - {file = "pycares-4.4.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:c6a8bde63106f162fca736e842a916853cad3c8d9d137e11c9ffa37efa818b02"}, - {file = "pycares-4.4.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f5f646eec041db6ffdbcaf3e0756fb92018f7af3266138c756bb09d2b5baadec"}, - {file = "pycares-4.4.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9dc04c54c6ea615210c1b9e803d0e2d2255f87a3d5d119b6482c8f0dfa15b26b"}, - {file = "pycares-4.4.0-cp39-cp39-win32.whl", hash = "sha256:97892cced5794d721fb4ff8765764aa4ea48fe8b2c3820677505b96b83d4ef47"}, - {file = "pycares-4.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:917f08f0b5d9324e9a34211e68d27447c552b50ab967044776bbab7e42a553a2"}, - {file = "pycares-4.4.0.tar.gz", hash = "sha256:f47579d508f2f56eddd16ce72045782ad3b1b3b678098699e2b6a1b30733e1c2"}, -] - -[package.dependencies] -cffi = ">=1.5.0" - -[package.extras] -idna = ["idna (>=2.1)"] - -[[package]] -name = "pycparser" -version = "2.22" -description = "C parser in Python" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"}, - {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, -] - -[[package]] -name = "pycryptodomex" -version = "3.20.0" -description = "Cryptographic library for Python" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -files = [ - {file = "pycryptodomex-3.20.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:645bd4ca6f543685d643dadf6a856cc382b654cc923460e3a10a49c1b3832aeb"}, - {file = "pycryptodomex-3.20.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:ff5c9a67f8a4fba4aed887216e32cbc48f2a6fb2673bb10a99e43be463e15913"}, - {file = "pycryptodomex-3.20.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:8ee606964553c1a0bc74057dd8782a37d1c2bc0f01b83193b6f8bb14523b877b"}, - {file = "pycryptodomex-3.20.0-cp27-cp27m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7805830e0c56d88f4d491fa5ac640dfc894c5ec570d1ece6ed1546e9df2e98d6"}, - {file = "pycryptodomex-3.20.0-cp27-cp27m-musllinux_1_1_aarch64.whl", hash = "sha256:bc3ee1b4d97081260d92ae813a83de4d2653206967c4a0a017580f8b9548ddbc"}, - {file = "pycryptodomex-3.20.0-cp27-cp27m-win32.whl", hash = "sha256:8af1a451ff9e123d0d8bd5d5e60f8e3315c3a64f3cdd6bc853e26090e195cdc8"}, - {file = "pycryptodomex-3.20.0-cp27-cp27m-win_amd64.whl", hash = "sha256:cbe71b6712429650e3883dc81286edb94c328ffcd24849accac0a4dbcc76958a"}, - {file = "pycryptodomex-3.20.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:76bd15bb65c14900d98835fcd10f59e5e0435077431d3a394b60b15864fddd64"}, - {file = "pycryptodomex-3.20.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:653b29b0819605fe0898829c8ad6400a6ccde096146730c2da54eede9b7b8baa"}, - {file = "pycryptodomex-3.20.0-cp27-cp27mu-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62a5ec91388984909bb5398ea49ee61b68ecb579123694bffa172c3b0a107079"}, - {file = "pycryptodomex-3.20.0-cp27-cp27mu-musllinux_1_1_aarch64.whl", hash = "sha256:108e5f1c1cd70ffce0b68739c75734437c919d2eaec8e85bffc2c8b4d2794305"}, - {file = "pycryptodomex-3.20.0-cp35-abi3-macosx_10_9_universal2.whl", hash = "sha256:59af01efb011b0e8b686ba7758d59cf4a8263f9ad35911bfe3f416cee4f5c08c"}, - {file = "pycryptodomex-3.20.0-cp35-abi3-macosx_10_9_x86_64.whl", hash = "sha256:82ee7696ed8eb9a82c7037f32ba9b7c59e51dda6f105b39f043b6ef293989cb3"}, - {file = "pycryptodomex-3.20.0-cp35-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:91852d4480a4537d169c29a9d104dda44094c78f1f5b67bca76c29a91042b623"}, - {file = "pycryptodomex-3.20.0-cp35-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bca649483d5ed251d06daf25957f802e44e6bb6df2e8f218ae71968ff8f8edc4"}, - {file = "pycryptodomex-3.20.0-cp35-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e186342cfcc3aafaad565cbd496060e5a614b441cacc3995ef0091115c1f6c5"}, - {file = "pycryptodomex-3.20.0-cp35-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:25cd61e846aaab76d5791d006497134602a9e451e954833018161befc3b5b9ed"}, - {file = "pycryptodomex-3.20.0-cp35-abi3-musllinux_1_1_i686.whl", hash = "sha256:9c682436c359b5ada67e882fec34689726a09c461efd75b6ea77b2403d5665b7"}, - {file = "pycryptodomex-3.20.0-cp35-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:7a7a8f33a1f1fb762ede6cc9cbab8f2a9ba13b196bfaf7bc6f0b39d2ba315a43"}, - {file = "pycryptodomex-3.20.0-cp35-abi3-win32.whl", hash = "sha256:c39778fd0548d78917b61f03c1fa8bfda6cfcf98c767decf360945fe6f97461e"}, - {file = "pycryptodomex-3.20.0-cp35-abi3-win_amd64.whl", hash = "sha256:2a47bcc478741b71273b917232f521fd5704ab4b25d301669879e7273d3586cc"}, - {file = "pycryptodomex-3.20.0-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:1be97461c439a6af4fe1cf8bf6ca5936d3db252737d2f379cc6b2e394e12a458"}, - {file = "pycryptodomex-3.20.0-pp27-pypy_73-win32.whl", hash = "sha256:19764605feea0df966445d46533729b645033f134baeb3ea26ad518c9fdf212c"}, - {file = "pycryptodomex-3.20.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:f2e497413560e03421484189a6b65e33fe800d3bd75590e6d78d4dfdb7accf3b"}, - {file = "pycryptodomex-3.20.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e48217c7901edd95f9f097feaa0388da215ed14ce2ece803d3f300b4e694abea"}, - {file = "pycryptodomex-3.20.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d00fe8596e1cc46b44bf3907354e9377aa030ec4cd04afbbf6e899fc1e2a7781"}, - {file = "pycryptodomex-3.20.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:88afd7a3af7ddddd42c2deda43d53d3dfc016c11327d0915f90ca34ebda91499"}, - {file = "pycryptodomex-3.20.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d3584623e68a5064a04748fb6d76117a21a7cb5eaba20608a41c7d0c61721794"}, - {file = "pycryptodomex-3.20.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0daad007b685db36d977f9de73f61f8da2a7104e20aca3effd30752fd56f73e1"}, - {file = "pycryptodomex-3.20.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5dcac11031a71348faaed1f403a0debd56bf5404232284cf8c761ff918886ebc"}, - {file = "pycryptodomex-3.20.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:69138068268127cd605e03438312d8f271135a33140e2742b417d027a0539427"}, - {file = "pycryptodomex-3.20.0.tar.gz", hash = "sha256:7a710b79baddd65b806402e14766c721aee8fb83381769c27920f26476276c1e"}, -] - -[[package]] -name = "pydantic" -version = "1.10.15" -description = "Data validation and settings management using python type hints" -optional = false -python-versions = ">=3.7" -files = [ - {file = "pydantic-1.10.15-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:22ed12ee588b1df028a2aa5d66f07bf8f8b4c8579c2e96d5a9c1f96b77f3bb55"}, - {file = "pydantic-1.10.15-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:75279d3cac98186b6ebc2597b06bcbc7244744f6b0b44a23e4ef01e5683cc0d2"}, - {file = "pydantic-1.10.15-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50f1666a9940d3d68683c9d96e39640f709d7a72ff8702987dab1761036206bb"}, - {file = "pydantic-1.10.15-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82790d4753ee5d00739d6cb5cf56bceb186d9d6ce134aca3ba7befb1eedbc2c8"}, - {file = "pydantic-1.10.15-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:d207d5b87f6cbefbdb1198154292faee8017d7495a54ae58db06762004500d00"}, - {file = "pydantic-1.10.15-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e49db944fad339b2ccb80128ffd3f8af076f9f287197a480bf1e4ca053a866f0"}, - {file = "pydantic-1.10.15-cp310-cp310-win_amd64.whl", hash = "sha256:d3b5c4cbd0c9cb61bbbb19ce335e1f8ab87a811f6d589ed52b0254cf585d709c"}, - {file = "pydantic-1.10.15-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c3d5731a120752248844676bf92f25a12f6e45425e63ce22e0849297a093b5b0"}, - {file = "pydantic-1.10.15-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c365ad9c394f9eeffcb30a82f4246c0006417f03a7c0f8315d6211f25f7cb654"}, - {file = "pydantic-1.10.15-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3287e1614393119c67bd4404f46e33ae3be3ed4cd10360b48d0a4459f420c6a3"}, - {file = "pydantic-1.10.15-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:be51dd2c8596b25fe43c0a4a59c2bee4f18d88efb8031188f9e7ddc6b469cf44"}, - {file = "pydantic-1.10.15-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:6a51a1dd4aa7b3f1317f65493a182d3cff708385327c1c82c81e4a9d6d65b2e4"}, - {file = "pydantic-1.10.15-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4e316e54b5775d1eb59187f9290aeb38acf620e10f7fd2f776d97bb788199e53"}, - {file = "pydantic-1.10.15-cp311-cp311-win_amd64.whl", hash = "sha256:0d142fa1b8f2f0ae11ddd5e3e317dcac060b951d605fda26ca9b234b92214986"}, - {file = "pydantic-1.10.15-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7ea210336b891f5ea334f8fc9f8f862b87acd5d4a0cbc9e3e208e7aa1775dabf"}, - {file = "pydantic-1.10.15-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3453685ccd7140715e05f2193d64030101eaad26076fad4e246c1cc97e1bb30d"}, - {file = "pydantic-1.10.15-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bea1f03b8d4e8e86702c918ccfd5d947ac268f0f0cc6ed71782e4b09353b26f"}, - {file = "pydantic-1.10.15-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:005655cabc29081de8243126e036f2065bd7ea5b9dff95fde6d2c642d39755de"}, - {file = "pydantic-1.10.15-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:af9850d98fc21e5bc24ea9e35dd80a29faf6462c608728a110c0a30b595e58b7"}, - {file = "pydantic-1.10.15-cp37-cp37m-win_amd64.whl", hash = "sha256:d31ee5b14a82c9afe2bd26aaa405293d4237d0591527d9129ce36e58f19f95c1"}, - {file = "pydantic-1.10.15-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5e09c19df304b8123938dc3c53d3d3be6ec74b9d7d0d80f4f4b5432ae16c2022"}, - {file = "pydantic-1.10.15-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7ac9237cd62947db00a0d16acf2f3e00d1ae9d3bd602b9c415f93e7a9fc10528"}, - {file = "pydantic-1.10.15-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:584f2d4c98ffec420e02305cf675857bae03c9d617fcfdc34946b1160213a948"}, - {file = "pydantic-1.10.15-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bbc6989fad0c030bd70a0b6f626f98a862224bc2b1e36bfc531ea2facc0a340c"}, - {file = "pydantic-1.10.15-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:d573082c6ef99336f2cb5b667b781d2f776d4af311574fb53d908517ba523c22"}, - {file = "pydantic-1.10.15-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6bd7030c9abc80134087d8b6e7aa957e43d35714daa116aced57269a445b8f7b"}, - {file = "pydantic-1.10.15-cp38-cp38-win_amd64.whl", hash = "sha256:3350f527bb04138f8aff932dc828f154847fbdc7a1a44c240fbfff1b57f49a12"}, - {file = "pydantic-1.10.15-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:51d405b42f1b86703555797270e4970a9f9bd7953f3990142e69d1037f9d9e51"}, - {file = "pydantic-1.10.15-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a980a77c52723b0dc56640ced396b73a024d4b74f02bcb2d21dbbac1debbe9d0"}, - {file = "pydantic-1.10.15-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67f1a1fb467d3f49e1708a3f632b11c69fccb4e748a325d5a491ddc7b5d22383"}, - {file = "pydantic-1.10.15-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:676ed48f2c5bbad835f1a8ed8a6d44c1cd5a21121116d2ac40bd1cd3619746ed"}, - {file = "pydantic-1.10.15-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:92229f73400b80c13afcd050687f4d7e88de9234d74b27e6728aa689abcf58cc"}, - {file = "pydantic-1.10.15-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2746189100c646682eff0bce95efa7d2e203420d8e1c613dc0c6b4c1d9c1fde4"}, - {file = "pydantic-1.10.15-cp39-cp39-win_amd64.whl", hash = "sha256:394f08750bd8eaad714718812e7fab615f873b3cdd0b9d84e76e51ef3b50b6b7"}, - {file = "pydantic-1.10.15-py3-none-any.whl", hash = "sha256:28e552a060ba2740d0d2aabe35162652c1459a0b9069fe0db7f4ee0e18e74d58"}, - {file = "pydantic-1.10.15.tar.gz", hash = "sha256:ca832e124eda231a60a041da4f013e3ff24949d94a01154b137fc2f2a43c3ffb"}, -] - -[package.dependencies] -typing-extensions = ">=4.2.0" - -[package.extras] -dotenv = ["python-dotenv (>=0.10.4)"] -email = ["email-validator (>=1.0.3)"] - -[[package]] -name = "pyjwt" -version = "2.8.0" -description = "JSON Web Token implementation in Python" -optional = false -python-versions = ">=3.7" -files = [ - {file = "PyJWT-2.8.0-py3-none-any.whl", hash = "sha256:59127c392cc44c2da5bb3192169a91f429924e17aff6534d70fdc02ab3e04320"}, - {file = "PyJWT-2.8.0.tar.gz", hash = "sha256:57e28d156e3d5c10088e0c68abb90bfac3df82b40a71bd0daa20c65ccd5c23de"}, -] - -[package.extras] -crypto = ["cryptography (>=3.4.0)"] -dev = ["coverage[toml] (==5.0.4)", "cryptography (>=3.4.0)", "pre-commit", "pytest (>=6.0.0,<7.0.0)", "sphinx (>=4.5.0,<5.0.0)", "sphinx-rtd-theme", "zope.interface"] -docs = ["sphinx (>=4.5.0,<5.0.0)", "sphinx-rtd-theme", "zope.interface"] -tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"] - -[[package]] -name = "pyseto" -version = "1.7.7" -description = "A Python implementation of PASETO/PASERK." -optional = false -python-versions = ">=3.8,<4.0" -files = [ - {file = "pyseto-1.7.7-py3-none-any.whl", hash = "sha256:05dbb906a0b27bba22e28fc4289b7a7743b1d820fa2afaa79a25f6cef945014d"}, - {file = "pyseto-1.7.7.tar.gz", hash = "sha256:c0f1fe56c529f7001e93b6f646d4a47469ecee45df3fa749d467e8721f5b0f6e"}, -] - -[package.dependencies] -argon2-cffi = ">=23.1.0,<24.0.0" -cryptography = ">=41.0.5,<42" -iso8601 = ">=1.0.2,<3.0.0" -pycryptodomex = ">=3.18.0,<4.0.0" - -[package.extras] -docs = ["Sphinx[docs] (>=6,<8)", "sphinx-autodoc-typehints[docs] (>=1.21.0,<2.0.0)", "sphinx-rtd-theme[docs] (>=1.2.1,<2.0.0)"] - -[[package]] -name = "python-dateutil" -version = "2.9.0.post0" -description = "Extensions to the standard Python datetime module" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -files = [ - {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, - {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, -] - -[package.dependencies] -six = ">=1.5" - -[[package]] -name = "pytzdata" -version = "2020.1" -description = "The Olson timezone database for Python." -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -files = [ - {file = "pytzdata-2020.1-py2.py3-none-any.whl", hash = "sha256:e1e14750bcf95016381e4d472bad004eef710f2d6417240904070b3d6654485f"}, - {file = "pytzdata-2020.1.tar.gz", hash = "sha256:3efa13b335a00a8de1d345ae41ec78dd11c9f8807f522d39850f2dd828681540"}, -] - -[[package]] -name = "pyyaml" -version = "6.0.1" -description = "YAML parser and emitter for Python" -optional = false -python-versions = ">=3.6" -files = [ - {file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"}, - {file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"}, - {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, - {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, - {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, - {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, - {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, - {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, - {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, - {file = "PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab"}, - {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, - {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, - {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, - {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, - {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, - {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, - {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, - {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, - {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, - {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, - {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, - {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, - {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, - {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, - {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, - {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, - {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd"}, - {file = "PyYAML-6.0.1-cp36-cp36m-win32.whl", hash = "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585"}, - {file = "PyYAML-6.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa"}, - {file = "PyYAML-6.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3"}, - {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27"}, - {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3"}, - {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c"}, - {file = "PyYAML-6.0.1-cp37-cp37m-win32.whl", hash = "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba"}, - {file = "PyYAML-6.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867"}, - {file = "PyYAML-6.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595"}, - {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, - {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, - {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, - {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, - {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, - {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, - {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, - {file = "PyYAML-6.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859"}, - {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, - {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, - {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, - {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, - {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, - {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, - {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, -] - -[[package]] -name = "sanic" -version = "23.12.1" -description = "A web server and web framework that's written to go fast. Build fast. Run fast." -optional = false -python-versions = ">=3.8" -files = [ - {file = "sanic-23.12.1-py3-none-any.whl", hash = "sha256:e292293b2663a7afeb380bdc48ab93978468b27deae46ad9561513941eb0311f"}, - {file = "sanic-23.12.1.tar.gz", hash = "sha256:2528ca81d2bdc58ea67d93c500df1a9c58404904b0bc3442425b464c72b4bb84"}, -] - -[package.dependencies] -aiofiles = ">=0.6.0" -html5tagger = ">=1.2.1" -httptools = ">=0.0.10" -multidict = ">=5.0,<7.0" -sanic-ext = {version = "*", optional = true, markers = "extra == \"ext\""} -sanic-routing = ">=23.12.0" -tracerite = ">=1.0.0" -typing-extensions = ">=4.4.0" -ujson = {version = ">=1.35", markers = "sys_platform != \"win32\" and implementation_name == \"cpython\""} -uvloop = {version = ">=0.15.0", markers = "sys_platform != \"win32\" and implementation_name == \"cpython\""} -websockets = ">=10.0" - -[package.extras] -all = ["autodocsumm (>=0.2.11)", "bandit", "beautifulsoup4", "chardet (==3.*)", "coverage", "cryptography", "docutils", "enum-tools[sphinx]", "m2r2", "mistune (<2.0.0)", "mypy", "pygments", "pytest (==7.1.*)", "pytest-benchmark", "pytest-sanic", "ruff", "sanic-testing (>=23.6.0)", "slotscheck (>=0.8.0,<1)", "sphinx (>=2.1.2)", "sphinx-rtd-theme (>=0.4.3)", "towncrier", "tox", "types-ujson", "uvicorn (<0.15.0)"] -dev = ["bandit", "beautifulsoup4", "chardet (==3.*)", "coverage", "cryptography", "docutils", "mypy", "pygments", "pytest (==7.1.*)", "pytest-benchmark", "pytest-sanic", "ruff", "sanic-testing (>=23.6.0)", "slotscheck (>=0.8.0,<1)", "towncrier", "tox", "types-ujson", "uvicorn (<0.15.0)"] -docs = ["autodocsumm (>=0.2.11)", "docutils", "enum-tools[sphinx]", "m2r2", "mistune (<2.0.0)", "pygments", "sphinx (>=2.1.2)", "sphinx-rtd-theme (>=0.4.3)"] -ext = ["sanic-ext"] -http3 = ["aioquic"] -test = ["bandit", "beautifulsoup4", "chardet (==3.*)", "coverage", "docutils", "mypy", "pygments", "pytest (==7.1.*)", "pytest-benchmark", "pytest-sanic", "ruff", "sanic-testing (>=23.6.0)", "slotscheck (>=0.8.0,<1)", "types-ujson", "uvicorn (<0.15.0)"] - -[[package]] -name = "sanic-beskar" -version = "2.3.3.post1" -description = "Strong, Simple, (now async!) and Precise security for Sanic APIs" -optional = false -python-versions = ">=3.9,<4.0" -files = [ - {file = "sanic_beskar-2.3.3.post1-py3-none-any.whl", hash = "sha256:25b25fcc8dbd9d5214ada9fd613d6d41f02a76f001e919648b122bf4c6689bb1"}, - {file = "sanic_beskar-2.3.3.post1.tar.gz", hash = "sha256:31a5a09b943772555634eb627d97e3e374160fdcfe5dde57dabc4f5e3e9ea012"}, -] - -[package.dependencies] -cryptography = ">=39.0.0" -jinja2 = ">=3.1.2" -passlib = ">=1.7" -pendulum = [ - {version = ">=2.1,<3.0", markers = "python_version >= \"3.9\" and python_version < \"3.12\""}, - {version = "==3.0.*", markers = "python_version >= \"3.12\""}, -] -py-buzz = ">3.2.0" -pyjwt = ">=2.6" -pyseto = ">=1.6.9" -sanic = ">=22.6.0" - -[[package]] -name = "sanic-ext" -version = "23.12.0" -description = "Extend your Sanic installation with some core functionality." -optional = false -python-versions = "*" -files = [ - {file = "sanic-ext-23.12.0.tar.gz", hash = "sha256:42fc41e7fafa58f3b790f685f3dd8a8de281460b4169d0e91f4e11b8747f845c"}, - {file = "sanic_ext-23.12.0-py3-none-any.whl", hash = "sha256:3ba2c143d7c41d89b87a11c6214b9d9b52c3994ff8ce3a03792b54ec5627e2c3"}, -] - -[package.dependencies] -pyyaml = ">=3.0.0" - -[package.extras] -dev = ["Jinja2", "black (>=21.4b2)", "coverage", "flake8 (>=3.7.7)", "isort (>=5.0.0)", "pytest", "pytest-asyncio", "pytest-cov", "sanic-testing (>=22.9.0)", "tox"] -test = ["Jinja2", "coverage", "pytest", "pytest-asyncio", "pytest-cov", "sanic-testing (>=22.9.0)", "tox"] - -[[package]] -name = "sanic_limiter" -version = "0.1.4" -description = "Provides rate limiting features for Sanic. Supports in-memory, redis and memcache as storage." -optional = false -python-versions = "*" -files = [] -develop = false - -[package.dependencies] -limits = ">=1.2.1" -sanic = ">=0.4.0" -six = ">=1.4.1" - -[package.source] -type = "git" -url = "https://github.com/Omegastick/sanic-limiter" -reference = "HEAD" -resolved_reference = "843e13144aa21d843ce212a7c1db31b72ce8a103" - -[[package]] -name = "sanic-routing" -version = "23.12.0" -description = "Core routing component for Sanic" -optional = false -python-versions = "*" -files = [ - {file = "sanic-routing-23.12.0.tar.gz", hash = "sha256:1dcadc62c443e48c852392dba03603f9862b6197fc4cba5bbefeb1ace0848b04"}, - {file = "sanic_routing-23.12.0-py3-none-any.whl", hash = "sha256:1558a72afcb9046ed3134a5edae02fc1552cff08f0fff2e8d5de0877ea43ed73"}, -] - -[[package]] -name = "sentry-sdk" -version = "1.44.1" -description = "Python client for Sentry (https://sentry.io)" -optional = false -python-versions = "*" -files = [ - {file = "sentry-sdk-1.44.1.tar.gz", hash = "sha256:24e6a53eeabffd2f95d952aa35ca52f0f4201d17f820ac9d3ff7244c665aaf68"}, - {file = "sentry_sdk-1.44.1-py2.py3-none-any.whl", hash = "sha256:5f75eb91d8ab6037c754a87b8501cc581b2827e923682f593bed3539ce5b3999"}, -] - -[package.dependencies] -certifi = "*" -sanic = {version = ">=0.8", optional = true, markers = "extra == \"sanic\""} -urllib3 = {version = ">=1.26.11", markers = "python_version >= \"3.6\""} - -[package.extras] -aiohttp = ["aiohttp (>=3.5)"] -arq = ["arq (>=0.23)"] -asyncpg = ["asyncpg (>=0.23)"] -beam = ["apache-beam (>=2.12)"] -bottle = ["bottle (>=0.12.13)"] -celery = ["celery (>=3)"] -celery-redbeat = ["celery-redbeat (>=2)"] -chalice = ["chalice (>=1.16.0)"] -clickhouse-driver = ["clickhouse-driver (>=0.2.0)"] -django = ["django (>=1.8)"] -falcon = ["falcon (>=1.4)"] -fastapi = ["fastapi (>=0.79.0)"] -flask = ["blinker (>=1.1)", "flask (>=0.11)", "markupsafe"] -grpcio = ["grpcio (>=1.21.1)"] -httpx = ["httpx (>=0.16.0)"] -huey = ["huey (>=2)"] -loguru = ["loguru (>=0.5)"] -openai = ["openai (>=1.0.0)", "tiktoken (>=0.3.0)"] -opentelemetry = ["opentelemetry-distro (>=0.35b0)"] -opentelemetry-experimental = ["opentelemetry-distro (>=0.40b0,<1.0)", "opentelemetry-instrumentation-aiohttp-client (>=0.40b0,<1.0)", "opentelemetry-instrumentation-django (>=0.40b0,<1.0)", "opentelemetry-instrumentation-fastapi (>=0.40b0,<1.0)", "opentelemetry-instrumentation-flask (>=0.40b0,<1.0)", "opentelemetry-instrumentation-requests (>=0.40b0,<1.0)", "opentelemetry-instrumentation-sqlite3 (>=0.40b0,<1.0)", "opentelemetry-instrumentation-urllib (>=0.40b0,<1.0)"] -pure-eval = ["asttokens", "executing", "pure-eval"] -pymongo = ["pymongo (>=3.1)"] -pyspark = ["pyspark (>=2.4.4)"] -quart = ["blinker (>=1.1)", "quart (>=0.16.1)"] -rq = ["rq (>=0.6)"] -sanic = ["sanic (>=0.8)"] -sqlalchemy = ["sqlalchemy (>=1.2)"] -starlette = ["starlette (>=0.19.1)"] -starlite = ["starlite (>=1.48)"] -tornado = ["tornado (>=5)"] - -[[package]] -name = "setuptools" -version = "69.2.0" -description = "Easily download, build, install, upgrade, and uninstall Python packages" -optional = false -python-versions = ">=3.8" -files = [ - {file = "setuptools-69.2.0-py3-none-any.whl", hash = "sha256:c21c49fb1042386df081cb5d86759792ab89efca84cf114889191cd09aacc80c"}, - {file = "setuptools-69.2.0.tar.gz", hash = "sha256:0ff4183f8f42cd8fa3acea16c45205521a4ef28f73c6391d8a25e92893134f2e"}, -] - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] -testing = ["build[virtualenv]", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "mypy (==1.9)", "packaging (>=23.2)", "pip (>=19.1)", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff (>=0.2.1)", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] -testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.2)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] - -[[package]] -name = "six" -version = "1.16.0" -description = "Python 2 and 3 compatibility utilities" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" -files = [ - {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, - {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, -] - -[[package]] -name = "soupsieve" -version = "2.5" -description = "A modern CSS selector implementation for Beautiful Soup." -optional = false -python-versions = ">=3.8" -files = [ - {file = "soupsieve-2.5-py3-none-any.whl", hash = "sha256:eaa337ff55a1579b6549dc679565eac1e3d000563bcb1c8ab0d0fefbc0c2cdc7"}, - {file = "soupsieve-2.5.tar.gz", hash = "sha256:5663d5a7b3bfaeee0bc4372e7fc48f9cff4940b3eec54a6451cc5299f1097690"}, -] - -[[package]] -name = "sqlalchemy" -version = "2.0.29" -description = "Database Abstraction Library" -optional = false -python-versions = ">=3.7" -files = [ - {file = "SQLAlchemy-2.0.29-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4c142852ae192e9fe5aad5c350ea6befe9db14370b34047e1f0f7cf99e63c63b"}, - {file = "SQLAlchemy-2.0.29-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:99a1e69d4e26f71e750e9ad6fdc8614fbddb67cfe2173a3628a2566034e223c7"}, - {file = "SQLAlchemy-2.0.29-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ef3fbccb4058355053c51b82fd3501a6e13dd808c8d8cd2561e610c5456013c"}, - {file = "SQLAlchemy-2.0.29-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d6753305936eddc8ed190e006b7bb33a8f50b9854823485eed3a886857ab8d1"}, - {file = "SQLAlchemy-2.0.29-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0f3ca96af060a5250a8ad5a63699180bc780c2edf8abf96c58af175921df847a"}, - {file = "SQLAlchemy-2.0.29-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c4520047006b1d3f0d89e0532978c0688219857eb2fee7c48052560ae76aca1e"}, - {file = "SQLAlchemy-2.0.29-cp310-cp310-win32.whl", hash = "sha256:b2a0e3cf0caac2085ff172c3faacd1e00c376e6884b5bc4dd5b6b84623e29e4f"}, - {file = "SQLAlchemy-2.0.29-cp310-cp310-win_amd64.whl", hash = "sha256:01d10638a37460616708062a40c7b55f73e4d35eaa146781c683e0fa7f6c43fb"}, - {file = "SQLAlchemy-2.0.29-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:308ef9cb41d099099fffc9d35781638986870b29f744382904bf9c7dadd08513"}, - {file = "SQLAlchemy-2.0.29-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:296195df68326a48385e7a96e877bc19aa210e485fa381c5246bc0234c36c78e"}, - {file = "SQLAlchemy-2.0.29-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a13b917b4ffe5a0a31b83d051d60477819ddf18276852ea68037a144a506efb9"}, - {file = "SQLAlchemy-2.0.29-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f6d971255d9ddbd3189e2e79d743ff4845c07f0633adfd1de3f63d930dbe673"}, - {file = "SQLAlchemy-2.0.29-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:61405ea2d563407d316c63a7b5271ae5d274a2a9fbcd01b0aa5503635699fa1e"}, - {file = "SQLAlchemy-2.0.29-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:de7202ffe4d4a8c1e3cde1c03e01c1a3772c92858837e8f3879b497158e4cb44"}, - {file = "SQLAlchemy-2.0.29-cp311-cp311-win32.whl", hash = "sha256:b5d7ed79df55a731749ce65ec20d666d82b185fa4898430b17cb90c892741520"}, - {file = "SQLAlchemy-2.0.29-cp311-cp311-win_amd64.whl", hash = "sha256:205f5a2b39d7c380cbc3b5dcc8f2762fb5bcb716838e2d26ccbc54330775b003"}, - {file = "SQLAlchemy-2.0.29-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d96710d834a6fb31e21381c6d7b76ec729bd08c75a25a5184b1089141356171f"}, - {file = "SQLAlchemy-2.0.29-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:52de4736404e53c5c6a91ef2698c01e52333988ebdc218f14c833237a0804f1b"}, - {file = "SQLAlchemy-2.0.29-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c7b02525ede2a164c5fa5014915ba3591730f2cc831f5be9ff3b7fd3e30958e"}, - {file = "SQLAlchemy-2.0.29-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0dfefdb3e54cd15f5d56fd5ae32f1da2d95d78319c1f6dfb9bcd0eb15d603d5d"}, - {file = "SQLAlchemy-2.0.29-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:a88913000da9205b13f6f195f0813b6ffd8a0c0c2bd58d499e00a30eb508870c"}, - {file = "SQLAlchemy-2.0.29-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fecd5089c4be1bcc37c35e9aa678938d2888845a134dd016de457b942cf5a758"}, - {file = "SQLAlchemy-2.0.29-cp312-cp312-win32.whl", hash = "sha256:8197d6f7a3d2b468861ebb4c9f998b9df9e358d6e1cf9c2a01061cb9b6cf4e41"}, - {file = "SQLAlchemy-2.0.29-cp312-cp312-win_amd64.whl", hash = "sha256:9b19836ccca0d321e237560e475fd99c3d8655d03da80c845c4da20dda31b6e1"}, - {file = "SQLAlchemy-2.0.29-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:87a1d53a5382cdbbf4b7619f107cc862c1b0a4feb29000922db72e5a66a5ffc0"}, - {file = "SQLAlchemy-2.0.29-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2a0732dffe32333211801b28339d2a0babc1971bc90a983e3035e7b0d6f06b93"}, - {file = "SQLAlchemy-2.0.29-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90453597a753322d6aa770c5935887ab1fc49cc4c4fdd436901308383d698b4b"}, - {file = "SQLAlchemy-2.0.29-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:ea311d4ee9a8fa67f139c088ae9f905fcf0277d6cd75c310a21a88bf85e130f5"}, - {file = "SQLAlchemy-2.0.29-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:5f20cb0a63a3e0ec4e169aa8890e32b949c8145983afa13a708bc4b0a1f30e03"}, - {file = "SQLAlchemy-2.0.29-cp37-cp37m-win32.whl", hash = "sha256:e5bbe55e8552019c6463709b39634a5fc55e080d0827e2a3a11e18eb73f5cdbd"}, - {file = "SQLAlchemy-2.0.29-cp37-cp37m-win_amd64.whl", hash = "sha256:c2f9c762a2735600654c654bf48dad388b888f8ce387b095806480e6e4ff6907"}, - {file = "SQLAlchemy-2.0.29-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7e614d7a25a43a9f54fcce4675c12761b248547f3d41b195e8010ca7297c369c"}, - {file = "SQLAlchemy-2.0.29-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:471fcb39c6adf37f820350c28aac4a7df9d3940c6548b624a642852e727ea586"}, - {file = "SQLAlchemy-2.0.29-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:988569c8732f54ad3234cf9c561364221a9e943b78dc7a4aaf35ccc2265f1930"}, - {file = "SQLAlchemy-2.0.29-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dddaae9b81c88083e6437de95c41e86823d150f4ee94bf24e158a4526cbead01"}, - {file = "SQLAlchemy-2.0.29-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:334184d1ab8f4c87f9652b048af3f7abea1c809dfe526fb0435348a6fef3d380"}, - {file = "SQLAlchemy-2.0.29-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:38b624e5cf02a69b113c8047cf7f66b5dfe4a2ca07ff8b8716da4f1b3ae81567"}, - {file = "SQLAlchemy-2.0.29-cp38-cp38-win32.whl", hash = "sha256:bab41acf151cd68bc2b466deae5deeb9e8ae9c50ad113444151ad965d5bf685b"}, - {file = "SQLAlchemy-2.0.29-cp38-cp38-win_amd64.whl", hash = "sha256:52c8011088305476691b8750c60e03b87910a123cfd9ad48576d6414b6ec2a1d"}, - {file = "SQLAlchemy-2.0.29-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3071ad498896907a5ef756206b9dc750f8e57352113c19272bdfdc429c7bd7de"}, - {file = "SQLAlchemy-2.0.29-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:dba622396a3170974f81bad49aacebd243455ec3cc70615aeaef9e9613b5bca5"}, - {file = "SQLAlchemy-2.0.29-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b184e3de58009cc0bf32e20f137f1ec75a32470f5fede06c58f6c355ed42a72"}, - {file = "SQLAlchemy-2.0.29-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c37f1050feb91f3d6c32f864d8e114ff5545a4a7afe56778d76a9aec62638ba"}, - {file = "SQLAlchemy-2.0.29-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bda7ce59b06d0f09afe22c56714c65c957b1068dee3d5e74d743edec7daba552"}, - {file = "SQLAlchemy-2.0.29-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:25664e18bef6dc45015b08f99c63952a53a0a61f61f2e48a9e70cec27e55f699"}, - {file = "SQLAlchemy-2.0.29-cp39-cp39-win32.whl", hash = "sha256:77d29cb6c34b14af8a484e831ab530c0f7188f8efed1c6a833a2c674bf3c26ec"}, - {file = "SQLAlchemy-2.0.29-cp39-cp39-win_amd64.whl", hash = "sha256:04c487305ab035a9548f573763915189fc0fe0824d9ba28433196f8436f1449c"}, - {file = "SQLAlchemy-2.0.29-py3-none-any.whl", hash = "sha256:dc4ee2d4ee43251905f88637d5281a8d52e916a021384ec10758826f5cbae305"}, - {file = "SQLAlchemy-2.0.29.tar.gz", hash = "sha256:bd9566b8e58cabd700bc367b60e90d9349cd16f0984973f98a9a09f9c64e86f0"}, -] - -[package.dependencies] -greenlet = {version = "!=0.4.17", markers = "platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\""} -typing-extensions = ">=4.6.0" - -[package.extras] -aiomysql = ["aiomysql (>=0.2.0)", "greenlet (!=0.4.17)"] -aioodbc = ["aioodbc", "greenlet (!=0.4.17)"] -aiosqlite = ["aiosqlite", "greenlet (!=0.4.17)", "typing_extensions (!=3.10.0.1)"] -asyncio = ["greenlet (!=0.4.17)"] -asyncmy = ["asyncmy (>=0.2.3,!=0.2.4,!=0.2.6)", "greenlet (!=0.4.17)"] -mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2,!=1.1.5)"] -mssql = ["pyodbc"] -mssql-pymssql = ["pymssql"] -mssql-pyodbc = ["pyodbc"] -mypy = ["mypy (>=0.910)"] -mysql = ["mysqlclient (>=1.4.0)"] -mysql-connector = ["mysql-connector-python"] -oracle = ["cx_oracle (>=8)"] -oracle-oracledb = ["oracledb (>=1.0.1)"] -postgresql = ["psycopg2 (>=2.7)"] -postgresql-asyncpg = ["asyncpg", "greenlet (!=0.4.17)"] -postgresql-pg8000 = ["pg8000 (>=1.29.1)"] -postgresql-psycopg = ["psycopg (>=3.0.7)"] -postgresql-psycopg2binary = ["psycopg2-binary"] -postgresql-psycopg2cffi = ["psycopg2cffi"] -postgresql-psycopgbinary = ["psycopg[binary] (>=3.0.7)"] -pymysql = ["pymysql"] -sqlcipher = ["sqlcipher3_binary"] - -[[package]] -name = "toolz" -version = "0.12.1" -description = "List processing tools and functional utilities" -optional = false -python-versions = ">=3.7" -files = [ - {file = "toolz-0.12.1-py3-none-any.whl", hash = "sha256:d22731364c07d72eea0a0ad45bafb2c2937ab6fd38a3507bf55eae8744aa7d85"}, - {file = "toolz-0.12.1.tar.gz", hash = "sha256:ecca342664893f177a13dac0e6b41cbd8ac25a358e5f215316d43e2100224f4d"}, -] - -[[package]] -name = "tracerite" -version = "1.1.1" -description = "Human-readable HTML tracebacks for Python exceptions" -optional = false -python-versions = "*" -files = [ - {file = "tracerite-1.1.1-py3-none-any.whl", hash = "sha256:3a787a9ecb1a136ea9ce17e6328e414ec414a4f644130af4e1e330bec2dece29"}, - {file = "tracerite-1.1.1.tar.gz", hash = "sha256:6400a35a187747189e4bb8d4a8e471bd86d14dbdcc94bcad23f4eda023f41356"}, -] - -[package.dependencies] -html5tagger = ">=1.2.1" - -[[package]] -name = "types-aiofiles" -version = "23.2.0.20240403" -description = "Typing stubs for aiofiles" -optional = false -python-versions = ">=3.8" -files = [ - {file = "types-aiofiles-23.2.0.20240403.tar.gz", hash = "sha256:1ffcf8f5f72b81f71139f754ea2610ab0017f27ba4fd771e187b07840ee49c0f"}, - {file = "types_aiofiles-23.2.0.20240403-py3-none-any.whl", hash = "sha256:adeeb4b999f19fda2dfe91c07857ff54701b6ee9b227b523a5a7be92125a2c5f"}, -] - -[[package]] -name = "types-beautifulsoup4" -version = "4.12.0.20240229" -description = "Typing stubs for beautifulsoup4" -optional = false -python-versions = ">=3.8" -files = [ - {file = "types-beautifulsoup4-4.12.0.20240229.tar.gz", hash = "sha256:e37e4cfa11b03b01775732e56d2c010cb24ee107786277bae6bc0fa3e305b686"}, - {file = "types_beautifulsoup4-4.12.0.20240229-py3-none-any.whl", hash = "sha256:000cdddb8aee4effb45a04be95654de8629fb8594a4f2f1231cff81108977324"}, -] - -[package.dependencies] -types-html5lib = "*" - -[[package]] -name = "types-html5lib" -version = "1.1.11.20240228" -description = "Typing stubs for html5lib" -optional = false -python-versions = ">=3.8" -files = [ - {file = "types-html5lib-1.1.11.20240228.tar.gz", hash = "sha256:22736b7299e605ec4ba539d48691e905fd0c61c3ea610acc59922232dc84cede"}, - {file = "types_html5lib-1.1.11.20240228-py3-none-any.whl", hash = "sha256:af5de0125cb0fe5667543b158db83849b22e25c0e36c9149836b095548bf1020"}, -] - -[[package]] -name = "types-ujson" -version = "5.9.0.0" -description = "Typing stubs for ujson" -optional = false -python-versions = ">=3.7" -files = [ - {file = "types-ujson-5.9.0.0.tar.gz", hash = "sha256:7e7042454dc7cd7f31b09c420d7caf36b93d30bdf4b8db93791bd0561713d017"}, - {file = "types_ujson-5.9.0.0-py3-none-any.whl", hash = "sha256:f274fa604ed6317effcd1c424ef4cf292c3b0689cb118fb3180689d40ed1f4ed"}, -] - -[[package]] -name = "typing-extensions" -version = "4.11.0" -description = "Backported and Experimental Type Hints for Python 3.8+" -optional = false -python-versions = ">=3.8" -files = [ - {file = "typing_extensions-4.11.0-py3-none-any.whl", hash = "sha256:c1f94d72897edaf4ce775bb7558d5b79d8126906a14ea5ed1635921406c0387a"}, - {file = "typing_extensions-4.11.0.tar.gz", hash = "sha256:83f085bd5ca59c80295fc2a82ab5dac679cbe02b9f33f7d83af68e241bea51b0"}, -] - -[[package]] -name = "tzdata" -version = "2024.1" -description = "Provider of IANA time zone data" -optional = false -python-versions = ">=2" -files = [ - {file = "tzdata-2024.1-py2.py3-none-any.whl", hash = "sha256:9068bc196136463f5245e51efda838afa15aaeca9903f49050dfa2679db4d252"}, - {file = "tzdata-2024.1.tar.gz", hash = "sha256:2674120f8d891909751c38abcdfd386ac0a5a1127954fbc332af6b5ceae07efd"}, -] - -[[package]] -name = "ujson" -version = "5.9.0" -description = "Ultra fast JSON encoder and decoder for Python" -optional = false -python-versions = ">=3.8" -files = [ - {file = "ujson-5.9.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ab71bf27b002eaf7d047c54a68e60230fbd5cd9da60de7ca0aa87d0bccead8fa"}, - {file = "ujson-5.9.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7a365eac66f5aa7a7fdf57e5066ada6226700884fc7dce2ba5483538bc16c8c5"}, - {file = "ujson-5.9.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e015122b337858dba5a3dc3533af2a8fc0410ee9e2374092f6a5b88b182e9fcc"}, - {file = "ujson-5.9.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:779a2a88c53039bebfbccca934430dabb5c62cc179e09a9c27a322023f363e0d"}, - {file = "ujson-5.9.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:10ca3c41e80509fd9805f7c149068fa8dbee18872bbdc03d7cca928926a358d5"}, - {file = "ujson-5.9.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4a566e465cb2fcfdf040c2447b7dd9718799d0d90134b37a20dff1e27c0e9096"}, - {file = "ujson-5.9.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:f833c529e922577226a05bc25b6a8b3eb6c4fb155b72dd88d33de99d53113124"}, - {file = "ujson-5.9.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b68a0caab33f359b4cbbc10065c88e3758c9f73a11a65a91f024b2e7a1257106"}, - {file = "ujson-5.9.0-cp310-cp310-win32.whl", hash = "sha256:7cc7e605d2aa6ae6b7321c3ae250d2e050f06082e71ab1a4200b4ae64d25863c"}, - {file = "ujson-5.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:a6d3f10eb8ccba4316a6b5465b705ed70a06011c6f82418b59278fbc919bef6f"}, - {file = "ujson-5.9.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3b23bbb46334ce51ddb5dded60c662fbf7bb74a37b8f87221c5b0fec1ec6454b"}, - {file = "ujson-5.9.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6974b3a7c17bbf829e6c3bfdc5823c67922e44ff169851a755eab79a3dd31ec0"}, - {file = "ujson-5.9.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5964ea916edfe24af1f4cc68488448fbb1ec27a3ddcddc2b236da575c12c8ae"}, - {file = "ujson-5.9.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8ba7cac47dd65ff88571eceeff48bf30ed5eb9c67b34b88cb22869b7aa19600d"}, - {file = "ujson-5.9.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6bbd91a151a8f3358c29355a491e915eb203f607267a25e6ab10531b3b157c5e"}, - {file = "ujson-5.9.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:829a69d451a49c0de14a9fecb2a2d544a9b2c884c2b542adb243b683a6f15908"}, - {file = "ujson-5.9.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:a807ae73c46ad5db161a7e883eec0fbe1bebc6a54890152ccc63072c4884823b"}, - {file = "ujson-5.9.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8fc2aa18b13d97b3c8ccecdf1a3c405f411a6e96adeee94233058c44ff92617d"}, - {file = "ujson-5.9.0-cp311-cp311-win32.whl", hash = "sha256:70e06849dfeb2548be48fdd3ceb53300640bc8100c379d6e19d78045e9c26120"}, - {file = "ujson-5.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:7309d063cd392811acc49b5016728a5e1b46ab9907d321ebbe1c2156bc3c0b99"}, - {file = "ujson-5.9.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:20509a8c9f775b3a511e308bbe0b72897ba6b800767a7c90c5cca59d20d7c42c"}, - {file = "ujson-5.9.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b28407cfe315bd1b34f1ebe65d3bd735d6b36d409b334100be8cdffae2177b2f"}, - {file = "ujson-5.9.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9d302bd17989b6bd90d49bade66943c78f9e3670407dbc53ebcf61271cadc399"}, - {file = "ujson-5.9.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f21315f51e0db8ee245e33a649dd2d9dce0594522de6f278d62f15f998e050e"}, - {file = "ujson-5.9.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5635b78b636a54a86fdbf6f027e461aa6c6b948363bdf8d4fbb56a42b7388320"}, - {file = "ujson-5.9.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:82b5a56609f1235d72835ee109163c7041b30920d70fe7dac9176c64df87c164"}, - {file = "ujson-5.9.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:5ca35f484622fd208f55041b042d9d94f3b2c9c5add4e9af5ee9946d2d30db01"}, - {file = "ujson-5.9.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:829b824953ebad76d46e4ae709e940bb229e8999e40881338b3cc94c771b876c"}, - {file = "ujson-5.9.0-cp312-cp312-win32.whl", hash = "sha256:25fa46e4ff0a2deecbcf7100af3a5d70090b461906f2299506485ff31d9ec437"}, - {file = "ujson-5.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:60718f1720a61560618eff3b56fd517d107518d3c0160ca7a5a66ac949c6cf1c"}, - {file = "ujson-5.9.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d581db9db9e41d8ea0b2705c90518ba623cbdc74f8d644d7eb0d107be0d85d9c"}, - {file = "ujson-5.9.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ff741a5b4be2d08fceaab681c9d4bc89abf3c9db600ab435e20b9b6d4dfef12e"}, - {file = "ujson-5.9.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cdcb02cabcb1e44381221840a7af04433c1dc3297af76fde924a50c3054c708c"}, - {file = "ujson-5.9.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e208d3bf02c6963e6ef7324dadf1d73239fb7008491fdf523208f60be6437402"}, - {file = "ujson-5.9.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f4b3917296630a075e04d3d07601ce2a176479c23af838b6cf90a2d6b39b0d95"}, - {file = "ujson-5.9.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:0c4d6adb2c7bb9eb7c71ad6f6f612e13b264942e841f8cc3314a21a289a76c4e"}, - {file = "ujson-5.9.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:0b159efece9ab5c01f70b9d10bbb77241ce111a45bc8d21a44c219a2aec8ddfd"}, - {file = "ujson-5.9.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:f0cb4a7814940ddd6619bdce6be637a4b37a8c4760de9373bac54bb7b229698b"}, - {file = "ujson-5.9.0-cp38-cp38-win32.whl", hash = "sha256:dc80f0f5abf33bd7099f7ac94ab1206730a3c0a2d17549911ed2cb6b7aa36d2d"}, - {file = "ujson-5.9.0-cp38-cp38-win_amd64.whl", hash = "sha256:506a45e5fcbb2d46f1a51fead991c39529fc3737c0f5d47c9b4a1d762578fc30"}, - {file = "ujson-5.9.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d0fd2eba664a22447102062814bd13e63c6130540222c0aa620701dd01f4be81"}, - {file = "ujson-5.9.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:bdf7fc21a03bafe4ba208dafa84ae38e04e5d36c0e1c746726edf5392e9f9f36"}, - {file = "ujson-5.9.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2f909bc08ce01f122fd9c24bc6f9876aa087188dfaf3c4116fe6e4daf7e194f"}, - {file = "ujson-5.9.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd4ea86c2afd41429751d22a3ccd03311c067bd6aeee2d054f83f97e41e11d8f"}, - {file = "ujson-5.9.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:63fb2e6599d96fdffdb553af0ed3f76b85fda63281063f1cb5b1141a6fcd0617"}, - {file = "ujson-5.9.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:32bba5870c8fa2a97f4a68f6401038d3f1922e66c34280d710af00b14a3ca562"}, - {file = "ujson-5.9.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:37ef92e42535a81bf72179d0e252c9af42a4ed966dc6be6967ebfb929a87bc60"}, - {file = "ujson-5.9.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:f69f16b8f1c69da00e38dc5f2d08a86b0e781d0ad3e4cc6a13ea033a439c4844"}, - {file = "ujson-5.9.0-cp39-cp39-win32.whl", hash = "sha256:3382a3ce0ccc0558b1c1668950008cece9bf463ebb17463ebf6a8bfc060dae34"}, - {file = "ujson-5.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:6adef377ed583477cf005b58c3025051b5faa6b8cc25876e594afbb772578f21"}, - {file = "ujson-5.9.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ffdfebd819f492e48e4f31c97cb593b9c1a8251933d8f8972e81697f00326ff1"}, - {file = "ujson-5.9.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4eec2ddc046360d087cf35659c7ba0cbd101f32035e19047013162274e71fcf"}, - {file = "ujson-5.9.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fbb90aa5c23cb3d4b803c12aa220d26778c31b6e4b7a13a1f49971f6c7d088e"}, - {file = "ujson-5.9.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ba0823cb70866f0d6a4ad48d998dd338dce7314598721bc1b7986d054d782dfd"}, - {file = "ujson-5.9.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:4e35d7885ed612feb6b3dd1b7de28e89baaba4011ecdf995e88be9ac614765e9"}, - {file = "ujson-5.9.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:b048aa93eace8571eedbd67b3766623e7f0acbf08ee291bef7d8106210432427"}, - {file = "ujson-5.9.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:323279e68c195110ef85cbe5edce885219e3d4a48705448720ad925d88c9f851"}, - {file = "ujson-5.9.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9ac92d86ff34296f881e12aa955f7014d276895e0e4e868ba7fddebbde38e378"}, - {file = "ujson-5.9.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:6eecbd09b316cea1fd929b1e25f70382917542ab11b692cb46ec9b0a26c7427f"}, - {file = "ujson-5.9.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:473fb8dff1d58f49912323d7cb0859df5585cfc932e4b9c053bf8cf7f2d7c5c4"}, - {file = "ujson-5.9.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f91719c6abafe429c1a144cfe27883eace9fb1c09a9c5ef1bcb3ae80a3076a4e"}, - {file = "ujson-5.9.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b1c0991c4fe256f5fdb19758f7eac7f47caac29a6c57d0de16a19048eb86bad"}, - {file = "ujson-5.9.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2a8ea0f55a1396708e564595aaa6696c0d8af532340f477162ff6927ecc46e21"}, - {file = "ujson-5.9.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:07e0cfdde5fd91f54cd2d7ffb3482c8ff1bf558abf32a8b953a5d169575ae1cd"}, - {file = "ujson-5.9.0.tar.gz", hash = "sha256:89cc92e73d5501b8a7f48575eeb14ad27156ad092c2e9fc7e3cf949f07e75532"}, -] - -[[package]] -name = "urllib3" -version = "2.2.1" -description = "HTTP library with thread-safe connection pooling, file post, and more." -optional = false -python-versions = ">=3.8" -files = [ - {file = "urllib3-2.2.1-py3-none-any.whl", hash = "sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d"}, - {file = "urllib3-2.2.1.tar.gz", hash = "sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19"}, -] - -[package.extras] -brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] -h2 = ["h2 (>=4,<5)"] -socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] -zstd = ["zstandard (>=0.18.0)"] - -[[package]] -name = "uvloop" -version = "0.19.0" -description = "Fast implementation of asyncio event loop on top of libuv" -optional = false -python-versions = ">=3.8.0" -files = [ - {file = "uvloop-0.19.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:de4313d7f575474c8f5a12e163f6d89c0a878bc49219641d49e6f1444369a90e"}, - {file = "uvloop-0.19.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5588bd21cf1fcf06bded085f37e43ce0e00424197e7c10e77afd4bbefffef428"}, - {file = "uvloop-0.19.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b1fd71c3843327f3bbc3237bedcdb6504fd50368ab3e04d0410e52ec293f5b8"}, - {file = "uvloop-0.19.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a05128d315e2912791de6088c34136bfcdd0c7cbc1cf85fd6fd1bb321b7c849"}, - {file = "uvloop-0.19.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:cd81bdc2b8219cb4b2556eea39d2e36bfa375a2dd021404f90a62e44efaaf957"}, - {file = "uvloop-0.19.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5f17766fb6da94135526273080f3455a112f82570b2ee5daa64d682387fe0dcd"}, - {file = "uvloop-0.19.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4ce6b0af8f2729a02a5d1575feacb2a94fc7b2e983868b009d51c9a9d2149bef"}, - {file = "uvloop-0.19.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:31e672bb38b45abc4f26e273be83b72a0d28d074d5b370fc4dcf4c4eb15417d2"}, - {file = "uvloop-0.19.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:570fc0ed613883d8d30ee40397b79207eedd2624891692471808a95069a007c1"}, - {file = "uvloop-0.19.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5138821e40b0c3e6c9478643b4660bd44372ae1e16a322b8fc07478f92684e24"}, - {file = "uvloop-0.19.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:91ab01c6cd00e39cde50173ba4ec68a1e578fee9279ba64f5221810a9e786533"}, - {file = "uvloop-0.19.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:47bf3e9312f63684efe283f7342afb414eea4d3011542155c7e625cd799c3b12"}, - {file = "uvloop-0.19.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:da8435a3bd498419ee8c13c34b89b5005130a476bda1d6ca8cfdde3de35cd650"}, - {file = "uvloop-0.19.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:02506dc23a5d90e04d4f65c7791e65cf44bd91b37f24cfc3ef6cf2aff05dc7ec"}, - {file = "uvloop-0.19.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2693049be9d36fef81741fddb3f441673ba12a34a704e7b4361efb75cf30befc"}, - {file = "uvloop-0.19.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7010271303961c6f0fe37731004335401eb9075a12680738731e9c92ddd96ad6"}, - {file = "uvloop-0.19.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:5daa304d2161d2918fa9a17d5635099a2f78ae5b5960e742b2fcfbb7aefaa593"}, - {file = "uvloop-0.19.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:7207272c9520203fea9b93843bb775d03e1cf88a80a936ce760f60bb5add92f3"}, - {file = "uvloop-0.19.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:78ab247f0b5671cc887c31d33f9b3abfb88d2614b84e4303f1a63b46c046c8bd"}, - {file = "uvloop-0.19.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:472d61143059c84947aa8bb74eabbace30d577a03a1805b77933d6bd13ddebbd"}, - {file = "uvloop-0.19.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45bf4c24c19fb8a50902ae37c5de50da81de4922af65baf760f7c0c42e1088be"}, - {file = "uvloop-0.19.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:271718e26b3e17906b28b67314c45d19106112067205119dddbd834c2b7ce797"}, - {file = "uvloop-0.19.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:34175c9fd2a4bc3adc1380e1261f60306344e3407c20a4d684fd5f3be010fa3d"}, - {file = "uvloop-0.19.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e27f100e1ff17f6feeb1f33968bc185bf8ce41ca557deee9d9bbbffeb72030b7"}, - {file = "uvloop-0.19.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:13dfdf492af0aa0a0edf66807d2b465607d11c4fa48f4a1fd41cbea5b18e8e8b"}, - {file = "uvloop-0.19.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6e3d4e85ac060e2342ff85e90d0c04157acb210b9ce508e784a944f852a40e67"}, - {file = "uvloop-0.19.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8ca4956c9ab567d87d59d49fa3704cf29e37109ad348f2d5223c9bf761a332e7"}, - {file = "uvloop-0.19.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f467a5fd23b4fc43ed86342641f3936a68ded707f4627622fa3f82a120e18256"}, - {file = "uvloop-0.19.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:492e2c32c2af3f971473bc22f086513cedfc66a130756145a931a90c3958cb17"}, - {file = "uvloop-0.19.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2df95fca285a9f5bfe730e51945ffe2fa71ccbfdde3b0da5772b4ee4f2e770d5"}, - {file = "uvloop-0.19.0.tar.gz", hash = "sha256:0246f4fd1bf2bf702e06b0d45ee91677ee5c31242f39aab4ea6fe0c51aedd0fd"}, -] - -[package.extras] -docs = ["Sphinx (>=4.1.2,<4.2.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)"] -test = ["Cython (>=0.29.36,<0.30.0)", "aiohttp (==3.9.0b0)", "aiohttp (>=3.8.1)", "flake8 (>=5.0,<6.0)", "mypy (>=0.800)", "psutil", "pyOpenSSL (>=23.0.0,<23.1.0)", "pycodestyle (>=2.9.0,<2.10.0)"] - -[[package]] -name = "websockets" -version = "12.0" -description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" -optional = false -python-versions = ">=3.8" -files = [ - {file = "websockets-12.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d554236b2a2006e0ce16315c16eaa0d628dab009c33b63ea03f41c6107958374"}, - {file = "websockets-12.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2d225bb6886591b1746b17c0573e29804619c8f755b5598d875bb4235ea639be"}, - {file = "websockets-12.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:eb809e816916a3b210bed3c82fb88eaf16e8afcf9c115ebb2bacede1797d2547"}, - {file = "websockets-12.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c588f6abc13f78a67044c6b1273a99e1cf31038ad51815b3b016ce699f0d75c2"}, - {file = "websockets-12.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5aa9348186d79a5f232115ed3fa9020eab66d6c3437d72f9d2c8ac0c6858c558"}, - {file = "websockets-12.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6350b14a40c95ddd53e775dbdbbbc59b124a5c8ecd6fbb09c2e52029f7a9f480"}, - {file = "websockets-12.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:70ec754cc2a769bcd218ed8d7209055667b30860ffecb8633a834dde27d6307c"}, - {file = "websockets-12.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6e96f5ed1b83a8ddb07909b45bd94833b0710f738115751cdaa9da1fb0cb66e8"}, - {file = "websockets-12.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4d87be612cbef86f994178d5186add3d94e9f31cc3cb499a0482b866ec477603"}, - {file = "websockets-12.0-cp310-cp310-win32.whl", hash = "sha256:befe90632d66caaf72e8b2ed4d7f02b348913813c8b0a32fae1cc5fe3730902f"}, - {file = "websockets-12.0-cp310-cp310-win_amd64.whl", hash = "sha256:363f57ca8bc8576195d0540c648aa58ac18cf85b76ad5202b9f976918f4219cf"}, - {file = "websockets-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5d873c7de42dea355d73f170be0f23788cf3fa9f7bed718fd2830eefedce01b4"}, - {file = "websockets-12.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3f61726cae9f65b872502ff3c1496abc93ffbe31b278455c418492016e2afc8f"}, - {file = "websockets-12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ed2fcf7a07334c77fc8a230755c2209223a7cc44fc27597729b8ef5425aa61a3"}, - {file = "websockets-12.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e332c210b14b57904869ca9f9bf4ca32f5427a03eeb625da9b616c85a3a506c"}, - {file = "websockets-12.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5693ef74233122f8ebab026817b1b37fe25c411ecfca084b29bc7d6efc548f45"}, - {file = "websockets-12.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e9e7db18b4539a29cc5ad8c8b252738a30e2b13f033c2d6e9d0549b45841c04"}, - {file = "websockets-12.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6e2df67b8014767d0f785baa98393725739287684b9f8d8a1001eb2839031447"}, - {file = "websockets-12.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:bea88d71630c5900690fcb03161ab18f8f244805c59e2e0dc4ffadae0a7ee0ca"}, - {file = "websockets-12.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:dff6cdf35e31d1315790149fee351f9e52978130cef6c87c4b6c9b3baf78bc53"}, - {file = "websockets-12.0-cp311-cp311-win32.whl", hash = "sha256:3e3aa8c468af01d70332a382350ee95f6986db479ce7af14d5e81ec52aa2b402"}, - {file = "websockets-12.0-cp311-cp311-win_amd64.whl", hash = "sha256:25eb766c8ad27da0f79420b2af4b85d29914ba0edf69f547cc4f06ca6f1d403b"}, - {file = "websockets-12.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0e6e2711d5a8e6e482cacb927a49a3d432345dfe7dea8ace7b5790df5932e4df"}, - {file = "websockets-12.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:dbcf72a37f0b3316e993e13ecf32f10c0e1259c28ffd0a85cee26e8549595fbc"}, - {file = "websockets-12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:12743ab88ab2af1d17dd4acb4645677cb7063ef4db93abffbf164218a5d54c6b"}, - {file = "websockets-12.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b645f491f3c48d3f8a00d1fce07445fab7347fec54a3e65f0725d730d5b99cb"}, - {file = "websockets-12.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9893d1aa45a7f8b3bc4510f6ccf8db8c3b62120917af15e3de247f0780294b92"}, - {file = "websockets-12.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f38a7b376117ef7aff996e737583172bdf535932c9ca021746573bce40165ed"}, - {file = "websockets-12.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:f764ba54e33daf20e167915edc443b6f88956f37fb606449b4a5b10ba42235a5"}, - {file = "websockets-12.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:1e4b3f8ea6a9cfa8be8484c9221ec0257508e3a1ec43c36acdefb2a9c3b00aa2"}, - {file = "websockets-12.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9fdf06fd06c32205a07e47328ab49c40fc1407cdec801d698a7c41167ea45113"}, - {file = "websockets-12.0-cp312-cp312-win32.whl", hash = "sha256:baa386875b70cbd81798fa9f71be689c1bf484f65fd6fb08d051a0ee4e79924d"}, - {file = "websockets-12.0-cp312-cp312-win_amd64.whl", hash = "sha256:ae0a5da8f35a5be197f328d4727dbcfafa53d1824fac3d96cdd3a642fe09394f"}, - {file = "websockets-12.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5f6ffe2c6598f7f7207eef9a1228b6f5c818f9f4d53ee920aacd35cec8110438"}, - {file = "websockets-12.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9edf3fc590cc2ec20dc9d7a45108b5bbaf21c0d89f9fd3fd1685e223771dc0b2"}, - {file = "websockets-12.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8572132c7be52632201a35f5e08348137f658e5ffd21f51f94572ca6c05ea81d"}, - {file = "websockets-12.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:604428d1b87edbf02b233e2c207d7d528460fa978f9e391bd8aaf9c8311de137"}, - {file = "websockets-12.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1a9d160fd080c6285e202327aba140fc9a0d910b09e423afff4ae5cbbf1c7205"}, - {file = "websockets-12.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87b4aafed34653e465eb77b7c93ef058516cb5acf3eb21e42f33928616172def"}, - {file = "websockets-12.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b2ee7288b85959797970114deae81ab41b731f19ebcd3bd499ae9ca0e3f1d2c8"}, - {file = "websockets-12.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:7fa3d25e81bfe6a89718e9791128398a50dec6d57faf23770787ff441d851967"}, - {file = "websockets-12.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:a571f035a47212288e3b3519944f6bf4ac7bc7553243e41eac50dd48552b6df7"}, - {file = "websockets-12.0-cp38-cp38-win32.whl", hash = "sha256:3c6cc1360c10c17463aadd29dd3af332d4a1adaa8796f6b0e9f9df1fdb0bad62"}, - {file = "websockets-12.0-cp38-cp38-win_amd64.whl", hash = "sha256:1bf386089178ea69d720f8db6199a0504a406209a0fc23e603b27b300fdd6892"}, - {file = "websockets-12.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:ab3d732ad50a4fbd04a4490ef08acd0517b6ae6b77eb967251f4c263011a990d"}, - {file = "websockets-12.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a1d9697f3337a89691e3bd8dc56dea45a6f6d975f92e7d5f773bc715c15dde28"}, - {file = "websockets-12.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1df2fbd2c8a98d38a66f5238484405b8d1d16f929bb7a33ed73e4801222a6f53"}, - {file = "websockets-12.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23509452b3bc38e3a057382c2e941d5ac2e01e251acce7adc74011d7d8de434c"}, - {file = "websockets-12.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2e5fc14ec6ea568200ea4ef46545073da81900a2b67b3e666f04adf53ad452ec"}, - {file = "websockets-12.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46e71dbbd12850224243f5d2aeec90f0aaa0f2dde5aeeb8fc8df21e04d99eff9"}, - {file = "websockets-12.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b81f90dcc6c85a9b7f29873beb56c94c85d6f0dac2ea8b60d995bd18bf3e2aae"}, - {file = "websockets-12.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:a02413bc474feda2849c59ed2dfb2cddb4cd3d2f03a2fedec51d6e959d9b608b"}, - {file = "websockets-12.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:bbe6013f9f791944ed31ca08b077e26249309639313fff132bfbf3ba105673b9"}, - {file = "websockets-12.0-cp39-cp39-win32.whl", hash = "sha256:cbe83a6bbdf207ff0541de01e11904827540aa069293696dd528a6640bd6a5f6"}, - {file = "websockets-12.0-cp39-cp39-win_amd64.whl", hash = "sha256:fc4e7fa5414512b481a2483775a8e8be7803a35b30ca805afa4998a84f9fd9e8"}, - {file = "websockets-12.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:248d8e2446e13c1d4326e0a6a4e9629cb13a11195051a73acf414812700badbd"}, - {file = "websockets-12.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f44069528d45a933997a6fef143030d8ca8042f0dfaad753e2906398290e2870"}, - {file = "websockets-12.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c4e37d36f0d19f0a4413d3e18c0d03d0c268ada2061868c1e6f5ab1a6d575077"}, - {file = "websockets-12.0-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d829f975fc2e527a3ef2f9c8f25e553eb7bc779c6665e8e1d52aa22800bb38b"}, - {file = "websockets-12.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:2c71bd45a777433dd9113847af751aae36e448bc6b8c361a566cb043eda6ec30"}, - {file = "websockets-12.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0bee75f400895aef54157b36ed6d3b308fcab62e5260703add87f44cee9c82a6"}, - {file = "websockets-12.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:423fc1ed29f7512fceb727e2d2aecb952c46aa34895e9ed96071821309951123"}, - {file = "websockets-12.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:27a5e9964ef509016759f2ef3f2c1e13f403725a5e6a1775555994966a66e931"}, - {file = "websockets-12.0-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c3181df4583c4d3994d31fb235dc681d2aaad744fbdbf94c4802485ececdecf2"}, - {file = "websockets-12.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:b067cb952ce8bf40115f6c19f478dc71c5e719b7fbaa511359795dfd9d1a6468"}, - {file = "websockets-12.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:00700340c6c7ab788f176d118775202aadea7602c5cc6be6ae127761c16d6b0b"}, - {file = "websockets-12.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e469d01137942849cff40517c97a30a93ae79917752b34029f0ec72df6b46399"}, - {file = "websockets-12.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffefa1374cd508d633646d51a8e9277763a9b78ae71324183693959cf94635a7"}, - {file = "websockets-12.0-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba0cab91b3956dfa9f512147860783a1829a8d905ee218a9837c18f683239611"}, - {file = "websockets-12.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2cb388a5bfb56df4d9a406783b7f9dbefb888c09b71629351cc6b036e9259370"}, - {file = "websockets-12.0-py3-none-any.whl", hash = "sha256:dc284bbc8d7c78a6c69e0c7325ab46ee5e40bb4d50e494d8131a07ef47500e9e"}, - {file = "websockets-12.0.tar.gz", hash = "sha256:81df9cbcbb6c260de1e007e58c011bfebe2dafc8435107b0537f393dd38c8b1b"}, -] - -[[package]] -name = "wrapt" -version = "1.16.0" -description = "Module for decorators, wrappers and monkey patching." -optional = false -python-versions = ">=3.6" -files = [ - {file = "wrapt-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ffa565331890b90056c01db69c0fe634a776f8019c143a5ae265f9c6bc4bd6d4"}, - {file = "wrapt-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e4fdb9275308292e880dcbeb12546df7f3e0f96c6b41197e0cf37d2826359020"}, - {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb2dee3874a500de01c93d5c71415fcaef1d858370d405824783e7a8ef5db440"}, - {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2a88e6010048489cda82b1326889ec075a8c856c2e6a256072b28eaee3ccf487"}, - {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac83a914ebaf589b69f7d0a1277602ff494e21f4c2f743313414378f8f50a4cf"}, - {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:73aa7d98215d39b8455f103de64391cb79dfcad601701a3aa0dddacf74911d72"}, - {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:807cc8543a477ab7422f1120a217054f958a66ef7314f76dd9e77d3f02cdccd0"}, - {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bf5703fdeb350e36885f2875d853ce13172ae281c56e509f4e6eca049bdfb136"}, - {file = "wrapt-1.16.0-cp310-cp310-win32.whl", hash = "sha256:f6b2d0c6703c988d334f297aa5df18c45e97b0af3679bb75059e0e0bd8b1069d"}, - {file = "wrapt-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:decbfa2f618fa8ed81c95ee18a387ff973143c656ef800c9f24fb7e9c16054e2"}, - {file = "wrapt-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1a5db485fe2de4403f13fafdc231b0dbae5eca4359232d2efc79025527375b09"}, - {file = "wrapt-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:75ea7d0ee2a15733684badb16de6794894ed9c55aa5e9903260922f0482e687d"}, - {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a452f9ca3e3267cd4d0fcf2edd0d035b1934ac2bd7e0e57ac91ad6b95c0c6389"}, - {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:43aa59eadec7890d9958748db829df269f0368521ba6dc68cc172d5d03ed8060"}, - {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72554a23c78a8e7aa02abbd699d129eead8b147a23c56e08d08dfc29cfdddca1"}, - {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d2efee35b4b0a347e0d99d28e884dfd82797852d62fcd7ebdeee26f3ceb72cf3"}, - {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:6dcfcffe73710be01d90cae08c3e548d90932d37b39ef83969ae135d36ef3956"}, - {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:eb6e651000a19c96f452c85132811d25e9264d836951022d6e81df2fff38337d"}, - {file = "wrapt-1.16.0-cp311-cp311-win32.whl", hash = "sha256:66027d667efe95cc4fa945af59f92c5a02c6f5bb6012bff9e60542c74c75c362"}, - {file = "wrapt-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:aefbc4cb0a54f91af643660a0a150ce2c090d3652cf4052a5397fb2de549cd89"}, - {file = "wrapt-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5eb404d89131ec9b4f748fa5cfb5346802e5ee8836f57d516576e61f304f3b7b"}, - {file = "wrapt-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9090c9e676d5236a6948330e83cb89969f433b1943a558968f659ead07cb3b36"}, - {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94265b00870aa407bd0cbcfd536f17ecde43b94fb8d228560a1e9d3041462d73"}, - {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f2058f813d4f2b5e3a9eb2eb3faf8f1d99b81c3e51aeda4b168406443e8ba809"}, - {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98b5e1f498a8ca1858a1cdbffb023bfd954da4e3fa2c0cb5853d40014557248b"}, - {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:14d7dc606219cdd7405133c713f2c218d4252f2a469003f8c46bb92d5d095d81"}, - {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:49aac49dc4782cb04f58986e81ea0b4768e4ff197b57324dcbd7699c5dfb40b9"}, - {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:418abb18146475c310d7a6dc71143d6f7adec5b004ac9ce08dc7a34e2babdc5c"}, - {file = "wrapt-1.16.0-cp312-cp312-win32.whl", hash = "sha256:685f568fa5e627e93f3b52fda002c7ed2fa1800b50ce51f6ed1d572d8ab3e7fc"}, - {file = "wrapt-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:dcdba5c86e368442528f7060039eda390cc4091bfd1dca41e8046af7c910dda8"}, - {file = "wrapt-1.16.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d462f28826f4657968ae51d2181a074dfe03c200d6131690b7d65d55b0f360f8"}, - {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a33a747400b94b6d6b8a165e4480264a64a78c8a4c734b62136062e9a248dd39"}, - {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3646eefa23daeba62643a58aac816945cadc0afaf21800a1421eeba5f6cfb9c"}, - {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ebf019be5c09d400cf7b024aa52b1f3aeebeff51550d007e92c3c1c4afc2a40"}, - {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:0d2691979e93d06a95a26257adb7bfd0c93818e89b1406f5a28f36e0d8c1e1fc"}, - {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:1acd723ee2a8826f3d53910255643e33673e1d11db84ce5880675954183ec47e"}, - {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:bc57efac2da352a51cc4658878a68d2b1b67dbe9d33c36cb826ca449d80a8465"}, - {file = "wrapt-1.16.0-cp36-cp36m-win32.whl", hash = "sha256:da4813f751142436b075ed7aa012a8778aa43a99f7b36afe9b742d3ed8bdc95e"}, - {file = "wrapt-1.16.0-cp36-cp36m-win_amd64.whl", hash = "sha256:6f6eac2360f2d543cc875a0e5efd413b6cbd483cb3ad7ebf888884a6e0d2e966"}, - {file = "wrapt-1.16.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a0ea261ce52b5952bf669684a251a66df239ec6d441ccb59ec7afa882265d593"}, - {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bd2d7ff69a2cac767fbf7a2b206add2e9a210e57947dd7ce03e25d03d2de292"}, - {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9159485323798c8dc530a224bd3ffcf76659319ccc7bbd52e01e73bd0241a0c5"}, - {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a86373cf37cd7764f2201b76496aba58a52e76dedfaa698ef9e9688bfd9e41cf"}, - {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:73870c364c11f03ed072dda68ff7aea6d2a3a5c3fe250d917a429c7432e15228"}, - {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b935ae30c6e7400022b50f8d359c03ed233d45b725cfdd299462f41ee5ffba6f"}, - {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:db98ad84a55eb09b3c32a96c576476777e87c520a34e2519d3e59c44710c002c"}, - {file = "wrapt-1.16.0-cp37-cp37m-win32.whl", hash = "sha256:9153ed35fc5e4fa3b2fe97bddaa7cbec0ed22412b85bcdaf54aeba92ea37428c"}, - {file = "wrapt-1.16.0-cp37-cp37m-win_amd64.whl", hash = "sha256:66dfbaa7cfa3eb707bbfcd46dab2bc6207b005cbc9caa2199bcbc81d95071a00"}, - {file = "wrapt-1.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1dd50a2696ff89f57bd8847647a1c363b687d3d796dc30d4dd4a9d1689a706f0"}, - {file = "wrapt-1.16.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:44a2754372e32ab315734c6c73b24351d06e77ffff6ae27d2ecf14cf3d229202"}, - {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e9723528b9f787dc59168369e42ae1c3b0d3fadb2f1a71de14531d321ee05b0"}, - {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dbed418ba5c3dce92619656802cc5355cb679e58d0d89b50f116e4a9d5a9603e"}, - {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:941988b89b4fd6b41c3f0bfb20e92bd23746579736b7343283297c4c8cbae68f"}, - {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6a42cd0cfa8ffc1915aef79cb4284f6383d8a3e9dcca70c445dcfdd639d51267"}, - {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1ca9b6085e4f866bd584fb135a041bfc32cab916e69f714a7d1d397f8c4891ca"}, - {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d5e49454f19ef621089e204f862388d29e6e8d8b162efce05208913dde5b9ad6"}, - {file = "wrapt-1.16.0-cp38-cp38-win32.whl", hash = "sha256:c31f72b1b6624c9d863fc095da460802f43a7c6868c5dda140f51da24fd47d7b"}, - {file = "wrapt-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:490b0ee15c1a55be9c1bd8609b8cecd60e325f0575fc98f50058eae366e01f41"}, - {file = "wrapt-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9b201ae332c3637a42f02d1045e1d0cccfdc41f1f2f801dafbaa7e9b4797bfc2"}, - {file = "wrapt-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2076fad65c6736184e77d7d4729b63a6d1ae0b70da4868adeec40989858eb3fb"}, - {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5cd603b575ebceca7da5a3a251e69561bec509e0b46e4993e1cac402b7247b8"}, - {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b47cfad9e9bbbed2339081f4e346c93ecd7ab504299403320bf85f7f85c7d46c"}, - {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8212564d49c50eb4565e502814f694e240c55551a5f1bc841d4fcaabb0a9b8a"}, - {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5f15814a33e42b04e3de432e573aa557f9f0f56458745c2074952f564c50e664"}, - {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db2e408d983b0e61e238cf579c09ef7020560441906ca990fe8412153e3b291f"}, - {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:edfad1d29c73f9b863ebe7082ae9321374ccb10879eeabc84ba3b69f2579d537"}, - {file = "wrapt-1.16.0-cp39-cp39-win32.whl", hash = "sha256:ed867c42c268f876097248e05b6117a65bcd1e63b779e916fe2e33cd6fd0d3c3"}, - {file = "wrapt-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:eb1b046be06b0fce7249f1d025cd359b4b80fc1c3e24ad9eca33e0dcdb2e4a35"}, - {file = "wrapt-1.16.0-py3-none-any.whl", hash = "sha256:6906c4100a8fcbf2fa735f6059214bb13b97f75b1a61777fcf6432121ef12ef1"}, - {file = "wrapt-1.16.0.tar.gz", hash = "sha256:5f370f952971e7d17c7d1ead40e49f32345a7f7a5373571ef44d800d06b1899d"}, -] - -[[package]] -name = "yarl" -version = "1.9.4" -description = "Yet another URL library" -optional = false -python-versions = ">=3.7" -files = [ - {file = "yarl-1.9.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a8c1df72eb746f4136fe9a2e72b0c9dc1da1cbd23b5372f94b5820ff8ae30e0e"}, - {file = "yarl-1.9.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a3a6ed1d525bfb91b3fc9b690c5a21bb52de28c018530ad85093cc488bee2dd2"}, - {file = "yarl-1.9.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c38c9ddb6103ceae4e4498f9c08fac9b590c5c71b0370f98714768e22ac6fa66"}, - {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d9e09c9d74f4566e905a0b8fa668c58109f7624db96a2171f21747abc7524234"}, - {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8477c1ee4bd47c57d49621a062121c3023609f7a13b8a46953eb6c9716ca392"}, - {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5ff2c858f5f6a42c2a8e751100f237c5e869cbde669a724f2062d4c4ef93551"}, - {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:357495293086c5b6d34ca9616a43d329317feab7917518bc97a08f9e55648455"}, - {file = "yarl-1.9.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:54525ae423d7b7a8ee81ba189f131054defdb122cde31ff17477951464c1691c"}, - {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:801e9264d19643548651b9db361ce3287176671fb0117f96b5ac0ee1c3530d53"}, - {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e516dc8baf7b380e6c1c26792610230f37147bb754d6426462ab115a02944385"}, - {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:7d5aaac37d19b2904bb9dfe12cdb08c8443e7ba7d2852894ad448d4b8f442863"}, - {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:54beabb809ffcacbd9d28ac57b0db46e42a6e341a030293fb3185c409e626b8b"}, - {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bac8d525a8dbc2a1507ec731d2867025d11ceadcb4dd421423a5d42c56818541"}, - {file = "yarl-1.9.4-cp310-cp310-win32.whl", hash = "sha256:7855426dfbddac81896b6e533ebefc0af2f132d4a47340cee6d22cac7190022d"}, - {file = "yarl-1.9.4-cp310-cp310-win_amd64.whl", hash = "sha256:848cd2a1df56ddbffeb375535fb62c9d1645dde33ca4d51341378b3f5954429b"}, - {file = "yarl-1.9.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:35a2b9396879ce32754bd457d31a51ff0a9d426fd9e0e3c33394bf4b9036b099"}, - {file = "yarl-1.9.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c7d56b293cc071e82532f70adcbd8b61909eec973ae9d2d1f9b233f3d943f2c"}, - {file = "yarl-1.9.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d8a1c6c0be645c745a081c192e747c5de06e944a0d21245f4cf7c05e457c36e0"}, - {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b3c1ffe10069f655ea2d731808e76e0f452fc6c749bea04781daf18e6039525"}, - {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:549d19c84c55d11687ddbd47eeb348a89df9cb30e1993f1b128f4685cd0ebbf8"}, - {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a7409f968456111140c1c95301cadf071bd30a81cbd7ab829169fb9e3d72eae9"}, - {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e23a6d84d9d1738dbc6e38167776107e63307dfc8ad108e580548d1f2c587f42"}, - {file = "yarl-1.9.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d8b889777de69897406c9fb0b76cdf2fd0f31267861ae7501d93003d55f54fbe"}, - {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:03caa9507d3d3c83bca08650678e25364e1843b484f19986a527630ca376ecce"}, - {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4e9035df8d0880b2f1c7f5031f33f69e071dfe72ee9310cfc76f7b605958ceb9"}, - {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:c0ec0ed476f77db9fb29bca17f0a8fcc7bc97ad4c6c1d8959c507decb22e8572"}, - {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:ee04010f26d5102399bd17f8df8bc38dc7ccd7701dc77f4a68c5b8d733406958"}, - {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:49a180c2e0743d5d6e0b4d1a9e5f633c62eca3f8a86ba5dd3c471060e352ca98"}, - {file = "yarl-1.9.4-cp311-cp311-win32.whl", hash = "sha256:81eb57278deb6098a5b62e88ad8281b2ba09f2f1147c4767522353eaa6260b31"}, - {file = "yarl-1.9.4-cp311-cp311-win_amd64.whl", hash = "sha256:d1d2532b340b692880261c15aee4dc94dd22ca5d61b9db9a8a361953d36410b1"}, - {file = "yarl-1.9.4-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0d2454f0aef65ea81037759be5ca9947539667eecebca092733b2eb43c965a81"}, - {file = "yarl-1.9.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:44d8ffbb9c06e5a7f529f38f53eda23e50d1ed33c6c869e01481d3fafa6b8142"}, - {file = "yarl-1.9.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:aaaea1e536f98754a6e5c56091baa1b6ce2f2700cc4a00b0d49eca8dea471074"}, - {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3777ce5536d17989c91696db1d459574e9a9bd37660ea7ee4d3344579bb6f129"}, - {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fc5fc1eeb029757349ad26bbc5880557389a03fa6ada41703db5e068881e5f2"}, - {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea65804b5dc88dacd4a40279af0cdadcfe74b3e5b4c897aa0d81cf86927fee78"}, - {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa102d6d280a5455ad6a0f9e6d769989638718e938a6a0a2ff3f4a7ff8c62cc4"}, - {file = "yarl-1.9.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09efe4615ada057ba2d30df871d2f668af661e971dfeedf0c159927d48bbeff0"}, - {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:008d3e808d03ef28542372d01057fd09168419cdc8f848efe2804f894ae03e51"}, - {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:6f5cb257bc2ec58f437da2b37a8cd48f666db96d47b8a3115c29f316313654ff"}, - {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:992f18e0ea248ee03b5a6e8b3b4738850ae7dbb172cc41c966462801cbf62cf7"}, - {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:0e9d124c191d5b881060a9e5060627694c3bdd1fe24c5eecc8d5d7d0eb6faabc"}, - {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3986b6f41ad22988e53d5778f91855dc0399b043fc8946d4f2e68af22ee9ff10"}, - {file = "yarl-1.9.4-cp312-cp312-win32.whl", hash = "sha256:4b21516d181cd77ebd06ce160ef8cc2a5e9ad35fb1c5930882baff5ac865eee7"}, - {file = "yarl-1.9.4-cp312-cp312-win_amd64.whl", hash = "sha256:a9bd00dc3bc395a662900f33f74feb3e757429e545d831eef5bb280252631984"}, - {file = "yarl-1.9.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:63b20738b5aac74e239622d2fe30df4fca4942a86e31bf47a81a0e94c14df94f"}, - {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7d7f7de27b8944f1fee2c26a88b4dabc2409d2fea7a9ed3df79b67277644e17"}, - {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c74018551e31269d56fab81a728f683667e7c28c04e807ba08f8c9e3bba32f14"}, - {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ca06675212f94e7a610e85ca36948bb8fc023e458dd6c63ef71abfd482481aa5"}, - {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5aef935237d60a51a62b86249839b51345f47564208c6ee615ed2a40878dccdd"}, - {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2b134fd795e2322b7684155b7855cc99409d10b2e408056db2b93b51a52accc7"}, - {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d25039a474c4c72a5ad4b52495056f843a7ff07b632c1b92ea9043a3d9950f6e"}, - {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f7d6b36dd2e029b6bcb8a13cf19664c7b8e19ab3a58e0fefbb5b8461447ed5ec"}, - {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:957b4774373cf6f709359e5c8c4a0af9f6d7875db657adb0feaf8d6cb3c3964c"}, - {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:d7eeb6d22331e2fd42fce928a81c697c9ee2d51400bd1a28803965883e13cead"}, - {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:6a962e04b8f91f8c4e5917e518d17958e3bdee71fd1d8b88cdce74dd0ebbf434"}, - {file = "yarl-1.9.4-cp37-cp37m-win32.whl", hash = "sha256:f3bc6af6e2b8f92eced34ef6a96ffb248e863af20ef4fde9448cc8c9b858b749"}, - {file = "yarl-1.9.4-cp37-cp37m-win_amd64.whl", hash = "sha256:ad4d7a90a92e528aadf4965d685c17dacff3df282db1121136c382dc0b6014d2"}, - {file = "yarl-1.9.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ec61d826d80fc293ed46c9dd26995921e3a82146feacd952ef0757236fc137be"}, - {file = "yarl-1.9.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8be9e837ea9113676e5754b43b940b50cce76d9ed7d2461df1af39a8ee674d9f"}, - {file = "yarl-1.9.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:bef596fdaa8f26e3d66af846bbe77057237cb6e8efff8cd7cc8dff9a62278bbf"}, - {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d47552b6e52c3319fede1b60b3de120fe83bde9b7bddad11a69fb0af7db32f1"}, - {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84fc30f71689d7fc9168b92788abc977dc8cefa806909565fc2951d02f6b7d57"}, - {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4aa9741085f635934f3a2583e16fcf62ba835719a8b2b28fb2917bb0537c1dfa"}, - {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:206a55215e6d05dbc6c98ce598a59e6fbd0c493e2de4ea6cc2f4934d5a18d130"}, - {file = "yarl-1.9.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07574b007ee20e5c375a8fe4a0789fad26db905f9813be0f9fef5a68080de559"}, - {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5a2e2433eb9344a163aced6a5f6c9222c0786e5a9e9cac2c89f0b28433f56e23"}, - {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:6ad6d10ed9b67a382b45f29ea028f92d25bc0bc1daf6c5b801b90b5aa70fb9ec"}, - {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:6fe79f998a4052d79e1c30eeb7d6c1c1056ad33300f682465e1b4e9b5a188b78"}, - {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:a825ec844298c791fd28ed14ed1bffc56a98d15b8c58a20e0e08c1f5f2bea1be"}, - {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8619d6915b3b0b34420cf9b2bb6d81ef59d984cb0fde7544e9ece32b4b3043c3"}, - {file = "yarl-1.9.4-cp38-cp38-win32.whl", hash = "sha256:686a0c2f85f83463272ddffd4deb5e591c98aac1897d65e92319f729c320eece"}, - {file = "yarl-1.9.4-cp38-cp38-win_amd64.whl", hash = "sha256:a00862fb23195b6b8322f7d781b0dc1d82cb3bcac346d1e38689370cc1cc398b"}, - {file = "yarl-1.9.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:604f31d97fa493083ea21bd9b92c419012531c4e17ea6da0f65cacdcf5d0bd27"}, - {file = "yarl-1.9.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8a854227cf581330ffa2c4824d96e52ee621dd571078a252c25e3a3b3d94a1b1"}, - {file = "yarl-1.9.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ba6f52cbc7809cd8d74604cce9c14868306ae4aa0282016b641c661f981a6e91"}, - {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a6327976c7c2f4ee6816eff196e25385ccc02cb81427952414a64811037bbc8b"}, - {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8397a3817d7dcdd14bb266283cd1d6fc7264a48c186b986f32e86d86d35fbac5"}, - {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e0381b4ce23ff92f8170080c97678040fc5b08da85e9e292292aba67fdac6c34"}, - {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23d32a2594cb5d565d358a92e151315d1b2268bc10f4610d098f96b147370136"}, - {file = "yarl-1.9.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ddb2a5c08a4eaaba605340fdee8fc08e406c56617566d9643ad8bf6852778fc7"}, - {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:26a1dc6285e03f3cc9e839a2da83bcbf31dcb0d004c72d0730e755b33466c30e"}, - {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:18580f672e44ce1238b82f7fb87d727c4a131f3a9d33a5e0e82b793362bf18b4"}, - {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:29e0f83f37610f173eb7e7b5562dd71467993495e568e708d99e9d1944f561ec"}, - {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:1f23e4fe1e8794f74b6027d7cf19dc25f8b63af1483d91d595d4a07eca1fb26c"}, - {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:db8e58b9d79200c76956cefd14d5c90af54416ff5353c5bfd7cbe58818e26ef0"}, - {file = "yarl-1.9.4-cp39-cp39-win32.whl", hash = "sha256:c7224cab95645c7ab53791022ae77a4509472613e839dab722a72abe5a684575"}, - {file = "yarl-1.9.4-cp39-cp39-win_amd64.whl", hash = "sha256:824d6c50492add5da9374875ce72db7a0733b29c2394890aef23d533106e2b15"}, - {file = "yarl-1.9.4-py3-none-any.whl", hash = "sha256:928cecb0ef9d5a7946eb6ff58417ad2fe9375762382f1bf5c55e61645f2c43ad"}, - {file = "yarl-1.9.4.tar.gz", hash = "sha256:566db86717cf8080b99b58b083b773a908ae40f06681e87e589a976faf8246bf"}, -] - -[package.dependencies] -idna = ">=2.0" -multidict = ">=4.0" - -[metadata] -lock-version = "2.0" -python-versions = ">=3.11,<3.13" -content-hash = "674baa9bbe0b000407c2864d512462b9fa77f42ed81da2fea0fc8e8b944e403e" diff --git a/pyproject.toml b/pyproject.toml deleted file mode 100644 index a6261c8..0000000 --- a/pyproject.toml +++ /dev/null @@ -1,45 +0,0 @@ -[tool.poetry] -name = "revanced-api" -version = "0.1.0" -description = "" -authors = ["Alexandre Teles "] -license = "AGPLv3" -readme = "README.md" - -[tool.poetry.dependencies] -python = ">=3.11,<3.13" -aiohttp = { version = "^3.9.1", extras = ["speedups"] } -sanic = { version = "^23.12.1", extras = ["ext"] } -ujson = "^5.9.0" -pydantic = "^1.10.13" -asyncstdlib = "^3.12.0" -cytoolz = "^0.12.2" -beautifulsoup4 = "^4.12.2" -lxml = "^5.1.0" -sqlalchemy = "^2.0.25" -sanic-beskar = "^2.3.2" -bson = "^0.5.10" -fastpbkdf2 = "^0.2" -cryptography = "^41.0.7" -sanic-limiter = { git = "https://github.com/Omegastick/sanic-limiter" } -sentry-sdk = { extras = ["sanic"], version = "^1.39.2" } - -[tool.poetry.dev-dependencies] -mypy = "^1.8.0" -types-ujson = "^5.9.0.0" -types-aiofiles = "^23.2.0.20240106" -types-beautifulsoup4 = "^4.12.0.20240106" - -[tool.poetry.group.dev.dependencies] -setuptools = "^69.2.0" - -[tool.pytest.ini_options] -asyncio_mode = "auto" -filterwarnings = [ - "ignore::DeprecationWarning", - "ignore::pytest.PytestCollectionWarning", -] - -[build-system] -requires = ["poetry-core"] -build-backend = "poetry.core.masonry.api" diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 62ff52d..0000000 --- a/requirements.txt +++ /dev/null @@ -1,62 +0,0 @@ -aiodns==3.1.1 ; (sys_platform == "linux" or sys_platform == "darwin") and python_version >= "3.11" and python_version < "3.13" -aiofiles==23.2.1 ; python_version >= "3.11" and python_version < "3.13" -aiohttp[speedups]==3.9.1 ; python_version >= "3.11" and python_version < "3.13" -aiosignal==1.3.1 ; python_version >= "3.11" and python_version < "3.13" -argon2-cffi==23.1.0 ; python_version >= "3.11" and python_version < "3.13" -argon2-cffi-bindings==21.2.0 ; python_version >= "3.11" and python_version < "3.13" -asyncstdlib==3.12.0 ; python_version >= "3.11" and python_version < "3.13" -attrs==23.2.0 ; python_version >= "3.11" and python_version < "3.13" -beautifulsoup4==4.12.2 ; python_version >= "3.11" and python_version < "3.13" -brotli==1.1.0 ; platform_python_implementation == "CPython" and python_version >= "3.11" and python_version < "3.13" -brotlicffi==1.1.0.0 ; platform_python_implementation != "CPython" and python_version >= "3.11" and python_version < "3.13" -bson==0.5.10 ; python_version >= "3.11" and python_version < "3.13" -certifi==2023.11.17 ; python_version >= "3.11" and python_version < "3.13" -cffi==1.16.0 ; python_version >= "3.11" and python_version < "3.13" -cryptography==41.0.7 ; python_version >= "3.11" and python_version < "3.13" -cytoolz==0.12.2 ; python_version >= "3.11" and python_version < "3.13" -deprecated==1.2.14 ; python_version >= "3.11" and python_version < "3.13" -fastpbkdf2==0.2 ; python_version >= "3.11" and python_version < "3.13" -frozenlist==1.4.1 ; python_version >= "3.11" and python_version < "3.13" -greenlet==3.0.3 ; python_version >= "3.11" and python_version < "3.13" and (platform_machine == "aarch64" or platform_machine == "ppc64le" or platform_machine == "x86_64" or platform_machine == "amd64" or platform_machine == "AMD64" or platform_machine == "win32" or platform_machine == "WIN32") -html5tagger==1.3.0 ; python_version >= "3.11" and python_version < "3.13" -httptools==0.6.1 ; python_version >= "3.11" and python_version < "3.13" -idna==3.6 ; python_version >= "3.11" and python_version < "3.13" -importlib-resources==6.1.1 ; python_version >= "3.11" and python_version < "3.13" -iso8601==2.1.0 ; python_version >= "3.11" and python_version < "3.13" -jinja2==3.1.3 ; python_version >= "3.11" and python_version < "3.13" -limits==3.7.0 ; python_version >= "3.11" and python_version < "3.13" -lxml==5.1.0 ; python_version >= "3.11" and python_version < "3.13" -markupsafe==2.1.3 ; python_version >= "3.11" and python_version < "3.13" -multidict==6.0.5 ; python_version >= "3.11" and python_version < "3.13" -packaging==23.2 ; python_version >= "3.11" and python_version < "3.13" -passlib==1.7.4 ; python_version >= "3.11" and python_version < "3.13" -pendulum==3.0.0 ; python_version >= "3.11" and python_version < "3.13" -py-buzz==4.1.0 ; python_version >= "3.11" and python_version < "3.13" -pycares==4.4.0 ; (sys_platform == "linux" or sys_platform == "darwin") and python_version >= "3.11" and python_version < "3.13" -pycparser==2.21 ; python_version >= "3.11" and python_version < "3.13" -pycryptodomex==3.20.0 ; python_version >= "3.11" and python_version < "3.13" -pydantic==1.10.13 ; python_version >= "3.11" and python_version < "3.13" -pyjwt==2.8.0 ; python_version >= "3.11" and python_version < "3.13" -pyseto==1.7.7 ; python_version >= "3.11" and python_version < "3.13" -python-dateutil==2.8.2 ; python_version >= "3.11" and python_version < "3.13" -pyyaml==6.0.1 ; python_version >= "3.11" and python_version < "3.13" -sanic==23.12.1 ; python_version >= "3.11" and python_version < "3.13" -sanic-beskar==2.3.2 ; python_version >= "3.11" and python_version < "3.13" -sanic-ext==23.12.0 ; python_version >= "3.11" and python_version < "3.13" -sanic-limiter @ git+https://github.com/Omegastick/sanic-limiter@843e13144aa21d843ce212a7c1db31b72ce8a103 ; python_version >= "3.11" and python_version < "3.13" -sanic-routing==23.12.0 ; python_version >= "3.11" and python_version < "3.13" -sanic[ext]==23.12.1 ; python_version >= "3.11" and python_version < "3.13" -sentry-sdk[sanic]==1.39.2 ; python_version >= "3.11" and python_version < "3.13" -six==1.16.0 ; python_version >= "3.11" and python_version < "3.13" -soupsieve==2.5 ; python_version >= "3.11" and python_version < "3.13" -sqlalchemy==2.0.25 ; python_version >= "3.11" and python_version < "3.13" -toolz==0.12.0 ; python_version >= "3.11" and python_version < "3.13" -tracerite==1.1.1 ; python_version >= "3.11" and python_version < "3.13" -typing-extensions==4.9.0 ; python_version >= "3.11" and python_version < "3.13" -tzdata==2023.4 ; python_version >= "3.11" and python_version < "3.13" -ujson==5.9.0 ; python_version >= "3.11" and python_version < "3.13" -urllib3==2.1.0 ; python_version >= "3.11" and python_version < "3.13" -uvloop==0.19.0 ; sys_platform != "win32" and implementation_name == "cpython" and python_version >= "3.11" and python_version < "3.13" -websockets==12.0 ; python_version >= "3.11" and python_version < "3.13" -wrapt==1.16.0 ; python_version >= "3.11" and python_version < "3.13" -yarl==1.9.4 ; python_version >= "3.11" and python_version < "3.13" diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 0000000..37eb408 --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,7 @@ +rootProject.name = "app.revanced.revanced-api" + +buildCache { + local { + isEnabled = "CI" !in System.getenv() + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/api/Application.kt b/src/main/kotlin/app/revanced/api/Application.kt new file mode 100644 index 0000000..d7dd38b --- /dev/null +++ b/src/main/kotlin/app/revanced/api/Application.kt @@ -0,0 +1,24 @@ +package app.revanced.api + +import app.revanced.api.plugins.* +import io.github.cdimascio.dotenv.Dotenv +import io.ktor.server.application.* +import io.ktor.server.engine.* +import io.ktor.server.netty.* + +fun main() { + Dotenv.load() + + embeddedServer(Netty, port = 8080, host = "0.0.0.0", configure = { + connectionGroupSize = 1 + workerGroupSize = 1 + callGroupSize = 1 + }) { + configureHTTP() + configureSerialization() + configureDatabases() + configureSecurity() + configureDependencies() + configureRouting() + }.start(wait = true) +} diff --git a/src/main/kotlin/app/revanced/api/backend/Backend.kt b/src/main/kotlin/app/revanced/api/backend/Backend.kt new file mode 100644 index 0000000..efb1d02 --- /dev/null +++ b/src/main/kotlin/app/revanced/api/backend/Backend.kt @@ -0,0 +1,140 @@ +package app.revanced.api.backend + +import io.ktor.client.* +import io.ktor.client.engine.okhttp.* +import kotlinx.serialization.Serializable + +/** + * The backend of the application used to get data for the API. + * + * @param httpClientConfig The configuration of the HTTP client. + */ +abstract class Backend( + httpClientConfig: HttpClientConfig.() -> Unit = {} +) { + protected val client: HttpClient = HttpClient(OkHttp, httpClientConfig) + + /** + * A user. + * + * @property name The name of the user. + * @property avatarUrl The URL to the avatar of the user. + * @property profileUrl The URL to the profile of the user. + */ + interface User { + val name: String + val avatarUrl: String + val profileUrl: String + } + + /** + * An organization. + * + * @property members The members of the organization. + */ + class Organization( + val members: Set + ) { + /** + * A member of an organization. + * + * @property name The name of the member. + * @property avatarUrl The URL to the avatar of the member. + * @property profileUrl The URL to the profile of the member. + * @property bio The bio of the member. + * @property gpgKeysUrl The URL to the GPG keys of the member. + */ + @Serializable + class Member ( + override val name: String, + override val avatarUrl: String, + override val profileUrl: String, + val bio: String?, + val gpgKeysUrl: String? + ) : User + + /** + * A repository of an organization. + * + * @property contributors The contributors of the repository. + */ + class Repository( + val contributors: Set + ) { + /** + * A contributor of a repository. + * + * @property name The name of the contributor. + * @property avatarUrl The URL to the avatar of the contributor. + * @property profileUrl The URL to the profile of the contributor. + */ + @Serializable + class Contributor( + override val name: String, + override val avatarUrl: String, + override val profileUrl: String + ) : User + + /** + * A release of a repository. + * + * @property tag The tag of the release. + * @property assets The assets of the release. + * @property createdAt The date and time the release was created. + * @property releaseNote The release note of the release. + */ + @Serializable + class Release( + val tag: String, + val releaseNote: String, + val createdAt: String, + val assets: Set + ) { + /** + * An asset of a release. + * + * @property downloadUrl The URL to download the asset. + */ + @Serializable + class Asset( + val downloadUrl: String + ) + } + } + } + + /** + * Get a release of a repository. + * + * @param owner The owner of the repository. + * @param repository The name of the repository. + * @param tag The tag of the release. If null, the latest release is returned. + * @param preRelease Whether to return a pre-release. + * If no pre-release exists, the latest release is returned. + * If tag is not null, this parameter is ignored. + * @return The release. + */ + abstract suspend fun getRelease( + owner: String, + repository: String, + tag: String? = null, + preRelease: Boolean = false + ): Organization.Repository.Release + + /** + * Get the contributors of a repository. + * + * @param owner The owner of the repository. + * @param repository The name of the repository. + * @return The contributors. + */ + abstract suspend fun getContributors(owner: String, repository: String): Set + + /** + * Get the members of an organization. + * + * @param organization The name of the organization. + * @return The members. + */ + abstract suspend fun getMembers(organization: String): Set +} diff --git a/src/main/kotlin/app/revanced/api/backend/github/GitHubBackend.kt b/src/main/kotlin/app/revanced/api/backend/github/GitHubBackend.kt new file mode 100644 index 0000000..db144f5 --- /dev/null +++ b/src/main/kotlin/app/revanced/api/backend/github/GitHubBackend.kt @@ -0,0 +1,116 @@ +package app.revanced.api.backend.github + +import app.revanced.api.backend.Backend +import app.revanced.api.backend.github.api.Request +import io.ktor.client.call.* +import io.ktor.client.plugins.* +import io.ktor.client.plugins.auth.* +import io.ktor.client.plugins.auth.providers.* +import io.ktor.client.plugins.cache.* +import io.ktor.client.plugins.contentnegotiation.* +import io.ktor.client.plugins.resources.* +import app.revanced.api.backend.github.api.Request.Organization.Repository.Releases +import app.revanced.api.backend.github.api.Request.Organization.Repository.Contributors +import app.revanced.api.backend.github.api.Request.Organization.Members +import app.revanced.api.backend.github.api.Response +import app.revanced.api.backend.github.api.Response.Organization.Repository.Release +import app.revanced.api.backend.github.api.Response.Organization.Repository.Contributor +import app.revanced.api.backend.github.api.Response.Organization.Member +import io.ktor.client.plugins.resources.Resources +import io.ktor.serialization.kotlinx.json.* +import kotlinx.coroutines.* +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonNamingStrategy + +@OptIn(ExperimentalSerializationApi::class) +class GitHubBackend(token: String? = null) : Backend({ + install(HttpCache) + install(Resources) + install(ContentNegotiation) { + json(Json { + ignoreUnknownKeys = true + namingStrategy = JsonNamingStrategy.SnakeCase + }) + } + + defaultRequest { url("https://api.github.com") } + + token?.let { + install(Auth) { + bearer { + loadTokens { + BearerTokens( + accessToken = it, + refreshToken = "" // Required dummy value + ) + } + + sendWithoutRequest { true } + } + } + } +}) { + override suspend fun getRelease( + owner: String, + repository: String, + tag: String?, + preRelease: Boolean + ): Organization.Repository.Release { + val release = if (preRelease) { + val releases: Set = client.get(Releases(owner, repository)).body() + releases.firstOrNull { it.preReleases } ?: releases.first() // Latest pre-release or latest release + } else { + client.get( + tag?.let { Releases.Tag(owner, repository, it) } + ?: Releases.Latest(owner, repository) + ).body() + } + + return Organization.Repository.Release( + tag = release.tagName, + releaseNote = release.body, + createdAt = release.createdAt, + assets = release.assets.map { + Organization.Repository.Release.Asset( + downloadUrl = it.browserDownloadUrl + ) + }.toSet() + ) + } + + override suspend fun getContributors(owner: String, repository: String): Set { + val contributors: Set = client.get(Contributors(owner, repository)).body() + + return contributors.map { + Organization.Repository.Contributor( + name = it.login, + avatarUrl = it.avatarUrl, + profileUrl = it.url + ) + }.toSet() + } + + override suspend fun getMembers(organization: String): Set { + // Get the list of members of the organization. + val members: Set = client.get(Members(organization)).body>() + + return runBlocking(Dispatchers.Default) { + members.map { member -> + // Map the member to a user in order to get the bio. + async { + client.get(Request.User(member.login)).body() + } + } + }.awaitAll().map { user -> + // Map the user back to a member. + Organization.Member( + name = user.login, + avatarUrl = user.avatarUrl, + profileUrl = user.url, + bio = user.bio, + gpgKeysUrl = "https://github.com/${user.login}.gpg", + ) + }.toSet() + } +} diff --git a/src/main/kotlin/app/revanced/api/backend/github/api/RequestResource.kt b/src/main/kotlin/app/revanced/api/backend/github/api/RequestResource.kt new file mode 100644 index 0000000..ab78199 --- /dev/null +++ b/src/main/kotlin/app/revanced/api/backend/github/api/RequestResource.kt @@ -0,0 +1,26 @@ +package app.revanced.api.backend.github.api + +import io.ktor.resources.* + +class Request { + @Resource("/users/{username}") + class User(val username: String) + class Organization { + @Resource("/orgs/{org}/members") + class Members(val org: String) + + class Repository { + @Resource("/repos/{owner}/{repo}/contributors") + class Contributors(val owner: String, val repo: String) + + @Resource("/repos/{owner}/{repo}/releases") + class Releases(val owner: String, val repo: String) { + @Resource("/repos/{owner}/{repo}/releases/tags/{tag}") + class Tag(val owner: String, val repo: String, val tag: String) + + @Resource("/repos/{owner}/{repo}/releases/latest") + class Latest(val owner: String, val repo: String) + } + } + } +} diff --git a/src/main/kotlin/app/revanced/api/backend/github/api/ResponseSchema.kt b/src/main/kotlin/app/revanced/api/backend/github/api/ResponseSchema.kt new file mode 100644 index 0000000..e286b95 --- /dev/null +++ b/src/main/kotlin/app/revanced/api/backend/github/api/ResponseSchema.kt @@ -0,0 +1,52 @@ +package app.revanced.api.backend.github.api + +import kotlinx.serialization.Serializable + + +class Response { + interface IUser { + val login: String + val avatarUrl: String + val url: String + } + + @Serializable + class User ( + override val login: String, + override val avatarUrl: String, + override val url: String, + val bio: String?, + ) : IUser + + class Organization { + @Serializable + class Member( + override val login: String, + override val avatarUrl: String, + override val url: String, + ) : IUser + + class Repository { + @Serializable + class Contributor( + override val login: String, + override val avatarUrl: String, + override val url: String, + ) : IUser + + @Serializable + class Release( + val tagName: String, + val assets: Set, + val preReleases: Boolean, + val createdAt: String, + val body: String + ) { + @Serializable + class Asset( + val browserDownloadUrl: String + ) + } + } + } +} diff --git a/src/main/kotlin/app/revanced/api/plugins/Databases.kt b/src/main/kotlin/app/revanced/api/plugins/Databases.kt new file mode 100644 index 0000000..c023db1 --- /dev/null +++ b/src/main/kotlin/app/revanced/api/plugins/Databases.kt @@ -0,0 +1,49 @@ +package app.revanced.api.plugins + +import io.ktor.http.* +import io.ktor.server.application.* +import io.ktor.server.request.* +import io.ktor.server.response.* +import io.ktor.server.routing.* +import org.jetbrains.exposed.sql.* + +fun Application.configureDatabases() { + val database = Database.connect( + url = "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1", + user = "root", + driver = "org.h2.Driver", + password = "" + ) + val userService = UserService(database) + routing { + // Create user + post("/users") { + val user = call.receive() + val id = userService.create(user) + call.respond(HttpStatusCode.Created, id) + } + // Read user + get("/users/{id}") { + val id = call.parameters["id"]?.toInt() ?: throw IllegalArgumentException("Invalid ID") + val user = userService.read(id) + if (user != null) { + call.respond(HttpStatusCode.OK, user) + } else { + call.respond(HttpStatusCode.NotFound) + } + } + // Update user + put("/users/{id}") { + val id = call.parameters["id"]?.toInt() ?: throw IllegalArgumentException("Invalid ID") + val user = call.receive() + userService.update(id, user) + call.respond(HttpStatusCode.OK) + } + // Delete user + delete("/users/{id}") { + val id = call.parameters["id"]?.toInt() ?: throw IllegalArgumentException("Invalid ID") + userService.delete(id) + call.respond(HttpStatusCode.OK) + } + } +} diff --git a/src/main/kotlin/app/revanced/api/plugins/Dependencies.kt b/src/main/kotlin/app/revanced/api/plugins/Dependencies.kt new file mode 100644 index 0000000..3950ec9 --- /dev/null +++ b/src/main/kotlin/app/revanced/api/plugins/Dependencies.kt @@ -0,0 +1,21 @@ +package app.revanced.api.plugins + +import app.revanced.api.backend.github.GitHubBackend +import io.github.cdimascio.dotenv.Dotenv +import io.ktor.server.application.* +import org.koin.core.context.startKoin +import org.koin.dsl.module +import org.koin.ktor.ext.inject +import org.koin.ktor.plugin.Koin + +fun Application.configureDependencies() { + + install(Koin) { + modules( + module { + single { Dotenv.load() } + single { GitHubBackend(get().get("GITHUB_TOKEN")) } + } + ) + } +} diff --git a/src/main/kotlin/app/revanced/api/plugins/HTTP.kt b/src/main/kotlin/app/revanced/api/plugins/HTTP.kt new file mode 100644 index 0000000..8ea4b73 --- /dev/null +++ b/src/main/kotlin/app/revanced/api/plugins/HTTP.kt @@ -0,0 +1,37 @@ +package app.revanced.api.plugins + +import io.ktor.http.* +import io.ktor.http.content.* +import io.ktor.server.application.* +import io.ktor.server.plugins.cachingheaders.* +import io.ktor.server.plugins.conditionalheaders.* +import io.ktor.server.plugins.cors.routing.* +import io.ktor.server.plugins.openapi.* +import io.ktor.server.plugins.swagger.* +import io.ktor.server.routing.* + +fun Application.configureHTTP() { + install(ConditionalHeaders) + routing { + swaggerUI(path = "openapi") + } + routing { + openAPI(path = "openapi") + } + install(CORS) { + allowMethod(HttpMethod.Options) + allowMethod(HttpMethod.Put) + allowMethod(HttpMethod.Delete) + allowMethod(HttpMethod.Patch) + allowHeader(HttpHeaders.Authorization) + anyHost() // @TODO: Don't do this in production if possible. Try to limit it. + } + install(CachingHeaders) { + options { _, outgoingContent -> + when (outgoingContent.contentType?.withoutParameters()) { + ContentType.Text.CSS -> CachingOptions(CacheControl.MaxAge(maxAgeSeconds = 24 * 60 * 60)) + else -> null + } + } + } +} diff --git a/src/main/kotlin/app/revanced/api/plugins/Routing.kt b/src/main/kotlin/app/revanced/api/plugins/Routing.kt new file mode 100644 index 0000000..c14a7c3 --- /dev/null +++ b/src/main/kotlin/app/revanced/api/plugins/Routing.kt @@ -0,0 +1,45 @@ +package app.revanced.api.plugins + +import app.revanced.api.backend.github.GitHubBackend +import io.github.cdimascio.dotenv.Dotenv +import io.ktor.http.* +import io.ktor.server.application.* +import io.ktor.server.http.content.* +import io.ktor.server.response.* +import io.ktor.server.routing.* +import org.koin.ktor.ext.inject + +fun Application.configureRouting() { + val backend by inject() + val dotenv by inject() + + routing { + route("/v${dotenv.get("API_VERSION", "1")}") { + route("/manager") { + get("/contributors") { + val contributors = backend.getContributors("revanced", "revanced-patches") + + call.respond(contributors) + } + + get("/members") { + val members = backend.getMembers("revanced") + + call.respond(members) + } + } + + route("/patches") { + + } + + route("/ping") { + handle { + call.respond(HttpStatusCode.NoContent) + } + } + } + + staticResources("/", "static") + } +} diff --git a/src/main/kotlin/app/revanced/api/plugins/Security.kt b/src/main/kotlin/app/revanced/api/plugins/Security.kt new file mode 100644 index 0000000..38bcf7f --- /dev/null +++ b/src/main/kotlin/app/revanced/api/plugins/Security.kt @@ -0,0 +1,30 @@ +package app.revanced.api.plugins + +import com.auth0.jwt.JWT +import com.auth0.jwt.algorithms.Algorithm +import io.ktor.server.application.* +import io.ktor.server.auth.* +import io.ktor.server.auth.jwt.* + +fun Application.configureSecurity() { + // Please read the jwt property from the config file if you are using EngineMain + val jwtAudience = "jwt-audience" + val jwtDomain = "https://jwt-provider-domain/" + val jwtRealm = "ktor sample app" + val jwtSecret = "secret" + authentication { + jwt { + realm = jwtRealm + verifier( + JWT + .require(Algorithm.HMAC256(jwtSecret)) + .withAudience(jwtAudience) + .withIssuer(jwtDomain) + .build() + ) + validate { credential -> + if (credential.payload.audience.contains(jwtAudience)) JWTPrincipal(credential.payload) else null + } + } + } +} diff --git a/src/main/kotlin/app/revanced/api/plugins/Serialization.kt b/src/main/kotlin/app/revanced/api/plugins/Serialization.kt new file mode 100644 index 0000000..e2cb0d1 --- /dev/null +++ b/src/main/kotlin/app/revanced/api/plugins/Serialization.kt @@ -0,0 +1,11 @@ +package app.revanced.api.plugins + +import io.ktor.serialization.kotlinx.json.* +import io.ktor.server.application.* +import io.ktor.server.plugins.contentnegotiation.* + +fun Application.configureSerialization() { + install(ContentNegotiation) { + json() + } +} diff --git a/src/main/kotlin/app/revanced/api/plugins/UsersSchema.kt b/src/main/kotlin/app/revanced/api/plugins/UsersSchema.kt new file mode 100644 index 0000000..9867997 --- /dev/null +++ b/src/main/kotlin/app/revanced/api/plugins/UsersSchema.kt @@ -0,0 +1,59 @@ +package app.revanced.api.plugins + +import org.jetbrains.exposed.sql.transactions.transaction +import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction +import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq +import kotlinx.serialization.Serializable +import kotlinx.coroutines.Dispatchers +import org.jetbrains.exposed.sql.* + +@Serializable +data class ExposedUser(val name: String, val age: Int) +class UserService(private val database: Database) { + object Users : Table() { + val id = integer("id").autoIncrement() + val name = varchar("name", length = 50) + val age = integer("age") + + override val primaryKey = PrimaryKey(id) + } + + init { + transaction(database) { + SchemaUtils.create(Users) + } + } + + suspend fun dbQuery(block: suspend () -> T): T = + newSuspendedTransaction(Dispatchers.IO) { block() } + + suspend fun create(user: ExposedUser): Int = dbQuery { + Users.insert { + it[name] = user.name + it[age] = user.age + }[Users.id] + } + + suspend fun read(id: Int): ExposedUser? { + return dbQuery { + Users.select { Users.id eq id } + .map { ExposedUser(it[Users.name], it[Users.age]) } + .singleOrNull() + } + } + + suspend fun update(id: Int, user: ExposedUser) { + dbQuery { + Users.update({ Users.id eq id }) { + it[name] = user.name + it[age] = user.age + } + } + } + + suspend fun delete(id: Int) { + dbQuery { + Users.deleteWhere { Users.id.eq(id) } + } + } +} diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml new file mode 100644 index 0000000..3e11d78 --- /dev/null +++ b/src/main/resources/logback.xml @@ -0,0 +1,12 @@ + + + + %d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/openapi/documentation.yaml b/src/main/resources/openapi/documentation.yaml new file mode 100644 index 0000000..2e6c6d7 --- /dev/null +++ b/src/main/resources/openapi/documentation.yaml @@ -0,0 +1,23 @@ +openapi: "3.0.3" +info: + title: "Application API" + description: "Application API" + version: "1.0.0" +servers: + - url: "http://0.0.0.0:8080" +paths: + /: + get: + description: "Hello World!" + responses: + "200": + description: "OK" + content: + text/plain: + schema: + type: "string" + examples: + Example#1: + value: "Hello World!" +components: + schemas: diff --git a/src/main/resources/static/about.json b/src/main/resources/static/about.json new file mode 100644 index 0000000..8f62d26 --- /dev/null +++ b/src/main/resources/static/about.json @@ -0,0 +1,99 @@ +{ + "name": "ReVanced", + "about": "ReVanced was born out of Vanced's discontinuation and it is our goal to continue the legacy of what Vanced left behind. Thanks to ReVanced Patcher, it's possible to create long-lasting patches for nearly any Android app. ReVanced's patching system is designed to allow patches to work on new versions of the apps automatically with bare minimum maintenance.", + "branding": + { + "logo": "https://raw.githubusercontent.com/ReVanced/revanced-branding/main/assets/revanced-logo/revanced-logo.svg" + }, + "contact": + { + "email": "contact@revanced.app" + }, + "socials": + [ + { + "name": "Website", + "url": "https://revanced.app", + "preferred": true + }, + { + "name": "GitHub", + "url": "https://github.com/revanced", + "preferred": false + }, + { + "name": "Twitter", + "url": "https://twitter.com/revancedapp", + "preferred": false + }, + { + "name": "Discord", + "url": "https://revanced.app/discord", + "preferred": true + }, + { + "name": "Reddit", + "url": "https://www.reddit.com/r/revancedapp", + "preferred": false + }, + { + "name": "Telegram", + "url": "https://t.me/app_revanced", + "preferred": false + }, + { + "name": "YouTube", + "url": "https://www.youtube.com/@ReVanced", + "preferred": false + } + ], + "donations": + { + "wallets": + [ + { + "network": "Bitcoin", + "currency_code": "BTC", + "address": "bc1q4x8j6mt27y5gv0q625t8wkr87ruy8fprpy4v3f", + "preferred": false + }, + { + "network": "Dogecoin", + "currency_code": "DOGE", + "address": "D8GH73rNjudgi6bS2krrXWEsU9KShedLXp", + "preferred": true + }, + { + "network": "Ethereum", + "currency_code": "ETH", + "address": "0x7ab4091e00363654bf84B34151225742cd92FCE5", + "preferred": false + }, + { + "network": "Litecoin", + "currency_code": "LTC", + "address": "LbJi8EuoDcwaZvykcKmcrM74jpjde23qJ2", + "preferred": false + }, + { + "network": "Monero", + "currency_code": "XMR", + "address": "46YwWDbZD6jVptuk5mLHsuAmh1BnUMSjSNYacozQQEraWSQ93nb2yYVRHoMR6PmFYWEHsLHg9tr1cH5M8Rtn7YaaGQPCjSh", + "preferred": false + } + ], + "links": + [ + { + "name": "Open Collective", + "url": "https://opencollective.com/revanced", + "preferred": true + }, + { + "name": "GitHub Sponsors", + "url": "https://github.com/sponsors/ReVanced", + "preferred": false + } + ] + } +} \ No newline at end of file diff --git a/src/main/resources/static/robots.txt b/src/main/resources/static/robots.txt new file mode 100644 index 0000000..77470cb --- /dev/null +++ b/src/main/resources/static/robots.txt @@ -0,0 +1,2 @@ +User-agent: * +Disallow: / \ No newline at end of file diff --git a/src/test/kotlin/app/revanced/ApplicationTest.kt b/src/test/kotlin/app/revanced/ApplicationTest.kt new file mode 100644 index 0000000..c4ba45b --- /dev/null +++ b/src/test/kotlin/app/revanced/ApplicationTest.kt @@ -0,0 +1,21 @@ +package app.revanced + +import app.revanced.api.plugins.configureRouting +import io.ktor.client.request.* +import io.ktor.client.statement.* +import io.ktor.http.* +import io.ktor.server.testing.* +import kotlin.test.* + +class ApplicationTest { + @Test + fun testRoot() = testApplication { + application { + configureRouting() + } + client.get("/").apply { + assertEquals(HttpStatusCode.OK, status) + assertEquals("Hello World!", bodyAsText()) + } + } +} diff --git a/static/img/favicon.ico b/static/img/favicon.ico deleted file mode 100644 index 0a4ba62047862221c80b95a793c80a4acb67a6c3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20239 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 JPDHLkV1l{h5JLa} From 9999b242ad05dcea9e6b021e3ed4eeead4567805 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Mon, 29 Jan 2024 03:18:31 +0100 Subject: [PATCH 006/102] feat: Implement more routes and add configuration --- .env.example | 2 +- .gitignore | 3 +- build.gradle.kts | 4 ++ configuration.example.toml | 5 ++ configuration.toml | 14 +++++ gradle/libs.versions.toml | 3 + src/main/kotlin/app/revanced/api/APISchema.kt | 55 ++++++++++++++++ .../kotlin/app/revanced/api/Application.kt | 2 - .../app/revanced/api/ConfigurationSchema.kt | 17 +++++ .../app/revanced/api/backend/Backend.kt | 50 +++++++-------- .../api/backend/github/GitHubBackend.kt | 53 ++++++++-------- .../api/backend/github/api/ResponseSchema.kt | 27 ++++---- .../app/revanced/api/plugins/Dependencies.kt | 20 +++++- .../app/revanced/api/plugins/Routing.kt | 63 +++++++++++++++---- .../static/{about.json => api/about} | 0 15 files changed, 236 insertions(+), 82 deletions(-) create mode 100644 configuration.example.toml create mode 100644 configuration.toml create mode 100644 src/main/kotlin/app/revanced/api/APISchema.kt create mode 100644 src/main/kotlin/app/revanced/api/ConfigurationSchema.kt rename src/main/resources/static/{about.json => api/about} (100%) diff --git a/.env.example b/.env.example index 6129bbb..2bdac94 100644 --- a/.env.example +++ b/.env.example @@ -1,2 +1,2 @@ GITHUB_TOKEN= -API_VERSION= \ No newline at end of file +CONFIG_FILE_PATH= \ No newline at end of file diff --git a/.gitignore b/.gitignore index d16fa9e..dd55c73 100644 --- a/.gitignore +++ b/.gitignore @@ -36,4 +36,5 @@ out/ .vscode/ ### Project ### -.env \ No newline at end of file +.env +configuration.toml \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index 822a45f..d1c7c33 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -42,6 +42,10 @@ dependencies { implementation(libs.exposed.core) implementation(libs.exposed.jdbc) implementation(libs.dotenv.kotlin) + implementation(libs.ktoml.core) + implementation(libs.ktoml.file) + testImplementation(libs.ktor.server.tests) testImplementation(libs.kotlin.test.junit) + } diff --git a/configuration.example.toml b/configuration.example.toml new file mode 100644 index 0000000..5935fd6 --- /dev/null +++ b/configuration.example.toml @@ -0,0 +1,5 @@ +organization = "org" +patches-repository = "patches" +integrations-repositories = ["integrations"] +contributors-repositories = ["patches", "integrations"] +api-version = 1 diff --git a/configuration.toml b/configuration.toml new file mode 100644 index 0000000..dcef633 --- /dev/null +++ b/configuration.toml @@ -0,0 +1,14 @@ +organization = "revanced" +patches-repository = "revanced-patches" +integrations-repositories = [ + "revanced-integrations" +] +contributors-repositories = [ + "revanced-patcher", + "revanced-patches", + "revanced-integrations", + "revanced-website", + "revanced-cli", + "revanced-manager", +] +api-version = 1 diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ae96f6a..5074e91 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -6,6 +6,7 @@ h2="2.1.214" koin="3.5.3" dotenv="6.4.1" ktor = "2.3.7" +ktoml = "0.5.1" [libraries] ktor-client-core = { module = "io.ktor:ktor-client-core" } @@ -34,6 +35,8 @@ exposed-jdbc = { module = "org.jetbrains.exposed:exposed-jdbc", version.ref = "e dotenv-kotlin = { module = "io.github.cdimascio:dotenv-kotlin", version.ref = "dotenv" } ktor-server-tests = { module = "io.ktor:ktor-server-tests" } kotlin-test-junit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kotlin" } +ktoml-core = { module = "com.akuleshov7:ktoml-core", version.ref = "ktoml" } +ktoml-file = { module = "com.akuleshov7:ktoml-file", version.ref = "ktoml" } [plugins] serilization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } diff --git a/src/main/kotlin/app/revanced/api/APISchema.kt b/src/main/kotlin/app/revanced/api/APISchema.kt new file mode 100644 index 0000000..5e2de99 --- /dev/null +++ b/src/main/kotlin/app/revanced/api/APISchema.kt @@ -0,0 +1,55 @@ +package app.revanced.api + +import kotlinx.serialization.Serializable + +@Serializable +class APIRelease( + val version: String, + val createdAt: String, + val changelog: String, + val assets: Set +) + +interface APIUser { + val name: String + val avatarUrl: String + val url: String +} + +@Serializable +class APIMember( + override val name: String, + override val avatarUrl: String, + override val url: String, + val gpgKeysUrl: String +) : APIUser + +@Serializable +class APIContributor( + override val name: String, + override val avatarUrl: String, + override val url: String, + val contributions: Int, +) : APIUser + +@Serializable +class APIContributable( + val name: String, + val contributors: Set +) + +@Serializable +class APIAsset( + val downloadUrl: String, +) { + val type = when { + downloadUrl.endsWith(".jar") -> "patches" + downloadUrl.endsWith(".apk") -> "integrations" + else -> "unknown" + } +} + +@Serializable +class APIReleaseVersion( + val version: String +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/api/Application.kt b/src/main/kotlin/app/revanced/api/Application.kt index d7dd38b..0b7c571 100644 --- a/src/main/kotlin/app/revanced/api/Application.kt +++ b/src/main/kotlin/app/revanced/api/Application.kt @@ -7,8 +7,6 @@ import io.ktor.server.engine.* import io.ktor.server.netty.* fun main() { - Dotenv.load() - embeddedServer(Netty, port = 8080, host = "0.0.0.0", configure = { connectionGroupSize = 1 workerGroupSize = 1 diff --git a/src/main/kotlin/app/revanced/api/ConfigurationSchema.kt b/src/main/kotlin/app/revanced/api/ConfigurationSchema.kt new file mode 100644 index 0000000..0a5979a --- /dev/null +++ b/src/main/kotlin/app/revanced/api/ConfigurationSchema.kt @@ -0,0 +1,17 @@ +package app.revanced.api + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +class APIConfiguration( + val organization: String, + @SerialName("patches-repository") + val patchesRepository: String, + @SerialName("integrations-repositories") + val integrationsRepositoryNames: Set, + @SerialName("contributors-repositories") + val contributorsRepositoryNames: Set, + @SerialName("api-version") + val apiVersion: Int = 1 +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/api/backend/Backend.kt b/src/main/kotlin/app/revanced/api/backend/Backend.kt index efb1d02..6549c64 100644 --- a/src/main/kotlin/app/revanced/api/backend/Backend.kt +++ b/src/main/kotlin/app/revanced/api/backend/Backend.kt @@ -19,12 +19,12 @@ abstract class Backend( * * @property name The name of the user. * @property avatarUrl The URL to the avatar of the user. - * @property profileUrl The URL to the profile of the user. + * @property url The URL to the profile of the user. */ - interface User { + interface BackendUser { val name: String val avatarUrl: String - val profileUrl: String + val url: String } /** @@ -32,48 +32,50 @@ abstract class Backend( * * @property members The members of the organization. */ - class Organization( - val members: Set + class BackendOrganization( + val members: Set ) { /** * A member of an organization. * * @property name The name of the member. * @property avatarUrl The URL to the avatar of the member. - * @property profileUrl The URL to the profile of the member. + * @property url The URL to the profile of the member. * @property bio The bio of the member. * @property gpgKeysUrl The URL to the GPG keys of the member. */ @Serializable - class Member ( + class BackendMember ( override val name: String, override val avatarUrl: String, - override val profileUrl: String, + override val url: String, val bio: String?, - val gpgKeysUrl: String? - ) : User + val gpgKeysUrl: String + ) : BackendUser /** * A repository of an organization. * * @property contributors The contributors of the repository. */ - class Repository( - val contributors: Set + class BackendRepository( + val contributors: Set ) { /** * A contributor of a repository. * * @property name The name of the contributor. * @property avatarUrl The URL to the avatar of the contributor. - * @property profileUrl The URL to the profile of the contributor. + * @property url The URL to the profile of the contributor. + * @property contributions The number of contributions of the contributor. */ @Serializable - class Contributor( + class BackendContributor( override val name: String, override val avatarUrl: String, - override val profileUrl: String - ) : User + override val url: String, + val contributions: Int + ) : BackendUser /** * A release of a repository. @@ -84,11 +86,11 @@ abstract class Backend( * @property releaseNote The release note of the release. */ @Serializable - class Release( + class BackendRelease( val tag: String, val releaseNote: String, val createdAt: String, - val assets: Set + val assets: Set ) { /** * An asset of a release. @@ -96,7 +98,7 @@ abstract class Backend( * @property downloadUrl The URL to download the asset. */ @Serializable - class Asset( + class BackendAsset( val downloadUrl: String ) } @@ -109,17 +111,13 @@ abstract class Backend( * @param owner The owner of the repository. * @param repository The name of the repository. * @param tag The tag of the release. If null, the latest release is returned. - * @param preRelease Whether to return a pre-release. - * If no pre-release exists, the latest release is returned. - * If tag is not null, this parameter is ignored. * @return The release. */ abstract suspend fun getRelease( owner: String, repository: String, tag: String? = null, - preRelease: Boolean = false - ): Organization.Repository.Release + ): BackendOrganization.BackendRepository.BackendRelease /** * Get the contributors of a repository. @@ -128,7 +126,7 @@ abstract class Backend( * @param repository The name of the repository. * @return The contributors. */ - abstract suspend fun getContributors(owner: String, repository: String): Set + abstract suspend fun getContributors(owner: String, repository: String): Set /** * Get the members of an organization. @@ -136,5 +134,5 @@ abstract class Backend( * @param organization The name of the organization. * @return The members. */ - abstract suspend fun getMembers(organization: String): Set + abstract suspend fun getMembers(organization: String): Set } diff --git a/src/main/kotlin/app/revanced/api/backend/github/GitHubBackend.kt b/src/main/kotlin/app/revanced/api/backend/github/GitHubBackend.kt index db144f5..e024ce5 100644 --- a/src/main/kotlin/app/revanced/api/backend/github/GitHubBackend.kt +++ b/src/main/kotlin/app/revanced/api/backend/github/GitHubBackend.kt @@ -9,13 +9,17 @@ import io.ktor.client.plugins.auth.providers.* import io.ktor.client.plugins.cache.* import io.ktor.client.plugins.contentnegotiation.* import io.ktor.client.plugins.resources.* +import app.revanced.api.backend.Backend.BackendOrganization.BackendMember +import app.revanced.api.backend.Backend.BackendOrganization.BackendRepository.BackendRelease +import app.revanced.api.backend.Backend.BackendOrganization.BackendRepository.BackendContributor +import app.revanced.api.backend.Backend.BackendOrganization.BackendRepository.BackendRelease.BackendAsset import app.revanced.api.backend.github.api.Request.Organization.Repository.Releases import app.revanced.api.backend.github.api.Request.Organization.Repository.Contributors import app.revanced.api.backend.github.api.Request.Organization.Members import app.revanced.api.backend.github.api.Response -import app.revanced.api.backend.github.api.Response.Organization.Repository.Release -import app.revanced.api.backend.github.api.Response.Organization.Repository.Contributor -import app.revanced.api.backend.github.api.Response.Organization.Member +import app.revanced.api.backend.github.api.Response.GitHubOrganization.GitHubRepository.GitHubRelease +import app.revanced.api.backend.github.api.Response.GitHubOrganization.GitHubRepository.GitHubContributor +import app.revanced.api.backend.github.api.Response.GitHubOrganization.GitHubMember import io.ktor.client.plugins.resources.Resources import io.ktor.serialization.kotlinx.json.* import kotlinx.coroutines.* @@ -55,59 +59,58 @@ class GitHubBackend(token: String? = null) : Backend({ owner: String, repository: String, tag: String?, - preRelease: Boolean - ): Organization.Repository.Release { - val release = if (preRelease) { - val releases: Set = client.get(Releases(owner, repository)).body() - releases.firstOrNull { it.preReleases } ?: releases.first() // Latest pre-release or latest release - } else { - client.get( - tag?.let { Releases.Tag(owner, repository, it) } - ?: Releases.Latest(owner, repository) - ).body() - } + ): BackendRelease { + val release: GitHubRelease = if (tag != null) + client.get(Releases.Tag(owner, repository, tag)).body() + else + client.get(Releases.Latest(owner, repository)).body() - return Organization.Repository.Release( + + return BackendRelease( tag = release.tagName, releaseNote = release.body, createdAt = release.createdAt, assets = release.assets.map { - Organization.Repository.Release.Asset( + BackendAsset( downloadUrl = it.browserDownloadUrl ) }.toSet() ) } - override suspend fun getContributors(owner: String, repository: String): Set { - val contributors: Set = client.get(Contributors(owner, repository)).body() + override suspend fun getContributors( + owner: String, + repository: String + ): Set { + val contributors: Set = client.get(Contributors(owner, repository)).body() return contributors.map { - Organization.Repository.Contributor( + BackendContributor( name = it.login, avatarUrl = it.avatarUrl, - profileUrl = it.url + url = it.url, + contributions = it.contributions ) }.toSet() } - override suspend fun getMembers(organization: String): Set { + override suspend fun getMembers(organization: String): Set { // Get the list of members of the organization. - val members: Set = client.get(Members(organization)).body>() + val members: Set = client.get(Members(organization)).body() return runBlocking(Dispatchers.Default) { members.map { member -> // Map the member to a user in order to get the bio. async { - client.get(Request.User(member.login)).body() + client.get(Request.User(member.login)).body() } } }.awaitAll().map { user -> // Map the user back to a member. - Organization.Member( + BackendMember( name = user.login, avatarUrl = user.avatarUrl, - profileUrl = user.url, + url = user.url, bio = user.bio, gpgKeysUrl = "https://github.com/${user.login}.gpg", ) diff --git a/src/main/kotlin/app/revanced/api/backend/github/api/ResponseSchema.kt b/src/main/kotlin/app/revanced/api/backend/github/api/ResponseSchema.kt index e286b95..26d56b5 100644 --- a/src/main/kotlin/app/revanced/api/backend/github/api/ResponseSchema.kt +++ b/src/main/kotlin/app/revanced/api/backend/github/api/ResponseSchema.kt @@ -1,49 +1,50 @@ package app.revanced.api.backend.github.api +import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable class Response { - interface IUser { + interface IGitHubUser { val login: String val avatarUrl: String val url: String } @Serializable - class User ( + class GitHubUser ( override val login: String, override val avatarUrl: String, override val url: String, val bio: String?, - ) : IUser + ) : IGitHubUser - class Organization { + class GitHubOrganization { @Serializable - class Member( + class GitHubMember( override val login: String, override val avatarUrl: String, override val url: String, - ) : IUser + ) : IGitHubUser - class Repository { + class GitHubRepository { @Serializable - class Contributor( + class GitHubContributor( override val login: String, override val avatarUrl: String, override val url: String, - ) : IUser + val contributions: Int, + ) : IGitHubUser @Serializable - class Release( + class GitHubRelease( val tagName: String, - val assets: Set, - val preReleases: Boolean, + val assets: Set, val createdAt: String, val body: String ) { @Serializable - class Asset( + class GitHubAsset( val browserDownloadUrl: String ) } diff --git a/src/main/kotlin/app/revanced/api/plugins/Dependencies.kt b/src/main/kotlin/app/revanced/api/plugins/Dependencies.kt index 3950ec9..7e0a3d3 100644 --- a/src/main/kotlin/app/revanced/api/plugins/Dependencies.kt +++ b/src/main/kotlin/app/revanced/api/plugins/Dependencies.kt @@ -1,21 +1,37 @@ package app.revanced.api.plugins +import app.revanced.api.APIConfiguration import app.revanced.api.backend.github.GitHubBackend +import com.akuleshov7.ktoml.Toml +import com.akuleshov7.ktoml.source.decodeFromStream import io.github.cdimascio.dotenv.Dotenv import io.ktor.server.application.* +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.encodeToString import org.koin.core.context.startKoin import org.koin.dsl.module import org.koin.ktor.ext.inject import org.koin.ktor.plugin.Koin +import java.io.File fun Application.configureDependencies() { install(Koin) { modules( module { - single { Dotenv.load() } - single { GitHubBackend(get().get("GITHUB_TOKEN")) } + single { + Dotenv.load() + } + single { + val configFilePath = get().get("CONFIG_FILE_PATH")!! + Toml.decodeFromStream(File(configFilePath).inputStream()) + } + single { + val token = get().get("GITHUB_TOKEN") + GitHubBackend(token) + } } ) } } + diff --git a/src/main/kotlin/app/revanced/api/plugins/Routing.kt b/src/main/kotlin/app/revanced/api/plugins/Routing.kt index c14a7c3..1bd019f 100644 --- a/src/main/kotlin/app/revanced/api/plugins/Routing.kt +++ b/src/main/kotlin/app/revanced/api/plugins/Routing.kt @@ -1,36 +1,74 @@ package app.revanced.api.plugins +import app.revanced.api.* import app.revanced.api.backend.github.GitHubBackend -import io.github.cdimascio.dotenv.Dotenv +import io.ktor.client.utils.EmptyContent.contentType import io.ktor.http.* import io.ktor.server.application.* import io.ktor.server.http.content.* import io.ktor.server.response.* import io.ktor.server.routing.* +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll import org.koin.ktor.ext.inject fun Application.configureRouting() { val backend by inject() - val dotenv by inject() + val configuration by inject() routing { - route("/v${dotenv.get("API_VERSION", "1")}") { - route("/manager") { - get("/contributors") { - val contributors = backend.getContributors("revanced", "revanced-patches") + route("/v${configuration.apiVersion}") { + route("/patches") { + get { + val patches = backend.getRelease(configuration.organization, configuration.patchesRepository) + val integrations = configuration.integrationsRepositoryNames.map { + async { backend.getRelease(configuration.organization, it) } + }.awaitAll() - call.respond(contributors) + val assets = (patches.assets + integrations.flatMap { it.assets }).filter { + it.downloadUrl.endsWith(".apk") || it.downloadUrl.endsWith(".jar") + }.map { APIAsset(it.downloadUrl) }.toSet() + + val release = APIRelease( + patches.tag, + patches.createdAt, + patches.releaseNote, + assets + ) + + call.respond(release) } - get("/members") { - val members = backend.getMembers("revanced") + get("/version") { + val patches = backend.getRelease(configuration.organization, configuration.patchesRepository) - call.respond(members) + val release = APIReleaseVersion(patches.tag) + + call.respond(release) } } - route("/patches") { + get("/contributors") { + val contributors = configuration.contributorsRepositoryNames.map { + async { + APIContributable( + it, + backend.getContributors(configuration.organization, it).map { + APIContributor(it.name, it.avatarUrl, it.url, it.contributions) + }.toSet() + ) + } + }.awaitAll() + call.respond(contributors) + } + + get("/members") { + val members = backend.getMembers(configuration.organization).map { + APIMember(it.name, it.avatarUrl, it.url, it.gpgKeysUrl) + } + + call.respond(members) } route("/ping") { @@ -38,8 +76,9 @@ fun Application.configureRouting() { call.respond(HttpStatusCode.NoContent) } } + + staticResources("/", "/static/api") { contentType { ContentType.Application.Json } } } - staticResources("/", "static") } } diff --git a/src/main/resources/static/about.json b/src/main/resources/static/api/about similarity index 100% rename from src/main/resources/static/about.json rename to src/main/resources/static/api/about From a988ffbd2303a79ee18be7263ef6cd45c7bc4daf Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Mon, 29 Jan 2024 03:46:17 +0100 Subject: [PATCH 007/102] feat: Add CLI --- build.gradle.kts | 9 ++++- gradle/libs.versions.toml | 2 + .../kotlin/app/revanced/api/Application.kt | 22 ----------- .../app/revanced/api/command/MainCommand.kt | 34 ++++++++++++++++ .../revanced/api/command/StartAPICommand.kt | 39 +++++++++++++++++++ .../app/revanced/api/plugins/Dependencies.kt | 6 +-- .../app/revanced/api/plugins/Routing.kt | 3 +- .../revanced/api/{ => schema}/APISchema.kt | 22 ++++++++++- .../api/{ => schema}/ConfigurationSchema.kt | 2 +- .../app.revanced.api/version.properties | 1 + 10 files changed, 107 insertions(+), 33 deletions(-) delete mode 100644 src/main/kotlin/app/revanced/api/Application.kt create mode 100644 src/main/kotlin/app/revanced/api/command/MainCommand.kt create mode 100644 src/main/kotlin/app/revanced/api/command/StartAPICommand.kt rename src/main/kotlin/app/revanced/api/{ => schema}/APISchema.kt (72%) rename src/main/kotlin/app/revanced/api/{ => schema}/ConfigurationSchema.kt (93%) create mode 100644 src/main/resources/app.revanced.api/version.properties diff --git a/build.gradle.kts b/build.gradle.kts index d1c7c33..bfc4d3f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -7,12 +7,18 @@ plugins { group = "app.revanced" application { - mainClass.set("app.revanced.api.ApplicationKt") + mainClass.set("app.revanced.api.command.MainCommandKt") val isDevelopment: Boolean = project.ext.has("development") applicationDefaultJvmArgs = listOf("-Dio.ktor.development=$isDevelopment") } +tasks { + processResources { + expand("projectVersion" to project.version) + } +} + repositories { mavenCentral() } @@ -44,6 +50,7 @@ dependencies { implementation(libs.dotenv.kotlin) implementation(libs.ktoml.core) implementation(libs.ktoml.file) + implementation(libs.picocli) testImplementation(libs.ktor.server.tests) testImplementation(libs.kotlin.test.junit) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 5074e91..413b850 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -7,6 +7,7 @@ koin="3.5.3" dotenv="6.4.1" ktor = "2.3.7" ktoml = "0.5.1" +picocli = "4.7.3" [libraries] ktor-client-core = { module = "io.ktor:ktor-client-core" } @@ -37,6 +38,7 @@ ktor-server-tests = { module = "io.ktor:ktor-server-tests" } kotlin-test-junit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kotlin" } ktoml-core = { module = "com.akuleshov7:ktoml-core", version.ref = "ktoml" } ktoml-file = { module = "com.akuleshov7:ktoml-file", version.ref = "ktoml" } +picocli = { module = "info.picocli:picocli", version.ref = "picocli" } [plugins] serilization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } diff --git a/src/main/kotlin/app/revanced/api/Application.kt b/src/main/kotlin/app/revanced/api/Application.kt deleted file mode 100644 index 0b7c571..0000000 --- a/src/main/kotlin/app/revanced/api/Application.kt +++ /dev/null @@ -1,22 +0,0 @@ -package app.revanced.api - -import app.revanced.api.plugins.* -import io.github.cdimascio.dotenv.Dotenv -import io.ktor.server.application.* -import io.ktor.server.engine.* -import io.ktor.server.netty.* - -fun main() { - embeddedServer(Netty, port = 8080, host = "0.0.0.0", configure = { - connectionGroupSize = 1 - workerGroupSize = 1 - callGroupSize = 1 - }) { - configureHTTP() - configureSerialization() - configureDatabases() - configureSecurity() - configureDependencies() - configureRouting() - }.start(wait = true) -} diff --git a/src/main/kotlin/app/revanced/api/command/MainCommand.kt b/src/main/kotlin/app/revanced/api/command/MainCommand.kt new file mode 100644 index 0000000..3038e96 --- /dev/null +++ b/src/main/kotlin/app/revanced/api/command/MainCommand.kt @@ -0,0 +1,34 @@ +package app.revanced.api.command + +import picocli.CommandLine +import java.util.* + +fun main(args: Array) { + CommandLine(MainCommand).execute(*args).let(System::exit) +} + +private object CLIVersionProvider : CommandLine.IVersionProvider { + override fun getVersion() = + arrayOf( + MainCommand::class.java.getResourceAsStream( + "/app/revanced/api/version.properties", + )?.use { stream -> + Properties().apply { + load(stream) + }.let { + "ReVanced API v${it.getProperty("version")}" + } + } ?: "ReVanced API", + ) +} + +@CommandLine.Command( + name = "revanced-api", + description = ["API server for ReVanced"], + mixinStandardHelpOptions = true, + versionProvider = CLIVersionProvider::class, + subcommands = [ + StartAPICommand::class, + ], +) +private object MainCommand \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/api/command/StartAPICommand.kt b/src/main/kotlin/app/revanced/api/command/StartAPICommand.kt new file mode 100644 index 0000000..be7abfb --- /dev/null +++ b/src/main/kotlin/app/revanced/api/command/StartAPICommand.kt @@ -0,0 +1,39 @@ +package app.revanced.api.command + +import app.revanced.api.plugins.* +import io.ktor.server.engine.* +import io.ktor.server.netty.* +import picocli.CommandLine + +@CommandLine.Command( + name = "start", + description = ["Start the API server"], +) +internal object StartAPICommand : Runnable { + @CommandLine.Option( + names = ["-h", "--host"], + description = ["The host address to bind to."], + ) + private var host: String = "0.0.0.0" + + @CommandLine.Option( + names = ["-p", "--port"], + description = ["The port to listen on."], + ) + private var port: Int = 8080 + + override fun run() { + embeddedServer(Netty, port, host, configure = { + connectionGroupSize = 1 + workerGroupSize = 1 + callGroupSize = 1 + }) { + configureHTTP() + configureSerialization() + configureDatabases() + configureSecurity() + configureDependencies() + configureRouting() + }.start(wait = true) + } +} diff --git a/src/main/kotlin/app/revanced/api/plugins/Dependencies.kt b/src/main/kotlin/app/revanced/api/plugins/Dependencies.kt index 7e0a3d3..7667e3f 100644 --- a/src/main/kotlin/app/revanced/api/plugins/Dependencies.kt +++ b/src/main/kotlin/app/revanced/api/plugins/Dependencies.kt @@ -1,16 +1,12 @@ package app.revanced.api.plugins -import app.revanced.api.APIConfiguration +import app.revanced.api.schema.APIConfiguration import app.revanced.api.backend.github.GitHubBackend import com.akuleshov7.ktoml.Toml import com.akuleshov7.ktoml.source.decodeFromStream import io.github.cdimascio.dotenv.Dotenv import io.ktor.server.application.* -import kotlinx.serialization.decodeFromString -import kotlinx.serialization.encodeToString -import org.koin.core.context.startKoin import org.koin.dsl.module -import org.koin.ktor.ext.inject import org.koin.ktor.plugin.Koin import java.io.File diff --git a/src/main/kotlin/app/revanced/api/plugins/Routing.kt b/src/main/kotlin/app/revanced/api/plugins/Routing.kt index 1bd019f..8736aff 100644 --- a/src/main/kotlin/app/revanced/api/plugins/Routing.kt +++ b/src/main/kotlin/app/revanced/api/plugins/Routing.kt @@ -1,8 +1,7 @@ package app.revanced.api.plugins -import app.revanced.api.* import app.revanced.api.backend.github.GitHubBackend -import io.ktor.client.utils.EmptyContent.contentType +import app.revanced.api.schema.* import io.ktor.http.* import io.ktor.server.application.* import io.ktor.server.http.content.* diff --git a/src/main/kotlin/app/revanced/api/APISchema.kt b/src/main/kotlin/app/revanced/api/schema/APISchema.kt similarity index 72% rename from src/main/kotlin/app/revanced/api/APISchema.kt rename to src/main/kotlin/app/revanced/api/schema/APISchema.kt index 5e2de99..4432470 100644 --- a/src/main/kotlin/app/revanced/api/APISchema.kt +++ b/src/main/kotlin/app/revanced/api/schema/APISchema.kt @@ -1,4 +1,4 @@ -package app.revanced.api +package app.revanced.api.schema import kotlinx.serialization.Serializable @@ -52,4 +52,22 @@ class APIAsset( @Serializable class APIReleaseVersion( val version: String -) \ No newline at end of file +) + +@Serializable +class APIAnnouncement( + val id: Int, + val author: APIUser?, + val title: String, + val content: APIAnnouncementContent, + val channel: String, + val createdAt: String, + val archivedAt: String?, + val level: Int, +) + +@Serializable +class APIAnnouncementContent( + val message: String, + val attachmentUrls: Set +) diff --git a/src/main/kotlin/app/revanced/api/ConfigurationSchema.kt b/src/main/kotlin/app/revanced/api/schema/ConfigurationSchema.kt similarity index 93% rename from src/main/kotlin/app/revanced/api/ConfigurationSchema.kt rename to src/main/kotlin/app/revanced/api/schema/ConfigurationSchema.kt index 0a5979a..db90377 100644 --- a/src/main/kotlin/app/revanced/api/ConfigurationSchema.kt +++ b/src/main/kotlin/app/revanced/api/schema/ConfigurationSchema.kt @@ -1,4 +1,4 @@ -package app.revanced.api +package app.revanced.api.schema import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable diff --git a/src/main/resources/app.revanced.api/version.properties b/src/main/resources/app.revanced.api/version.properties new file mode 100644 index 0000000..308c9f8 --- /dev/null +++ b/src/main/resources/app.revanced.api/version.properties @@ -0,0 +1 @@ +version=${projectVersion} \ No newline at end of file From 4dffd32c99a5a3deafe21bc9e9960795ff93ff1d Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Mon, 29 Jan 2024 03:47:36 +0100 Subject: [PATCH 008/102] fix: Use correct resource path --- .../{app.revanced.api => app/revanced/api}/version.properties | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/main/resources/{app.revanced.api => app/revanced/api}/version.properties (100%) diff --git a/src/main/resources/app.revanced.api/version.properties b/src/main/resources/app/revanced/api/version.properties similarity index 100% rename from src/main/resources/app.revanced.api/version.properties rename to src/main/resources/app/revanced/api/version.properties From db0bfc3be5b8d86466fa7f1a01a72247b4e878aa Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Mon, 29 Jan 2024 03:47:56 +0100 Subject: [PATCH 009/102] feat: Show default CLI option values --- src/main/kotlin/app/revanced/api/command/StartAPICommand.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/kotlin/app/revanced/api/command/StartAPICommand.kt b/src/main/kotlin/app/revanced/api/command/StartAPICommand.kt index be7abfb..0aa50ed 100644 --- a/src/main/kotlin/app/revanced/api/command/StartAPICommand.kt +++ b/src/main/kotlin/app/revanced/api/command/StartAPICommand.kt @@ -13,12 +13,14 @@ internal object StartAPICommand : Runnable { @CommandLine.Option( names = ["-h", "--host"], description = ["The host address to bind to."], + showDefaultValue = CommandLine.Help.Visibility.ALWAYS, ) private var host: String = "0.0.0.0" @CommandLine.Option( names = ["-p", "--port"], description = ["The port to listen on."], + showDefaultValue = CommandLine.Help.Visibility.ALWAYS, ) private var port: Int = 8080 From 80c8083610fbcc30341ae392463d6141a3623bf3 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Mon, 29 Jan 2024 10:57:47 +0100 Subject: [PATCH 010/102] chore: Specify extension for static API files --- .../app/revanced/api/plugins/Routing.kt | 5 +- .../static/api/{about => about.json} | 48 +++++++------------ 2 files changed, 20 insertions(+), 33 deletions(-) rename src/main/resources/static/api/{about => about.json} (65%) diff --git a/src/main/kotlin/app/revanced/api/plugins/Routing.kt b/src/main/kotlin/app/revanced/api/plugins/Routing.kt index 8736aff..3976400 100644 --- a/src/main/kotlin/app/revanced/api/plugins/Routing.kt +++ b/src/main/kotlin/app/revanced/api/plugins/Routing.kt @@ -76,7 +76,10 @@ fun Application.configureRouting() { } } - staticResources("/", "/static/api") { contentType { ContentType.Application.Json } } + staticResources("/", "/static/api") { + contentType { ContentType.Application.Json } + extensions("json") + } } } diff --git a/src/main/resources/static/api/about b/src/main/resources/static/api/about.json similarity index 65% rename from src/main/resources/static/api/about rename to src/main/resources/static/api/about.json index 8f62d26..1a0d1cb 100644 --- a/src/main/resources/static/api/about +++ b/src/main/resources/static/api/about.json @@ -1,16 +1,13 @@ { "name": "ReVanced", "about": "ReVanced was born out of Vanced's discontinuation and it is our goal to continue the legacy of what Vanced left behind. Thanks to ReVanced Patcher, it's possible to create long-lasting patches for nearly any Android app. ReVanced's patching system is designed to allow patches to work on new versions of the apps automatically with bare minimum maintenance.", - "branding": - { + "branding": { "logo": "https://raw.githubusercontent.com/ReVanced/revanced-branding/main/assets/revanced-logo/revanced-logo.svg" }, - "contact": - { + "contact": { "email": "contact@revanced.app" }, - "socials": - [ + "socials": [ { "name": "Website", "url": "https://revanced.app", @@ -18,13 +15,11 @@ }, { "name": "GitHub", - "url": "https://github.com/revanced", - "preferred": false + "url": "https://github.com/revanced" }, { "name": "Twitter", - "url": "https://twitter.com/revancedapp", - "preferred": false + "url": "https://twitter.com/revancedapp" }, { "name": "Discord", @@ -33,29 +28,23 @@ }, { "name": "Reddit", - "url": "https://www.reddit.com/r/revancedapp", - "preferred": false + "url": "https://www.reddit.com/r/revancedapp" }, { "name": "Telegram", - "url": "https://t.me/app_revanced", - "preferred": false + "url": "https://t.me/app_revanced" }, { "name": "YouTube", - "url": "https://www.youtube.com/@ReVanced", - "preferred": false + "url": "https://www.youtube.com/@ReVanced" } ], - "donations": - { - "wallets": - [ + "donations": { + "wallets": [ { "network": "Bitcoin", "currency_code": "BTC", - "address": "bc1q4x8j6mt27y5gv0q625t8wkr87ruy8fprpy4v3f", - "preferred": false + "address": "bc1q4x8j6mt27y5gv0q625t8wkr87ruy8fprpy4v3f" }, { "network": "Dogecoin", @@ -66,24 +55,20 @@ { "network": "Ethereum", "currency_code": "ETH", - "address": "0x7ab4091e00363654bf84B34151225742cd92FCE5", - "preferred": false + "address": "0x7ab4091e00363654bf84B34151225742cd92FCE5" }, { "network": "Litecoin", "currency_code": "LTC", - "address": "LbJi8EuoDcwaZvykcKmcrM74jpjde23qJ2", - "preferred": false + "address": "LbJi8EuoDcwaZvykcKmcrM74jpjde23qJ2" }, { "network": "Monero", "currency_code": "XMR", - "address": "46YwWDbZD6jVptuk5mLHsuAmh1BnUMSjSNYacozQQEraWSQ93nb2yYVRHoMR6PmFYWEHsLHg9tr1cH5M8Rtn7YaaGQPCjSh", - "preferred": false + "address": "46YwWDbZD6jVptuk5mLHsuAmh1BnUMSjSNYacozQQEraWSQ93nb2yYVRHoMR6PmFYWEHsLHg9tr1cH5M8Rtn7YaaGQPCjSh" } ], - "links": - [ + "links": [ { "name": "Open Collective", "url": "https://opencollective.com/revanced", @@ -91,8 +76,7 @@ }, { "name": "GitHub Sponsors", - "url": "https://github.com/sponsors/ReVanced", - "preferred": false + "url": "https://github.com/sponsors/ReVanced" } ] } From df999c00c4c1f8645cc67ae19b732f0af9d71823 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Mon, 29 Jan 2024 10:58:15 +0100 Subject: [PATCH 011/102] feat: Improve routing paths --- .../app/revanced/api/plugins/Routing.kt | 42 ++++++++++--------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/src/main/kotlin/app/revanced/api/plugins/Routing.kt b/src/main/kotlin/app/revanced/api/plugins/Routing.kt index 3976400..0138207 100644 --- a/src/main/kotlin/app/revanced/api/plugins/Routing.kt +++ b/src/main/kotlin/app/revanced/api/plugins/Routing.kt @@ -18,32 +18,34 @@ fun Application.configureRouting() { routing { route("/v${configuration.apiVersion}") { route("/patches") { - get { - val patches = backend.getRelease(configuration.organization, configuration.patchesRepository) - val integrations = configuration.integrationsRepositoryNames.map { - async { backend.getRelease(configuration.organization, it) } - }.awaitAll() + route("latest") { + get { + val patches = backend.getRelease(configuration.organization, configuration.patchesRepository) + val integrations = configuration.integrationsRepositoryNames.map { + async { backend.getRelease(configuration.organization, it) } + }.awaitAll() - val assets = (patches.assets + integrations.flatMap { it.assets }).filter { - it.downloadUrl.endsWith(".apk") || it.downloadUrl.endsWith(".jar") - }.map { APIAsset(it.downloadUrl) }.toSet() + val assets = (patches.assets + integrations.flatMap { it.assets }).filter { + it.downloadUrl.endsWith(".apk") || it.downloadUrl.endsWith(".jar") + }.map { APIAsset(it.downloadUrl) }.toSet() - val release = APIRelease( - patches.tag, - patches.createdAt, - patches.releaseNote, - assets - ) + val release = APIRelease( + patches.tag, + patches.createdAt, + patches.releaseNote, + assets + ) - call.respond(release) - } + call.respond(release) + } - get("/version") { - val patches = backend.getRelease(configuration.organization, configuration.patchesRepository) + get("/version") { + val patches = backend.getRelease(configuration.organization, configuration.patchesRepository) - val release = APIReleaseVersion(patches.tag) + val release = APIReleaseVersion(patches.tag) - call.respond(release) + call.respond(release) + } } } From af0b0865f4c2f22975a836b72ff0b902ddac1ce9 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Mon, 29 Jan 2024 11:31:15 +0100 Subject: [PATCH 012/102] feat: Remove Swagger and OpenAPI Support and features are lacking --- build.gradle.kts | 3 - docs/.swagger-codegen-ignore | 23 - docs/.swagger-codegen/VERSION | 1 - docs/index.html | 2752 ----------------- gradle/libs.versions.toml | 2 - .../kotlin/app/revanced/api/plugins/HTTP.kt | 17 +- src/main/resources/openapi/documentation.yaml | 23 - 7 files changed, 3 insertions(+), 2818 deletions(-) delete mode 100644 docs/.swagger-codegen-ignore delete mode 100644 docs/.swagger-codegen/VERSION delete mode 100644 docs/index.html delete mode 100644 src/main/resources/openapi/documentation.yaml diff --git a/build.gradle.kts b/build.gradle.kts index bfc4d3f..50cccda 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -34,8 +34,6 @@ dependencies { implementation(libs.ktor.server.content.negotiation) implementation(libs.ktor.server.auth) implementation(libs.ktor.server.auth.jwt) - implementation(libs.ktor.server.swagger) - implementation(libs.ktor.server.openapi) implementation(libs.ktor.server.cors) implementation(libs.ktor.server.caching.headers) implementation(libs.ktor.server.host.common) @@ -54,5 +52,4 @@ dependencies { testImplementation(libs.ktor.server.tests) testImplementation(libs.kotlin.test.junit) - } diff --git a/docs/.swagger-codegen-ignore b/docs/.swagger-codegen-ignore deleted file mode 100644 index c5fa491..0000000 --- a/docs/.swagger-codegen-ignore +++ /dev/null @@ -1,23 +0,0 @@ -# Swagger Codegen Ignore -# Generated by swagger-codegen https://github.com/swagger-api/swagger-codegen - -# Use this file to prevent files from being overwritten by the generator. -# The patterns follow closely to .gitignore or .dockerignore. - -# As an example, the C# client generator defines ApiClient.cs. -# You can make changes and tell Swagger Codgen to ignore just this file by uncommenting the following line: -#ApiClient.cs - -# You can match any string of characters against a directory, file or extension with a single asterisk (*): -#foo/*/qux -# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux - -# You can recursively match patterns against a directory, file or extension with a double asterisk (**): -#foo/**/qux -# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux - -# You can also negate patterns with an exclamation (!). -# For example, you can ignore all files in a docs folder with the file extension .md: -#docs/*.md -# Then explicitly reverse the ignore rule for a single file: -#!docs/README.md diff --git a/docs/.swagger-codegen/VERSION b/docs/.swagger-codegen/VERSION deleted file mode 100644 index a254f0a..0000000 --- a/docs/.swagger-codegen/VERSION +++ /dev/null @@ -1 +0,0 @@ -3.0.41 \ No newline at end of file diff --git a/docs/index.html b/docs/index.html deleted file mode 100644 index af14f14..0000000 --- a/docs/index.html +++ /dev/null @@ -1,2752 +0,0 @@ - - - - - Application API - - - - - - - - - - - - - - - - - - - - - - - diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 413b850..016787b 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -21,8 +21,6 @@ ktor-server-core = { module = "io.ktor:ktor-server-core" } ktor-server-content-negotiation = { module = "io.ktor:ktor-server-content-negotiation" } ktor-server-auth = { module = "io.ktor:ktor-server-auth" } ktor-server-auth-jwt = { module = "io.ktor:ktor-server-auth-jwt" } -ktor-server-swagger = { module = "io.ktor:ktor-server-swagger" } -ktor-server-openapi = { module = "io.ktor:ktor-server-openapi" } ktor-server-cors = { module = "io.ktor:ktor-server-cors" } ktor-server-caching-headers = { module = "io.ktor:ktor-server-caching-headers" } ktor-server-host-common = { module = "io.ktor:ktor-server-host-common" } diff --git a/src/main/kotlin/app/revanced/api/plugins/HTTP.kt b/src/main/kotlin/app/revanced/api/plugins/HTTP.kt index 8ea4b73..b46f168 100644 --- a/src/main/kotlin/app/revanced/api/plugins/HTTP.kt +++ b/src/main/kotlin/app/revanced/api/plugins/HTTP.kt @@ -6,18 +6,10 @@ import io.ktor.server.application.* import io.ktor.server.plugins.cachingheaders.* import io.ktor.server.plugins.conditionalheaders.* import io.ktor.server.plugins.cors.routing.* -import io.ktor.server.plugins.openapi.* -import io.ktor.server.plugins.swagger.* -import io.ktor.server.routing.* +import kotlin.time.Duration.Companion.minutes fun Application.configureHTTP() { install(ConditionalHeaders) - routing { - swaggerUI(path = "openapi") - } - routing { - openAPI(path = "openapi") - } install(CORS) { allowMethod(HttpMethod.Options) allowMethod(HttpMethod.Put) @@ -27,11 +19,8 @@ fun Application.configureHTTP() { anyHost() // @TODO: Don't do this in production if possible. Try to limit it. } install(CachingHeaders) { - options { _, outgoingContent -> - when (outgoingContent.contentType?.withoutParameters()) { - ContentType.Text.CSS -> CachingOptions(CacheControl.MaxAge(maxAgeSeconds = 24 * 60 * 60)) - else -> null - } + options { _, _ -> + CachingOptions(CacheControl.MaxAge(maxAgeSeconds = 5.minutes.inWholeSeconds.toInt())) } } } diff --git a/src/main/resources/openapi/documentation.yaml b/src/main/resources/openapi/documentation.yaml deleted file mode 100644 index 2e6c6d7..0000000 --- a/src/main/resources/openapi/documentation.yaml +++ /dev/null @@ -1,23 +0,0 @@ -openapi: "3.0.3" -info: - title: "Application API" - description: "Application API" - version: "1.0.0" -servers: - - url: "http://0.0.0.0:8080" -paths: - /: - get: - description: "Hello World!" - responses: - "200": - description: "OK" - content: - text/plain: - schema: - type: "string" - examples: - Example#1: - value: "Hello World!" -components: - schemas: From 42f731854d0b91070bd7b075054d67d3d9e1d1b4 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Wed, 31 Jan 2024 03:01:07 +0100 Subject: [PATCH 013/102] feat: Add announcements API --- .env.example | 13 +- build.gradle.kts | 3 + configuration.example.toml | 2 +- gradle/libs.versions.toml | 16 +- .../app/revanced/api/backend/Backend.kt | 3 +- .../api/backend/github/GitHubBackend.kt | 2 + .../api/backend/github/api/ResponseSchema.kt | 3 +- .../revanced/api/command/StartAPICommand.kt | 5 +- .../app/revanced/api/modules/Database.kt | 160 ++++++++++++++++++ .../app/revanced/api/modules/Dependencies.kt | 73 ++++++++ .../revanced/api/{plugins => modules}/HTTP.kt | 2 +- .../app/revanced/api/modules/Routing.kt | 158 +++++++++++++++++ .../app/revanced/api/modules/Security.kt | 53 ++++++ .../api/{plugins => modules}/Serialization.kt | 2 +- .../app/revanced/api/plugins/Databases.kt | 49 ------ .../app/revanced/api/plugins/Dependencies.kt | 33 ---- .../app/revanced/api/plugins/Routing.kt | 88 ---------- .../app/revanced/api/plugins/Security.kt | 30 ---- .../app/revanced/api/plugins/UsersSchema.kt | 59 ------- .../app/revanced/api/schema/APISchema.kt | 39 +++-- .../kotlin/app/revanced/ApplicationTest.kt | 2 +- 21 files changed, 509 insertions(+), 286 deletions(-) create mode 100644 src/main/kotlin/app/revanced/api/modules/Database.kt create mode 100644 src/main/kotlin/app/revanced/api/modules/Dependencies.kt rename src/main/kotlin/app/revanced/api/{plugins => modules}/HTTP.kt (96%) create mode 100644 src/main/kotlin/app/revanced/api/modules/Routing.kt create mode 100644 src/main/kotlin/app/revanced/api/modules/Security.kt rename src/main/kotlin/app/revanced/api/{plugins => modules}/Serialization.kt (87%) delete mode 100644 src/main/kotlin/app/revanced/api/plugins/Databases.kt delete mode 100644 src/main/kotlin/app/revanced/api/plugins/Dependencies.kt delete mode 100644 src/main/kotlin/app/revanced/api/plugins/Routing.kt delete mode 100644 src/main/kotlin/app/revanced/api/plugins/Security.kt delete mode 100644 src/main/kotlin/app/revanced/api/plugins/UsersSchema.kt diff --git a/.env.example b/.env.example index 2bdac94..91a9819 100644 --- a/.env.example +++ b/.env.example @@ -1,2 +1,13 @@ GITHUB_TOKEN= -CONFIG_FILE_PATH= \ No newline at end of file +CONFIG_FILE_PATH=configuration.toml + +DB_URL=jdbc:h2:./api.db +DB_USER= +DB_PASSWORD= + +JWT_SECRET= +JWT_ISSUER= +JWT_VALIDITY_IN_MIN= + +BASIC_USERNAME= +BASIC_PASSWORD= diff --git a/build.gradle.kts b/build.gradle.kts index 50cccda..b932b6f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -45,10 +45,13 @@ dependencies { implementation(libs.logback.classic) implementation(libs.exposed.core) implementation(libs.exposed.jdbc) + implementation(libs.exposed.dao) + implementation(libs.exposed.kotlin.datetime) implementation(libs.dotenv.kotlin) implementation(libs.ktoml.core) implementation(libs.ktoml.file) implementation(libs.picocli) + implementation(libs.kotlinx.datetime) testImplementation(libs.ktor.server.tests) testImplementation(libs.kotlin.test.junit) diff --git a/configuration.example.toml b/configuration.example.toml index 5935fd6..0fdadb0 100644 --- a/configuration.example.toml +++ b/configuration.example.toml @@ -2,4 +2,4 @@ organization = "org" patches-repository = "patches" integrations-repositories = ["integrations"] contributors-repositories = ["patches", "integrations"] -api-version = 1 +api-version = 1 \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 016787b..f5d3655 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,13 +1,14 @@ [versions] -kotlin="1.9.22" -logback="1.4.14" -exposed="0.41.1" -h2="2.1.214" -koin="3.5.3" -dotenv="6.4.1" +kotlin = "1.9.22" +logback = "1.4.14" +exposed = "0.41.1" +h2 = "2.2.224" +koin = "3.5.3" +dotenv = "6.4.1" ktor = "2.3.7" ktoml = "0.5.1" picocli = "4.7.3" +datetime = "0.5.0" [libraries] ktor-client-core = { module = "io.ktor:ktor-client-core" } @@ -31,12 +32,15 @@ h2 = { module = "com.h2database:h2", version.ref = "h2" } logback-classic = { module = "ch.qos.logback:logback-classic", version.ref = "logback" } exposed-core = { module = "org.jetbrains.exposed:exposed-core", version.ref = "exposed" } exposed-jdbc = { module = "org.jetbrains.exposed:exposed-jdbc", version.ref = "exposed" } +exposed-dao = { module = "org.jetbrains.exposed:exposed-dao", version.ref = "exposed" } +exposed-kotlin-datetime = { module = "org.jetbrains.exposed:exposed-kotlin-datetime", version.ref = "exposed" } dotenv-kotlin = { module = "io.github.cdimascio:dotenv-kotlin", version.ref = "dotenv" } ktor-server-tests = { module = "io.ktor:ktor-server-tests" } kotlin-test-junit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kotlin" } ktoml-core = { module = "com.akuleshov7:ktoml-core", version.ref = "ktoml" } ktoml-file = { module = "com.akuleshov7:ktoml-file", version.ref = "ktoml" } picocli = { module = "info.picocli:picocli", version.ref = "picocli" } +kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "datetime" } [plugins] serilization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } diff --git a/src/main/kotlin/app/revanced/api/backend/Backend.kt b/src/main/kotlin/app/revanced/api/backend/Backend.kt index 6549c64..e231949 100644 --- a/src/main/kotlin/app/revanced/api/backend/Backend.kt +++ b/src/main/kotlin/app/revanced/api/backend/Backend.kt @@ -2,6 +2,7 @@ package app.revanced.api.backend import io.ktor.client.* import io.ktor.client.engine.okhttp.* +import kotlinx.datetime.LocalDateTime import kotlinx.serialization.Serializable /** @@ -89,7 +90,7 @@ abstract class Backend( class BackendRelease( val tag: String, val releaseNote: String, - val createdAt: String, + val createdAt: LocalDateTime, val assets: Set ) { /** diff --git a/src/main/kotlin/app/revanced/api/backend/github/GitHubBackend.kt b/src/main/kotlin/app/revanced/api/backend/github/GitHubBackend.kt index e024ce5..10dcfda 100644 --- a/src/main/kotlin/app/revanced/api/backend/github/GitHubBackend.kt +++ b/src/main/kotlin/app/revanced/api/backend/github/GitHubBackend.kt @@ -26,6 +26,8 @@ import kotlinx.coroutines.* import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonNamingStrategy +import org.koin.dsl.bind +import org.koin.dsl.module @OptIn(ExperimentalSerializationApi::class) class GitHubBackend(token: String? = null) : Backend({ diff --git a/src/main/kotlin/app/revanced/api/backend/github/api/ResponseSchema.kt b/src/main/kotlin/app/revanced/api/backend/github/api/ResponseSchema.kt index 26d56b5..9fe86ab 100644 --- a/src/main/kotlin/app/revanced/api/backend/github/api/ResponseSchema.kt +++ b/src/main/kotlin/app/revanced/api/backend/github/api/ResponseSchema.kt @@ -1,5 +1,6 @@ package app.revanced.api.backend.github.api +import kotlinx.datetime.LocalDateTime import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @@ -40,7 +41,7 @@ class Response { class GitHubRelease( val tagName: String, val assets: Set, - val createdAt: String, + val createdAt: LocalDateTime, val body: String ) { @Serializable diff --git a/src/main/kotlin/app/revanced/api/command/StartAPICommand.kt b/src/main/kotlin/app/revanced/api/command/StartAPICommand.kt index 0aa50ed..b4e3cd6 100644 --- a/src/main/kotlin/app/revanced/api/command/StartAPICommand.kt +++ b/src/main/kotlin/app/revanced/api/command/StartAPICommand.kt @@ -1,6 +1,6 @@ package app.revanced.api.command -import app.revanced.api.plugins.* +import app.revanced.api.modules.* import io.ktor.server.engine.* import io.ktor.server.netty.* import picocli.CommandLine @@ -30,11 +30,10 @@ internal object StartAPICommand : Runnable { workerGroupSize = 1 callGroupSize = 1 }) { + configureDependencies() configureHTTP() configureSerialization() - configureDatabases() configureSecurity() - configureDependencies() configureRouting() }.start(wait = true) } diff --git a/src/main/kotlin/app/revanced/api/modules/Database.kt b/src/main/kotlin/app/revanced/api/modules/Database.kt new file mode 100644 index 0000000..fcd0955 --- /dev/null +++ b/src/main/kotlin/app/revanced/api/modules/Database.kt @@ -0,0 +1,160 @@ +package app.revanced.api.modules + +import app.revanced.api.modules.AnnouncementService.Attachments.announcement +import app.revanced.api.schema.APIResponseAnnouncement +import app.revanced.api.schema.APIAnnouncement +import app.revanced.api.schema.APILatestAnnouncement +import kotlinx.datetime.* +import org.jetbrains.exposed.sql.transactions.transaction +import org.jetbrains.exposed.dao.IntEntity +import org.jetbrains.exposed.dao.IntEntityClass +import org.jetbrains.exposed.dao.id.EntityID +import org.jetbrains.exposed.dao.id.IntIdTable +import org.jetbrains.exposed.sql.* +import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq +import org.jetbrains.exposed.sql.kotlin.datetime.datetime + + +class AnnouncementService(private val database: Database) { + private object Announcements : IntIdTable() { + val author = varchar("author", 32).nullable() + val title = varchar("title", 64) + val content = text("content").nullable() + val channel = varchar("channel", 16).nullable() + val createdAt = datetime("createdAt") + val archivedAt = datetime("archivedAt").nullable() + val level = integer("level") + } + + private object Attachments : IntIdTable() { + val url = varchar("url", 256) + val announcement = reference("announcement", Announcements, onDelete = ReferenceOption.CASCADE) + } + + class Announcement(id: EntityID) : IntEntity(id) { + companion object : IntEntityClass(Announcements) + + var author by Announcements.author + var title by Announcements.title + var content by Announcements.content + val attachments by Attachment referrersOn announcement + var channel by Announcements.channel + var createdAt by Announcements.createdAt + var archivedAt by Announcements.archivedAt + var level by Announcements.level + + fun api() = APIResponseAnnouncement( + id.value, + author, + title, + content, + attachments.map(Attachment::url).toSet(), + channel, + createdAt, + archivedAt, + level + ) + } + + class Attachment(id: EntityID) : IntEntity(id) { + companion object : IntEntityClass(Attachments) + + var url by Attachments.url + var announcement by Announcement referencedOn Attachments.announcement + } + + init { + transaction { + SchemaUtils.create(Announcements, Attachments) + } + } + + private fun transaction(block: Transaction.() -> T) = transaction(database, block) + + fun read() = transaction { + Announcement.all().map { it.api() }.toSet() + } + + fun read(channel: String) = transaction { + Announcement.find { Announcements.channel eq channel }.map { it.api() }.toSet() + } + + fun delete(id: Int) = transaction { + val announcement = Announcement.findById(id) ?: return@transaction + + announcement.delete() + } + + fun latest() = transaction { + Announcement.all().maxByOrNull { it.createdAt }?.api() + } + + fun latest(channel: String) = transaction { + Announcement.find { Announcements.channel eq channel }.maxByOrNull { it.createdAt }?.api() + } + + fun latestId() = transaction { + Announcement.all().maxByOrNull { it.createdAt }?.id?.value?.let { + APILatestAnnouncement(it) + } + } + + fun latestId(channel: String) = transaction { + Announcement.find { Announcements.channel eq channel }.maxByOrNull { it.createdAt }?.id?.value?.let { + APILatestAnnouncement(it) + } + } + + fun archive( + id: Int, + archivedAt: LocalDateTime? + ) = transaction { + Announcement.findById(id)?.apply { + this.archivedAt = archivedAt ?: java.time.LocalDateTime.now().toKotlinLocalDateTime() + } + } + + fun unarchive(id: Int) = transaction { + Announcement.findById(id)?.apply { + archivedAt = null + } + } + + fun new(new: APIAnnouncement) = transaction { + Announcement.new announcement@{ + author = new.author + title = new.title + content = new.content + channel = new.channel + createdAt = Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault()) + archivedAt = new.archivedAt + level = new.level + }.also { newAnnouncement -> + new.attachmentUrls.map { + Attachment.new { + url = it + announcement = newAnnouncement + } + } + } + } + + fun update(id: Int, new: APIAnnouncement) = transaction { + Announcement.findById(id)?.apply { + author = new.author + title = new.title + content = new.content + channel = new.channel + archivedAt = new.archivedAt + level = new.level + + attachments.forEach(Attachment::delete) + new.attachmentUrls.map { + Attachment.new { + url = it + announcement = this@apply + } + } + } + } +} diff --git a/src/main/kotlin/app/revanced/api/modules/Dependencies.kt b/src/main/kotlin/app/revanced/api/modules/Dependencies.kt new file mode 100644 index 0000000..48ee6c7 --- /dev/null +++ b/src/main/kotlin/app/revanced/api/modules/Dependencies.kt @@ -0,0 +1,73 @@ +package app.revanced.api.modules + +import app.revanced.api.backend.Backend +import app.revanced.api.backend.github.GitHubBackend +import app.revanced.api.schema.APIConfiguration +import com.akuleshov7.ktoml.Toml +import com.akuleshov7.ktoml.source.decodeFromStream +import io.github.cdimascio.dotenv.Dotenv +import io.ktor.server.application.* +import org.jetbrains.exposed.sql.Database +import org.koin.dsl.bind +import org.koin.dsl.module +import org.koin.ktor.plugin.Koin +import java.io.File + +fun Application.configureDependencies() { + install(Koin) { + modules( + globalModule, + gitHubBackendModule, + databaseModule, + authModule + ) + } +} + +val globalModule = module { + single { + Dotenv.load() + } + single { + val configFilePath = get().get("CONFIG_FILE_PATH")!! + Toml.decodeFromStream(File(configFilePath).inputStream()) + } +} + +val gitHubBackendModule = module { + single { + val token = get().get("GITHUB_TOKEN") + GitHubBackend(token) + } bind Backend::class +} + +val databaseModule = module { + single { + val dotenv = get() + + Database.connect( + url = dotenv.get("DB_URL"), + user = dotenv.get("DB_USER"), + password = dotenv.get("DB_PASSWORD"), + driver = "org.h2.Driver" + ) + } + factory { + AnnouncementService(get()) + } +} + +val authModule = module { + single { + val dotenv = get() + + val jwtSecret = dotenv.get("JWT_SECRET")!! + val issuer = dotenv.get("JWT_ISSUER")!! + val validityInMin = dotenv.get("JWT_VALIDITY_IN_MIN")!!.toInt() + + val basicUsername = dotenv.get("BASIC_USERNAME")!! + val basicPassword = dotenv.get("BASIC_PASSWORD")!! + + AuthService(issuer, validityInMin, jwtSecret, basicUsername, basicPassword) + } +} diff --git a/src/main/kotlin/app/revanced/api/plugins/HTTP.kt b/src/main/kotlin/app/revanced/api/modules/HTTP.kt similarity index 96% rename from src/main/kotlin/app/revanced/api/plugins/HTTP.kt rename to src/main/kotlin/app/revanced/api/modules/HTTP.kt index b46f168..590629b 100644 --- a/src/main/kotlin/app/revanced/api/plugins/HTTP.kt +++ b/src/main/kotlin/app/revanced/api/modules/HTTP.kt @@ -1,4 +1,4 @@ -package app.revanced.api.plugins +package app.revanced.api.modules import io.ktor.http.* import io.ktor.http.content.* diff --git a/src/main/kotlin/app/revanced/api/modules/Routing.kt b/src/main/kotlin/app/revanced/api/modules/Routing.kt new file mode 100644 index 0000000..18da381 --- /dev/null +++ b/src/main/kotlin/app/revanced/api/modules/Routing.kt @@ -0,0 +1,158 @@ +package app.revanced.api.modules + +import app.revanced.api.backend.Backend +import app.revanced.api.schema.* +import io.ktor.http.* +import io.ktor.server.application.* +import io.ktor.server.auth.* +import io.ktor.server.http.content.* +import io.ktor.server.request.* +import io.ktor.server.response.* +import io.ktor.server.routing.* +import io.ktor.util.pipeline.* +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll +import kotlinx.datetime.toKotlinLocalDateTime +import java.time.LocalDateTime +import org.koin.ktor.ext.get as koinGet + +fun Application.configureRouting() { + val backend: Backend = koinGet() + val configuration: APIConfiguration = koinGet() + val announcementService: AnnouncementService = koinGet() + val authService: AuthService = koinGet() + + routing { + route("/v${configuration.apiVersion}") { + route("/announcements") { + suspend fun PipelineContext<*, ApplicationCall>.announcement( + block: AnnouncementService.() -> APIResponseAnnouncement? + ) = announcementService.block()?.let { call.respond(it) } + ?: call.respond(HttpStatusCode.NotFound) + + suspend fun PipelineContext<*, ApplicationCall>.announcementId( + block: AnnouncementService.() -> APILatestAnnouncement? + ) = announcementService.block()?.let { call.respond(it) } + ?: call.respond(HttpStatusCode.NotFound) + + suspend fun PipelineContext<*, ApplicationCall>.channel(block: suspend (String) -> Unit) = + block(call.parameters["channel"]!!) + + announcementService.new( + APIAnnouncement( + "author", + "title", + "content", + setOf("https://example.com"), + "channel", + LocalDateTime.now().toKotlinLocalDateTime(), + ) + ) + route("/{channel}/latest") { + get("/id") { channel { announcementId { latestId(it) } } } + + get { channel { announcement { latest(it) } } } + } + + get("/{channel}") { channel { call.respond(announcementService.read(it)) } } + + route("/latest") { + get("/id") { announcementId { latestId() } } + + get { announcement { latest() } } + } + + get { call.respond(announcementService.read()) } + + authenticate("jwt") { + suspend fun PipelineContext<*, ApplicationCall>.id(block: suspend (Int) -> Unit) = + call.parameters["id"]!!.toIntOrNull()?.let { block(it) } + ?: call.respond(HttpStatusCode.BadRequest) + + post { announcementService.new(call.receive()) } + + post("/{id}/archive") { + id { + val archivedAt = call.receiveNullable()?.archivedAt + announcementService.archive(it, archivedAt) + } + } + + post("/{id}/unarchive") { id { announcementService.unarchive(it) } } + + patch("/{id}") { id { announcementService.update(it, call.receive()) } } + + delete("/{id}") { id { announcementService.delete(it) } } + } + } + + route("/patches") { + route("latest") { + get { + val patches = backend.getRelease(configuration.organization, configuration.patchesRepository) + val integrations = configuration.integrationsRepositoryNames.map { + async { backend.getRelease(configuration.organization, it) } + }.awaitAll() + + val assets = (patches.assets + integrations.flatMap { it.assets }).filter { + it.downloadUrl.endsWith(".apk") || it.downloadUrl.endsWith(".jar") + }.map { APIAsset(it.downloadUrl) }.toSet() + + val release = APIRelease( + patches.tag, + patches.createdAt, + patches.releaseNote, + assets + ) + + call.respond(release) + } + + get("/version") { + val patches = backend.getRelease(configuration.organization, configuration.patchesRepository) + + val release = APIReleaseVersion(patches.tag) + + call.respond(release) + } + } + } + + staticResources("/", "/static/api") { + contentType { ContentType.Application.Json } + extensions("json") + } + + get("/contributors") { + val contributors = configuration.contributorsRepositoryNames.map { + async { + APIContributable( + it, + backend.getContributors(configuration.organization, it).map { + APIContributor(it.name, it.avatarUrl, it.url, it.contributions) + }.toSet() + ) + } + }.awaitAll() + + call.respond(contributors) + } + + get("/team") { + val team = backend.getMembers(configuration.organization).map { + APIMember(it.name, it.avatarUrl, it.url, it.gpgKeysUrl) + } + + call.respond(team) + } + + route("/ping") { + handle { + call.respond(HttpStatusCode.NoContent) + } + } + + authenticate("basic") { get("/token") { call.respond(authService.newToken()) } } + } + } +} diff --git a/src/main/kotlin/app/revanced/api/modules/Security.kt b/src/main/kotlin/app/revanced/api/modules/Security.kt new file mode 100644 index 0000000..0dd2dcb --- /dev/null +++ b/src/main/kotlin/app/revanced/api/modules/Security.kt @@ -0,0 +1,53 @@ +package app.revanced.api.modules + +import com.auth0.jwt.JWT +import com.auth0.jwt.algorithms.Algorithm +import io.ktor.server.application.* +import io.ktor.server.auth.* +import io.ktor.server.auth.jwt.* +import org.koin.ktor.ext.get +import java.util.* +import kotlin.time.Duration.Companion.minutes + +class AuthService( + private val issuer: String, + private val validityInMin: Int, + private val jwtSecret: String, + private val basicUsername: String, + private val basicPassword: String, +) { + val configureSecurity: Application.() -> Unit = { + install(Authentication) { + jwt("jwt") { + verifier( + JWT.require(Algorithm.HMAC256(jwtSecret)) + .withIssuer(issuer) + .build() + ) + validate { credential -> JWTPrincipal(credential.payload) } + } + + basic("basic") { + validate { credentials -> + if (credentials.name == basicUsername && credentials.password == basicPassword) { + UserIdPrincipal(credentials.name) + } else { + null + } + } + } + } + } + + fun newToken(): String { + return JWT.create() + .withIssuer(issuer) + .withExpiresAt(Date(System.currentTimeMillis() + validityInMin.minutes.inWholeMilliseconds)) + .sign(Algorithm.HMAC256(jwtSecret)) + } +} + +fun Application.configureSecurity() { + val configureSecurity = get().configureSecurity + configureSecurity() +} \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/api/plugins/Serialization.kt b/src/main/kotlin/app/revanced/api/modules/Serialization.kt similarity index 87% rename from src/main/kotlin/app/revanced/api/plugins/Serialization.kt rename to src/main/kotlin/app/revanced/api/modules/Serialization.kt index e2cb0d1..ef38558 100644 --- a/src/main/kotlin/app/revanced/api/plugins/Serialization.kt +++ b/src/main/kotlin/app/revanced/api/modules/Serialization.kt @@ -1,4 +1,4 @@ -package app.revanced.api.plugins +package app.revanced.api.modules import io.ktor.serialization.kotlinx.json.* import io.ktor.server.application.* diff --git a/src/main/kotlin/app/revanced/api/plugins/Databases.kt b/src/main/kotlin/app/revanced/api/plugins/Databases.kt deleted file mode 100644 index c023db1..0000000 --- a/src/main/kotlin/app/revanced/api/plugins/Databases.kt +++ /dev/null @@ -1,49 +0,0 @@ -package app.revanced.api.plugins - -import io.ktor.http.* -import io.ktor.server.application.* -import io.ktor.server.request.* -import io.ktor.server.response.* -import io.ktor.server.routing.* -import org.jetbrains.exposed.sql.* - -fun Application.configureDatabases() { - val database = Database.connect( - url = "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1", - user = "root", - driver = "org.h2.Driver", - password = "" - ) - val userService = UserService(database) - routing { - // Create user - post("/users") { - val user = call.receive() - val id = userService.create(user) - call.respond(HttpStatusCode.Created, id) - } - // Read user - get("/users/{id}") { - val id = call.parameters["id"]?.toInt() ?: throw IllegalArgumentException("Invalid ID") - val user = userService.read(id) - if (user != null) { - call.respond(HttpStatusCode.OK, user) - } else { - call.respond(HttpStatusCode.NotFound) - } - } - // Update user - put("/users/{id}") { - val id = call.parameters["id"]?.toInt() ?: throw IllegalArgumentException("Invalid ID") - val user = call.receive() - userService.update(id, user) - call.respond(HttpStatusCode.OK) - } - // Delete user - delete("/users/{id}") { - val id = call.parameters["id"]?.toInt() ?: throw IllegalArgumentException("Invalid ID") - userService.delete(id) - call.respond(HttpStatusCode.OK) - } - } -} diff --git a/src/main/kotlin/app/revanced/api/plugins/Dependencies.kt b/src/main/kotlin/app/revanced/api/plugins/Dependencies.kt deleted file mode 100644 index 7667e3f..0000000 --- a/src/main/kotlin/app/revanced/api/plugins/Dependencies.kt +++ /dev/null @@ -1,33 +0,0 @@ -package app.revanced.api.plugins - -import app.revanced.api.schema.APIConfiguration -import app.revanced.api.backend.github.GitHubBackend -import com.akuleshov7.ktoml.Toml -import com.akuleshov7.ktoml.source.decodeFromStream -import io.github.cdimascio.dotenv.Dotenv -import io.ktor.server.application.* -import org.koin.dsl.module -import org.koin.ktor.plugin.Koin -import java.io.File - -fun Application.configureDependencies() { - - install(Koin) { - modules( - module { - single { - Dotenv.load() - } - single { - val configFilePath = get().get("CONFIG_FILE_PATH")!! - Toml.decodeFromStream(File(configFilePath).inputStream()) - } - single { - val token = get().get("GITHUB_TOKEN") - GitHubBackend(token) - } - } - ) - } -} - diff --git a/src/main/kotlin/app/revanced/api/plugins/Routing.kt b/src/main/kotlin/app/revanced/api/plugins/Routing.kt deleted file mode 100644 index 0138207..0000000 --- a/src/main/kotlin/app/revanced/api/plugins/Routing.kt +++ /dev/null @@ -1,88 +0,0 @@ -package app.revanced.api.plugins - -import app.revanced.api.backend.github.GitHubBackend -import app.revanced.api.schema.* -import io.ktor.http.* -import io.ktor.server.application.* -import io.ktor.server.http.content.* -import io.ktor.server.response.* -import io.ktor.server.routing.* -import kotlinx.coroutines.async -import kotlinx.coroutines.awaitAll -import org.koin.ktor.ext.inject - -fun Application.configureRouting() { - val backend by inject() - val configuration by inject() - - routing { - route("/v${configuration.apiVersion}") { - route("/patches") { - route("latest") { - get { - val patches = backend.getRelease(configuration.organization, configuration.patchesRepository) - val integrations = configuration.integrationsRepositoryNames.map { - async { backend.getRelease(configuration.organization, it) } - }.awaitAll() - - val assets = (patches.assets + integrations.flatMap { it.assets }).filter { - it.downloadUrl.endsWith(".apk") || it.downloadUrl.endsWith(".jar") - }.map { APIAsset(it.downloadUrl) }.toSet() - - val release = APIRelease( - patches.tag, - patches.createdAt, - patches.releaseNote, - assets - ) - - call.respond(release) - } - - get("/version") { - val patches = backend.getRelease(configuration.organization, configuration.patchesRepository) - - val release = APIReleaseVersion(patches.tag) - - call.respond(release) - } - } - } - - get("/contributors") { - val contributors = configuration.contributorsRepositoryNames.map { - async { - APIContributable( - it, - backend.getContributors(configuration.organization, it).map { - APIContributor(it.name, it.avatarUrl, it.url, it.contributions) - }.toSet() - ) - } - }.awaitAll() - - call.respond(contributors) - } - - get("/members") { - val members = backend.getMembers(configuration.organization).map { - APIMember(it.name, it.avatarUrl, it.url, it.gpgKeysUrl) - } - - call.respond(members) - } - - route("/ping") { - handle { - call.respond(HttpStatusCode.NoContent) - } - } - - staticResources("/", "/static/api") { - contentType { ContentType.Application.Json } - extensions("json") - } - } - - } -} diff --git a/src/main/kotlin/app/revanced/api/plugins/Security.kt b/src/main/kotlin/app/revanced/api/plugins/Security.kt deleted file mode 100644 index 38bcf7f..0000000 --- a/src/main/kotlin/app/revanced/api/plugins/Security.kt +++ /dev/null @@ -1,30 +0,0 @@ -package app.revanced.api.plugins - -import com.auth0.jwt.JWT -import com.auth0.jwt.algorithms.Algorithm -import io.ktor.server.application.* -import io.ktor.server.auth.* -import io.ktor.server.auth.jwt.* - -fun Application.configureSecurity() { - // Please read the jwt property from the config file if you are using EngineMain - val jwtAudience = "jwt-audience" - val jwtDomain = "https://jwt-provider-domain/" - val jwtRealm = "ktor sample app" - val jwtSecret = "secret" - authentication { - jwt { - realm = jwtRealm - verifier( - JWT - .require(Algorithm.HMAC256(jwtSecret)) - .withAudience(jwtAudience) - .withIssuer(jwtDomain) - .build() - ) - validate { credential -> - if (credential.payload.audience.contains(jwtAudience)) JWTPrincipal(credential.payload) else null - } - } - } -} diff --git a/src/main/kotlin/app/revanced/api/plugins/UsersSchema.kt b/src/main/kotlin/app/revanced/api/plugins/UsersSchema.kt deleted file mode 100644 index 9867997..0000000 --- a/src/main/kotlin/app/revanced/api/plugins/UsersSchema.kt +++ /dev/null @@ -1,59 +0,0 @@ -package app.revanced.api.plugins - -import org.jetbrains.exposed.sql.transactions.transaction -import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction -import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq -import kotlinx.serialization.Serializable -import kotlinx.coroutines.Dispatchers -import org.jetbrains.exposed.sql.* - -@Serializable -data class ExposedUser(val name: String, val age: Int) -class UserService(private val database: Database) { - object Users : Table() { - val id = integer("id").autoIncrement() - val name = varchar("name", length = 50) - val age = integer("age") - - override val primaryKey = PrimaryKey(id) - } - - init { - transaction(database) { - SchemaUtils.create(Users) - } - } - - suspend fun dbQuery(block: suspend () -> T): T = - newSuspendedTransaction(Dispatchers.IO) { block() } - - suspend fun create(user: ExposedUser): Int = dbQuery { - Users.insert { - it[name] = user.name - it[age] = user.age - }[Users.id] - } - - suspend fun read(id: Int): ExposedUser? { - return dbQuery { - Users.select { Users.id eq id } - .map { ExposedUser(it[Users.name], it[Users.age]) } - .singleOrNull() - } - } - - suspend fun update(id: Int, user: ExposedUser) { - dbQuery { - Users.update({ Users.id eq id }) { - it[name] = user.name - it[age] = user.age - } - } - } - - suspend fun delete(id: Int) { - dbQuery { - Users.deleteWhere { Users.id.eq(id) } - } - } -} diff --git a/src/main/kotlin/app/revanced/api/schema/APISchema.kt b/src/main/kotlin/app/revanced/api/schema/APISchema.kt index 4432470..295278a 100644 --- a/src/main/kotlin/app/revanced/api/schema/APISchema.kt +++ b/src/main/kotlin/app/revanced/api/schema/APISchema.kt @@ -1,11 +1,12 @@ package app.revanced.api.schema +import kotlinx.datetime.LocalDateTime import kotlinx.serialization.Serializable @Serializable class APIRelease( val version: String, - val createdAt: String, + val createdAt: LocalDateTime, val changelog: String, val assets: Set ) @@ -56,18 +57,34 @@ class APIReleaseVersion( @Serializable class APIAnnouncement( - val id: Int, - val author: APIUser?, + val author: String? = null, val title: String, - val content: APIAnnouncementContent, - val channel: String, - val createdAt: String, - val archivedAt: String?, - val level: Int, + val content: String? = null, + val attachmentUrls: Set = emptySet(), + val channel: String? = null, + val archivedAt: LocalDateTime? = null, + val level: Int = 0 ) @Serializable -class APIAnnouncementContent( - val message: String, - val attachmentUrls: Set +class APIResponseAnnouncement( + val id: Int, + val author: String? = null, + val title: String, + val content: String? = null, + val attachmentUrls: Set = emptySet(), + val channel: String? = null, + val createdAt: LocalDateTime, + val archivedAt: LocalDateTime? = null, + val level: Int = 0 ) + +@Serializable +class APILatestAnnouncement( + val id: Int +) + +@Serializable +class APIAnnouncementArchivedAt( + val archivedAt: LocalDateTime +) \ No newline at end of file diff --git a/src/test/kotlin/app/revanced/ApplicationTest.kt b/src/test/kotlin/app/revanced/ApplicationTest.kt index c4ba45b..a38fa2b 100644 --- a/src/test/kotlin/app/revanced/ApplicationTest.kt +++ b/src/test/kotlin/app/revanced/ApplicationTest.kt @@ -1,6 +1,6 @@ package app.revanced -import app.revanced.api.plugins.configureRouting +import app.revanced.api.modules.configureRouting import io.ktor.client.request.* import io.ktor.client.statement.* import io.ktor.http.* From 1dba4923c80cd113942dfc3c63aff81251102701 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Wed, 31 Jan 2024 09:20:28 +0100 Subject: [PATCH 014/102] chore: Remove test --- src/main/kotlin/app/revanced/api/modules/Routing.kt | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/main/kotlin/app/revanced/api/modules/Routing.kt b/src/main/kotlin/app/revanced/api/modules/Routing.kt index 18da381..b5fdfaf 100644 --- a/src/main/kotlin/app/revanced/api/modules/Routing.kt +++ b/src/main/kotlin/app/revanced/api/modules/Routing.kt @@ -38,16 +38,6 @@ fun Application.configureRouting() { suspend fun PipelineContext<*, ApplicationCall>.channel(block: suspend (String) -> Unit) = block(call.parameters["channel"]!!) - announcementService.new( - APIAnnouncement( - "author", - "title", - "content", - setOf("https://example.com"), - "channel", - LocalDateTime.now().toKotlinLocalDateTime(), - ) - ) route("/{channel}/latest") { get("/id") { channel { announcementId { latestId(it) } } } From 673efc803869dd686eb72982d99d824e0eec4a83 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Wed, 31 Jan 2024 11:58:52 +0100 Subject: [PATCH 015/102] test: Add initial test for token --- build.gradle.kts | 1 + gradle/libs.versions.toml | 2 + .../app/revanced/api/modules/Dependencies.kt | 20 ++++---- .../kotlin/app/revanced/ApplicationTest.kt | 48 ++++++++++++++++--- 4 files changed, 55 insertions(+), 16 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index b932b6f..dfab2d5 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -53,6 +53,7 @@ dependencies { implementation(libs.picocli) implementation(libs.kotlinx.datetime) + testImplementation(libs.mockk) testImplementation(libs.ktor.server.tests) testImplementation(libs.kotlin.test.junit) } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index f5d3655..c182bba 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -9,6 +9,7 @@ ktor = "2.3.7" ktoml = "0.5.1" picocli = "4.7.3" datetime = "0.5.0" +mockk = "1.13.9" [libraries] ktor-client-core = { module = "io.ktor:ktor-client-core" } @@ -41,6 +42,7 @@ ktoml-core = { module = "com.akuleshov7:ktoml-core", version.ref = "ktoml" } ktoml-file = { module = "com.akuleshov7:ktoml-file", version.ref = "ktoml" } picocli = { module = "info.picocli:picocli", version.ref = "picocli" } kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "datetime" } +mockk = { module = "io.mockk:mockk", version.ref = "mockk" } [plugins] serilization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } diff --git a/src/main/kotlin/app/revanced/api/modules/Dependencies.kt b/src/main/kotlin/app/revanced/api/modules/Dependencies.kt index 48ee6c7..80181a7 100644 --- a/src/main/kotlin/app/revanced/api/modules/Dependencies.kt +++ b/src/main/kotlin/app/revanced/api/modules/Dependencies.kt @@ -29,14 +29,14 @@ val globalModule = module { Dotenv.load() } single { - val configFilePath = get().get("CONFIG_FILE_PATH")!! + val configFilePath = get()["CONFIG_FILE_PATH"] Toml.decodeFromStream(File(configFilePath).inputStream()) } } val gitHubBackendModule = module { single { - val token = get().get("GITHUB_TOKEN") + val token = get()["GITHUB_TOKEN"] GitHubBackend(token) } bind Backend::class } @@ -46,9 +46,9 @@ val databaseModule = module { val dotenv = get() Database.connect( - url = dotenv.get("DB_URL"), - user = dotenv.get("DB_USER"), - password = dotenv.get("DB_PASSWORD"), + url = dotenv["DB_URL"], + user = dotenv["DB_USER"], + password = dotenv["DB_PASSWORD"], driver = "org.h2.Driver" ) } @@ -61,12 +61,12 @@ val authModule = module { single { val dotenv = get() - val jwtSecret = dotenv.get("JWT_SECRET")!! - val issuer = dotenv.get("JWT_ISSUER")!! - val validityInMin = dotenv.get("JWT_VALIDITY_IN_MIN")!!.toInt() + val jwtSecret = dotenv["JWT_SECRET"] + val issuer = dotenv["JWT_ISSUER"] + val validityInMin = dotenv["JWT_VALIDITY_IN_MIN"].toInt() - val basicUsername = dotenv.get("BASIC_USERNAME")!! - val basicPassword = dotenv.get("BASIC_PASSWORD")!! + val basicUsername = dotenv["BASIC_USERNAME"] + val basicPassword = dotenv["BASIC_PASSWORD"] AuthService(issuer, validityInMin, jwtSecret, basicUsername, basicPassword) } diff --git a/src/test/kotlin/app/revanced/ApplicationTest.kt b/src/test/kotlin/app/revanced/ApplicationTest.kt index a38fa2b..51682fe 100644 --- a/src/test/kotlin/app/revanced/ApplicationTest.kt +++ b/src/test/kotlin/app/revanced/ApplicationTest.kt @@ -1,21 +1,57 @@ package app.revanced -import app.revanced.api.modules.configureRouting +import app.revanced.api.modules.* +import app.revanced.api.schema.APIConfiguration +import com.akuleshov7.ktoml.Toml +import io.github.cdimascio.dotenv.Dotenv import io.ktor.client.request.* import io.ktor.client.statement.* import io.ktor.http.* import io.ktor.server.testing.* +import io.ktor.util.* +import io.mockk.every +import io.mockk.mockk +import kotlinx.serialization.encodeToString import kotlin.test.* class ApplicationTest { @Test - fun testRoot() = testApplication { + fun `successfully create a token`() = testApplication { + val apiConfigurationFile = kotlin.io.path.createTempFile().toFile().apply { + Toml.encodeToString( + APIConfiguration( + organization = "ReVanced", + patchesRepository = "", + integrationsRepositoryNames = setOf(), + contributorsRepositoryNames = setOf() + ) + ).let(::writeText) + + deleteOnExit() + } + + val dotenv = mockk() + every { dotenv[any()] } returns "ReVanced" + every { dotenv["JWT_VALIDITY_IN_MIN"] } returns "5" + every { dotenv["CONFIG_FILE_PATH"] } returns apiConfigurationFile.absolutePath + application { + configureDependencies() + configureHTTP() + configureSerialization() + configureSecurity() configureRouting() } - client.get("/").apply { - assertEquals(HttpStatusCode.OK, status) - assertEquals("Hello World!", bodyAsText()) - } + + val token = client.get("/v1/token") { + headers { + append( + HttpHeaders.Authorization, + "Basic ${"${dotenv["BASIC_USERNAME"]}:${dotenv["BASIC_PASSWORD"]}".encodeBase64()}" + ) + } + }.bodyAsText() + + assert(token.isNotEmpty()) } } From e373d269982428357bd04b659d9d17110cf0d5d2 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Wed, 31 Jan 2024 22:27:31 +0100 Subject: [PATCH 016/102] feat: Load system properties --- src/main/kotlin/app/revanced/api/modules/Dependencies.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/app/revanced/api/modules/Dependencies.kt b/src/main/kotlin/app/revanced/api/modules/Dependencies.kt index 80181a7..4f0b14f 100644 --- a/src/main/kotlin/app/revanced/api/modules/Dependencies.kt +++ b/src/main/kotlin/app/revanced/api/modules/Dependencies.kt @@ -26,7 +26,10 @@ fun Application.configureDependencies() { val globalModule = module { single { - Dotenv.load() + Dotenv.configure() + .ignoreIfMissing() + .systemProperties() + .load() } single { val configFilePath = get()["CONFIG_FILE_PATH"] From 1dccfd2deff3c5de6a6cf2156cac8516b40bdd22 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Thu, 1 Feb 2024 03:52:44 +0100 Subject: [PATCH 017/102] fix: Serialize response correctly --- .../app/revanced/api/backend/github/GitHubBackend.kt | 8 ++++---- .../app/revanced/api/backend/github/api/ResponseSchema.kt | 5 ++--- src/main/kotlin/app/revanced/api/modules/Routing.kt | 2 -- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/src/main/kotlin/app/revanced/api/backend/github/GitHubBackend.kt b/src/main/kotlin/app/revanced/api/backend/github/GitHubBackend.kt index 10dcfda..49d4c32 100644 --- a/src/main/kotlin/app/revanced/api/backend/github/GitHubBackend.kt +++ b/src/main/kotlin/app/revanced/api/backend/github/GitHubBackend.kt @@ -23,6 +23,8 @@ import app.revanced.api.backend.github.api.Response.GitHubOrganization.GitHubMem import io.ktor.client.plugins.resources.Resources import io.ktor.serialization.kotlinx.json.* import kotlinx.coroutines.* +import kotlinx.datetime.TimeZone +import kotlinx.datetime.toLocalDateTime import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonNamingStrategy @@ -71,11 +73,9 @@ class GitHubBackend(token: String? = null) : Backend({ return BackendRelease( tag = release.tagName, releaseNote = release.body, - createdAt = release.createdAt, + createdAt = release.createdAt.toLocalDateTime(TimeZone.UTC), assets = release.assets.map { - BackendAsset( - downloadUrl = it.browserDownloadUrl - ) + BackendAsset(downloadUrl = it.browserDownloadUrl) }.toSet() ) } diff --git a/src/main/kotlin/app/revanced/api/backend/github/api/ResponseSchema.kt b/src/main/kotlin/app/revanced/api/backend/github/api/ResponseSchema.kt index 9fe86ab..fbfd89a 100644 --- a/src/main/kotlin/app/revanced/api/backend/github/api/ResponseSchema.kt +++ b/src/main/kotlin/app/revanced/api/backend/github/api/ResponseSchema.kt @@ -1,7 +1,6 @@ package app.revanced.api.backend.github.api -import kotlinx.datetime.LocalDateTime -import kotlinx.serialization.SerialName +import kotlinx.datetime.Instant import kotlinx.serialization.Serializable @@ -41,7 +40,7 @@ class Response { class GitHubRelease( val tagName: String, val assets: Set, - val createdAt: LocalDateTime, + val createdAt: Instant, val body: String ) { @Serializable diff --git a/src/main/kotlin/app/revanced/api/modules/Routing.kt b/src/main/kotlin/app/revanced/api/modules/Routing.kt index b5fdfaf..f39d7e7 100644 --- a/src/main/kotlin/app/revanced/api/modules/Routing.kt +++ b/src/main/kotlin/app/revanced/api/modules/Routing.kt @@ -12,8 +12,6 @@ import io.ktor.server.routing.* import io.ktor.util.pipeline.* import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll -import kotlinx.datetime.toKotlinLocalDateTime -import java.time.LocalDateTime import org.koin.ktor.ext.get as koinGet fun Application.configureRouting() { From c736a75d92d97124e1aca392f37049449a786c84 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Thu, 1 Feb 2024 03:54:04 +0100 Subject: [PATCH 018/102] feat: Setup CI/CD --- .env.example | 4 +- .gitattributes | 9 + .github/workflows/pull_request.yml | 26 + .github/workflows/release.yml | 89 + .gitignore | 4 +- .releaserc | 49 + Dockerfile | 31 + LICENSE | 674 +++ build.gradle.kts | 35 +- docker-compose.yml | 23 + gradle.properties | 2 +- gradle/libs.versions.toml | 2 +- package-lock.json | 6895 ++++++++++++++++++++++++++++ package.json | 9 + settings.gradle.kts | 4 +- 15 files changed, 7842 insertions(+), 14 deletions(-) create mode 100644 .gitattributes create mode 100644 .github/workflows/pull_request.yml create mode 100644 .github/workflows/release.yml create mode 100644 .releaserc create mode 100644 Dockerfile create mode 100644 LICENSE create mode 100644 docker-compose.yml create mode 100644 package-lock.json create mode 100644 package.json diff --git a/.env.example b/.env.example index 91a9819..e3a8ba0 100644 --- a/.env.example +++ b/.env.example @@ -1,7 +1,7 @@ -GITHUB_TOKEN= CONFIG_FILE_PATH=configuration.toml +GITHUB_TOKEN= -DB_URL=jdbc:h2:./api.db +DB_URL=jdbc:h2:./persistence/revanced-api DB_USER= DB_PASSWORD= diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..097f9f9 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,9 @@ +# +# https://help.github.com/articles/dealing-with-line-endings/ +# +# Linux start script should use lf +/gradlew text eol=lf + +# These are Windows script files and should use crlf +*.bat text eol=crlf + diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml new file mode 100644 index 0000000..41e4554 --- /dev/null +++ b/.github/workflows/pull_request.yml @@ -0,0 +1,26 @@ +name: Open a PR to main + +on: + push: + branches: + - dev + workflow_dispatch: + +env: + MESSAGE: Merge branch `${{ github.head_ref || github.ref_name }}` to `main` + +jobs: + pull-request: + name: Open pull request + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Open pull request + uses: repo-sync/pull-request@v2 + with: + destination_branch: 'main' + pr_title: 'chore: ${{ env.MESSAGE }}' + pr_body: 'This pull request will ${{ env.MESSAGE }}.' + pr_draft: true \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..da4fb63 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,89 @@ +name: Release + +on: + workflow_dispatch: + push: + branches: + - main + - dev + pull_request: + branches: + - main + - dev + +jobs: + release: + name: Release + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + # Make sure the release step uses its own credentials: + # https://github.com/cycjimmy/semantic-release-action#private-packages + persist-credentials: false + fetch-depth: 0 + + - name: Cache Gradle + uses: burrunan/gradle-cache-action@v1 + + - name: Build + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: ./gradlew build clean + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: "lts/*" + cache: 'npm' + + - name: Install dependencies + run: npm install + + - name: Release + env: + GITHUB_TOKEN: ${{ secrets.REPOSITORY_PUSH_ACCESS }} + run: npm exec semantic-release + + - name: Setup QEMU + uses: docker/setup-qemu-action@v3 + with: + image: tonistiigi/binfmt:latest + platforms: all + + - name: Setup Docker buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.repository_owner }} + password: ${{ secrets.GH_TOKEN }} + + - name: Extract metadata (tags, labels) for Docker + id: metadata + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + flavor: | + latest=${{ startsWith(github.ref, 'refs/heads/main') }} + suffix=-${{ github.sha }} + + - name: Build and push Docker image + id: build + uses: docker/build-push-action@v5 + with: + build-args: GH_TOKEN=${{ secrets.GH_TOKEN }} + context: . + file: ./Dockerfile + platforms: linux/amd64,linux/arm64/v8 + cache-to: type=gha,mode=max,ignore-error=true + cache-from: type=gha + push: true + tags: ${{ steps.metadata.outputs.tags }} + labels: ${{ steps.metadata.outputs.labels }} diff --git a/.gitignore b/.gitignore index dd55c73..a300aea 100644 --- a/.gitignore +++ b/.gitignore @@ -37,4 +37,6 @@ out/ ### Project ### .env -configuration.toml \ No newline at end of file +persistence/ +configuration.toml +.db \ No newline at end of file diff --git a/.releaserc b/.releaserc new file mode 100644 index 0000000..79200dc --- /dev/null +++ b/.releaserc @@ -0,0 +1,49 @@ +{ + "branches": [ + "main", + { + "name": "dev", + "prerelease": true + } + ], + "plugins": [ + [ + "@semantic-release/commit-analyzer", { + "releaseRules": [ + { "type": "build", "scope": "Needs bump", "release": "patch" } + ] + } + ], + "@semantic-release/release-notes-generator", + "@semantic-release/changelog", + "gradle-semantic-release-plugin", + [ + "@semantic-release/git", + { + "assets": [ + "README.md", + "CHANGELOG.md", + "gradle.properties", + ] + } + ], + [ + "@semantic-release/github", + { + "assets": [ + { + "path": "build/libs/*.jar" + } + ], + successComment: false + } + ], + [ + "@saithodev/semantic-release-backmerge", + { + backmergeBranches: [{"from": "main", "to": "dev"}], + clearWorkspace: true + } + ] + ] +} diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..5a0c2a1 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,31 @@ +FROM azul/zulu-openjdk:latest + +ARG CONFIG_FILE_PATH + +ARG DB_URL +ARG DB_USER +ARG DB_PASSWORD + +ARG JWT_SECRET +ARG JWT_ISSUER +ARG JWT_VALIDITY_IN_MIN + +ARG BASIC_USERNAME +ARG BASIC_PASSWORD + +ENV CONFIG_FILE_PATH $CONFIG_FILE_PATH + +ENV DB_URL $DB_URL +ENV DB_USER $DB_USER +ENV DB_PASSWORD $DB_PASSWORD + +ENV JWT_SECRET $JWT_SECRET +ENV JWT_ISSUER $JWT_ISSUER +ENV JWT_VALIDITY_IN_MIN $JWT_VALIDITY_IN_MIN + +ENV BASIC_USERNAME $BASIC_USERNAME +ENV BASIC_PASSWORD $BASIC_PASSWORD + +COPY build/libs/revanced-api-*.jar revanced-api.jar + +CMD java -jar revanced-api.jar start \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f288702 --- /dev/null +++ b/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/build.gradle.kts b/build.gradle.kts index dfab2d5..cb4f866 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -6,17 +6,38 @@ plugins { group = "app.revanced" -application { - mainClass.set("app.revanced.api.command.MainCommandKt") - - val isDevelopment: Boolean = project.ext.has("development") - applicationDefaultJvmArgs = listOf("-Dio.ktor.development=$isDevelopment") -} - tasks { processResources { expand("projectVersion" to project.version) } + + /* + Dummy task to hack gradle-semantic-release-plugin to release this project. + + Explanation: + SemVer is a standard for versioning libraries. + For that reason the semantic-release plugin uses the "publish" task to publish libraries. + However, this subproject is not a library, and the "publish" task is not available for this subproject. + Because semantic-release is not designed to handle this case, we need to hack it. + + RE: https://github.com/KengoTODA/gradle-semantic-release-plugin/issues/435 + */ + + register("publish") { + group = "publishing" + description = "Dummy task to hack gradle-semantic-release-plugin to release ReVanced API" + dependsOn(startShadowScripts) + } +} + +application { + mainClass.set("app.revanced.api.command.MainCommandKt") +} + +ktor { + fatJar { + archiveFileName.set("${project.name}-${project.version}.jar") + } } repositories { diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..78d2377 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,23 @@ +version: "3.8" + +services: + revanced-api: + container_name: revanced-api + image: ghcr.io/revanced/revanced-api:latest + volumes: + - /data/revanced-api/persistence:/persistence + - /data/revanced-api/configuration.toml:/configuration.toml + environment: + - CONFIG_FILE_PATH=configuration.toml + - GITHUB_TOKEN= + - DB_URL=jdbc:h2:./persistence/revanced-api + - DB_USER= + - DB_PASSWORD= + - JWT_SECRET= + - JWT_ISSUER= + - JWT_VALIDITY_IN_MIN=5 + - BASIC_USERNAME= + - BASIC_PASSWORD= + ports: + - 127.0.0.1:8080:8000 + restart: unless-stopped diff --git a/gradle.properties b/gradle.properties index 859e808..65e2d64 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ org.gradle.parallel = true org.gradle.caching = true kotlin.code.style = official -version = 0.0.1 \ No newline at end of file +version = 1.0.0 \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c182bba..6bce316 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -47,4 +47,4 @@ mockk = { module = "io.mockk:mockk", version.ref = "mockk" } [plugins] serilization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } ktor = { id = "io.ktor.plugin", version.ref = "ktor" } -kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } \ No newline at end of file +kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..29e79ed --- /dev/null +++ b/package-lock.json @@ -0,0 +1,6895 @@ +{ + "name": "revanced-patches", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "devDependencies": { + "@saithodev/semantic-release-backmerge": "^4.0.1", + "@semantic-release/changelog": "^6.0.3", + "@semantic-release/git": "^10.0.1", + "gradle-semantic-release-plugin": "^1.9.1", + "semantic-release": "^23.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz", + "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.23.4", + "chalk": "^2.4.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/code-frame/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/code-frame/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/@babel/code-frame/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz", + "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/highlight/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/@babel/highlight/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "dev": true, + "optional": true, + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@octokit/auth-token": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-4.0.0.tgz", + "integrity": "sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA==", + "dev": true, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/core": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@octokit/core/-/core-5.1.0.tgz", + "integrity": "sha512-BDa2VAMLSh3otEiaMJ/3Y36GU4qf6GI+VivQ/P41NC6GHcdxpKlqV0ikSZ5gdQsmS3ojXeRx5vasgNTinF0Q4g==", + "dev": true, + "dependencies": { + "@octokit/auth-token": "^4.0.0", + "@octokit/graphql": "^7.0.0", + "@octokit/request": "^8.0.2", + "@octokit/request-error": "^5.0.0", + "@octokit/types": "^12.0.0", + "before-after-hook": "^2.2.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/endpoint": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-9.0.4.tgz", + "integrity": "sha512-DWPLtr1Kz3tv8L0UvXTDP1fNwM0S+z6EJpRcvH66orY6Eld4XBMCSYsaWp4xIm61jTWxK68BrR7ibO+vSDnZqw==", + "dev": true, + "dependencies": { + "@octokit/types": "^12.0.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/graphql": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-7.0.2.tgz", + "integrity": "sha512-OJ2iGMtj5Tg3s6RaXH22cJcxXRi7Y3EBqbHTBRq+PQAqfaS8f/236fUrWhfSn8P4jovyzqucxme7/vWSSZBX2Q==", + "dev": true, + "dependencies": { + "@octokit/request": "^8.0.1", + "@octokit/types": "^12.0.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/openapi-types": { + "version": "19.1.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-19.1.0.tgz", + "integrity": "sha512-6G+ywGClliGQwRsjvqVYpklIfa7oRPA0vyhPQG/1Feh+B+wU0vGH1JiJ5T25d3g1JZYBHzR2qefLi9x8Gt+cpw==", + "dev": true + }, + "node_modules/@octokit/plugin-paginate-rest": { + "version": "9.1.5", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-9.1.5.tgz", + "integrity": "sha512-WKTQXxK+bu49qzwv4qKbMMRXej1DU2gq017euWyKVudA6MldaSSQuxtz+vGbhxV4CjxpUxjZu6rM2wfc1FiWVg==", + "dev": true, + "dependencies": { + "@octokit/types": "^12.4.0" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": ">=5" + } + }, + "node_modules/@octokit/plugin-retry": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@octokit/plugin-retry/-/plugin-retry-6.0.1.tgz", + "integrity": "sha512-SKs+Tz9oj0g4p28qkZwl/topGcb0k0qPNX/i7vBKmDsjoeqnVfFUquqrE/O9oJY7+oLzdCtkiWSXLpLjvl6uog==", + "dev": true, + "dependencies": { + "@octokit/request-error": "^5.0.0", + "@octokit/types": "^12.0.0", + "bottleneck": "^2.15.3" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": ">=5" + } + }, + "node_modules/@octokit/plugin-throttling": { + "version": "8.1.3", + "resolved": "https://registry.npmjs.org/@octokit/plugin-throttling/-/plugin-throttling-8.1.3.tgz", + "integrity": "sha512-pfyqaqpc0EXh5Cn4HX9lWYsZ4gGbjnSmUILeu4u2gnuM50K/wIk9s1Pxt3lVeVwekmITgN/nJdoh43Ka+vye8A==", + "dev": true, + "dependencies": { + "@octokit/types": "^12.2.0", + "bottleneck": "^2.15.3" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": "^5.0.0" + } + }, + "node_modules/@octokit/request": { + "version": "8.1.6", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-8.1.6.tgz", + "integrity": "sha512-YhPaGml3ncZC1NfXpP3WZ7iliL1ap6tLkAp6MvbK2fTTPytzVUyUesBBogcdMm86uRYO5rHaM1xIWxigWZ17MQ==", + "dev": true, + "dependencies": { + "@octokit/endpoint": "^9.0.0", + "@octokit/request-error": "^5.0.0", + "@octokit/types": "^12.0.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/request-error": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-5.0.1.tgz", + "integrity": "sha512-X7pnyTMV7MgtGmiXBwmO6M5kIPrntOXdyKZLigNfQWSEQzVxR4a4vo49vJjTWX70mPndj8KhfT4Dx+2Ng3vnBQ==", + "dev": true, + "dependencies": { + "@octokit/types": "^12.0.0", + "deprecation": "^2.0.0", + "once": "^1.4.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/types": { + "version": "12.4.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-12.4.0.tgz", + "integrity": "sha512-FLWs/AvZllw/AGVs+nJ+ELCDZZJk+kY0zMen118xhL2zD0s1etIUHm1odgjP7epxYU1ln7SZxEUWYop5bhsdgQ==", + "dev": true, + "dependencies": { + "@octokit/openapi-types": "^19.1.0" + } + }, + "node_modules/@pnpm/config.env-replace": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@pnpm/config.env-replace/-/config.env-replace-1.1.0.tgz", + "integrity": "sha512-htyl8TWnKL7K/ESFa1oW2UB5lVDxuF5DpM7tBi6Hu2LNL3mWkIzNLG6N4zoCUP1lCKNxWy/3iu8mS8MvToGd6w==", + "dev": true, + "engines": { + "node": ">=12.22.0" + } + }, + "node_modules/@pnpm/network.ca-file": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@pnpm/network.ca-file/-/network.ca-file-1.0.2.tgz", + "integrity": "sha512-YcPQ8a0jwYU9bTdJDpXjMi7Brhkr1mXsXrUJvjqM2mQDgkRiz8jFaQGOdaLxgjtUfQgZhKy/O3cG/YwmgKaxLA==", + "dev": true, + "dependencies": { + "graceful-fs": "4.2.10" + }, + "engines": { + "node": ">=12.22.0" + } + }, + "node_modules/@pnpm/network.ca-file/node_modules/graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "dev": true + }, + "node_modules/@pnpm/npm-conf": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@pnpm/npm-conf/-/npm-conf-2.2.2.tgz", + "integrity": "sha512-UA91GwWPhFExt3IizW6bOeY/pQ0BkuNwKjk9iQW9KqxluGCrg4VenZ0/L+2Y0+ZOtme72EVvg6v0zo3AMQRCeA==", + "dev": true, + "dependencies": { + "@pnpm/config.env-replace": "^1.1.0", + "@pnpm/network.ca-file": "^1.0.1", + "config-chain": "^1.1.11" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@saithodev/semantic-release-backmerge": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@saithodev/semantic-release-backmerge/-/semantic-release-backmerge-4.0.1.tgz", + "integrity": "sha512-WDsU28YrXSLx0xny7FgFlEk8DCKGcj6OOhA+4Q9k3te1jJD1GZuqY8sbIkVQaw9cqJ7CT+fCZUN6QDad8JW4Dg==", + "dev": true, + "dependencies": { + "@semantic-release/error": "^3.0.0", + "aggregate-error": "^3.1.0", + "debug": "^4.3.4", + "execa": "^5.1.1", + "lodash": "^4.17.21", + "semantic-release": "^22.0.7" + } + }, + "node_modules/@saithodev/semantic-release-backmerge/node_modules/clean-stack": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-5.2.0.tgz", + "integrity": "sha512-TyUIUJgdFnCISzG5zu3291TAsE77ddchd0bepon1VVQrKLGKFED4iXFEDQ24mIPdPBbyE16PK3F8MYE1CmcBEQ==", + "dev": true, + "dependencies": { + "escape-string-regexp": "5.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@saithodev/semantic-release-backmerge/node_modules/cosmiconfig": { + "version": "8.3.6", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", + "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", + "dev": true, + "dependencies": { + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0", + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@saithodev/semantic-release-backmerge/node_modules/env-ci": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/env-ci/-/env-ci-10.0.0.tgz", + "integrity": "sha512-U4xcd/utDYFgMh0yWj07R1H6L5fwhVbmxBCpnL0DbVSDZVnsC82HONw0wxtxNkIAcua3KtbomQvIk5xFZGAQJw==", + "dev": true, + "dependencies": { + "execa": "^8.0.0", + "java-properties": "^1.0.2" + }, + "engines": { + "node": "^18.17 || >=20.6.1" + } + }, + "node_modules/@saithodev/semantic-release-backmerge/node_modules/env-ci/node_modules/execa": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", + "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/@saithodev/semantic-release-backmerge/node_modules/env-ci/node_modules/get-stream": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", + "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", + "dev": true, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@saithodev/semantic-release-backmerge/node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@saithodev/semantic-release-backmerge/node_modules/human-signals": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", + "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", + "dev": true, + "engines": { + "node": ">=16.17.0" + } + }, + "node_modules/@saithodev/semantic-release-backmerge/node_modules/indent-string": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz", + "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@saithodev/semantic-release-backmerge/node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@saithodev/semantic-release-backmerge/node_modules/marked": { + "version": "9.1.6", + "resolved": "https://registry.npmjs.org/marked/-/marked-9.1.6.tgz", + "integrity": "sha512-jcByLnIFkd5gSXZmjNvS1TlmRhCXZjIzHYlaGkPlLIekG55JDR2Z4va9tZwCiP+/RDERiNhMOFu01xd6O5ct1Q==", + "dev": true, + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 16" + } + }, + "node_modules/@saithodev/semantic-release-backmerge/node_modules/mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@saithodev/semantic-release-backmerge/node_modules/npm-run-path": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.2.0.tgz", + "integrity": "sha512-W4/tgAXFqFA0iL7fk0+uQ3g7wkL8xJmx3XdK0VGb4cHW//eZTtKGvFBBoRKVTpY7n6ze4NL9ly7rgXcHufqXKg==", + "dev": true, + "dependencies": { + "path-key": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@saithodev/semantic-release-backmerge/node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dev": true, + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@saithodev/semantic-release-backmerge/node_modules/p-reduce": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-reduce/-/p-reduce-3.0.0.tgz", + "integrity": "sha512-xsrIUgI0Kn6iyDYm9StOpOeK29XM1aboGji26+QEortiFST1hGZaUQOLhtEbqHErPpGW/aSz6allwK2qcptp0Q==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@saithodev/semantic-release-backmerge/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@saithodev/semantic-release-backmerge/node_modules/semantic-release": { + "version": "22.0.12", + "resolved": "https://registry.npmjs.org/semantic-release/-/semantic-release-22.0.12.tgz", + "integrity": "sha512-0mhiCR/4sZb00RVFJIUlMuiBkW3NMpVIW2Gse7noqEMoFGkvfPPAImEQbkBV8xga4KOPP4FdTRYuLLy32R1fPw==", + "dev": true, + "dependencies": { + "@semantic-release/commit-analyzer": "^11.0.0", + "@semantic-release/error": "^4.0.0", + "@semantic-release/github": "^9.0.0", + "@semantic-release/npm": "^11.0.0", + "@semantic-release/release-notes-generator": "^12.0.0", + "aggregate-error": "^5.0.0", + "cosmiconfig": "^8.0.0", + "debug": "^4.0.0", + "env-ci": "^10.0.0", + "execa": "^8.0.0", + "figures": "^6.0.0", + "find-versions": "^5.1.0", + "get-stream": "^6.0.0", + "git-log-parser": "^1.2.0", + "hook-std": "^3.0.0", + "hosted-git-info": "^7.0.0", + "import-from-esm": "^1.3.1", + "lodash-es": "^4.17.21", + "marked": "^9.0.0", + "marked-terminal": "^6.0.0", + "micromatch": "^4.0.2", + "p-each-series": "^3.0.0", + "p-reduce": "^3.0.0", + "read-pkg-up": "^11.0.0", + "resolve-from": "^5.0.0", + "semver": "^7.3.2", + "semver-diff": "^4.0.0", + "signale": "^1.2.1", + "yargs": "^17.5.1" + }, + "bin": { + "semantic-release": "bin/semantic-release.js" + }, + "engines": { + "node": "^18.17 || >=20.6.1" + } + }, + "node_modules/@saithodev/semantic-release-backmerge/node_modules/semantic-release/node_modules/@semantic-release/error": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@semantic-release/error/-/error-4.0.0.tgz", + "integrity": "sha512-mgdxrHTLOjOddRVYIYDo0fR3/v61GNN1YGkfbrjuIKg/uMgCd+Qzo3UAXJ+woLQQpos4pl5Esuw5A7AoNlzjUQ==", + "dev": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@saithodev/semantic-release-backmerge/node_modules/semantic-release/node_modules/aggregate-error": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-5.0.0.tgz", + "integrity": "sha512-gOsf2YwSlleG6IjRYG2A7k0HmBMEo6qVNk9Bp/EaLgAJT5ngH6PXbqa4ItvnEwCm/velL5jAnQgsHsWnjhGmvw==", + "dev": true, + "dependencies": { + "clean-stack": "^5.2.0", + "indent-string": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@saithodev/semantic-release-backmerge/node_modules/semantic-release/node_modules/execa": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", + "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/@saithodev/semantic-release-backmerge/node_modules/semantic-release/node_modules/execa/node_modules/get-stream": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", + "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", + "dev": true, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@saithodev/semantic-release-backmerge/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@saithodev/semantic-release-backmerge/node_modules/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@semantic-release/changelog": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@semantic-release/changelog/-/changelog-6.0.3.tgz", + "integrity": "sha512-dZuR5qByyfe3Y03TpmCvAxCyTnp7r5XwtHRf/8vD9EAn4ZWbavUX8adMtXYzE86EVh0gyLA7lm5yW4IV30XUag==", + "dev": true, + "dependencies": { + "@semantic-release/error": "^3.0.0", + "aggregate-error": "^3.0.0", + "fs-extra": "^11.0.0", + "lodash": "^4.17.4" + }, + "engines": { + "node": ">=14.17" + }, + "peerDependencies": { + "semantic-release": ">=18.0.0" + } + }, + "node_modules/@semantic-release/commit-analyzer": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/@semantic-release/commit-analyzer/-/commit-analyzer-11.1.0.tgz", + "integrity": "sha512-cXNTbv3nXR2hlzHjAMgbuiQVtvWHTlwwISt60B+4NZv01y/QRY7p2HcJm8Eh2StzcTJoNnflvKjHH/cjFS7d5g==", + "dev": true, + "dependencies": { + "conventional-changelog-angular": "^7.0.0", + "conventional-commits-filter": "^4.0.0", + "conventional-commits-parser": "^5.0.0", + "debug": "^4.0.0", + "import-from-esm": "^1.0.3", + "lodash-es": "^4.17.21", + "micromatch": "^4.0.2" + }, + "engines": { + "node": "^18.17 || >=20.6.1" + }, + "peerDependencies": { + "semantic-release": ">=20.1.0" + } + }, + "node_modules/@semantic-release/error": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@semantic-release/error/-/error-3.0.0.tgz", + "integrity": "sha512-5hiM4Un+tpl4cKw3lV4UgzJj+SmfNIDCLLw0TepzQxz9ZGV5ixnqkzIVF+3tp0ZHgcMKE+VNGHJjEeyFG2dcSw==", + "dev": true, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/@semantic-release/git": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@semantic-release/git/-/git-10.0.1.tgz", + "integrity": "sha512-eWrx5KguUcU2wUPaO6sfvZI0wPafUKAMNC18aXY4EnNcrZL86dEmpNVnC9uMpGZkmZJ9EfCVJBQx4pV4EMGT1w==", + "dev": true, + "dependencies": { + "@semantic-release/error": "^3.0.0", + "aggregate-error": "^3.0.0", + "debug": "^4.0.0", + "dir-glob": "^3.0.0", + "execa": "^5.0.0", + "lodash": "^4.17.4", + "micromatch": "^4.0.0", + "p-reduce": "^2.0.0" + }, + "engines": { + "node": ">=14.17" + }, + "peerDependencies": { + "semantic-release": ">=18.0.0" + } + }, + "node_modules/@semantic-release/github": { + "version": "9.2.6", + "resolved": "https://registry.npmjs.org/@semantic-release/github/-/github-9.2.6.tgz", + "integrity": "sha512-shi+Lrf6exeNZF+sBhK+P011LSbhmIAoUEgEY6SsxF8irJ+J2stwI5jkyDQ+4gzYyDImzV6LCKdYB9FXnQRWKA==", + "dev": true, + "dependencies": { + "@octokit/core": "^5.0.0", + "@octokit/plugin-paginate-rest": "^9.0.0", + "@octokit/plugin-retry": "^6.0.0", + "@octokit/plugin-throttling": "^8.0.0", + "@semantic-release/error": "^4.0.0", + "aggregate-error": "^5.0.0", + "debug": "^4.3.4", + "dir-glob": "^3.0.1", + "globby": "^14.0.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.0", + "issue-parser": "^6.0.0", + "lodash-es": "^4.17.21", + "mime": "^4.0.0", + "p-filter": "^4.0.0", + "url-join": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "semantic-release": ">=20.1.0" + } + }, + "node_modules/@semantic-release/github/node_modules/@semantic-release/error": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@semantic-release/error/-/error-4.0.0.tgz", + "integrity": "sha512-mgdxrHTLOjOddRVYIYDo0fR3/v61GNN1YGkfbrjuIKg/uMgCd+Qzo3UAXJ+woLQQpos4pl5Esuw5A7AoNlzjUQ==", + "dev": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@semantic-release/github/node_modules/aggregate-error": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-5.0.0.tgz", + "integrity": "sha512-gOsf2YwSlleG6IjRYG2A7k0HmBMEo6qVNk9Bp/EaLgAJT5ngH6PXbqa4ItvnEwCm/velL5jAnQgsHsWnjhGmvw==", + "dev": true, + "dependencies": { + "clean-stack": "^5.2.0", + "indent-string": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@semantic-release/github/node_modules/clean-stack": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-5.2.0.tgz", + "integrity": "sha512-TyUIUJgdFnCISzG5zu3291TAsE77ddchd0bepon1VVQrKLGKFED4iXFEDQ24mIPdPBbyE16PK3F8MYE1CmcBEQ==", + "dev": true, + "dependencies": { + "escape-string-regexp": "5.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@semantic-release/github/node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@semantic-release/github/node_modules/indent-string": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz", + "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@semantic-release/npm": { + "version": "11.0.2", + "resolved": "https://registry.npmjs.org/@semantic-release/npm/-/npm-11.0.2.tgz", + "integrity": "sha512-owtf3RjyPvRE63iUKZ5/xO4uqjRpVQDUB9+nnXj0xwfIeM9pRl+cG+zGDzdftR4m3f2s4Wyf3SexW+kF5DFtWA==", + "dev": true, + "dependencies": { + "@semantic-release/error": "^4.0.0", + "aggregate-error": "^5.0.0", + "execa": "^8.0.0", + "fs-extra": "^11.0.0", + "lodash-es": "^4.17.21", + "nerf-dart": "^1.0.0", + "normalize-url": "^8.0.0", + "npm": "^10.0.0", + "rc": "^1.2.8", + "read-pkg": "^9.0.0", + "registry-auth-token": "^5.0.0", + "semver": "^7.1.2", + "tempy": "^3.0.0" + }, + "engines": { + "node": "^18.17 || >=20" + }, + "peerDependencies": { + "semantic-release": ">=20.1.0" + } + }, + "node_modules/@semantic-release/npm/node_modules/@semantic-release/error": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@semantic-release/error/-/error-4.0.0.tgz", + "integrity": "sha512-mgdxrHTLOjOddRVYIYDo0fR3/v61GNN1YGkfbrjuIKg/uMgCd+Qzo3UAXJ+woLQQpos4pl5Esuw5A7AoNlzjUQ==", + "dev": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@semantic-release/npm/node_modules/aggregate-error": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-5.0.0.tgz", + "integrity": "sha512-gOsf2YwSlleG6IjRYG2A7k0HmBMEo6qVNk9Bp/EaLgAJT5ngH6PXbqa4ItvnEwCm/velL5jAnQgsHsWnjhGmvw==", + "dev": true, + "dependencies": { + "clean-stack": "^5.2.0", + "indent-string": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@semantic-release/npm/node_modules/clean-stack": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-5.2.0.tgz", + "integrity": "sha512-TyUIUJgdFnCISzG5zu3291TAsE77ddchd0bepon1VVQrKLGKFED4iXFEDQ24mIPdPBbyE16PK3F8MYE1CmcBEQ==", + "dev": true, + "dependencies": { + "escape-string-regexp": "5.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@semantic-release/npm/node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@semantic-release/npm/node_modules/execa": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", + "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/@semantic-release/npm/node_modules/get-stream": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", + "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", + "dev": true, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@semantic-release/npm/node_modules/human-signals": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", + "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", + "dev": true, + "engines": { + "node": ">=16.17.0" + } + }, + "node_modules/@semantic-release/npm/node_modules/indent-string": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz", + "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@semantic-release/npm/node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@semantic-release/npm/node_modules/mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm-run-path": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.2.0.tgz", + "integrity": "sha512-W4/tgAXFqFA0iL7fk0+uQ3g7wkL8xJmx3XdK0VGb4cHW//eZTtKGvFBBoRKVTpY7n6ze4NL9ly7rgXcHufqXKg==", + "dev": true, + "dependencies": { + "path-key": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@semantic-release/npm/node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dev": true, + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@semantic-release/npm/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@semantic-release/npm/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@semantic-release/npm/node_modules/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@semantic-release/release-notes-generator": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/@semantic-release/release-notes-generator/-/release-notes-generator-12.1.0.tgz", + "integrity": "sha512-g6M9AjUKAZUZnxaJZnouNBeDNTCUrJ5Ltj+VJ60gJeDaRRahcHsry9HW8yKrnKkKNkx5lbWiEP1FPMqVNQz8Kg==", + "dev": true, + "dependencies": { + "conventional-changelog-angular": "^7.0.0", + "conventional-changelog-writer": "^7.0.0", + "conventional-commits-filter": "^4.0.0", + "conventional-commits-parser": "^5.0.0", + "debug": "^4.0.0", + "get-stream": "^7.0.0", + "import-from-esm": "^1.0.3", + "into-stream": "^7.0.0", + "lodash-es": "^4.17.21", + "read-pkg-up": "^11.0.0" + }, + "engines": { + "node": "^18.17 || >=20.6.1" + }, + "peerDependencies": { + "semantic-release": ">=20.1.0" + } + }, + "node_modules/@semantic-release/release-notes-generator/node_modules/get-stream": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-7.0.1.tgz", + "integrity": "sha512-3M8C1EOFN6r8AMUhwUAACIoXZJEOufDU5+0gFFN5uNs6XYOralD2Pqkl7m046va6x77FwposWXbAhPPIOus7mQ==", + "dev": true, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@sindresorhus/is": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", + "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/@sindresorhus/merge-streams": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-1.0.0.tgz", + "integrity": "sha512-rUV5WyJrJLoloD4NDN1V1+LDMDWOa4OTsT4yYJwQNpTU6FWxkxHpL7eu4w+DmiH8x/EAM1otkPE1+LaspIbplw==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@types/normalize-package-data": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz", + "integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==", + "dev": true + }, + "node_modules/agent-base": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", + "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==", + "dev": true, + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dev": true, + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-escapes": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-6.2.0.tgz", + "integrity": "sha512-kzRaCqXnpzWs+3z5ABPQiVke+iq0KXkHo8xiWV4RPTi5Yli0l97BEQuhXV1s7+aSU/fu1kUuxgS4MsQ0fRuygw==", + "dev": true, + "dependencies": { + "type-fest": "^3.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/ansicolors": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/ansicolors/-/ansicolors-0.3.2.tgz", + "integrity": "sha512-QXu7BPrP29VllRxH8GwB7x5iX5qWKAAMLqKQGWTeLWVlNHNOpVMJ91dsxQAIWXpjuW5wqvxu3Jd/nRjrJ+0pqg==", + "dev": true + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/argv-formatter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/argv-formatter/-/argv-formatter-1.0.0.tgz", + "integrity": "sha512-F2+Hkm9xFaRg+GkaNnbwXNDV5O6pnCFEmqyhvfC/Ic5LbgOWjJh3L+mN/s91rxVL3znE7DYVpW0GJFT+4YBgWw==", + "dev": true + }, + "node_modules/array-ify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-ify/-/array-ify-1.0.0.tgz", + "integrity": "sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==", + "dev": true + }, + "node_modules/before-after-hook": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.3.tgz", + "integrity": "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==", + "dev": true + }, + "node_modules/bottleneck": { + "version": "2.19.5", + "resolved": "https://registry.npmjs.org/bottleneck/-/bottleneck-2.19.5.tgz", + "integrity": "sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==", + "dev": true + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/cardinal": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/cardinal/-/cardinal-2.1.1.tgz", + "integrity": "sha512-JSr5eOgoEymtYHBjNWyjrMqet9Am2miJhlfKNdqLp6zoeAh0KN5dRAcxlecj5mAJrmQomgiOBj35xHLrFjqBpw==", + "dev": true, + "dependencies": { + "ansicolors": "~0.3.2", + "redeyed": "~2.1.0" + }, + "bin": { + "cdl": "bin/cdl.js" + } + }, + "node_modules/chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "dev": true, + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/cli-table3": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.3.tgz", + "integrity": "sha512-w5Jac5SykAeZJKntOxJCrm63Eg5/4dhMWIcuTbo9rpE+brgaSZo0RuNJZeOyMgsUdhDeojvgyQLmjI+K50ZGyg==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0" + }, + "engines": { + "node": "10.* || >= 12.*" + }, + "optionalDependencies": { + "@colors/colors": "1.5.0" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/compare-func": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz", + "integrity": "sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==", + "dev": true, + "dependencies": { + "array-ify": "^1.0.0", + "dot-prop": "^5.1.0" + } + }, + "node_modules/config-chain": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", + "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", + "dev": true, + "dependencies": { + "ini": "^1.3.4", + "proto-list": "~1.2.1" + } + }, + "node_modules/conventional-changelog-angular": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-7.0.0.tgz", + "integrity": "sha512-ROjNchA9LgfNMTTFSIWPzebCwOGFdgkEq45EnvvrmSLvCtAw0HSmrCs7/ty+wAeYUZyNay0YMUNYFTRL72PkBQ==", + "dev": true, + "dependencies": { + "compare-func": "^2.0.0" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/conventional-changelog-writer": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-7.0.1.tgz", + "integrity": "sha512-Uo+R9neH3r/foIvQ0MKcsXkX642hdm9odUp7TqgFS7BsalTcjzRlIfWZrZR1gbxOozKucaKt5KAbjW8J8xRSmA==", + "dev": true, + "dependencies": { + "conventional-commits-filter": "^4.0.0", + "handlebars": "^4.7.7", + "json-stringify-safe": "^5.0.1", + "meow": "^12.0.1", + "semver": "^7.5.2", + "split2": "^4.0.0" + }, + "bin": { + "conventional-changelog-writer": "cli.mjs" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/conventional-commits-filter": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/conventional-commits-filter/-/conventional-commits-filter-4.0.0.tgz", + "integrity": "sha512-rnpnibcSOdFcdclpFwWa+pPlZJhXE7l+XK04zxhbWrhgpR96h33QLz8hITTXbcYICxVr3HZFtbtUAQ+4LdBo9A==", + "dev": true, + "engines": { + "node": ">=16" + } + }, + "node_modules/conventional-commits-parser": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-5.0.0.tgz", + "integrity": "sha512-ZPMl0ZJbw74iS9LuX9YIAiW8pfM5p3yh2o/NbXHbkFuZzY5jvdi5jFycEOkmBW5H5I7nA+D6f3UcsCLP2vvSEA==", + "dev": true, + "dependencies": { + "is-text-path": "^2.0.0", + "JSONStream": "^1.3.5", + "meow": "^12.0.1", + "split2": "^4.0.0" + }, + "bin": { + "conventional-commits-parser": "cli.mjs" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true + }, + "node_modules/cosmiconfig": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", + "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", + "dev": true, + "dependencies": { + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/crypto-random-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-4.0.0.tgz", + "integrity": "sha512-x8dy3RnvYdlUcPOjkEHqozhiwzKNSq7GcPuXFbnyMOCHxX8V3OgIg/pYuabl2sbUPfIJaeAQB7PMOK8DFIdoRA==", + "dev": true, + "dependencies": { + "type-fest": "^1.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/crypto-random-string/node_modules/type-fest": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", + "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/deprecation": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz", + "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==", + "dev": true + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/dot-prop": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", + "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", + "dev": true, + "dependencies": { + "is-obj": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/duplexer2": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", + "integrity": "sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==", + "dev": true, + "dependencies": { + "readable-stream": "^2.0.2" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/emojilib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/emojilib/-/emojilib-2.4.0.tgz", + "integrity": "sha512-5U0rVMU5Y2n2+ykNLQqMoqklN9ICBT/KsvC1Gz6vqHbz2AXXGkG+Pm5rMWk/8Vjrr/mY9985Hi8DYzn1F09Nyw==", + "dev": true + }, + "node_modules/env-ci": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/env-ci/-/env-ci-11.0.0.tgz", + "integrity": "sha512-apikxMgkipkgTvMdRT9MNqWx5VLOci79F4VBd7Op/7OPjjoanjdAvn6fglMCCEf/1bAh8eOiuEVCUs4V3qP3nQ==", + "dev": true, + "dependencies": { + "execa": "^8.0.0", + "java-properties": "^1.0.2" + }, + "engines": { + "node": "^18.17 || >=20.6.1" + } + }, + "node_modules/env-ci/node_modules/execa": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", + "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/env-ci/node_modules/get-stream": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", + "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", + "dev": true, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/env-ci/node_modules/human-signals": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", + "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", + "dev": true, + "engines": { + "node": ">=16.17.0" + } + }, + "node_modules/env-ci/node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/env-ci/node_modules/mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/env-ci/node_modules/npm-run-path": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.2.0.tgz", + "integrity": "sha512-W4/tgAXFqFA0iL7fk0+uQ3g7wkL8xJmx3XdK0VGb4cHW//eZTtKGvFBBoRKVTpY7n6ze4NL9ly7rgXcHufqXKg==", + "dev": true, + "dependencies": { + "path-key": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/env-ci/node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dev": true, + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/env-ci/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/env-ci/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/env-ci/node_modules/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/fast-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fastq": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.16.0.tgz", + "integrity": "sha512-ifCoaXsDrsdkWTtiNJX5uzHDsrck5TzfKKDcuFFTIrrc/BS076qgEIfoIy1VeZqViznfKiysPYTh/QeHtnIsYA==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/figures": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/figures/-/figures-6.0.1.tgz", + "integrity": "sha512-0oY/olScYD4IhQ8u//gCPA4F3mlTn2dacYmiDm/mbDQvpmLjV4uH+zhsQ5IyXRyvqkvtUkXkNdGvg5OFJTCsuQ==", + "dev": true, + "dependencies": { + "is-unicode-supported": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==", + "dev": true, + "dependencies": { + "locate-path": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/find-up-simple": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/find-up-simple/-/find-up-simple-1.0.0.tgz", + "integrity": "sha512-q7Us7kcjj2VMePAa02hDAF6d+MzsdsAWEwYyOpwUtlerRBkOEPBCRZrAV4XfcSN8fHAgaD0hP7miwoay6DCprw==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/find-versions": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/find-versions/-/find-versions-5.1.0.tgz", + "integrity": "sha512-+iwzCJ7C5v5KgcBuueqVoNiHVoQpwiUK5XFLjf0affFTep+Wcw93tPvmb8tqujDNmzhBDPddnWV/qgWSXgq+Hg==", + "dev": true, + "dependencies": { + "semver-regex": "^4.0.5" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/from2": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", + "integrity": "sha512-OMcX/4IC/uqEPVgGeyfN22LJk6AZrMkRZHxcHBMBvHScDGgwTm2GT2Wkgtocyd3JfZffjj2kYUDXXII0Fk9W0g==", + "dev": true, + "dependencies": { + "inherits": "^2.0.1", + "readable-stream": "^2.0.0" + } + }, + "node_modules/fs-extra": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", + "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/git-log-parser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/git-log-parser/-/git-log-parser-1.2.0.tgz", + "integrity": "sha512-rnCVNfkTL8tdNryFuaY0fYiBWEBcgF748O6ZI61rslBvr2o7U65c2/6npCRqH40vuAhtgtDiqLTJjBVdrejCzA==", + "dev": true, + "dependencies": { + "argv-formatter": "~1.0.0", + "spawn-error-forwarder": "~1.0.0", + "split2": "~1.0.0", + "stream-combiner2": "~1.1.1", + "through2": "~2.0.0", + "traverse": "~0.6.6" + } + }, + "node_modules/git-log-parser/node_modules/split2": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-1.0.0.tgz", + "integrity": "sha512-NKywug4u4pX/AZBB1FCPzZ6/7O+Xhz1qMVbzTvvKvikjO99oPN87SkK08mEY9P63/5lWjK+wgOOgApnTg5r6qg==", + "dev": true, + "dependencies": { + "through2": "~2.0.0" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/globby": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-14.0.0.tgz", + "integrity": "sha512-/1WM/LNHRAOH9lZta77uGbq0dAEQM+XjNesWwhlERDVenqothRbnzTrL3/LrIoEPPjeUHC3vrS6TwoyxeHs7MQ==", + "dev": true, + "dependencies": { + "@sindresorhus/merge-streams": "^1.0.0", + "fast-glob": "^3.3.2", + "ignore": "^5.2.4", + "path-type": "^5.0.0", + "slash": "^5.1.0", + "unicorn-magic": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby/node_modules/path-type": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-5.0.0.tgz", + "integrity": "sha512-5HviZNaZcfqP95rwpv+1HDgUamezbqdSYTyzjTvwtJSnIH+3vnbmWsItli8OFEndS984VT55M3jduxZbX351gg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true + }, + "node_modules/gradle-semantic-release-plugin": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/gradle-semantic-release-plugin/-/gradle-semantic-release-plugin-1.9.1.tgz", + "integrity": "sha512-lCrw22itszP/FLSL3N61E40vH1+CU95/4LG9ZF+Fxr8tcx7EPthh2eqVPAq67udFlM8ZgO2LETnn8LSDRq1J2w==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/KengoTODA" + } + ], + "dependencies": { + "promisified-properties": "^3.0.0", + "split2": "^4.1.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "semantic-release": "^23.0.0" + } + }, + "node_modules/handlebars": { + "version": "4.7.8", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", + "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", + "dev": true, + "dependencies": { + "minimist": "^1.2.5", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/hasown": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", + "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hook-std": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hook-std/-/hook-std-3.0.0.tgz", + "integrity": "sha512-jHRQzjSDzMtFy34AGj1DN+vq54WVuhSvKgrHf0OMiFQTwDD4L/qqofVEWjLOBMTn5+lCD3fPg32W9yOfnEJTTw==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/hosted-git-info": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.1.tgz", + "integrity": "sha512-+K84LB1DYwMHoHSgaOY/Jfhw3ucPmSET5v98Ke/HdNSw4a0UktWzyW1mjhjpuxxTqOOsfWT/7iVshHmVZ4IpOA==", + "dev": true, + "dependencies": { + "lru-cache": "^10.0.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/http-proxy-agent": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.0.tgz", + "integrity": "sha512-+ZT+iBxVUQ1asugqnD6oWoRiS25AkjNfG085dKJGtGxkdwLQrMKU5wJr2bOOFAXzKcTuqq+7fZlTMgG3SRfIYQ==", + "dev": true, + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.2.tgz", + "integrity": "sha512-NmLNjm6ucYwtcUmL7JQC1ZQ57LmHP4lT15FQ8D61nak1rO6DH+fz5qNK2Ap5UN4ZapYICE3/0KodcLYSPsPbaA==", + "dev": true, + "dependencies": { + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/ignore": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz", + "integrity": "sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-fresh/node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/import-from-esm": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/import-from-esm/-/import-from-esm-1.3.3.tgz", + "integrity": "sha512-U3Qt/CyfFpTUv6LOP2jRTLYjphH6zg3okMfHbyqRa/W2w6hr8OsJWVggNlR4jxuojQy81TgTJTxgSkyoteRGMQ==", + "dev": true, + "dependencies": { + "debug": "^4.3.4", + "import-meta-resolve": "^4.0.0" + }, + "engines": { + "node": ">=16.20" + } + }, + "node_modules/import-meta-resolve": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.0.0.tgz", + "integrity": "sha512-okYUR7ZQPH+efeuMJGlq4f8ubUgO50kByRPyt/Cy1Io4PSRsPjxME+YlVaCOx+NIToW7hCsZNFJyTPFFKepRSA==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/index-to-position": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/index-to-position/-/index-to-position-0.1.2.tgz", + "integrity": "sha512-MWDKS3AS1bGCHLBA2VLImJz42f7bJh8wQsTGCzI3j519/CASStoDONUBVz2I/VID0MpiX3SGSnbOD2xUalbE5g==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true + }, + "node_modules/into-stream": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/into-stream/-/into-stream-7.0.0.tgz", + "integrity": "sha512-2dYz766i9HprMBasCMvHMuazJ7u4WzhJwo5kb3iPSiW/iRYV6uPari3zHoqZlnuaR7V1bEiNMxikhp37rdBXbw==", + "dev": true, + "dependencies": { + "from2": "^2.3.0", + "p-is-promise": "^3.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true + }, + "node_modules/is-core-module": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", + "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", + "dev": true, + "dependencies": { + "hasown": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-text-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-text-path/-/is-text-path-2.0.0.tgz", + "integrity": "sha512-+oDTluR6WEjdXEJMnC2z6A4FRwFoYuvShVVEGsS7ewc0UTi2QtAKMDJuL4BDEVt+5T7MjFo12RP8ghOM75oKJw==", + "dev": true, + "dependencies": { + "text-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-unicode-supported": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.0.0.tgz", + "integrity": "sha512-FRdAyx5lusK1iHG0TWpVtk9+1i+GjrzRffhDg4ovQ7mcidMQ6mj+MhKPmvh7Xwyv5gIS06ns49CA7Sqg7lC22Q==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/issue-parser": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/issue-parser/-/issue-parser-6.0.0.tgz", + "integrity": "sha512-zKa/Dxq2lGsBIXQ7CUZWTHfvxPC2ej0KfO7fIPqLlHB9J2hJ7rGhZ5rilhuufylr4RXYPzJUeFjKxz305OsNlA==", + "dev": true, + "dependencies": { + "lodash.capitalize": "^4.2.1", + "lodash.escaperegexp": "^4.1.2", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.uniqby": "^4.7.0" + }, + "engines": { + "node": ">=10.13" + } + }, + "node_modules/java-properties": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/java-properties/-/java-properties-1.0.2.tgz", + "integrity": "sha512-qjdpeo2yKlYTH7nFdK0vbZWuTCesk4o63v5iVOlhMQPfuIZQfW/HI35SjfhA+4qpg36rnFSvUK5b1m+ckIblQQ==", + "dev": true, + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "dev": true + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "dev": true + }, + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==", + "dev": true, + "engines": [ + "node >= 0.2.0" + ] + }, + "node_modules/JSONStream": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", + "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", + "dev": true, + "dependencies": { + "jsonparse": "^1.2.0", + "through": ">=2.2.7 <3" + }, + "bin": { + "JSONStream": "bin.js" + }, + "engines": { + "node": "*" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, + "node_modules/load-json-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/load-json-file/node_modules/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", + "dev": true, + "dependencies": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==", + "dev": true, + "dependencies": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "node_modules/lodash-es": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", + "dev": true + }, + "node_modules/lodash.capitalize": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/lodash.capitalize/-/lodash.capitalize-4.2.1.tgz", + "integrity": "sha512-kZzYOKspf8XVX5AvmQF94gQW0lejFVgb80G85bU4ZWzoJ6C03PQg3coYAUpSTpQWelrZELd3XWgHzw4Ck5kaIw==", + "dev": true + }, + "node_modules/lodash.escaperegexp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz", + "integrity": "sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==", + "dev": true + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "dev": true + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "dev": true + }, + "node_modules/lodash.uniqby": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash.uniqby/-/lodash.uniqby-4.7.0.tgz", + "integrity": "sha512-e/zcLx6CSbmaEgFHCA7BnoQKyCtKMxnuWrJygbwPs/AIn+IMKl66L8/s+wBUn5LRw2pZx3bUHibiV1b6aTWIww==", + "dev": true + }, + "node_modules/lru-cache": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz", + "integrity": "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==", + "dev": true, + "engines": { + "node": "14 || >=16.14" + } + }, + "node_modules/marked": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/marked/-/marked-11.1.1.tgz", + "integrity": "sha512-EgxRjgK9axsQuUa/oKMx5DEY8oXpKJfk61rT5iY3aRlgU6QJtUcxU5OAymdhCvWvhYcd9FKmO5eQoX8m9VGJXg==", + "dev": true, + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/marked-terminal": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/marked-terminal/-/marked-terminal-6.2.0.tgz", + "integrity": "sha512-ubWhwcBFHnXsjYNsu+Wndpg0zhY4CahSpPlA70PlO0rR9r2sZpkyU+rkCsOWH+KMEkx847UpALON+HWgxowFtw==", + "dev": true, + "dependencies": { + "ansi-escapes": "^6.2.0", + "cardinal": "^2.1.1", + "chalk": "^5.3.0", + "cli-table3": "^0.6.3", + "node-emoji": "^2.1.3", + "supports-hyperlinks": "^3.0.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "marked": ">=1 <12" + } + }, + "node_modules/meow": { + "version": "12.1.1", + "resolved": "https://registry.npmjs.org/meow/-/meow-12.1.1.tgz", + "integrity": "sha512-BhXM0Au22RwUneMPwSCnyhTOizdWoIEPU9sp0Aqa1PnDMR5Wv2FGXYDjuzJEIX+Eo2Rb8xuYe5jrnm5QowQFkw==", + "dev": true, + "engines": { + "node": ">=16.10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/mime/-/mime-4.0.1.tgz", + "integrity": "sha512-5lZ5tyrIfliMXzFtkYyekWbtRXObT9OWa8IwQ5uxTBDHucNNwniRqo0yInflj+iYi5CBa6qxadGzGarDfuEOxA==", + "dev": true, + "funding": [ + "https://github.com/sponsors/broofa" + ], + "bin": { + "mime": "bin/cli.js" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true + }, + "node_modules/nerf-dart": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/nerf-dart/-/nerf-dart-1.0.0.tgz", + "integrity": "sha512-EZSPZB70jiVsivaBLYDCyntd5eH8NTSMOn3rB+HxwdmKThGELLdYv8qVIMWvZEFy9w8ZZpW9h9OB32l1rGtj7g==", + "dev": true + }, + "node_modules/node-emoji": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-2.1.3.tgz", + "integrity": "sha512-E2WEOVsgs7O16zsURJ/eH8BqhF029wGpEOnv7Urwdo2wmQanOACwJQh0devF9D9RhoZru0+9JXIS0dBXIAz+lA==", + "dev": true, + "dependencies": { + "@sindresorhus/is": "^4.6.0", + "char-regex": "^1.0.2", + "emojilib": "^2.4.0", + "skin-tone": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/normalize-package-data": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-6.0.0.tgz", + "integrity": "sha512-UL7ELRVxYBHBgYEtZCXjxuD5vPxnmvMGq0jp/dGPKKrN7tfsBh2IY7TlJ15WWwdjRWD3RJbnsygUurTK3xkPkg==", + "dev": true, + "dependencies": { + "hosted-git-info": "^7.0.0", + "is-core-module": "^2.8.1", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/normalize-url": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.0.0.tgz", + "integrity": "sha512-uVFpKhj5MheNBJRTiMZ9pE/7hD1QTeEvugSJW/OmLzAp78PB5O6adfMNTvmfKhXBkvCzC+rqifWcVYpGFwTjnw==", + "dev": true, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/npm/-/npm-10.4.0.tgz", + "integrity": "sha512-RS7Mx0OVfXlOcQLRePuDIYdFCVBPCNapWHplDK+mh7GDdP/Tvor4ocuybRRPSvfcRb2vjRJt1fHCqw3cr8qACQ==", + "bundleDependencies": [ + "@isaacs/string-locale-compare", + "@npmcli/arborist", + "@npmcli/config", + "@npmcli/fs", + "@npmcli/map-workspaces", + "@npmcli/package-json", + "@npmcli/promise-spawn", + "@npmcli/run-script", + "@sigstore/tuf", + "abbrev", + "archy", + "cacache", + "chalk", + "ci-info", + "cli-columns", + "cli-table3", + "columnify", + "fastest-levenshtein", + "fs-minipass", + "glob", + "graceful-fs", + "hosted-git-info", + "ini", + "init-package-json", + "is-cidr", + "json-parse-even-better-errors", + "libnpmaccess", + "libnpmdiff", + "libnpmexec", + "libnpmfund", + "libnpmhook", + "libnpmorg", + "libnpmpack", + "libnpmpublish", + "libnpmsearch", + "libnpmteam", + "libnpmversion", + "make-fetch-happen", + "minimatch", + "minipass", + "minipass-pipeline", + "ms", + "node-gyp", + "nopt", + "normalize-package-data", + "npm-audit-report", + "npm-install-checks", + "npm-package-arg", + "npm-pick-manifest", + "npm-profile", + "npm-registry-fetch", + "npm-user-validate", + "npmlog", + "p-map", + "pacote", + "parse-conflict-json", + "proc-log", + "qrcode-terminal", + "read", + "semver", + "spdx-expression-parse", + "ssri", + "supports-color", + "tar", + "text-table", + "tiny-relative-date", + "treeverse", + "validate-npm-package-name", + "which", + "write-file-atomic" + ], + "dev": true, + "dependencies": { + "@isaacs/string-locale-compare": "^1.1.0", + "@npmcli/arborist": "^7.2.1", + "@npmcli/config": "^8.0.2", + "@npmcli/fs": "^3.1.0", + "@npmcli/map-workspaces": "^3.0.4", + "@npmcli/package-json": "^5.0.0", + "@npmcli/promise-spawn": "^7.0.1", + "@npmcli/run-script": "^7.0.4", + "@sigstore/tuf": "^2.3.0", + "abbrev": "^2.0.0", + "archy": "~1.0.0", + "cacache": "^18.0.2", + "chalk": "^5.3.0", + "ci-info": "^4.0.0", + "cli-columns": "^4.0.0", + "cli-table3": "^0.6.3", + "columnify": "^1.6.0", + "fastest-levenshtein": "^1.0.16", + "fs-minipass": "^3.0.3", + "glob": "^10.3.10", + "graceful-fs": "^4.2.11", + "hosted-git-info": "^7.0.1", + "ini": "^4.1.1", + "init-package-json": "^6.0.0", + "is-cidr": "^5.0.3", + "json-parse-even-better-errors": "^3.0.1", + "libnpmaccess": "^8.0.1", + "libnpmdiff": "^6.0.3", + "libnpmexec": "^7.0.4", + "libnpmfund": "^5.0.1", + "libnpmhook": "^10.0.0", + "libnpmorg": "^6.0.1", + "libnpmpack": "^6.0.3", + "libnpmpublish": "^9.0.2", + "libnpmsearch": "^7.0.0", + "libnpmteam": "^6.0.0", + "libnpmversion": "^5.0.1", + "make-fetch-happen": "^13.0.0", + "minimatch": "^9.0.3", + "minipass": "^7.0.4", + "minipass-pipeline": "^1.2.4", + "ms": "^2.1.2", + "node-gyp": "^10.0.1", + "nopt": "^7.2.0", + "normalize-package-data": "^6.0.0", + "npm-audit-report": "^5.0.0", + "npm-install-checks": "^6.3.0", + "npm-package-arg": "^11.0.1", + "npm-pick-manifest": "^9.0.0", + "npm-profile": "^9.0.0", + "npm-registry-fetch": "^16.1.0", + "npm-user-validate": "^2.0.0", + "npmlog": "^7.0.1", + "p-map": "^4.0.0", + "pacote": "^17.0.6", + "parse-conflict-json": "^3.0.1", + "proc-log": "^3.0.0", + "qrcode-terminal": "^0.12.0", + "read": "^2.1.0", + "semver": "^7.5.4", + "spdx-expression-parse": "^3.0.1", + "ssri": "^10.0.5", + "supports-color": "^9.4.0", + "tar": "^6.2.0", + "text-table": "~0.2.0", + "tiny-relative-date": "^1.3.0", + "treeverse": "^3.0.0", + "validate-npm-package-name": "^5.0.0", + "which": "^4.0.0", + "write-file-atomic": "^5.0.1" + }, + "bin": { + "npm": "bin/npm-cli.js", + "npx": "bin/npx-cli.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/@colors/colors": { + "version": "1.5.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/npm/node_modules/@isaacs/cliui": { + "version": "8.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/npm/node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/npm/node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm/node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/npm/node_modules/@isaacs/string-locale-compare": { + "version": "1.1.0", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/@npmcli/agent": { + "version": "2.2.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "agent-base": "^7.1.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.1", + "lru-cache": "^10.0.1", + "socks-proxy-agent": "^8.0.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/arborist": { + "version": "7.3.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@isaacs/string-locale-compare": "^1.1.0", + "@npmcli/fs": "^3.1.0", + "@npmcli/installed-package-contents": "^2.0.2", + "@npmcli/map-workspaces": "^3.0.2", + "@npmcli/metavuln-calculator": "^7.0.0", + "@npmcli/name-from-folder": "^2.0.0", + "@npmcli/node-gyp": "^3.0.0", + "@npmcli/package-json": "^5.0.0", + "@npmcli/query": "^3.0.1", + "@npmcli/run-script": "^7.0.2", + "bin-links": "^4.0.1", + "cacache": "^18.0.0", + "common-ancestor-path": "^1.0.1", + "hosted-git-info": "^7.0.1", + "json-parse-even-better-errors": "^3.0.0", + "json-stringify-nice": "^1.1.4", + "minimatch": "^9.0.0", + "nopt": "^7.0.0", + "npm-install-checks": "^6.2.0", + "npm-package-arg": "^11.0.1", + "npm-pick-manifest": "^9.0.0", + "npm-registry-fetch": "^16.0.0", + "npmlog": "^7.0.1", + "pacote": "^17.0.4", + "parse-conflict-json": "^3.0.0", + "proc-log": "^3.0.0", + "promise-all-reject-late": "^1.0.0", + "promise-call-limit": "^3.0.1", + "read-package-json-fast": "^3.0.2", + "semver": "^7.3.7", + "ssri": "^10.0.5", + "treeverse": "^3.0.0", + "walk-up-path": "^3.0.1" + }, + "bin": { + "arborist": "bin/index.js" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/config": { + "version": "8.1.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/map-workspaces": "^3.0.2", + "ci-info": "^4.0.0", + "ini": "^4.1.0", + "nopt": "^7.0.0", + "proc-log": "^3.0.0", + "read-package-json-fast": "^3.0.2", + "semver": "^7.3.5", + "walk-up-path": "^3.0.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/disparity-colors": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "ansi-styles": "^4.3.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/disparity-colors/node_modules/ansi-styles": { + "version": "4.3.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/npm/node_modules/@npmcli/fs": { + "version": "3.1.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/git": { + "version": "5.0.4", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/promise-spawn": "^7.0.0", + "lru-cache": "^10.0.1", + "npm-pick-manifest": "^9.0.0", + "proc-log": "^3.0.0", + "promise-inflight": "^1.0.1", + "promise-retry": "^2.0.1", + "semver": "^7.3.5", + "which": "^4.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/installed-package-contents": { + "version": "2.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-bundled": "^3.0.0", + "npm-normalize-package-bin": "^3.0.0" + }, + "bin": { + "installed-package-contents": "lib/index.js" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/map-workspaces": { + "version": "3.0.4", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/name-from-folder": "^2.0.0", + "glob": "^10.2.2", + "minimatch": "^9.0.0", + "read-package-json-fast": "^3.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/metavuln-calculator": { + "version": "7.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "cacache": "^18.0.0", + "json-parse-even-better-errors": "^3.0.0", + "pacote": "^17.0.0", + "semver": "^7.3.5" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/name-from-folder": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/node-gyp": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/package-json": { + "version": "5.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/git": "^5.0.0", + "glob": "^10.2.2", + "hosted-git-info": "^7.0.0", + "json-parse-even-better-errors": "^3.0.0", + "normalize-package-data": "^6.0.0", + "proc-log": "^3.0.0", + "semver": "^7.5.3" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/promise-spawn": { + "version": "7.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "which": "^4.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/query": { + "version": "3.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "postcss-selector-parser": "^6.0.10" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/run-script": { + "version": "7.0.4", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/node-gyp": "^3.0.0", + "@npmcli/package-json": "^5.0.0", + "@npmcli/promise-spawn": "^7.0.0", + "node-gyp": "^10.0.0", + "which": "^4.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/npm/node_modules/@sigstore/bundle": { + "version": "2.1.1", + "dev": true, + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/protobuf-specs": "^0.2.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@sigstore/core": { + "version": "0.2.0", + "dev": true, + "inBundle": true, + "license": "Apache-2.0", + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@sigstore/protobuf-specs": { + "version": "0.2.1", + "dev": true, + "inBundle": true, + "license": "Apache-2.0", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@sigstore/sign": { + "version": "2.2.1", + "dev": true, + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/bundle": "^2.1.1", + "@sigstore/core": "^0.2.0", + "@sigstore/protobuf-specs": "^0.2.1", + "make-fetch-happen": "^13.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@sigstore/tuf": { + "version": "2.3.0", + "dev": true, + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/protobuf-specs": "^0.2.1", + "tuf-js": "^2.2.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@sigstore/verify": { + "version": "0.1.0", + "dev": true, + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/bundle": "^2.1.1", + "@sigstore/core": "^0.2.0", + "@sigstore/protobuf-specs": "^0.2.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@tufjs/canonical-json": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@tufjs/models": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "@tufjs/canonical-json": "2.0.0", + "minimatch": "^9.0.3" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/abbrev": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/agent-base": { + "version": "7.1.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/npm/node_modules/aggregate-error": { + "version": "3.1.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/ansi-regex": { + "version": "5.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/ansi-styles": { + "version": "6.2.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/npm/node_modules/aproba": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/archy": { + "version": "1.0.0", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/are-we-there-yet": { + "version": "4.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/balanced-match": { + "version": "1.0.2", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/bin-links": { + "version": "4.0.3", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "cmd-shim": "^6.0.0", + "npm-normalize-package-bin": "^3.0.0", + "read-cmd-shim": "^4.0.0", + "write-file-atomic": "^5.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/binary-extensions": { + "version": "2.2.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/brace-expansion": { + "version": "2.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/npm/node_modules/builtins": { + "version": "5.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "semver": "^7.0.0" + } + }, + "node_modules/npm/node_modules/cacache": { + "version": "18.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/fs": "^3.1.0", + "fs-minipass": "^3.0.0", + "glob": "^10.2.2", + "lru-cache": "^10.0.1", + "minipass": "^7.0.3", + "minipass-collect": "^2.0.1", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "p-map": "^4.0.0", + "ssri": "^10.0.0", + "tar": "^6.1.11", + "unique-filename": "^3.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/chalk": { + "version": "5.3.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/npm/node_modules/chownr": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/ci-info": { + "version": "4.0.0", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/cidr-regex": { + "version": "4.0.3", + "dev": true, + "inBundle": true, + "license": "BSD-2-Clause", + "dependencies": { + "ip-regex": "^5.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/npm/node_modules/clean-stack": { + "version": "2.2.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/npm/node_modules/cli-columns": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/npm/node_modules/cli-table3": { + "version": "0.6.3", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "string-width": "^4.2.0" + }, + "engines": { + "node": "10.* || >= 12.*" + }, + "optionalDependencies": { + "@colors/colors": "1.5.0" + } + }, + "node_modules/npm/node_modules/clone": { + "version": "1.0.4", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/npm/node_modules/cmd-shim": { + "version": "6.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/color-convert": { + "version": "2.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/npm/node_modules/color-name": { + "version": "1.1.4", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/color-support": { + "version": "1.1.3", + "dev": true, + "inBundle": true, + "license": "ISC", + "bin": { + "color-support": "bin.js" + } + }, + "node_modules/npm/node_modules/columnify": { + "version": "1.6.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "strip-ansi": "^6.0.1", + "wcwidth": "^1.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/npm/node_modules/common-ancestor-path": { + "version": "1.0.1", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/console-control-strings": { + "version": "1.1.0", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/cross-spawn": { + "version": "7.0.3", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/npm/node_modules/cross-spawn/node_modules/which": { + "version": "2.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/npm/node_modules/cssesc": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm/node_modules/debug": { + "version": "4.3.4", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/npm/node_modules/debug/node_modules/ms": { + "version": "2.1.2", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/defaults": { + "version": "1.0.4", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "clone": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm/node_modules/diff": { + "version": "5.1.0", + "dev": true, + "inBundle": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/npm/node_modules/eastasianwidth": { + "version": "0.2.0", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/emoji-regex": { + "version": "8.0.0", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/encoding": { + "version": "0.1.13", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "iconv-lite": "^0.6.2" + } + }, + "node_modules/npm/node_modules/env-paths": { + "version": "2.2.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/npm/node_modules/err-code": { + "version": "2.0.3", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/exponential-backoff": { + "version": "3.1.1", + "dev": true, + "inBundle": true, + "license": "Apache-2.0" + }, + "node_modules/npm/node_modules/fastest-levenshtein": { + "version": "1.0.16", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 4.9.1" + } + }, + "node_modules/npm/node_modules/foreground-child": { + "version": "3.1.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/fs-minipass": { + "version": "3.0.3", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/function-bind": { + "version": "1.1.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/npm/node_modules/gauge": { + "version": "5.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.3", + "console-control-strings": "^1.1.0", + "has-unicode": "^2.0.1", + "signal-exit": "^4.0.1", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.5" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/glob": { + "version": "10.3.10", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^2.3.5", + "minimatch": "^9.0.1", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", + "path-scurry": "^1.10.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/graceful-fs": { + "version": "4.2.11", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/has-unicode": { + "version": "2.0.1", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/hasown": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/npm/node_modules/hosted-git-info": { + "version": "7.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^10.0.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/http-cache-semantics": { + "version": "4.1.1", + "dev": true, + "inBundle": true, + "license": "BSD-2-Clause" + }, + "node_modules/npm/node_modules/http-proxy-agent": { + "version": "7.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/npm/node_modules/https-proxy-agent": { + "version": "7.0.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/npm/node_modules/iconv-lite": { + "version": "0.6.3", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm/node_modules/ignore-walk": { + "version": "6.0.4", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "minimatch": "^9.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/imurmurhash": { + "version": "0.1.4", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/npm/node_modules/indent-string": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/ini": { + "version": "4.1.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/init-package-json": { + "version": "6.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-package-arg": "^11.0.0", + "promzard": "^1.0.0", + "read": "^2.0.0", + "read-package-json": "^7.0.0", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4", + "validate-npm-package-name": "^5.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/ip": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/ip-regex": { + "version": "5.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm/node_modules/is-cidr": { + "version": "5.0.3", + "dev": true, + "inBundle": true, + "license": "BSD-2-Clause", + "dependencies": { + "cidr-regex": "4.0.3" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/npm/node_modules/is-core-module": { + "version": "2.13.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/npm/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/is-lambda": { + "version": "1.0.1", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/isexe": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/jackspeak": { + "version": "2.3.6", + "dev": true, + "inBundle": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/npm/node_modules/json-parse-even-better-errors": { + "version": "3.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/json-stringify-nice": { + "version": "1.1.4", + "dev": true, + "inBundle": true, + "license": "ISC", + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/jsonparse": { + "version": "1.3.1", + "dev": true, + "engines": [ + "node >= 0.2.0" + ], + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/just-diff": { + "version": "6.0.2", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/just-diff-apply": { + "version": "5.5.0", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/libnpmaccess": { + "version": "8.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-package-arg": "^11.0.1", + "npm-registry-fetch": "^16.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/libnpmdiff": { + "version": "6.0.6", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/arborist": "^7.2.1", + "@npmcli/disparity-colors": "^3.0.0", + "@npmcli/installed-package-contents": "^2.0.2", + "binary-extensions": "^2.2.0", + "diff": "^5.1.0", + "minimatch": "^9.0.0", + "npm-package-arg": "^11.0.1", + "pacote": "^17.0.4", + "tar": "^6.2.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/libnpmexec": { + "version": "7.0.7", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/arborist": "^7.2.1", + "@npmcli/run-script": "^7.0.2", + "ci-info": "^4.0.0", + "npm-package-arg": "^11.0.1", + "npmlog": "^7.0.1", + "pacote": "^17.0.4", + "proc-log": "^3.0.0", + "read": "^2.0.0", + "read-package-json-fast": "^3.0.2", + "semver": "^7.3.7", + "walk-up-path": "^3.0.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/libnpmfund": { + "version": "5.0.4", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/arborist": "^7.2.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/libnpmhook": { + "version": "10.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "aproba": "^2.0.0", + "npm-registry-fetch": "^16.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/libnpmorg": { + "version": "6.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "aproba": "^2.0.0", + "npm-registry-fetch": "^16.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/libnpmpack": { + "version": "6.0.6", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/arborist": "^7.2.1", + "@npmcli/run-script": "^7.0.2", + "npm-package-arg": "^11.0.1", + "pacote": "^17.0.4" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/libnpmpublish": { + "version": "9.0.4", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "ci-info": "^4.0.0", + "normalize-package-data": "^6.0.0", + "npm-package-arg": "^11.0.1", + "npm-registry-fetch": "^16.0.0", + "proc-log": "^3.0.0", + "semver": "^7.3.7", + "sigstore": "^2.2.0", + "ssri": "^10.0.5" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/libnpmsearch": { + "version": "7.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-registry-fetch": "^16.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/libnpmteam": { + "version": "6.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "aproba": "^2.0.0", + "npm-registry-fetch": "^16.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/libnpmversion": { + "version": "5.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/git": "^5.0.3", + "@npmcli/run-script": "^7.0.2", + "json-parse-even-better-errors": "^3.0.0", + "proc-log": "^3.0.0", + "semver": "^7.3.7" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/lru-cache": { + "version": "10.1.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "14 || >=16.14" + } + }, + "node_modules/npm/node_modules/make-fetch-happen": { + "version": "13.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/agent": "^2.0.0", + "cacache": "^18.0.0", + "http-cache-semantics": "^4.1.1", + "is-lambda": "^1.0.1", + "minipass": "^7.0.2", + "minipass-fetch": "^3.0.0", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.3", + "promise-retry": "^2.0.1", + "ssri": "^10.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/minimatch": { + "version": "9.0.3", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/minipass": { + "version": "7.0.4", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/npm/node_modules/minipass-collect": { + "version": "2.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/npm/node_modules/minipass-fetch": { + "version": "3.0.4", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.0.3", + "minipass-sized": "^1.0.3", + "minizlib": "^2.1.2" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + }, + "optionalDependencies": { + "encoding": "^0.1.13" + } + }, + "node_modules/npm/node_modules/minipass-flush": { + "version": "1.0.5", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/npm/node_modules/minipass-flush/node_modules/minipass": { + "version": "3.3.6", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/minipass-json-stream": { + "version": "1.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "jsonparse": "^1.3.1", + "minipass": "^3.0.0" + } + }, + "node_modules/npm/node_modules/minipass-json-stream/node_modules/minipass": { + "version": "3.3.6", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/minipass-pipeline": { + "version": "1.2.4", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/minipass-pipeline/node_modules/minipass": { + "version": "3.3.6", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/minipass-sized": { + "version": "1.0.3", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/minipass-sized/node_modules/minipass": { + "version": "3.3.6", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/minizlib": { + "version": "2.1.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/npm/node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/mkdirp": { + "version": "1.0.4", + "dev": true, + "inBundle": true, + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/ms": { + "version": "2.1.3", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/mute-stream": { + "version": "1.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/negotiator": { + "version": "0.6.3", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/npm/node_modules/node-gyp": { + "version": "10.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.0", + "exponential-backoff": "^3.1.1", + "glob": "^10.3.10", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^13.0.0", + "nopt": "^7.0.0", + "proc-log": "^3.0.0", + "semver": "^7.3.5", + "tar": "^6.1.2", + "which": "^4.0.0" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/nopt": { + "version": "7.2.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "abbrev": "^2.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/normalize-package-data": { + "version": "6.0.0", + "dev": true, + "inBundle": true, + "license": "BSD-2-Clause", + "dependencies": { + "hosted-git-info": "^7.0.0", + "is-core-module": "^2.8.1", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/npm-audit-report": { + "version": "5.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/npm-bundled": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-normalize-package-bin": "^3.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/npm-install-checks": { + "version": "6.3.0", + "dev": true, + "inBundle": true, + "license": "BSD-2-Clause", + "dependencies": { + "semver": "^7.1.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/npm-normalize-package-bin": { + "version": "3.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/npm-package-arg": { + "version": "11.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "hosted-git-info": "^7.0.0", + "proc-log": "^3.0.0", + "semver": "^7.3.5", + "validate-npm-package-name": "^5.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/npm-packlist": { + "version": "8.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "ignore-walk": "^6.0.4" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/npm-pick-manifest": { + "version": "9.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-install-checks": "^6.0.0", + "npm-normalize-package-bin": "^3.0.0", + "npm-package-arg": "^11.0.0", + "semver": "^7.3.5" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/npm-profile": { + "version": "9.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-registry-fetch": "^16.0.0", + "proc-log": "^3.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/npm-registry-fetch": { + "version": "16.1.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "make-fetch-happen": "^13.0.0", + "minipass": "^7.0.2", + "minipass-fetch": "^3.0.0", + "minipass-json-stream": "^1.0.1", + "minizlib": "^2.1.2", + "npm-package-arg": "^11.0.0", + "proc-log": "^3.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/npm-user-validate": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "BSD-2-Clause", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/npmlog": { + "version": "7.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "are-we-there-yet": "^4.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^5.0.0", + "set-blocking": "^2.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/p-map": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm/node_modules/pacote": { + "version": "17.0.6", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/git": "^5.0.0", + "@npmcli/installed-package-contents": "^2.0.1", + "@npmcli/promise-spawn": "^7.0.0", + "@npmcli/run-script": "^7.0.0", + "cacache": "^18.0.0", + "fs-minipass": "^3.0.0", + "minipass": "^7.0.2", + "npm-package-arg": "^11.0.0", + "npm-packlist": "^8.0.0", + "npm-pick-manifest": "^9.0.0", + "npm-registry-fetch": "^16.0.0", + "proc-log": "^3.0.0", + "promise-retry": "^2.0.1", + "read-package-json": "^7.0.0", + "read-package-json-fast": "^3.0.0", + "sigstore": "^2.2.0", + "ssri": "^10.0.0", + "tar": "^6.1.11" + }, + "bin": { + "pacote": "lib/bin.js" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/parse-conflict-json": { + "version": "3.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "json-parse-even-better-errors": "^3.0.0", + "just-diff": "^6.0.0", + "just-diff-apply": "^5.2.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/path-key": { + "version": "3.1.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/path-scurry": { + "version": "1.10.1", + "dev": true, + "inBundle": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^9.1.1 || ^10.0.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/postcss-selector-parser": { + "version": "6.0.15", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm/node_modules/proc-log": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/promise-all-reject-late": { + "version": "1.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/promise-call-limit": { + "version": "3.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/promise-inflight": { + "version": "1.0.1", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/promise-retry": { + "version": "2.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/promzard": { + "version": "1.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "read": "^2.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/qrcode-terminal": { + "version": "0.12.0", + "dev": true, + "inBundle": true, + "bin": { + "qrcode-terminal": "bin/qrcode-terminal.js" + } + }, + "node_modules/npm/node_modules/read": { + "version": "2.1.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "mute-stream": "~1.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/read-cmd-shim": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/read-package-json": { + "version": "7.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "glob": "^10.2.2", + "json-parse-even-better-errors": "^3.0.0", + "normalize-package-data": "^6.0.0", + "npm-normalize-package-bin": "^3.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/read-package-json-fast": { + "version": "3.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "json-parse-even-better-errors": "^3.0.0", + "npm-normalize-package-bin": "^3.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/retry": { + "version": "0.12.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/npm/node_modules/safer-buffer": { + "version": "2.1.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true + }, + "node_modules/npm/node_modules/semver": { + "version": "7.5.4", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/semver/node_modules/lru-cache": { + "version": "6.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/set-blocking": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/shebang-command": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/shebang-regex": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/signal-exit": { + "version": "4.1.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/sigstore": { + "version": "2.2.0", + "dev": true, + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/bundle": "^2.1.1", + "@sigstore/core": "^0.2.0", + "@sigstore/protobuf-specs": "^0.2.1", + "@sigstore/sign": "^2.2.1", + "@sigstore/tuf": "^2.3.0", + "@sigstore/verify": "^0.1.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/smart-buffer": { + "version": "4.2.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/npm/node_modules/socks": { + "version": "2.7.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "ip": "^2.0.0", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.13.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/npm/node_modules/socks-proxy-agent": { + "version": "8.0.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.0.2", + "debug": "^4.3.4", + "socks": "^2.7.1" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/npm/node_modules/spdx-correct": { + "version": "3.2.0", + "dev": true, + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/npm/node_modules/spdx-exceptions": { + "version": "2.3.0", + "dev": true, + "inBundle": true, + "license": "CC-BY-3.0" + }, + "node_modules/npm/node_modules/spdx-expression-parse": { + "version": "3.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/npm/node_modules/spdx-license-ids": { + "version": "3.0.16", + "dev": true, + "inBundle": true, + "license": "CC0-1.0" + }, + "node_modules/npm/node_modules/ssri": { + "version": "10.0.5", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/string-width": { + "version": "4.2.3", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/strip-ansi": { + "version": "6.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/supports-color": { + "version": "9.4.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/npm/node_modules/tar": { + "version": "6.2.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/tar/node_modules/fs-minipass": { + "version": "2.1.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/npm/node_modules/tar/node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/tar/node_modules/minipass": { + "version": "5.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/text-table": { + "version": "0.2.0", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/tiny-relative-date": { + "version": "1.3.0", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/treeverse": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/tuf-js": { + "version": "2.2.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "@tufjs/models": "2.0.0", + "debug": "^4.3.4", + "make-fetch-happen": "^13.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/unique-filename": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "unique-slug": "^4.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/unique-slug": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/util-deprecate": { + "version": "1.0.2", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/validate-npm-package-license": { + "version": "3.0.4", + "dev": true, + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/npm/node_modules/validate-npm-package-name": { + "version": "5.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "builtins": "^5.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/walk-up-path": { + "version": "3.0.1", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/wcwidth": { + "version": "1.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "defaults": "^1.0.3" + } + }, + "node_modules/npm/node_modules/which": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/which/node_modules/isexe": { + "version": "3.1.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": ">=16" + } + }, + "node_modules/npm/node_modules/wide-align": { + "version": "1.1.5", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, + "node_modules/npm/node_modules/wrap-ansi": { + "version": "8.1.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/npm/node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/npm/node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/npm/node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "6.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/npm/node_modules/wrap-ansi/node_modules/emoji-regex": { + "version": "9.2.2", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/wrap-ansi/node_modules/string-width": { + "version": "5.1.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm/node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "7.1.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/npm/node_modules/write-file-atomic": { + "version": "5.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/yallist": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-each-series": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-3.0.0.tgz", + "integrity": "sha512-lastgtAdoH9YaLyDa5i5z64q+kzOcQHsQ5SsZJD3q0VEyI8mq872S3geuNbRUQLVAE9siMfgKrpj7MloKFHruw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-filter": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-filter/-/p-filter-4.1.0.tgz", + "integrity": "sha512-37/tPdZ3oJwHaS3gNJdenCDB3Tz26i9sjhnguBtvN0vYlRIiDNnvTWkuh+0hETV9rLPdJ3rlL3yVOYPIAnM8rw==", + "dev": true, + "dependencies": { + "p-map": "^7.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-is-promise": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-3.0.0.tgz", + "integrity": "sha512-Wo8VsW4IRQSKVXsJCn7TomUaVtyfjVDn3nUP7kE967BQk0CwFpdbZs0X0uk5sW9mkBa9eNM7hCMaG93WUAwxYQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "dependencies": { + "p-try": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==", + "dev": true, + "dependencies": { + "p-limit": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/p-map": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.1.tgz", + "integrity": "sha512-2wnaR0XL/FDOj+TgpDuRb2KTjLnu3Fma6b1ZUwGY7LcqenMcvP/YFpjpbPKY6WVGsbuJZRuoUz8iPrt8ORnAFw==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-reduce": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-reduce/-/p-reduce-2.1.0.tgz", + "integrity": "sha512-2USApvnsutq8uoxZBGbbWM0JIYLiEMJ9RlaN7fAzVNb9OZN0SHjjTTfIcb667XynS5Y1VhwDJVDa72TnPzAYWw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parsimmon": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/parsimmon/-/parsimmon-1.18.1.tgz", + "integrity": "sha512-u7p959wLfGAhJpSDJVYXoyMCXWYwHia78HhRBWqk7AIbxdmlrfdp5wX0l3xv/iTSH5HvhN9K7o26hwwpgS5Nmw==", + "dev": true + }, + "node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/pkg-conf": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/pkg-conf/-/pkg-conf-2.1.0.tgz", + "integrity": "sha512-C+VUP+8jis7EsQZIhDYmS5qlNtjv2yP4SNtjXK9AP1ZcTRlnSfuumaTnRfYZnYgUUYVIKqL0fRvmUGDV2fmp6g==", + "dev": true, + "dependencies": { + "find-up": "^2.0.0", + "load-json-file": "^4.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true + }, + "node_modules/promisified-properties": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/promisified-properties/-/promisified-properties-3.0.0.tgz", + "integrity": "sha512-ARteuBuUpPg/+spsMhcKHvdtOW/q8btyyVYYxxegGgx+7u9ix9at8DjP2KM2t8+4SuI8wBLt+3X876FMQx91yQ==", + "dev": true, + "dependencies": { + "parsimmon": "^1.13.0" + }, + "engines": { + "node": ">=18", + "npm": ">=7.12" + } + }, + "node_modules/proto-list": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", + "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==", + "dev": true + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dev": true, + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/read-pkg": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-9.0.1.tgz", + "integrity": "sha512-9viLL4/n1BJUCT1NXVTdS1jtm80yDEgR5T4yCelII49Mbj0v1rZdKqj7zCiYdbB0CuCgdrvHcNogAKTFPBocFA==", + "dev": true, + "dependencies": { + "@types/normalize-package-data": "^2.4.3", + "normalize-package-data": "^6.0.0", + "parse-json": "^8.0.0", + "type-fest": "^4.6.0", + "unicorn-magic": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg-up": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-11.0.0.tgz", + "integrity": "sha512-LOVbvF1Q0SZdjClSefZ0Nz5z8u+tIE7mV5NibzmE9VYmDe9CaBbAVtz1veOSZbofrdsilxuDAYnFenukZVp8/Q==", + "deprecated": "Renamed to read-package-up", + "dev": true, + "dependencies": { + "find-up-simple": "^1.0.0", + "read-pkg": "^9.0.0", + "type-fest": "^4.6.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg-up/node_modules/type-fest": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.10.1.tgz", + "integrity": "sha512-7ZnJYTp6uc04uYRISWtiX3DSKB/fxNQT0B5o1OUeCqiQiwF+JC9+rJiZIDrPrNCLLuTqyQmh4VdQqh/ZOkv9MQ==", + "dev": true, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg/node_modules/parse-json": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-8.1.0.tgz", + "integrity": "sha512-rum1bPifK5SSar35Z6EKZuYPJx85pkNaFrxBK3mwdfSJ1/WKbYrjoW/zTPSjRRamfmVX1ACBIdFAO0VRErW/EA==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.22.13", + "index-to-position": "^0.1.2", + "type-fest": "^4.7.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg/node_modules/type-fest": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.10.1.tgz", + "integrity": "sha512-7ZnJYTp6uc04uYRISWtiX3DSKB/fxNQT0B5o1OUeCqiQiwF+JC9+rJiZIDrPrNCLLuTqyQmh4VdQqh/ZOkv9MQ==", + "dev": true, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/redeyed": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/redeyed/-/redeyed-2.1.1.tgz", + "integrity": "sha512-FNpGGo1DycYAdnrKFxCMmKYgo/mILAqtRYbkdQD8Ep/Hk2PQ5+aEAEx+IU713RTDmuBaH0c8P5ZozurNu5ObRQ==", + "dev": true, + "dependencies": { + "esprima": "~4.0.0" + } + }, + "node_modules/registry-auth-token": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-5.0.2.tgz", + "integrity": "sha512-o/3ikDxtXaA59BmZuZrJZDJv8NMDGSj+6j6XaeBmHw8eY1i1qd9+6H+LjVvQXx3HN6aRCGa1cUdJ9RaJZUugnQ==", + "dev": true, + "dependencies": { + "@pnpm/npm-conf": "^2.1.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/semantic-release": { + "version": "23.0.0", + "resolved": "https://registry.npmjs.org/semantic-release/-/semantic-release-23.0.0.tgz", + "integrity": "sha512-Jz7jEWO2igTtske112gC4PPE2whCMVrsgxUPG3/SZI7VE357suIUZFlJd1Yu0g2I6RPc2HxNEfUg7KhmDTjwqg==", + "dev": true, + "dependencies": { + "@semantic-release/commit-analyzer": "^11.0.0", + "@semantic-release/error": "^4.0.0", + "@semantic-release/github": "^9.0.0", + "@semantic-release/npm": "^11.0.0", + "@semantic-release/release-notes-generator": "^12.0.0", + "aggregate-error": "^5.0.0", + "cosmiconfig": "^9.0.0", + "debug": "^4.0.0", + "env-ci": "^11.0.0", + "execa": "^8.0.0", + "figures": "^6.0.0", + "find-versions": "^5.1.0", + "get-stream": "^6.0.0", + "git-log-parser": "^1.2.0", + "hook-std": "^3.0.0", + "hosted-git-info": "^7.0.0", + "import-from-esm": "^1.3.1", + "lodash-es": "^4.17.21", + "marked": "^11.0.0", + "marked-terminal": "^6.0.0", + "micromatch": "^4.0.2", + "p-each-series": "^3.0.0", + "p-reduce": "^3.0.0", + "read-pkg-up": "^11.0.0", + "resolve-from": "^5.0.0", + "semver": "^7.3.2", + "semver-diff": "^4.0.0", + "signale": "^1.2.1", + "yargs": "^17.5.1" + }, + "bin": { + "semantic-release": "bin/semantic-release.js" + }, + "engines": { + "node": ">=20.8.1" + } + }, + "node_modules/semantic-release/node_modules/@semantic-release/error": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@semantic-release/error/-/error-4.0.0.tgz", + "integrity": "sha512-mgdxrHTLOjOddRVYIYDo0fR3/v61GNN1YGkfbrjuIKg/uMgCd+Qzo3UAXJ+woLQQpos4pl5Esuw5A7AoNlzjUQ==", + "dev": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/semantic-release/node_modules/aggregate-error": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-5.0.0.tgz", + "integrity": "sha512-gOsf2YwSlleG6IjRYG2A7k0HmBMEo6qVNk9Bp/EaLgAJT5ngH6PXbqa4ItvnEwCm/velL5jAnQgsHsWnjhGmvw==", + "dev": true, + "dependencies": { + "clean-stack": "^5.2.0", + "indent-string": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/semantic-release/node_modules/clean-stack": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-5.2.0.tgz", + "integrity": "sha512-TyUIUJgdFnCISzG5zu3291TAsE77ddchd0bepon1VVQrKLGKFED4iXFEDQ24mIPdPBbyE16PK3F8MYE1CmcBEQ==", + "dev": true, + "dependencies": { + "escape-string-regexp": "5.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/semantic-release/node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/semantic-release/node_modules/execa": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", + "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/semantic-release/node_modules/execa/node_modules/get-stream": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", + "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", + "dev": true, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/semantic-release/node_modules/human-signals": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", + "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", + "dev": true, + "engines": { + "node": ">=16.17.0" + } + }, + "node_modules/semantic-release/node_modules/indent-string": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz", + "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/semantic-release/node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/semantic-release/node_modules/mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/semantic-release/node_modules/npm-run-path": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.2.0.tgz", + "integrity": "sha512-W4/tgAXFqFA0iL7fk0+uQ3g7wkL8xJmx3XdK0VGb4cHW//eZTtKGvFBBoRKVTpY7n6ze4NL9ly7rgXcHufqXKg==", + "dev": true, + "dependencies": { + "path-key": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/semantic-release/node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dev": true, + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/semantic-release/node_modules/p-reduce": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-reduce/-/p-reduce-3.0.0.tgz", + "integrity": "sha512-xsrIUgI0Kn6iyDYm9StOpOeK29XM1aboGji26+QEortiFST1hGZaUQOLhtEbqHErPpGW/aSz6allwK2qcptp0Q==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/semantic-release/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/semantic-release/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/semantic-release/node_modules/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-4.0.0.tgz", + "integrity": "sha512-0Ju4+6A8iOnpL/Thra7dZsSlOHYAHIeMxfhWQRI1/VLcT3WDBZKKtQt/QkBOsiIN9ZpuvHE6cGZ0x4glCMmfiA==", + "dev": true, + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/semver-regex": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/semver-regex/-/semver-regex-4.0.5.tgz", + "integrity": "sha512-hunMQrEy1T6Jr2uEVjrAIqjwWcQTgOAcIM52C8MY1EZSD3DDNft04XzvYKPqjED65bNVVko0YI38nYeEHCX3yw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/semver/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "node_modules/signale": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/signale/-/signale-1.4.0.tgz", + "integrity": "sha512-iuh+gPf28RkltuJC7W5MRi6XAjTDCAPC/prJUpQoG4vIP3MJZ+GTydVnodXA7pwvTKb2cA0m9OFZW/cdWy/I/w==", + "dev": true, + "dependencies": { + "chalk": "^2.3.2", + "figures": "^2.0.0", + "pkg-conf": "^2.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/signale/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/signale/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/signale/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/signale/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/signale/node_modules/figures": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", + "integrity": "sha512-Oa2M9atig69ZkfwiApY8F2Yy+tzMbazyvqv21R0NsSC8floSOC09BbT1ITWAdoMGQvJ/aZnR1KMwdx9tvHnTNA==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^1.0.5" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/signale/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/signale/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/skin-tone": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/skin-tone/-/skin-tone-2.0.0.tgz", + "integrity": "sha512-kUMbT1oBJCpgrnKoSr0o6wPtvRWT9W9UKvGLwfJYO2WuahZRHOpEyL1ckyMGgMWh0UdpmaoFqKKD29WTomNEGA==", + "dev": true, + "dependencies": { + "unicode-emoji-modifier-base": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/slash": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", + "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", + "dev": true, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/spawn-error-forwarder": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/spawn-error-forwarder/-/spawn-error-forwarder-1.0.0.tgz", + "integrity": "sha512-gRjMgK5uFjbCvdibeGJuy3I5OYz6VLoVdsOJdA6wV0WlfQVLFueoqMxwwYD9RODdgb6oUIvlRlsyFSiQkMKu0g==", + "dev": true + }, + "node_modules/spdx-correct": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", + "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", + "dev": true, + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.4.0.tgz", + "integrity": "sha512-hcjppoJ68fhxA/cjbN4T8N6uCUejN8yFw69ttpqtBeCbF3u13n7mb31NB9jKwGTTWWnt9IbRA/mf1FprYS8wfw==", + "dev": true + }, + "node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.16", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.16.tgz", + "integrity": "sha512-eWN+LnM3GR6gPu35WxNgbGl8rmY1AEmoMDvL/QD6zYmPWgywxWqJWNdLGT+ke8dKNWrcYgYjPpG5gbTfghP8rw==", + "dev": true + }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "dev": true, + "engines": { + "node": ">= 10.x" + } + }, + "node_modules/stream-combiner2": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/stream-combiner2/-/stream-combiner2-1.1.1.tgz", + "integrity": "sha512-3PnJbYgS56AeWgtKF5jtJRT6uFJe56Z0Hc5Ngg/6sI6rIt8iiMBTa9cvdyFfpMQjaVHr8dusbNeFGIIonxOvKw==", + "dev": true, + "dependencies": { + "duplexer2": "~0.1.0", + "readable-stream": "^2.0.2" + } + }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-hyperlinks": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-3.0.0.tgz", + "integrity": "sha512-QBDPHyPQDRTy9ku4URNGY5Lah8PAaXs6tAAwp55sL5WCsSW7GIfdf6W5ixfziW+t7wh3GVvHyHHyQ1ESsoRvaA==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0", + "supports-color": "^7.0.0" + }, + "engines": { + "node": ">=14.18" + } + }, + "node_modules/temp-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-3.0.0.tgz", + "integrity": "sha512-nHc6S/bwIilKHNRgK/3jlhDoIHcp45YgyiwcAk46Tr0LfEqGBVpmiAyuiuxeVE44m3mXnEeVhaipLOEWmH+Njw==", + "dev": true, + "engines": { + "node": ">=14.16" + } + }, + "node_modules/tempy": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/tempy/-/tempy-3.1.0.tgz", + "integrity": "sha512-7jDLIdD2Zp0bDe5r3D2qtkd1QOCacylBuL7oa4udvN6v2pqr4+LcCr67C8DR1zkpaZ8XosF5m1yQSabKAW6f2g==", + "dev": true, + "dependencies": { + "is-stream": "^3.0.0", + "temp-dir": "^3.0.0", + "type-fest": "^2.12.2", + "unique-string": "^3.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/tempy/node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/tempy/node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "dev": true, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/text-extensions": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/text-extensions/-/text-extensions-2.4.0.tgz", + "integrity": "sha512-te/NtwBwfiNRLf9Ijqx3T0nlqZiQ2XrrtBvu+cLL8ZRrGkO0NHTug8MYFKyoSrv/sHTaSKfilUkizV6XhxMJ3g==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", + "dev": true + }, + "node_modules/through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/traverse": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.8.tgz", + "integrity": "sha512-aXJDbk6SnumuaZSANd21XAo15ucCDE38H4fkqiGsc3MhCK+wOlZvLP9cB/TvpHT0mOyWgC4Z8EwRlzqYSUzdsA==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/type-fest": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-3.13.1.tgz", + "integrity": "sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==", + "dev": true, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/uglify-js": { + "version": "3.17.4", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", + "integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==", + "dev": true, + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/unicode-emoji-modifier-base": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unicode-emoji-modifier-base/-/unicode-emoji-modifier-base-1.0.0.tgz", + "integrity": "sha512-yLSH4py7oFH3oG/9K+XWrz1pSi3dfUrWEnInbxMfArOfc1+33BlGPQtLsOYwvdMy11AwUBetYuaRxSPqgkq+8g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicorn-magic": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz", + "integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/unique-string": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-3.0.0.tgz", + "integrity": "sha512-VGXBUVwxKMBUznyffQweQABPRRW1vHZAbadFZud4pLFAqRGvv/96vafgjWFqzourzr8YonlQiPgH0YCJfawoGQ==", + "dev": true, + "dependencies": { + "crypto-random-string": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/universal-user-agent": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.1.tgz", + "integrity": "sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ==", + "dev": true + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/url-join": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/url-join/-/url-join-5.0.0.tgz", + "integrity": "sha512-n2huDr9h9yzd6exQVnH/jU5mr+Pfx08LRXXZhkLLetAMESRj+anQsTAh940iMrIetKAmry9coFuZQ2jY8/p3WA==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true + }, + "node_modules/validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", + "dev": true + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true, + "engines": { + "node": ">=0.4" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "engines": { + "node": ">=12" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..3101070 --- /dev/null +++ b/package.json @@ -0,0 +1,9 @@ +{ + "devDependencies": { + "@saithodev/semantic-release-backmerge": "^4.0.1", + "@semantic-release/changelog": "^6.0.3", + "@semantic-release/git": "^10.0.1", + "gradle-semantic-release-plugin": "^1.9.1", + "semantic-release": "^23.0.0" + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 37eb408..44d1e0d 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,7 +1,7 @@ -rootProject.name = "app.revanced.revanced-api" +rootProject.name = "revanced-api" buildCache { local { isEnabled = "CI" !in System.getenv() } -} \ No newline at end of file +} From d0461ff242689426e3de88f88014f40fc05eba7d Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Thu, 1 Feb 2024 04:16:58 +0100 Subject: [PATCH 019/102] build: Fix git permissions --- gradlew | 0 gradlew.bat | 0 2 files changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 gradlew mode change 100644 => 100755 gradlew.bat diff --git a/gradlew b/gradlew old mode 100644 new mode 100755 diff --git a/gradlew.bat b/gradlew.bat old mode 100644 new mode 100755 From 81ef4241ded71cd71130f459fb923ffa5eeee100 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Thu, 1 Feb 2024 04:23:36 +0100 Subject: [PATCH 020/102] ci: Fix build --- .github/workflows/release.yml | 2 +- build.gradle.kts | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index da4fb63..8043363 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -33,7 +33,7 @@ jobs: - name: Build env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: ./gradlew build clean + run: ./gradlew startShadowScripts clean - name: Setup Node.js uses: actions/setup-node@v4 diff --git a/build.gradle.kts b/build.gradle.kts index cb4f866..82436b3 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -22,7 +22,6 @@ tasks { RE: https://github.com/KengoTODA/gradle-semantic-release-plugin/issues/435 */ - register("publish") { group = "publishing" description = "Dummy task to hack gradle-semantic-release-plugin to release ReVanced API" From 280dbc30f607483adad0bde6ab1b016d5da047ba Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Mon, 5 Feb 2024 22:52:55 +0100 Subject: [PATCH 021/102] fix: Don't configure server --- src/main/kotlin/app/revanced/api/command/StartAPICommand.kt | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/main/kotlin/app/revanced/api/command/StartAPICommand.kt b/src/main/kotlin/app/revanced/api/command/StartAPICommand.kt index b4e3cd6..b1898ef 100644 --- a/src/main/kotlin/app/revanced/api/command/StartAPICommand.kt +++ b/src/main/kotlin/app/revanced/api/command/StartAPICommand.kt @@ -25,11 +25,7 @@ internal object StartAPICommand : Runnable { private var port: Int = 8080 override fun run() { - embeddedServer(Netty, port, host, configure = { - connectionGroupSize = 1 - workerGroupSize = 1 - callGroupSize = 1 - }) { + embeddedServer(Netty, port, host) { configureDependencies() configureHTTP() configureSerialization() From c89fdd2588299f49cc8250f103bab68df9ccb12b Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Mon, 5 Feb 2024 23:56:42 +0100 Subject: [PATCH 022/102] docs: Add README & contribution guidelines --- CONTRIBUTING.md | 97 ++++++++++++++++ README.md | 108 ++++++++++++++++++ .../revanced-headline-vertical-dark.svg | 1 + .../revanced-headline-vertical-light.svg | 1 + assets/revanced-logo/revanced-logo.svg | 1 + 5 files changed, 208 insertions(+) create mode 100644 CONTRIBUTING.md create mode 100644 README.md create mode 100644 assets/revanced-headline/revanced-headline-vertical-dark.svg create mode 100644 assets/revanced-headline/revanced-headline-vertical-light.svg create mode 100644 assets/revanced-logo/revanced-logo.svg diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..b140bcf --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,97 @@ +

+ + + + +
+ + + + + +     + + + + + +     + + + + + +     + + + + + +     + + + + + +     + + + + + +     + + + + + + +
+
+ Continuing the legacy of Vanced +

+ +# 👋 Contribution guidelines + +This document describes how to contribute to ReVanced API. + +## 📖 Resources to help you get started + +* [Our backlog](https://github.com/orgs/ReVanced/projects/12) is where we keep track of what we're working on +* [Issues](https://github.com/ReVanced/revanced-api/issues) are where we keep track of bugs and feature requests + +## 🙏 Submitting a feature request + +Features can be requested by opening an issue using the +[Feature request issue template](https://github.com/ReVanced/revanced-api/issues/new?assignees=&labels=Feature+request&projects=&template=feature-request.yml&title=feat%3A+). + +> **Note** +> Requests can be accepted or rejected at the discretion of maintainers of ReVanced API. +> Good motivation has to be provided for a request to be accepted. + +## 🐞 Submitting a bug report + +If you encounter a bug while using ReVanced API, open an issue using the +[Bug report issue template](https://github.com/ReVanced/revanced-api/issues/new?assignees=&labels=Bug+report&projects=&template=bug-report.yml&title=bug%3A+). + +## 📝 How to contribute + +1. Before contributing, it is recommended to open an issue to discuss your change +with the maintainers of ReVanced API. This will help you determine whether your change is acceptable +and whether it is worth your time to implement it +2. Development happens on the `dev` branch. Fork the repository and create your branch from `dev` +3. Commit your changes +4. Submit a pull request to the `dev` branch of the repository and reference issues +that your pull request closes in the description of your pull request +5. Our team will review your pull request and provide feedback. Once your pull request is approved, +it will be merged into the `dev` branch and will be included in the next release of ReVanced API + +❤️ Thank you for considering contributing to ReVanced API, +ReVanced diff --git a/README.md b/README.md new file mode 100644 index 0000000..5ce803d --- /dev/null +++ b/README.md @@ -0,0 +1,108 @@ +

+ + + + +
+ + + + + +     + + + + + +     + + + + + +     + + + + + +     + + + + + +     + + + + + +     + + + + + + +
+
+ Continuing the legacy of Vanced +

+ +# 🚀 ReVanced API + +![GitHub Workflow Status (with event)](https://img.shields.io/github/actions/workflow/status/ReVanced/revanced-api/release.yml) +![GPLv3 License](https://img.shields.io/badge/License-GPL%20v3-yellow.svg) + +API server for ReVanced. + +## ❓ About + +ReVanced API is a server that is used as the backend for ReVanced. +ReVanced API powers [ReVanced Manager](https://github.com/ReVanced/revanced-manager) with updates and ReVanced Patches, +and acts as a data source for [ReVanced Website](https://github.com/ReVanced/revanced-website). + +## 💪 Features + +Some of the features of ReVanced API include: + +- 📢 **Announcements**: Push announcements grouped by channels +- ℹ️ About: Get more information such as a description, ways to donate and links about the hoster of ReVanced API +- 🧩 Patches: Get the latest updates of ReVanced Patches directly from ReVanced API +- 👥 Contributors: List all contributors involved in the project + +## 🚀 How to get started + +1. Clone the repository +2. Set up the environment variables in a `.env` file using the `.env.example` file as a template +3. Configure the `configuration.toml` file using the `configuration.toml.example` file as a template +4. Run the server using `gradlew run --args=start` + +## 📚 Everything else + +### 📙 Contributing + +Thank you for considering contributing to ReVanced API. You can find the contribution guidelines [here](CONTRIBUTING.md). + +### 🛠️ Building + +In order to build ReVanced API, follow these steps: + +1. Clone the repository +2. Run `gradlew build` to build the project + +## 📜 Licence + +ReVanced API is licensed under the GPLv3 licence. Please see the [licence file](LICENSE) for more information. +[tl;dr](https://www.tldrlegal.com/license/gnu-general-public-license-v3-gpl-3) you may copy, distribute and +modify ReVanced API as long as you track changes/dates in source files. +Any modifications to ReVanced API must also be made available under the GPL along with build & install instructions. diff --git a/assets/revanced-headline/revanced-headline-vertical-dark.svg b/assets/revanced-headline/revanced-headline-vertical-dark.svg new file mode 100644 index 0000000..a59bfb5 --- /dev/null +++ b/assets/revanced-headline/revanced-headline-vertical-dark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/revanced-headline/revanced-headline-vertical-light.svg b/assets/revanced-headline/revanced-headline-vertical-light.svg new file mode 100644 index 0000000..3c5eecc --- /dev/null +++ b/assets/revanced-headline/revanced-headline-vertical-light.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/revanced-logo/revanced-logo.svg b/assets/revanced-logo/revanced-logo.svg new file mode 100644 index 0000000..901e191 --- /dev/null +++ b/assets/revanced-logo/revanced-logo.svg @@ -0,0 +1 @@ + \ No newline at end of file From 24c6f4e4354b4e6da0e4a4e7f0ee0a7a5e3c90ed Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Mon, 5 Feb 2024 23:57:21 +0100 Subject: [PATCH 023/102] feat: Do not ignore, if `.env` file is missing --- src/main/kotlin/app/revanced/api/modules/Dependencies.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/kotlin/app/revanced/api/modules/Dependencies.kt b/src/main/kotlin/app/revanced/api/modules/Dependencies.kt index 4f0b14f..3910400 100644 --- a/src/main/kotlin/app/revanced/api/modules/Dependencies.kt +++ b/src/main/kotlin/app/revanced/api/modules/Dependencies.kt @@ -27,7 +27,6 @@ fun Application.configureDependencies() { val globalModule = module { single { Dotenv.configure() - .ignoreIfMissing() .systemProperties() .load() } From 0d19eb7e0f6be9846398a8d3c98a69b47ae72352 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Tue, 6 Feb 2024 00:01:11 +0100 Subject: [PATCH 024/102] docs: Fix line-break --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b140bcf..fa880ff 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -93,5 +93,5 @@ that your pull request closes in the description of your pull request 5. Our team will review your pull request and provide feedback. Once your pull request is approved, it will be merged into the `dev` branch and will be included in the next release of ReVanced API -❤️ Thank you for considering contributing to ReVanced API, +❤️ Thank you for considering contributing to ReVanced API, ReVanced From 906fd26bdb069caa297231ba00b6d6a48c074c85 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Tue, 6 Feb 2024 00:01:23 +0100 Subject: [PATCH 025/102] docs: Improve wording --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 5ce803d..ba68ed7 100644 --- a/README.md +++ b/README.md @@ -68,8 +68,7 @@ API server for ReVanced. ## ❓ About ReVanced API is a server that is used as the backend for ReVanced. -ReVanced API powers [ReVanced Manager](https://github.com/ReVanced/revanced-manager) with updates and ReVanced Patches, -and acts as a data source for [ReVanced Website](https://github.com/ReVanced/revanced-website). +ReVanced API acts as the data source for [ReVanced Website](https://github.com/ReVanced/revanced-website) and powers [ReVanced Manager](https://github.com/ReVanced/revanced-manager) with updates and ReVanced Patches. ## 💪 Features From 6b34aaf5db06638e0b67e7cc58d546acf455c2e0 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Tue, 6 Feb 2024 00:03:40 +0100 Subject: [PATCH 026/102] docs: Fix spelling error --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ba68ed7..5973b63 100644 --- a/README.md +++ b/README.md @@ -72,10 +72,10 @@ ReVanced API acts as the data source for [ReVanced Website](https://github.com/R ## 💪 Features -Some of the features of ReVanced API include: +Some of the features ReVanced API include: - 📢 **Announcements**: Push announcements grouped by channels -- ℹ️ About: Get more information such as a description, ways to donate and links about the hoster of ReVanced API +- ℹ️ About: Get more information such as a description, ways to donate, and links about the hoster of ReVanced API - 🧩 Patches: Get the latest updates of ReVanced Patches directly from ReVanced API - 👥 Contributors: List all contributors involved in the project From e798a4c070b313bf2141d00ea1d5b70a73c34866 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Tue, 6 Feb 2024 00:21:58 +0100 Subject: [PATCH 027/102] style: Lint code --- .editorconfig | 3 + .../app/revanced/api/backend/Backend.kt | 16 +- .../api/backend/github/GitHubBackend.kt | 48 +++--- .../api/backend/github/api/RequestResource.kt | 1 + .../api/backend/github/api/ResponseSchema.kt | 7 +- .../app/revanced/api/command/MainCommand.kt | 2 +- .../app/revanced/api/modules/Database.kt | 9 +- .../app/revanced/api/modules/Dependencies.kt | 4 +- .../app/revanced/api/modules/Routing.kt | 140 ++++++++++++------ .../app/revanced/api/modules/Security.kt | 4 +- .../app/revanced/api/schema/APISchema.kt | 18 +-- .../api/schema/ConfigurationSchema.kt | 4 +- .../kotlin/app/revanced/ApplicationTest.kt | 6 +- 13 files changed, 156 insertions(+), 106 deletions(-) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..2d6d258 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,3 @@ +[*.{kt,kts}] +ktlint_code_style = intellij_idea +ktlint_standard_no-wildcard-imports = disabled \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/api/backend/Backend.kt b/src/main/kotlin/app/revanced/api/backend/Backend.kt index e231949..c3e53c1 100644 --- a/src/main/kotlin/app/revanced/api/backend/Backend.kt +++ b/src/main/kotlin/app/revanced/api/backend/Backend.kt @@ -11,7 +11,7 @@ import kotlinx.serialization.Serializable * @param httpClientConfig The configuration of the HTTP client. */ abstract class Backend( - httpClientConfig: HttpClientConfig.() -> Unit = {} + httpClientConfig: HttpClientConfig.() -> Unit = {}, ) { protected val client: HttpClient = HttpClient(OkHttp, httpClientConfig) @@ -34,7 +34,7 @@ abstract class Backend( * @property members The members of the organization. */ class BackendOrganization( - val members: Set + val members: Set, ) { /** * A member of an organization. @@ -46,12 +46,12 @@ abstract class Backend( * @property gpgKeysUrl The URL to the GPG keys of the member. */ @Serializable - class BackendMember ( + class BackendMember( override val name: String, override val avatarUrl: String, override val url: String, val bio: String?, - val gpgKeysUrl: String + val gpgKeysUrl: String, ) : BackendUser /** @@ -60,7 +60,7 @@ abstract class Backend( * @property contributors The contributors of the repository. */ class BackendRepository( - val contributors: Set + val contributors: Set, ) { /** * A contributor of a repository. @@ -75,7 +75,7 @@ abstract class Backend( override val name: String, override val avatarUrl: String, override val url: String, - val contributions: Int + val contributions: Int, ) : BackendUser /** @@ -91,7 +91,7 @@ abstract class Backend( val tag: String, val releaseNote: String, val createdAt: LocalDateTime, - val assets: Set + val assets: Set, ) { /** * An asset of a release. @@ -100,7 +100,7 @@ abstract class Backend( */ @Serializable class BackendAsset( - val downloadUrl: String + val downloadUrl: String, ) } } diff --git a/src/main/kotlin/app/revanced/api/backend/github/GitHubBackend.kt b/src/main/kotlin/app/revanced/api/backend/github/GitHubBackend.kt index 49d4c32..be0db97 100644 --- a/src/main/kotlin/app/revanced/api/backend/github/GitHubBackend.kt +++ b/src/main/kotlin/app/revanced/api/backend/github/GitHubBackend.kt @@ -1,7 +1,18 @@ package app.revanced.api.backend.github import app.revanced.api.backend.Backend +import app.revanced.api.backend.Backend.BackendOrganization.BackendMember +import app.revanced.api.backend.Backend.BackendOrganization.BackendRepository.BackendContributor +import app.revanced.api.backend.Backend.BackendOrganization.BackendRepository.BackendRelease +import app.revanced.api.backend.Backend.BackendOrganization.BackendRepository.BackendRelease.BackendAsset import app.revanced.api.backend.github.api.Request +import app.revanced.api.backend.github.api.Request.Organization.Members +import app.revanced.api.backend.github.api.Request.Organization.Repository.Contributors +import app.revanced.api.backend.github.api.Request.Organization.Repository.Releases +import app.revanced.api.backend.github.api.Response +import app.revanced.api.backend.github.api.Response.GitHubOrganization.GitHubMember +import app.revanced.api.backend.github.api.Response.GitHubOrganization.GitHubRepository.GitHubContributor +import app.revanced.api.backend.github.api.Response.GitHubOrganization.GitHubRepository.GitHubRelease import io.ktor.client.call.* import io.ktor.client.plugins.* import io.ktor.client.plugins.auth.* @@ -9,17 +20,6 @@ import io.ktor.client.plugins.auth.providers.* import io.ktor.client.plugins.cache.* import io.ktor.client.plugins.contentnegotiation.* import io.ktor.client.plugins.resources.* -import app.revanced.api.backend.Backend.BackendOrganization.BackendMember -import app.revanced.api.backend.Backend.BackendOrganization.BackendRepository.BackendRelease -import app.revanced.api.backend.Backend.BackendOrganization.BackendRepository.BackendContributor -import app.revanced.api.backend.Backend.BackendOrganization.BackendRepository.BackendRelease.BackendAsset -import app.revanced.api.backend.github.api.Request.Organization.Repository.Releases -import app.revanced.api.backend.github.api.Request.Organization.Repository.Contributors -import app.revanced.api.backend.github.api.Request.Organization.Members -import app.revanced.api.backend.github.api.Response -import app.revanced.api.backend.github.api.Response.GitHubOrganization.GitHubRepository.GitHubRelease -import app.revanced.api.backend.github.api.Response.GitHubOrganization.GitHubRepository.GitHubContributor -import app.revanced.api.backend.github.api.Response.GitHubOrganization.GitHubMember import io.ktor.client.plugins.resources.Resources import io.ktor.serialization.kotlinx.json.* import kotlinx.coroutines.* @@ -28,18 +28,18 @@ import kotlinx.datetime.toLocalDateTime import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonNamingStrategy -import org.koin.dsl.bind -import org.koin.dsl.module @OptIn(ExperimentalSerializationApi::class) class GitHubBackend(token: String? = null) : Backend({ install(HttpCache) install(Resources) install(ContentNegotiation) { - json(Json { - ignoreUnknownKeys = true - namingStrategy = JsonNamingStrategy.SnakeCase - }) + json( + Json { + ignoreUnknownKeys = true + namingStrategy = JsonNamingStrategy.SnakeCase + }, + ) } defaultRequest { url("https://api.github.com") } @@ -50,7 +50,7 @@ class GitHubBackend(token: String? = null) : Backend({ loadTokens { BearerTokens( accessToken = it, - refreshToken = "" // Required dummy value + refreshToken = "", // Required dummy value ) } @@ -64,11 +64,11 @@ class GitHubBackend(token: String? = null) : Backend({ repository: String, tag: String?, ): BackendRelease { - val release: GitHubRelease = if (tag != null) + val release: GitHubRelease = if (tag != null) { client.get(Releases.Tag(owner, repository, tag)).body() - else + } else { client.get(Releases.Latest(owner, repository)).body() - + } return BackendRelease( tag = release.tagName, @@ -76,13 +76,13 @@ class GitHubBackend(token: String? = null) : Backend({ createdAt = release.createdAt.toLocalDateTime(TimeZone.UTC), assets = release.assets.map { BackendAsset(downloadUrl = it.browserDownloadUrl) - }.toSet() + }.toSet(), ) } override suspend fun getContributors( owner: String, - repository: String + repository: String, ): Set { val contributors: Set = client.get(Contributors(owner, repository)).body() @@ -91,7 +91,7 @@ class GitHubBackend(token: String? = null) : Backend({ name = it.login, avatarUrl = it.avatarUrl, url = it.url, - contributions = it.contributions + contributions = it.contributions, ) }.toSet() } diff --git a/src/main/kotlin/app/revanced/api/backend/github/api/RequestResource.kt b/src/main/kotlin/app/revanced/api/backend/github/api/RequestResource.kt index ab78199..dc480ba 100644 --- a/src/main/kotlin/app/revanced/api/backend/github/api/RequestResource.kt +++ b/src/main/kotlin/app/revanced/api/backend/github/api/RequestResource.kt @@ -5,6 +5,7 @@ import io.ktor.resources.* class Request { @Resource("/users/{username}") class User(val username: String) + class Organization { @Resource("/orgs/{org}/members") class Members(val org: String) diff --git a/src/main/kotlin/app/revanced/api/backend/github/api/ResponseSchema.kt b/src/main/kotlin/app/revanced/api/backend/github/api/ResponseSchema.kt index fbfd89a..693a897 100644 --- a/src/main/kotlin/app/revanced/api/backend/github/api/ResponseSchema.kt +++ b/src/main/kotlin/app/revanced/api/backend/github/api/ResponseSchema.kt @@ -3,7 +3,6 @@ package app.revanced.api.backend.github.api import kotlinx.datetime.Instant import kotlinx.serialization.Serializable - class Response { interface IGitHubUser { val login: String @@ -12,7 +11,7 @@ class Response { } @Serializable - class GitHubUser ( + class GitHubUser( override val login: String, override val avatarUrl: String, override val url: String, @@ -41,11 +40,11 @@ class Response { val tagName: String, val assets: Set, val createdAt: Instant, - val body: String + val body: String, ) { @Serializable class GitHubAsset( - val browserDownloadUrl: String + val browserDownloadUrl: String, ) } } diff --git a/src/main/kotlin/app/revanced/api/command/MainCommand.kt b/src/main/kotlin/app/revanced/api/command/MainCommand.kt index 3038e96..6b77387 100644 --- a/src/main/kotlin/app/revanced/api/command/MainCommand.kt +++ b/src/main/kotlin/app/revanced/api/command/MainCommand.kt @@ -31,4 +31,4 @@ private object CLIVersionProvider : CommandLine.IVersionProvider { StartAPICommand::class, ], ) -private object MainCommand \ No newline at end of file +private object MainCommand diff --git a/src/main/kotlin/app/revanced/api/modules/Database.kt b/src/main/kotlin/app/revanced/api/modules/Database.kt index fcd0955..4727a0b 100644 --- a/src/main/kotlin/app/revanced/api/modules/Database.kt +++ b/src/main/kotlin/app/revanced/api/modules/Database.kt @@ -1,11 +1,10 @@ package app.revanced.api.modules import app.revanced.api.modules.AnnouncementService.Attachments.announcement -import app.revanced.api.schema.APIResponseAnnouncement import app.revanced.api.schema.APIAnnouncement import app.revanced.api.schema.APILatestAnnouncement +import app.revanced.api.schema.APIResponseAnnouncement import kotlinx.datetime.* -import org.jetbrains.exposed.sql.transactions.transaction import org.jetbrains.exposed.dao.IntEntity import org.jetbrains.exposed.dao.IntEntityClass import org.jetbrains.exposed.dao.id.EntityID @@ -13,7 +12,7 @@ import org.jetbrains.exposed.dao.id.IntIdTable import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq import org.jetbrains.exposed.sql.kotlin.datetime.datetime - +import org.jetbrains.exposed.sql.transactions.transaction class AnnouncementService(private val database: Database) { private object Announcements : IntIdTable() { @@ -52,7 +51,7 @@ class AnnouncementService(private val database: Database) { channel, createdAt, archivedAt, - level + level, ) } @@ -107,7 +106,7 @@ class AnnouncementService(private val database: Database) { fun archive( id: Int, - archivedAt: LocalDateTime? + archivedAt: LocalDateTime?, ) = transaction { Announcement.findById(id)?.apply { this.archivedAt = archivedAt ?: java.time.LocalDateTime.now().toKotlinLocalDateTime() diff --git a/src/main/kotlin/app/revanced/api/modules/Dependencies.kt b/src/main/kotlin/app/revanced/api/modules/Dependencies.kt index 3910400..cf8afe0 100644 --- a/src/main/kotlin/app/revanced/api/modules/Dependencies.kt +++ b/src/main/kotlin/app/revanced/api/modules/Dependencies.kt @@ -19,7 +19,7 @@ fun Application.configureDependencies() { globalModule, gitHubBackendModule, databaseModule, - authModule + authModule, ) } } @@ -51,7 +51,7 @@ val databaseModule = module { url = dotenv["DB_URL"], user = dotenv["DB_USER"], password = dotenv["DB_PASSWORD"], - driver = "org.h2.Driver" + driver = "org.h2.Driver", ) } factory { diff --git a/src/main/kotlin/app/revanced/api/modules/Routing.kt b/src/main/kotlin/app/revanced/api/modules/Routing.kt index f39d7e7..846bfc2 100644 --- a/src/main/kotlin/app/revanced/api/modules/Routing.kt +++ b/src/main/kotlin/app/revanced/api/modules/Routing.kt @@ -23,41 +23,68 @@ fun Application.configureRouting() { routing { route("/v${configuration.apiVersion}") { route("/announcements") { - suspend fun PipelineContext<*, ApplicationCall>.announcement( - block: AnnouncementService.() -> APIResponseAnnouncement? - ) = announcementService.block()?.let { call.respond(it) } - ?: call.respond(HttpStatusCode.NotFound) + suspend fun PipelineContext<*, ApplicationCall>.announcement(block: AnnouncementService.() -> APIResponseAnnouncement?) = + announcementService.block()?.let { call.respond(it) } + ?: call.respond(HttpStatusCode.NotFound) - suspend fun PipelineContext<*, ApplicationCall>.announcementId( - block: AnnouncementService.() -> APILatestAnnouncement? - ) = announcementService.block()?.let { call.respond(it) } - ?: call.respond(HttpStatusCode.NotFound) + suspend fun PipelineContext<*, ApplicationCall>.announcementId(block: AnnouncementService.() -> APILatestAnnouncement?) = + announcementService.block()?.let { call.respond(it) } + ?: call.respond(HttpStatusCode.NotFound) suspend fun PipelineContext<*, ApplicationCall>.channel(block: suspend (String) -> Unit) = block(call.parameters["channel"]!!) route("/{channel}/latest") { - get("/id") { channel { announcementId { latestId(it) } } } + get("/id") { + channel { + announcementId { + latestId(it) + } + } + } - get { channel { announcement { latest(it) } } } + get { + channel { + announcement { + latest(it) + } + } + } } - get("/{channel}") { channel { call.respond(announcementService.read(it)) } } + get("/{channel}") { + channel { + call.respond(announcementService.read(it)) + } + } route("/latest") { - get("/id") { announcementId { latestId() } } + get("/id") { + announcementId { + latestId() + } + } - get { announcement { latest() } } + get { + announcement { + latest() + } + } } - get { call.respond(announcementService.read()) } + get { + call.respond(announcementService.read()) + } authenticate("jwt") { suspend fun PipelineContext<*, ApplicationCall>.id(block: suspend (Int) -> Unit) = - call.parameters["id"]!!.toIntOrNull()?.let { block(it) } - ?: call.respond(HttpStatusCode.BadRequest) + call.parameters["id"]!!.toIntOrNull()?.let { + block(it) + } ?: call.respond(HttpStatusCode.BadRequest) - post { announcementService.new(call.receive()) } + post { + announcementService.new(call.receive()) + } post("/{id}/archive") { id { @@ -66,11 +93,23 @@ fun Application.configureRouting() { } } - post("/{id}/unarchive") { id { announcementService.unarchive(it) } } + post("/{id}/unarchive") { + id { + announcementService.unarchive(it) + } + } - patch("/{id}") { id { announcementService.update(it, call.receive()) } } + patch("/{id}") { + id { + announcementService.update(it, call.receive()) + } + } - delete("/{id}") { id { announcementService.delete(it) } } + delete("/{id}") { + id { + announcementService.delete(it) + } + } } } @@ -78,20 +117,23 @@ fun Application.configureRouting() { route("latest") { get { val patches = backend.getRelease(configuration.organization, configuration.patchesRepository) - val integrations = configuration.integrationsRepositoryNames.map { - async { backend.getRelease(configuration.organization, it) } - }.awaitAll() + val integrations = + configuration.integrationsRepositoryNames.map { + async { backend.getRelease(configuration.organization, it) } + }.awaitAll() - val assets = (patches.assets + integrations.flatMap { it.assets }).filter { - it.downloadUrl.endsWith(".apk") || it.downloadUrl.endsWith(".jar") - }.map { APIAsset(it.downloadUrl) }.toSet() + val assets = + (patches.assets + integrations.flatMap { it.assets }).filter { + it.downloadUrl.endsWith(".apk") || it.downloadUrl.endsWith(".jar") + }.map { APIAsset(it.downloadUrl) }.toSet() - val release = APIRelease( - patches.tag, - patches.createdAt, - patches.releaseNote, - assets - ) + val release = + APIRelease( + patches.tag, + patches.createdAt, + patches.releaseNote, + assets, + ) call.respond(release) } @@ -112,24 +154,26 @@ fun Application.configureRouting() { } get("/contributors") { - val contributors = configuration.contributorsRepositoryNames.map { - async { - APIContributable( - it, - backend.getContributors(configuration.organization, it).map { - APIContributor(it.name, it.avatarUrl, it.url, it.contributions) - }.toSet() - ) - } - }.awaitAll() + val contributors = + configuration.contributorsRepositoryNames.map { + async { + APIContributable( + it, + backend.getContributors(configuration.organization, it).map { + APIContributor(it.name, it.avatarUrl, it.url, it.contributions) + }.toSet(), + ) + } + }.awaitAll() call.respond(contributors) } get("/team") { - val team = backend.getMembers(configuration.organization).map { - APIMember(it.name, it.avatarUrl, it.url, it.gpgKeysUrl) - } + val team = + backend.getMembers(configuration.organization).map { + APIMember(it.name, it.avatarUrl, it.url, it.gpgKeysUrl) + } call.respond(team) } @@ -140,7 +184,11 @@ fun Application.configureRouting() { } } - authenticate("basic") { get("/token") { call.respond(authService.newToken()) } } + authenticate("basic") { + get("/token") { + call.respond(authService.newToken()) + } + } } } } diff --git a/src/main/kotlin/app/revanced/api/modules/Security.kt b/src/main/kotlin/app/revanced/api/modules/Security.kt index 0dd2dcb..02cc5f3 100644 --- a/src/main/kotlin/app/revanced/api/modules/Security.kt +++ b/src/main/kotlin/app/revanced/api/modules/Security.kt @@ -22,7 +22,7 @@ class AuthService( verifier( JWT.require(Algorithm.HMAC256(jwtSecret)) .withIssuer(issuer) - .build() + .build(), ) validate { credential -> JWTPrincipal(credential.payload) } } @@ -50,4 +50,4 @@ class AuthService( fun Application.configureSecurity() { val configureSecurity = get().configureSecurity configureSecurity() -} \ No newline at end of file +} diff --git a/src/main/kotlin/app/revanced/api/schema/APISchema.kt b/src/main/kotlin/app/revanced/api/schema/APISchema.kt index 295278a..fe3e2c9 100644 --- a/src/main/kotlin/app/revanced/api/schema/APISchema.kt +++ b/src/main/kotlin/app/revanced/api/schema/APISchema.kt @@ -8,7 +8,7 @@ class APIRelease( val version: String, val createdAt: LocalDateTime, val changelog: String, - val assets: Set + val assets: Set, ) interface APIUser { @@ -22,7 +22,7 @@ class APIMember( override val name: String, override val avatarUrl: String, override val url: String, - val gpgKeysUrl: String + val gpgKeysUrl: String, ) : APIUser @Serializable @@ -36,7 +36,7 @@ class APIContributor( @Serializable class APIContributable( val name: String, - val contributors: Set + val contributors: Set, ) @Serializable @@ -52,7 +52,7 @@ class APIAsset( @Serializable class APIReleaseVersion( - val version: String + val version: String, ) @Serializable @@ -63,7 +63,7 @@ class APIAnnouncement( val attachmentUrls: Set = emptySet(), val channel: String? = null, val archivedAt: LocalDateTime? = null, - val level: Int = 0 + val level: Int = 0, ) @Serializable @@ -76,15 +76,15 @@ class APIResponseAnnouncement( val channel: String? = null, val createdAt: LocalDateTime, val archivedAt: LocalDateTime? = null, - val level: Int = 0 + val level: Int = 0, ) @Serializable class APILatestAnnouncement( - val id: Int + val id: Int, ) @Serializable class APIAnnouncementArchivedAt( - val archivedAt: LocalDateTime -) \ No newline at end of file + val archivedAt: LocalDateTime, +) diff --git a/src/main/kotlin/app/revanced/api/schema/ConfigurationSchema.kt b/src/main/kotlin/app/revanced/api/schema/ConfigurationSchema.kt index db90377..5eff0b6 100644 --- a/src/main/kotlin/app/revanced/api/schema/ConfigurationSchema.kt +++ b/src/main/kotlin/app/revanced/api/schema/ConfigurationSchema.kt @@ -13,5 +13,5 @@ class APIConfiguration( @SerialName("contributors-repositories") val contributorsRepositoryNames: Set, @SerialName("api-version") - val apiVersion: Int = 1 -) \ No newline at end of file + val apiVersion: Int = 1, +) diff --git a/src/test/kotlin/app/revanced/ApplicationTest.kt b/src/test/kotlin/app/revanced/ApplicationTest.kt index 51682fe..5f1ce2e 100644 --- a/src/test/kotlin/app/revanced/ApplicationTest.kt +++ b/src/test/kotlin/app/revanced/ApplicationTest.kt @@ -23,8 +23,8 @@ class ApplicationTest { organization = "ReVanced", patchesRepository = "", integrationsRepositoryNames = setOf(), - contributorsRepositoryNames = setOf() - ) + contributorsRepositoryNames = setOf(), + ), ).let(::writeText) deleteOnExit() @@ -47,7 +47,7 @@ class ApplicationTest { headers { append( HttpHeaders.Authorization, - "Basic ${"${dotenv["BASIC_USERNAME"]}:${dotenv["BASIC_PASSWORD"]}".encodeBase64()}" + "Basic ${"${dotenv["BASIC_USERNAME"]}:${dotenv["BASIC_PASSWORD"]}".encodeBase64()}", ) } }.bodyAsText() From 6c930fff9a99f9ee23edd3d661694d2074f53b23 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Wed, 7 Feb 2024 01:16:35 +0100 Subject: [PATCH 028/102] feat: Add `/list` route --- build.gradle.kts | 8 +- gradle/libs.versions.toml | 6 ++ .../kotlin/app/revanced/api/modules/HTTP.kt | 4 +- .../app/revanced/api/modules/Routing.kt | 81 ++++++++++++++----- .../app/revanced/api/schema/APISchema.kt | 18 ++++- 5 files changed, 90 insertions(+), 27 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 82436b3..c9b481b 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -21,7 +21,7 @@ tasks { Because semantic-release is not designed to handle this case, we need to hack it. RE: https://github.com/KengoTODA/gradle-semantic-release-plugin/issues/435 - */ + */ register("publish") { group = "publishing" description = "Dummy task to hack gradle-semantic-release-plugin to release ReVanced API" @@ -41,6 +41,9 @@ ktor { repositories { mavenCentral() + google() + maven { url = uri("https://jitpack.io") } + mavenLocal() } dependencies { @@ -72,6 +75,9 @@ dependencies { implementation(libs.ktoml.file) implementation(libs.picocli) implementation(libs.kotlinx.datetime) + implementation(libs.revanced.patcher) + implementation(libs.revanced.library) + implementation(libs.caffeine) testImplementation(libs.mockk) testImplementation(libs.ktor.server.tests) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 6bce316..f00cb0f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -10,6 +10,9 @@ ktoml = "0.5.1" picocli = "4.7.3" datetime = "0.5.0" mockk = "1.13.9" +revanced-patcher = "19.2.0" +revanced-library = "1.5.0" +caffeine = "3.1.8" [libraries] ktor-client-core = { module = "io.ktor:ktor-client-core" } @@ -43,6 +46,9 @@ ktoml-file = { module = "com.akuleshov7:ktoml-file", version.ref = "ktoml" } picocli = { module = "info.picocli:picocli", version.ref = "picocli" } kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "datetime" } mockk = { module = "io.mockk:mockk", version.ref = "mockk" } +revanced-patcher = { module = "app.revanced:revanced-patcher", version.ref = "revanced-patcher" } +revanced-library = { module = "app.revanced:revanced-library", version.ref = "revanced-library" } +caffeine = { module = "com.github.ben-manes.caffeine:caffeine", version.ref = "caffeine" } [plugins] serilization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } diff --git a/src/main/kotlin/app/revanced/api/modules/HTTP.kt b/src/main/kotlin/app/revanced/api/modules/HTTP.kt index 590629b..1f3248f 100644 --- a/src/main/kotlin/app/revanced/api/modules/HTTP.kt +++ b/src/main/kotlin/app/revanced/api/modules/HTTP.kt @@ -19,8 +19,6 @@ fun Application.configureHTTP() { anyHost() // @TODO: Don't do this in production if possible. Try to limit it. } install(CachingHeaders) { - options { _, _ -> - CachingOptions(CacheControl.MaxAge(maxAgeSeconds = 5.minutes.inWholeSeconds.toInt())) - } + options { _, _ -> CachingOptions(CacheControl.MaxAge(maxAgeSeconds = 5.minutes.inWholeSeconds.toInt())) } } } diff --git a/src/main/kotlin/app/revanced/api/modules/Routing.kt b/src/main/kotlin/app/revanced/api/modules/Routing.kt index 846bfc2..61ad106 100644 --- a/src/main/kotlin/app/revanced/api/modules/Routing.kt +++ b/src/main/kotlin/app/revanced/api/modules/Routing.kt @@ -2,6 +2,9 @@ package app.revanced.api.modules import app.revanced.api.backend.Backend import app.revanced.api.schema.* +import app.revanced.library.PatchUtils +import app.revanced.patcher.PatchBundleLoader +import com.github.benmanes.caffeine.cache.Caffeine import io.ktor.http.* import io.ktor.server.application.* import io.ktor.server.auth.* @@ -12,6 +15,8 @@ import io.ktor.server.routing.* import io.ktor.util.pipeline.* import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll +import java.io.File +import java.net.URL import org.koin.ktor.ext.get as koinGet fun Application.configureRouting() { @@ -116,34 +121,70 @@ fun Application.configureRouting() { route("/patches") { route("latest") { get { - val patches = backend.getRelease(configuration.organization, configuration.patchesRepository) - val integrations = - configuration.integrationsRepositoryNames.map { - async { backend.getRelease(configuration.organization, it) } - }.awaitAll() + val patchesRelease = + backend.getRelease(configuration.organization, configuration.patchesRepository) + val integrationsReleases = configuration.integrationsRepositoryNames.map { + async { backend.getRelease(configuration.organization, it) } + }.awaitAll() - val assets = - (patches.assets + integrations.flatMap { it.assets }).filter { - it.downloadUrl.endsWith(".apk") || it.downloadUrl.endsWith(".jar") - }.map { APIAsset(it.downloadUrl) }.toSet() + val assets = (patchesRelease.assets + integrationsReleases.flatMap { it.assets }) + .map { APIAsset(it.downloadUrl) } + .filter { it.type != APIAsset.Type.UNKNOWN } + .toSet() - val release = - APIRelease( - patches.tag, - patches.createdAt, - patches.releaseNote, - assets, - ) + val apiRelease = APIRelease( + patchesRelease.tag, + patchesRelease.createdAt, + patchesRelease.releaseNote, + assets, + ) - call.respond(release) + call.respond(apiRelease) } get("/version") { - val patches = backend.getRelease(configuration.organization, configuration.patchesRepository) + val patchesRelease = + backend.getRelease(configuration.organization, configuration.patchesRepository) - val release = APIReleaseVersion(patches.tag) + val apiPatchesRelease = APIReleaseVersion(patchesRelease.tag) - call.respond(release) + call.respond(apiPatchesRelease) + } + + val fileCache = Caffeine + .newBuilder() + .evictionListener { _, value, _ -> value?.delete() } + .maximumSize(1) + .build() + + get("/list") { + val patchesRelease = + backend.getRelease(configuration.organization, configuration.patchesRepository) + + // Get the cached patches file or download and cache a new one. + // The old file is deleted on eviction. + val patchesFile = fileCache.getIfPresent(patchesRelease.tag) ?: run { + val downloadUrl = patchesRelease.assets + .map { APIAsset(it.downloadUrl) } + .find { it.type == APIAsset.Type.PATCHES } + ?.downloadUrl + + kotlin.io.path.createTempFile().toFile().apply { + outputStream().use { URL(downloadUrl).openStream().copyTo(it) } + }.also { + fileCache.put(patchesRelease.tag, it) + it.deleteOnExit() + } + } + + call.respondOutputStream( + contentType = ContentType.Application.Json, + ) { + PatchUtils.Json.serialize( + PatchBundleLoader.Jar(patchesFile), + outputStream = this, + ) + } } } } diff --git a/src/main/kotlin/app/revanced/api/schema/APISchema.kt b/src/main/kotlin/app/revanced/api/schema/APISchema.kt index fe3e2c9..ec6355b 100644 --- a/src/main/kotlin/app/revanced/api/schema/APISchema.kt +++ b/src/main/kotlin/app/revanced/api/schema/APISchema.kt @@ -1,6 +1,7 @@ package app.revanced.api.schema import kotlinx.datetime.LocalDateTime +import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable @@ -44,9 +45,20 @@ class APIAsset( val downloadUrl: String, ) { val type = when { - downloadUrl.endsWith(".jar") -> "patches" - downloadUrl.endsWith(".apk") -> "integrations" - else -> "unknown" + downloadUrl.endsWith(".jar") -> Type.PATCHES + downloadUrl.endsWith(".apk") -> Type.INTEGRATIONS + else -> Type.UNKNOWN + } + + enum class Type { + @SerialName("patches") + PATCHES, + + @SerialName("integrations") + INTEGRATIONS, + + @SerialName("unknown") + UNKNOWN, } } From ee971e3902bb7a04a38be642eab0f5f3e3b503da Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Wed, 7 Feb 2024 16:46:26 +0100 Subject: [PATCH 029/102] docs: Fix wording --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5973b63..dcdfb16 100644 --- a/README.md +++ b/README.md @@ -75,7 +75,7 @@ ReVanced API acts as the data source for [ReVanced Website](https://github.com/R Some of the features ReVanced API include: - 📢 **Announcements**: Push announcements grouped by channels -- ℹ️ About: Get more information such as a description, ways to donate, and links about the hoster of ReVanced API +- ℹ️ About: Get more information such as a description about, ways to donate to, and links of the hoster of ReVanced API - 🧩 Patches: Get the latest updates of ReVanced Patches directly from ReVanced API - 👥 Contributors: List all contributors involved in the project From bb0b78df8ace5993cbbb11583c4c6baa404b4a0c Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Wed, 7 Feb 2024 16:47:52 +0100 Subject: [PATCH 030/102] docs: Make all feature titles bold --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index dcdfb16..91ba5df 100644 --- a/README.md +++ b/README.md @@ -75,9 +75,9 @@ ReVanced API acts as the data source for [ReVanced Website](https://github.com/R Some of the features ReVanced API include: - 📢 **Announcements**: Push announcements grouped by channels -- ℹ️ About: Get more information such as a description about, ways to donate to, and links of the hoster of ReVanced API -- 🧩 Patches: Get the latest updates of ReVanced Patches directly from ReVanced API -- 👥 Contributors: List all contributors involved in the project +- ℹ️ **About**: Get more information such as a description about, ways to donate to, and links of the hoster of ReVanced API +- 🧩 **Patches**: Get the latest updates of ReVanced Patches directly from ReVanced API +- 👥 **Contributors**: List all contributors involved in the project ## 🚀 How to get started From 7a1957d013cfd0851dc0191b715e8f36c530d9d2 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Thu, 8 Feb 2024 03:56:16 +0100 Subject: [PATCH 031/102] perf: Cache patches list instead of just the patches file --- .../app/revanced/api/modules/Routing.kt | 33 +++++++++---------- 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/src/main/kotlin/app/revanced/api/modules/Routing.kt b/src/main/kotlin/app/revanced/api/modules/Routing.kt index 61ad106..616e2e5 100644 --- a/src/main/kotlin/app/revanced/api/modules/Routing.kt +++ b/src/main/kotlin/app/revanced/api/modules/Routing.kt @@ -15,7 +15,7 @@ import io.ktor.server.routing.* import io.ktor.util.pipeline.* import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll -import java.io.File +import java.io.ByteArrayOutputStream import java.net.URL import org.koin.ktor.ext.get as koinGet @@ -151,40 +151,37 @@ fun Application.configureRouting() { call.respond(apiPatchesRelease) } - val fileCache = Caffeine + val patchesListCache = Caffeine .newBuilder() - .evictionListener { _, value, _ -> value?.delete() } .maximumSize(1) - .build() + .build() get("/list") { val patchesRelease = backend.getRelease(configuration.organization, configuration.patchesRepository) - // Get the cached patches file or download and cache a new one. - // The old file is deleted on eviction. - val patchesFile = fileCache.getIfPresent(patchesRelease.tag) ?: run { + val patchesListByteArray = patchesListCache.getIfPresent(patchesRelease.tag) ?: run { val downloadUrl = patchesRelease.assets .map { APIAsset(it.downloadUrl) } .find { it.type == APIAsset.Type.PATCHES } ?.downloadUrl - kotlin.io.path.createTempFile().toFile().apply { + val patches = kotlin.io.path.createTempFile().toFile().apply { outputStream().use { URL(downloadUrl).openStream().copyTo(it) } + }.let { file -> + PatchBundleLoader.Jar(file).also { file.delete() } + } + + ByteArrayOutputStream().use { stream -> + PatchUtils.Json.serialize(patches, outputStream = stream) + + stream.toByteArray() }.also { - fileCache.put(patchesRelease.tag, it) - it.deleteOnExit() + patchesListCache.put(patchesRelease.tag, it) } } - call.respondOutputStream( - contentType = ContentType.Application.Json, - ) { - PatchUtils.Json.serialize( - PatchBundleLoader.Jar(patchesFile), - outputStream = this, - ) - } + call.respondBytes(ContentType.Application.Json) { patchesListByteArray } } } } From fa2f8b2f86d2eb5b11196ba868fb95b2b6335a94 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Wed, 5 Jun 2024 03:07:28 +0200 Subject: [PATCH 032/102] refactor: Refactor into services and repositories --- build.gradle.kts | 29 +-- gradle/libs.versions.toml | 12 +- gradle/wrapper/gradle-wrapper.jar | Bin 59536 -> 43453 bytes gradle/wrapper/gradle-wrapper.properties | 5 +- gradlew | 41 +++- gradlew.bat | 35 +-- .../revanced/api/command/StartAPICommand.kt | 5 +- .../api/configuration/Dependencies.kt | 83 +++++++ .../api/{modules => configuration}/HTTP.kt | 8 +- .../revanced/api/configuration/Security.kt | 9 + .../api/configuration/Serialization.kt | 19 ++ .../api/configuration/routing/Routing.kt | 19 ++ .../routing/routes/Announcements.kt | 86 +++++++ .../configuration/routing/routes/ApiRoute.kt | 41 ++++ .../routing/routes/PatchesRoute.kt | 26 ++ .../app/revanced/api/modules/Dependencies.kt | 75 ------ .../app/revanced/api/modules/Routing.kt | 232 ------------------ .../app/revanced/api/modules/Serialization.kt | 11 - .../AnnouncementRepository.kt} | 151 ++++++------ .../ConfigurationRepository.kt} | 4 +- .../backend/BackendRepository.kt} | 10 +- .../github/GitHubBackendRepository.kt} | 36 +-- .../backend/github/api/Request.kt} | 2 +- .../backend/github/api/Response.kt} | 2 +- .../api/services/AnnouncementService.kt | 35 +++ .../app/revanced/api/services/ApiService.kt | 33 +++ .../Security.kt => services/AuthService.kt} | 10 +- .../revanced/api/services/PatchesService.kt | 87 +++++++ src/main/resources/static/api/about.json | 2 +- .../kotlin/app/revanced/ApplicationTest.kt | 57 ----- 30 files changed, 623 insertions(+), 542 deletions(-) create mode 100644 src/main/kotlin/app/revanced/api/configuration/Dependencies.kt rename src/main/kotlin/app/revanced/api/{modules => configuration}/HTTP.kt (81%) create mode 100644 src/main/kotlin/app/revanced/api/configuration/Security.kt create mode 100644 src/main/kotlin/app/revanced/api/configuration/Serialization.kt create mode 100644 src/main/kotlin/app/revanced/api/configuration/routing/Routing.kt create mode 100644 src/main/kotlin/app/revanced/api/configuration/routing/routes/Announcements.kt create mode 100644 src/main/kotlin/app/revanced/api/configuration/routing/routes/ApiRoute.kt create mode 100644 src/main/kotlin/app/revanced/api/configuration/routing/routes/PatchesRoute.kt delete mode 100644 src/main/kotlin/app/revanced/api/modules/Dependencies.kt delete mode 100644 src/main/kotlin/app/revanced/api/modules/Routing.kt delete mode 100644 src/main/kotlin/app/revanced/api/modules/Serialization.kt rename src/main/kotlin/app/revanced/api/{modules/Database.kt => repository/AnnouncementRepository.kt} (52%) rename src/main/kotlin/app/revanced/api/{schema/ConfigurationSchema.kt => repository/ConfigurationRepository.kt} (85%) rename src/main/kotlin/app/revanced/api/{backend/Backend.kt => repository/backend/BackendRepository.kt} (91%) rename src/main/kotlin/app/revanced/api/{backend/github/GitHubBackend.kt => repository/backend/github/GitHubBackendRepository.kt} (67%) rename src/main/kotlin/app/revanced/api/{backend/github/api/RequestResource.kt => repository/backend/github/api/Request.kt} (93%) rename src/main/kotlin/app/revanced/api/{backend/github/api/ResponseSchema.kt => repository/backend/github/api/Response.kt} (96%) create mode 100644 src/main/kotlin/app/revanced/api/services/AnnouncementService.kt create mode 100644 src/main/kotlin/app/revanced/api/services/ApiService.kt rename src/main/kotlin/app/revanced/api/{modules/Security.kt => services/AuthService.kt} (87%) create mode 100644 src/main/kotlin/app/revanced/api/services/PatchesService.kt delete mode 100644 src/test/kotlin/app/revanced/ApplicationTest.kt diff --git a/build.gradle.kts b/build.gradle.kts index c9b481b..a62319d 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -11,21 +11,11 @@ tasks { expand("projectVersion" to project.version) } - /* - Dummy task to hack gradle-semantic-release-plugin to release this project. - - Explanation: - SemVer is a standard for versioning libraries. - For that reason the semantic-release plugin uses the "publish" task to publish libraries. - However, this subproject is not a library, and the "publish" task is not available for this subproject. - Because semantic-release is not designed to handle this case, we need to hack it. - - RE: https://github.com/KengoTODA/gradle-semantic-release-plugin/issues/435 - */ + // Needed by gradle-semantic-release-plugin. + // Tracking: https://github.com/KengoTODA/gradle-semantic-release-plugin/issues/435 register("publish") { group = "publishing" - description = "Dummy task to hack gradle-semantic-release-plugin to release ReVanced API" - dependsOn(startShadowScripts) + dependsOn(shadowJar) } } @@ -42,8 +32,15 @@ ktor { repositories { mavenCentral() google() - maven { url = uri("https://jitpack.io") } mavenLocal() + maven { + // A repository must be specified for some reason. "registry" is a dummy. + url = uri("https://maven.pkg.github.com/revanced/registry") + credentials { + username = project.findProperty("gpr.user") as String? ?: System.getenv("GITHUB_ACTOR") + password = project.findProperty("gpr.key") as String? ?: System.getenv("GITHUB_TOKEN") + } + } } dependencies { @@ -78,8 +75,4 @@ dependencies { implementation(libs.revanced.patcher) implementation(libs.revanced.library) implementation(libs.caffeine) - - testImplementation(libs.mockk) - testImplementation(libs.ktor.server.tests) - testImplementation(libs.kotlin.test.junit) } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index f00cb0f..66cbed3 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -kotlin = "1.9.22" +kotlin = "2.0.0" logback = "1.4.14" exposed = "0.41.1" h2 = "2.2.224" @@ -7,11 +7,10 @@ koin = "3.5.3" dotenv = "6.4.1" ktor = "2.3.7" ktoml = "0.5.1" -picocli = "4.7.3" +picocli = "4.7.5" datetime = "0.5.0" -mockk = "1.13.9" -revanced-patcher = "19.2.0" -revanced-library = "1.5.0" +revanced-patcher = "19.3.1" +revanced-library = "2.3.0" caffeine = "3.1.8" [libraries] @@ -39,13 +38,10 @@ exposed-jdbc = { module = "org.jetbrains.exposed:exposed-jdbc", version.ref = "e exposed-dao = { module = "org.jetbrains.exposed:exposed-dao", version.ref = "exposed" } exposed-kotlin-datetime = { module = "org.jetbrains.exposed:exposed-kotlin-datetime", version.ref = "exposed" } dotenv-kotlin = { module = "io.github.cdimascio:dotenv-kotlin", version.ref = "dotenv" } -ktor-server-tests = { module = "io.ktor:ktor-server-tests" } -kotlin-test-junit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kotlin" } ktoml-core = { module = "com.akuleshov7:ktoml-core", version.ref = "ktoml" } ktoml-file = { module = "com.akuleshov7:ktoml-file", version.ref = "ktoml" } picocli = { module = "info.picocli:picocli", version.ref = "picocli" } kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "datetime" } -mockk = { module = "io.mockk:mockk", version.ref = "mockk" } revanced-patcher = { module = "app.revanced:revanced-patcher", version.ref = "revanced-patcher" } revanced-library = { module = "app.revanced:revanced-library", version.ref = "revanced-library" } caffeine = { module = "com.github.ben-manes.caffeine:caffeine", version.ref = "caffeine" } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 7454180f2ae8848c63b8b4dea2cb829da983f2fa..e6441136f3d4ba8a0da8d277868979cfbc8ad796 100644 GIT binary patch literal 43453 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 yu?e%!_ptzGB|8GrCfu%p?(_ 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
NnX)EH+Nua)3Y(c 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 QuEJmfE|#`Dqx|px0L@K;Y5)KL diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index e411586..8a1f6b9 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,8 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip +distributionSha256Sum=a4b4158601f8636cdeeab09bd76afb640030bb5b144aafe261a5e8af027dc612 +distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip +networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 1b6c787..b740cf1 100755 --- a/gradlew +++ b/gradlew @@ -55,7 +55,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -80,13 +80,11 @@ do esac done -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit - -APP_NAME="Gradle" +# This is normally unused +# shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -133,22 +131,29 @@ location of your Java installation." fi else JAVACMD=java - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac case $MAX_FD in #( '' | soft) :;; #( *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -193,11 +198,15 @@ if "$cygwin" || "$msys" ; then done fi -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ @@ -205,6 +214,12 @@ set -- \ org.gradle.wrapper.GradleWrapperMain \ "$@" +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + # Use "xargs" to parse quoted args. # # With -n1 it outputs one arg per line, with the quotes and backslashes removed. diff --git a/gradlew.bat b/gradlew.bat index 107acd3..25da30d 100755 --- a/gradlew.bat +++ b/gradlew.bat @@ -14,7 +14,7 @@ @rem limitations under the License. @rem -@if "%DEBUG%" == "" @echo off +@if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @@ -25,7 +25,8 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @@ -40,13 +41,13 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto execute +if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -56,11 +57,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -75,13 +76,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar :end @rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd +if %ERRORLEVEL% equ 0 goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal diff --git a/src/main/kotlin/app/revanced/api/command/StartAPICommand.kt b/src/main/kotlin/app/revanced/api/command/StartAPICommand.kt index b1898ef..585730a 100644 --- a/src/main/kotlin/app/revanced/api/command/StartAPICommand.kt +++ b/src/main/kotlin/app/revanced/api/command/StartAPICommand.kt @@ -1,6 +1,7 @@ package app.revanced.api.command -import app.revanced.api.modules.* +import app.revanced.api.configuration.* +import app.revanced.api.configuration.routing.configureRouting import io.ktor.server.engine.* import io.ktor.server.netty.* import picocli.CommandLine @@ -27,7 +28,7 @@ internal object StartAPICommand : Runnable { override fun run() { embeddedServer(Netty, port, host) { configureDependencies() - configureHTTP() + configureHTTP(allowedHost = host) configureSerialization() configureSecurity() configureRouting() diff --git a/src/main/kotlin/app/revanced/api/configuration/Dependencies.kt b/src/main/kotlin/app/revanced/api/configuration/Dependencies.kt new file mode 100644 index 0000000..dc8f620 --- /dev/null +++ b/src/main/kotlin/app/revanced/api/configuration/Dependencies.kt @@ -0,0 +1,83 @@ +package app.revanced.api.configuration + +import app.revanced.api.repository.AnnouncementRepository +import app.revanced.api.repository.ConfigurationRepository +import app.revanced.api.repository.backend.BackendRepository +import app.revanced.api.repository.backend.github.GitHubBackendRepository +import app.revanced.api.services.AnnouncementService +import app.revanced.api.services.ApiService +import app.revanced.api.services.AuthService +import app.revanced.api.services.PatchesService +import com.akuleshov7.ktoml.Toml +import com.akuleshov7.ktoml.source.decodeFromStream +import io.github.cdimascio.dotenv.Dotenv +import io.ktor.server.application.* +import org.jetbrains.exposed.sql.Database +import org.koin.core.module.dsl.singleOf +import org.koin.dsl.bind +import org.koin.dsl.module +import org.koin.ktor.plugin.Koin +import java.io.File + +fun Application.configureDependencies() { + val globalModule = module { + single { + Dotenv.configure() + .systemProperties() + .load() + } + } + + val repositoryModule = module { + single { + val dotenv = get() + + Database.connect( + url = dotenv["DB_URL"], + user = dotenv["DB_USER"], + password = dotenv["DB_PASSWORD"], + driver = "org.h2.Driver", + ) + } + + single { + val configFilePath = get()["CONFIG_FILE_PATH"] + val configFile = File(configFilePath).inputStream() + + Toml.decodeFromStream(configFile) + } + + singleOf(::AnnouncementRepository) + } + + val serviceModule = module { + single { + val dotenv = get() + + val jwtSecret = dotenv["JWT_SECRET"] + val issuer = dotenv["JWT_ISSUER"] + val validityInMin = dotenv["JWT_VALIDITY_IN_MIN"].toInt() + + val basicUsername = dotenv["BASIC_USERNAME"] + val basicPassword = dotenv["BASIC_PASSWORD"] + + AuthService(issuer, validityInMin, jwtSecret, basicUsername, basicPassword) + } + single { + val token = get()["GITHUB_TOKEN"] + + GitHubBackendRepository(token) + } bind BackendRepository::class + singleOf(::AnnouncementService) + singleOf(::PatchesService) + singleOf(::ApiService) + } + + install(Koin) { + modules( + globalModule, + repositoryModule, + serviceModule, + ) + } +} diff --git a/src/main/kotlin/app/revanced/api/modules/HTTP.kt b/src/main/kotlin/app/revanced/api/configuration/HTTP.kt similarity index 81% rename from src/main/kotlin/app/revanced/api/modules/HTTP.kt rename to src/main/kotlin/app/revanced/api/configuration/HTTP.kt index 1f3248f..336b100 100644 --- a/src/main/kotlin/app/revanced/api/modules/HTTP.kt +++ b/src/main/kotlin/app/revanced/api/configuration/HTTP.kt @@ -1,4 +1,4 @@ -package app.revanced.api.modules +package app.revanced.api.configuration import io.ktor.http.* import io.ktor.http.content.* @@ -8,7 +8,9 @@ import io.ktor.server.plugins.conditionalheaders.* import io.ktor.server.plugins.cors.routing.* import kotlin.time.Duration.Companion.minutes -fun Application.configureHTTP() { +fun Application.configureHTTP( + allowedHost: String, +) { install(ConditionalHeaders) install(CORS) { allowMethod(HttpMethod.Options) @@ -16,7 +18,7 @@ fun Application.configureHTTP() { allowMethod(HttpMethod.Delete) allowMethod(HttpMethod.Patch) allowHeader(HttpHeaders.Authorization) - anyHost() // @TODO: Don't do this in production if possible. Try to limit it. + allowHost(allowedHost) } install(CachingHeaders) { options { _, _ -> CachingOptions(CacheControl.MaxAge(maxAgeSeconds = 5.minutes.inWholeSeconds.toInt())) } diff --git a/src/main/kotlin/app/revanced/api/configuration/Security.kt b/src/main/kotlin/app/revanced/api/configuration/Security.kt new file mode 100644 index 0000000..2543fb1 --- /dev/null +++ b/src/main/kotlin/app/revanced/api/configuration/Security.kt @@ -0,0 +1,9 @@ +package app.revanced.api.configuration + +import app.revanced.api.services.AuthService +import io.ktor.server.application.* +import org.koin.ktor.ext.get + +fun Application.configureSecurity() { + get().configureSecurity(this) +} diff --git a/src/main/kotlin/app/revanced/api/configuration/Serialization.kt b/src/main/kotlin/app/revanced/api/configuration/Serialization.kt new file mode 100644 index 0000000..4e9f7ed --- /dev/null +++ b/src/main/kotlin/app/revanced/api/configuration/Serialization.kt @@ -0,0 +1,19 @@ +package app.revanced.api.configuration + +import io.ktor.serialization.kotlinx.json.* +import io.ktor.server.application.* +import io.ktor.server.plugins.contentnegotiation.* +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonNamingStrategy + +@OptIn(ExperimentalSerializationApi::class) +fun Application.configureSerialization() { + install(ContentNegotiation) { + json( + Json { + namingStrategy = JsonNamingStrategy.SnakeCase + }, + ) + } +} diff --git a/src/main/kotlin/app/revanced/api/configuration/routing/Routing.kt b/src/main/kotlin/app/revanced/api/configuration/routing/Routing.kt new file mode 100644 index 0000000..18420b6 --- /dev/null +++ b/src/main/kotlin/app/revanced/api/configuration/routing/Routing.kt @@ -0,0 +1,19 @@ +package app.revanced.api.configuration.routing + +import app.revanced.api.configuration.routing.routes.configureAnnouncementsRoute +import app.revanced.api.configuration.routing.routes.configurePatchesRoute +import app.revanced.api.configuration.routing.routes.configureRootRoute +import app.revanced.api.repository.ConfigurationRepository +import io.ktor.server.application.* +import io.ktor.server.routing.* +import org.koin.ktor.ext.get + +internal fun Application.configureRouting() = routing { + val configuration = get() + + route("/v${configuration.apiVersion}") { + configureRootRoute() + configurePatchesRoute() + configureAnnouncementsRoute() + } +} diff --git a/src/main/kotlin/app/revanced/api/configuration/routing/routes/Announcements.kt b/src/main/kotlin/app/revanced/api/configuration/routing/routes/Announcements.kt new file mode 100644 index 0000000..4f9f038 --- /dev/null +++ b/src/main/kotlin/app/revanced/api/configuration/routing/routes/Announcements.kt @@ -0,0 +1,86 @@ +package app.revanced.api.configuration.routing.routes + +import app.revanced.api.schema.APIAnnouncement +import app.revanced.api.schema.APIAnnouncementArchivedAt +import app.revanced.api.services.AnnouncementService +import io.ktor.http.* +import io.ktor.server.application.* +import io.ktor.server.auth.* +import io.ktor.server.request.* +import io.ktor.server.response.* +import io.ktor.server.routing.* +import io.ktor.server.util.* +import org.koin.ktor.ext.get as koinGet + +internal fun Route.configureAnnouncementsRoute() = route("/announcements") { + val announcementService = koinGet() + + route("/{channel}/latest") { + get("/id") { + val channel: String by call.parameters + + call.respond( + announcementService.latestId(channel) ?: return@get call.respond(HttpStatusCode.NotFound), + ) + } + + get { + val channel: String by call.parameters + + call.respond( + announcementService.latest(channel) ?: return@get call.respond(HttpStatusCode.NotFound), + ) + } + } + + get("/{channel}") { + val channel: String by call.parameters + + call.respond(announcementService.all(channel)) + } + + route("/latest") { + get("/id") { + call.respond(announcementService.latestId() ?: return@get call.respond(HttpStatusCode.NotFound)) + } + + get { + call.respond(announcementService.latest() ?: return@get call.respond(HttpStatusCode.NotFound)) + } + } + + get { + call.respond(announcementService.all()) + } + + authenticate("jwt") { + post { + announcementService.new(call.receive()) + } + + post("/{id}/archive") { + val id: Int by call.parameters + val archivedAt = call.receiveNullable()?.archivedAt + + announcementService.archive(id, archivedAt) + } + + post("/{id}/unarchive") { + val id: Int by call.parameters + + announcementService.unarchive(id) + } + + patch("/{id}") { + val id: Int by call.parameters + + announcementService.update(id, call.receive()) + } + + delete("/{id}") { + val id: Int by call.parameters + + announcementService.delete(id) + } + } +} diff --git a/src/main/kotlin/app/revanced/api/configuration/routing/routes/ApiRoute.kt b/src/main/kotlin/app/revanced/api/configuration/routing/routes/ApiRoute.kt new file mode 100644 index 0000000..b502ac8 --- /dev/null +++ b/src/main/kotlin/app/revanced/api/configuration/routing/routes/ApiRoute.kt @@ -0,0 +1,41 @@ +package app.revanced.api.configuration.routing.routes + +import app.revanced.api.services.ApiService +import app.revanced.api.services.AuthService +import io.ktor.http.* +import io.ktor.server.application.* +import io.ktor.server.auth.* +import io.ktor.server.http.content.* +import io.ktor.server.response.* +import io.ktor.server.routing.* +import org.koin.ktor.ext.get + +internal fun Route.configureRootRoute() { + val apiService = get() + val authService = get() + + get("/contributors") { + call.respond(apiService.contributors()) + } + + get("/team") { + call.respond(apiService.team()) + } + + route("/ping") { + handle { + call.respond(HttpStatusCode.NoContent) + } + } + + authenticate("basic") { + get("/token") { + call.respond(authService.newToken()) + } + } + + staticResources("/", "/static/api") { + contentType { ContentType.Application.Json } + extensions("json") + } +} diff --git a/src/main/kotlin/app/revanced/api/configuration/routing/routes/PatchesRoute.kt b/src/main/kotlin/app/revanced/api/configuration/routing/routes/PatchesRoute.kt new file mode 100644 index 0000000..21e811e --- /dev/null +++ b/src/main/kotlin/app/revanced/api/configuration/routing/routes/PatchesRoute.kt @@ -0,0 +1,26 @@ +package app.revanced.api.configuration.routing.routes + +import app.revanced.api.services.PatchesService +import io.ktor.http.* +import io.ktor.server.application.* +import io.ktor.server.response.* +import io.ktor.server.routing.* +import org.koin.ktor.ext.get as koinGet + +internal fun Route.configurePatchesRoute() = route("/patches") { + val patchesService = koinGet() + + route("latest") { + get { + call.respond(patchesService.latestRelease()) + } + + get("/version") { + call.respond(patchesService.latestVersion()) + } + + get("/list") { + call.respondBytes(ContentType.Application.Json) { patchesService.list() } + } + } +} diff --git a/src/main/kotlin/app/revanced/api/modules/Dependencies.kt b/src/main/kotlin/app/revanced/api/modules/Dependencies.kt deleted file mode 100644 index cf8afe0..0000000 --- a/src/main/kotlin/app/revanced/api/modules/Dependencies.kt +++ /dev/null @@ -1,75 +0,0 @@ -package app.revanced.api.modules - -import app.revanced.api.backend.Backend -import app.revanced.api.backend.github.GitHubBackend -import app.revanced.api.schema.APIConfiguration -import com.akuleshov7.ktoml.Toml -import com.akuleshov7.ktoml.source.decodeFromStream -import io.github.cdimascio.dotenv.Dotenv -import io.ktor.server.application.* -import org.jetbrains.exposed.sql.Database -import org.koin.dsl.bind -import org.koin.dsl.module -import org.koin.ktor.plugin.Koin -import java.io.File - -fun Application.configureDependencies() { - install(Koin) { - modules( - globalModule, - gitHubBackendModule, - databaseModule, - authModule, - ) - } -} - -val globalModule = module { - single { - Dotenv.configure() - .systemProperties() - .load() - } - single { - val configFilePath = get()["CONFIG_FILE_PATH"] - Toml.decodeFromStream(File(configFilePath).inputStream()) - } -} - -val gitHubBackendModule = module { - single { - val token = get()["GITHUB_TOKEN"] - GitHubBackend(token) - } bind Backend::class -} - -val databaseModule = module { - single { - val dotenv = get() - - Database.connect( - url = dotenv["DB_URL"], - user = dotenv["DB_USER"], - password = dotenv["DB_PASSWORD"], - driver = "org.h2.Driver", - ) - } - factory { - AnnouncementService(get()) - } -} - -val authModule = module { - single { - val dotenv = get() - - val jwtSecret = dotenv["JWT_SECRET"] - val issuer = dotenv["JWT_ISSUER"] - val validityInMin = dotenv["JWT_VALIDITY_IN_MIN"].toInt() - - val basicUsername = dotenv["BASIC_USERNAME"] - val basicPassword = dotenv["BASIC_PASSWORD"] - - AuthService(issuer, validityInMin, jwtSecret, basicUsername, basicPassword) - } -} diff --git a/src/main/kotlin/app/revanced/api/modules/Routing.kt b/src/main/kotlin/app/revanced/api/modules/Routing.kt deleted file mode 100644 index 616e2e5..0000000 --- a/src/main/kotlin/app/revanced/api/modules/Routing.kt +++ /dev/null @@ -1,232 +0,0 @@ -package app.revanced.api.modules - -import app.revanced.api.backend.Backend -import app.revanced.api.schema.* -import app.revanced.library.PatchUtils -import app.revanced.patcher.PatchBundleLoader -import com.github.benmanes.caffeine.cache.Caffeine -import io.ktor.http.* -import io.ktor.server.application.* -import io.ktor.server.auth.* -import io.ktor.server.http.content.* -import io.ktor.server.request.* -import io.ktor.server.response.* -import io.ktor.server.routing.* -import io.ktor.util.pipeline.* -import kotlinx.coroutines.async -import kotlinx.coroutines.awaitAll -import java.io.ByteArrayOutputStream -import java.net.URL -import org.koin.ktor.ext.get as koinGet - -fun Application.configureRouting() { - val backend: Backend = koinGet() - val configuration: APIConfiguration = koinGet() - val announcementService: AnnouncementService = koinGet() - val authService: AuthService = koinGet() - - routing { - route("/v${configuration.apiVersion}") { - route("/announcements") { - suspend fun PipelineContext<*, ApplicationCall>.announcement(block: AnnouncementService.() -> APIResponseAnnouncement?) = - announcementService.block()?.let { call.respond(it) } - ?: call.respond(HttpStatusCode.NotFound) - - suspend fun PipelineContext<*, ApplicationCall>.announcementId(block: AnnouncementService.() -> APILatestAnnouncement?) = - announcementService.block()?.let { call.respond(it) } - ?: call.respond(HttpStatusCode.NotFound) - - suspend fun PipelineContext<*, ApplicationCall>.channel(block: suspend (String) -> Unit) = - block(call.parameters["channel"]!!) - - route("/{channel}/latest") { - get("/id") { - channel { - announcementId { - latestId(it) - } - } - } - - get { - channel { - announcement { - latest(it) - } - } - } - } - - get("/{channel}") { - channel { - call.respond(announcementService.read(it)) - } - } - - route("/latest") { - get("/id") { - announcementId { - latestId() - } - } - - get { - announcement { - latest() - } - } - } - - get { - call.respond(announcementService.read()) - } - - authenticate("jwt") { - suspend fun PipelineContext<*, ApplicationCall>.id(block: suspend (Int) -> Unit) = - call.parameters["id"]!!.toIntOrNull()?.let { - block(it) - } ?: call.respond(HttpStatusCode.BadRequest) - - post { - announcementService.new(call.receive()) - } - - post("/{id}/archive") { - id { - val archivedAt = call.receiveNullable()?.archivedAt - announcementService.archive(it, archivedAt) - } - } - - post("/{id}/unarchive") { - id { - announcementService.unarchive(it) - } - } - - patch("/{id}") { - id { - announcementService.update(it, call.receive()) - } - } - - delete("/{id}") { - id { - announcementService.delete(it) - } - } - } - } - - route("/patches") { - route("latest") { - get { - val patchesRelease = - backend.getRelease(configuration.organization, configuration.patchesRepository) - val integrationsReleases = configuration.integrationsRepositoryNames.map { - async { backend.getRelease(configuration.organization, it) } - }.awaitAll() - - val assets = (patchesRelease.assets + integrationsReleases.flatMap { it.assets }) - .map { APIAsset(it.downloadUrl) } - .filter { it.type != APIAsset.Type.UNKNOWN } - .toSet() - - val apiRelease = APIRelease( - patchesRelease.tag, - patchesRelease.createdAt, - patchesRelease.releaseNote, - assets, - ) - - call.respond(apiRelease) - } - - get("/version") { - val patchesRelease = - backend.getRelease(configuration.organization, configuration.patchesRepository) - - val apiPatchesRelease = APIReleaseVersion(patchesRelease.tag) - - call.respond(apiPatchesRelease) - } - - val patchesListCache = Caffeine - .newBuilder() - .maximumSize(1) - .build() - - get("/list") { - val patchesRelease = - backend.getRelease(configuration.organization, configuration.patchesRepository) - - val patchesListByteArray = patchesListCache.getIfPresent(patchesRelease.tag) ?: run { - val downloadUrl = patchesRelease.assets - .map { APIAsset(it.downloadUrl) } - .find { it.type == APIAsset.Type.PATCHES } - ?.downloadUrl - - val patches = kotlin.io.path.createTempFile().toFile().apply { - outputStream().use { URL(downloadUrl).openStream().copyTo(it) } - }.let { file -> - PatchBundleLoader.Jar(file).also { file.delete() } - } - - ByteArrayOutputStream().use { stream -> - PatchUtils.Json.serialize(patches, outputStream = stream) - - stream.toByteArray() - }.also { - patchesListCache.put(patchesRelease.tag, it) - } - } - - call.respondBytes(ContentType.Application.Json) { patchesListByteArray } - } - } - } - - staticResources("/", "/static/api") { - contentType { ContentType.Application.Json } - extensions("json") - } - - get("/contributors") { - val contributors = - configuration.contributorsRepositoryNames.map { - async { - APIContributable( - it, - backend.getContributors(configuration.organization, it).map { - APIContributor(it.name, it.avatarUrl, it.url, it.contributions) - }.toSet(), - ) - } - }.awaitAll() - - call.respond(contributors) - } - - get("/team") { - val team = - backend.getMembers(configuration.organization).map { - APIMember(it.name, it.avatarUrl, it.url, it.gpgKeysUrl) - } - - call.respond(team) - } - - route("/ping") { - handle { - call.respond(HttpStatusCode.NoContent) - } - } - - authenticate("basic") { - get("/token") { - call.respond(authService.newToken()) - } - } - } - } -} diff --git a/src/main/kotlin/app/revanced/api/modules/Serialization.kt b/src/main/kotlin/app/revanced/api/modules/Serialization.kt deleted file mode 100644 index ef38558..0000000 --- a/src/main/kotlin/app/revanced/api/modules/Serialization.kt +++ /dev/null @@ -1,11 +0,0 @@ -package app.revanced.api.modules - -import io.ktor.serialization.kotlinx.json.* -import io.ktor.server.application.* -import io.ktor.server.plugins.contentnegotiation.* - -fun Application.configureSerialization() { - install(ContentNegotiation) { - json() - } -} diff --git a/src/main/kotlin/app/revanced/api/modules/Database.kt b/src/main/kotlin/app/revanced/api/repository/AnnouncementRepository.kt similarity index 52% rename from src/main/kotlin/app/revanced/api/modules/Database.kt rename to src/main/kotlin/app/revanced/api/repository/AnnouncementRepository.kt index 4727a0b..a6c4b63 100644 --- a/src/main/kotlin/app/revanced/api/modules/Database.kt +++ b/src/main/kotlin/app/revanced/api/repository/AnnouncementRepository.kt @@ -1,6 +1,6 @@ -package app.revanced.api.modules +package app.revanced.api.repository -import app.revanced.api.modules.AnnouncementService.Attachments.announcement +import app.revanced.api.repository.AnnouncementRepository.AttachmentTable.announcement import app.revanced.api.schema.APIAnnouncement import app.revanced.api.schema.APILatestAnnouncement import app.revanced.api.schema.APIResponseAnnouncement @@ -10,96 +10,54 @@ import org.jetbrains.exposed.dao.IntEntityClass import org.jetbrains.exposed.dao.id.EntityID import org.jetbrains.exposed.dao.id.IntIdTable import org.jetbrains.exposed.sql.* -import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq import org.jetbrains.exposed.sql.kotlin.datetime.datetime import org.jetbrains.exposed.sql.transactions.transaction -class AnnouncementService(private val database: Database) { - private object Announcements : IntIdTable() { - val author = varchar("author", 32).nullable() - val title = varchar("title", 64) - val content = text("content").nullable() - val channel = varchar("channel", 16).nullable() - val createdAt = datetime("createdAt") - val archivedAt = datetime("archivedAt").nullable() - val level = integer("level") - } - - private object Attachments : IntIdTable() { - val url = varchar("url", 256) - val announcement = reference("announcement", Announcements, onDelete = ReferenceOption.CASCADE) - } - - class Announcement(id: EntityID) : IntEntity(id) { - companion object : IntEntityClass(Announcements) - - var author by Announcements.author - var title by Announcements.title - var content by Announcements.content - val attachments by Attachment referrersOn announcement - var channel by Announcements.channel - var createdAt by Announcements.createdAt - var archivedAt by Announcements.archivedAt - var level by Announcements.level - - fun api() = APIResponseAnnouncement( - id.value, - author, - title, - content, - attachments.map(Attachment::url).toSet(), - channel, - createdAt, - archivedAt, - level, - ) - } - - class Attachment(id: EntityID) : IntEntity(id) { - companion object : IntEntityClass(Attachments) - - var url by Attachments.url - var announcement by Announcement referencedOn Attachments.announcement - } - +internal class AnnouncementRepository(private val database: Database) { init { transaction { - SchemaUtils.create(Announcements, Attachments) + SchemaUtils.create(AnnouncementTable, AttachmentTable) } } - private fun transaction(block: Transaction.() -> T) = transaction(database, block) - - fun read() = transaction { - Announcement.all().map { it.api() }.toSet() + fun all() = transaction { + buildSet { + AnnouncementEntity.all().forEach { announcement -> + add(announcement.toApi()) + } + } } - fun read(channel: String) = transaction { - Announcement.find { Announcements.channel eq channel }.map { it.api() }.toSet() + fun all(channel: String) = transaction { + buildSet { + AnnouncementEntity.find { AnnouncementTable.channel eq channel }.forEach { announcement -> + add(announcement.toApi()) + } + } } fun delete(id: Int) = transaction { - val announcement = Announcement.findById(id) ?: return@transaction + val announcement = AnnouncementEntity.findById(id) ?: return@transaction announcement.delete() } fun latest() = transaction { - Announcement.all().maxByOrNull { it.createdAt }?.api() + AnnouncementEntity.all().maxByOrNull { it.createdAt }?.toApi() } fun latest(channel: String) = transaction { - Announcement.find { Announcements.channel eq channel }.maxByOrNull { it.createdAt }?.api() + AnnouncementEntity.find { AnnouncementTable.channel eq channel }.maxByOrNull { it.createdAt }?.toApi() } fun latestId() = transaction { - Announcement.all().maxByOrNull { it.createdAt }?.id?.value?.let { + AnnouncementEntity.all().maxByOrNull { it.createdAt }?.id?.value?.let { APILatestAnnouncement(it) } } fun latestId(channel: String) = transaction { - Announcement.find { Announcements.channel eq channel }.maxByOrNull { it.createdAt }?.id?.value?.let { + AnnouncementEntity.find { AnnouncementTable.channel eq channel }.maxByOrNull { it.createdAt }?.id?.value?.let { APILatestAnnouncement(it) } } @@ -108,19 +66,19 @@ class AnnouncementService(private val database: Database) { id: Int, archivedAt: LocalDateTime?, ) = transaction { - Announcement.findById(id)?.apply { + AnnouncementEntity.findById(id)?.apply { this.archivedAt = archivedAt ?: java.time.LocalDateTime.now().toKotlinLocalDateTime() } } fun unarchive(id: Int) = transaction { - Announcement.findById(id)?.apply { + AnnouncementEntity.findById(id)?.apply { archivedAt = null } } fun new(new: APIAnnouncement) = transaction { - Announcement.new announcement@{ + AnnouncementEntity.new announcement@{ author = new.author title = new.title content = new.content @@ -130,7 +88,7 @@ class AnnouncementService(private val database: Database) { level = new.level }.also { newAnnouncement -> new.attachmentUrls.map { - Attachment.new { + AttachmentEntity.new { url = it announcement = newAnnouncement } @@ -139,7 +97,7 @@ class AnnouncementService(private val database: Database) { } fun update(id: Int, new: APIAnnouncement) = transaction { - Announcement.findById(id)?.apply { + AnnouncementEntity.findById(id)?.apply { author = new.author title = new.title content = new.content @@ -147,13 +105,66 @@ class AnnouncementService(private val database: Database) { archivedAt = new.archivedAt level = new.level - attachments.forEach(Attachment::delete) + attachments.forEach(AttachmentEntity::delete) new.attachmentUrls.map { - Attachment.new { + AttachmentEntity.new { url = it announcement = this@apply } } } } + + private fun transaction(block: Transaction.() -> T) = transaction(database, block) + + private object AnnouncementTable : IntIdTable() { + val author = varchar("author", 32).nullable() + val title = varchar("title", 64) + val content = text("content").nullable() + val channel = varchar("channel", 16).nullable() + val createdAt = datetime("createdAt") + val archivedAt = datetime("archivedAt").nullable() + val level = integer("level") + } + + private object AttachmentTable : IntIdTable() { + val url = varchar("url", 256) + val announcement = reference("announcement", AnnouncementTable, onDelete = ReferenceOption.CASCADE) + } + + class AnnouncementEntity(id: EntityID) : IntEntity(id) { + companion object : IntEntityClass(AnnouncementTable) + + var author by AnnouncementTable.author + var title by AnnouncementTable.title + var content by AnnouncementTable.content + val attachments by AttachmentEntity referrersOn announcement + var channel by AnnouncementTable.channel + var createdAt by AnnouncementTable.createdAt + var archivedAt by AnnouncementTable.archivedAt + var level by AnnouncementTable.level + + fun toApi() = APIResponseAnnouncement( + id.value, + author, + title, + content, + attachmentUrls = buildSet { + attachments.forEach { + add(it.url) + } + }, + channel, + createdAt, + archivedAt, + level, + ) + } + + class AttachmentEntity(id: EntityID) : IntEntity(id) { + companion object : IntEntityClass(AttachmentTable) + + var url by AttachmentTable.url + var announcement by AnnouncementEntity referencedOn AttachmentTable.announcement + } } diff --git a/src/main/kotlin/app/revanced/api/schema/ConfigurationSchema.kt b/src/main/kotlin/app/revanced/api/repository/ConfigurationRepository.kt similarity index 85% rename from src/main/kotlin/app/revanced/api/schema/ConfigurationSchema.kt rename to src/main/kotlin/app/revanced/api/repository/ConfigurationRepository.kt index 5eff0b6..531de56 100644 --- a/src/main/kotlin/app/revanced/api/schema/ConfigurationSchema.kt +++ b/src/main/kotlin/app/revanced/api/repository/ConfigurationRepository.kt @@ -1,10 +1,10 @@ -package app.revanced.api.schema +package app.revanced.api.repository import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable -class APIConfiguration( +internal class ConfigurationRepository( val organization: String, @SerialName("patches-repository") val patchesRepository: String, diff --git a/src/main/kotlin/app/revanced/api/backend/Backend.kt b/src/main/kotlin/app/revanced/api/repository/backend/BackendRepository.kt similarity index 91% rename from src/main/kotlin/app/revanced/api/backend/Backend.kt rename to src/main/kotlin/app/revanced/api/repository/backend/BackendRepository.kt index c3e53c1..47ec9d8 100644 --- a/src/main/kotlin/app/revanced/api/backend/Backend.kt +++ b/src/main/kotlin/app/revanced/api/repository/backend/BackendRepository.kt @@ -1,4 +1,4 @@ -package app.revanced.api.backend +package app.revanced.api.repository.backend import io.ktor.client.* import io.ktor.client.engine.okhttp.* @@ -10,7 +10,7 @@ import kotlinx.serialization.Serializable * * @param httpClientConfig The configuration of the HTTP client. */ -abstract class Backend( +abstract class BackendRepository internal constructor( httpClientConfig: HttpClientConfig.() -> Unit = {}, ) { protected val client: HttpClient = HttpClient(OkHttp, httpClientConfig) @@ -114,7 +114,7 @@ abstract class Backend( * @param tag The tag of the release. If null, the latest release is returned. * @return The release. */ - abstract suspend fun getRelease( + abstract suspend fun release( owner: String, repository: String, tag: String? = null, @@ -127,7 +127,7 @@ abstract class Backend( * @param repository The name of the repository. * @return The contributors. */ - abstract suspend fun getContributors(owner: String, repository: String): Set + abstract suspend fun contributors(owner: String, repository: String): Set /** * Get the members of an organization. @@ -135,5 +135,5 @@ abstract class Backend( * @param organization The name of the organization. * @return The members. */ - abstract suspend fun getMembers(organization: String): Set + abstract suspend fun members(organization: String): Set } diff --git a/src/main/kotlin/app/revanced/api/backend/github/GitHubBackend.kt b/src/main/kotlin/app/revanced/api/repository/backend/github/GitHubBackendRepository.kt similarity index 67% rename from src/main/kotlin/app/revanced/api/backend/github/GitHubBackend.kt rename to src/main/kotlin/app/revanced/api/repository/backend/github/GitHubBackendRepository.kt index be0db97..5687f72 100644 --- a/src/main/kotlin/app/revanced/api/backend/github/GitHubBackend.kt +++ b/src/main/kotlin/app/revanced/api/repository/backend/github/GitHubBackendRepository.kt @@ -1,18 +1,18 @@ -package app.revanced.api.backend.github +package app.revanced.api.repository.backend.github -import app.revanced.api.backend.Backend -import app.revanced.api.backend.Backend.BackendOrganization.BackendMember -import app.revanced.api.backend.Backend.BackendOrganization.BackendRepository.BackendContributor -import app.revanced.api.backend.Backend.BackendOrganization.BackendRepository.BackendRelease -import app.revanced.api.backend.Backend.BackendOrganization.BackendRepository.BackendRelease.BackendAsset -import app.revanced.api.backend.github.api.Request -import app.revanced.api.backend.github.api.Request.Organization.Members -import app.revanced.api.backend.github.api.Request.Organization.Repository.Contributors -import app.revanced.api.backend.github.api.Request.Organization.Repository.Releases -import app.revanced.api.backend.github.api.Response -import app.revanced.api.backend.github.api.Response.GitHubOrganization.GitHubMember -import app.revanced.api.backend.github.api.Response.GitHubOrganization.GitHubRepository.GitHubContributor -import app.revanced.api.backend.github.api.Response.GitHubOrganization.GitHubRepository.GitHubRelease +import app.revanced.api.repository.backend.BackendRepository +import app.revanced.api.repository.backend.BackendRepository.BackendOrganization.BackendMember +import app.revanced.api.repository.backend.BackendRepository.BackendOrganization.BackendRepository.BackendContributor +import app.revanced.api.repository.backend.BackendRepository.BackendOrganization.BackendRepository.BackendRelease +import app.revanced.api.repository.backend.BackendRepository.BackendOrganization.BackendRepository.BackendRelease.BackendAsset +import app.revanced.api.repository.backend.github.api.Request +import app.revanced.api.repository.backend.github.api.Request.Organization.Members +import app.revanced.api.repository.backend.github.api.Request.Organization.Repository.Contributors +import app.revanced.api.repository.backend.github.api.Request.Organization.Repository.Releases +import app.revanced.api.repository.backend.github.api.Response +import app.revanced.api.repository.backend.github.api.Response.GitHubOrganization.GitHubMember +import app.revanced.api.repository.backend.github.api.Response.GitHubOrganization.GitHubRepository.GitHubContributor +import app.revanced.api.repository.backend.github.api.Response.GitHubOrganization.GitHubRepository.GitHubRelease import io.ktor.client.call.* import io.ktor.client.plugins.* import io.ktor.client.plugins.auth.* @@ -30,7 +30,7 @@ import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonNamingStrategy @OptIn(ExperimentalSerializationApi::class) -class GitHubBackend(token: String? = null) : Backend({ +class GitHubBackendRepository(token: String? = null) : BackendRepository({ install(HttpCache) install(Resources) install(ContentNegotiation) { @@ -59,7 +59,7 @@ class GitHubBackend(token: String? = null) : Backend({ } } }) { - override suspend fun getRelease( + override suspend fun release( owner: String, repository: String, tag: String?, @@ -80,7 +80,7 @@ class GitHubBackend(token: String? = null) : Backend({ ) } - override suspend fun getContributors( + override suspend fun contributors( owner: String, repository: String, ): Set { @@ -96,7 +96,7 @@ class GitHubBackend(token: String? = null) : Backend({ }.toSet() } - override suspend fun getMembers(organization: String): Set { + override suspend fun members(organization: String): Set { // Get the list of members of the organization. val members: Set = client.get(Members(organization)).body() diff --git a/src/main/kotlin/app/revanced/api/backend/github/api/RequestResource.kt b/src/main/kotlin/app/revanced/api/repository/backend/github/api/Request.kt similarity index 93% rename from src/main/kotlin/app/revanced/api/backend/github/api/RequestResource.kt rename to src/main/kotlin/app/revanced/api/repository/backend/github/api/Request.kt index dc480ba..557f9e2 100644 --- a/src/main/kotlin/app/revanced/api/backend/github/api/RequestResource.kt +++ b/src/main/kotlin/app/revanced/api/repository/backend/github/api/Request.kt @@ -1,4 +1,4 @@ -package app.revanced.api.backend.github.api +package app.revanced.api.repository.backend.github.api import io.ktor.resources.* diff --git a/src/main/kotlin/app/revanced/api/backend/github/api/ResponseSchema.kt b/src/main/kotlin/app/revanced/api/repository/backend/github/api/Response.kt similarity index 96% rename from src/main/kotlin/app/revanced/api/backend/github/api/ResponseSchema.kt rename to src/main/kotlin/app/revanced/api/repository/backend/github/api/Response.kt index 693a897..2ddc8f1 100644 --- a/src/main/kotlin/app/revanced/api/backend/github/api/ResponseSchema.kt +++ b/src/main/kotlin/app/revanced/api/repository/backend/github/api/Response.kt @@ -1,4 +1,4 @@ -package app.revanced.api.backend.github.api +package app.revanced.api.repository.backend.github.api import kotlinx.datetime.Instant import kotlinx.serialization.Serializable diff --git a/src/main/kotlin/app/revanced/api/services/AnnouncementService.kt b/src/main/kotlin/app/revanced/api/services/AnnouncementService.kt new file mode 100644 index 0000000..63418da --- /dev/null +++ b/src/main/kotlin/app/revanced/api/services/AnnouncementService.kt @@ -0,0 +1,35 @@ +package app.revanced.api.services + +import app.revanced.api.repository.AnnouncementRepository +import app.revanced.api.schema.APIAnnouncement +import app.revanced.api.schema.APILatestAnnouncement +import kotlinx.datetime.LocalDateTime + +internal class AnnouncementService( + private val announcementRepository: AnnouncementRepository, +) { + fun latestId(channel: String): APILatestAnnouncement? = announcementRepository.latestId(channel) + fun latestId(): APILatestAnnouncement? = announcementRepository.latestId() + + fun latest(channel: String) = announcementRepository.latest(channel) + fun latest() = announcementRepository.latest() + + fun all(channel: String) = announcementRepository.all(channel) + fun all() = announcementRepository.all() + + fun new(new: APIAnnouncement) { + announcementRepository.new(new) + } + fun archive(id: Int, archivedAt: LocalDateTime?) { + announcementRepository.archive(id, archivedAt) + } + fun unarchive(id: Int) { + announcementRepository.unarchive(id) + } + fun update(id: Int, new: APIAnnouncement) { + announcementRepository.update(id, new) + } + fun delete(id: Int) { + announcementRepository.delete(id) + } +} diff --git a/src/main/kotlin/app/revanced/api/services/ApiService.kt b/src/main/kotlin/app/revanced/api/services/ApiService.kt new file mode 100644 index 0000000..d36e64b --- /dev/null +++ b/src/main/kotlin/app/revanced/api/services/ApiService.kt @@ -0,0 +1,33 @@ +package app.revanced.api.services + +import app.revanced.api.repository.ConfigurationRepository +import app.revanced.api.repository.backend.BackendRepository +import app.revanced.api.schema.APIContributable +import app.revanced.api.schema.APIContributor +import app.revanced.api.schema.APIMember +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.withContext + +internal class ApiService( + private val backendRepository: BackendRepository, + private val configurationRepository: ConfigurationRepository, +) { + suspend fun contributors() = withContext(Dispatchers.IO) { + configurationRepository.contributorsRepositoryNames.map { + async { + APIContributable( + it, + backendRepository.contributors(configurationRepository.organization, it).map { + APIContributor(it.name, it.avatarUrl, it.url, it.contributions) + }.toSet(), + ) + } + } + }.awaitAll() + + suspend fun team() = backendRepository.members(configurationRepository.organization).map { + APIMember(it.name, it.avatarUrl, it.url, it.gpgKeysUrl) + } +} diff --git a/src/main/kotlin/app/revanced/api/modules/Security.kt b/src/main/kotlin/app/revanced/api/services/AuthService.kt similarity index 87% rename from src/main/kotlin/app/revanced/api/modules/Security.kt rename to src/main/kotlin/app/revanced/api/services/AuthService.kt index 02cc5f3..af824a0 100644 --- a/src/main/kotlin/app/revanced/api/modules/Security.kt +++ b/src/main/kotlin/app/revanced/api/services/AuthService.kt @@ -1,15 +1,14 @@ -package app.revanced.api.modules +package app.revanced.api.services import com.auth0.jwt.JWT import com.auth0.jwt.algorithms.Algorithm import io.ktor.server.application.* import io.ktor.server.auth.* import io.ktor.server.auth.jwt.* -import org.koin.ktor.ext.get import java.util.* import kotlin.time.Duration.Companion.minutes -class AuthService( +internal class AuthService( private val issuer: String, private val validityInMin: Int, private val jwtSecret: String, @@ -46,8 +45,3 @@ class AuthService( .sign(Algorithm.HMAC256(jwtSecret)) } } - -fun Application.configureSecurity() { - val configureSecurity = get().configureSecurity - configureSecurity() -} diff --git a/src/main/kotlin/app/revanced/api/services/PatchesService.kt b/src/main/kotlin/app/revanced/api/services/PatchesService.kt new file mode 100644 index 0000000..22afd70 --- /dev/null +++ b/src/main/kotlin/app/revanced/api/services/PatchesService.kt @@ -0,0 +1,87 @@ +package app.revanced.api.services + +import app.revanced.api.repository.ConfigurationRepository +import app.revanced.api.repository.backend.BackendRepository +import app.revanced.api.schema.APIAsset +import app.revanced.api.schema.APIRelease +import app.revanced.api.schema.APIReleaseVersion +import app.revanced.library.PatchUtils +import app.revanced.patcher.PatchBundleLoader +import com.github.benmanes.caffeine.cache.Caffeine +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.withContext +import java.io.ByteArrayOutputStream +import java.net.URL + +internal class PatchesService( + private val backendRepository: BackendRepository, + private val configurationRepository: ConfigurationRepository, +) { + private val patchesListCache = Caffeine + .newBuilder() + .maximumSize(1) + .build() + + suspend fun latestRelease(): APIRelease { + val patchesRelease = backendRepository.release( + configurationRepository.organization, + configurationRepository.patchesRepository, + ) + val integrationsReleases = withContext(Dispatchers.Default) { + configurationRepository.integrationsRepositoryNames.map { + async { backendRepository.release(configurationRepository.organization, it) } + } + }.awaitAll() + + val assets = (patchesRelease.assets + integrationsReleases.flatMap { it.assets }) + .map { APIAsset(it.downloadUrl) } + .filter { it.type != APIAsset.Type.UNKNOWN } + .toSet() + + return APIRelease( + patchesRelease.tag, + patchesRelease.createdAt, + patchesRelease.releaseNote, + assets, + ) + } + + suspend fun latestVersion(): APIReleaseVersion { + val patchesRelease = backendRepository.release( + configurationRepository.organization, + configurationRepository.patchesRepository, + ) + + return APIReleaseVersion(patchesRelease.tag) + } + + suspend fun list(): ByteArray { + val patchesRelease = backendRepository.release( + configurationRepository.organization, + configurationRepository.patchesRepository, + ) + + return patchesListCache.getIfPresent(patchesRelease.tag) ?: run { + val downloadUrl = patchesRelease.assets + .map { APIAsset(it.downloadUrl) } + .find { it.type == APIAsset.Type.PATCHES } + ?.downloadUrl + + val patches = kotlin.io.path.createTempFile().toFile().apply { + outputStream().use { URL(downloadUrl).openStream().copyTo(it) } + }.let { file -> + PatchBundleLoader.Jar(file).also { file.delete() } + } + + ByteArrayOutputStream().use { stream -> + PatchUtils.Json.serialize(patches, outputStream = stream) + + stream.toByteArray() + }.also { + patchesListCache.put(patchesRelease.tag, it) + } + } + } +} diff --git a/src/main/resources/static/api/about.json b/src/main/resources/static/api/about.json index 1a0d1cb..a947c48 100644 --- a/src/main/resources/static/api/about.json +++ b/src/main/resources/static/api/about.json @@ -80,4 +80,4 @@ } ] } -} \ No newline at end of file +} diff --git a/src/test/kotlin/app/revanced/ApplicationTest.kt b/src/test/kotlin/app/revanced/ApplicationTest.kt deleted file mode 100644 index 5f1ce2e..0000000 --- a/src/test/kotlin/app/revanced/ApplicationTest.kt +++ /dev/null @@ -1,57 +0,0 @@ -package app.revanced - -import app.revanced.api.modules.* -import app.revanced.api.schema.APIConfiguration -import com.akuleshov7.ktoml.Toml -import io.github.cdimascio.dotenv.Dotenv -import io.ktor.client.request.* -import io.ktor.client.statement.* -import io.ktor.http.* -import io.ktor.server.testing.* -import io.ktor.util.* -import io.mockk.every -import io.mockk.mockk -import kotlinx.serialization.encodeToString -import kotlin.test.* - -class ApplicationTest { - @Test - fun `successfully create a token`() = testApplication { - val apiConfigurationFile = kotlin.io.path.createTempFile().toFile().apply { - Toml.encodeToString( - APIConfiguration( - organization = "ReVanced", - patchesRepository = "", - integrationsRepositoryNames = setOf(), - contributorsRepositoryNames = setOf(), - ), - ).let(::writeText) - - deleteOnExit() - } - - val dotenv = mockk() - every { dotenv[any()] } returns "ReVanced" - every { dotenv["JWT_VALIDITY_IN_MIN"] } returns "5" - every { dotenv["CONFIG_FILE_PATH"] } returns apiConfigurationFile.absolutePath - - application { - configureDependencies() - configureHTTP() - configureSerialization() - configureSecurity() - configureRouting() - } - - val token = client.get("/v1/token") { - headers { - append( - HttpHeaders.Authorization, - "Basic ${"${dotenv["BASIC_USERNAME"]}:${dotenv["BASIC_PASSWORD"]}".encodeBase64()}", - ) - } - }.bodyAsText() - - assert(token.isNotEmpty()) - } -} From 39f54bbb32512a6df255bbec6821f7f7acf91340 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Wed, 5 Jun 2024 04:28:36 +0200 Subject: [PATCH 033/102] feat: Add proxy for old API --- .env.example | 2 + .../revanced/api/command/StartAPICommand.kt | 3 + .../api/configuration/Dependencies.kt | 74 +++++++++++++++++-- .../api/configuration/routing/Routing.kt | 16 ++-- .../routing/routes/Announcements.kt | 20 ++--- .../configuration/routing/routes/ApiRoute.kt | 12 +-- .../configuration/routing/routes/OldApi.kt | 16 ++++ .../routing/routes/PatchesRoute.kt | 6 +- .../revanced/api/repository/OldApiService.kt | 65 ++++++++++++++++ .../repository/backend/BackendRepository.kt | 7 +- .../backend/github/GitHubBackendRepository.kt | 42 +---------- .../revanced/api/static}/about.json | 0 .../{ => app/revanced/api}/static/robots.txt | 0 13 files changed, 188 insertions(+), 75 deletions(-) create mode 100644 src/main/kotlin/app/revanced/api/configuration/routing/routes/OldApi.kt create mode 100644 src/main/kotlin/app/revanced/api/repository/OldApiService.kt rename src/main/resources/{static/api => app/revanced/api/static}/about.json (100%) rename src/main/resources/{ => app/revanced/api}/static/robots.txt (100%) diff --git a/.env.example b/.env.example index e3a8ba0..78477d2 100644 --- a/.env.example +++ b/.env.example @@ -11,3 +11,5 @@ JWT_VALIDITY_IN_MIN= BASIC_USERNAME= BASIC_PASSWORD= + +OLD_API_URL= \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/api/command/StartAPICommand.kt b/src/main/kotlin/app/revanced/api/command/StartAPICommand.kt index 585730a..ba47344 100644 --- a/src/main/kotlin/app/revanced/api/command/StartAPICommand.kt +++ b/src/main/kotlin/app/revanced/api/command/StartAPICommand.kt @@ -2,8 +2,11 @@ package app.revanced.api.command import app.revanced.api.configuration.* import app.revanced.api.configuration.routing.configureRouting +import io.ktor.server.application.* import io.ktor.server.engine.* import io.ktor.server.netty.* +import io.ktor.server.response.* +import io.ktor.server.routing.* import picocli.CommandLine @CommandLine.Command( diff --git a/src/main/kotlin/app/revanced/api/configuration/Dependencies.kt b/src/main/kotlin/app/revanced/api/configuration/Dependencies.kt index dc8f620..8a60324 100644 --- a/src/main/kotlin/app/revanced/api/configuration/Dependencies.kt +++ b/src/main/kotlin/app/revanced/api/configuration/Dependencies.kt @@ -2,6 +2,7 @@ package app.revanced.api.configuration import app.revanced.api.repository.AnnouncementRepository import app.revanced.api.repository.ConfigurationRepository +import app.revanced.api.repository.OldApiService import app.revanced.api.repository.backend.BackendRepository import app.revanced.api.repository.backend.github.GitHubBackendRepository import app.revanced.api.services.AnnouncementService @@ -11,14 +12,27 @@ import app.revanced.api.services.PatchesService import com.akuleshov7.ktoml.Toml import com.akuleshov7.ktoml.source.decodeFromStream import io.github.cdimascio.dotenv.Dotenv +import io.ktor.client.* +import io.ktor.client.engine.okhttp.* +import io.ktor.client.plugins.* +import io.ktor.client.plugins.auth.* +import io.ktor.client.plugins.auth.providers.* +import io.ktor.client.plugins.cache.* +import io.ktor.client.plugins.contentnegotiation.* +import io.ktor.client.plugins.resources.* +import io.ktor.serialization.kotlinx.json.* import io.ktor.server.application.* +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonNamingStrategy import org.jetbrains.exposed.sql.Database import org.koin.core.module.dsl.singleOf -import org.koin.dsl.bind +import org.koin.core.parameter.parameterArrayOf import org.koin.dsl.module import org.koin.ktor.plugin.Koin import java.io.File +@OptIn(ExperimentalSerializationApi::class) fun Application.configureDependencies() { val globalModule = module { single { @@ -26,6 +40,16 @@ fun Application.configureDependencies() { .systemProperties() .load() } + factory { params -> + val defaultRequestUri: String = params.get() + val configBlock = params.getOrNull<(HttpClientConfig.() -> Unit)>() ?: {} + + HttpClient(OkHttp) { + defaultRequest { url(defaultRequestUri) } + + configBlock() + } + } } val repositoryModule = module { @@ -40,6 +64,43 @@ fun Application.configureDependencies() { ) } + single { + GitHubBackendRepository( + get { + val defaultRequestUri = "https://api.github.com" + val configBlock: HttpClientConfig.() -> Unit = { + install(HttpCache) + install(Resources) + install(ContentNegotiation) { + json( + Json { + ignoreUnknownKeys = true + namingStrategy = JsonNamingStrategy.SnakeCase + }, + ) + } + + get()["GITHUB_TOKEN"]?.let { + install(Auth) { + bearer { + loadTokens { + BearerTokens( + accessToken = it, + refreshToken = "", // Required dummy value + ) + } + + sendWithoutRequest { true } + } + } + } + } + + parameterArrayOf(defaultRequestUri, configBlock) + }, + ) + } + single { val configFilePath = get()["CONFIG_FILE_PATH"] val configFile = File(configFilePath).inputStream() @@ -64,10 +125,13 @@ fun Application.configureDependencies() { AuthService(issuer, validityInMin, jwtSecret, basicUsername, basicPassword) } single { - val token = get()["GITHUB_TOKEN"] - - GitHubBackendRepository(token) - } bind BackendRepository::class + OldApiService( + get { + val defaultRequestUri = get()["OLD_API_URL"] + parameterArrayOf(defaultRequestUri) + }, + ) + } singleOf(::AnnouncementService) singleOf(::PatchesService) singleOf(::ApiService) diff --git a/src/main/kotlin/app/revanced/api/configuration/routing/Routing.kt b/src/main/kotlin/app/revanced/api/configuration/routing/Routing.kt index 18420b6..8296ee3 100644 --- a/src/main/kotlin/app/revanced/api/configuration/routing/Routing.kt +++ b/src/main/kotlin/app/revanced/api/configuration/routing/Routing.kt @@ -1,8 +1,9 @@ package app.revanced.api.configuration.routing -import app.revanced.api.configuration.routing.routes.configureAnnouncementsRoute -import app.revanced.api.configuration.routing.routes.configurePatchesRoute -import app.revanced.api.configuration.routing.routes.configureRootRoute +import app.revanced.api.configuration.routing.routes.announcementsRoute +import app.revanced.api.configuration.routing.routes.oldApiRoute +import app.revanced.api.configuration.routing.routes.patchesRoute +import app.revanced.api.configuration.routing.routes.rootRoute import app.revanced.api.repository.ConfigurationRepository import io.ktor.server.application.* import io.ktor.server.routing.* @@ -12,8 +13,11 @@ internal fun Application.configureRouting() = routing { val configuration = get() route("/v${configuration.apiVersion}") { - configureRootRoute() - configurePatchesRoute() - configureAnnouncementsRoute() + rootRoute() + patchesRoute() + announcementsRoute() } + + // TODO: Remove, once migration period from v2 API is over (In 1-2 years). + oldApiRoute() } diff --git a/src/main/kotlin/app/revanced/api/configuration/routing/routes/Announcements.kt b/src/main/kotlin/app/revanced/api/configuration/routing/routes/Announcements.kt index 4f9f038..bc4859d 100644 --- a/src/main/kotlin/app/revanced/api/configuration/routing/routes/Announcements.kt +++ b/src/main/kotlin/app/revanced/api/configuration/routing/routes/Announcements.kt @@ -12,11 +12,11 @@ import io.ktor.server.routing.* import io.ktor.server.util.* import org.koin.ktor.ext.get as koinGet -internal fun Route.configureAnnouncementsRoute() = route("/announcements") { +internal fun Route.announcementsRoute() = route("announcements") { val announcementService = koinGet() - route("/{channel}/latest") { - get("/id") { + route("{channel}/latest") { + get("id") { val channel: String by call.parameters call.respond( @@ -33,14 +33,14 @@ internal fun Route.configureAnnouncementsRoute() = route("/announcements") { } } - get("/{channel}") { + get("{channel}") { val channel: String by call.parameters call.respond(announcementService.all(channel)) } - route("/latest") { - get("/id") { + route("latest") { + get("id") { call.respond(announcementService.latestId() ?: return@get call.respond(HttpStatusCode.NotFound)) } @@ -58,26 +58,26 @@ internal fun Route.configureAnnouncementsRoute() = route("/announcements") { announcementService.new(call.receive()) } - post("/{id}/archive") { + post("{id}/archive") { val id: Int by call.parameters val archivedAt = call.receiveNullable()?.archivedAt announcementService.archive(id, archivedAt) } - post("/{id}/unarchive") { + post("{id}/unarchive") { val id: Int by call.parameters announcementService.unarchive(id) } - patch("/{id}") { + patch("{id}") { val id: Int by call.parameters announcementService.update(id, call.receive()) } - delete("/{id}") { + delete("{id}") { val id: Int by call.parameters announcementService.delete(id) diff --git a/src/main/kotlin/app/revanced/api/configuration/routing/routes/ApiRoute.kt b/src/main/kotlin/app/revanced/api/configuration/routing/routes/ApiRoute.kt index b502ac8..56eb553 100644 --- a/src/main/kotlin/app/revanced/api/configuration/routing/routes/ApiRoute.kt +++ b/src/main/kotlin/app/revanced/api/configuration/routing/routes/ApiRoute.kt @@ -10,31 +10,31 @@ import io.ktor.server.response.* import io.ktor.server.routing.* import org.koin.ktor.ext.get -internal fun Route.configureRootRoute() { +internal fun Route.rootRoute() { val apiService = get() val authService = get() - get("/contributors") { + get("contributors") { call.respond(apiService.contributors()) } - get("/team") { + get("team") { call.respond(apiService.team()) } - route("/ping") { + route("ping") { handle { call.respond(HttpStatusCode.NoContent) } } authenticate("basic") { - get("/token") { + get("token") { call.respond(authService.newToken()) } } - staticResources("/", "/static/api") { + staticResources("/", "/app/revanced/api/static") { contentType { ContentType.Application.Json } extensions("json") } diff --git a/src/main/kotlin/app/revanced/api/configuration/routing/routes/OldApi.kt b/src/main/kotlin/app/revanced/api/configuration/routing/routes/OldApi.kt new file mode 100644 index 0000000..64f8006 --- /dev/null +++ b/src/main/kotlin/app/revanced/api/configuration/routing/routes/OldApi.kt @@ -0,0 +1,16 @@ +package app.revanced.api.configuration.routing.routes + +import app.revanced.api.repository.OldApiService +import io.ktor.server.application.* +import io.ktor.server.routing.* +import org.koin.ktor.ext.get + +internal fun Route.oldApiRoute() { + val oldApiService = get() + + route(Regex("(v2|tools|contributor).*")) { + handle { + oldApiService.proxy(call) + } + } +} diff --git a/src/main/kotlin/app/revanced/api/configuration/routing/routes/PatchesRoute.kt b/src/main/kotlin/app/revanced/api/configuration/routing/routes/PatchesRoute.kt index 21e811e..4ddfd65 100644 --- a/src/main/kotlin/app/revanced/api/configuration/routing/routes/PatchesRoute.kt +++ b/src/main/kotlin/app/revanced/api/configuration/routing/routes/PatchesRoute.kt @@ -7,7 +7,7 @@ import io.ktor.server.response.* import io.ktor.server.routing.* import org.koin.ktor.ext.get as koinGet -internal fun Route.configurePatchesRoute() = route("/patches") { +internal fun Route.patchesRoute() = route("patches") { val patchesService = koinGet() route("latest") { @@ -15,11 +15,11 @@ internal fun Route.configurePatchesRoute() = route("/patches") { call.respond(patchesService.latestRelease()) } - get("/version") { + get("version") { call.respond(patchesService.latestVersion()) } - get("/list") { + get("list") { call.respondBytes(ContentType.Application.Json) { patchesService.list() } } } diff --git a/src/main/kotlin/app/revanced/api/repository/OldApiService.kt b/src/main/kotlin/app/revanced/api/repository/OldApiService.kt new file mode 100644 index 0000000..4c5612d --- /dev/null +++ b/src/main/kotlin/app/revanced/api/repository/OldApiService.kt @@ -0,0 +1,65 @@ +package app.revanced.api.repository + +import io.ktor.client.* +import io.ktor.client.request.* +import io.ktor.client.statement.* +import io.ktor.http.* +import io.ktor.http.content.* +import io.ktor.server.application.* +import io.ktor.server.request.* +import io.ktor.server.response.* +import io.ktor.util.* +import io.ktor.utils.io.* + +internal class OldApiService(private val client: HttpClient) { + @OptIn(InternalAPI::class) + suspend fun proxy(call: ApplicationCall) { + val channel = call.request.receiveChannel() + val size = channel.availableForRead + val byteArray = ByteArray(size) + channel.readFully(byteArray) + + val response: HttpResponse = client.request(call.request.uri) { + method = call.request.httpMethod + + headers { + appendAll( + call.request.headers.filter { key, _ -> + !key.equals( + HttpHeaders.ContentType, + ignoreCase = true, + ) && !key.equals( + HttpHeaders.ContentLength, + ignoreCase = true, + ) && !key.equals(HttpHeaders.Host, ignoreCase = true) + }, + ) + } + if (call.request.httpMethod == HttpMethod.Post) { + body = ByteArrayContent(byteArray, call.request.contentType()) + } + } + + val headers = response.headers + + call.respond(object : OutgoingContent.WriteChannelContent() { + override val contentLength: Long? = headers[HttpHeaders.ContentLength]?.toLong() + override val contentType = headers[HttpHeaders.ContentType]?.let { ContentType.parse(it) } + override val headers: Headers = Headers.build { + appendAll( + headers.filter { key, _ -> + !key.equals( + HttpHeaders.ContentType, + ignoreCase = true, + ) && !key.equals(HttpHeaders.ContentLength, ignoreCase = true) + }, + ) + } + override val status = response.status + + override suspend fun writeTo(channel: ByteWriteChannel) { + response.content.copyAndClose(channel) + } + }) + } +} diff --git a/src/main/kotlin/app/revanced/api/repository/backend/BackendRepository.kt b/src/main/kotlin/app/revanced/api/repository/backend/BackendRepository.kt index 47ec9d8..f27012d 100644 --- a/src/main/kotlin/app/revanced/api/repository/backend/BackendRepository.kt +++ b/src/main/kotlin/app/revanced/api/repository/backend/BackendRepository.kt @@ -1,20 +1,17 @@ package app.revanced.api.repository.backend import io.ktor.client.* -import io.ktor.client.engine.okhttp.* import kotlinx.datetime.LocalDateTime import kotlinx.serialization.Serializable /** * The backend of the application used to get data for the API. * - * @param httpClientConfig The configuration of the HTTP client. + * @param client The HTTP client to use for requests. */ abstract class BackendRepository internal constructor( - httpClientConfig: HttpClientConfig.() -> Unit = {}, + protected val client: HttpClient, ) { - protected val client: HttpClient = HttpClient(OkHttp, httpClientConfig) - /** * A user. * diff --git a/src/main/kotlin/app/revanced/api/repository/backend/github/GitHubBackendRepository.kt b/src/main/kotlin/app/revanced/api/repository/backend/github/GitHubBackendRepository.kt index 5687f72..1664adf 100644 --- a/src/main/kotlin/app/revanced/api/repository/backend/github/GitHubBackendRepository.kt +++ b/src/main/kotlin/app/revanced/api/repository/backend/github/GitHubBackendRepository.kt @@ -13,52 +13,14 @@ import app.revanced.api.repository.backend.github.api.Response import app.revanced.api.repository.backend.github.api.Response.GitHubOrganization.GitHubMember import app.revanced.api.repository.backend.github.api.Response.GitHubOrganization.GitHubRepository.GitHubContributor import app.revanced.api.repository.backend.github.api.Response.GitHubOrganization.GitHubRepository.GitHubRelease +import io.ktor.client.* import io.ktor.client.call.* -import io.ktor.client.plugins.* -import io.ktor.client.plugins.auth.* -import io.ktor.client.plugins.auth.providers.* -import io.ktor.client.plugins.cache.* -import io.ktor.client.plugins.contentnegotiation.* import io.ktor.client.plugins.resources.* -import io.ktor.client.plugins.resources.Resources -import io.ktor.serialization.kotlinx.json.* import kotlinx.coroutines.* import kotlinx.datetime.TimeZone import kotlinx.datetime.toLocalDateTime -import kotlinx.serialization.ExperimentalSerializationApi -import kotlinx.serialization.json.Json -import kotlinx.serialization.json.JsonNamingStrategy -@OptIn(ExperimentalSerializationApi::class) -class GitHubBackendRepository(token: String? = null) : BackendRepository({ - install(HttpCache) - install(Resources) - install(ContentNegotiation) { - json( - Json { - ignoreUnknownKeys = true - namingStrategy = JsonNamingStrategy.SnakeCase - }, - ) - } - - defaultRequest { url("https://api.github.com") } - - token?.let { - install(Auth) { - bearer { - loadTokens { - BearerTokens( - accessToken = it, - refreshToken = "", // Required dummy value - ) - } - - sendWithoutRequest { true } - } - } - } -}) { +class GitHubBackendRepository(client: HttpClient) : BackendRepository(client) { override suspend fun release( owner: String, repository: String, diff --git a/src/main/resources/static/api/about.json b/src/main/resources/app/revanced/api/static/about.json similarity index 100% rename from src/main/resources/static/api/about.json rename to src/main/resources/app/revanced/api/static/about.json diff --git a/src/main/resources/static/robots.txt b/src/main/resources/app/revanced/api/static/robots.txt similarity index 100% rename from src/main/resources/static/robots.txt rename to src/main/resources/app/revanced/api/static/robots.txt From 09043904818aa37e151cce4a64b17805933ad04c Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Wed, 5 Jun 2024 04:32:27 +0200 Subject: [PATCH 034/102] refactor: Lint & code cleanup --- .../app/revanced/api/command/StartAPICommand.kt | 8 ++++---- .../app/revanced/api/repository/OldApiService.kt | 13 ++++++++----- .../kotlin/app/revanced/api/services/AuthService.kt | 10 ++++------ 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/src/main/kotlin/app/revanced/api/command/StartAPICommand.kt b/src/main/kotlin/app/revanced/api/command/StartAPICommand.kt index ba47344..88e6945 100644 --- a/src/main/kotlin/app/revanced/api/command/StartAPICommand.kt +++ b/src/main/kotlin/app/revanced/api/command/StartAPICommand.kt @@ -1,12 +1,12 @@ package app.revanced.api.command -import app.revanced.api.configuration.* +import app.revanced.api.configuration.configureDependencies +import app.revanced.api.configuration.configureHTTP +import app.revanced.api.configuration.configureSecurity +import app.revanced.api.configuration.configureSerialization import app.revanced.api.configuration.routing.configureRouting -import io.ktor.server.application.* import io.ktor.server.engine.* import io.ktor.server.netty.* -import io.ktor.server.response.* -import io.ktor.server.routing.* import picocli.CommandLine @CommandLine.Command( diff --git a/src/main/kotlin/app/revanced/api/repository/OldApiService.kt b/src/main/kotlin/app/revanced/api/repository/OldApiService.kt index 4c5612d..97a4443 100644 --- a/src/main/kotlin/app/revanced/api/repository/OldApiService.kt +++ b/src/main/kotlin/app/revanced/api/repository/OldApiService.kt @@ -28,10 +28,12 @@ internal class OldApiService(private val client: HttpClient) { !key.equals( HttpHeaders.ContentType, ignoreCase = true, - ) && !key.equals( - HttpHeaders.ContentLength, - ignoreCase = true, - ) && !key.equals(HttpHeaders.Host, ignoreCase = true) + ) && + !key.equals( + HttpHeaders.ContentLength, + ignoreCase = true, + ) && + !key.equals(HttpHeaders.Host, ignoreCase = true) }, ) } @@ -51,7 +53,8 @@ internal class OldApiService(private val client: HttpClient) { !key.equals( HttpHeaders.ContentType, ignoreCase = true, - ) && !key.equals(HttpHeaders.ContentLength, ignoreCase = true) + ) && + !key.equals(HttpHeaders.ContentLength, ignoreCase = true) }, ) } diff --git a/src/main/kotlin/app/revanced/api/services/AuthService.kt b/src/main/kotlin/app/revanced/api/services/AuthService.kt index af824a0..29f9a81 100644 --- a/src/main/kotlin/app/revanced/api/services/AuthService.kt +++ b/src/main/kotlin/app/revanced/api/services/AuthService.kt @@ -38,10 +38,8 @@ internal class AuthService( } } - fun newToken(): String { - return JWT.create() - .withIssuer(issuer) - .withExpiresAt(Date(System.currentTimeMillis() + validityInMin.minutes.inWholeMilliseconds)) - .sign(Algorithm.HMAC256(jwtSecret)) - } + fun newToken(): String = JWT.create() + .withIssuer(issuer) + .withExpiresAt(Date(System.currentTimeMillis() + validityInMin.minutes.inWholeMilliseconds)) + .sign(Algorithm.HMAC256(jwtSecret)) } From 9d816223fb1ac8cded324aa486d9e79a7881ad28 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Wed, 5 Jun 2024 14:47:55 +0200 Subject: [PATCH 035/102] build: Update dockerfile and env vars --- .env.example | 17 +++++--- .gitignore | 3 +- Dockerfile | 40 +++++++------------ docker-compose.yml | 20 +++------- .../revanced/api/command/StartAPICommand.kt | 2 +- .../api/configuration/Dependencies.kt | 6 +-- 6 files changed, 34 insertions(+), 54 deletions(-) diff --git a/.env.example b/.env.example index 78477d2..9d7c0e1 100644 --- a/.env.example +++ b/.env.example @@ -1,15 +1,20 @@ +# File path to store configurations for the API CONFIG_FILE_PATH=configuration.toml -GITHUB_TOKEN= +# Optional token for API calls to the backend +BACKEND_API_TOKEN= +# An option URL to the old API to proxy for migration purposes +OLD_API_URL= +# Database connection details DB_URL=jdbc:h2:./persistence/revanced-api DB_USER= DB_PASSWORD= -JWT_SECRET= -JWT_ISSUER= -JWT_VALIDITY_IN_MIN= - +# Basic authentication to issue JWT tokens BASIC_USERNAME= BASIC_PASSWORD= -OLD_API_URL= \ No newline at end of file +# JWT configuration for authenticated API endpoints +JWT_SECRET= +JWT_ISSUER= +JWT_VALIDITY_IN_MIN= diff --git a/.gitignore b/.gitignore index a300aea..8f403a3 100644 --- a/.gitignore +++ b/.gitignore @@ -38,5 +38,4 @@ out/ ### Project ### .env persistence/ -configuration.toml -.db \ No newline at end of file +configuration.toml \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 5a0c2a1..1dcd313 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,31 +1,19 @@ -FROM azul/zulu-openjdk:latest +# Build the application +FROM gradle:latest AS build -ARG CONFIG_FILE_PATH +ARG GITHUB_ACTOR +ARG GITHUB_TOKEN -ARG DB_URL -ARG DB_USER -ARG DB_PASSWORD +ENV GITHUB_ACTOR $GITHUB_ACTOR +ENV GITHUB_TOKEN $GITHUB_TOKEN -ARG JWT_SECRET -ARG JWT_ISSUER -ARG JWT_VALIDITY_IN_MIN +WORKDIR /app +COPY . . +RUN gradle publish --no-daemon -ARG BASIC_USERNAME -ARG BASIC_PASSWORD +# Build the runtime container +FROM eclipse-temurin:latest -ENV CONFIG_FILE_PATH $CONFIG_FILE_PATH - -ENV DB_URL $DB_URL -ENV DB_USER $DB_USER -ENV DB_PASSWORD $DB_PASSWORD - -ENV JWT_SECRET $JWT_SECRET -ENV JWT_ISSUER $JWT_ISSUER -ENV JWT_VALIDITY_IN_MIN $JWT_VALIDITY_IN_MIN - -ENV BASIC_USERNAME $BASIC_USERNAME -ENV BASIC_PASSWORD $BASIC_PASSWORD - -COPY build/libs/revanced-api-*.jar revanced-api.jar - -CMD java -jar revanced-api.jar start \ No newline at end of file +WORKDIR /app +COPY --from=build /app/build/libs/revanced-api-*.jar revanced-api.jar +CMD java -jar revanced-api.jar $COMMAND diff --git a/docker-compose.yml b/docker-compose.yml index 78d2377..95b9158 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,23 +1,13 @@ -version: "3.8" - services: revanced-api: container_name: revanced-api image: ghcr.io/revanced/revanced-api:latest volumes: - - /data/revanced-api/persistence:/persistence - - /data/revanced-api/configuration.toml:/configuration.toml + - /data/revanced-api/persistence:/app/persistence + - /data/revanced-api/.env:/app/.env + - /data/revanced-api/configuration.toml:/app/configuration.toml environment: - - CONFIG_FILE_PATH=configuration.toml - - GITHUB_TOKEN= - - DB_URL=jdbc:h2:./persistence/revanced-api - - DB_USER= - - DB_PASSWORD= - - JWT_SECRET= - - JWT_ISSUER= - - JWT_VALIDITY_IN_MIN=5 - - BASIC_USERNAME= - - BASIC_PASSWORD= + - COMMAND=start ports: - - 127.0.0.1:8080:8000 + - localhost:8080:8080 restart: unless-stopped diff --git a/src/main/kotlin/app/revanced/api/command/StartAPICommand.kt b/src/main/kotlin/app/revanced/api/command/StartAPICommand.kt index 88e6945..2e78e65 100644 --- a/src/main/kotlin/app/revanced/api/command/StartAPICommand.kt +++ b/src/main/kotlin/app/revanced/api/command/StartAPICommand.kt @@ -19,7 +19,7 @@ internal object StartAPICommand : Runnable { description = ["The host address to bind to."], showDefaultValue = CommandLine.Help.Visibility.ALWAYS, ) - private var host: String = "0.0.0.0" + private var host: String = "127.0.0.1" @CommandLine.Option( names = ["-p", "--port"], diff --git a/src/main/kotlin/app/revanced/api/configuration/Dependencies.kt b/src/main/kotlin/app/revanced/api/configuration/Dependencies.kt index 8a60324..b4f6542 100644 --- a/src/main/kotlin/app/revanced/api/configuration/Dependencies.kt +++ b/src/main/kotlin/app/revanced/api/configuration/Dependencies.kt @@ -36,9 +36,7 @@ import java.io.File fun Application.configureDependencies() { val globalModule = module { single { - Dotenv.configure() - .systemProperties() - .load() + Dotenv.configure().load() } factory { params -> val defaultRequestUri: String = params.get() @@ -80,7 +78,7 @@ fun Application.configureDependencies() { ) } - get()["GITHUB_TOKEN"]?.let { + get()["BACKEND_API_TOKEN"]?.let { install(Auth) { bearer { loadTokens { From af47edeb5b152caa4cae3b6334d06f3ef03c27d4 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Wed, 5 Jun 2024 14:58:48 +0200 Subject: [PATCH 036/102] docs: Add issue templates and update README --- .github/ISSUE_TEMPLATE/bug_report.yml | 109 +++++++++++++++++++++ .github/ISSUE_TEMPLATE/config.yml | 5 + .github/ISSUE_TEMPLATE/feature_request.yml | 105 ++++++++++++++++++++ .github/config.yml | 2 + .github/workflows/pull_request.yml | 2 +- README.md | 7 +- configuration.example.toml | 2 +- 7 files changed, 227 insertions(+), 5 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.yml create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/ISSUE_TEMPLATE/feature_request.yml create mode 100644 .github/config.yml diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..9cae518 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,109 @@ +name: 🐞 Bug report +description: Report a bug or an issue. +title: 'bug: ' +labels: ['Bug report'] +body: + - type: markdown + attributes: + value: | +

+ + + + +
+
+ + + + +     + + + + + +     + + + + + +     + + + + + +     + + + + + +     + + + + + +     + + + + + + +
+
+ Continuing the legacy of Vanced +

+ + # ReVanced API bug report + + Before creating a new bug report, please keep the following in mind: + + - **Do not submit a duplicate bug report**: Search for existing bug reports [here](https://github.com/ReVanced/revanced-api/issues?q=label%3A%22Bug+report%22). + - **Review the contribution guidelines**: Make sure your bug report adheres to it. You can find the guidelines [here](https://github.com/ReVanced/revanced-api/blob/main/CONTRIBUTING.md). + - **Do not use the issue page for support**: If you need help or have questions, check out other platforms on [revanced.app](https://revanced.app). + - type: textarea + attributes: + label: Bug description + description: | + - Describe your bug in detail + - Add steps to reproduce the bug if possible (Step 1. ... Step 2. ...) + - Add images and videos if possible + validations: + required: true + - type: textarea + attributes: + label: Error logs + description: Exceptions can be captured by running `logcat | grep AndroidRuntime` in a shell. + render: shell + - type: textarea + attributes: + label: Solution + description: If applicable, add a possible solution to the bug. + - type: textarea + attributes: + label: Additional context + description: Add additional context here. + - type: checkboxes + id: acknowledgements + attributes: + label: Acknowledgements + description: Your bug report will be closed if you don't follow the checklist below. + options: + - label: I have checked all open and closed bug reports and this is not a duplicate. + required: true + - label: I have chosen an appropriate title. + required: true + - label: All requested information has been provided properly. + required: true diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..8bc7f7e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: false +contact_links: + - name: 🗨 Discussions + url: https://github.com/revanced/revanced-suggestions/discussions + about: Have something unspecific to ReVanced APi in mind? Search for or start a new discussion! diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000..fab7f3f --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,105 @@ +name: ⭐ Feature request +description: Create a detailed request for a new feature. +title: 'feat: ' +labels: ['Feature request'] +body: + - type: markdown + attributes: + value: | +

+ + + + +
+ + + + + +     + + + + + +     + + + + + +     + + + + + +     + + + + + +     + + + + + +     + + + + + + +
+
+ Continuing the legacy of Vanced +

+ + # ReVanced APi feature request + + Before creating a new feature request, please keep the following in mind: + + - **Do not submit a duplicate feature request**: Search for existing feature requests [here](https://github.com/ReVanced/revanced-api/issues?q=label%3A%22Feature+request%22). + - **Review the contribution guidelines**: Make sure your feature request adheres to it. You can find the guidelines [here](https://github.com/ReVanced/revanced-api/blob/main/CONTRIBUTING.md). + - **Do not use the issue page for support**: If you need help or have questions, check out other platforms on [revanced.app](https://revanced.app). + - type: textarea + attributes: + label: Feature description + description: | + - Describe your feature in detail + - Add images, videos, links, examples, references, etc. if possible + - type: textarea + attributes: + label: Motivation + description: | + A strong motivation is necessary for a feature request to be considered. + + - Why should this feature be implemented? + - What is the explicit use case? + - What are the benefits? + - What makes this feature important? + validations: + required: true + - type: checkboxes + id: acknowledgements + attributes: + label: Acknowledgements + description: Your feature request will be closed if you don't follow the checklist below. + options: + - label: I have checked all open and closed feature requests and this is not a duplicate + required: true + - label: I have chosen an appropriate title. + required: true + - label: All requested information has been provided properly. + required: true diff --git a/.github/config.yml b/.github/config.yml new file mode 100644 index 0000000..075f56b --- /dev/null +++ b/.github/config.yml @@ -0,0 +1,2 @@ +firstPRMergeComment: > + Thank you for contributing to ReVanced. Join us on [Discord](https://revanced.app/discord) to receive a role for your contribution. diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 41e4554..75b8e67 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -23,4 +23,4 @@ jobs: destination_branch: 'main' pr_title: 'chore: ${{ env.MESSAGE }}' pr_body: 'This pull request will ${{ env.MESSAGE }}.' - pr_draft: true \ No newline at end of file + pr_draft: true diff --git a/README.md b/README.md index 91ba5df..8b7732a 100644 --- a/README.md +++ b/README.md @@ -74,10 +74,11 @@ ReVanced API acts as the data source for [ReVanced Website](https://github.com/R Some of the features ReVanced API include: -- 📢 **Announcements**: Push announcements grouped by channels -- ℹ️ **About**: Get more information such as a description about, ways to donate to, and links of the hoster of ReVanced API -- 🧩 **Patches**: Get the latest updates of ReVanced Patches directly from ReVanced API +- 📢 **Announcements**: Post and get announcements grouped by channels +- ℹ️ **About**: Get more information such as a description, ways to donate to, and links of the hoster of ReVanced API +- 🧩 **Patches**: Get the latest updates of ReVanced Patches, directly from ReVanced API - 👥 **Contributors**: List all contributors involved in the project +- 🔄 **Proxy**: A proxy to an old API for migration purposes and backwards compatibility ## 🚀 How to get started diff --git a/configuration.example.toml b/configuration.example.toml index 0fdadb0..5935fd6 100644 --- a/configuration.example.toml +++ b/configuration.example.toml @@ -2,4 +2,4 @@ organization = "org" patches-repository = "patches" integrations-repositories = ["integrations"] contributors-repositories = ["patches", "integrations"] -api-version = 1 \ No newline at end of file +api-version = 1 From 8d5b09bd02809578ee5fb1d519d4b1972e6eb126 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Wed, 5 Jun 2024 15:18:27 +0200 Subject: [PATCH 037/102] ci: Setup workflows for proper release --- .github/workflows/build_pull_request.yml | 25 ++++++++++ ...pull_request.yml => open_pull_request.yml} | 0 .github/workflows/release.yml | 46 ++++++++----------- build.gradle.kts | 2 +- 4 files changed, 46 insertions(+), 27 deletions(-) create mode 100644 .github/workflows/build_pull_request.yml rename .github/workflows/{pull_request.yml => open_pull_request.yml} (100%) diff --git a/.github/workflows/build_pull_request.yml b/.github/workflows/build_pull_request.yml new file mode 100644 index 0000000..250871b --- /dev/null +++ b/.github/workflows/build_pull_request.yml @@ -0,0 +1,25 @@ +name: Build pull request + +on: + workflow_dispatch: + pull_request: + branches: + - dev + +jobs: + release: + name: Build + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Cache Gradle + uses: burrunan/gradle-cache-action@v1 + + - name: Build + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: ./gradlew build --no-daemon diff --git a/.github/workflows/pull_request.yml b/.github/workflows/open_pull_request.yml similarity index 100% rename from .github/workflows/pull_request.yml rename to .github/workflows/open_pull_request.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8043363..b007442 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -6,10 +6,6 @@ on: branches: - main - dev - pull_request: - branches: - - main - - dev jobs: release: @@ -44,6 +40,13 @@ jobs: - name: Install dependencies run: npm install + - name: Import GPG key + uses: crazy-max/ghaction-import-gpg@v6 + with: + gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }} + passphrase: ${{ secrets.GPG_PASSPHRASE }} + fingerprint: ${{ env.GPG_FINGERPRINT }} + - name: Release env: GITHUB_TOKEN: ${{ secrets.REPOSITORY_PUSH_ACCESS }} @@ -51,39 +54,30 @@ jobs: - name: Setup QEMU uses: docker/setup-qemu-action@v3 - with: - image: tonistiigi/binfmt:latest - platforms: all - - name: Setup Docker buildx - uses: docker/setup-buildx-action@v3 + - name: Extract metadata for the Docker image + id: metadata + uses: docker/metadata-action@v5 + with: + images: ghcr.io/${{ env.IMAGE_NAME }} + flavor: | + latest=${{ startsWith(github.ref, 'refs/heads/main') }} + suffix=-${{ github.sha }} - name: Login to GitHub Container Registry uses: docker/login-action@v3 with: - registry: ${{ env.REGISTRY }} - username: ${{ github.repository_owner }} - password: ${{ secrets.GH_TOKEN }} - - - name: Extract metadata (tags, labels) for Docker - id: metadata - uses: docker/metadata-action@v5 - with: - images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} - flavor: | - latest=${{ startsWith(github.ref, 'refs/heads/main') }} - suffix=-${{ github.sha }} + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} - name: Build and push Docker image id: build uses: docker/build-push-action@v5 with: - build-args: GH_TOKEN=${{ secrets.GH_TOKEN }} - context: . - file: ./Dockerfile - platforms: linux/amd64,linux/arm64/v8 - cache-to: type=gha,mode=max,ignore-error=true + platforms: linux/amd64,linux/arm64 cache-from: type=gha + cache-to: type=gha,mode=max,ignore-error=true push: true tags: ${{ steps.metadata.outputs.tags }} labels: ${{ steps.metadata.outputs.labels }} diff --git a/build.gradle.kts b/build.gradle.kts index a62319d..bc19853 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -15,7 +15,7 @@ tasks { // Tracking: https://github.com/KengoTODA/gradle-semantic-release-plugin/issues/435 register("publish") { group = "publishing" - dependsOn(shadowJar) + dependsOn(startShadowScripts) } } From 2430be75d8b24f12eacc8f3a35c2c3d7b6e0dff3 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Wed, 5 Jun 2024 17:22:46 +0200 Subject: [PATCH 038/102] chore: Use AGPL license --- LICENSE | 143 ++++++++++++++++++++++++++------------------------------ 1 file changed, 65 insertions(+), 78 deletions(-) diff --git a/LICENSE b/LICENSE index f288702..0ad25db 100644 --- a/LICENSE +++ b/LICENSE @@ -1,5 +1,5 @@ - GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies @@ -7,17 +7,15 @@ Preamble - The GNU General Public License is a free, copyleft license for -software and other kinds of works. + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to +our General Public Licenses are intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. +software for all its users. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you @@ -26,44 +24,34 @@ them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. - To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. - Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. - For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - - Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - - Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. The precise terms and conditions for copying, distribution and modification follow. @@ -72,7 +60,7 @@ modification follow. 0. Definitions. - "This License" refers to version 3 of the GNU General Public License. + "This License" refers to version 3 of the GNU Affero General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. @@ -549,35 +537,45 @@ to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. - 13. Use with the GNU Affero General Public License. + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single +under version 3 of the GNU General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General +Program specifies that a certain numbered version of the GNU Affero General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published +GNU Affero General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's +versions of the GNU Affero General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. @@ -635,40 +633,29 @@ the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. + GNU Affero General Public License for more details. - You should have received a copy of the GNU General Public License + You should have received a copy of the GNU Affero General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. - If the program does terminal interaction, make it output a short -notice like this when it starts in an interactive mode: - - Copyright (C) - This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, your program's commands -might be different; for a GUI interface, you would use an "about box". + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU GPL, see +For more information on this, and how to apply and follow the GNU AGPL, see . - - The GNU General Public License does not permit incorporating your program -into proprietary programs. If your program is a subroutine library, you -may consider it more useful to permit linking proprietary applications with -the library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. But first, please read -. From 659cce3e0300586f31522d663bf2db35bc1c4a6e Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Wed, 5 Jun 2024 17:27:25 +0200 Subject: [PATCH 039/102] chore: Move files to correct folders --- .../api/configuration/Dependencies.kt | 18 ++++++------ .../revanced/api/configuration/Security.kt | 2 +- .../repository/AnnouncementRepository.kt | 10 +++---- .../repository/ConfigurationRepository.kt | 2 +- .../repository/backend/BackendRepository.kt | 2 +- .../backend/github/GitHubBackendRepository.kt | 28 +++++++++---------- .../repository/backend/github/api/Request.kt | 2 +- .../repository/backend/github/api/Response.kt | 2 +- .../api/configuration/routing/Routing.kt | 2 +- .../routing/routes/Announcements.kt | 6 ++-- .../configuration/routing/routes/ApiRoute.kt | 4 +-- .../configuration/routing/routes/OldApi.kt | 2 +- .../routing/routes/PatchesRoute.kt | 2 +- .../{ => configuration}/schema/APISchema.kt | 2 +- .../services/AnnouncementService.kt | 8 +++--- .../services/ApiService.kt | 12 ++++---- .../services/AuthService.kt | 2 +- .../services}/OldApiService.kt | 2 +- .../services/PatchesService.kt | 12 ++++---- 19 files changed, 60 insertions(+), 60 deletions(-) rename src/main/kotlin/app/revanced/api/{ => configuration}/repository/AnnouncementRepository.kt (93%) rename src/main/kotlin/app/revanced/api/{ => configuration}/repository/ConfigurationRepository.kt (90%) rename src/main/kotlin/app/revanced/api/{ => configuration}/repository/backend/BackendRepository.kt (98%) rename src/main/kotlin/app/revanced/api/{ => configuration}/repository/backend/github/GitHubBackendRepository.kt (60%) rename src/main/kotlin/app/revanced/api/{ => configuration}/repository/backend/github/api/Request.kt (92%) rename src/main/kotlin/app/revanced/api/{ => configuration}/repository/backend/github/api/Response.kt (95%) rename src/main/kotlin/app/revanced/api/{ => configuration}/schema/APISchema.kt (97%) rename src/main/kotlin/app/revanced/api/{ => configuration}/services/AnnouncementService.kt (80%) rename src/main/kotlin/app/revanced/api/{ => configuration}/services/ApiService.kt (71%) rename src/main/kotlin/app/revanced/api/{ => configuration}/services/AuthService.kt (96%) rename src/main/kotlin/app/revanced/api/{repository => configuration/services}/OldApiService.kt (98%) rename src/main/kotlin/app/revanced/api/{ => configuration}/services/PatchesService.kt (88%) diff --git a/src/main/kotlin/app/revanced/api/configuration/Dependencies.kt b/src/main/kotlin/app/revanced/api/configuration/Dependencies.kt index b4f6542..7cc14f4 100644 --- a/src/main/kotlin/app/revanced/api/configuration/Dependencies.kt +++ b/src/main/kotlin/app/revanced/api/configuration/Dependencies.kt @@ -1,14 +1,14 @@ package app.revanced.api.configuration -import app.revanced.api.repository.AnnouncementRepository -import app.revanced.api.repository.ConfigurationRepository -import app.revanced.api.repository.OldApiService -import app.revanced.api.repository.backend.BackendRepository -import app.revanced.api.repository.backend.github.GitHubBackendRepository -import app.revanced.api.services.AnnouncementService -import app.revanced.api.services.ApiService -import app.revanced.api.services.AuthService -import app.revanced.api.services.PatchesService +import app.revanced.api.configuration.repository.AnnouncementRepository +import app.revanced.api.configuration.repository.ConfigurationRepository +import app.revanced.api.configuration.repository.backend.BackendRepository +import app.revanced.api.configuration.repository.backend.github.GitHubBackendRepository +import app.revanced.api.configuration.services.AnnouncementService +import app.revanced.api.configuration.services.ApiService +import app.revanced.api.configuration.services.AuthService +import app.revanced.api.configuration.services.OldApiService +import app.revanced.api.configuration.services.PatchesService import com.akuleshov7.ktoml.Toml import com.akuleshov7.ktoml.source.decodeFromStream import io.github.cdimascio.dotenv.Dotenv diff --git a/src/main/kotlin/app/revanced/api/configuration/Security.kt b/src/main/kotlin/app/revanced/api/configuration/Security.kt index 2543fb1..1d6d386 100644 --- a/src/main/kotlin/app/revanced/api/configuration/Security.kt +++ b/src/main/kotlin/app/revanced/api/configuration/Security.kt @@ -1,6 +1,6 @@ package app.revanced.api.configuration -import app.revanced.api.services.AuthService +import app.revanced.api.configuration.services.AuthService import io.ktor.server.application.* import org.koin.ktor.ext.get diff --git a/src/main/kotlin/app/revanced/api/repository/AnnouncementRepository.kt b/src/main/kotlin/app/revanced/api/configuration/repository/AnnouncementRepository.kt similarity index 93% rename from src/main/kotlin/app/revanced/api/repository/AnnouncementRepository.kt rename to src/main/kotlin/app/revanced/api/configuration/repository/AnnouncementRepository.kt index a6c4b63..7da8300 100644 --- a/src/main/kotlin/app/revanced/api/repository/AnnouncementRepository.kt +++ b/src/main/kotlin/app/revanced/api/configuration/repository/AnnouncementRepository.kt @@ -1,9 +1,9 @@ -package app.revanced.api.repository +package app.revanced.api.configuration.repository -import app.revanced.api.repository.AnnouncementRepository.AttachmentTable.announcement -import app.revanced.api.schema.APIAnnouncement -import app.revanced.api.schema.APILatestAnnouncement -import app.revanced.api.schema.APIResponseAnnouncement +import app.revanced.api.configuration.repository.AnnouncementRepository.AttachmentTable.announcement +import app.revanced.api.configuration.schema.APIAnnouncement +import app.revanced.api.configuration.schema.APILatestAnnouncement +import app.revanced.api.configuration.schema.APIResponseAnnouncement import kotlinx.datetime.* import org.jetbrains.exposed.dao.IntEntity import org.jetbrains.exposed.dao.IntEntityClass diff --git a/src/main/kotlin/app/revanced/api/repository/ConfigurationRepository.kt b/src/main/kotlin/app/revanced/api/configuration/repository/ConfigurationRepository.kt similarity index 90% rename from src/main/kotlin/app/revanced/api/repository/ConfigurationRepository.kt rename to src/main/kotlin/app/revanced/api/configuration/repository/ConfigurationRepository.kt index 531de56..3d5f28c 100644 --- a/src/main/kotlin/app/revanced/api/repository/ConfigurationRepository.kt +++ b/src/main/kotlin/app/revanced/api/configuration/repository/ConfigurationRepository.kt @@ -1,4 +1,4 @@ -package app.revanced.api.repository +package app.revanced.api.configuration.repository import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable diff --git a/src/main/kotlin/app/revanced/api/repository/backend/BackendRepository.kt b/src/main/kotlin/app/revanced/api/configuration/repository/backend/BackendRepository.kt similarity index 98% rename from src/main/kotlin/app/revanced/api/repository/backend/BackendRepository.kt rename to src/main/kotlin/app/revanced/api/configuration/repository/backend/BackendRepository.kt index f27012d..279c354 100644 --- a/src/main/kotlin/app/revanced/api/repository/backend/BackendRepository.kt +++ b/src/main/kotlin/app/revanced/api/configuration/repository/backend/BackendRepository.kt @@ -1,4 +1,4 @@ -package app.revanced.api.repository.backend +package app.revanced.api.configuration.repository.backend import io.ktor.client.* import kotlinx.datetime.LocalDateTime diff --git a/src/main/kotlin/app/revanced/api/repository/backend/github/GitHubBackendRepository.kt b/src/main/kotlin/app/revanced/api/configuration/repository/backend/github/GitHubBackendRepository.kt similarity index 60% rename from src/main/kotlin/app/revanced/api/repository/backend/github/GitHubBackendRepository.kt rename to src/main/kotlin/app/revanced/api/configuration/repository/backend/github/GitHubBackendRepository.kt index 1664adf..cb22d58 100644 --- a/src/main/kotlin/app/revanced/api/repository/backend/github/GitHubBackendRepository.kt +++ b/src/main/kotlin/app/revanced/api/configuration/repository/backend/github/GitHubBackendRepository.kt @@ -1,18 +1,18 @@ -package app.revanced.api.repository.backend.github +package app.revanced.api.configuration.repository.backend.github -import app.revanced.api.repository.backend.BackendRepository -import app.revanced.api.repository.backend.BackendRepository.BackendOrganization.BackendMember -import app.revanced.api.repository.backend.BackendRepository.BackendOrganization.BackendRepository.BackendContributor -import app.revanced.api.repository.backend.BackendRepository.BackendOrganization.BackendRepository.BackendRelease -import app.revanced.api.repository.backend.BackendRepository.BackendOrganization.BackendRepository.BackendRelease.BackendAsset -import app.revanced.api.repository.backend.github.api.Request -import app.revanced.api.repository.backend.github.api.Request.Organization.Members -import app.revanced.api.repository.backend.github.api.Request.Organization.Repository.Contributors -import app.revanced.api.repository.backend.github.api.Request.Organization.Repository.Releases -import app.revanced.api.repository.backend.github.api.Response -import app.revanced.api.repository.backend.github.api.Response.GitHubOrganization.GitHubMember -import app.revanced.api.repository.backend.github.api.Response.GitHubOrganization.GitHubRepository.GitHubContributor -import app.revanced.api.repository.backend.github.api.Response.GitHubOrganization.GitHubRepository.GitHubRelease +import app.revanced.api.configuration.repository.backend.BackendRepository +import app.revanced.api.configuration.repository.backend.BackendRepository.BackendOrganization.BackendMember +import app.revanced.api.configuration.repository.backend.BackendRepository.BackendOrganization.BackendRepository.BackendContributor +import app.revanced.api.configuration.repository.backend.BackendRepository.BackendOrganization.BackendRepository.BackendRelease +import app.revanced.api.configuration.repository.backend.BackendRepository.BackendOrganization.BackendRepository.BackendRelease.BackendAsset +import app.revanced.api.configuration.repository.backend.github.api.Request +import app.revanced.api.configuration.repository.backend.github.api.Request.Organization.Members +import app.revanced.api.configuration.repository.backend.github.api.Request.Organization.Repository.Contributors +import app.revanced.api.configuration.repository.backend.github.api.Request.Organization.Repository.Releases +import app.revanced.api.configuration.repository.backend.github.api.Response +import app.revanced.api.configuration.repository.backend.github.api.Response.GitHubOrganization.GitHubMember +import app.revanced.api.configuration.repository.backend.github.api.Response.GitHubOrganization.GitHubRepository.GitHubContributor +import app.revanced.api.configuration.repository.backend.github.api.Response.GitHubOrganization.GitHubRepository.GitHubRelease import io.ktor.client.* import io.ktor.client.call.* import io.ktor.client.plugins.resources.* diff --git a/src/main/kotlin/app/revanced/api/repository/backend/github/api/Request.kt b/src/main/kotlin/app/revanced/api/configuration/repository/backend/github/api/Request.kt similarity index 92% rename from src/main/kotlin/app/revanced/api/repository/backend/github/api/Request.kt rename to src/main/kotlin/app/revanced/api/configuration/repository/backend/github/api/Request.kt index 557f9e2..7eb9897 100644 --- a/src/main/kotlin/app/revanced/api/repository/backend/github/api/Request.kt +++ b/src/main/kotlin/app/revanced/api/configuration/repository/backend/github/api/Request.kt @@ -1,4 +1,4 @@ -package app.revanced.api.repository.backend.github.api +package app.revanced.api.configuration.repository.backend.github.api import io.ktor.resources.* diff --git a/src/main/kotlin/app/revanced/api/repository/backend/github/api/Response.kt b/src/main/kotlin/app/revanced/api/configuration/repository/backend/github/api/Response.kt similarity index 95% rename from src/main/kotlin/app/revanced/api/repository/backend/github/api/Response.kt rename to src/main/kotlin/app/revanced/api/configuration/repository/backend/github/api/Response.kt index 2ddc8f1..0128ee4 100644 --- a/src/main/kotlin/app/revanced/api/repository/backend/github/api/Response.kt +++ b/src/main/kotlin/app/revanced/api/configuration/repository/backend/github/api/Response.kt @@ -1,4 +1,4 @@ -package app.revanced.api.repository.backend.github.api +package app.revanced.api.configuration.repository.backend.github.api import kotlinx.datetime.Instant import kotlinx.serialization.Serializable diff --git a/src/main/kotlin/app/revanced/api/configuration/routing/Routing.kt b/src/main/kotlin/app/revanced/api/configuration/routing/Routing.kt index 8296ee3..61e1edb 100644 --- a/src/main/kotlin/app/revanced/api/configuration/routing/Routing.kt +++ b/src/main/kotlin/app/revanced/api/configuration/routing/Routing.kt @@ -1,10 +1,10 @@ package app.revanced.api.configuration.routing +import app.revanced.api.configuration.repository.ConfigurationRepository import app.revanced.api.configuration.routing.routes.announcementsRoute import app.revanced.api.configuration.routing.routes.oldApiRoute import app.revanced.api.configuration.routing.routes.patchesRoute import app.revanced.api.configuration.routing.routes.rootRoute -import app.revanced.api.repository.ConfigurationRepository import io.ktor.server.application.* import io.ktor.server.routing.* import org.koin.ktor.ext.get diff --git a/src/main/kotlin/app/revanced/api/configuration/routing/routes/Announcements.kt b/src/main/kotlin/app/revanced/api/configuration/routing/routes/Announcements.kt index bc4859d..560e4dd 100644 --- a/src/main/kotlin/app/revanced/api/configuration/routing/routes/Announcements.kt +++ b/src/main/kotlin/app/revanced/api/configuration/routing/routes/Announcements.kt @@ -1,8 +1,8 @@ package app.revanced.api.configuration.routing.routes -import app.revanced.api.schema.APIAnnouncement -import app.revanced.api.schema.APIAnnouncementArchivedAt -import app.revanced.api.services.AnnouncementService +import app.revanced.api.configuration.schema.APIAnnouncement +import app.revanced.api.configuration.schema.APIAnnouncementArchivedAt +import app.revanced.api.configuration.services.AnnouncementService import io.ktor.http.* import io.ktor.server.application.* import io.ktor.server.auth.* diff --git a/src/main/kotlin/app/revanced/api/configuration/routing/routes/ApiRoute.kt b/src/main/kotlin/app/revanced/api/configuration/routing/routes/ApiRoute.kt index 56eb553..762bdff 100644 --- a/src/main/kotlin/app/revanced/api/configuration/routing/routes/ApiRoute.kt +++ b/src/main/kotlin/app/revanced/api/configuration/routing/routes/ApiRoute.kt @@ -1,7 +1,7 @@ package app.revanced.api.configuration.routing.routes -import app.revanced.api.services.ApiService -import app.revanced.api.services.AuthService +import app.revanced.api.configuration.services.ApiService +import app.revanced.api.configuration.services.AuthService import io.ktor.http.* import io.ktor.server.application.* import io.ktor.server.auth.* diff --git a/src/main/kotlin/app/revanced/api/configuration/routing/routes/OldApi.kt b/src/main/kotlin/app/revanced/api/configuration/routing/routes/OldApi.kt index 64f8006..d45785f 100644 --- a/src/main/kotlin/app/revanced/api/configuration/routing/routes/OldApi.kt +++ b/src/main/kotlin/app/revanced/api/configuration/routing/routes/OldApi.kt @@ -1,6 +1,6 @@ package app.revanced.api.configuration.routing.routes -import app.revanced.api.repository.OldApiService +import app.revanced.api.configuration.services.OldApiService import io.ktor.server.application.* import io.ktor.server.routing.* import org.koin.ktor.ext.get diff --git a/src/main/kotlin/app/revanced/api/configuration/routing/routes/PatchesRoute.kt b/src/main/kotlin/app/revanced/api/configuration/routing/routes/PatchesRoute.kt index 4ddfd65..6567c48 100644 --- a/src/main/kotlin/app/revanced/api/configuration/routing/routes/PatchesRoute.kt +++ b/src/main/kotlin/app/revanced/api/configuration/routing/routes/PatchesRoute.kt @@ -1,6 +1,6 @@ package app.revanced.api.configuration.routing.routes -import app.revanced.api.services.PatchesService +import app.revanced.api.configuration.services.PatchesService import io.ktor.http.* import io.ktor.server.application.* import io.ktor.server.response.* diff --git a/src/main/kotlin/app/revanced/api/schema/APISchema.kt b/src/main/kotlin/app/revanced/api/configuration/schema/APISchema.kt similarity index 97% rename from src/main/kotlin/app/revanced/api/schema/APISchema.kt rename to src/main/kotlin/app/revanced/api/configuration/schema/APISchema.kt index ec6355b..8658460 100644 --- a/src/main/kotlin/app/revanced/api/schema/APISchema.kt +++ b/src/main/kotlin/app/revanced/api/configuration/schema/APISchema.kt @@ -1,4 +1,4 @@ -package app.revanced.api.schema +package app.revanced.api.configuration.schema import kotlinx.datetime.LocalDateTime import kotlinx.serialization.SerialName diff --git a/src/main/kotlin/app/revanced/api/services/AnnouncementService.kt b/src/main/kotlin/app/revanced/api/configuration/services/AnnouncementService.kt similarity index 80% rename from src/main/kotlin/app/revanced/api/services/AnnouncementService.kt rename to src/main/kotlin/app/revanced/api/configuration/services/AnnouncementService.kt index 63418da..80a3ddb 100644 --- a/src/main/kotlin/app/revanced/api/services/AnnouncementService.kt +++ b/src/main/kotlin/app/revanced/api/configuration/services/AnnouncementService.kt @@ -1,8 +1,8 @@ -package app.revanced.api.services +package app.revanced.api.configuration.services -import app.revanced.api.repository.AnnouncementRepository -import app.revanced.api.schema.APIAnnouncement -import app.revanced.api.schema.APILatestAnnouncement +import app.revanced.api.configuration.repository.AnnouncementRepository +import app.revanced.api.configuration.schema.APIAnnouncement +import app.revanced.api.configuration.schema.APILatestAnnouncement import kotlinx.datetime.LocalDateTime internal class AnnouncementService( diff --git a/src/main/kotlin/app/revanced/api/services/ApiService.kt b/src/main/kotlin/app/revanced/api/configuration/services/ApiService.kt similarity index 71% rename from src/main/kotlin/app/revanced/api/services/ApiService.kt rename to src/main/kotlin/app/revanced/api/configuration/services/ApiService.kt index d36e64b..7e25b69 100644 --- a/src/main/kotlin/app/revanced/api/services/ApiService.kt +++ b/src/main/kotlin/app/revanced/api/configuration/services/ApiService.kt @@ -1,10 +1,10 @@ -package app.revanced.api.services +package app.revanced.api.configuration.services -import app.revanced.api.repository.ConfigurationRepository -import app.revanced.api.repository.backend.BackendRepository -import app.revanced.api.schema.APIContributable -import app.revanced.api.schema.APIContributor -import app.revanced.api.schema.APIMember +import app.revanced.api.configuration.repository.ConfigurationRepository +import app.revanced.api.configuration.repository.backend.BackendRepository +import app.revanced.api.configuration.schema.APIContributable +import app.revanced.api.configuration.schema.APIContributor +import app.revanced.api.configuration.schema.APIMember import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll diff --git a/src/main/kotlin/app/revanced/api/services/AuthService.kt b/src/main/kotlin/app/revanced/api/configuration/services/AuthService.kt similarity index 96% rename from src/main/kotlin/app/revanced/api/services/AuthService.kt rename to src/main/kotlin/app/revanced/api/configuration/services/AuthService.kt index 29f9a81..8e4b8a9 100644 --- a/src/main/kotlin/app/revanced/api/services/AuthService.kt +++ b/src/main/kotlin/app/revanced/api/configuration/services/AuthService.kt @@ -1,4 +1,4 @@ -package app.revanced.api.services +package app.revanced.api.configuration.services import com.auth0.jwt.JWT import com.auth0.jwt.algorithms.Algorithm diff --git a/src/main/kotlin/app/revanced/api/repository/OldApiService.kt b/src/main/kotlin/app/revanced/api/configuration/services/OldApiService.kt similarity index 98% rename from src/main/kotlin/app/revanced/api/repository/OldApiService.kt rename to src/main/kotlin/app/revanced/api/configuration/services/OldApiService.kt index 97a4443..ea15427 100644 --- a/src/main/kotlin/app/revanced/api/repository/OldApiService.kt +++ b/src/main/kotlin/app/revanced/api/configuration/services/OldApiService.kt @@ -1,4 +1,4 @@ -package app.revanced.api.repository +package app.revanced.api.configuration.services import io.ktor.client.* import io.ktor.client.request.* diff --git a/src/main/kotlin/app/revanced/api/services/PatchesService.kt b/src/main/kotlin/app/revanced/api/configuration/services/PatchesService.kt similarity index 88% rename from src/main/kotlin/app/revanced/api/services/PatchesService.kt rename to src/main/kotlin/app/revanced/api/configuration/services/PatchesService.kt index 22afd70..2281eed 100644 --- a/src/main/kotlin/app/revanced/api/services/PatchesService.kt +++ b/src/main/kotlin/app/revanced/api/configuration/services/PatchesService.kt @@ -1,10 +1,10 @@ -package app.revanced.api.services +package app.revanced.api.configuration.services -import app.revanced.api.repository.ConfigurationRepository -import app.revanced.api.repository.backend.BackendRepository -import app.revanced.api.schema.APIAsset -import app.revanced.api.schema.APIRelease -import app.revanced.api.schema.APIReleaseVersion +import app.revanced.api.configuration.repository.ConfigurationRepository +import app.revanced.api.configuration.repository.backend.BackendRepository +import app.revanced.api.configuration.schema.APIAsset +import app.revanced.api.configuration.schema.APIRelease +import app.revanced.api.configuration.schema.APIReleaseVersion import app.revanced.library.PatchUtils import app.revanced.patcher.PatchBundleLoader import com.github.benmanes.caffeine.cache.Caffeine From c6cacef907a5039ed029e1e26204aaba8e698aaa Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Wed, 5 Jun 2024 17:29:20 +0200 Subject: [PATCH 040/102] fix: Set body for all eligible request methods --- .../configuration/services/OldApiService.kt | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/main/kotlin/app/revanced/api/configuration/services/OldApiService.kt b/src/main/kotlin/app/revanced/api/configuration/services/OldApiService.kt index ea15427..2c464aa 100644 --- a/src/main/kotlin/app/revanced/api/configuration/services/OldApiService.kt +++ b/src/main/kotlin/app/revanced/api/configuration/services/OldApiService.kt @@ -25,20 +25,21 @@ internal class OldApiService(private val client: HttpClient) { headers { appendAll( call.request.headers.filter { key, _ -> - !key.equals( - HttpHeaders.ContentType, - ignoreCase = true, - ) && - !key.equals( - HttpHeaders.ContentLength, - ignoreCase = true, - ) && - !key.equals(HttpHeaders.Host, ignoreCase = true) + !( + key.equals(HttpHeaders.ContentType, ignoreCase = true) || + key.equals(HttpHeaders.ContentLength, ignoreCase = true) || + key.equals(HttpHeaders.Host, ignoreCase = true) + ) }, ) } - if (call.request.httpMethod == HttpMethod.Post) { - body = ByteArrayContent(byteArray, call.request.contentType()) + + when (call.request.httpMethod) { + HttpMethod.Post, + HttpMethod.Put, + HttpMethod.Patch, + HttpMethod.Delete, + -> body = ByteArrayContent(byteArray, call.request.contentType()) } } From ef927688a377c16fe37b578ea870207871c30056 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Wed, 5 Jun 2024 17:45:10 +0200 Subject: [PATCH 041/102] fix: Use correct proxy path --- .../app/revanced/api/configuration/routing/routes/OldApi.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/app/revanced/api/configuration/routing/routes/OldApi.kt b/src/main/kotlin/app/revanced/api/configuration/routing/routes/OldApi.kt index d45785f..fe4f5be 100644 --- a/src/main/kotlin/app/revanced/api/configuration/routing/routes/OldApi.kt +++ b/src/main/kotlin/app/revanced/api/configuration/routing/routes/OldApi.kt @@ -8,7 +8,7 @@ import org.koin.ktor.ext.get internal fun Route.oldApiRoute() { val oldApiService = get() - route(Regex("(v2|tools|contributor).*")) { + route(Regex("/(v2|tools|contributors).*")) { handle { oldApiService.proxy(call) } From c23cd5cdad01fee52aecdb36b93a619886edfa4d Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Wed, 5 Jun 2024 17:49:40 +0200 Subject: [PATCH 042/102] feat: Use Jetty instead of Netty --- build.gradle.kts | 7 ++++++- gradle/libs.versions.toml | 2 +- .../kotlin/app/revanced/api/command/StartAPICommand.kt | 4 ++-- src/main/resources/logback.xml | 3 +-- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index bc19853..16ad9f7 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -17,6 +17,11 @@ tasks { group = "publishing" dependsOn(startShadowScripts) } + + shadowJar { + // Needed for Jetty to work. + mergeServiceFiles() + } } application { @@ -57,7 +62,7 @@ dependencies { implementation(libs.ktor.server.cors) implementation(libs.ktor.server.caching.headers) implementation(libs.ktor.server.host.common) - implementation(libs.ktor.server.netty) + implementation(libs.ktor.server.jetty) implementation(libs.ktor.server.conditional.headers) implementation(libs.ktor.serialization.kotlinx.json) implementation(libs.koin.ktor) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 66cbed3..d800cbf 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -28,7 +28,7 @@ ktor-server-auth-jwt = { module = "io.ktor:ktor-server-auth-jwt" } ktor-server-cors = { module = "io.ktor:ktor-server-cors" } ktor-server-caching-headers = { module = "io.ktor:ktor-server-caching-headers" } ktor-server-host-common = { module = "io.ktor:ktor-server-host-common" } -ktor-server-netty = { module = "io.ktor:ktor-server-netty" } +ktor-server-jetty = { module = "io.ktor:ktor-server-jetty" } ktor-serialization-kotlinx-json = { module = "io.ktor:ktor-serialization-kotlinx-json" } koin-ktor = { module = "io.insert-koin:koin-ktor", version.ref = "koin" } h2 = { module = "com.h2database:h2", version.ref = "h2" } diff --git a/src/main/kotlin/app/revanced/api/command/StartAPICommand.kt b/src/main/kotlin/app/revanced/api/command/StartAPICommand.kt index 2e78e65..d94e8cf 100644 --- a/src/main/kotlin/app/revanced/api/command/StartAPICommand.kt +++ b/src/main/kotlin/app/revanced/api/command/StartAPICommand.kt @@ -6,7 +6,7 @@ import app.revanced.api.configuration.configureSecurity import app.revanced.api.configuration.configureSerialization import app.revanced.api.configuration.routing.configureRouting import io.ktor.server.engine.* -import io.ktor.server.netty.* +import io.ktor.server.jetty.* import picocli.CommandLine @CommandLine.Command( @@ -29,7 +29,7 @@ internal object StartAPICommand : Runnable { private var port: Int = 8080 override fun run() { - embeddedServer(Netty, port, host) { + embeddedServer(Jetty, port, host) { configureDependencies() configureHTTP(allowedHost = host) configureSerialization() diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml index 3e11d78..de52ac6 100644 --- a/src/main/resources/logback.xml +++ b/src/main/resources/logback.xml @@ -8,5 +8,4 @@ - - \ No newline at end of file + From 0e00d045a61d9378072113b260c41464140f90a6 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Thu, 6 Jun 2024 02:45:48 +0200 Subject: [PATCH 043/102] docs: Fix links --- CONTRIBUTING.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index fa880ff..6b1aadd 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -70,7 +70,7 @@ This document describes how to contribute to ReVanced API. ## 🙏 Submitting a feature request Features can be requested by opening an issue using the -[Feature request issue template](https://github.com/ReVanced/revanced-api/issues/new?assignees=&labels=Feature+request&projects=&template=feature-request.yml&title=feat%3A+). +[Feature request issue template](https://github.com/ReVanced/revanced-api/issues/new?assignees=&labels=Feature+request&projects=&template=feature_request.yml&title=feat%3A+). > **Note** > Requests can be accepted or rejected at the discretion of maintainers of ReVanced API. @@ -79,7 +79,7 @@ Features can be requested by opening an issue using the ## 🐞 Submitting a bug report If you encounter a bug while using ReVanced API, open an issue using the -[Bug report issue template](https://github.com/ReVanced/revanced-api/issues/new?assignees=&labels=Bug+report&projects=&template=bug-report.yml&title=bug%3A+). +[Bug report issue template](https://github.com/ReVanced/revanced-api/issues/new?assignees=&labels=Bug+report&projects=&template=bug_report.yml&title=bug%3A+). ## 📝 How to contribute From 9d7b0493498bbf594928fc61181beadf6f59643b Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Thu, 6 Jun 2024 02:46:11 +0200 Subject: [PATCH 044/102] fix: Correct env var comment --- .env.example | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.env.example b/.env.example index 9d7c0e1..0b0ef68 100644 --- a/.env.example +++ b/.env.example @@ -2,7 +2,7 @@ CONFIG_FILE_PATH=configuration.toml # Optional token for API calls to the backend BACKEND_API_TOKEN= -# An option URL to the old API to proxy for migration purposes +# A URL to the old API to proxy for migration purposes OLD_API_URL= # Database connection details From 9825865bbc7505fc3e808d21a46a151151796591 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Thu, 6 Jun 2024 02:47:00 +0200 Subject: [PATCH 045/102] feat: Change default port to avoid using existing port --- docker-compose.yml | 2 +- src/main/kotlin/app/revanced/api/command/StartAPICommand.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 95b9158..6f8b392 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -9,5 +9,5 @@ services: environment: - COMMAND=start ports: - - localhost:8080:8080 + - localhost:8888:8888 restart: unless-stopped diff --git a/src/main/kotlin/app/revanced/api/command/StartAPICommand.kt b/src/main/kotlin/app/revanced/api/command/StartAPICommand.kt index d94e8cf..7c6c018 100644 --- a/src/main/kotlin/app/revanced/api/command/StartAPICommand.kt +++ b/src/main/kotlin/app/revanced/api/command/StartAPICommand.kt @@ -26,7 +26,7 @@ internal object StartAPICommand : Runnable { description = ["The port to listen on."], showDefaultValue = CommandLine.Help.Visibility.ALWAYS, ) - private var port: Int = 8080 + private var port: Int = 8888 override fun run() { embeddedServer(Jetty, port, host) { From 8614e5eed685a4d44f319b4bf6f45ca8ec07e786 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Thu, 6 Jun 2024 02:49:44 +0200 Subject: [PATCH 046/102] chore: Change docker compose file to example --- .gitignore | 3 ++- configuration.example.toml | 17 +++++++++++++---- ...er-compose.yml => docker-compose.example.yml | 0 3 files changed, 15 insertions(+), 5 deletions(-) rename docker-compose.yml => docker-compose.example.yml (100%) diff --git a/.gitignore b/.gitignore index 8f403a3..083ef1c 100644 --- a/.gitignore +++ b/.gitignore @@ -38,4 +38,5 @@ out/ ### Project ### .env persistence/ -configuration.toml \ No newline at end of file +configuration.toml +docker-compose.yml \ No newline at end of file diff --git a/configuration.example.toml b/configuration.example.toml index 5935fd6..dcef633 100644 --- a/configuration.example.toml +++ b/configuration.example.toml @@ -1,5 +1,14 @@ -organization = "org" -patches-repository = "patches" -integrations-repositories = ["integrations"] -contributors-repositories = ["patches", "integrations"] +organization = "revanced" +patches-repository = "revanced-patches" +integrations-repositories = [ + "revanced-integrations" +] +contributors-repositories = [ + "revanced-patcher", + "revanced-patches", + "revanced-integrations", + "revanced-website", + "revanced-cli", + "revanced-manager", +] api-version = 1 diff --git a/docker-compose.yml b/docker-compose.example.yml similarity index 100% rename from docker-compose.yml rename to docker-compose.example.yml From 6a9f0cadac9e09bcf834ef708fab180038ebd4bd Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Thu, 6 Jun 2024 03:36:00 +0200 Subject: [PATCH 047/102] feat: Move config file to CLI argument --- .env.example | 2 -- .../app/revanced/api/command/StartAPICommand.kt | 10 +++++++++- .../app/revanced/api/configuration/Dependencies.kt | 11 +++++------ 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/.env.example b/.env.example index 0b0ef68..a3cb37d 100644 --- a/.env.example +++ b/.env.example @@ -1,5 +1,3 @@ -# File path to store configurations for the API -CONFIG_FILE_PATH=configuration.toml # Optional token for API calls to the backend BACKEND_API_TOKEN= # A URL to the old API to proxy for migration purposes diff --git a/src/main/kotlin/app/revanced/api/command/StartAPICommand.kt b/src/main/kotlin/app/revanced/api/command/StartAPICommand.kt index 7c6c018..f337ce5 100644 --- a/src/main/kotlin/app/revanced/api/command/StartAPICommand.kt +++ b/src/main/kotlin/app/revanced/api/command/StartAPICommand.kt @@ -8,6 +8,7 @@ import app.revanced.api.configuration.routing.configureRouting import io.ktor.server.engine.* import io.ktor.server.jetty.* import picocli.CommandLine +import java.io.File @CommandLine.Command( name = "start", @@ -28,9 +29,16 @@ internal object StartAPICommand : Runnable { ) private var port: Int = 8888 + @CommandLine.Option( + names = ["-c", "--config"], + description = ["The path to the configuration file."], + showDefaultValue = CommandLine.Help.Visibility.ALWAYS, + ) + private var configFile = File("configuration.toml") + override fun run() { embeddedServer(Jetty, port, host) { - configureDependencies() + configureDependencies(configFile) configureHTTP(allowedHost = host) configureSerialization() configureSecurity() diff --git a/src/main/kotlin/app/revanced/api/configuration/Dependencies.kt b/src/main/kotlin/app/revanced/api/configuration/Dependencies.kt index 7cc14f4..7328ea8 100644 --- a/src/main/kotlin/app/revanced/api/configuration/Dependencies.kt +++ b/src/main/kotlin/app/revanced/api/configuration/Dependencies.kt @@ -33,7 +33,9 @@ import org.koin.ktor.plugin.Koin import java.io.File @OptIn(ExperimentalSerializationApi::class) -fun Application.configureDependencies() { +fun Application.configureDependencies( + configFile: File, +) { val globalModule = module { single { Dotenv.configure().load() @@ -99,11 +101,8 @@ fun Application.configureDependencies() { ) } - single { - val configFilePath = get()["CONFIG_FILE_PATH"] - val configFile = File(configFilePath).inputStream() - - Toml.decodeFromStream(configFile) + single { + Toml.decodeFromStream(configFile.inputStream()) } singleOf(::AnnouncementRepository) From 874383e5af5ecfcacb8cedaef09b3aa740065c50 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Thu, 6 Jun 2024 03:43:48 +0200 Subject: [PATCH 048/102] build: Sign builds --- .releaserc | 2 +- Dockerfile | 2 +- build.gradle.kts | 26 +++++++++++++++++++++++--- 3 files changed, 25 insertions(+), 5 deletions(-) diff --git a/.releaserc b/.releaserc index 79200dc..193b3fb 100644 --- a/.releaserc +++ b/.releaserc @@ -32,7 +32,7 @@ { "assets": [ { - "path": "build/libs/*.jar" + "path": "build/libs/*" } ], successComment: false diff --git a/Dockerfile b/Dockerfile index 1dcd313..6d1bc5a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -9,7 +9,7 @@ ENV GITHUB_TOKEN $GITHUB_TOKEN WORKDIR /app COPY . . -RUN gradle publish --no-daemon +RUN gradle startShadowScript --no-daemon # Build the runtime container FROM eclipse-temurin:latest diff --git a/build.gradle.kts b/build.gradle.kts index 16ad9f7..7e1f877 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -2,6 +2,8 @@ plugins { alias(libs.plugins.kotlin) alias(libs.plugins.ktor) alias(libs.plugins.serilization) + `maven-publish` + signing } group = "app.revanced" @@ -13,9 +15,8 @@ tasks { // Needed by gradle-semantic-release-plugin. // Tracking: https://github.com/KengoTODA/gradle-semantic-release-plugin/issues/435 - register("publish") { - group = "publishing" - dependsOn(startShadowScripts) + publish { + dependsOn(shadowJar) } shadowJar { @@ -81,3 +82,22 @@ dependencies { implementation(libs.revanced.library) implementation(libs.caffeine) } + +// The maven-publish plugin is necessary to make signing work. +publishing { + repositories { + mavenLocal() + } + + publications { + create("revanced-api-publication") { + from(components["java"]) + } + } +} + +signing { + useGpgCmd() + + sign(publishing.publications["revanced-api-publication"]) +} From 6c39353ef5ef0479050c28390ddd58713020e86d Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Thu, 6 Jun 2024 03:44:00 +0200 Subject: [PATCH 049/102] chore: Remove version --- gradle.properties | 1 - 1 file changed, 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 65e2d64..3178960 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,3 @@ org.gradle.parallel = true org.gradle.caching = true kotlin.code.style = official -version = 1.0.0 \ No newline at end of file From e9badb4501263f76e57dc308563b409e83a398f9 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Thu, 6 Jun 2024 03:44:29 +0200 Subject: [PATCH 050/102] docs: Add Docker as a method to deploy --- README.md | 71 +++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 61 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 8b7732a..8c830ed 100644 --- a/README.md +++ b/README.md @@ -68,24 +68,74 @@ API server for ReVanced. ## ❓ About ReVanced API is a server that is used as the backend for ReVanced. -ReVanced API acts as the data source for [ReVanced Website](https://github.com/ReVanced/revanced-website) and powers [ReVanced Manager](https://github.com/ReVanced/revanced-manager) with updates and ReVanced Patches. +ReVanced API acts as the data source for [ReVanced Website](https://github.com/ReVanced/revanced-website) and powers [ReVanced Manager](https://github.com/ReVanced/revanced-manager) +with updates and ReVanced Patches. ## 💪 Features Some of the features ReVanced API include: - 📢 **Announcements**: Post and get announcements grouped by channels -- ℹ️ **About**: Get more information such as a description, ways to donate to, and links of the hoster of ReVanced API +- ℹ️ **About**: Get more information such as a description, ways to donate to, +and links of the hoster of ReVanced API - 🧩 **Patches**: Get the latest updates of ReVanced Patches, directly from ReVanced API - 👥 **Contributors**: List all contributors involved in the project - 🔄 **Proxy**: A proxy to an old API for migration purposes and backwards compatibility ## 🚀 How to get started -1. Clone the repository -2. Set up the environment variables in a `.env` file using the `.env.example` file as a template -3. Configure the `configuration.toml` file using the `configuration.toml.example` file as a template -4. Run the server using `gradlew run --args=start` +ReVanced API can be deployed as a Docker container or used standalone. + +## 🐳 Docker + +To deploy ReVanced API as a Docker container, you can use Docker Compose or Docker CLI. +The Docker image is published on GitHub Container registry, +so before you can pull the image, you need to [authenticate to the Container registry](https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-container-registry#authenticating-to-the-container-registry). + +### 🗄️ Docker Compose + +1. Create an `.env` file using [.env.example](.env.example) as a template +2. Create a `configuration.toml` file using [configuration.example.toml](configuration.example.toml) as a template +3. Create a `docker-compose.yml` file using [docker-compose.example.yml](docker-compose.example.yml) as a template +4. Run `docker-compose up -d` to start the server + +### 🐳 Docker CLI + +1. Create an `.env` file using [.env.example](.env.example) as a template +2. Create a `configuration.toml` file using [configuration.example.toml](configuration.example.toml) as a template +3. Start the container using the following command: + + ```shell + docker run -d --name revanced-api \ + # Mount the .env file + -v $(pwd)/.env:/app/.env \ + # Mount the configuration.toml file + -v $(pwd)/configuration.toml:/app/configuration.toml \ + # Mount the persistence folder + -v $(pwd)/persistence:/app/persistence \ + # Expose the port 8888 + -p 8888:8888 \ + ghcr.io/revanced/revanced-api:latest + ``` + +## 🖥️ Standalone + +To deploy ReVanced API standalone, you can either use the pre-built executable or build it from source. + +### 📦 Pre-built executable + +1. [Download](https://github.com/ReVanced/revanced-patches/releases/latest) ReVanced API to a folder +2. In the same folder, create an `.env` file using [.env.example](.env.example) as a template +3. In the same folder, create a `configuration.toml` file +using [configuration.example.toml](configuration.example.toml) as a template +4. Run `java -jar revanced-api.jar start` to start the server + +### 🛠️ From source + +1. Run `git clone git@github.com:ReVanced/revanced-api.git` to clone the repository +2. Copy [.env.example](.env.example) to `.env` and fill in the required values +3. Copy [configuration.example.toml](configuration.example.toml) to `configuration.toml` and fill in the required values +4. Run `gradlew run --args=start` to start the server ## 📚 Everything else @@ -95,14 +145,15 @@ Thank you for considering contributing to ReVanced API. You can find the contrib ### 🛠️ Building -In order to build ReVanced API, follow these steps: +In order to build ReVanced API, Java Development Kit (JDK) and Git must be installed first. +Follow the steps below to build ReVanced API: -1. Clone the repository +1. Run `git clone git@github.com:ReVanced/revanced-api.git` to clone the repository 2. Run `gradlew build` to build the project ## 📜 Licence -ReVanced API is licensed under the GPLv3 licence. Please see the [licence file](LICENSE) for more information. -[tl;dr](https://www.tldrlegal.com/license/gnu-general-public-license-v3-gpl-3) you may copy, distribute and +ReVanced API is licensed under the AGPLv3 licence. Please see the [licence file](LICENSE) for more information. +[tl;dr](https://www.tldrlegal.com/license/gnu-affero-general-public-license-v3-agpl-3-0) you may copy, distribute and modify ReVanced API as long as you track changes/dates in source files. Any modifications to ReVanced API must also be made available under the GPL along with build & install instructions. From 9e1dbb9302be923e9dfb80d9669a3c29c8163e2c Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Thu, 6 Jun 2024 03:46:34 +0200 Subject: [PATCH 051/102] chore: Change license badge to AGPL --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8c830ed..a71e211 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,7 @@ # 🚀 ReVanced API ![GitHub Workflow Status (with event)](https://img.shields.io/github/actions/workflow/status/ReVanced/revanced-api/release.yml) -![GPLv3 License](https://img.shields.io/badge/License-GPL%20v3-yellow.svg) +![AGPLv3 License](https://img.shields.io/badge/License-AGPL%20v3-yellow.svg) API server for ReVanced. From 2b0d595133078ff3f65466337943bca3a2284007 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Thu, 6 Jun 2024 03:47:29 +0200 Subject: [PATCH 052/102] docs: Use a different icon for title --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a71e211..9e49e23 100644 --- a/README.md +++ b/README.md @@ -99,7 +99,7 @@ so before you can pull the image, you need to [authenticate to the Container reg 3. Create a `docker-compose.yml` file using [docker-compose.example.yml](docker-compose.example.yml) as a template 4. Run `docker-compose up -d` to start the server -### 🐳 Docker CLI +### 💻 Docker CLI 1. Create an `.env` file using [.env.example](.env.example) as a template 2. Create a `configuration.toml` file using [configuration.example.toml](configuration.example.toml) as a template From 632001edc82db1b9e732a59f323d7441ff018e55 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Thu, 6 Jun 2024 03:49:24 +0200 Subject: [PATCH 053/102] docs: Add missing command to Docker deployment command --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 9e49e23..0d34b6b 100644 --- a/README.md +++ b/README.md @@ -104,7 +104,6 @@ so before you can pull the image, you need to [authenticate to the Container reg 1. Create an `.env` file using [.env.example](.env.example) as a template 2. Create a `configuration.toml` file using [configuration.example.toml](configuration.example.toml) as a template 3. Start the container using the following command: - ```shell docker run -d --name revanced-api \ # Mount the .env file @@ -115,6 +114,9 @@ so before you can pull the image, you need to [authenticate to the Container reg -v $(pwd)/persistence:/app/persistence \ # Expose the port 8888 -p 8888:8888 \ + # Use the start command to start the server + -e COMMAND=start \ + # Pull the image from the GitHub Container registry ghcr.io/revanced/revanced-api:latest ``` From 282659d621e86d691d7be8bb35e562c1d7b6d4db Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Thu, 6 Jun 2024 03:54:31 +0200 Subject: [PATCH 054/102] docs: Reword feature --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0d34b6b..c56fb0b 100644 --- a/README.md +++ b/README.md @@ -80,7 +80,7 @@ Some of the features ReVanced API include: and links of the hoster of ReVanced API - 🧩 **Patches**: Get the latest updates of ReVanced Patches, directly from ReVanced API - 👥 **Contributors**: List all contributors involved in the project -- 🔄 **Proxy**: A proxy to an old API for migration purposes and backwards compatibility +- 🔄 **Backwards compatibility**: Proxy an old API for migration purposes and backwards compatibility ## 🚀 How to get started From 5976dfd536fcc13c824e2799ade1f7b789b11e20 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Thu, 6 Jun 2024 03:56:04 +0200 Subject: [PATCH 055/102] docs: Fix link to correct repository --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c56fb0b..75a37f3 100644 --- a/README.md +++ b/README.md @@ -126,7 +126,7 @@ To deploy ReVanced API standalone, you can either use the pre-built executable o ### 📦 Pre-built executable -1. [Download](https://github.com/ReVanced/revanced-patches/releases/latest) ReVanced API to a folder +1. [Download](https://github.com/ReVanced/revanced-api/releases/latest) ReVanced API to a folder 2. In the same folder, create an `.env` file using [.env.example](.env.example) as a template 3. In the same folder, create a `configuration.toml` file using [configuration.example.toml](configuration.example.toml) as a template From 6b3dbab90b7e5f1e8739b6ca2a546e2caaa03012 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Thu, 6 Jun 2024 03:59:29 +0200 Subject: [PATCH 056/102] docs: Add prerequisites --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 75a37f3..e520b85 100644 --- a/README.md +++ b/README.md @@ -126,6 +126,8 @@ To deploy ReVanced API standalone, you can either use the pre-built executable o ### 📦 Pre-built executable +A Java Runtime Environment (JRE) must be installed. + 1. [Download](https://github.com/ReVanced/revanced-api/releases/latest) ReVanced API to a folder 2. In the same folder, create an `.env` file using [.env.example](.env.example) as a template 3. In the same folder, create a `configuration.toml` file @@ -134,6 +136,8 @@ using [configuration.example.toml](configuration.example.toml) as a template ### 🛠️ From source +A Java Development Kit (JDK) and Git must be installed. + 1. Run `git clone git@github.com:ReVanced/revanced-api.git` to clone the repository 2. Copy [.env.example](.env.example) to `.env` and fill in the required values 3. Copy [configuration.example.toml](configuration.example.toml) to `configuration.toml` and fill in the required values @@ -147,7 +151,7 @@ Thank you for considering contributing to ReVanced API. You can find the contrib ### 🛠️ Building -In order to build ReVanced API, Java Development Kit (JDK) and Git must be installed first. +To build ReVanced API, a Java Development Kit (JDK) and Git must be installed. Follow the steps below to build ReVanced API: 1. Run `git clone git@github.com:ReVanced/revanced-api.git` to clone the repository From 71f58cf352e06b8504e00582e0c68aec55c4688b Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Thu, 6 Jun 2024 23:20:21 +0200 Subject: [PATCH 057/102] feat: Add GPG key to team members --- .../api/configuration/Dependencies.kt | 4 +- .../revanced/api/configuration/Extensions.kt | 7 + .../api/configuration/Serialization.kt | 1 + .../{backend => }/BackendRepository.kt | 23 +- .../repository/GitHubBackendRepository.kt | 203 ++++++++++++++++++ .../backend/github/GitHubBackendRepository.kt | 83 ------- .../repository/backend/github/api/Request.kt | 27 --- .../repository/backend/github/api/Response.kt | 52 ----- .../routing/routes/Announcements.kt | 17 +- .../api/configuration/schema/APISchema.kt | 8 +- .../api/configuration/services/ApiService.kt | 24 ++- .../configuration/services/PatchesService.kt | 2 +- 12 files changed, 261 insertions(+), 190 deletions(-) create mode 100644 src/main/kotlin/app/revanced/api/configuration/Extensions.kt rename src/main/kotlin/app/revanced/api/configuration/repository/{backend => }/BackendRepository.kt (89%) create mode 100644 src/main/kotlin/app/revanced/api/configuration/repository/GitHubBackendRepository.kt delete mode 100644 src/main/kotlin/app/revanced/api/configuration/repository/backend/github/GitHubBackendRepository.kt delete mode 100644 src/main/kotlin/app/revanced/api/configuration/repository/backend/github/api/Request.kt delete mode 100644 src/main/kotlin/app/revanced/api/configuration/repository/backend/github/api/Response.kt diff --git a/src/main/kotlin/app/revanced/api/configuration/Dependencies.kt b/src/main/kotlin/app/revanced/api/configuration/Dependencies.kt index 7328ea8..0fbed12 100644 --- a/src/main/kotlin/app/revanced/api/configuration/Dependencies.kt +++ b/src/main/kotlin/app/revanced/api/configuration/Dependencies.kt @@ -1,9 +1,9 @@ package app.revanced.api.configuration import app.revanced.api.configuration.repository.AnnouncementRepository +import app.revanced.api.configuration.repository.BackendRepository import app.revanced.api.configuration.repository.ConfigurationRepository -import app.revanced.api.configuration.repository.backend.BackendRepository -import app.revanced.api.configuration.repository.backend.github.GitHubBackendRepository +import app.revanced.api.configuration.repository.GitHubBackendRepository import app.revanced.api.configuration.services.AnnouncementService import app.revanced.api.configuration.services.ApiService import app.revanced.api.configuration.services.AuthService diff --git a/src/main/kotlin/app/revanced/api/configuration/Extensions.kt b/src/main/kotlin/app/revanced/api/configuration/Extensions.kt new file mode 100644 index 0000000..e630bc6 --- /dev/null +++ b/src/main/kotlin/app/revanced/api/configuration/Extensions.kt @@ -0,0 +1,7 @@ +package app.revanced.api.configuration + +import io.ktor.http.* +import io.ktor.server.application.* +import io.ktor.server.response.* + +suspend fun ApplicationCall.respondOrNotFound(value: Any?) = respond(value ?: HttpStatusCode.NotFound) diff --git a/src/main/kotlin/app/revanced/api/configuration/Serialization.kt b/src/main/kotlin/app/revanced/api/configuration/Serialization.kt index 4e9f7ed..43d3eab 100644 --- a/src/main/kotlin/app/revanced/api/configuration/Serialization.kt +++ b/src/main/kotlin/app/revanced/api/configuration/Serialization.kt @@ -13,6 +13,7 @@ fun Application.configureSerialization() { json( Json { namingStrategy = JsonNamingStrategy.SnakeCase + explicitNulls = false }, ) } diff --git a/src/main/kotlin/app/revanced/api/configuration/repository/backend/BackendRepository.kt b/src/main/kotlin/app/revanced/api/configuration/repository/BackendRepository.kt similarity index 89% rename from src/main/kotlin/app/revanced/api/configuration/repository/backend/BackendRepository.kt rename to src/main/kotlin/app/revanced/api/configuration/repository/BackendRepository.kt index 279c354..a024b51 100644 --- a/src/main/kotlin/app/revanced/api/configuration/repository/backend/BackendRepository.kt +++ b/src/main/kotlin/app/revanced/api/configuration/repository/BackendRepository.kt @@ -1,8 +1,7 @@ -package app.revanced.api.configuration.repository.backend +package app.revanced.api.configuration.repository import io.ktor.client.* import kotlinx.datetime.LocalDateTime -import kotlinx.serialization.Serializable /** * The backend of the application used to get data for the API. @@ -40,7 +39,7 @@ abstract class BackendRepository internal constructor( * @property avatarUrl The URL to the avatar of the member. * @property url The URL to the profile of the member. * @property bio The bio of the member. - * @property gpgKeysUrl The URL to the GPG keys of the member. + * @property gpgKeys The GPG key of the member. */ @Serializable class BackendMember( @@ -48,8 +47,19 @@ abstract class BackendRepository internal constructor( override val avatarUrl: String, override val url: String, val bio: String?, - val gpgKeysUrl: String, - ) : BackendUser + val gpgKeys: GpgKeys, + ) : BackendUser { + /** + * The GPG keys of a member. + * + * @property ids The IDs of the GPG keys. + * @property url The URL to the GPG master key. + */ + class GpgKeys( + val ids: Set, + val url: String, + ) + } /** * A repository of an organization. @@ -67,7 +77,6 @@ abstract class BackendRepository internal constructor( * @property url The URL to the profile of the contributor. * @property contributions The number of contributions of the contributor. */ - @Serializable class BackendContributor( override val name: String, override val avatarUrl: String, @@ -83,7 +92,6 @@ abstract class BackendRepository internal constructor( * @property createdAt The date and time the release was created. * @property releaseNote The release note of the release. */ - @Serializable class BackendRelease( val tag: String, val releaseNote: String, @@ -95,7 +103,6 @@ abstract class BackendRepository internal constructor( * * @property downloadUrl The URL to download the asset. */ - @Serializable class BackendAsset( val downloadUrl: String, ) diff --git a/src/main/kotlin/app/revanced/api/configuration/repository/GitHubBackendRepository.kt b/src/main/kotlin/app/revanced/api/configuration/repository/GitHubBackendRepository.kt new file mode 100644 index 0000000..8084fb0 --- /dev/null +++ b/src/main/kotlin/app/revanced/api/configuration/repository/GitHubBackendRepository.kt @@ -0,0 +1,203 @@ +package app.revanced.api.configuration.repository + +import app.revanced.api.configuration.repository.BackendRepository.BackendOrganization.BackendMember +import app.revanced.api.configuration.repository.BackendRepository.BackendOrganization.BackendRepository.BackendContributor +import app.revanced.api.configuration.repository.BackendRepository.BackendOrganization.BackendRepository.BackendRelease +import app.revanced.api.configuration.repository.BackendRepository.BackendOrganization.BackendRepository.BackendRelease.BackendAsset +import app.revanced.api.configuration.repository.GitHubOrganization.GitHubRepository.GitHubContributor +import app.revanced.api.configuration.repository.GitHubOrganization.GitHubRepository.GitHubRelease +import app.revanced.api.configuration.repository.Organization.Repository.Contributors +import app.revanced.api.configuration.repository.Organization.Repository.Releases +import io.ktor.client.* +import io.ktor.client.call.* +import io.ktor.client.plugins.resources.* +import io.ktor.resources.* +import kotlinx.coroutines.* +import kotlinx.datetime.Instant +import kotlinx.datetime.TimeZone +import kotlinx.datetime.toLocalDateTime +import kotlinx.serialization.Serializable + +class GitHubBackendRepository(client: HttpClient) : BackendRepository(client) { + override suspend fun release( + owner: String, + repository: String, + tag: String?, + ): BackendRelease { + val release: GitHubRelease = if (tag != null) { + client.get(Releases.Tag(owner, repository, tag)).body() + } else { + client.get(Releases.Latest(owner, repository)).body() + } + + return BackendRelease( + tag = release.tagName, + releaseNote = release.body, + createdAt = release.createdAt.toLocalDateTime(TimeZone.UTC), + assets = release.assets.map { + BackendAsset(downloadUrl = it.browserDownloadUrl) + }.toSet(), + ) + } + + override suspend fun contributors( + owner: String, + repository: String, + ): Set { + val contributors: Set = client.get( + Contributors( + owner, + repository, + ), + ).body() + + return contributors.map { + BackendContributor( + name = it.login, + avatarUrl = it.avatarUrl, + url = it.htmlUrl, + contributions = it.contributions, + ) + }.toSet() + } + + override suspend fun members(organization: String): Set { + // Get the list of members of the organization. + val members: Set = client.get(Organization.Members(organization)).body() + + return coroutineScope { + members.map { member -> + async { + awaitAll( + async { + // Get the user. + client.get(User(member.login)).body() + }, + async { + // Get the GPG key of the user. + client.get(User.GpgKeys(member.login)).body>() + }, + ) + } + } + }.awaitAll().map { responses -> + val user = responses[0] as GitHubUser + + @Suppress("UNCHECKED_CAST") + val gpgKeys = responses[1] as Set + + BackendMember( + name = user.login, + avatarUrl = user.avatarUrl, + url = user.htmlUrl, + bio = user.bio, + gpgKeys = + BackendMember.GpgKeys( + ids = gpgKeys.map { it.keyId }.toSet(), + url = "https://api.github.com/users/${user.login}.gpg", + ), + ) + }.toSet() + } + + override suspend fun rateLimit(): BackendRateLimit { + val rateLimit: GitHubRateLimit = client.get(RateLimit()).body() + + return BackendRateLimit( + limit = rateLimit.rate.limit, + remaining = rateLimit.rate.remaining, + reset = Instant.fromEpochSeconds(rateLimit.rate.reset).toLocalDateTime(TimeZone.UTC), + ) + } +} + +interface IGitHubUser { + val login: String + val avatarUrl: String + val htmlUrl: String +} + +@Serializable +class GitHubUser( + override val login: String, + override val avatarUrl: String, + override val htmlUrl: String, + val bio: String?, +) : IGitHubUser { + @Serializable + class GitHubGpgKey( + val keyId: String, + ) +} + +class GitHubOrganization { + @Serializable + class GitHubMember( + override val login: String, + override val avatarUrl: String, + override val htmlUrl: String, + ) : IGitHubUser + + class GitHubRepository { + @Serializable + class GitHubContributor( + override val login: String, + override val avatarUrl: String, + override val htmlUrl: String, + val contributions: Int, + ) : IGitHubUser + + @Serializable + class GitHubRelease( + val tagName: String, + val assets: Set, + val createdAt: Instant, + val body: String, + ) { + @Serializable + class GitHubAsset( + val browserDownloadUrl: String, + ) + } + } +} + +@Serializable +class GitHubRateLimit( + val rate: Rate, +) { + @Serializable + class Rate( + val limit: Int, + val remaining: Int, + val reset: Long, + ) +} + +@Resource("/users/{login}") +class User(val login: String) { + @Resource("/users/{login}/gpg_keys") + class GpgKeys(val login: String) +} + +class Organization { + @Resource("/orgs/{org}/members") + class Members(val org: String) + + class Repository { + @Resource("/repos/{owner}/{repo}/contributors") + class Contributors(val owner: String, val repo: String) + + @Resource("/repos/{owner}/{repo}/releases") + class Releases(val owner: String, val repo: String) { + @Resource("/repos/{owner}/{repo}/releases/tags/{tag}") + class Tag(val owner: String, val repo: String, val tag: String) + + @Resource("/repos/{owner}/{repo}/releases/latest") + class Latest(val owner: String, val repo: String) + } + } +} + +@Resource("/rate_limit") +class RateLimit diff --git a/src/main/kotlin/app/revanced/api/configuration/repository/backend/github/GitHubBackendRepository.kt b/src/main/kotlin/app/revanced/api/configuration/repository/backend/github/GitHubBackendRepository.kt deleted file mode 100644 index cb22d58..0000000 --- a/src/main/kotlin/app/revanced/api/configuration/repository/backend/github/GitHubBackendRepository.kt +++ /dev/null @@ -1,83 +0,0 @@ -package app.revanced.api.configuration.repository.backend.github - -import app.revanced.api.configuration.repository.backend.BackendRepository -import app.revanced.api.configuration.repository.backend.BackendRepository.BackendOrganization.BackendMember -import app.revanced.api.configuration.repository.backend.BackendRepository.BackendOrganization.BackendRepository.BackendContributor -import app.revanced.api.configuration.repository.backend.BackendRepository.BackendOrganization.BackendRepository.BackendRelease -import app.revanced.api.configuration.repository.backend.BackendRepository.BackendOrganization.BackendRepository.BackendRelease.BackendAsset -import app.revanced.api.configuration.repository.backend.github.api.Request -import app.revanced.api.configuration.repository.backend.github.api.Request.Organization.Members -import app.revanced.api.configuration.repository.backend.github.api.Request.Organization.Repository.Contributors -import app.revanced.api.configuration.repository.backend.github.api.Request.Organization.Repository.Releases -import app.revanced.api.configuration.repository.backend.github.api.Response -import app.revanced.api.configuration.repository.backend.github.api.Response.GitHubOrganization.GitHubMember -import app.revanced.api.configuration.repository.backend.github.api.Response.GitHubOrganization.GitHubRepository.GitHubContributor -import app.revanced.api.configuration.repository.backend.github.api.Response.GitHubOrganization.GitHubRepository.GitHubRelease -import io.ktor.client.* -import io.ktor.client.call.* -import io.ktor.client.plugins.resources.* -import kotlinx.coroutines.* -import kotlinx.datetime.TimeZone -import kotlinx.datetime.toLocalDateTime - -class GitHubBackendRepository(client: HttpClient) : BackendRepository(client) { - override suspend fun release( - owner: String, - repository: String, - tag: String?, - ): BackendRelease { - val release: GitHubRelease = if (tag != null) { - client.get(Releases.Tag(owner, repository, tag)).body() - } else { - client.get(Releases.Latest(owner, repository)).body() - } - - return BackendRelease( - tag = release.tagName, - releaseNote = release.body, - createdAt = release.createdAt.toLocalDateTime(TimeZone.UTC), - assets = release.assets.map { - BackendAsset(downloadUrl = it.browserDownloadUrl) - }.toSet(), - ) - } - - override suspend fun contributors( - owner: String, - repository: String, - ): Set { - val contributors: Set = client.get(Contributors(owner, repository)).body() - - return contributors.map { - BackendContributor( - name = it.login, - avatarUrl = it.avatarUrl, - url = it.url, - contributions = it.contributions, - ) - }.toSet() - } - - override suspend fun members(organization: String): Set { - // Get the list of members of the organization. - val members: Set = client.get(Members(organization)).body() - - return runBlocking(Dispatchers.Default) { - members.map { member -> - // Map the member to a user in order to get the bio. - async { - client.get(Request.User(member.login)).body() - } - } - }.awaitAll().map { user -> - // Map the user back to a member. - BackendMember( - name = user.login, - avatarUrl = user.avatarUrl, - url = user.url, - bio = user.bio, - gpgKeysUrl = "https://github.com/${user.login}.gpg", - ) - }.toSet() - } -} diff --git a/src/main/kotlin/app/revanced/api/configuration/repository/backend/github/api/Request.kt b/src/main/kotlin/app/revanced/api/configuration/repository/backend/github/api/Request.kt deleted file mode 100644 index 7eb9897..0000000 --- a/src/main/kotlin/app/revanced/api/configuration/repository/backend/github/api/Request.kt +++ /dev/null @@ -1,27 +0,0 @@ -package app.revanced.api.configuration.repository.backend.github.api - -import io.ktor.resources.* - -class Request { - @Resource("/users/{username}") - class User(val username: String) - - class Organization { - @Resource("/orgs/{org}/members") - class Members(val org: String) - - class Repository { - @Resource("/repos/{owner}/{repo}/contributors") - class Contributors(val owner: String, val repo: String) - - @Resource("/repos/{owner}/{repo}/releases") - class Releases(val owner: String, val repo: String) { - @Resource("/repos/{owner}/{repo}/releases/tags/{tag}") - class Tag(val owner: String, val repo: String, val tag: String) - - @Resource("/repos/{owner}/{repo}/releases/latest") - class Latest(val owner: String, val repo: String) - } - } - } -} diff --git a/src/main/kotlin/app/revanced/api/configuration/repository/backend/github/api/Response.kt b/src/main/kotlin/app/revanced/api/configuration/repository/backend/github/api/Response.kt deleted file mode 100644 index 0128ee4..0000000 --- a/src/main/kotlin/app/revanced/api/configuration/repository/backend/github/api/Response.kt +++ /dev/null @@ -1,52 +0,0 @@ -package app.revanced.api.configuration.repository.backend.github.api - -import kotlinx.datetime.Instant -import kotlinx.serialization.Serializable - -class Response { - interface IGitHubUser { - val login: String - val avatarUrl: String - val url: String - } - - @Serializable - class GitHubUser( - override val login: String, - override val avatarUrl: String, - override val url: String, - val bio: String?, - ) : IGitHubUser - - class GitHubOrganization { - @Serializable - class GitHubMember( - override val login: String, - override val avatarUrl: String, - override val url: String, - ) : IGitHubUser - - class GitHubRepository { - @Serializable - class GitHubContributor( - override val login: String, - override val avatarUrl: String, - override val url: String, - val contributions: Int, - ) : IGitHubUser - - @Serializable - class GitHubRelease( - val tagName: String, - val assets: Set, - val createdAt: Instant, - val body: String, - ) { - @Serializable - class GitHubAsset( - val browserDownloadUrl: String, - ) - } - } - } -} diff --git a/src/main/kotlin/app/revanced/api/configuration/routing/routes/Announcements.kt b/src/main/kotlin/app/revanced/api/configuration/routing/routes/Announcements.kt index 560e4dd..0e96856 100644 --- a/src/main/kotlin/app/revanced/api/configuration/routing/routes/Announcements.kt +++ b/src/main/kotlin/app/revanced/api/configuration/routing/routes/Announcements.kt @@ -1,9 +1,9 @@ package app.revanced.api.configuration.routing.routes +import app.revanced.api.configuration.respondOrNotFound import app.revanced.api.configuration.schema.APIAnnouncement import app.revanced.api.configuration.schema.APIAnnouncementArchivedAt import app.revanced.api.configuration.services.AnnouncementService -import io.ktor.http.* import io.ktor.server.application.* import io.ktor.server.auth.* import io.ktor.server.request.* @@ -19,17 +19,13 @@ internal fun Route.announcementsRoute() = route("announcements") { get("id") { val channel: String by call.parameters - call.respond( - announcementService.latestId(channel) ?: return@get call.respond(HttpStatusCode.NotFound), - ) + call.respondOrNotFound(announcementService.latestId(channel)) } get { val channel: String by call.parameters - call.respond( - announcementService.latest(channel) ?: return@get call.respond(HttpStatusCode.NotFound), - ) + call.respondOrNotFound(announcementService.latest(channel)) } } @@ -41,11 +37,11 @@ internal fun Route.announcementsRoute() = route("announcements") { route("latest") { get("id") { - call.respond(announcementService.latestId() ?: return@get call.respond(HttpStatusCode.NotFound)) + call.respondOrNotFound(announcementService.latestId()) } get { - call.respond(announcementService.latest() ?: return@get call.respond(HttpStatusCode.NotFound)) + call.respondOrNotFound(announcementService.latest()) } } @@ -73,8 +69,9 @@ internal fun Route.announcementsRoute() = route("announcements") { patch("{id}") { val id: Int by call.parameters + val announcement = call.receive() - announcementService.update(id, call.receive()) + announcementService.update(id, announcement) } delete("{id}") { diff --git a/src/main/kotlin/app/revanced/api/configuration/schema/APISchema.kt b/src/main/kotlin/app/revanced/api/configuration/schema/APISchema.kt index 8658460..11d8446 100644 --- a/src/main/kotlin/app/revanced/api/configuration/schema/APISchema.kt +++ b/src/main/kotlin/app/revanced/api/configuration/schema/APISchema.kt @@ -23,9 +23,15 @@ class APIMember( override val name: String, override val avatarUrl: String, override val url: String, - val gpgKeysUrl: String, + val gpgKey: APIGpgKey?, ) : APIUser +@Serializable +class APIGpgKey( + val id: String, + val url: String, +) + @Serializable class APIContributor( override val name: String, diff --git a/src/main/kotlin/app/revanced/api/configuration/services/ApiService.kt b/src/main/kotlin/app/revanced/api/configuration/services/ApiService.kt index 7e25b69..940c6e5 100644 --- a/src/main/kotlin/app/revanced/api/configuration/services/ApiService.kt +++ b/src/main/kotlin/app/revanced/api/configuration/services/ApiService.kt @@ -1,10 +1,8 @@ package app.revanced.api.configuration.services +import app.revanced.api.configuration.repository.BackendRepository import app.revanced.api.configuration.repository.ConfigurationRepository -import app.revanced.api.configuration.repository.backend.BackendRepository -import app.revanced.api.configuration.schema.APIContributable -import app.revanced.api.configuration.schema.APIContributor -import app.revanced.api.configuration.schema.APIMember +import app.revanced.api.configuration.schema.* import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll @@ -27,7 +25,21 @@ internal class ApiService( } }.awaitAll() - suspend fun team() = backendRepository.members(configurationRepository.organization).map { - APIMember(it.name, it.avatarUrl, it.url, it.gpgKeysUrl) + suspend fun team() = backendRepository.members(configurationRepository.organization).map { member -> + APIMember( + member.name, + member.avatarUrl, + member.url, + if (member.gpgKeys.ids.isNotEmpty()) { + APIGpgKey( + // Must choose one of the GPG keys, because it does not make sense to have multiple GPG keys for the API. + member.gpgKeys.ids.first(), + member.gpgKeys.url, + ) + } else { + null + }, + + ) } } diff --git a/src/main/kotlin/app/revanced/api/configuration/services/PatchesService.kt b/src/main/kotlin/app/revanced/api/configuration/services/PatchesService.kt index 2281eed..591e4fe 100644 --- a/src/main/kotlin/app/revanced/api/configuration/services/PatchesService.kt +++ b/src/main/kotlin/app/revanced/api/configuration/services/PatchesService.kt @@ -1,7 +1,7 @@ package app.revanced.api.configuration.services +import app.revanced.api.configuration.repository.BackendRepository import app.revanced.api.configuration.repository.ConfigurationRepository -import app.revanced.api.configuration.repository.backend.BackendRepository import app.revanced.api.configuration.schema.APIAsset import app.revanced.api.configuration.schema.APIRelease import app.revanced.api.configuration.schema.APIReleaseVersion From b9671703be9f0842c00c38caa0953a177dc74491 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Thu, 6 Jun 2024 23:21:03 +0200 Subject: [PATCH 058/102] feat: Add backend rate limit route --- .../repository/BackendRepository.kt | 21 ++++++++++++++++++- .../configuration/routing/routes/ApiRoute.kt | 5 +++++ .../api/configuration/schema/APISchema.kt | 7 +++++++ .../api/configuration/services/ApiService.kt | 4 ++++ 4 files changed, 36 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/app/revanced/api/configuration/repository/BackendRepository.kt b/src/main/kotlin/app/revanced/api/configuration/repository/BackendRepository.kt index a024b51..b48ee81 100644 --- a/src/main/kotlin/app/revanced/api/configuration/repository/BackendRepository.kt +++ b/src/main/kotlin/app/revanced/api/configuration/repository/BackendRepository.kt @@ -41,7 +41,6 @@ abstract class BackendRepository internal constructor( * @property bio The bio of the member. * @property gpgKeys The GPG key of the member. */ - @Serializable class BackendMember( override val name: String, override val avatarUrl: String, @@ -110,6 +109,19 @@ abstract class BackendRepository internal constructor( } } + /** + * The rate limit of the backend. + * + * @property limit The limit of the rate limit. + * @property remaining The remaining requests of the rate limit. + * @property reset The date and time the rate limit resets. + */ + class BackendRateLimit( + val limit: Int, + val remaining: Int, + val reset: LocalDateTime, + ) + /** * Get a release of a repository. * @@ -140,4 +152,11 @@ abstract class BackendRepository internal constructor( * @return The members. */ abstract suspend fun members(organization: String): Set + + /** + * Get the rate limit of the backend. + * + * @return The rate limit. + */ + abstract suspend fun rateLimit(): BackendRateLimit? } diff --git a/src/main/kotlin/app/revanced/api/configuration/routing/routes/ApiRoute.kt b/src/main/kotlin/app/revanced/api/configuration/routing/routes/ApiRoute.kt index 762bdff..143e14b 100644 --- a/src/main/kotlin/app/revanced/api/configuration/routing/routes/ApiRoute.kt +++ b/src/main/kotlin/app/revanced/api/configuration/routing/routes/ApiRoute.kt @@ -1,5 +1,6 @@ package app.revanced.api.configuration.routing.routes +import app.revanced.api.configuration.respondOrNotFound import app.revanced.api.configuration.services.ApiService import app.revanced.api.configuration.services.AuthService import io.ktor.http.* @@ -28,6 +29,10 @@ internal fun Route.rootRoute() { } } + get("backend/rate_limit") { + call.respondOrNotFound(apiService.rateLimit()) + } + authenticate("basic") { get("token") { call.respond(authService.newToken()) diff --git a/src/main/kotlin/app/revanced/api/configuration/schema/APISchema.kt b/src/main/kotlin/app/revanced/api/configuration/schema/APISchema.kt index 11d8446..b6018e4 100644 --- a/src/main/kotlin/app/revanced/api/configuration/schema/APISchema.kt +++ b/src/main/kotlin/app/revanced/api/configuration/schema/APISchema.kt @@ -106,3 +106,10 @@ class APILatestAnnouncement( class APIAnnouncementArchivedAt( val archivedAt: LocalDateTime, ) + +@Serializable +class APIRateLimit( + val limit: Int, + val remaining: Int, + val reset: LocalDateTime, +) diff --git a/src/main/kotlin/app/revanced/api/configuration/services/ApiService.kt b/src/main/kotlin/app/revanced/api/configuration/services/ApiService.kt index 940c6e5..bf40861 100644 --- a/src/main/kotlin/app/revanced/api/configuration/services/ApiService.kt +++ b/src/main/kotlin/app/revanced/api/configuration/services/ApiService.kt @@ -42,4 +42,8 @@ internal class ApiService( ) } + + suspend fun rateLimit() = backendRepository.rateLimit()?.let { + APIRateLimit(it.limit, it.remaining, it.reset) + } } From 80403f7130cd48e68e802ee3111760256e49c77d Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Thu, 6 Jun 2024 23:59:06 +0200 Subject: [PATCH 059/102] feat: Add rate limiting to routes --- build.gradle.kts | 2 +- gradle/libs.versions.toml | 2 +- .../app/revanced/api/configuration/HTTP.kt | 14 ++- .../routing/routes/Announcements.kt | 104 ++++++++++-------- .../configuration/routing/routes/ApiRoute.kt | 37 ++++--- .../configuration/routing/routes/OldApi.kt | 9 +- .../routing/routes/PatchesRoute.kt | 21 ++-- 7 files changed, 111 insertions(+), 78 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 7e1f877..8c3226d 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -62,9 +62,9 @@ dependencies { implementation(libs.ktor.server.auth.jwt) implementation(libs.ktor.server.cors) implementation(libs.ktor.server.caching.headers) + implementation(libs.ktor.server.rate.limit) implementation(libs.ktor.server.host.common) implementation(libs.ktor.server.jetty) - implementation(libs.ktor.server.conditional.headers) implementation(libs.ktor.serialization.kotlinx.json) implementation(libs.koin.ktor) implementation(libs.h2) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d800cbf..3d38a95 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -20,13 +20,13 @@ ktor-client-okhttp = { module = "io.ktor:ktor-client-okhttp" } ktor-client-resources = { module = "io.ktor:ktor-client-resources" } ktor-client-auth = { module = "io.ktor:ktor-client-auth" } ktor-client-content-negotiation = { module = "io.ktor:ktor-client-content-negotiation" } -ktor-server-conditional-headers = { module = "io.ktor:ktor-server-conditional-headers" } ktor-server-core = { module = "io.ktor:ktor-server-core" } ktor-server-content-negotiation = { module = "io.ktor:ktor-server-content-negotiation" } ktor-server-auth = { module = "io.ktor:ktor-server-auth" } ktor-server-auth-jwt = { module = "io.ktor:ktor-server-auth-jwt" } ktor-server-cors = { module = "io.ktor:ktor-server-cors" } ktor-server-caching-headers = { module = "io.ktor:ktor-server-caching-headers" } +ktor-server-rate-limit = { module = "io.ktor:ktor-server-rate-limit" } ktor-server-host-common = { module = "io.ktor:ktor-server-host-common" } ktor-server-jetty = { module = "io.ktor:ktor-server-jetty" } ktor-serialization-kotlinx-json = { module = "io.ktor:ktor-serialization-kotlinx-json" } diff --git a/src/main/kotlin/app/revanced/api/configuration/HTTP.kt b/src/main/kotlin/app/revanced/api/configuration/HTTP.kt index 336b100..d61350d 100644 --- a/src/main/kotlin/app/revanced/api/configuration/HTTP.kt +++ b/src/main/kotlin/app/revanced/api/configuration/HTTP.kt @@ -3,15 +3,15 @@ package app.revanced.api.configuration import io.ktor.http.* import io.ktor.http.content.* import io.ktor.server.application.* +import io.ktor.server.plugins.* import io.ktor.server.plugins.cachingheaders.* -import io.ktor.server.plugins.conditionalheaders.* import io.ktor.server.plugins.cors.routing.* +import io.ktor.server.plugins.ratelimit.* import kotlin.time.Duration.Companion.minutes fun Application.configureHTTP( allowedHost: String, ) { - install(ConditionalHeaders) install(CORS) { allowMethod(HttpMethod.Options) allowMethod(HttpMethod.Put) @@ -23,4 +23,14 @@ fun Application.configureHTTP( install(CachingHeaders) { options { _, _ -> CachingOptions(CacheControl.MaxAge(maxAgeSeconds = 5.minutes.inWholeSeconds.toInt())) } } + install(RateLimit) { + register(RateLimitName("weak")) { + rateLimiter(limit = 30, refillPeriod = 2.minutes) + requestKey { it.request.origin.remoteAddress } + } + register(RateLimitName("strong")) { + rateLimiter(limit = 5, refillPeriod = 1.minutes) + requestKey { it.request.origin.remoteHost } + } + } } diff --git a/src/main/kotlin/app/revanced/api/configuration/routing/routes/Announcements.kt b/src/main/kotlin/app/revanced/api/configuration/routing/routes/Announcements.kt index 0e96856..2901942 100644 --- a/src/main/kotlin/app/revanced/api/configuration/routing/routes/Announcements.kt +++ b/src/main/kotlin/app/revanced/api/configuration/routing/routes/Announcements.kt @@ -6,6 +6,7 @@ import app.revanced.api.configuration.schema.APIAnnouncementArchivedAt import app.revanced.api.configuration.services.AnnouncementService import io.ktor.server.application.* import io.ktor.server.auth.* +import io.ktor.server.plugins.ratelimit.* import io.ktor.server.request.* import io.ktor.server.response.* import io.ktor.server.routing.* @@ -15,69 +16,78 @@ import org.koin.ktor.ext.get as koinGet internal fun Route.announcementsRoute() = route("announcements") { val announcementService = koinGet() - route("{channel}/latest") { - get("id") { + rateLimit(RateLimitName("weak")) { + route("{channel}/latest") { + get("id") { + val channel: String by call.parameters + + call.respondOrNotFound(announcementService.latestId(channel)) + } + + get { + val channel: String by call.parameters + + call.respondOrNotFound(announcementService.latest(channel)) + } + } + } + + rateLimit(RateLimitName("strong")) { + get("{channel}") { val channel: String by call.parameters - call.respondOrNotFound(announcementService.latestId(channel)) + call.respond(announcementService.all(channel)) } + } + rateLimit(RateLimitName("strong")) { + route("latest") { + get("id") { + call.respondOrNotFound(announcementService.latestId()) + } + get { + call.respondOrNotFound(announcementService.latest()) + } + } + } + + rateLimit(RateLimitName("strong")) { get { - val channel: String by call.parameters - - call.respondOrNotFound(announcementService.latest(channel)) + call.respond(announcementService.all()) } } - get("{channel}") { - val channel: String by call.parameters + rateLimit(RateLimitName("strong")) { + authenticate("jwt") { + post { + announcementService.new(call.receive()) + } - call.respond(announcementService.all(channel)) - } + post("{id}/archive") { + val id: Int by call.parameters + val archivedAt = call.receiveNullable()?.archivedAt - route("latest") { - get("id") { - call.respondOrNotFound(announcementService.latestId()) - } + announcementService.archive(id, archivedAt) + } - get { - call.respondOrNotFound(announcementService.latest()) - } - } + post("{id}/unarchive") { + val id: Int by call.parameters - get { - call.respond(announcementService.all()) - } + announcementService.unarchive(id) + } - authenticate("jwt") { - post { - announcementService.new(call.receive()) - } + patch("{id}") { + val id: Int by call.parameters + val announcement = call.receive() - post("{id}/archive") { - val id: Int by call.parameters - val archivedAt = call.receiveNullable()?.archivedAt + announcementService.update(id, announcement) + } - announcementService.archive(id, archivedAt) - } + delete("{id}") { + val id: Int by call.parameters - post("{id}/unarchive") { - val id: Int by call.parameters - - announcementService.unarchive(id) - } - - patch("{id}") { - val id: Int by call.parameters - val announcement = call.receive() - - announcementService.update(id, announcement) - } - - delete("{id}") { - val id: Int by call.parameters - - announcementService.delete(id) + announcementService.delete(id) + } } } } diff --git a/src/main/kotlin/app/revanced/api/configuration/routing/routes/ApiRoute.kt b/src/main/kotlin/app/revanced/api/configuration/routing/routes/ApiRoute.kt index 143e14b..2e1ede3 100644 --- a/src/main/kotlin/app/revanced/api/configuration/routing/routes/ApiRoute.kt +++ b/src/main/kotlin/app/revanced/api/configuration/routing/routes/ApiRoute.kt @@ -7,6 +7,7 @@ import io.ktor.http.* import io.ktor.server.application.* import io.ktor.server.auth.* import io.ktor.server.http.content.* +import io.ktor.server.plugins.ratelimit.* import io.ktor.server.response.* import io.ktor.server.routing.* import org.koin.ktor.ext.get @@ -15,12 +16,20 @@ internal fun Route.rootRoute() { val apiService = get() val authService = get() - get("contributors") { - call.respond(apiService.contributors()) - } + rateLimit(RateLimitName("strong")) { + authenticate("basic") { + get("token") { + call.respond(authService.newToken()) + } + } - get("team") { - call.respond(apiService.team()) + get("contributors") { + call.respond(apiService.contributors()) + } + + get("team") { + call.respond(apiService.team()) + } } route("ping") { @@ -29,18 +38,14 @@ internal fun Route.rootRoute() { } } - get("backend/rate_limit") { - call.respondOrNotFound(apiService.rateLimit()) - } + rateLimit(RateLimitName("weak")) { + get("backend/rate_limit") { + call.respondOrNotFound(apiService.rateLimit()) + } - authenticate("basic") { - get("token") { - call.respond(authService.newToken()) + staticResources("/", "/app/revanced/api/static") { + contentType { ContentType.Application.Json } + extensions("json") } } - - staticResources("/", "/app/revanced/api/static") { - contentType { ContentType.Application.Json } - extensions("json") - } } diff --git a/src/main/kotlin/app/revanced/api/configuration/routing/routes/OldApi.kt b/src/main/kotlin/app/revanced/api/configuration/routing/routes/OldApi.kt index fe4f5be..f214f26 100644 --- a/src/main/kotlin/app/revanced/api/configuration/routing/routes/OldApi.kt +++ b/src/main/kotlin/app/revanced/api/configuration/routing/routes/OldApi.kt @@ -2,15 +2,18 @@ package app.revanced.api.configuration.routing.routes import app.revanced.api.configuration.services.OldApiService import io.ktor.server.application.* +import io.ktor.server.plugins.ratelimit.* import io.ktor.server.routing.* import org.koin.ktor.ext.get internal fun Route.oldApiRoute() { val oldApiService = get() - route(Regex("/(v2|tools|contributors).*")) { - handle { - oldApiService.proxy(call) + rateLimit(RateLimitName("weak")) { + route(Regex("/(v2|tools|contributors).*")) { + handle { + oldApiService.proxy(call) + } } } } diff --git a/src/main/kotlin/app/revanced/api/configuration/routing/routes/PatchesRoute.kt b/src/main/kotlin/app/revanced/api/configuration/routing/routes/PatchesRoute.kt index 6567c48..ac3445f 100644 --- a/src/main/kotlin/app/revanced/api/configuration/routing/routes/PatchesRoute.kt +++ b/src/main/kotlin/app/revanced/api/configuration/routing/routes/PatchesRoute.kt @@ -3,6 +3,7 @@ package app.revanced.api.configuration.routing.routes import app.revanced.api.configuration.services.PatchesService import io.ktor.http.* import io.ktor.server.application.* +import io.ktor.server.plugins.ratelimit.* import io.ktor.server.response.* import io.ktor.server.routing.* import org.koin.ktor.ext.get as koinGet @@ -11,16 +12,20 @@ internal fun Route.patchesRoute() = route("patches") { val patchesService = koinGet() route("latest") { - get { - call.respond(patchesService.latestRelease()) + rateLimit(RateLimitName("weak")) { + get { + call.respond(patchesService.latestRelease()) + } + + get("version") { + call.respond(patchesService.latestVersion()) + } } - get("version") { - call.respond(patchesService.latestVersion()) - } - - get("list") { - call.respondBytes(ContentType.Application.Json) { patchesService.list() } + rateLimit(RateLimitName("strong")) { + get("list") { + call.respondBytes(ContentType.Application.Json) { patchesService.list() } + } } } } From 81cc5968d4d994b646986028cda036ab60adcc6b Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Fri, 7 Jun 2024 13:55:50 +0200 Subject: [PATCH 060/102] refactor: Move files to correct folder --- .../kotlin/app/revanced/api/command/StartAPICommand.kt | 2 +- .../api/configuration/{routing => }/Routing.kt | 10 +++++----- .../{routing => }/routes/Announcements.kt | 2 +- .../api/configuration/{routing => }/routes/ApiRoute.kt | 2 +- .../api/configuration/{routing => }/routes/OldApi.kt | 2 +- .../configuration/{routing => }/routes/PatchesRoute.kt | 2 +- 6 files changed, 10 insertions(+), 10 deletions(-) rename src/main/kotlin/app/revanced/api/configuration/{routing => }/Routing.kt (61%) rename src/main/kotlin/app/revanced/api/configuration/{routing => }/routes/Announcements.kt (98%) rename src/main/kotlin/app/revanced/api/configuration/{routing => }/routes/ApiRoute.kt (96%) rename src/main/kotlin/app/revanced/api/configuration/{routing => }/routes/OldApi.kt (89%) rename src/main/kotlin/app/revanced/api/configuration/{routing => }/routes/PatchesRoute.kt (93%) diff --git a/src/main/kotlin/app/revanced/api/command/StartAPICommand.kt b/src/main/kotlin/app/revanced/api/command/StartAPICommand.kt index f337ce5..826a1eb 100644 --- a/src/main/kotlin/app/revanced/api/command/StartAPICommand.kt +++ b/src/main/kotlin/app/revanced/api/command/StartAPICommand.kt @@ -2,9 +2,9 @@ package app.revanced.api.command import app.revanced.api.configuration.configureDependencies import app.revanced.api.configuration.configureHTTP +import app.revanced.api.configuration.configureRouting import app.revanced.api.configuration.configureSecurity import app.revanced.api.configuration.configureSerialization -import app.revanced.api.configuration.routing.configureRouting import io.ktor.server.engine.* import io.ktor.server.jetty.* import picocli.CommandLine diff --git a/src/main/kotlin/app/revanced/api/configuration/routing/Routing.kt b/src/main/kotlin/app/revanced/api/configuration/Routing.kt similarity index 61% rename from src/main/kotlin/app/revanced/api/configuration/routing/Routing.kt rename to src/main/kotlin/app/revanced/api/configuration/Routing.kt index 61e1edb..1aee522 100644 --- a/src/main/kotlin/app/revanced/api/configuration/routing/Routing.kt +++ b/src/main/kotlin/app/revanced/api/configuration/Routing.kt @@ -1,10 +1,10 @@ -package app.revanced.api.configuration.routing +package app.revanced.api.configuration import app.revanced.api.configuration.repository.ConfigurationRepository -import app.revanced.api.configuration.routing.routes.announcementsRoute -import app.revanced.api.configuration.routing.routes.oldApiRoute -import app.revanced.api.configuration.routing.routes.patchesRoute -import app.revanced.api.configuration.routing.routes.rootRoute +import app.revanced.api.configuration.routes.announcementsRoute +import app.revanced.api.configuration.routes.oldApiRoute +import app.revanced.api.configuration.routes.patchesRoute +import app.revanced.api.configuration.routes.rootRoute import io.ktor.server.application.* import io.ktor.server.routing.* import org.koin.ktor.ext.get diff --git a/src/main/kotlin/app/revanced/api/configuration/routing/routes/Announcements.kt b/src/main/kotlin/app/revanced/api/configuration/routes/Announcements.kt similarity index 98% rename from src/main/kotlin/app/revanced/api/configuration/routing/routes/Announcements.kt rename to src/main/kotlin/app/revanced/api/configuration/routes/Announcements.kt index 2901942..7630b6b 100644 --- a/src/main/kotlin/app/revanced/api/configuration/routing/routes/Announcements.kt +++ b/src/main/kotlin/app/revanced/api/configuration/routes/Announcements.kt @@ -1,4 +1,4 @@ -package app.revanced.api.configuration.routing.routes +package app.revanced.api.configuration.routes import app.revanced.api.configuration.respondOrNotFound import app.revanced.api.configuration.schema.APIAnnouncement diff --git a/src/main/kotlin/app/revanced/api/configuration/routing/routes/ApiRoute.kt b/src/main/kotlin/app/revanced/api/configuration/routes/ApiRoute.kt similarity index 96% rename from src/main/kotlin/app/revanced/api/configuration/routing/routes/ApiRoute.kt rename to src/main/kotlin/app/revanced/api/configuration/routes/ApiRoute.kt index 2e1ede3..3271b81 100644 --- a/src/main/kotlin/app/revanced/api/configuration/routing/routes/ApiRoute.kt +++ b/src/main/kotlin/app/revanced/api/configuration/routes/ApiRoute.kt @@ -1,4 +1,4 @@ -package app.revanced.api.configuration.routing.routes +package app.revanced.api.configuration.routes import app.revanced.api.configuration.respondOrNotFound import app.revanced.api.configuration.services.ApiService diff --git a/src/main/kotlin/app/revanced/api/configuration/routing/routes/OldApi.kt b/src/main/kotlin/app/revanced/api/configuration/routes/OldApi.kt similarity index 89% rename from src/main/kotlin/app/revanced/api/configuration/routing/routes/OldApi.kt rename to src/main/kotlin/app/revanced/api/configuration/routes/OldApi.kt index f214f26..e72f470 100644 --- a/src/main/kotlin/app/revanced/api/configuration/routing/routes/OldApi.kt +++ b/src/main/kotlin/app/revanced/api/configuration/routes/OldApi.kt @@ -1,4 +1,4 @@ -package app.revanced.api.configuration.routing.routes +package app.revanced.api.configuration.routes import app.revanced.api.configuration.services.OldApiService import io.ktor.server.application.* diff --git a/src/main/kotlin/app/revanced/api/configuration/routing/routes/PatchesRoute.kt b/src/main/kotlin/app/revanced/api/configuration/routes/PatchesRoute.kt similarity index 93% rename from src/main/kotlin/app/revanced/api/configuration/routing/routes/PatchesRoute.kt rename to src/main/kotlin/app/revanced/api/configuration/routes/PatchesRoute.kt index ac3445f..a5811bf 100644 --- a/src/main/kotlin/app/revanced/api/configuration/routing/routes/PatchesRoute.kt +++ b/src/main/kotlin/app/revanced/api/configuration/routes/PatchesRoute.kt @@ -1,4 +1,4 @@ -package app.revanced.api.configuration.routing.routes +package app.revanced.api.configuration.routes import app.revanced.api.configuration.services.PatchesService import io.ktor.http.* From 205bcde77aad90e0eb49fc25961399f1e37698bb Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Sat, 8 Jun 2024 03:41:48 +0200 Subject: [PATCH 061/102] feat: Setup cors and cache --- configuration.example.toml | 1 + configuration.toml | 1 + .../revanced/api/command/StartAPICommand.kt | 2 +- .../app/revanced/api/configuration/HTTP.kt | 22 ++++------ .../app/revanced/api/configuration/Routing.kt | 12 ++++++ .../repository/ConfigurationRepository.kt | 1 + .../api/configuration/routes/Announcements.kt | 13 ++++++ .../api/configuration/routes/ApiRoute.kt | 40 +++++++++++++++---- 8 files changed, 69 insertions(+), 23 deletions(-) diff --git a/configuration.example.toml b/configuration.example.toml index dcef633..e969f57 100644 --- a/configuration.example.toml +++ b/configuration.example.toml @@ -12,3 +12,4 @@ contributors-repositories = [ "revanced-manager", ] api-version = 1 +host = "*.revanced.app" diff --git a/configuration.toml b/configuration.toml index dcef633..044b6e4 100644 --- a/configuration.toml +++ b/configuration.toml @@ -12,3 +12,4 @@ contributors-repositories = [ "revanced-manager", ] api-version = 1 +host = "*.revanced.app" \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/api/command/StartAPICommand.kt b/src/main/kotlin/app/revanced/api/command/StartAPICommand.kt index 826a1eb..7131be4 100644 --- a/src/main/kotlin/app/revanced/api/command/StartAPICommand.kt +++ b/src/main/kotlin/app/revanced/api/command/StartAPICommand.kt @@ -39,7 +39,7 @@ internal object StartAPICommand : Runnable { override fun run() { embeddedServer(Jetty, port, host) { configureDependencies(configFile) - configureHTTP(allowedHost = host) + configureHTTP() configureSerialization() configureSecurity() configureRouting() diff --git a/src/main/kotlin/app/revanced/api/configuration/HTTP.kt b/src/main/kotlin/app/revanced/api/configuration/HTTP.kt index d61350d..09811a1 100644 --- a/src/main/kotlin/app/revanced/api/configuration/HTTP.kt +++ b/src/main/kotlin/app/revanced/api/configuration/HTTP.kt @@ -1,28 +1,20 @@ package app.revanced.api.configuration -import io.ktor.http.* -import io.ktor.http.content.* +import app.revanced.api.configuration.repository.ConfigurationRepository import io.ktor.server.application.* import io.ktor.server.plugins.* -import io.ktor.server.plugins.cachingheaders.* import io.ktor.server.plugins.cors.routing.* import io.ktor.server.plugins.ratelimit.* +import org.koin.ktor.ext.get import kotlin.time.Duration.Companion.minutes -fun Application.configureHTTP( - allowedHost: String, -) { +fun Application.configureHTTP() { + val configurationRepository = get() + install(CORS) { - allowMethod(HttpMethod.Options) - allowMethod(HttpMethod.Put) - allowMethod(HttpMethod.Delete) - allowMethod(HttpMethod.Patch) - allowHeader(HttpHeaders.Authorization) - allowHost(allowedHost) - } - install(CachingHeaders) { - options { _, _ -> CachingOptions(CacheControl.MaxAge(maxAgeSeconds = 5.minutes.inWholeSeconds.toInt())) } + allowHost(host = configurationRepository.host) } + install(RateLimit) { register(RateLimitName("weak")) { rateLimiter(limit = 30, refillPeriod = 2.minutes) diff --git a/src/main/kotlin/app/revanced/api/configuration/Routing.kt b/src/main/kotlin/app/revanced/api/configuration/Routing.kt index 1aee522..d6dcff8 100644 --- a/src/main/kotlin/app/revanced/api/configuration/Routing.kt +++ b/src/main/kotlin/app/revanced/api/configuration/Routing.kt @@ -5,13 +5,25 @@ import app.revanced.api.configuration.routes.announcementsRoute import app.revanced.api.configuration.routes.oldApiRoute import app.revanced.api.configuration.routes.patchesRoute import app.revanced.api.configuration.routes.rootRoute +import io.ktor.http.* +import io.ktor.http.content.* import io.ktor.server.application.* +import io.ktor.server.plugins.cachingheaders.* import io.ktor.server.routing.* import org.koin.ktor.ext.get +import kotlin.time.Duration.Companion.minutes internal fun Application.configureRouting() = routing { val configuration = get() + install(CachingHeaders) { + options { _, _ -> + CachingOptions( + CacheControl.MaxAge(maxAgeSeconds = 5.minutes.inWholeSeconds.toInt()), + ) + } + } + route("/v${configuration.apiVersion}") { rootRoute() patchesRoute() diff --git a/src/main/kotlin/app/revanced/api/configuration/repository/ConfigurationRepository.kt b/src/main/kotlin/app/revanced/api/configuration/repository/ConfigurationRepository.kt index 3d5f28c..a7a3702 100644 --- a/src/main/kotlin/app/revanced/api/configuration/repository/ConfigurationRepository.kt +++ b/src/main/kotlin/app/revanced/api/configuration/repository/ConfigurationRepository.kt @@ -14,4 +14,5 @@ internal class ConfigurationRepository( val contributorsRepositoryNames: Set, @SerialName("api-version") val apiVersion: Int = 1, + val host: String, ) diff --git a/src/main/kotlin/app/revanced/api/configuration/routes/Announcements.kt b/src/main/kotlin/app/revanced/api/configuration/routes/Announcements.kt index 7630b6b..984eb17 100644 --- a/src/main/kotlin/app/revanced/api/configuration/routes/Announcements.kt +++ b/src/main/kotlin/app/revanced/api/configuration/routes/Announcements.kt @@ -4,18 +4,30 @@ import app.revanced.api.configuration.respondOrNotFound import app.revanced.api.configuration.schema.APIAnnouncement import app.revanced.api.configuration.schema.APIAnnouncementArchivedAt import app.revanced.api.configuration.services.AnnouncementService +import io.ktor.http.* +import io.ktor.http.content.* import io.ktor.server.application.* import io.ktor.server.auth.* +import io.ktor.server.plugins.cachingheaders.* import io.ktor.server.plugins.ratelimit.* import io.ktor.server.request.* import io.ktor.server.response.* import io.ktor.server.routing.* import io.ktor.server.util.* +import kotlin.time.Duration.Companion.minutes import org.koin.ktor.ext.get as koinGet internal fun Route.announcementsRoute() = route("announcements") { val announcementService = koinGet() + install(CachingHeaders) { + options { _, _ -> + CachingOptions( + CacheControl.MaxAge(maxAgeSeconds = 1.minutes.inWholeSeconds.toInt()), + ) + } + } + rateLimit(RateLimitName("weak")) { route("{channel}/latest") { get("id") { @@ -39,6 +51,7 @@ internal fun Route.announcementsRoute() = route("announcements") { call.respond(announcementService.all(channel)) } } + rateLimit(RateLimitName("strong")) { route("latest") { get("id") { diff --git a/src/main/kotlin/app/revanced/api/configuration/routes/ApiRoute.kt b/src/main/kotlin/app/revanced/api/configuration/routes/ApiRoute.kt index 3271b81..2b20b0b 100644 --- a/src/main/kotlin/app/revanced/api/configuration/routes/ApiRoute.kt +++ b/src/main/kotlin/app/revanced/api/configuration/routes/ApiRoute.kt @@ -4,17 +4,21 @@ import app.revanced.api.configuration.respondOrNotFound import app.revanced.api.configuration.services.ApiService import app.revanced.api.configuration.services.AuthService import io.ktor.http.* +import io.ktor.http.content.CachingOptions import io.ktor.server.application.* import io.ktor.server.auth.* import io.ktor.server.http.content.* +import io.ktor.server.plugins.cachingheaders.* +import io.ktor.server.plugins.cors.routing.* import io.ktor.server.plugins.ratelimit.* import io.ktor.server.response.* import io.ktor.server.routing.* -import org.koin.ktor.ext.get +import kotlin.time.Duration.Companion.days +import org.koin.ktor.ext.get as koinGet internal fun Route.rootRoute() { - val apiService = get() - val authService = get() + val apiService = koinGet() + val authService = koinGet() rateLimit(RateLimitName("strong")) { authenticate("basic") { @@ -23,16 +27,38 @@ internal fun Route.rootRoute() { } } - get("contributors") { - call.respond(apiService.contributors()) + route("contributors") { + install(CachingHeaders) { + options { _, _ -> + CachingOptions(CacheControl.MaxAge(maxAgeSeconds = 1.days.inWholeSeconds.toInt())) + } + } + + get { + call.respond(apiService.contributors()) + } } - get("team") { - call.respond(apiService.team()) + route("team") { + install(CachingHeaders) { + options { _, _ -> + CachingOptions(CacheControl.MaxAge(maxAgeSeconds = 1.days.inWholeSeconds.toInt())) + } + } + + get { + call.respond(apiService.team()) + } } } route("ping") { + install(CachingHeaders) { + options { _, _ -> + CachingOptions(CacheControl.NoCache(null)) + } + } + handle { call.respond(HttpStatusCode.NoContent) } From 6ea63be490e7786c6486ee78c1fa38f302e8b81c Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Sun, 9 Jun 2024 01:28:33 +0200 Subject: [PATCH 062/102] feat: Add OpenAPI docs and cache to routes --- build.gradle.kts | 1 + .../app/revanced/api/command/MainCommand.kt | 18 +- .../revanced/api/command/StartAPICommand.kt | 7 +- .../revanced/api/configuration/Extensions.kt | 22 +- .../app/revanced/api/configuration/OpenAPI.kt | 52 +++ .../app/revanced/api/configuration/Routing.kt | 11 +- .../api/configuration/Serialization.kt | 2 + .../repository/AnnouncementRepository.kt | 8 +- .../api/configuration/routes/Announcements.kt | 390 +++++++++++++++--- .../api/configuration/routes/ApiRoute.kt | 126 ++++-- .../api/configuration/routes/PatchesRoute.kt | 67 ++- .../api/configuration/schema/APISchema.kt | 2 +- .../services/AnnouncementService.kt | 6 +- .../api/configuration/services/ApiService.kt | 4 +- 14 files changed, 592 insertions(+), 124 deletions(-) create mode 100644 src/main/kotlin/app/revanced/api/configuration/OpenAPI.kt diff --git a/build.gradle.kts b/build.gradle.kts index 8c3226d..7a42155 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -67,6 +67,7 @@ dependencies { implementation(libs.ktor.server.jetty) implementation(libs.ktor.serialization.kotlinx.json) implementation(libs.koin.ktor) + implementation("io.bkbn:kompendium-core:latest.release") implementation(libs.h2) implementation(libs.logback.classic) implementation(libs.exposed.core) diff --git a/src/main/kotlin/app/revanced/api/command/MainCommand.kt b/src/main/kotlin/app/revanced/api/command/MainCommand.kt index 6b77387..c665597 100644 --- a/src/main/kotlin/app/revanced/api/command/MainCommand.kt +++ b/src/main/kotlin/app/revanced/api/command/MainCommand.kt @@ -3,6 +3,14 @@ package app.revanced.api.command import picocli.CommandLine import java.util.* +internal val applicationVersion = MainCommand::class.java.getResourceAsStream( + "/app/revanced/api/version.properties", +)?.use { stream -> + Properties().apply { + load(stream) + }.getProperty("version") +} ?: "v0.0.0" + fun main(args: Array) { CommandLine(MainCommand).execute(*args).let(System::exit) } @@ -10,15 +18,7 @@ fun main(args: Array) { private object CLIVersionProvider : CommandLine.IVersionProvider { override fun getVersion() = arrayOf( - MainCommand::class.java.getResourceAsStream( - "/app/revanced/api/version.properties", - )?.use { stream -> - Properties().apply { - load(stream) - }.let { - "ReVanced API v${it.getProperty("version")}" - } - } ?: "ReVanced API", + "ReVanced API $applicationVersion", ) } diff --git a/src/main/kotlin/app/revanced/api/command/StartAPICommand.kt b/src/main/kotlin/app/revanced/api/command/StartAPICommand.kt index 7131be4..a220add 100644 --- a/src/main/kotlin/app/revanced/api/command/StartAPICommand.kt +++ b/src/main/kotlin/app/revanced/api/command/StartAPICommand.kt @@ -1,10 +1,6 @@ package app.revanced.api.command -import app.revanced.api.configuration.configureDependencies -import app.revanced.api.configuration.configureHTTP -import app.revanced.api.configuration.configureRouting -import app.revanced.api.configuration.configureSecurity -import app.revanced.api.configuration.configureSerialization +import app.revanced.api.configuration.* import io.ktor.server.engine.* import io.ktor.server.jetty.* import picocli.CommandLine @@ -42,6 +38,7 @@ internal object StartAPICommand : Runnable { configureHTTP() configureSerialization() configureSecurity() + configureOpenAPI() configureRouting() }.start(wait = true) } diff --git a/src/main/kotlin/app/revanced/api/configuration/Extensions.kt b/src/main/kotlin/app/revanced/api/configuration/Extensions.kt index e630bc6..d754693 100644 --- a/src/main/kotlin/app/revanced/api/configuration/Extensions.kt +++ b/src/main/kotlin/app/revanced/api/configuration/Extensions.kt @@ -1,7 +1,27 @@ package app.revanced.api.configuration +import io.bkbn.kompendium.core.plugin.NotarizedRoute import io.ktor.http.* +import io.ktor.http.content.* import io.ktor.server.application.* +import io.ktor.server.plugins.cachingheaders.* import io.ktor.server.response.* +import kotlin.time.Duration -suspend fun ApplicationCall.respondOrNotFound(value: Any?) = respond(value ?: HttpStatusCode.NotFound) +internal suspend fun ApplicationCall.respondOrNotFound(value: Any?) = respond(value ?: HttpStatusCode.NotFound) + +internal fun ApplicationCallPipeline.installCache(maxAge: Duration) = + installCache(CacheControl.MaxAge(maxAgeSeconds = maxAge.inWholeSeconds.toInt())) + +internal fun ApplicationCallPipeline.installNoCache() = + installCache(CacheControl.NoCache(null)) + +internal fun ApplicationCallPipeline.installCache(cacheControl: CacheControl) = + install(CachingHeaders) { + options { _, _ -> + CachingOptions(cacheControl) + } + } + +internal fun ApplicationCallPipeline.installNotarizedRoute(configure: NotarizedRoute.Config.() -> Unit = {}) = + install(NotarizedRoute(), configure) diff --git a/src/main/kotlin/app/revanced/api/configuration/OpenAPI.kt b/src/main/kotlin/app/revanced/api/configuration/OpenAPI.kt new file mode 100644 index 0000000..48bcb88 --- /dev/null +++ b/src/main/kotlin/app/revanced/api/configuration/OpenAPI.kt @@ -0,0 +1,52 @@ +package app.revanced.api.configuration + +import app.revanced.api.command.applicationVersion +import io.bkbn.kompendium.core.plugin.NotarizedApplication +import io.bkbn.kompendium.json.schema.KotlinXSchemaConfigurator +import io.bkbn.kompendium.oas.OpenApiSpec +import io.bkbn.kompendium.oas.component.Components +import io.bkbn.kompendium.oas.info.Contact +import io.bkbn.kompendium.oas.info.Info +import io.bkbn.kompendium.oas.info.License +import io.bkbn.kompendium.oas.security.BasicAuth +import io.bkbn.kompendium.oas.security.BearerAuth +import io.bkbn.kompendium.oas.server.Server +import io.ktor.server.application.* +import java.net.URI + +internal fun Application.configureOpenAPI() { + install(NotarizedApplication()) { + spec = { + OpenApiSpec( + info = Info( + title = "Revanced API", + version = applicationVersion, + description = "API server for ReVanced.", + contact = Contact( + name = "ReVanced", + url = URI("https://revanced.app"), + email = "contact@revanced.app", + ), + license = License( + name = "AGPLv3", + url = URI("https://github.com/ReVanced/revanced-api/blob/main/LICENSE"), + ), + ), + components = Components( + securitySchemes = mutableMapOf( + "bearer" to BearerAuth(), + "basic" to BasicAuth(), + ), + ), + + ).apply { + servers += Server( + url = URI("https://api.revanced.app"), + description = "ReVanced API server.", + ) + } + } + + schemaConfigurator = KotlinXSchemaConfigurator() + } +} diff --git a/src/main/kotlin/app/revanced/api/configuration/Routing.kt b/src/main/kotlin/app/revanced/api/configuration/Routing.kt index d6dcff8..f20dc4c 100644 --- a/src/main/kotlin/app/revanced/api/configuration/Routing.kt +++ b/src/main/kotlin/app/revanced/api/configuration/Routing.kt @@ -5,10 +5,7 @@ import app.revanced.api.configuration.routes.announcementsRoute import app.revanced.api.configuration.routes.oldApiRoute import app.revanced.api.configuration.routes.patchesRoute import app.revanced.api.configuration.routes.rootRoute -import io.ktor.http.* -import io.ktor.http.content.* import io.ktor.server.application.* -import io.ktor.server.plugins.cachingheaders.* import io.ktor.server.routing.* import org.koin.ktor.ext.get import kotlin.time.Duration.Companion.minutes @@ -16,13 +13,7 @@ import kotlin.time.Duration.Companion.minutes internal fun Application.configureRouting() = routing { val configuration = get() - install(CachingHeaders) { - options { _, _ -> - CachingOptions( - CacheControl.MaxAge(maxAgeSeconds = 5.minutes.inWholeSeconds.toInt()), - ) - } - } + installCache(5.minutes) route("/v${configuration.apiVersion}") { rootRoute() diff --git a/src/main/kotlin/app/revanced/api/configuration/Serialization.kt b/src/main/kotlin/app/revanced/api/configuration/Serialization.kt index 43d3eab..ceb6182 100644 --- a/src/main/kotlin/app/revanced/api/configuration/Serialization.kt +++ b/src/main/kotlin/app/revanced/api/configuration/Serialization.kt @@ -1,5 +1,6 @@ package app.revanced.api.configuration +import io.bkbn.kompendium.oas.serialization.KompendiumSerializersModule import io.ktor.serialization.kotlinx.json.* import io.ktor.server.application.* import io.ktor.server.plugins.contentnegotiation.* @@ -12,6 +13,7 @@ fun Application.configureSerialization() { install(ContentNegotiation) { json( Json { + serializersModule = KompendiumSerializersModule.module namingStrategy = JsonNamingStrategy.SnakeCase explicitNulls = false }, diff --git a/src/main/kotlin/app/revanced/api/configuration/repository/AnnouncementRepository.kt b/src/main/kotlin/app/revanced/api/configuration/repository/AnnouncementRepository.kt index 7da8300..eb8475e 100644 --- a/src/main/kotlin/app/revanced/api/configuration/repository/AnnouncementRepository.kt +++ b/src/main/kotlin/app/revanced/api/configuration/repository/AnnouncementRepository.kt @@ -2,8 +2,8 @@ package app.revanced.api.configuration.repository import app.revanced.api.configuration.repository.AnnouncementRepository.AttachmentTable.announcement import app.revanced.api.configuration.schema.APIAnnouncement -import app.revanced.api.configuration.schema.APILatestAnnouncement import app.revanced.api.configuration.schema.APIResponseAnnouncement +import app.revanced.api.configuration.schema.APIResponseAnnouncementId import kotlinx.datetime.* import org.jetbrains.exposed.dao.IntEntity import org.jetbrains.exposed.dao.IntEntityClass @@ -42,6 +42,8 @@ internal class AnnouncementRepository(private val database: Database) { announcement.delete() } + // TODO: These are inefficient, but I'm not sure how to make them more efficient. + fun latest() = transaction { AnnouncementEntity.all().maxByOrNull { it.createdAt }?.toApi() } @@ -52,13 +54,13 @@ internal class AnnouncementRepository(private val database: Database) { fun latestId() = transaction { AnnouncementEntity.all().maxByOrNull { it.createdAt }?.id?.value?.let { - APILatestAnnouncement(it) + APIResponseAnnouncementId(it) } } fun latestId(channel: String) = transaction { AnnouncementEntity.find { AnnouncementTable.channel eq channel }.maxByOrNull { it.createdAt }?.id?.value?.let { - APILatestAnnouncement(it) + APIResponseAnnouncementId(it) } } diff --git a/src/main/kotlin/app/revanced/api/configuration/routes/Announcements.kt b/src/main/kotlin/app/revanced/api/configuration/routes/Announcements.kt index 984eb17..814402c 100644 --- a/src/main/kotlin/app/revanced/api/configuration/routes/Announcements.kt +++ b/src/main/kotlin/app/revanced/api/configuration/routes/Announcements.kt @@ -1,14 +1,22 @@ package app.revanced.api.configuration.routes +import app.revanced.api.configuration.installCache +import app.revanced.api.configuration.installNotarizedRoute import app.revanced.api.configuration.respondOrNotFound import app.revanced.api.configuration.schema.APIAnnouncement import app.revanced.api.configuration.schema.APIAnnouncementArchivedAt +import app.revanced.api.configuration.schema.APIResponseAnnouncement +import app.revanced.api.configuration.schema.APIResponseAnnouncementId import app.revanced.api.configuration.services.AnnouncementService +import io.bkbn.kompendium.core.metadata.DeleteInfo +import io.bkbn.kompendium.core.metadata.GetInfo +import io.bkbn.kompendium.core.metadata.PatchInfo +import io.bkbn.kompendium.core.metadata.PostInfo +import io.bkbn.kompendium.json.schema.definition.TypeDefinition +import io.bkbn.kompendium.oas.payload.Parameter import io.ktor.http.* -import io.ktor.http.content.* import io.ktor.server.application.* import io.ktor.server.auth.* -import io.ktor.server.plugins.cachingheaders.* import io.ktor.server.plugins.ratelimit.* import io.ktor.server.request.* import io.ktor.server.response.* @@ -20,49 +28,9 @@ import org.koin.ktor.ext.get as koinGet internal fun Route.announcementsRoute() = route("announcements") { val announcementService = koinGet() - install(CachingHeaders) { - options { _, _ -> - CachingOptions( - CacheControl.MaxAge(maxAgeSeconds = 1.minutes.inWholeSeconds.toInt()), - ) - } - } + installCache(5.minutes) - rateLimit(RateLimitName("weak")) { - route("{channel}/latest") { - get("id") { - val channel: String by call.parameters - - call.respondOrNotFound(announcementService.latestId(channel)) - } - - get { - val channel: String by call.parameters - - call.respondOrNotFound(announcementService.latest(channel)) - } - } - } - - rateLimit(RateLimitName("strong")) { - get("{channel}") { - val channel: String by call.parameters - - call.respond(announcementService.all(channel)) - } - } - - rateLimit(RateLimitName("strong")) { - route("latest") { - get("id") { - call.respondOrNotFound(announcementService.latestId()) - } - - get { - call.respondOrNotFound(announcementService.latest()) - } - } - } + installAnnouncementsRouteDocumentation() rateLimit(RateLimitName("strong")) { get { @@ -70,37 +38,333 @@ internal fun Route.announcementsRoute() = route("announcements") { } } + rateLimit(RateLimitName("strong")) { + route("{channel}/latest") { + installLatestChannelAnnouncementRouteDocumentation() + + get { + val channel: String by call.parameters + + call.respondOrNotFound(announcementService.latest(channel)) + } + + route("id") { + installLatestChannelAnnouncementIdRouteDocumentation() + + get { + val channel: String by call.parameters + + call.respondOrNotFound(announcementService.latestId(channel)) + } + } + } + } + + rateLimit(RateLimitName("strong")) { + route("{channel}") { + installChannelAnnouncementsRouteDocumentation() + + get { + val channel: String by call.parameters + + call.respond(announcementService.all(channel)) + } + } + } + + rateLimit(RateLimitName("strong")) { + route("latest") { + installLatestAnnouncementRouteDocumentation() + + get { + call.respondOrNotFound(announcementService.latest()) + } + + route("id") { + installLatestAnnouncementIdRouteDocumentation() + + get { + call.respondOrNotFound(announcementService.latestId()) + } + } + } + } + rateLimit(RateLimitName("strong")) { authenticate("jwt") { - post { - announcementService.new(call.receive()) + post { announcement -> + announcementService.new(announcement) } - post("{id}/archive") { - val id: Int by call.parameters - val archivedAt = call.receiveNullable()?.archivedAt + route("{id}") { + installAnnouncementIdRouteDocumentation() - announcementService.archive(id, archivedAt) - } + patch { announcement -> + val id: Int by call.parameters - post("{id}/unarchive") { - val id: Int by call.parameters + announcementService.update(id, announcement) + } - announcementService.unarchive(id) - } + delete { + val id: Int by call.parameters - patch("{id}") { - val id: Int by call.parameters - val announcement = call.receive() + announcementService.delete(id) + } - announcementService.update(id, announcement) - } + route("archive") { + installAnnouncementArchiveRouteDocumentation() - delete("{id}") { - val id: Int by call.parameters + post { + val id: Int by call.parameters + val archivedAt = call.receiveNullable()?.archivedAt - announcementService.delete(id) + announcementService.archive(id, archivedAt) + } + } + + route("unarchive") { + installAnnouncementUnarchiveRouteDocumentation() + + post { + val id: Int by call.parameters + + announcementService.unarchive(id) + } + } } } } } + +private fun Route.installLatestAnnouncementRouteDocumentation() = installNotarizedRoute { + tags = setOf("Announcements") + + get = GetInfo.builder { + description("Get the latest announcement") + summary("Get latest announcement") + response { + responseCode(HttpStatusCode.OK) + mediaTypes("application/json") + description("The latest announcement") + responseType() + } + canRespond { + responseCode(HttpStatusCode.NotFound) + description("No announcement exists") + responseType() + } + } +} + +private fun Route.installLatestAnnouncementIdRouteDocumentation() = installNotarizedRoute { + tags = setOf("Announcements") + + get = GetInfo.builder { + description("Get the id of the latest announcement") + summary("Get id of latest announcement") + response { + responseCode(HttpStatusCode.OK) + mediaTypes("application/json") + description("The id of the latest announcement") + responseType() + } + canRespond { + responseCode(HttpStatusCode.NotFound) + description("No announcement exists") + responseType() + } + } +} + +private fun Route.installChannelAnnouncementsRouteDocumentation() = installNotarizedRoute { + tags = setOf("Announcements") + + parameters = listOf( + Parameter( + name = "channel", + `in` = Parameter.Location.path, + schema = TypeDefinition.STRING, + description = "The channel to get the announcements from", + required = true, + ), + ) + + get = GetInfo.builder { + description("Get the announcements from a channel") + summary("Get announcements from channel") + response { + responseCode(HttpStatusCode.OK) + mediaTypes("application/json") + description("The announcements in the channel") + responseType>() + } + } +} + +private fun Route.installAnnouncementArchiveRouteDocumentation() = installNotarizedRoute { + tags = setOf("Announcements") + + parameters = listOf( + Parameter( + name = "id", + `in` = Parameter.Location.path, + schema = TypeDefinition.INT, + description = "The id of the announcement to archive", + required = true, + ), + Parameter( + name = "archivedAt", + `in` = Parameter.Location.query, + schema = TypeDefinition.STRING, + description = "The date and time the announcement to be archived", + required = false, + ), + ) + + post = PostInfo.builder { + description("Archive an announcement") + summary("Archive announcement") + response { + description("When the announcement was archived") + responseCode(HttpStatusCode.OK) + responseType() + } + } +} + +private fun Route.installAnnouncementUnarchiveRouteDocumentation() = installNotarizedRoute { + tags = setOf("Announcements") + + parameters = listOf( + Parameter( + name = "id", + `in` = Parameter.Location.path, + schema = TypeDefinition.INT, + description = "The id of the announcement to unarchive", + required = true, + ), + ) + + post = PostInfo.builder { + description("Unarchive an announcement") + summary("Unarchive announcement") + response { + description("When announcement was unarchived") + responseCode(HttpStatusCode.OK) + responseType() + } + } +} + +private fun Route.installAnnouncementIdRouteDocumentation() = installNotarizedRoute { + tags = setOf("Announcements") + + parameters = listOf( + Parameter( + name = "id", + `in` = Parameter.Location.path, + schema = TypeDefinition.INT, + description = "The id of the announcement to update", + required = true, + ), + ) + + patch = PatchInfo.builder { + description("Update an announcement") + summary("Update announcement") + request { + requestType() + description("The new announcement") + } + response { + description("When announcement was updated") + responseCode(HttpStatusCode.OK) + responseType() + } + } + + delete = DeleteInfo.builder { + description("Delete an announcement") + summary("Delete announcement") + response { + description("When the announcement was deleted") + responseCode(HttpStatusCode.OK) + responseType() + } + } +} + +private fun Route.installAnnouncementsRouteDocumentation() = installNotarizedRoute { + tags = setOf("Announcements") + + get = GetInfo.builder { + description("Get the announcements") + summary("Get announcement") + response { + responseCode(HttpStatusCode.OK) + mediaTypes("application/json") + description("The announcements") + responseType>() + } + } +} + +private fun Route.installLatestChannelAnnouncementRouteDocumentation() = installNotarizedRoute { + tags = setOf("Announcements") + + parameters = listOf( + Parameter( + name = "channel", + `in` = Parameter.Location.path, + schema = TypeDefinition.STRING, + description = "The channel to get the latest announcement from", + required = true, + ), + ) + + get = GetInfo.builder { + description("Get the latest announcement from a channel") + summary("Get latest channel announcement") + response { + responseCode(HttpStatusCode.OK) + mediaTypes("application/json") + description("The latest announcement in the channel") + responseType() + } + canRespond { + responseCode(HttpStatusCode.NotFound) + description("The channel does not exist") + responseType() + } + } +} + +private fun Route.installLatestChannelAnnouncementIdRouteDocumentation() = installNotarizedRoute { + tags = setOf("Announcements") + + parameters = listOf( + Parameter( + name = "channel", + `in` = Parameter.Location.path, + schema = TypeDefinition.STRING, + description = "The channel to get the latest announcement id from", + required = true, + ), + ) + + get = GetInfo.builder { + description("Get the id of the latest announcement from a channel") + summary("Get id of latest announcement from channel") + response { + responseCode(HttpStatusCode.OK) + mediaTypes("application/json") + description("The id of the latest announcement from the channel") + responseType() + } + canRespond { + responseCode(HttpStatusCode.NotFound) + description("The channel does not exist") + responseType() + } + } +} diff --git a/src/main/kotlin/app/revanced/api/configuration/routes/ApiRoute.kt b/src/main/kotlin/app/revanced/api/configuration/routes/ApiRoute.kt index 2b20b0b..fe2d330 100644 --- a/src/main/kotlin/app/revanced/api/configuration/routes/ApiRoute.kt +++ b/src/main/kotlin/app/revanced/api/configuration/routes/ApiRoute.kt @@ -1,15 +1,19 @@ package app.revanced.api.configuration.routes +import app.revanced.api.configuration.installCache +import app.revanced.api.configuration.installNoCache +import app.revanced.api.configuration.installNotarizedRoute import app.revanced.api.configuration.respondOrNotFound +import app.revanced.api.configuration.schema.APIContributable +import app.revanced.api.configuration.schema.APIMember +import app.revanced.api.configuration.schema.APIRateLimit import app.revanced.api.configuration.services.ApiService import app.revanced.api.configuration.services.AuthService +import io.bkbn.kompendium.core.metadata.* import io.ktor.http.* -import io.ktor.http.content.CachingOptions import io.ktor.server.application.* import io.ktor.server.auth.* import io.ktor.server.http.content.* -import io.ktor.server.plugins.cachingheaders.* -import io.ktor.server.plugins.cors.routing.* import io.ktor.server.plugins.ratelimit.* import io.ktor.server.response.* import io.ktor.server.routing.* @@ -22,17 +26,19 @@ internal fun Route.rootRoute() { rateLimit(RateLimitName("strong")) { authenticate("basic") { - get("token") { - call.respond(authService.newToken()) + route("token") { + installTokenRouteDocumentation() + + get { + call.respond(authService.newToken()) + } } } route("contributors") { - install(CachingHeaders) { - options { _, _ -> - CachingOptions(CacheControl.MaxAge(maxAgeSeconds = 1.days.inWholeSeconds.toInt())) - } - } + installCache(1.days) + + installContributorsRouteDocumentation() get { call.respond(apiService.contributors()) @@ -40,11 +46,9 @@ internal fun Route.rootRoute() { } route("team") { - install(CachingHeaders) { - options { _, _ -> - CachingOptions(CacheControl.MaxAge(maxAgeSeconds = 1.days.inWholeSeconds.toInt())) - } - } + installCache(1.days) + + installTeamRouteDocumentation() get { call.respond(apiService.team()) @@ -53,20 +57,22 @@ internal fun Route.rootRoute() { } route("ping") { - install(CachingHeaders) { - options { _, _ -> - CachingOptions(CacheControl.NoCache(null)) - } - } + installNoCache() - handle { + installPingRouteDocumentation() + + head { call.respond(HttpStatusCode.NoContent) } } rateLimit(RateLimitName("weak")) { - get("backend/rate_limit") { - call.respondOrNotFound(apiService.rateLimit()) + route("backend/rate_limit") { + installRateLimitRouteDocumentation() + + get { + call.respondOrNotFound(apiService.rateLimit()) + } } staticResources("/", "/app/revanced/api/static") { @@ -75,3 +81,77 @@ internal fun Route.rootRoute() { } } } + +fun Route.installRateLimitRouteDocumentation() = installNotarizedRoute { + tags = setOf("API") + + get = GetInfo.builder { + description("Get the rate limit of the backend") + summary("Get rate limit of backend") + response { + description("The rate limit of the backend") + mediaTypes("application/json") + responseCode(HttpStatusCode.OK) + responseType() + } + } +} + +fun Route.installPingRouteDocumentation() = installNotarizedRoute { + tags = setOf("API") + + head = HeadInfo.builder { + description("Ping the server") + summary("Ping") + response { + description("The server is reachable") + responseCode(HttpStatusCode.NoContent) + responseType() + } + } +} + +fun Route.installTeamRouteDocumentation() = installNotarizedRoute { + tags = setOf("API") + + get = GetInfo.builder { + description("Get the list of team members") + summary("Get team members") + response { + description("The list of team members") + mediaTypes("application/json") + responseCode(HttpStatusCode.OK) + responseType>() + } + } +} + +fun Route.installContributorsRouteDocumentation() = installNotarizedRoute { + tags = setOf("API") + + get = GetInfo.builder { + description("Get the list of contributors") + summary("Get contributors") + response { + description("The list of contributors") + mediaTypes("application/json") + responseCode(HttpStatusCode.OK) + responseType>() + } + } +} + +fun Route.installTokenRouteDocumentation() = installNotarizedRoute { + tags = setOf("API") + + get = GetInfo.builder { + description("Get a new authorization token") + summary("Get authorization token") + response { + description("The authorization token") + mediaTypes("application/json") + responseCode(HttpStatusCode.OK) + responseType() + } + } +} diff --git a/src/main/kotlin/app/revanced/api/configuration/routes/PatchesRoute.kt b/src/main/kotlin/app/revanced/api/configuration/routes/PatchesRoute.kt index a5811bf..ab11b32 100644 --- a/src/main/kotlin/app/revanced/api/configuration/routes/PatchesRoute.kt +++ b/src/main/kotlin/app/revanced/api/configuration/routes/PatchesRoute.kt @@ -1,6 +1,10 @@ package app.revanced.api.configuration.routes +import app.revanced.api.configuration.installNotarizedRoute +import app.revanced.api.configuration.schema.APIRelease +import app.revanced.api.configuration.schema.APIReleaseVersion import app.revanced.api.configuration.services.PatchesService +import io.bkbn.kompendium.core.metadata.GetInfo import io.ktor.http.* import io.ktor.server.application.* import io.ktor.server.plugins.ratelimit.* @@ -12,20 +16,75 @@ internal fun Route.patchesRoute() = route("patches") { val patchesService = koinGet() route("latest") { + installLatestPatchesRouteDocumentation() + rateLimit(RateLimitName("weak")) { get { call.respond(patchesService.latestRelease()) } - get("version") { - call.respond(patchesService.latestVersion()) + route("version") { + installLatestPatchesVersionRouteDocumentation() + + get { + call.respond(patchesService.latestVersion()) + } } } rateLimit(RateLimitName("strong")) { - get("list") { - call.respondBytes(ContentType.Application.Json) { patchesService.list() } + route("list") { + installLatestPatchesListRouteDocumentation() + + get { + call.respondBytes(ContentType.Application.Json) { patchesService.list() } + } } } } } + +fun Route.installLatestPatchesRouteDocumentation() = installNotarizedRoute { + tags = setOf("Patches") + + get = GetInfo.builder { + description("Get the latest patches release") + summary("Get latest patches release") + response { + description("The latest patches release") + mediaTypes("application/json") + responseCode(HttpStatusCode.OK) + responseType() + } + } +} + +fun Route.installLatestPatchesVersionRouteDocumentation() = installNotarizedRoute { + tags = setOf("Patches") + + get = GetInfo.builder { + description("Get the latest patches release version") + summary("Get latest patches release version") + response { + description("The latest patches release version") + mediaTypes("application/json") + responseCode(HttpStatusCode.OK) + responseType() + } + } +} + +fun Route.installLatestPatchesListRouteDocumentation() = installNotarizedRoute { + tags = setOf("Patches") + + get = GetInfo.builder { + description("Get the list of patches from the latest patches release") + summary("Get list of patches from latest patches release") + response { + description("The list of patches") + mediaTypes("application/json") + responseCode(HttpStatusCode.OK) + responseType() + } + } +} diff --git a/src/main/kotlin/app/revanced/api/configuration/schema/APISchema.kt b/src/main/kotlin/app/revanced/api/configuration/schema/APISchema.kt index b6018e4..1dad95f 100644 --- a/src/main/kotlin/app/revanced/api/configuration/schema/APISchema.kt +++ b/src/main/kotlin/app/revanced/api/configuration/schema/APISchema.kt @@ -98,7 +98,7 @@ class APIResponseAnnouncement( ) @Serializable -class APILatestAnnouncement( +class APIResponseAnnouncementId( val id: Int, ) diff --git a/src/main/kotlin/app/revanced/api/configuration/services/AnnouncementService.kt b/src/main/kotlin/app/revanced/api/configuration/services/AnnouncementService.kt index 80a3ddb..7a0b460 100644 --- a/src/main/kotlin/app/revanced/api/configuration/services/AnnouncementService.kt +++ b/src/main/kotlin/app/revanced/api/configuration/services/AnnouncementService.kt @@ -2,14 +2,14 @@ package app.revanced.api.configuration.services import app.revanced.api.configuration.repository.AnnouncementRepository import app.revanced.api.configuration.schema.APIAnnouncement -import app.revanced.api.configuration.schema.APILatestAnnouncement +import app.revanced.api.configuration.schema.APIResponseAnnouncementId import kotlinx.datetime.LocalDateTime internal class AnnouncementService( private val announcementRepository: AnnouncementRepository, ) { - fun latestId(channel: String): APILatestAnnouncement? = announcementRepository.latestId(channel) - fun latestId(): APILatestAnnouncement? = announcementRepository.latestId() + fun latestId(channel: String): APIResponseAnnouncementId? = announcementRepository.latestId(channel) + fun latestId(): APIResponseAnnouncementId? = announcementRepository.latestId() fun latest(channel: String) = announcementRepository.latest(channel) fun latest() = announcementRepository.latest() diff --git a/src/main/kotlin/app/revanced/api/configuration/services/ApiService.kt b/src/main/kotlin/app/revanced/api/configuration/services/ApiService.kt index bf40861..10b54cf 100644 --- a/src/main/kotlin/app/revanced/api/configuration/services/ApiService.kt +++ b/src/main/kotlin/app/revanced/api/configuration/services/ApiService.kt @@ -23,7 +23,7 @@ internal class ApiService( ) } } - }.awaitAll() + }.awaitAll().toSet() suspend fun team() = backendRepository.members(configurationRepository.organization).map { member -> APIMember( @@ -41,7 +41,7 @@ internal class ApiService( }, ) - } + }.toSet() suspend fun rateLimit() = backendRepository.rateLimit()?.let { APIRateLimit(it.limit, it.remaining, it.reset) From 17ecf58e550d13dd93ab69e1cf522366aeb3da3f Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Sun, 9 Jun 2024 02:01:11 +0200 Subject: [PATCH 063/102] fix: Fix spelling mistake --- src/main/kotlin/app/revanced/api/configuration/OpenAPI.kt | 2 +- .../app/revanced/api/configuration/routes/Announcements.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/app/revanced/api/configuration/OpenAPI.kt b/src/main/kotlin/app/revanced/api/configuration/OpenAPI.kt index 48bcb88..0719ebc 100644 --- a/src/main/kotlin/app/revanced/api/configuration/OpenAPI.kt +++ b/src/main/kotlin/app/revanced/api/configuration/OpenAPI.kt @@ -19,7 +19,7 @@ internal fun Application.configureOpenAPI() { spec = { OpenApiSpec( info = Info( - title = "Revanced API", + title = "ReVanced API", version = applicationVersion, description = "API server for ReVanced.", contact = Contact( diff --git a/src/main/kotlin/app/revanced/api/configuration/routes/Announcements.kt b/src/main/kotlin/app/revanced/api/configuration/routes/Announcements.kt index 814402c..70a6bc0 100644 --- a/src/main/kotlin/app/revanced/api/configuration/routes/Announcements.kt +++ b/src/main/kotlin/app/revanced/api/configuration/routes/Announcements.kt @@ -299,7 +299,7 @@ private fun Route.installAnnouncementsRouteDocumentation() = installNotarizedRou get = GetInfo.builder { description("Get the announcements") - summary("Get announcement") + summary("Get announcements") response { responseCode(HttpStatusCode.OK) mediaTypes("application/json") From cd5d57f8f87125df361e23715eda6e755203d727 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Sun, 9 Jun 2024 02:03:05 +0200 Subject: [PATCH 064/102] feat: Add local ReVanced API server --- src/main/kotlin/app/revanced/api/configuration/OpenAPI.kt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main/kotlin/app/revanced/api/configuration/OpenAPI.kt b/src/main/kotlin/app/revanced/api/configuration/OpenAPI.kt index 0719ebc..4c02028 100644 --- a/src/main/kotlin/app/revanced/api/configuration/OpenAPI.kt +++ b/src/main/kotlin/app/revanced/api/configuration/OpenAPI.kt @@ -44,6 +44,11 @@ internal fun Application.configureOpenAPI() { url = URI("https://api.revanced.app"), description = "ReVanced API server.", ) + + servers += Server( + url = URI("http://localhost:8888"), + description = "Local ReVanced API server.", + ) } } From f9cae1ea56c93aded25159f6b0814bf84d192192 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Sun, 9 Jun 2024 02:04:45 +0200 Subject: [PATCH 065/102] fix: Remove punctuation --- src/main/kotlin/app/revanced/api/configuration/OpenAPI.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/app/revanced/api/configuration/OpenAPI.kt b/src/main/kotlin/app/revanced/api/configuration/OpenAPI.kt index 4c02028..1dd1fa7 100644 --- a/src/main/kotlin/app/revanced/api/configuration/OpenAPI.kt +++ b/src/main/kotlin/app/revanced/api/configuration/OpenAPI.kt @@ -42,12 +42,12 @@ internal fun Application.configureOpenAPI() { ).apply { servers += Server( url = URI("https://api.revanced.app"), - description = "ReVanced API server.", + description = "ReVanced API server", ) servers += Server( url = URI("http://localhost:8888"), - description = "Local ReVanced API server.", + description = "Local ReVanced API server", ) } } From 4a685a2b538492238421e29f522573604634224a Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Sat, 29 Jun 2024 03:44:55 +0200 Subject: [PATCH 066/102] Send signatures and verify signature of patches file before loading it --- .github/workflows/release.yml | 3 + build.gradle.kts | 5 +- configuration.example.toml | 6 +- configuration.toml | 6 +- docker-compose.example.yml | 2 + gradle/libs.versions.toml | 8 +- .../revanced/api/command/StartAPICommand.kt | 1 + .../api/configuration/Dependencies.kt | 2 + .../app/revanced/api/configuration/Logging.kt | 16 +++ .../repository/BackendRepository.kt | 8 +- .../repository/ConfigurationRepository.kt | 70 +++++++++++- .../repository/GitHubBackendRepository.kt | 6 +- .../api/configuration/routes/PatchesRoute.kt | 30 +++++ .../api/configuration/schema/APISchema.kt | 33 +++--- .../configuration/services/PatchesService.kt | 104 ++++++++++++------ .../services/SignatureService.kt | 72 ++++++++++++ src/main/resources/logback.xml | 3 +- 17 files changed, 302 insertions(+), 73 deletions(-) create mode 100644 src/main/kotlin/app/revanced/api/configuration/Logging.kt create mode 100644 src/main/kotlin/app/revanced/api/configuration/services/SignatureService.kt diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b007442..2a4601f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -81,3 +81,6 @@ jobs: push: true tags: ${{ steps.metadata.outputs.tags }} labels: ${{ steps.metadata.outputs.labels }} + build-args: | + GITHUB_ACTOR=${{ github.actor }} + GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }} diff --git a/build.gradle.kts b/build.gradle.kts index 7a42155..de068fe 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -65,9 +65,10 @@ dependencies { implementation(libs.ktor.server.rate.limit) implementation(libs.ktor.server.host.common) implementation(libs.ktor.server.jetty) + implementation(libs.ktor.server.call.logging) implementation(libs.ktor.serialization.kotlinx.json) implementation(libs.koin.ktor) - implementation("io.bkbn:kompendium-core:latest.release") + implementation(libs.kompendium.core) implementation(libs.h2) implementation(libs.logback.classic) implementation(libs.exposed.core) @@ -82,6 +83,8 @@ dependencies { implementation(libs.revanced.patcher) implementation(libs.revanced.library) implementation(libs.caffeine) + implementation(libs.bouncy.castle.provider) + implementation(libs.bouncy.castle.pgp) } // The maven-publish plugin is necessary to make signing work. diff --git a/configuration.example.toml b/configuration.example.toml index e969f57..1530f04 100644 --- a/configuration.example.toml +++ b/configuration.example.toml @@ -1,8 +1,6 @@ organization = "revanced" -patches-repository = "revanced-patches" -integrations-repositories = [ - "revanced-integrations" -] +patches = { repository = "revanced-patches", asset-regex = "jar$", signature-asset-regex = "asc$", public-key-file = "patches-public-key.asc" } +integrations = { repository = "revanced-integrations", asset-regex = "apk$", signature-asset-regex = "asc$", public-key-file = "integrations-public-key.asc" } contributors-repositories = [ "revanced-patcher", "revanced-patches", diff --git a/configuration.toml b/configuration.toml index 044b6e4..2815595 100644 --- a/configuration.toml +++ b/configuration.toml @@ -1,8 +1,6 @@ organization = "revanced" -patches-repository = "revanced-patches" -integrations-repositories = [ - "revanced-integrations" -] +patches = { repository = "revanced-patches", asset-regex = "jar$", signature-asset-regex = "asc$", public-key-file = "key.asc" } +integrations = { repository = "revanced-integrations", asset-regex = "apk$", signature-asset-regex = "asc$", public-key-file = "key.asc" } contributors-repositories = [ "revanced-patcher", "revanced-patches", diff --git a/docker-compose.example.yml b/docker-compose.example.yml index 6f8b392..c8c032a 100644 --- a/docker-compose.example.yml +++ b/docker-compose.example.yml @@ -6,6 +6,8 @@ services: - /data/revanced-api/persistence:/app/persistence - /data/revanced-api/.env:/app/.env - /data/revanced-api/configuration.toml:/app/configuration.toml + - /data/revanced-api/patches-public-key.asc:/app/patches-public-key.asc + - /data/revanced-api/integrations-public-key.asc:/app/integrations-public-key.asc environment: - COMMAND=start ports: diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 3d38a95..cf64623 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,4 +1,5 @@ [versions] +kompendium-core = "latest.release" kotlin = "2.0.0" logback = "1.4.14" exposed = "0.41.1" @@ -7,13 +8,15 @@ koin = "3.5.3" dotenv = "6.4.1" ktor = "2.3.7" ktoml = "0.5.1" -picocli = "4.7.5" +picocli = "4.7.6" datetime = "0.5.0" revanced-patcher = "19.3.1" revanced-library = "2.3.0" caffeine = "3.1.8" +bouncy-castle = "1.78.1" [libraries] +kompendium-core = { module = "io.bkbn:kompendium-core", version.ref = "kompendium-core" } ktor-client-core = { module = "io.ktor:ktor-client-core" } ktor-client-cio = { module = "io.ktor:ktor-client-cio" } ktor-client-okhttp = { module = "io.ktor:ktor-client-okhttp" } @@ -29,6 +32,7 @@ ktor-server-caching-headers = { module = "io.ktor:ktor-server-caching-headers" } ktor-server-rate-limit = { module = "io.ktor:ktor-server-rate-limit" } ktor-server-host-common = { module = "io.ktor:ktor-server-host-common" } ktor-server-jetty = { module = "io.ktor:ktor-server-jetty" } +ktor-server-call-logging = { module = "io.ktor:ktor-server-call-logging" } ktor-serialization-kotlinx-json = { module = "io.ktor:ktor-serialization-kotlinx-json" } koin-ktor = { module = "io.insert-koin:koin-ktor", version.ref = "koin" } h2 = { module = "com.h2database:h2", version.ref = "h2" } @@ -45,6 +49,8 @@ kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version. revanced-patcher = { module = "app.revanced:revanced-patcher", version.ref = "revanced-patcher" } revanced-library = { module = "app.revanced:revanced-library", version.ref = "revanced-library" } caffeine = { module = "com.github.ben-manes.caffeine:caffeine", version.ref = "caffeine" } +bouncy-castle-provider = { module = "org.bouncycastle:bcprov-jdk18on", version.ref = "bouncy-castle" } +bouncy-castle-pgp = { module = "org.bouncycastle:bcpg-jdk18on", version.ref = "bouncy-castle" } [plugins] serilization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } diff --git a/src/main/kotlin/app/revanced/api/command/StartAPICommand.kt b/src/main/kotlin/app/revanced/api/command/StartAPICommand.kt index a220add..66faa07 100644 --- a/src/main/kotlin/app/revanced/api/command/StartAPICommand.kt +++ b/src/main/kotlin/app/revanced/api/command/StartAPICommand.kt @@ -39,6 +39,7 @@ internal object StartAPICommand : Runnable { configureSerialization() configureSecurity() configureOpenAPI() + configureLogging() configureRouting() }.start(wait = true) } diff --git a/src/main/kotlin/app/revanced/api/configuration/Dependencies.kt b/src/main/kotlin/app/revanced/api/configuration/Dependencies.kt index 0fbed12..24ebb54 100644 --- a/src/main/kotlin/app/revanced/api/configuration/Dependencies.kt +++ b/src/main/kotlin/app/revanced/api/configuration/Dependencies.kt @@ -4,6 +4,7 @@ import app.revanced.api.configuration.repository.AnnouncementRepository import app.revanced.api.configuration.repository.BackendRepository import app.revanced.api.configuration.repository.ConfigurationRepository import app.revanced.api.configuration.repository.GitHubBackendRepository +import app.revanced.api.configuration.services.* import app.revanced.api.configuration.services.AnnouncementService import app.revanced.api.configuration.services.ApiService import app.revanced.api.configuration.services.AuthService @@ -130,6 +131,7 @@ fun Application.configureDependencies( ) } singleOf(::AnnouncementService) + singleOf(::SignatureService) singleOf(::PatchesService) singleOf(::ApiService) } diff --git a/src/main/kotlin/app/revanced/api/configuration/Logging.kt b/src/main/kotlin/app/revanced/api/configuration/Logging.kt new file mode 100644 index 0000000..48b8eb7 --- /dev/null +++ b/src/main/kotlin/app/revanced/api/configuration/Logging.kt @@ -0,0 +1,16 @@ +package app.revanced.api.configuration + +import io.ktor.server.application.* +import io.ktor.server.plugins.callloging.* +import io.ktor.server.request.* + +internal fun Application.configureLogging() { + install(CallLogging) { + format { call -> + val status = call.response.status() + val httpMethod = call.request.httpMethod.value + val uri = call.request.uri + "$status $httpMethod $uri" + } + } +} diff --git a/src/main/kotlin/app/revanced/api/configuration/repository/BackendRepository.kt b/src/main/kotlin/app/revanced/api/configuration/repository/BackendRepository.kt index b48ee81..1ed0469 100644 --- a/src/main/kotlin/app/revanced/api/configuration/repository/BackendRepository.kt +++ b/src/main/kotlin/app/revanced/api/configuration/repository/BackendRepository.kt @@ -4,7 +4,7 @@ import io.ktor.client.* import kotlinx.datetime.LocalDateTime /** - * The backend of the application used to get data for the API. + * The backend of the API used to get data. * * @param client The HTTP client to use for requests. */ @@ -97,12 +97,18 @@ abstract class BackendRepository internal constructor( val createdAt: LocalDateTime, val assets: Set, ) { + companion object { + fun Set.first(assetRegex: Regex) = first { assetRegex.containsMatchIn(it.name) } + } + /** * An asset of a release. * + * @property name The name of the asset. * @property downloadUrl The URL to download the asset. */ class BackendAsset( + val name: String, val downloadUrl: String, ) } diff --git a/src/main/kotlin/app/revanced/api/configuration/repository/ConfigurationRepository.kt b/src/main/kotlin/app/revanced/api/configuration/repository/ConfigurationRepository.kt index a7a3702..eaf70be 100644 --- a/src/main/kotlin/app/revanced/api/configuration/repository/ConfigurationRepository.kt +++ b/src/main/kotlin/app/revanced/api/configuration/repository/ConfigurationRepository.kt @@ -1,18 +1,78 @@ package app.revanced.api.configuration.repository +import app.revanced.api.configuration.services.PatchesService +import kotlinx.serialization.KSerializer import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable +import kotlinx.serialization.descriptors.PrimitiveKind +import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import java.io.File +/** + * The repository storing the configuration for the API. + * + * @property organization The API backends organization name where the repositories for the patches and integrations are. + * @property patches The source of the patches. + * @property integrations The source of the integrations. + * @property contributorsRepositoryNames The names of the repositories to get contributors from. + * @property apiVersion The version to use for the API. + * @property host The host of the API to configure CORS. + */ @Serializable internal class ConfigurationRepository( val organization: String, - @SerialName("patches-repository") - val patchesRepository: String, - @SerialName("integrations-repositories") - val integrationsRepositoryNames: Set, + val patches: AssetConfiguration, + val integrations: AssetConfiguration, @SerialName("contributors-repositories") val contributorsRepositoryNames: Set, @SerialName("api-version") val apiVersion: Int = 1, val host: String, -) +) { + /** + * An asset configuration. + * + * [PatchesService] uses [BackendRepository] to get assets from its releases. + * A release contains multiple assets. + * + * This configuration is used in [ConfigurationRepository] + * to determine which release assets from repositories to get and to verify them. + * + * @property repository The repository in which releases are made to get an asset. + * @property assetRegex The regex matching the asset name. + * @property signatureAssetRegex The regex matching the signature asset name to verify the asset. + * @property publicKeyFile The public key file to verify the signature of the asset. + */ + @Serializable + internal class AssetConfiguration( + val repository: String, + @Serializable(with = RegexSerializer::class) + @SerialName("asset-regex") + val assetRegex: Regex, + @Serializable(with = RegexSerializer::class) + @SerialName("signature-asset-regex") + val signatureAssetRegex: Regex, + @Serializable(with = FileSerializer::class) + @SerialName("public-key-file") + val publicKeyFile: File, + ) +} + +private object RegexSerializer : KSerializer { + override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("Regex", PrimitiveKind.STRING) + + override fun serialize(encoder: Encoder, value: Regex) = encoder.encodeString(value.pattern) + + override fun deserialize(decoder: Decoder) = Regex(decoder.decodeString()) +} + +private object FileSerializer : KSerializer { + override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("File", PrimitiveKind.STRING) + + override fun serialize(encoder: Encoder, value: File) = encoder.encodeString(value.path) + + override fun deserialize(decoder: Decoder) = File(decoder.decodeString()) +} diff --git a/src/main/kotlin/app/revanced/api/configuration/repository/GitHubBackendRepository.kt b/src/main/kotlin/app/revanced/api/configuration/repository/GitHubBackendRepository.kt index 8084fb0..2664afe 100644 --- a/src/main/kotlin/app/revanced/api/configuration/repository/GitHubBackendRepository.kt +++ b/src/main/kotlin/app/revanced/api/configuration/repository/GitHubBackendRepository.kt @@ -35,7 +35,10 @@ class GitHubBackendRepository(client: HttpClient) : BackendRepository(client) { releaseNote = release.body, createdAt = release.createdAt.toLocalDateTime(TimeZone.UTC), assets = release.assets.map { - BackendAsset(downloadUrl = it.browserDownloadUrl) + BackendAsset( + name = it.name, + downloadUrl = it.browserDownloadUrl, + ) }.toSet(), ) } @@ -156,6 +159,7 @@ class GitHubOrganization { ) { @Serializable class GitHubAsset( + val name: String, val browserDownloadUrl: String, ) } diff --git a/src/main/kotlin/app/revanced/api/configuration/routes/PatchesRoute.kt b/src/main/kotlin/app/revanced/api/configuration/routes/PatchesRoute.kt index ab11b32..8419e6a 100644 --- a/src/main/kotlin/app/revanced/api/configuration/routes/PatchesRoute.kt +++ b/src/main/kotlin/app/revanced/api/configuration/routes/PatchesRoute.kt @@ -1,6 +1,8 @@ package app.revanced.api.configuration.routes +import app.revanced.api.configuration.installCache import app.revanced.api.configuration.installNotarizedRoute +import app.revanced.api.configuration.schema.APIAssetPublicKeys import app.revanced.api.configuration.schema.APIRelease import app.revanced.api.configuration.schema.APIReleaseVersion import app.revanced.api.configuration.services.PatchesService @@ -10,6 +12,7 @@ import io.ktor.server.application.* import io.ktor.server.plugins.ratelimit.* import io.ktor.server.response.* import io.ktor.server.routing.* +import kotlin.time.Duration.Companion.days import org.koin.ktor.ext.get as koinGet internal fun Route.patchesRoute() = route("patches") { @@ -42,6 +45,18 @@ internal fun Route.patchesRoute() = route("patches") { } } } + + rateLimit(RateLimitName("strong")) { + route("keys") { + installCache(356.days) + + installPatchesPublicKeyRouteDocumentation() + + get { + call.respond(patchesService.publicKeys()) + } + } + } } fun Route.installLatestPatchesRouteDocumentation() = installNotarizedRoute { @@ -88,3 +103,18 @@ fun Route.installLatestPatchesListRouteDocumentation() = installNotarizedRoute { } } } + +fun Route.installPatchesPublicKeyRouteDocumentation() = installNotarizedRoute { + tags = setOf("Patches") + + get = GetInfo.builder { + description("Get the public keys for verifying patches and integrations assets") + summary("Get patches and integrations public keys") + response { + description("The public keys") + mediaTypes("application/json") + responseCode(HttpStatusCode.OK) + responseType() + } + } +} diff --git a/src/main/kotlin/app/revanced/api/configuration/schema/APISchema.kt b/src/main/kotlin/app/revanced/api/configuration/schema/APISchema.kt index 1dad95f..1230ce1 100644 --- a/src/main/kotlin/app/revanced/api/configuration/schema/APISchema.kt +++ b/src/main/kotlin/app/revanced/api/configuration/schema/APISchema.kt @@ -1,14 +1,13 @@ package app.revanced.api.configuration.schema import kotlinx.datetime.LocalDateTime -import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable class APIRelease( val version: String, val createdAt: LocalDateTime, - val changelog: String, + val description: String, val assets: Set, ) @@ -49,23 +48,15 @@ class APIContributable( @Serializable class APIAsset( val downloadUrl: String, -) { - val type = when { - downloadUrl.endsWith(".jar") -> Type.PATCHES - downloadUrl.endsWith(".apk") -> Type.INTEGRATIONS - else -> Type.UNKNOWN - } + val signatureDownloadUrl: String, + // TODO: Remove this eventually when integrations are merged into patches. + val type: APIAssetType, +) - enum class Type { - @SerialName("patches") - PATCHES, - - @SerialName("integrations") - INTEGRATIONS, - - @SerialName("unknown") - UNKNOWN, - } +@Serializable +enum class APIAssetType { + PATCHES, + INTEGRATION, } @Serializable @@ -113,3 +104,9 @@ class APIRateLimit( val remaining: Int, val reset: LocalDateTime, ) + +@Serializable +class APIAssetPublicKeys( + val patchesPublicKey: String, + val integrationsPublicKey: String, +) diff --git a/src/main/kotlin/app/revanced/api/configuration/services/PatchesService.kt b/src/main/kotlin/app/revanced/api/configuration/services/PatchesService.kt index 591e4fe..abc107d 100644 --- a/src/main/kotlin/app/revanced/api/configuration/services/PatchesService.kt +++ b/src/main/kotlin/app/revanced/api/configuration/services/PatchesService.kt @@ -1,87 +1,119 @@ package app.revanced.api.configuration.services import app.revanced.api.configuration.repository.BackendRepository +import app.revanced.api.configuration.repository.BackendRepository.BackendOrganization.BackendRepository.BackendRelease.Companion.first import app.revanced.api.configuration.repository.ConfigurationRepository -import app.revanced.api.configuration.schema.APIAsset -import app.revanced.api.configuration.schema.APIRelease -import app.revanced.api.configuration.schema.APIReleaseVersion +import app.revanced.api.configuration.schema.* import app.revanced.library.PatchUtils import app.revanced.patcher.PatchBundleLoader import com.github.benmanes.caffeine.cache.Caffeine -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.async -import kotlinx.coroutines.awaitAll -import kotlinx.coroutines.withContext +import io.ktor.util.* import java.io.ByteArrayOutputStream import java.net.URL internal class PatchesService( + private val signatureService: SignatureService, private val backendRepository: BackendRepository, private val configurationRepository: ConfigurationRepository, ) { - private val patchesListCache = Caffeine - .newBuilder() - .maximumSize(1) - .build() - suspend fun latestRelease(): APIRelease { val patchesRelease = backendRepository.release( configurationRepository.organization, - configurationRepository.patchesRepository, + configurationRepository.patches.repository, ) - val integrationsReleases = withContext(Dispatchers.Default) { - configurationRepository.integrationsRepositoryNames.map { - async { backendRepository.release(configurationRepository.organization, it) } - } - }.awaitAll() - val assets = (patchesRelease.assets + integrationsReleases.flatMap { it.assets }) - .map { APIAsset(it.downloadUrl) } - .filter { it.type != APIAsset.Type.UNKNOWN } - .toSet() + val integrationsRelease = backendRepository.release( + configurationRepository.organization, + configurationRepository.integrations.repository, + ) + + fun ConfigurationRepository.AssetConfiguration.asset( + release: BackendRepository.BackendOrganization.BackendRepository.BackendRelease, + assetType: APIAssetType, + ) = APIAsset( + release.assets.first(assetRegex).downloadUrl, + release.assets.first(signatureAssetRegex).downloadUrl, + assetType, + ) + + val patchesAsset = configurationRepository.patches.asset( + patchesRelease, + APIAssetType.PATCHES, + ) + val integrationsAsset = configurationRepository.integrations.asset( + integrationsRelease, + APIAssetType.INTEGRATION, + ) return APIRelease( patchesRelease.tag, patchesRelease.createdAt, patchesRelease.releaseNote, - assets, + setOf(patchesAsset, integrationsAsset), ) } suspend fun latestVersion(): APIReleaseVersion { val patchesRelease = backendRepository.release( configurationRepository.organization, - configurationRepository.patchesRepository, + configurationRepository.patches.repository, ) return APIReleaseVersion(patchesRelease.tag) } + private val patchesListCache = Caffeine + .newBuilder() + .maximumSize(1) + .build() + suspend fun list(): ByteArray { val patchesRelease = backendRepository.release( configurationRepository.organization, - configurationRepository.patchesRepository, + configurationRepository.patches.repository, ) - return patchesListCache.getIfPresent(patchesRelease.tag) ?: run { - val downloadUrl = patchesRelease.assets - .map { APIAsset(it.downloadUrl) } - .find { it.type == APIAsset.Type.PATCHES } - ?.downloadUrl + return patchesListCache.get(patchesRelease.tag) { + val patchesDownloadUrl = patchesRelease.assets + .first(configurationRepository.patches.assetRegex).downloadUrl - val patches = kotlin.io.path.createTempFile().toFile().apply { - outputStream().use { URL(downloadUrl).openStream().copyTo(it) } - }.let { file -> - PatchBundleLoader.Jar(file).also { file.delete() } + val signatureDownloadUrl = patchesRelease.assets + .first(configurationRepository.patches.signatureAssetRegex).downloadUrl + + val patchesFile = kotlin.io.path.createTempFile().toFile().apply { + outputStream().use { URL(patchesDownloadUrl).openStream().copyTo(it) } } + val patches = if ( + signatureService.verify( + patchesFile, + signatureDownloadUrl, + configurationRepository.patches.publicKeyFile, + ) + ) { + PatchBundleLoader.Jar(patchesFile) + } else { + // Use an empty set of patches if the signature is invalid. + emptySet() + } + + patchesFile.delete() + ByteArrayOutputStream().use { stream -> PatchUtils.Json.serialize(patches, outputStream = stream) stream.toByteArray() - }.also { - patchesListCache.put(patchesRelease.tag, it) } } } + + fun publicKeys(): APIAssetPublicKeys { + fun publicKeyBase64(getAssetConfiguration: ConfigurationRepository.() -> ConfigurationRepository.AssetConfiguration) = + configurationRepository.getAssetConfiguration().publicKeyFile.readBytes().encodeBase64() + + return APIAssetPublicKeys( + publicKeyBase64 { patches }, + publicKeyBase64 { integrations }, + ) + } } diff --git a/src/main/kotlin/app/revanced/api/configuration/services/SignatureService.kt b/src/main/kotlin/app/revanced/api/configuration/services/SignatureService.kt new file mode 100644 index 0000000..89ea40e --- /dev/null +++ b/src/main/kotlin/app/revanced/api/configuration/services/SignatureService.kt @@ -0,0 +1,72 @@ +package app.revanced.api.configuration.services + +import com.github.benmanes.caffeine.cache.Caffeine +import org.bouncycastle.jce.provider.BouncyCastleProvider +import org.bouncycastle.openpgp.* +import org.bouncycastle.openpgp.operator.bc.BcKeyFingerprintCalculator +import org.bouncycastle.openpgp.operator.bc.BcPGPContentVerifierBuilderProvider +import java.io.File +import java.io.InputStream +import java.net.URL +import java.security.MessageDigest +import java.security.Security + +internal class SignatureService { + private val signatureCache = Caffeine + .newBuilder() + .maximumSize(2) // Assuming this is enough for patches and integrations. + .build() // Hash -> Verified. + + fun verify( + file: File, + signatureDownloadUrl: String, + publicKeyFile: File, + ): Boolean { + val fileBytes = file.readBytes() + + return signatureCache.get(MessageDigest.getInstance("SHA-256").digest(fileBytes)) { + verify( + fileBytes = fileBytes, + signatureInputStream = URL(signatureDownloadUrl).openStream(), + publicKeyInputStream = publicKeyFile.inputStream(), + ) + } + } + + private fun verify( + fileBytes: ByteArray, + signatureInputStream: InputStream, + publicKeyInputStream: InputStream, + ) = getSignature(signatureInputStream).apply { + init(BcPGPContentVerifierBuilderProvider(), getPublicKey(publicKeyInputStream)) + update(fileBytes) + }.verify() + + private fun getPublicKey(publicKeyInputStream: InputStream): PGPPublicKey { + val decoderStream = PGPUtil.getDecoderStream(publicKeyInputStream) + + PGPPublicKeyRingCollection(decoderStream, BcKeyFingerprintCalculator()).forEach { keyRing -> + keyRing.publicKeys.forEach { publicKey -> + if (publicKey.isEncryptionKey) { + return publicKey + } + } + } + + throw IllegalArgumentException("Can't find encryption key in key ring.") + } + + private fun getSignature(inputStream: InputStream): PGPSignature { + val decoderStream = PGPUtil.getDecoderStream(inputStream) + val pgpObjectFactory = PGPObjectFactory(decoderStream, BcKeyFingerprintCalculator()) + val signatureList = pgpObjectFactory.nextObject() as PGPSignatureList + + return signatureList.first() + } + + private companion object { + init { + Security.addProvider(BouncyCastleProvider()) + } + } +} diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml index de52ac6..6f0c78a 100644 --- a/src/main/resources/logback.xml +++ b/src/main/resources/logback.xml @@ -4,8 +4,7 @@ %d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n - + - From d92c1cf3738efbf87691a29c9dabc34d7a7446b1 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Sat, 29 Jun 2024 04:14:02 +0200 Subject: [PATCH 067/102] Add swagger and redoc routes --- .../kotlin/app/revanced/api/configuration/Routing.kt | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/app/revanced/api/configuration/Routing.kt b/src/main/kotlin/app/revanced/api/configuration/Routing.kt index f20dc4c..2ac2109 100644 --- a/src/main/kotlin/app/revanced/api/configuration/Routing.kt +++ b/src/main/kotlin/app/revanced/api/configuration/Routing.kt @@ -5,22 +5,27 @@ import app.revanced.api.configuration.routes.announcementsRoute import app.revanced.api.configuration.routes.oldApiRoute import app.revanced.api.configuration.routes.patchesRoute import app.revanced.api.configuration.routes.rootRoute +import io.bkbn.kompendium.core.routes.redoc +import io.bkbn.kompendium.core.routes.swagger import io.ktor.server.application.* import io.ktor.server.routing.* -import org.koin.ktor.ext.get import kotlin.time.Duration.Companion.minutes +import org.koin.ktor.ext.get as koinGet internal fun Application.configureRouting() = routing { - val configuration = get() + val configuration = koinGet() installCache(5.minutes) route("/v${configuration.apiVersion}") { - rootRoute() patchesRoute() announcementsRoute() + rootRoute() } + swagger(pageTitle = "ReVanced API", path = "/") + redoc(pageTitle = "ReVanced API", path = "/redoc") + // TODO: Remove, once migration period from v2 API is over (In 1-2 years). oldApiRoute() } From ba936ad134b5d11c879615b17af1c11ba0e23ef5 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Sat, 29 Jun 2024 14:41:18 +0200 Subject: [PATCH 068/102] ci: Add deployment steps --- .github/workflows/release.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2a4601f..ed8fcea 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -84,3 +84,17 @@ jobs: build-args: | GITHUB_ACTOR=${{ github.actor }} GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }} + + - name: Set Portainer stack webhook URL based on branch + run: | + if [[ "${{ github.ref }}" == "refs/heads/main" ]]; then + PORTAINER_WEBHOOK_URL=${{ secrets.PORTAINER_WEBHOOK_MAIN_URL }} + else + PORTAINER_WEBHOOK_URL=${{ secrets.PORTAINER_WEBHOOK_DEV_URL }} + fi + echo "PORTAINER_WEBHOOK_URL=$PORTAINER_WEBHOOK_URL" >> $GITHUB_ENV + + - name: Trigger Portainer stack update + uses: newarifrh/portainer-service-webhook@v1 + with: + webhook_url: ${{ env.PORTAINER_WEBHOOK_URL }} From 7c20e0fe9951817e9f2f5496acbe11b6b1a31d5e Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Sat, 29 Jun 2024 15:51:09 +0200 Subject: [PATCH 069/102] build: Update Kompendium --- gradle/libs.versions.toml | 2 +- .../app/revanced/api/configuration/OpenAPI.kt | 59 +++++++++---------- 2 files changed, 29 insertions(+), 32 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index cf64623..101125f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -kompendium-core = "latest.release" +kompendium-core = "3.14.4" kotlin = "2.0.0" logback = "1.4.14" exposed = "0.41.1" diff --git a/src/main/kotlin/app/revanced/api/configuration/OpenAPI.kt b/src/main/kotlin/app/revanced/api/configuration/OpenAPI.kt index 1dd1fa7..50ee19a 100644 --- a/src/main/kotlin/app/revanced/api/configuration/OpenAPI.kt +++ b/src/main/kotlin/app/revanced/api/configuration/OpenAPI.kt @@ -16,40 +16,37 @@ import java.net.URI internal fun Application.configureOpenAPI() { install(NotarizedApplication()) { - spec = { - OpenApiSpec( - info = Info( - title = "ReVanced API", - version = applicationVersion, - description = "API server for ReVanced.", - contact = Contact( - name = "ReVanced", - url = URI("https://revanced.app"), - email = "contact@revanced.app", - ), - license = License( - name = "AGPLv3", - url = URI("https://github.com/ReVanced/revanced-api/blob/main/LICENSE"), - ), + spec = OpenApiSpec( + info = Info( + title = "ReVanced API", + version = applicationVersion, + description = "API server for ReVanced.", + contact = Contact( + name = "ReVanced", + url = URI("https://revanced.app"), + email = "contact@revanced.app", ), - components = Components( - securitySchemes = mutableMapOf( - "bearer" to BearerAuth(), - "basic" to BasicAuth(), - ), + license = License( + name = "AGPLv3", + url = URI("https://github.com/ReVanced/revanced-api/blob/main/LICENSE"), ), + ), + components = Components( + securitySchemes = mutableMapOf( + "bearer" to BearerAuth(), + "basic" to BasicAuth(), + ), + ), + ).apply { + servers += Server( + url = URI("https://api.revanced.app"), + description = "ReVanced API server", + ) - ).apply { - servers += Server( - url = URI("https://api.revanced.app"), - description = "ReVanced API server", - ) - - servers += Server( - url = URI("http://localhost:8888"), - description = "Local ReVanced API server", - ) - } + servers += Server( + url = URI("http://localhost:8888"), + description = "Local ReVanced API server", + ) } schemaConfigurator = KotlinXSchemaConfigurator() From 744aafa9f081553cf1b5b2d887920350e1660dab Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Sat, 29 Jun 2024 15:51:30 +0200 Subject: [PATCH 070/102] chore: Fix Docker compose port mapping --- docker-compose.example.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.example.yml b/docker-compose.example.yml index c8c032a..a82e58e 100644 --- a/docker-compose.example.yml +++ b/docker-compose.example.yml @@ -11,5 +11,5 @@ services: environment: - COMMAND=start ports: - - localhost:8888:8888 + - 8888:8888 restart: unless-stopped From e9d1c8fae0bc46e761056197658c4bb045784104 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Sat, 29 Jun 2024 23:40:32 +0200 Subject: [PATCH 071/102] fix: Encode defaults to fix OpenAPI spec --- src/main/kotlin/app/revanced/api/configuration/OpenAPI.kt | 5 ----- .../kotlin/app/revanced/api/configuration/Serialization.kt | 1 + 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/src/main/kotlin/app/revanced/api/configuration/OpenAPI.kt b/src/main/kotlin/app/revanced/api/configuration/OpenAPI.kt index 50ee19a..45316ef 100644 --- a/src/main/kotlin/app/revanced/api/configuration/OpenAPI.kt +++ b/src/main/kotlin/app/revanced/api/configuration/OpenAPI.kt @@ -42,11 +42,6 @@ internal fun Application.configureOpenAPI() { url = URI("https://api.revanced.app"), description = "ReVanced API server", ) - - servers += Server( - url = URI("http://localhost:8888"), - description = "Local ReVanced API server", - ) } schemaConfigurator = KotlinXSchemaConfigurator() diff --git a/src/main/kotlin/app/revanced/api/configuration/Serialization.kt b/src/main/kotlin/app/revanced/api/configuration/Serialization.kt index ceb6182..1c4b939 100644 --- a/src/main/kotlin/app/revanced/api/configuration/Serialization.kt +++ b/src/main/kotlin/app/revanced/api/configuration/Serialization.kt @@ -16,6 +16,7 @@ fun Application.configureSerialization() { serializersModule = KompendiumSerializersModule.module namingStrategy = JsonNamingStrategy.SnakeCase explicitNulls = false + encodeDefaults = true }, ) } From 7f8f1ff589431f3825f7075f72c51f5c5dd42756 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Mon, 1 Jul 2024 00:35:28 +0200 Subject: [PATCH 072/102] Use IO context for IO operations --- .../repository/AnnouncementRepository.kt | 35 +++++++------ .../services/AnnouncementService.kt | 22 ++++---- .../configuration/services/PatchesService.kt | 52 ++++++++++--------- 3 files changed, 59 insertions(+), 50 deletions(-) diff --git a/src/main/kotlin/app/revanced/api/configuration/repository/AnnouncementRepository.kt b/src/main/kotlin/app/revanced/api/configuration/repository/AnnouncementRepository.kt index eb8475e..9835454 100644 --- a/src/main/kotlin/app/revanced/api/configuration/repository/AnnouncementRepository.kt +++ b/src/main/kotlin/app/revanced/api/configuration/repository/AnnouncementRepository.kt @@ -4,6 +4,8 @@ import app.revanced.api.configuration.repository.AnnouncementRepository.Attachme import app.revanced.api.configuration.schema.APIAnnouncement import app.revanced.api.configuration.schema.APIResponseAnnouncement import app.revanced.api.configuration.schema.APIResponseAnnouncementId +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.runBlocking import kotlinx.datetime.* import org.jetbrains.exposed.dao.IntEntity import org.jetbrains.exposed.dao.IntEntityClass @@ -11,16 +13,18 @@ import org.jetbrains.exposed.dao.id.EntityID import org.jetbrains.exposed.dao.id.IntIdTable import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.kotlin.datetime.datetime -import org.jetbrains.exposed.sql.transactions.transaction +import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction internal class AnnouncementRepository(private val database: Database) { init { - transaction { - SchemaUtils.create(AnnouncementTable, AttachmentTable) + runBlocking { + transaction { + SchemaUtils.create(AnnouncementTable, AttachmentTable) + } } } - fun all() = transaction { + suspend fun all() = transaction { buildSet { AnnouncementEntity.all().forEach { announcement -> add(announcement.toApi()) @@ -28,7 +32,7 @@ internal class AnnouncementRepository(private val database: Database) { } } - fun all(channel: String) = transaction { + suspend fun all(channel: String) = transaction { buildSet { AnnouncementEntity.find { AnnouncementTable.channel eq channel }.forEach { announcement -> add(announcement.toApi()) @@ -36,7 +40,7 @@ internal class AnnouncementRepository(private val database: Database) { } } - fun delete(id: Int) = transaction { + suspend fun delete(id: Int) = transaction { val announcement = AnnouncementEntity.findById(id) ?: return@transaction announcement.delete() @@ -44,27 +48,27 @@ internal class AnnouncementRepository(private val database: Database) { // TODO: These are inefficient, but I'm not sure how to make them more efficient. - fun latest() = transaction { + suspend fun latest() = transaction { AnnouncementEntity.all().maxByOrNull { it.createdAt }?.toApi() } - fun latest(channel: String) = transaction { + suspend fun latest(channel: String) = transaction { AnnouncementEntity.find { AnnouncementTable.channel eq channel }.maxByOrNull { it.createdAt }?.toApi() } - fun latestId() = transaction { + suspend fun latestId() = transaction { AnnouncementEntity.all().maxByOrNull { it.createdAt }?.id?.value?.let { APIResponseAnnouncementId(it) } } - fun latestId(channel: String) = transaction { + suspend fun latestId(channel: String) = transaction { AnnouncementEntity.find { AnnouncementTable.channel eq channel }.maxByOrNull { it.createdAt }?.id?.value?.let { APIResponseAnnouncementId(it) } } - fun archive( + suspend fun archive( id: Int, archivedAt: LocalDateTime?, ) = transaction { @@ -73,13 +77,13 @@ internal class AnnouncementRepository(private val database: Database) { } } - fun unarchive(id: Int) = transaction { + suspend fun unarchive(id: Int) = transaction { AnnouncementEntity.findById(id)?.apply { archivedAt = null } } - fun new(new: APIAnnouncement) = transaction { + suspend fun new(new: APIAnnouncement) = transaction { AnnouncementEntity.new announcement@{ author = new.author title = new.title @@ -98,7 +102,7 @@ internal class AnnouncementRepository(private val database: Database) { } } - fun update(id: Int, new: APIAnnouncement) = transaction { + suspend fun update(id: Int, new: APIAnnouncement) = transaction { AnnouncementEntity.findById(id)?.apply { author = new.author title = new.title @@ -117,7 +121,8 @@ internal class AnnouncementRepository(private val database: Database) { } } - private fun transaction(block: Transaction.() -> T) = transaction(database, block) + private suspend fun transaction(statement: Transaction.() -> T) = + newSuspendedTransaction(Dispatchers.IO, database, statement = statement) private object AnnouncementTable : IntIdTable() { val author = varchar("author", 32).nullable() diff --git a/src/main/kotlin/app/revanced/api/configuration/services/AnnouncementService.kt b/src/main/kotlin/app/revanced/api/configuration/services/AnnouncementService.kt index 7a0b460..7befd57 100644 --- a/src/main/kotlin/app/revanced/api/configuration/services/AnnouncementService.kt +++ b/src/main/kotlin/app/revanced/api/configuration/services/AnnouncementService.kt @@ -8,28 +8,28 @@ import kotlinx.datetime.LocalDateTime internal class AnnouncementService( private val announcementRepository: AnnouncementRepository, ) { - fun latestId(channel: String): APIResponseAnnouncementId? = announcementRepository.latestId(channel) - fun latestId(): APIResponseAnnouncementId? = announcementRepository.latestId() + suspend fun latestId(channel: String): APIResponseAnnouncementId? = announcementRepository.latestId(channel) + suspend fun latestId(): APIResponseAnnouncementId? = announcementRepository.latestId() - fun latest(channel: String) = announcementRepository.latest(channel) - fun latest() = announcementRepository.latest() + suspend fun latest(channel: String) = announcementRepository.latest(channel) + suspend fun latest() = announcementRepository.latest() - fun all(channel: String) = announcementRepository.all(channel) - fun all() = announcementRepository.all() + suspend fun all(channel: String) = announcementRepository.all(channel) + suspend fun all() = announcementRepository.all() - fun new(new: APIAnnouncement) { + suspend fun new(new: APIAnnouncement) { announcementRepository.new(new) } - fun archive(id: Int, archivedAt: LocalDateTime?) { + suspend fun archive(id: Int, archivedAt: LocalDateTime?) { announcementRepository.archive(id, archivedAt) } - fun unarchive(id: Int) { + suspend fun unarchive(id: Int) { announcementRepository.unarchive(id) } - fun update(id: Int, new: APIAnnouncement) { + suspend fun update(id: Int, new: APIAnnouncement) { announcementRepository.update(id, new) } - fun delete(id: Int) { + suspend fun delete(id: Int) { announcementRepository.delete(id) } } diff --git a/src/main/kotlin/app/revanced/api/configuration/services/PatchesService.kt b/src/main/kotlin/app/revanced/api/configuration/services/PatchesService.kt index abc107d..2024b67 100644 --- a/src/main/kotlin/app/revanced/api/configuration/services/PatchesService.kt +++ b/src/main/kotlin/app/revanced/api/configuration/services/PatchesService.kt @@ -8,6 +8,8 @@ import app.revanced.library.PatchUtils import app.revanced.patcher.PatchBundleLoader import com.github.benmanes.caffeine.cache.Caffeine import io.ktor.util.* +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext import java.io.ByteArrayOutputStream import java.net.URL @@ -73,36 +75,38 @@ internal class PatchesService( configurationRepository.patches.repository, ) - return patchesListCache.get(patchesRelease.tag) { - val patchesDownloadUrl = patchesRelease.assets - .first(configurationRepository.patches.assetRegex).downloadUrl + return withContext(Dispatchers.IO) { + patchesListCache.get(patchesRelease.tag) { + val patchesDownloadUrl = patchesRelease.assets + .first(configurationRepository.patches.assetRegex).downloadUrl - val signatureDownloadUrl = patchesRelease.assets - .first(configurationRepository.patches.signatureAssetRegex).downloadUrl + val signatureDownloadUrl = patchesRelease.assets + .first(configurationRepository.patches.signatureAssetRegex).downloadUrl - val patchesFile = kotlin.io.path.createTempFile().toFile().apply { - outputStream().use { URL(patchesDownloadUrl).openStream().copyTo(it) } - } + val patchesFile = kotlin.io.path.createTempFile().toFile().apply { + outputStream().use { URL(patchesDownloadUrl).openStream().copyTo(it) } + } - val patches = if ( - signatureService.verify( - patchesFile, - signatureDownloadUrl, - configurationRepository.patches.publicKeyFile, - ) - ) { - PatchBundleLoader.Jar(patchesFile) - } else { - // Use an empty set of patches if the signature is invalid. - emptySet() - } + val patches = if ( + signatureService.verify( + patchesFile, + signatureDownloadUrl, + configurationRepository.patches.publicKeyFile, + ) + ) { + PatchBundleLoader.Jar(patchesFile) + } else { + // Use an empty set of patches if the signature is invalid. + emptySet() + } - patchesFile.delete() + patchesFile.delete() - ByteArrayOutputStream().use { stream -> - PatchUtils.Json.serialize(patches, outputStream = stream) + ByteArrayOutputStream().use { stream -> + PatchUtils.Json.serialize(patches, outputStream = stream) - stream.toByteArray() + stream.toByteArray() + } } } } From 1ec6999ddbc1f50410ad7ca8447b8cefde51722d Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Mon, 1 Jul 2024 00:44:14 +0200 Subject: [PATCH 073/102] Bump packages and add files to .gitignore --- .gitignore | 4 +++- gradle/libs.versions.toml | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 083ef1c..ef1e3e8 100644 --- a/.gitignore +++ b/.gitignore @@ -39,4 +39,6 @@ out/ .env persistence/ configuration.toml -docker-compose.yml \ No newline at end of file +docker-compose.yml +patches-public-key.asc +integrations-public-key.asc \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 101125f..df974ce 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -9,7 +9,7 @@ dotenv = "6.4.1" ktor = "2.3.7" ktoml = "0.5.1" picocli = "4.7.6" -datetime = "0.5.0" +datetime = "0.6.0" revanced-patcher = "19.3.1" revanced-library = "2.3.0" caffeine = "3.1.8" From df325317702cea0d1317255e03c7b27e44e97d6b Mon Sep 17 00:00:00 2001 From: Ushie Date: Sun, 7 Jul 2024 21:28:04 +0300 Subject: [PATCH 074/102] build: Publish Docker images with semantic-release (#179) --- .github/workflows/release.yml | 44 ++++++++--------------------------- .releaserc | 22 ++++++++++++++---- 2 files changed, 27 insertions(+), 39 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ed8fcea..531a377 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -35,7 +35,7 @@ jobs: uses: actions/setup-node@v4 with: node-version: "lts/*" - cache: 'npm' + cache: "npm" - name: Install dependencies run: npm install @@ -52,39 +52,6 @@ jobs: GITHUB_TOKEN: ${{ secrets.REPOSITORY_PUSH_ACCESS }} run: npm exec semantic-release - - name: Setup QEMU - uses: docker/setup-qemu-action@v3 - - - name: Extract metadata for the Docker image - id: metadata - uses: docker/metadata-action@v5 - with: - images: ghcr.io/${{ env.IMAGE_NAME }} - flavor: | - latest=${{ startsWith(github.ref, 'refs/heads/main') }} - suffix=-${{ github.sha }} - - - name: Login to GitHub Container Registry - uses: docker/login-action@v3 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Build and push Docker image - id: build - uses: docker/build-push-action@v5 - with: - platforms: linux/amd64,linux/arm64 - cache-from: type=gha - cache-to: type=gha,mode=max,ignore-error=true - push: true - tags: ${{ steps.metadata.outputs.tags }} - labels: ${{ steps.metadata.outputs.labels }} - build-args: | - GITHUB_ACTOR=${{ github.actor }} - GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }} - - name: Set Portainer stack webhook URL based on branch run: | if [[ "${{ github.ref }}" == "refs/heads/main" ]]; then @@ -98,3 +65,12 @@ jobs: uses: newarifrh/portainer-service-webhook@v1 with: webhook_url: ${{ env.PORTAINER_WEBHOOK_URL }} + + - name: Purge outdated images + uses: snok/container-retention-policy@v3 + with: + account: ${{ github.actor }} + token: ${{ secrets.GITHUB_TOKEN }} + image-names: "revanced-api" + keep-n-most-recent: 5 + cut-off: 3M diff --git a/.releaserc b/.releaserc index 193b3fb..756d3a4 100644 --- a/.releaserc +++ b/.releaserc @@ -23,7 +23,7 @@ "assets": [ "README.md", "CHANGELOG.md", - "gradle.properties", + "gradle.properties" ] } ], @@ -35,15 +35,27 @@ "path": "build/libs/*" } ], - successComment: false + "successComment": false + } + ], + [ + "@codedependant/semantic-release-docker", + { + "dockerImage": "revanced-api", + "dockerRegistry": "ghcr.io", + "dockerProject": "revanced", + "dockerPlatform": [ + "linux/amd64", + "linux/arm64/v8" + ] } ], [ "@saithodev/semantic-release-backmerge", { - backmergeBranches: [{"from": "main", "to": "dev"}], - clearWorkspace: true + "backmergeBranches": [{"from": "main", "to": "dev"}], + "clearWorkspace": true } ] ] -} +} \ No newline at end of file From d9c6e521a675d7a0f004fb6ecff39c4ffbd119ab Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Mon, 1 Jul 2024 00:47:57 +0200 Subject: [PATCH 075/102] Rename field --- .../app/revanced/api/configuration/schema/APISchema.kt | 4 ++-- .../revanced/api/configuration/services/PatchesService.kt | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/kotlin/app/revanced/api/configuration/schema/APISchema.kt b/src/main/kotlin/app/revanced/api/configuration/schema/APISchema.kt index 1230ce1..8accc5a 100644 --- a/src/main/kotlin/app/revanced/api/configuration/schema/APISchema.kt +++ b/src/main/kotlin/app/revanced/api/configuration/schema/APISchema.kt @@ -50,11 +50,11 @@ class APIAsset( val downloadUrl: String, val signatureDownloadUrl: String, // TODO: Remove this eventually when integrations are merged into patches. - val type: APIAssetType, + val name: APIAssetName, ) @Serializable -enum class APIAssetType { +enum class APIAssetName { PATCHES, INTEGRATION, } diff --git a/src/main/kotlin/app/revanced/api/configuration/services/PatchesService.kt b/src/main/kotlin/app/revanced/api/configuration/services/PatchesService.kt index 2024b67..de23442 100644 --- a/src/main/kotlin/app/revanced/api/configuration/services/PatchesService.kt +++ b/src/main/kotlin/app/revanced/api/configuration/services/PatchesService.kt @@ -31,20 +31,20 @@ internal class PatchesService( fun ConfigurationRepository.AssetConfiguration.asset( release: BackendRepository.BackendOrganization.BackendRepository.BackendRelease, - assetType: APIAssetType, + assetName: APIAssetName, ) = APIAsset( release.assets.first(assetRegex).downloadUrl, release.assets.first(signatureAssetRegex).downloadUrl, - assetType, + assetName, ) val patchesAsset = configurationRepository.patches.asset( patchesRelease, - APIAssetType.PATCHES, + APIAssetName.PATCHES, ) val integrationsAsset = configurationRepository.integrations.asset( integrationsRelease, - APIAssetType.INTEGRATION, + APIAssetName.INTEGRATION, ) return APIRelease( From d4b228faa758af3aca7156d7a00656acc526a52b Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Mon, 1 Jul 2024 01:09:05 +0200 Subject: [PATCH 076/102] Use correct server url in OpenAPI spec --- configuration.example.toml | 3 ++- configuration.toml | 3 ++- .../app/revanced/api/configuration/HTTP.kt | 5 ++++- .../app/revanced/api/configuration/OpenAPI.kt | 6 +++++- .../repository/ConfigurationRepository.kt | 19 +++++++++++++++++-- 5 files changed, 30 insertions(+), 6 deletions(-) diff --git a/configuration.example.toml b/configuration.example.toml index 1530f04..47ff97e 100644 --- a/configuration.example.toml +++ b/configuration.example.toml @@ -10,4 +10,5 @@ contributors-repositories = [ "revanced-manager", ] api-version = 1 -host = "*.revanced.app" +cors = { host = "*.revanced.app", sub-domains = [] } +endpoint = "https://api.revanced.app" \ No newline at end of file diff --git a/configuration.toml b/configuration.toml index 2815595..37da260 100644 --- a/configuration.toml +++ b/configuration.toml @@ -10,4 +10,5 @@ contributors-repositories = [ "revanced-manager", ] api-version = 1 -host = "*.revanced.app" \ No newline at end of file +cors = { host = "*.127.0.0.1:8888", sub-domains = [] } +endpoint = "http://127.0.0.1:8888/" \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/api/configuration/HTTP.kt b/src/main/kotlin/app/revanced/api/configuration/HTTP.kt index 09811a1..aa970c6 100644 --- a/src/main/kotlin/app/revanced/api/configuration/HTTP.kt +++ b/src/main/kotlin/app/revanced/api/configuration/HTTP.kt @@ -12,7 +12,10 @@ fun Application.configureHTTP() { val configurationRepository = get() install(CORS) { - allowHost(host = configurationRepository.host) + allowHost( + host = configurationRepository.cors.host, + subDomains = configurationRepository.cors.subDomains, + ) } install(RateLimit) { diff --git a/src/main/kotlin/app/revanced/api/configuration/OpenAPI.kt b/src/main/kotlin/app/revanced/api/configuration/OpenAPI.kt index 45316ef..e1c637d 100644 --- a/src/main/kotlin/app/revanced/api/configuration/OpenAPI.kt +++ b/src/main/kotlin/app/revanced/api/configuration/OpenAPI.kt @@ -1,6 +1,7 @@ package app.revanced.api.configuration import app.revanced.api.command.applicationVersion +import app.revanced.api.configuration.repository.ConfigurationRepository import io.bkbn.kompendium.core.plugin.NotarizedApplication import io.bkbn.kompendium.json.schema.KotlinXSchemaConfigurator import io.bkbn.kompendium.oas.OpenApiSpec @@ -12,9 +13,12 @@ import io.bkbn.kompendium.oas.security.BasicAuth import io.bkbn.kompendium.oas.security.BearerAuth import io.bkbn.kompendium.oas.server.Server import io.ktor.server.application.* +import org.koin.ktor.ext.get import java.net.URI internal fun Application.configureOpenAPI() { + val configurationRepository = get() + install(NotarizedApplication()) { spec = OpenApiSpec( info = Info( @@ -39,7 +43,7 @@ internal fun Application.configureOpenAPI() { ), ).apply { servers += Server( - url = URI("https://api.revanced.app"), + url = URI(configurationRepository.endpoint), description = "ReVanced API server", ) } diff --git a/src/main/kotlin/app/revanced/api/configuration/repository/ConfigurationRepository.kt b/src/main/kotlin/app/revanced/api/configuration/repository/ConfigurationRepository.kt index eaf70be..aed455a 100644 --- a/src/main/kotlin/app/revanced/api/configuration/repository/ConfigurationRepository.kt +++ b/src/main/kotlin/app/revanced/api/configuration/repository/ConfigurationRepository.kt @@ -19,7 +19,8 @@ import java.io.File * @property integrations The source of the integrations. * @property contributorsRepositoryNames The names of the repositories to get contributors from. * @property apiVersion The version to use for the API. - * @property host The host of the API to configure CORS. + * @property cors The CORS configuration. + * @property endpoint The endpoint of the API. */ @Serializable internal class ConfigurationRepository( @@ -30,7 +31,8 @@ internal class ConfigurationRepository( val contributorsRepositoryNames: Set, @SerialName("api-version") val apiVersion: Int = 1, - val host: String, + val cors: Cors, + val endpoint: String, ) { /** * An asset configuration. @@ -59,6 +61,19 @@ internal class ConfigurationRepository( @SerialName("public-key-file") val publicKeyFile: File, ) + + /** + * The CORS configuration. + * + * @property host The host of the API to configure CORS. + * @property subDomains The subdomains to allow for CORS. + */ + @Serializable + internal class Cors( + val host: String, + @SerialName("sub-domains") + val subDomains: List, + ) } private object RegexSerializer : KSerializer { From 01780188b9abba21dd66375895515c4710790435 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Mon, 8 Jul 2024 00:39:35 +0200 Subject: [PATCH 077/102] build: Update dependencies --- build.gradle.kts | 4 ++-- gradle/libs.versions.toml | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index de068fe..4548120 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -13,8 +13,8 @@ tasks { expand("projectVersion" to project.version) } - // Needed by gradle-semantic-release-plugin. - // Tracking: https://github.com/KengoTODA/gradle-semantic-release-plugin/issues/435 + // Used by gradle-semantic-release-plugin. + // Tracking: https://github.com/KengoTODA/gradle-semantic-release-plugin/issues/435. publish { dependsOn(shadowJar) } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index df974ce..6730386 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,13 +1,13 @@ [versions] kompendium-core = "3.14.4" kotlin = "2.0.0" -logback = "1.4.14" -exposed = "0.41.1" +logback = "1.5.6" +exposed = "0.52.0" h2 = "2.2.224" koin = "3.5.3" dotenv = "6.4.1" ktor = "2.3.7" -ktoml = "0.5.1" +ktoml = "0.5.2" picocli = "4.7.6" datetime = "0.6.0" revanced-patcher = "19.3.1" From a7d1892343094ddb2762ca346759771e6a274081 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Mon, 8 Jul 2024 00:41:05 +0200 Subject: [PATCH 078/102] perf: Make async db transactions and use List instead of Set --- .../api/configuration/Dependencies.kt | 25 +-- .../repository/AnnouncementRepository.kt | 146 ++++++++---------- .../repository/BackendRepository.kt | 18 ++- .../repository/GitHubBackendRepository.kt | 23 +-- .../api/configuration/schema/APISchema.kt | 12 +- .../services/AnnouncementService.kt | 21 ++- .../api/configuration/services/ApiService.kt | 6 +- .../configuration/services/PatchesService.kt | 2 +- 8 files changed, 131 insertions(+), 122 deletions(-) diff --git a/src/main/kotlin/app/revanced/api/configuration/Dependencies.kt b/src/main/kotlin/app/revanced/api/configuration/Dependencies.kt index 24ebb54..5256dd4 100644 --- a/src/main/kotlin/app/revanced/api/configuration/Dependencies.kt +++ b/src/main/kotlin/app/revanced/api/configuration/Dependencies.kt @@ -27,6 +27,7 @@ import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonNamingStrategy import org.jetbrains.exposed.sql.Database +import org.jetbrains.exposed.sql.transactions.TransactionManager import org.koin.core.module.dsl.singleOf import org.koin.core.parameter.parameterArrayOf import org.koin.dsl.module @@ -41,6 +42,7 @@ fun Application.configureDependencies( single { Dotenv.configure().load() } + factory { params -> val defaultRequestUri: String = params.get() val configBlock = params.getOrNull<(HttpClientConfig.() -> Unit)>() ?: {} @@ -54,17 +56,6 @@ fun Application.configureDependencies( } val repositoryModule = module { - single { - val dotenv = get() - - Database.connect( - url = dotenv["DB_URL"], - user = dotenv["DB_USER"], - password = dotenv["DB_PASSWORD"], - driver = "org.h2.Driver", - ) - } - single { GitHubBackendRepository( get { @@ -106,7 +97,17 @@ fun Application.configureDependencies( Toml.decodeFromStream(configFile.inputStream()) } - singleOf(::AnnouncementRepository) + single { + val dotenv = get() + + TransactionManager.defaultDatabase = Database.connect( + url = dotenv["DB_URL"], + user = dotenv["DB_USER"], + password = dotenv["DB_PASSWORD"], + ) + + AnnouncementRepository() + } } val serviceModule = module { diff --git a/src/main/kotlin/app/revanced/api/configuration/repository/AnnouncementRepository.kt b/src/main/kotlin/app/revanced/api/configuration/repository/AnnouncementRepository.kt index 9835454..95c4daf 100644 --- a/src/main/kotlin/app/revanced/api/configuration/repository/AnnouncementRepository.kt +++ b/src/main/kotlin/app/revanced/api/configuration/repository/AnnouncementRepository.kt @@ -1,47 +1,41 @@ package app.revanced.api.configuration.repository -import app.revanced.api.configuration.repository.AnnouncementRepository.AttachmentTable.announcement import app.revanced.api.configuration.schema.APIAnnouncement -import app.revanced.api.configuration.schema.APIResponseAnnouncement import app.revanced.api.configuration.schema.APIResponseAnnouncementId import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.awaitAll import kotlinx.coroutines.runBlocking import kotlinx.datetime.* import org.jetbrains.exposed.dao.IntEntity import org.jetbrains.exposed.dao.IntEntityClass import org.jetbrains.exposed.dao.id.EntityID import org.jetbrains.exposed.dao.id.IntIdTable +import org.jetbrains.exposed.dao.load import org.jetbrains.exposed.sql.* +import org.jetbrains.exposed.sql.kotlin.datetime.CurrentDateTime import org.jetbrains.exposed.sql.kotlin.datetime.datetime import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction +import org.jetbrains.exposed.sql.transactions.experimental.suspendedTransactionAsync -internal class AnnouncementRepository(private val database: Database) { +internal class AnnouncementRepository { init { runBlocking { transaction { - SchemaUtils.create(AnnouncementTable, AttachmentTable) + SchemaUtils.create(Announcements, Attachments) } } } suspend fun all() = transaction { - buildSet { - AnnouncementEntity.all().forEach { announcement -> - add(announcement.toApi()) - } - } + Announcement.all() } suspend fun all(channel: String) = transaction { - buildSet { - AnnouncementEntity.find { AnnouncementTable.channel eq channel }.forEach { announcement -> - add(announcement.toApi()) - } - } + Announcement.find { Announcements.channel eq channel } } suspend fun delete(id: Int) = transaction { - val announcement = AnnouncementEntity.findById(id) ?: return@transaction + val announcement = Announcement.findById(id) ?: return@transaction announcement.delete() } @@ -49,21 +43,21 @@ internal class AnnouncementRepository(private val database: Database) { // TODO: These are inefficient, but I'm not sure how to make them more efficient. suspend fun latest() = transaction { - AnnouncementEntity.all().maxByOrNull { it.createdAt }?.toApi() + Announcement.all().maxByOrNull { it.id }?.load(Announcement::attachments) } suspend fun latest(channel: String) = transaction { - AnnouncementEntity.find { AnnouncementTable.channel eq channel }.maxByOrNull { it.createdAt }?.toApi() + Announcement.find { Announcements.channel eq channel }.maxByOrNull { it.id }?.load(Announcement::attachments) } suspend fun latestId() = transaction { - AnnouncementEntity.all().maxByOrNull { it.createdAt }?.id?.value?.let { + Announcement.all().maxByOrNull { it.id }?.id?.value?.let { APIResponseAnnouncementId(it) } } suspend fun latestId(channel: String) = transaction { - AnnouncementEntity.find { AnnouncementTable.channel eq channel }.maxByOrNull { it.createdAt }?.id?.value?.let { + Announcement.find { Announcements.channel eq channel }.maxByOrNull { it.id }?.id?.value?.let { APIResponseAnnouncementId(it) } } @@ -72,106 +66,98 @@ internal class AnnouncementRepository(private val database: Database) { id: Int, archivedAt: LocalDateTime?, ) = transaction { - AnnouncementEntity.findById(id)?.apply { - this.archivedAt = archivedAt ?: java.time.LocalDateTime.now().toKotlinLocalDateTime() + Announcement.findByIdAndUpdate(id) { + it.archivedAt = archivedAt ?: java.time.LocalDateTime.now().toKotlinLocalDateTime() } } suspend fun unarchive(id: Int) = transaction { - AnnouncementEntity.findById(id)?.apply { - archivedAt = null + Announcement.findByIdAndUpdate(id) { + it.archivedAt = null } } suspend fun new(new: APIAnnouncement) = transaction { - AnnouncementEntity.new announcement@{ + Announcement.new { author = new.author title = new.title content = new.content channel = new.channel - createdAt = Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault()) archivedAt = new.archivedAt level = new.level }.also { newAnnouncement -> - new.attachmentUrls.map { - AttachmentEntity.new { - url = it - announcement = newAnnouncement + new.attachmentUrls.map { newUrl -> + suspendedTransactionAsync { + Attachment.new { + url = newUrl + announcement = newAnnouncement + } } - } + }.awaitAll() } } suspend fun update(id: Int, new: APIAnnouncement) = transaction { - AnnouncementEntity.findById(id)?.apply { - author = new.author - title = new.title - content = new.content - channel = new.channel - archivedAt = new.archivedAt - level = new.level - - attachments.forEach(AttachmentEntity::delete) - new.attachmentUrls.map { - AttachmentEntity.new { - url = it - announcement = this@apply + Announcement.findByIdAndUpdate(id) { + it.author = new.author + it.title = new.title + it.content = new.content + it.channel = new.channel + it.archivedAt = new.archivedAt + it.level = new.level + }?.also { newAnnouncement -> + newAnnouncement.attachments.map { + suspendedTransactionAsync { + it.delete() } - } + }.awaitAll() + + new.attachmentUrls.map { newUrl -> + suspendedTransactionAsync { + Attachment.new { + url = newUrl + announcement = newAnnouncement + } + } + }.awaitAll() } } - private suspend fun transaction(statement: Transaction.() -> T) = - newSuspendedTransaction(Dispatchers.IO, database, statement = statement) + private suspend fun transaction(statement: suspend Transaction.() -> T) = + newSuspendedTransaction(Dispatchers.IO, statement = statement) - private object AnnouncementTable : IntIdTable() { + private object Announcements : IntIdTable() { val author = varchar("author", 32).nullable() val title = varchar("title", 64) val content = text("content").nullable() val channel = varchar("channel", 16).nullable() - val createdAt = datetime("createdAt") + val createdAt = datetime("createdAt").defaultExpression(CurrentDateTime) val archivedAt = datetime("archivedAt").nullable() val level = integer("level") } - private object AttachmentTable : IntIdTable() { + private object Attachments : IntIdTable() { val url = varchar("url", 256) - val announcement = reference("announcement", AnnouncementTable, onDelete = ReferenceOption.CASCADE) + val announcement = reference("announcement", Announcements, onDelete = ReferenceOption.CASCADE) } - class AnnouncementEntity(id: EntityID) : IntEntity(id) { - companion object : IntEntityClass(AnnouncementTable) + class Announcement(id: EntityID) : IntEntity(id) { + companion object : IntEntityClass(Announcements) - var author by AnnouncementTable.author - var title by AnnouncementTable.title - var content by AnnouncementTable.content - val attachments by AttachmentEntity referrersOn announcement - var channel by AnnouncementTable.channel - var createdAt by AnnouncementTable.createdAt - var archivedAt by AnnouncementTable.archivedAt - var level by AnnouncementTable.level - - fun toApi() = APIResponseAnnouncement( - id.value, - author, - title, - content, - attachmentUrls = buildSet { - attachments.forEach { - add(it.url) - } - }, - channel, - createdAt, - archivedAt, - level, - ) + var author by Announcements.author + var title by Announcements.title + var content by Announcements.content + val attachments by Attachment referrersOn Attachments.announcement + var channel by Announcements.channel + var createdAt by Announcements.createdAt + var archivedAt by Announcements.archivedAt + var level by Announcements.level } - class AttachmentEntity(id: EntityID) : IntEntity(id) { - companion object : IntEntityClass(AttachmentTable) + class Attachment(id: EntityID) : IntEntity(id) { + companion object : IntEntityClass(Attachments) - var url by AttachmentTable.url - var announcement by AnnouncementEntity referencedOn AttachmentTable.announcement + var url by Attachments.url + var announcement by Announcement referencedOn Attachments.announcement } } diff --git a/src/main/kotlin/app/revanced/api/configuration/repository/BackendRepository.kt b/src/main/kotlin/app/revanced/api/configuration/repository/BackendRepository.kt index 1ed0469..69429ca 100644 --- a/src/main/kotlin/app/revanced/api/configuration/repository/BackendRepository.kt +++ b/src/main/kotlin/app/revanced/api/configuration/repository/BackendRepository.kt @@ -30,7 +30,8 @@ abstract class BackendRepository internal constructor( * @property members The members of the organization. */ class BackendOrganization( - val members: Set, + // Using a list instead of a set because set semantics are unnecessary here. + val members: List, ) { /** * A member of an organization. @@ -55,7 +56,8 @@ abstract class BackendRepository internal constructor( * @property url The URL to the GPG master key. */ class GpgKeys( - val ids: Set, + // Using a list instead of a set because set semantics are unnecessary here. + val ids: List, val url: String, ) } @@ -66,7 +68,8 @@ abstract class BackendRepository internal constructor( * @property contributors The contributors of the repository. */ class BackendRepository( - val contributors: Set, + // Using a list instead of a set because set semantics are unnecessary here. + val contributors: List, ) { /** * A contributor of a repository. @@ -95,10 +98,11 @@ abstract class BackendRepository internal constructor( val tag: String, val releaseNote: String, val createdAt: LocalDateTime, - val assets: Set, + // Using a list instead of a set because set semantics are unnecessary here. + val assets: List, ) { companion object { - fun Set.first(assetRegex: Regex) = first { assetRegex.containsMatchIn(it.name) } + fun List.first(assetRegex: Regex) = first { assetRegex.containsMatchIn(it.name) } } /** @@ -149,7 +153,7 @@ abstract class BackendRepository internal constructor( * @param repository The name of the repository. * @return The contributors. */ - abstract suspend fun contributors(owner: String, repository: String): Set + abstract suspend fun contributors(owner: String, repository: String): List /** * Get the members of an organization. @@ -157,7 +161,7 @@ abstract class BackendRepository internal constructor( * @param organization The name of the organization. * @return The members. */ - abstract suspend fun members(organization: String): Set + abstract suspend fun members(organization: String): List /** * Get the rate limit of the backend. diff --git a/src/main/kotlin/app/revanced/api/configuration/repository/GitHubBackendRepository.kt b/src/main/kotlin/app/revanced/api/configuration/repository/GitHubBackendRepository.kt index 2664afe..a032f4d 100644 --- a/src/main/kotlin/app/revanced/api/configuration/repository/GitHubBackendRepository.kt +++ b/src/main/kotlin/app/revanced/api/configuration/repository/GitHubBackendRepository.kt @@ -39,15 +39,15 @@ class GitHubBackendRepository(client: HttpClient) : BackendRepository(client) { name = it.name, downloadUrl = it.browserDownloadUrl, ) - }.toSet(), + }, ) } override suspend fun contributors( owner: String, repository: String, - ): Set { - val contributors: Set = client.get( + ): List { + val contributors: List = client.get( Contributors( owner, repository, @@ -61,12 +61,12 @@ class GitHubBackendRepository(client: HttpClient) : BackendRepository(client) { url = it.htmlUrl, contributions = it.contributions, ) - }.toSet() + } } - override suspend fun members(organization: String): Set { + override suspend fun members(organization: String): List { // Get the list of members of the organization. - val members: Set = client.get(Organization.Members(organization)).body() + val members: List = client.get(Organization.Members(organization)).body() return coroutineScope { members.map { member -> @@ -78,7 +78,7 @@ class GitHubBackendRepository(client: HttpClient) : BackendRepository(client) { }, async { // Get the GPG key of the user. - client.get(User.GpgKeys(member.login)).body>() + client.get(User.GpgKeys(member.login)).body>() }, ) } @@ -87,7 +87,7 @@ class GitHubBackendRepository(client: HttpClient) : BackendRepository(client) { val user = responses[0] as GitHubUser @Suppress("UNCHECKED_CAST") - val gpgKeys = responses[1] as Set + val gpgKeys = responses[1] as List BackendMember( name = user.login, @@ -96,11 +96,11 @@ class GitHubBackendRepository(client: HttpClient) : BackendRepository(client) { bio = user.bio, gpgKeys = BackendMember.GpgKeys( - ids = gpgKeys.map { it.keyId }.toSet(), + ids = gpgKeys.map { it.keyId }, url = "https://api.github.com/users/${user.login}.gpg", ), ) - }.toSet() + } } override suspend fun rateLimit(): BackendRateLimit { @@ -153,7 +153,8 @@ class GitHubOrganization { @Serializable class GitHubRelease( val tagName: String, - val assets: Set, + // Using a list instead of a set because set semantics are unnecessary here. + val assets: List, val createdAt: Instant, val body: String, ) { diff --git a/src/main/kotlin/app/revanced/api/configuration/schema/APISchema.kt b/src/main/kotlin/app/revanced/api/configuration/schema/APISchema.kt index 8accc5a..888f811 100644 --- a/src/main/kotlin/app/revanced/api/configuration/schema/APISchema.kt +++ b/src/main/kotlin/app/revanced/api/configuration/schema/APISchema.kt @@ -8,7 +8,8 @@ class APIRelease( val version: String, val createdAt: LocalDateTime, val description: String, - val assets: Set, + // Using a list instead of a set because set semantics are unnecessary here. + val assets: List, ) interface APIUser { @@ -42,7 +43,8 @@ class APIContributor( @Serializable class APIContributable( val name: String, - val contributors: Set, + // Using a list instead of a set because set semantics are unnecessary here. + val contributors: List, ) @Serializable @@ -69,7 +71,8 @@ class APIAnnouncement( val author: String? = null, val title: String, val content: String? = null, - val attachmentUrls: Set = emptySet(), + // Using a list instead of a set because set semantics are unnecessary here. + val attachmentUrls: List = emptyList(), val channel: String? = null, val archivedAt: LocalDateTime? = null, val level: Int = 0, @@ -81,7 +84,8 @@ class APIResponseAnnouncement( val author: String? = null, val title: String, val content: String? = null, - val attachmentUrls: Set = emptySet(), + // Using a list instead of a set because set semantics are unnecessary here. + val attachmentUrls: List = emptyList(), val channel: String? = null, val createdAt: LocalDateTime, val archivedAt: LocalDateTime? = null, diff --git a/src/main/kotlin/app/revanced/api/configuration/services/AnnouncementService.kt b/src/main/kotlin/app/revanced/api/configuration/services/AnnouncementService.kt index 7befd57..b0142e5 100644 --- a/src/main/kotlin/app/revanced/api/configuration/services/AnnouncementService.kt +++ b/src/main/kotlin/app/revanced/api/configuration/services/AnnouncementService.kt @@ -2,6 +2,7 @@ package app.revanced.api.configuration.services import app.revanced.api.configuration.repository.AnnouncementRepository import app.revanced.api.configuration.schema.APIAnnouncement +import app.revanced.api.configuration.schema.APIResponseAnnouncement import app.revanced.api.configuration.schema.APIResponseAnnouncementId import kotlinx.datetime.LocalDateTime @@ -11,11 +12,11 @@ internal class AnnouncementService( suspend fun latestId(channel: String): APIResponseAnnouncementId? = announcementRepository.latestId(channel) suspend fun latestId(): APIResponseAnnouncementId? = announcementRepository.latestId() - suspend fun latest(channel: String) = announcementRepository.latest(channel) - suspend fun latest() = announcementRepository.latest() + suspend fun latest(channel: String) = announcementRepository.latest(channel)?.toApi() + suspend fun latest() = announcementRepository.latest()?.toApi() - suspend fun all(channel: String) = announcementRepository.all(channel) - suspend fun all() = announcementRepository.all() + suspend fun all(channel: String) = announcementRepository.all(channel).map { it.toApi() } + suspend fun all() = announcementRepository.all().map { it.toApi() } suspend fun new(new: APIAnnouncement) { announcementRepository.new(new) @@ -32,4 +33,16 @@ internal class AnnouncementService( suspend fun delete(id: Int) { announcementRepository.delete(id) } + + private fun AnnouncementRepository.Announcement.toApi() = APIResponseAnnouncement( + id.value, + author, + title, + content, + attachments.map { it.url }, + channel, + createdAt, + archivedAt, + level, + ) } diff --git a/src/main/kotlin/app/revanced/api/configuration/services/ApiService.kt b/src/main/kotlin/app/revanced/api/configuration/services/ApiService.kt index 10b54cf..7834362 100644 --- a/src/main/kotlin/app/revanced/api/configuration/services/ApiService.kt +++ b/src/main/kotlin/app/revanced/api/configuration/services/ApiService.kt @@ -19,11 +19,11 @@ internal class ApiService( it, backendRepository.contributors(configurationRepository.organization, it).map { APIContributor(it.name, it.avatarUrl, it.url, it.contributions) - }.toSet(), + }, ) } } - }.awaitAll().toSet() + }.awaitAll() suspend fun team() = backendRepository.members(configurationRepository.organization).map { member -> APIMember( @@ -41,7 +41,7 @@ internal class ApiService( }, ) - }.toSet() + } suspend fun rateLimit() = backendRepository.rateLimit()?.let { APIRateLimit(it.limit, it.remaining, it.reset) diff --git a/src/main/kotlin/app/revanced/api/configuration/services/PatchesService.kt b/src/main/kotlin/app/revanced/api/configuration/services/PatchesService.kt index de23442..31a0670 100644 --- a/src/main/kotlin/app/revanced/api/configuration/services/PatchesService.kt +++ b/src/main/kotlin/app/revanced/api/configuration/services/PatchesService.kt @@ -51,7 +51,7 @@ internal class PatchesService( patchesRelease.tag, patchesRelease.createdAt, patchesRelease.releaseNote, - setOf(patchesAsset, integrationsAsset), + listOf(patchesAsset, integrationsAsset), ) } From 1ca9952de81feeae1333872a7741119d6d8e7814 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Mon, 8 Jul 2024 01:54:37 +0200 Subject: [PATCH 079/102] perf: Cache latest announcements for constant access time --- .../repository/AnnouncementRepository.kt | 60 +++++++++++-------- .../services/AnnouncementService.kt | 10 ++-- 2 files changed, 42 insertions(+), 28 deletions(-) diff --git a/src/main/kotlin/app/revanced/api/configuration/repository/AnnouncementRepository.kt b/src/main/kotlin/app/revanced/api/configuration/repository/AnnouncementRepository.kt index 95c4daf..c095669 100644 --- a/src/main/kotlin/app/revanced/api/configuration/repository/AnnouncementRepository.kt +++ b/src/main/kotlin/app/revanced/api/configuration/repository/AnnouncementRepository.kt @@ -1,7 +1,6 @@ package app.revanced.api.configuration.repository import app.revanced.api.configuration.schema.APIAnnouncement -import app.revanced.api.configuration.schema.APIResponseAnnouncementId import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.awaitAll import kotlinx.coroutines.runBlocking @@ -10,7 +9,6 @@ import org.jetbrains.exposed.dao.IntEntity import org.jetbrains.exposed.dao.IntEntityClass import org.jetbrains.exposed.dao.id.EntityID import org.jetbrains.exposed.dao.id.IntIdTable -import org.jetbrains.exposed.dao.load import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.kotlin.datetime.CurrentDateTime import org.jetbrains.exposed.sql.kotlin.datetime.datetime @@ -18,10 +16,26 @@ import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransacti import org.jetbrains.exposed.sql.transactions.experimental.suspendedTransactionAsync internal class AnnouncementRepository { + // This is better than doing a maxByOrNull { it.id }. + private var latestAnnouncement: Announcement? = null + private val latestAnnouncementByChannel = mutableMapOf() + + private fun updateLatestAnnouncement(new: Announcement) { + if (latestAnnouncement?.id?.value == new.id.value) { + latestAnnouncement = new + latestAnnouncementByChannel[new.channel ?: return] = new + } + } + init { runBlocking { transaction { SchemaUtils.create(Announcements, Attachments) + + // Initialize the latest announcement. + latestAnnouncement = Announcement.all().onEach { + latestAnnouncementByChannel[it.channel ?: return@onEach] = it + }.maxByOrNull { it.id } ?: return@transaction } } } @@ -38,29 +52,27 @@ internal class AnnouncementRepository { val announcement = Announcement.findById(id) ?: return@transaction announcement.delete() - } - // TODO: These are inefficient, but I'm not sure how to make them more efficient. + // In case the latest announcement was deleted, query the new latest announcement again. + if (latestAnnouncement?.id?.value == id) { + latestAnnouncement = Announcement.all().maxByOrNull { it.id } - suspend fun latest() = transaction { - Announcement.all().maxByOrNull { it.id }?.load(Announcement::attachments) - } - - suspend fun latest(channel: String) = transaction { - Announcement.find { Announcements.channel eq channel }.maxByOrNull { it.id }?.load(Announcement::attachments) - } - - suspend fun latestId() = transaction { - Announcement.all().maxByOrNull { it.id }?.id?.value?.let { - APIResponseAnnouncementId(it) + // If no latest announcement was found, remove it from the channel map. + if (latestAnnouncement == null) { + latestAnnouncementByChannel.remove(announcement.channel) + } else { + latestAnnouncementByChannel[latestAnnouncement!!.channel ?: return@transaction] = latestAnnouncement!! + } } } - suspend fun latestId(channel: String) = transaction { - Announcement.find { Announcements.channel eq channel }.maxByOrNull { it.id }?.id?.value?.let { - APIResponseAnnouncementId(it) - } - } + fun latest() = latestAnnouncement + + fun latest(channel: String) = latestAnnouncementByChannel[channel] + + fun latestId() = latest()?.id?.value + + fun latestId(channel: String) = latest(channel)?.id?.value suspend fun archive( id: Int, @@ -68,13 +80,13 @@ internal class AnnouncementRepository { ) = transaction { Announcement.findByIdAndUpdate(id) { it.archivedAt = archivedAt ?: java.time.LocalDateTime.now().toKotlinLocalDateTime() - } + }?.also(::updateLatestAnnouncement) } suspend fun unarchive(id: Int) = transaction { Announcement.findByIdAndUpdate(id) { it.archivedAt = null - } + }?.also(::updateLatestAnnouncement) } suspend fun new(new: APIAnnouncement) = transaction { @@ -94,7 +106,7 @@ internal class AnnouncementRepository { } } }.awaitAll() - } + }.also(::updateLatestAnnouncement) } suspend fun update(id: Int, new: APIAnnouncement) = transaction { @@ -120,7 +132,7 @@ internal class AnnouncementRepository { } } }.awaitAll() - } + }?.also(::updateLatestAnnouncement) } private suspend fun transaction(statement: suspend Transaction.() -> T) = diff --git a/src/main/kotlin/app/revanced/api/configuration/services/AnnouncementService.kt b/src/main/kotlin/app/revanced/api/configuration/services/AnnouncementService.kt index b0142e5..ae4cf7e 100644 --- a/src/main/kotlin/app/revanced/api/configuration/services/AnnouncementService.kt +++ b/src/main/kotlin/app/revanced/api/configuration/services/AnnouncementService.kt @@ -9,11 +9,11 @@ import kotlinx.datetime.LocalDateTime internal class AnnouncementService( private val announcementRepository: AnnouncementRepository, ) { - suspend fun latestId(channel: String): APIResponseAnnouncementId? = announcementRepository.latestId(channel) - suspend fun latestId(): APIResponseAnnouncementId? = announcementRepository.latestId() + fun latestId(channel: String): APIResponseAnnouncementId? = announcementRepository.latestId(channel)?.toApi() + fun latestId(): APIResponseAnnouncementId? = announcementRepository.latestId()?.toApi() - suspend fun latest(channel: String) = announcementRepository.latest(channel)?.toApi() - suspend fun latest() = announcementRepository.latest()?.toApi() + fun latest(channel: String) = announcementRepository.latest(channel)?.toApi() + fun latest() = announcementRepository.latest()?.toApi() suspend fun all(channel: String) = announcementRepository.all(channel).map { it.toApi() } suspend fun all() = announcementRepository.all().map { it.toApi() } @@ -45,4 +45,6 @@ internal class AnnouncementService( archivedAt, level, ) + + private fun Int.toApi() = APIResponseAnnouncementId(this) } From 89a577e91abbfcd2865e770088661eac4aeb4dd7 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Mon, 8 Jul 2024 05:16:59 +0200 Subject: [PATCH 080/102] fix: Finish DB Model to API model transformation inside transaction --- .../repository/AnnouncementRepository.kt | 28 +++++++++++++++---- .../services/AnnouncementService.kt | 27 ++++-------------- 2 files changed, 28 insertions(+), 27 deletions(-) diff --git a/src/main/kotlin/app/revanced/api/configuration/repository/AnnouncementRepository.kt b/src/main/kotlin/app/revanced/api/configuration/repository/AnnouncementRepository.kt index c095669..992b06f 100644 --- a/src/main/kotlin/app/revanced/api/configuration/repository/AnnouncementRepository.kt +++ b/src/main/kotlin/app/revanced/api/configuration/repository/AnnouncementRepository.kt @@ -1,6 +1,8 @@ package app.revanced.api.configuration.repository import app.revanced.api.configuration.schema.APIAnnouncement +import app.revanced.api.configuration.schema.APIResponseAnnouncement +import app.revanced.api.configuration.schema.APIResponseAnnouncementId import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.awaitAll import kotlinx.coroutines.runBlocking @@ -41,11 +43,11 @@ internal class AnnouncementRepository { } suspend fun all() = transaction { - Announcement.all() + Announcement.all().map { it.toApi() } } suspend fun all(channel: String) = transaction { - Announcement.find { Announcements.channel eq channel } + Announcement.find { Announcements.channel eq channel }.map { it.toApi() } } suspend fun delete(id: Int) = transaction { @@ -66,13 +68,13 @@ internal class AnnouncementRepository { } } - fun latest() = latestAnnouncement + fun latest() = latestAnnouncement?.toApi() - fun latest(channel: String) = latestAnnouncementByChannel[channel] + fun latest(channel: String) = latestAnnouncementByChannel[channel]?.toApi() - fun latestId() = latest()?.id?.value + fun latestId() = latest()?.id?.toApi() - fun latestId(channel: String) = latest(channel)?.id?.value + fun latestId(channel: String) = latest(channel)?.id?.toApi() suspend fun archive( id: Int, @@ -172,4 +174,18 @@ internal class AnnouncementRepository { var url by Attachments.url var announcement by Announcement referencedOn Attachments.announcement } + + private fun Announcement.toApi() = APIResponseAnnouncement( + id.value, + author, + title, + content, + attachments.map { it.url }, + channel, + createdAt, + archivedAt, + level, + ) + + private fun Int.toApi() = APIResponseAnnouncementId(this) } diff --git a/src/main/kotlin/app/revanced/api/configuration/services/AnnouncementService.kt b/src/main/kotlin/app/revanced/api/configuration/services/AnnouncementService.kt index ae4cf7e..909cff8 100644 --- a/src/main/kotlin/app/revanced/api/configuration/services/AnnouncementService.kt +++ b/src/main/kotlin/app/revanced/api/configuration/services/AnnouncementService.kt @@ -2,21 +2,20 @@ package app.revanced.api.configuration.services import app.revanced.api.configuration.repository.AnnouncementRepository import app.revanced.api.configuration.schema.APIAnnouncement -import app.revanced.api.configuration.schema.APIResponseAnnouncement import app.revanced.api.configuration.schema.APIResponseAnnouncementId import kotlinx.datetime.LocalDateTime internal class AnnouncementService( private val announcementRepository: AnnouncementRepository, ) { - fun latestId(channel: String): APIResponseAnnouncementId? = announcementRepository.latestId(channel)?.toApi() - fun latestId(): APIResponseAnnouncementId? = announcementRepository.latestId()?.toApi() + fun latestId(channel: String): APIResponseAnnouncementId? = announcementRepository.latestId(channel) + fun latestId(): APIResponseAnnouncementId? = announcementRepository.latestId() - fun latest(channel: String) = announcementRepository.latest(channel)?.toApi() - fun latest() = announcementRepository.latest()?.toApi() + fun latest(channel: String) = announcementRepository.latest(channel) + fun latest() = announcementRepository.latest() - suspend fun all(channel: String) = announcementRepository.all(channel).map { it.toApi() } - suspend fun all() = announcementRepository.all().map { it.toApi() } + suspend fun all(channel: String) = announcementRepository.all(channel) + suspend fun all() = announcementRepository.all() suspend fun new(new: APIAnnouncement) { announcementRepository.new(new) @@ -33,18 +32,4 @@ internal class AnnouncementService( suspend fun delete(id: Int) { announcementRepository.delete(id) } - - private fun AnnouncementRepository.Announcement.toApi() = APIResponseAnnouncement( - id.value, - author, - title, - content, - attachments.map { it.url }, - channel, - createdAt, - archivedAt, - level, - ) - - private fun Int.toApi() = APIResponseAnnouncementId(this) } From 89e2acfebb5e14f71d9ce3d962c5531070b7600e Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Mon, 8 Jul 2024 05:17:45 +0200 Subject: [PATCH 081/102] feat: Use auth digest instead of basic auth --- .env.example | 5 ++-- .../api/configuration/Dependencies.kt | 5 ++-- .../app/revanced/api/configuration/OpenAPI.kt | 2 -- .../api/configuration/routes/ApiRoute.kt | 2 +- .../api/configuration/services/AuthService.kt | 27 ++++++++++++------- 5 files changed, 22 insertions(+), 19 deletions(-) diff --git a/.env.example b/.env.example index a3cb37d..1376e76 100644 --- a/.env.example +++ b/.env.example @@ -8,9 +8,8 @@ DB_URL=jdbc:h2:./persistence/revanced-api DB_USER= DB_PASSWORD= -# Basic authentication to issue JWT tokens -BASIC_USERNAME= -BASIC_PASSWORD= +# Digest auth to issue JWT tokens in the format SHA256("username:ReVanced:password") +AUTH_SHA256_DIGEST= # JWT configuration for authenticated API endpoints JWT_SECRET= diff --git a/src/main/kotlin/app/revanced/api/configuration/Dependencies.kt b/src/main/kotlin/app/revanced/api/configuration/Dependencies.kt index 5256dd4..3f9f285 100644 --- a/src/main/kotlin/app/revanced/api/configuration/Dependencies.kt +++ b/src/main/kotlin/app/revanced/api/configuration/Dependencies.kt @@ -118,10 +118,9 @@ fun Application.configureDependencies( val issuer = dotenv["JWT_ISSUER"] val validityInMin = dotenv["JWT_VALIDITY_IN_MIN"].toInt() - val basicUsername = dotenv["BASIC_USERNAME"] - val basicPassword = dotenv["BASIC_PASSWORD"] + val authSHA256DigestString = dotenv["AUTH_SHA256_DIGEST"] - AuthService(issuer, validityInMin, jwtSecret, basicUsername, basicPassword) + AuthService(issuer, validityInMin, jwtSecret, authSHA256DigestString) } single { OldApiService( diff --git a/src/main/kotlin/app/revanced/api/configuration/OpenAPI.kt b/src/main/kotlin/app/revanced/api/configuration/OpenAPI.kt index e1c637d..3c90321 100644 --- a/src/main/kotlin/app/revanced/api/configuration/OpenAPI.kt +++ b/src/main/kotlin/app/revanced/api/configuration/OpenAPI.kt @@ -9,7 +9,6 @@ import io.bkbn.kompendium.oas.component.Components import io.bkbn.kompendium.oas.info.Contact import io.bkbn.kompendium.oas.info.Info import io.bkbn.kompendium.oas.info.License -import io.bkbn.kompendium.oas.security.BasicAuth import io.bkbn.kompendium.oas.security.BearerAuth import io.bkbn.kompendium.oas.server.Server import io.ktor.server.application.* @@ -38,7 +37,6 @@ internal fun Application.configureOpenAPI() { components = Components( securitySchemes = mutableMapOf( "bearer" to BearerAuth(), - "basic" to BasicAuth(), ), ), ).apply { diff --git a/src/main/kotlin/app/revanced/api/configuration/routes/ApiRoute.kt b/src/main/kotlin/app/revanced/api/configuration/routes/ApiRoute.kt index fe2d330..7cb56f2 100644 --- a/src/main/kotlin/app/revanced/api/configuration/routes/ApiRoute.kt +++ b/src/main/kotlin/app/revanced/api/configuration/routes/ApiRoute.kt @@ -25,7 +25,7 @@ internal fun Route.rootRoute() { val authService = koinGet() rateLimit(RateLimitName("strong")) { - authenticate("basic") { + authenticate("auth-digest") { route("token") { installTokenRouteDocumentation() diff --git a/src/main/kotlin/app/revanced/api/configuration/services/AuthService.kt b/src/main/kotlin/app/revanced/api/configuration/services/AuthService.kt index 8e4b8a9..a20e6d9 100644 --- a/src/main/kotlin/app/revanced/api/configuration/services/AuthService.kt +++ b/src/main/kotlin/app/revanced/api/configuration/services/AuthService.kt @@ -6,15 +6,23 @@ import io.ktor.server.application.* import io.ktor.server.auth.* import io.ktor.server.auth.jwt.* import java.util.* +import kotlin.text.HexFormat import kotlin.time.Duration.Companion.minutes -internal class AuthService( +internal class AuthService private constructor( private val issuer: String, private val validityInMin: Int, private val jwtSecret: String, - private val basicUsername: String, - private val basicPassword: String, + private val authSHA256Digest: ByteArray, ) { + @OptIn(ExperimentalStdlibApi::class) + constructor(issuer: String, validityInMin: Int, jwtSecret: String, authSHA256DigestString: String) : this( + issuer, + validityInMin, + jwtSecret, + authSHA256DigestString.hexToByteArray(HexFormat.Default), + ) + val configureSecurity: Application.() -> Unit = { install(Authentication) { jwt("jwt") { @@ -26,13 +34,12 @@ internal class AuthService( validate { credential -> JWTPrincipal(credential.payload) } } - basic("basic") { - validate { credentials -> - if (credentials.name == basicUsername && credentials.password == basicPassword) { - UserIdPrincipal(credentials.name) - } else { - null - } + digest("auth-digest") { + realm = "ReVanced" + algorithmName = "SHA-256" + + digestProvider { _, _ -> + authSHA256Digest } } } From d4ac47194e51b3e708516150d1690a72830ab809 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Mon, 8 Jul 2024 05:27:41 +0200 Subject: [PATCH 082/102] fix: Add missing OpenAPI docs --- .../api/configuration/routes/Announcements.kt | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/main/kotlin/app/revanced/api/configuration/routes/Announcements.kt b/src/main/kotlin/app/revanced/api/configuration/routes/Announcements.kt index 70a6bc0..938d556 100644 --- a/src/main/kotlin/app/revanced/api/configuration/routes/Announcements.kt +++ b/src/main/kotlin/app/revanced/api/configuration/routes/Announcements.kt @@ -92,6 +92,8 @@ internal fun Route.announcementsRoute() = route("announcements") { rateLimit(RateLimitName("strong")) { authenticate("jwt") { + installAnnouncementRouteDocumentation() + post { announcement -> announcementService.new(announcement) } @@ -136,6 +138,24 @@ internal fun Route.announcementsRoute() = route("announcements") { } } +private fun Route.installAnnouncementRouteDocumentation() = installNotarizedRoute { + tags = setOf("Announcements") + + post = PostInfo.builder { + description("Create a new announcement") + summary("Create announcement") + request { + requestType() + description("The new announcement") + } + response { + description("When the announcement was created") + responseCode(HttpStatusCode.OK) + responseType() + } + } +} + private fun Route.installLatestAnnouncementRouteDocumentation() = installNotarizedRoute { tags = setOf("Announcements") From a6008a2fb6d01fb577dcf357a1d698b6c1068b31 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Mon, 8 Jul 2024 05:27:55 +0200 Subject: [PATCH 083/102] fix: Add missing auth realm --- .../revanced/api/configuration/services/AuthService.kt | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/main/kotlin/app/revanced/api/configuration/services/AuthService.kt b/src/main/kotlin/app/revanced/api/configuration/services/AuthService.kt index a20e6d9..55e4395 100644 --- a/src/main/kotlin/app/revanced/api/configuration/services/AuthService.kt +++ b/src/main/kotlin/app/revanced/api/configuration/services/AuthService.kt @@ -26,12 +26,9 @@ internal class AuthService private constructor( val configureSecurity: Application.() -> Unit = { install(Authentication) { jwt("jwt") { - verifier( - JWT.require(Algorithm.HMAC256(jwtSecret)) - .withIssuer(issuer) - .build(), - ) - validate { credential -> JWTPrincipal(credential.payload) } + realm = "ReVanced" + + verifier(JWT.require(Algorithm.HMAC256(jwtSecret)).withIssuer(issuer).build()) } digest("auth-digest") { From e8c2488bc61793345c4b8171e520fb0127b34643 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Mon, 8 Jul 2024 05:28:28 +0200 Subject: [PATCH 084/102] fix: Add uri to rate limiter request key --- .../app/revanced/api/configuration/HTTP.kt | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/main/kotlin/app/revanced/api/configuration/HTTP.kt b/src/main/kotlin/app/revanced/api/configuration/HTTP.kt index aa970c6..06c8a1f 100644 --- a/src/main/kotlin/app/revanced/api/configuration/HTTP.kt +++ b/src/main/kotlin/app/revanced/api/configuration/HTTP.kt @@ -5,6 +5,7 @@ import io.ktor.server.application.* import io.ktor.server.plugins.* import io.ktor.server.plugins.cors.routing.* import io.ktor.server.plugins.ratelimit.* +import io.ktor.server.request.* import org.koin.ktor.ext.get import kotlin.time.Duration.Companion.minutes @@ -19,13 +20,19 @@ fun Application.configureHTTP() { } install(RateLimit) { - register(RateLimitName("weak")) { - rateLimiter(limit = 30, refillPeriod = 2.minutes) - requestKey { it.request.origin.remoteAddress } + fun rateLimit(name: String, block: RateLimitProviderConfig.() -> Unit) = register(RateLimitName(name)) { + requestKey { + it.request.uri + it.request.origin.remoteAddress + } + + block() } - register(RateLimitName("strong")) { + + rateLimit("weak") { + rateLimiter(limit = 30, refillPeriod = 2.minutes) + } + rateLimit("strong") { rateLimiter(limit = 5, refillPeriod = 1.minutes) - requestKey { it.request.origin.remoteHost } } } } From 267546fc326780ce1a10a26574302cebd2332d2a Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Mon, 8 Jul 2024 14:02:25 +0200 Subject: [PATCH 085/102] build: Set JVM source and target version --- build.gradle.kts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/build.gradle.kts b/build.gradle.kts index 4548120..0ee566e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,3 +1,5 @@ +import org.jetbrains.kotlin.gradle.dsl.JvmTarget + plugins { alias(libs.plugins.kotlin) alias(libs.plugins.ktor) @@ -35,6 +37,17 @@ ktor { } } +java { + sourceCompatibility = JavaVersion.VERSION_21 + targetCompatibility = JavaVersion.VERSION_21 +} + +kotlin { + compilerOptions { + jvmTarget = JvmTarget.JVM_21 + } +} + repositories { mavenCentral() google() From a7666ba29db8d775c1aa220ad9570121c35cde16 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Wed, 10 Jul 2024 23:58:20 +0000 Subject: [PATCH 086/102] chore(release): 1.0.0-dev.1 [skip ci] # 1.0.0-dev.1 (2024-07-10) ### Bug Fixes * Add missing auth realm ([a6008a2](https://github.com/ReVanced/revanced-api/commit/a6008a2fb6d01fb577dcf357a1d698b6c1068b31)) * Add missing OpenAPI docs ([d4ac471](https://github.com/ReVanced/revanced-api/commit/d4ac47194e51b3e708516150d1690a72830ab809)) * add required headers ([#8](https://github.com/ReVanced/revanced-api/issues/8)) ([f4c10dc](https://github.com/ReVanced/revanced-api/commit/f4c10dc064c4ac0fb6b8e612d680e4f6bda2b0c7)) * Add uri to rate limiter request key ([e8c2488](https://github.com/ReVanced/revanced-api/commit/e8c2488bc61793345c4b8171e520fb0127b34643)) * **ci:** add git dep ([d61ddcc](https://github.com/ReVanced/revanced-api/commit/d61ddcc8ac76d78b7a79c09bbfba812f652aa4cf)) * **ci:** fix git deps ([5564c2d](https://github.com/ReVanced/revanced-api/commit/5564c2da0946d67abab0f95e43fe6e6b108f5ec7)) * Correct env var comment ([9d7b049](https://github.com/ReVanced/revanced-api/commit/9d7b0493498bbf594928fc61181beadf6f59643b)) * Correct persistence directory name ([6238e33](https://github.com/ReVanced/revanced-api/commit/6238e33c42216885d5e9e8075bfc46eaf2a990ed)) * **deps:** fix requirements file ([5f0ab26](https://github.com/ReVanced/revanced-api/commit/5f0ab26cedef27cc95eec58ea4dce00904b53289)) * **deps:** missing pydantic ([089f29e](https://github.com/ReVanced/revanced-api/commit/089f29e95fc6b87f90885eb31b8e3460857224a8)) * **deps:** update poetry.lock ([b2c2fa7](https://github.com/ReVanced/revanced-api/commit/b2c2fa7136f6305703ba11d0bd5a87c0d563eaf3)) * Don't configure server ([280dbc3](https://github.com/ReVanced/revanced-api/commit/280dbc30f607483adad0bde6ab1b016d5da047ba)) * Encode defaults to fix OpenAPI spec ([e9d1c8f](https://github.com/ReVanced/revanced-api/commit/e9d1c8fae0bc46e761056197658c4bb045784104)) * Finish DB Model to API model transformation inside transaction ([89a577e](https://github.com/ReVanced/revanced-api/commit/89a577e91abbfcd2865e770088661eac4aeb4dd7)) * fix codeql issues ([b5568db](https://github.com/ReVanced/revanced-api/commit/b5568db79fa0620d06d6945b42a66744b6340bc5)) * Fix spelling mistake ([17ecf58](https://github.com/ReVanced/revanced-api/commit/17ecf58e550d13dd93ab69e1cf522366aeb3da3f)) * Increase pool size to mitigate overflow ([#113](https://github.com/ReVanced/revanced-api/issues/113)) ([5aed3d6](https://github.com/ReVanced/revanced-api/commit/5aed3d6ce6efcaf04b3cfa8f344623413518c9b1)) * Remove punctuation ([f9cae1e](https://github.com/ReVanced/revanced-api/commit/f9cae1ea56c93aded25159f6b0814bf84d192192)) * remove revanced-api from tools map ([4800ee9](https://github.com/ReVanced/revanced-api/commit/4800ee96a82c0a9fc1905c6960cd560b55304944)) * Serialize response correctly ([1dccfd2](https://github.com/ReVanced/revanced-api/commit/1dccfd2deff3c5de6a6cf2156cac8516b40bdd22)) * Set body for all eligible request methods ([c6cacef](https://github.com/ReVanced/revanced-api/commit/c6cacef907a5039ed029e1e26204aaba8e698aaa)) * unversioned endpoints placement under v0 should be at root ([8b29f49](https://github.com/ReVanced/revanced-api/commit/8b29f49805d4a651bdd26aa4958a3639760a2f4b)) * Use correct persistance folder path ([500a589](https://github.com/ReVanced/revanced-api/commit/500a5896e34f6a9600dcec2834b3d340161bb90a)) * Use correct proxy path ([ef92768](https://github.com/ReVanced/revanced-api/commit/ef927688a377c16fe37b578ea870207871c30056)) * Use correct resource path ([4dffd32](https://github.com/ReVanced/revanced-api/commit/4dffd32c99a5a3deafe21bc9e9960795ff93ff1d)) * Use multidict 6.0.5, add setup tools as dev dependency ([af19446](https://github.com/ReVanced/revanced-api/commit/af19446a67445e96624dfdb9f1fa6b4de77545c8)) ### Features * Add `/list` route ([6c930ff](https://github.com/ReVanced/revanced-api/commit/6c930fff9a99f9ee23edd3d661694d2074f53b23)) * Add `preferred` field to socials ([#100](https://github.com/ReVanced/revanced-api/issues/100)) ([24c8f60](https://github.com/ReVanced/revanced-api/commit/24c8f60a707ebec9465d9fdcd234b1c949a11a60)) * Add announcements API ([42f7318](https://github.com/ReVanced/revanced-api/commit/42f731854d0b91070bd7b075054d67d3d9e1d1b4)) * Add announcements endpoints ([#91](https://github.com/ReVanced/revanced-api/issues/91)) ([8583e2a](https://github.com/ReVanced/revanced-api/commit/8583e2a2bbdd62c4c10eb2af607f0bf6b686331c)) * Add backend rate limit route ([b967170](https://github.com/ReVanced/revanced-api/commit/b9671703be9f0842c00c38caa0953a177dc74491)) * Add CLI ([a988ffb](https://github.com/ReVanced/revanced-api/commit/a988ffbd2303a79ee18be7263ef6cd45c7bc4daf)) * add download_count to releases ([#118](https://github.com/ReVanced/revanced-api/issues/118)) ([665b913](https://github.com/ReVanced/revanced-api/commit/665b913c04edd8e55e7ba89839c5c39f8dfa42ac)) * add friendly crypto names ([#53](https://github.com/ReVanced/revanced-api/issues/53)) ([7b70780](https://github.com/ReVanced/revanced-api/commit/7b707807cc307c1a64abbb09983fb70d5b095698)) * Add GPG key to team members ([71f58cf](https://github.com/ReVanced/revanced-api/commit/71f58cf352e06b8504e00582e0c68aec55c4688b)) * Add local ReVanced API server ([cd5d57f](https://github.com/ReVanced/revanced-api/commit/cd5d57f8f87125df361e23715eda6e755203d727)) * add member gpg keys ([80cdb3b](https://github.com/ReVanced/revanced-api/commit/80cdb3be6ad2264977bf84269b09546a744576f5)) * Add OpenAPI docs and cache to routes ([6ea63be](https://github.com/ReVanced/revanced-api/commit/6ea63be490e7786c6486ee78c1fa38f302e8b81c)) * add preferred field to donations ([#56](https://github.com/ReVanced/revanced-api/issues/56)) ([6b705b1](https://github.com/ReVanced/revanced-api/commit/6b705b10545d5e6d2c9424275fbbd996bc45089f)) * Add proxy for old API ([39f54bb](https://github.com/ReVanced/revanced-api/commit/39f54bbb32512a6df255bbec6821f7f7acf91340)) * Add rate limiting to routes ([80403f7](https://github.com/ReVanced/revanced-api/commit/80403f7130cd48e68e802ee3111760256e49c77d)) * added branding ([#104](https://github.com/ReVanced/revanced-api/issues/104)) ([edcad62](https://github.com/ReVanced/revanced-api/commit/edcad620f29ee7e522bbc3a29db607f9823d9ce9)) * API Fixes and Adjustments ([#23](https://github.com/ReVanced/revanced-api/issues/23)) ([b18097e](https://github.com/ReVanced/revanced-api/commit/b18097e030c82e97f3880fd0788b562645f6e002)) * API rewrite ([#2](https://github.com/ReVanced/revanced-api/issues/2)) ([45ef337](https://github.com/ReVanced/revanced-api/commit/45ef33741c7a8fbd0144fda04370fe361a1d7c0c)) * better versioning engine ([8d36663](https://github.com/ReVanced/revanced-api/commit/8d36663610164d8198c18e5080184c1357f29254)) * Change default port to avoid using existing port ([9825865](https://github.com/ReVanced/revanced-api/commit/9825865bbc7505fc3e808d21a46a151151796591)) * Disallow all web crawlers ([#111](https://github.com/ReVanced/revanced-api/issues/111)) ([b69acfa](https://github.com/ReVanced/revanced-api/commit/b69acfa8d7f5385735f933a761239be6afd07384)) * Do not ignore, if `.env` file is missing ([24c6f4e](https://github.com/ReVanced/revanced-api/commit/24c6f4e4354b4e6da0e4a4e7f0ee0a7a5e3c90ed)) * favicon ([a8126d7](https://github.com/ReVanced/revanced-api/commit/a8126d785f8f828a746f7f1ce2fe20d1a605e8f8)) * hostname filtering ([5482d9c](https://github.com/ReVanced/revanced-api/commit/5482d9c44245bae75007935db6ec02fb553bfa2d)) * Implement more routes and add configuration ([9999b24](https://github.com/ReVanced/revanced-api/commit/9999b242ad05dcea9e6b021e3ed4eeead4567805)) * Improve routing paths ([df999c0](https://github.com/ReVanced/revanced-api/commit/df999c00c4c1f8645cc67ae19b732f0af9d71823)) * info endpoint ([#71](https://github.com/ReVanced/revanced-api/issues/71)) ([9bbd056](https://github.com/ReVanced/revanced-api/commit/9bbd056c1bc6a30059a8ed0a47823c228531e4c0)) * Initialize project ([8ae50b5](https://github.com/ReVanced/revanced-api/commit/8ae50b543eb78f2173c66acc2e9b676a7026f80a)) * Load system properties ([e373d26](https://github.com/ReVanced/revanced-api/commit/e373d269982428357bd04b659d9d17110cf0d5d2)) * manager-related endpoints ([3a128c4](https://github.com/ReVanced/revanced-api/commit/3a128c4661b161dc9961837056a3b64b84824162)) * Move config file to CLI argument ([6a9f0ca](https://github.com/ReVanced/revanced-api/commit/6a9f0cadac9e09bcf834ef708fab180038ebd4bd)) * **observability:** sentry ([99e645c](https://github.com/ReVanced/revanced-api/commit/99e645c5f5604b5b1c8cbab0bed22177fc01c695)) * project init ([856fc66](https://github.com/ReVanced/revanced-api/commit/856fc667170d21ae5aba996bcc356ca44412e10d)) * remove appinfo capabilities ([10f5225](https://github.com/ReVanced/revanced-api/commit/10f5225f514a03b2532ec430b5caace6c6049b92)) * remove codecov tests ([9c69fa3](https://github.com/ReVanced/revanced-api/commit/9c69fa3b924b2ecda5c52256c383d8619ec61ece)) * Remove Swagger and OpenAPI ([af0b086](https://github.com/ReVanced/revanced-api/commit/af0b0865f4c2f22975a836b72ff0b902ddac1ce9)) * Setup CI/CD ([c736a75](https://github.com/ReVanced/revanced-api/commit/c736a75d92d97124e1aca392f37049449a786c84)) * Setup cors and cache ([205bcde](https://github.com/ReVanced/revanced-api/commit/205bcde77aad90e0eb49fc25961399f1e37698bb)) * Show default CLI option values ([db0bfc3](https://github.com/ReVanced/revanced-api/commit/db0bfc3be5b8d86466fa7f1a01a72247b4e878aa)) * Use auth digest instead of basic auth ([89e2acf](https://github.com/ReVanced/revanced-api/commit/89e2acfebb5e14f71d9ce3d962c5531070b7600e)) * Use Jetty instead of Netty ([c23cd5c](https://github.com/ReVanced/revanced-api/commit/c23cd5cdad01fee52aecdb36b93a619886edfa4d)) * use objects for /socials and /donations ([#51](https://github.com/ReVanced/revanced-api/issues/51)) ([d4eac5c](https://github.com/ReVanced/revanced-api/commit/d4eac5c757106c4762cb3e95f9e4b3509132b39f)) * use objects for contact field ([5922830](https://github.com/ReVanced/revanced-api/commit/5922830e0bf1c0504d3205d2fa864c76d0f09c02)) ### Performance Improvements * Cache latest announcements for constant access time ([1ca9952](https://github.com/ReVanced/revanced-api/commit/1ca9952de81feeae1333872a7741119d6d8e7814)) * Cache patches list instead of just the patches file ([7a1957d](https://github.com/ReVanced/revanced-api/commit/7a1957d013cfd0851dc0191b715e8f36c530d9d2)) * Make async db transactions and use List instead of Set ([a7d1892](https://github.com/ReVanced/revanced-api/commit/a7d1892343094ddb2762ca346759771e6a274081)) --- CHANGELOG.md | 85 +++++++++++++++++++++++++++++++++++++++++++++++ gradle.properties | 1 + 2 files changed, 86 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..4d51167 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,85 @@ +# 1.0.0-dev.1 (2024-07-10) + + +### Bug Fixes + +* Add missing auth realm ([a6008a2](https://github.com/ReVanced/revanced-api/commit/a6008a2fb6d01fb577dcf357a1d698b6c1068b31)) +* Add missing OpenAPI docs ([d4ac471](https://github.com/ReVanced/revanced-api/commit/d4ac47194e51b3e708516150d1690a72830ab809)) +* add required headers ([#8](https://github.com/ReVanced/revanced-api/issues/8)) ([f4c10dc](https://github.com/ReVanced/revanced-api/commit/f4c10dc064c4ac0fb6b8e612d680e4f6bda2b0c7)) +* Add uri to rate limiter request key ([e8c2488](https://github.com/ReVanced/revanced-api/commit/e8c2488bc61793345c4b8171e520fb0127b34643)) +* **ci:** add git dep ([d61ddcc](https://github.com/ReVanced/revanced-api/commit/d61ddcc8ac76d78b7a79c09bbfba812f652aa4cf)) +* **ci:** fix git deps ([5564c2d](https://github.com/ReVanced/revanced-api/commit/5564c2da0946d67abab0f95e43fe6e6b108f5ec7)) +* Correct env var comment ([9d7b049](https://github.com/ReVanced/revanced-api/commit/9d7b0493498bbf594928fc61181beadf6f59643b)) +* Correct persistence directory name ([6238e33](https://github.com/ReVanced/revanced-api/commit/6238e33c42216885d5e9e8075bfc46eaf2a990ed)) +* **deps:** fix requirements file ([5f0ab26](https://github.com/ReVanced/revanced-api/commit/5f0ab26cedef27cc95eec58ea4dce00904b53289)) +* **deps:** missing pydantic ([089f29e](https://github.com/ReVanced/revanced-api/commit/089f29e95fc6b87f90885eb31b8e3460857224a8)) +* **deps:** update poetry.lock ([b2c2fa7](https://github.com/ReVanced/revanced-api/commit/b2c2fa7136f6305703ba11d0bd5a87c0d563eaf3)) +* Don't configure server ([280dbc3](https://github.com/ReVanced/revanced-api/commit/280dbc30f607483adad0bde6ab1b016d5da047ba)) +* Encode defaults to fix OpenAPI spec ([e9d1c8f](https://github.com/ReVanced/revanced-api/commit/e9d1c8fae0bc46e761056197658c4bb045784104)) +* Finish DB Model to API model transformation inside transaction ([89a577e](https://github.com/ReVanced/revanced-api/commit/89a577e91abbfcd2865e770088661eac4aeb4dd7)) +* fix codeql issues ([b5568db](https://github.com/ReVanced/revanced-api/commit/b5568db79fa0620d06d6945b42a66744b6340bc5)) +* Fix spelling mistake ([17ecf58](https://github.com/ReVanced/revanced-api/commit/17ecf58e550d13dd93ab69e1cf522366aeb3da3f)) +* Increase pool size to mitigate overflow ([#113](https://github.com/ReVanced/revanced-api/issues/113)) ([5aed3d6](https://github.com/ReVanced/revanced-api/commit/5aed3d6ce6efcaf04b3cfa8f344623413518c9b1)) +* Remove punctuation ([f9cae1e](https://github.com/ReVanced/revanced-api/commit/f9cae1ea56c93aded25159f6b0814bf84d192192)) +* remove revanced-api from tools map ([4800ee9](https://github.com/ReVanced/revanced-api/commit/4800ee96a82c0a9fc1905c6960cd560b55304944)) +* Serialize response correctly ([1dccfd2](https://github.com/ReVanced/revanced-api/commit/1dccfd2deff3c5de6a6cf2156cac8516b40bdd22)) +* Set body for all eligible request methods ([c6cacef](https://github.com/ReVanced/revanced-api/commit/c6cacef907a5039ed029e1e26204aaba8e698aaa)) +* unversioned endpoints placement under v0 should be at root ([8b29f49](https://github.com/ReVanced/revanced-api/commit/8b29f49805d4a651bdd26aa4958a3639760a2f4b)) +* Use correct persistance folder path ([500a589](https://github.com/ReVanced/revanced-api/commit/500a5896e34f6a9600dcec2834b3d340161bb90a)) +* Use correct proxy path ([ef92768](https://github.com/ReVanced/revanced-api/commit/ef927688a377c16fe37b578ea870207871c30056)) +* Use correct resource path ([4dffd32](https://github.com/ReVanced/revanced-api/commit/4dffd32c99a5a3deafe21bc9e9960795ff93ff1d)) +* Use multidict 6.0.5, add setup tools as dev dependency ([af19446](https://github.com/ReVanced/revanced-api/commit/af19446a67445e96624dfdb9f1fa6b4de77545c8)) + + +### Features + +* Add `/list` route ([6c930ff](https://github.com/ReVanced/revanced-api/commit/6c930fff9a99f9ee23edd3d661694d2074f53b23)) +* Add `preferred` field to socials ([#100](https://github.com/ReVanced/revanced-api/issues/100)) ([24c8f60](https://github.com/ReVanced/revanced-api/commit/24c8f60a707ebec9465d9fdcd234b1c949a11a60)) +* Add announcements API ([42f7318](https://github.com/ReVanced/revanced-api/commit/42f731854d0b91070bd7b075054d67d3d9e1d1b4)) +* Add announcements endpoints ([#91](https://github.com/ReVanced/revanced-api/issues/91)) ([8583e2a](https://github.com/ReVanced/revanced-api/commit/8583e2a2bbdd62c4c10eb2af607f0bf6b686331c)) +* Add backend rate limit route ([b967170](https://github.com/ReVanced/revanced-api/commit/b9671703be9f0842c00c38caa0953a177dc74491)) +* Add CLI ([a988ffb](https://github.com/ReVanced/revanced-api/commit/a988ffbd2303a79ee18be7263ef6cd45c7bc4daf)) +* add download_count to releases ([#118](https://github.com/ReVanced/revanced-api/issues/118)) ([665b913](https://github.com/ReVanced/revanced-api/commit/665b913c04edd8e55e7ba89839c5c39f8dfa42ac)) +* add friendly crypto names ([#53](https://github.com/ReVanced/revanced-api/issues/53)) ([7b70780](https://github.com/ReVanced/revanced-api/commit/7b707807cc307c1a64abbb09983fb70d5b095698)) +* Add GPG key to team members ([71f58cf](https://github.com/ReVanced/revanced-api/commit/71f58cf352e06b8504e00582e0c68aec55c4688b)) +* Add local ReVanced API server ([cd5d57f](https://github.com/ReVanced/revanced-api/commit/cd5d57f8f87125df361e23715eda6e755203d727)) +* add member gpg keys ([80cdb3b](https://github.com/ReVanced/revanced-api/commit/80cdb3be6ad2264977bf84269b09546a744576f5)) +* Add OpenAPI docs and cache to routes ([6ea63be](https://github.com/ReVanced/revanced-api/commit/6ea63be490e7786c6486ee78c1fa38f302e8b81c)) +* add preferred field to donations ([#56](https://github.com/ReVanced/revanced-api/issues/56)) ([6b705b1](https://github.com/ReVanced/revanced-api/commit/6b705b10545d5e6d2c9424275fbbd996bc45089f)) +* Add proxy for old API ([39f54bb](https://github.com/ReVanced/revanced-api/commit/39f54bbb32512a6df255bbec6821f7f7acf91340)) +* Add rate limiting to routes ([80403f7](https://github.com/ReVanced/revanced-api/commit/80403f7130cd48e68e802ee3111760256e49c77d)) +* added branding ([#104](https://github.com/ReVanced/revanced-api/issues/104)) ([edcad62](https://github.com/ReVanced/revanced-api/commit/edcad620f29ee7e522bbc3a29db607f9823d9ce9)) +* API Fixes and Adjustments ([#23](https://github.com/ReVanced/revanced-api/issues/23)) ([b18097e](https://github.com/ReVanced/revanced-api/commit/b18097e030c82e97f3880fd0788b562645f6e002)) +* API rewrite ([#2](https://github.com/ReVanced/revanced-api/issues/2)) ([45ef337](https://github.com/ReVanced/revanced-api/commit/45ef33741c7a8fbd0144fda04370fe361a1d7c0c)) +* better versioning engine ([8d36663](https://github.com/ReVanced/revanced-api/commit/8d36663610164d8198c18e5080184c1357f29254)) +* Change default port to avoid using existing port ([9825865](https://github.com/ReVanced/revanced-api/commit/9825865bbc7505fc3e808d21a46a151151796591)) +* Disallow all web crawlers ([#111](https://github.com/ReVanced/revanced-api/issues/111)) ([b69acfa](https://github.com/ReVanced/revanced-api/commit/b69acfa8d7f5385735f933a761239be6afd07384)) +* Do not ignore, if `.env` file is missing ([24c6f4e](https://github.com/ReVanced/revanced-api/commit/24c6f4e4354b4e6da0e4a4e7f0ee0a7a5e3c90ed)) +* favicon ([a8126d7](https://github.com/ReVanced/revanced-api/commit/a8126d785f8f828a746f7f1ce2fe20d1a605e8f8)) +* hostname filtering ([5482d9c](https://github.com/ReVanced/revanced-api/commit/5482d9c44245bae75007935db6ec02fb553bfa2d)) +* Implement more routes and add configuration ([9999b24](https://github.com/ReVanced/revanced-api/commit/9999b242ad05dcea9e6b021e3ed4eeead4567805)) +* Improve routing paths ([df999c0](https://github.com/ReVanced/revanced-api/commit/df999c00c4c1f8645cc67ae19b732f0af9d71823)) +* info endpoint ([#71](https://github.com/ReVanced/revanced-api/issues/71)) ([9bbd056](https://github.com/ReVanced/revanced-api/commit/9bbd056c1bc6a30059a8ed0a47823c228531e4c0)) +* Initialize project ([8ae50b5](https://github.com/ReVanced/revanced-api/commit/8ae50b543eb78f2173c66acc2e9b676a7026f80a)) +* Load system properties ([e373d26](https://github.com/ReVanced/revanced-api/commit/e373d269982428357bd04b659d9d17110cf0d5d2)) +* manager-related endpoints ([3a128c4](https://github.com/ReVanced/revanced-api/commit/3a128c4661b161dc9961837056a3b64b84824162)) +* Move config file to CLI argument ([6a9f0ca](https://github.com/ReVanced/revanced-api/commit/6a9f0cadac9e09bcf834ef708fab180038ebd4bd)) +* **observability:** sentry ([99e645c](https://github.com/ReVanced/revanced-api/commit/99e645c5f5604b5b1c8cbab0bed22177fc01c695)) +* project init ([856fc66](https://github.com/ReVanced/revanced-api/commit/856fc667170d21ae5aba996bcc356ca44412e10d)) +* remove appinfo capabilities ([10f5225](https://github.com/ReVanced/revanced-api/commit/10f5225f514a03b2532ec430b5caace6c6049b92)) +* remove codecov tests ([9c69fa3](https://github.com/ReVanced/revanced-api/commit/9c69fa3b924b2ecda5c52256c383d8619ec61ece)) +* Remove Swagger and OpenAPI ([af0b086](https://github.com/ReVanced/revanced-api/commit/af0b0865f4c2f22975a836b72ff0b902ddac1ce9)) +* Setup CI/CD ([c736a75](https://github.com/ReVanced/revanced-api/commit/c736a75d92d97124e1aca392f37049449a786c84)) +* Setup cors and cache ([205bcde](https://github.com/ReVanced/revanced-api/commit/205bcde77aad90e0eb49fc25961399f1e37698bb)) +* Show default CLI option values ([db0bfc3](https://github.com/ReVanced/revanced-api/commit/db0bfc3be5b8d86466fa7f1a01a72247b4e878aa)) +* Use auth digest instead of basic auth ([89e2acf](https://github.com/ReVanced/revanced-api/commit/89e2acfebb5e14f71d9ce3d962c5531070b7600e)) +* Use Jetty instead of Netty ([c23cd5c](https://github.com/ReVanced/revanced-api/commit/c23cd5cdad01fee52aecdb36b93a619886edfa4d)) +* use objects for /socials and /donations ([#51](https://github.com/ReVanced/revanced-api/issues/51)) ([d4eac5c](https://github.com/ReVanced/revanced-api/commit/d4eac5c757106c4762cb3e95f9e4b3509132b39f)) +* use objects for contact field ([5922830](https://github.com/ReVanced/revanced-api/commit/5922830e0bf1c0504d3205d2fa864c76d0f09c02)) + + +### Performance Improvements + +* Cache latest announcements for constant access time ([1ca9952](https://github.com/ReVanced/revanced-api/commit/1ca9952de81feeae1333872a7741119d6d8e7814)) +* Cache patches list instead of just the patches file ([7a1957d](https://github.com/ReVanced/revanced-api/commit/7a1957d013cfd0851dc0191b715e8f36c530d9d2)) +* Make async db transactions and use List instead of Set ([a7d1892](https://github.com/ReVanced/revanced-api/commit/a7d1892343094ddb2762ca346759771e6a274081)) diff --git a/gradle.properties b/gradle.properties index 3178960..aa531a5 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,3 +1,4 @@ org.gradle.parallel = true org.gradle.caching = true kotlin.code.style = official +version = 1.0.0-dev.1 From 2ed4cf3b40caeb6181d068d411344e6732000f22 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Thu, 11 Jul 2024 03:50:48 +0200 Subject: [PATCH 087/102] fix: Configure CORS correctly --- configuration.example.toml | 7 +++++-- .../app/revanced/api/configuration/HTTP.kt | 10 ++++++---- .../repository/ConfigurationRepository.kt | 18 +++--------------- 3 files changed, 14 insertions(+), 21 deletions(-) diff --git a/configuration.example.toml b/configuration.example.toml index 47ff97e..6ea94c0 100644 --- a/configuration.example.toml +++ b/configuration.example.toml @@ -10,5 +10,8 @@ contributors-repositories = [ "revanced-manager", ] api-version = 1 -cors = { host = "*.revanced.app", sub-domains = [] } -endpoint = "https://api.revanced.app" \ No newline at end of file +cors-allowed-hosts = [ + "revanced.app", + "*.revanced.app" +] +endpoint = "https://api.revanced.app" diff --git a/src/main/kotlin/app/revanced/api/configuration/HTTP.kt b/src/main/kotlin/app/revanced/api/configuration/HTTP.kt index 06c8a1f..0e79530 100644 --- a/src/main/kotlin/app/revanced/api/configuration/HTTP.kt +++ b/src/main/kotlin/app/revanced/api/configuration/HTTP.kt @@ -13,10 +13,12 @@ fun Application.configureHTTP() { val configurationRepository = get() install(CORS) { - allowHost( - host = configurationRepository.cors.host, - subDomains = configurationRepository.cors.subDomains, - ) + configurationRepository.corsAllowedHosts.forEach { host -> + allowHost( + host = host, + schemes = listOf("http", "https") + ) + } } install(RateLimit) { diff --git a/src/main/kotlin/app/revanced/api/configuration/repository/ConfigurationRepository.kt b/src/main/kotlin/app/revanced/api/configuration/repository/ConfigurationRepository.kt index aed455a..752bccd 100644 --- a/src/main/kotlin/app/revanced/api/configuration/repository/ConfigurationRepository.kt +++ b/src/main/kotlin/app/revanced/api/configuration/repository/ConfigurationRepository.kt @@ -19,7 +19,7 @@ import java.io.File * @property integrations The source of the integrations. * @property contributorsRepositoryNames The names of the repositories to get contributors from. * @property apiVersion The version to use for the API. - * @property cors The CORS configuration. + * @property corsAllowedHosts The hosts allowed to make requests to the API. * @property endpoint The endpoint of the API. */ @Serializable @@ -31,7 +31,8 @@ internal class ConfigurationRepository( val contributorsRepositoryNames: Set, @SerialName("api-version") val apiVersion: Int = 1, - val cors: Cors, + @SerialName("cors-allowed-hosts") + val corsAllowedHosts: Set, val endpoint: String, ) { /** @@ -61,19 +62,6 @@ internal class ConfigurationRepository( @SerialName("public-key-file") val publicKeyFile: File, ) - - /** - * The CORS configuration. - * - * @property host The host of the API to configure CORS. - * @property subDomains The subdomains to allow for CORS. - */ - @Serializable - internal class Cors( - val host: String, - @SerialName("sub-domains") - val subDomains: List, - ) } private object RegexSerializer : KSerializer { From 8f77736a69183bfa5785a960c5194b3b13f48087 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Thu, 11 Jul 2024 01:52:59 +0000 Subject: [PATCH 088/102] chore(release): 1.0.0-dev.2 [skip ci] # [1.0.0-dev.2](https://github.com/ReVanced/revanced-api/compare/v1.0.0-dev.1...v1.0.0-dev.2) (2024-07-11) ### Bug Fixes * Configure CORS correctly ([2ed4cf3](https://github.com/ReVanced/revanced-api/commit/2ed4cf3b40caeb6181d068d411344e6732000f22)) --- CHANGELOG.md | 7 +++++++ gradle.properties | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d51167..23280d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +# [1.0.0-dev.2](https://github.com/ReVanced/revanced-api/compare/v1.0.0-dev.1...v1.0.0-dev.2) (2024-07-11) + + +### Bug Fixes + +* Configure CORS correctly ([2ed4cf3](https://github.com/ReVanced/revanced-api/commit/2ed4cf3b40caeb6181d068d411344e6732000f22)) + # 1.0.0-dev.1 (2024-07-10) diff --git a/gradle.properties b/gradle.properties index aa531a5..1518c00 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ org.gradle.parallel = true org.gradle.caching = true kotlin.code.style = official -version = 1.0.0-dev.1 +version = 1.0.0-dev.2 From 7e99e49af202c4ec0a0d7e61dd0182dd2097e867 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Thu, 11 Jul 2024 04:26:26 +0200 Subject: [PATCH 089/102] fix: Move old API endpoint configuration from env to configuration file --- .env.example | 2 -- configuration.example.toml | 1 + configuration.toml | 14 -------------- .../app/revanced/api/configuration/Dependencies.kt | 5 +++-- .../repository/ConfigurationRepository.kt | 3 +++ 5 files changed, 7 insertions(+), 18 deletions(-) delete mode 100644 configuration.toml diff --git a/.env.example b/.env.example index 1376e76..1b1b56f 100644 --- a/.env.example +++ b/.env.example @@ -1,7 +1,5 @@ # Optional token for API calls to the backend BACKEND_API_TOKEN= -# A URL to the old API to proxy for migration purposes -OLD_API_URL= # Database connection details DB_URL=jdbc:h2:./persistence/revanced-api diff --git a/configuration.example.toml b/configuration.example.toml index 6ea94c0..f2b1795 100644 --- a/configuration.example.toml +++ b/configuration.example.toml @@ -15,3 +15,4 @@ cors-allowed-hosts = [ "*.revanced.app" ] endpoint = "https://api.revanced.app" +old-api-endpoint = "https://old-api.revanced.app" diff --git a/configuration.toml b/configuration.toml deleted file mode 100644 index 37da260..0000000 --- a/configuration.toml +++ /dev/null @@ -1,14 +0,0 @@ -organization = "revanced" -patches = { repository = "revanced-patches", asset-regex = "jar$", signature-asset-regex = "asc$", public-key-file = "key.asc" } -integrations = { repository = "revanced-integrations", asset-regex = "apk$", signature-asset-regex = "asc$", public-key-file = "key.asc" } -contributors-repositories = [ - "revanced-patcher", - "revanced-patches", - "revanced-integrations", - "revanced-website", - "revanced-cli", - "revanced-manager", -] -api-version = 1 -cors = { host = "*.127.0.0.1:8888", sub-domains = [] } -endpoint = "http://127.0.0.1:8888/" \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/api/configuration/Dependencies.kt b/src/main/kotlin/app/revanced/api/configuration/Dependencies.kt index 3f9f285..559ac14 100644 --- a/src/main/kotlin/app/revanced/api/configuration/Dependencies.kt +++ b/src/main/kotlin/app/revanced/api/configuration/Dependencies.kt @@ -123,10 +123,11 @@ fun Application.configureDependencies( AuthService(issuer, validityInMin, jwtSecret, authSHA256DigestString) } single { + val configuration = get() + OldApiService( get { - val defaultRequestUri = get()["OLD_API_URL"] - parameterArrayOf(defaultRequestUri) + parameterArrayOf(configuration.oldApiEndpoint) }, ) } diff --git a/src/main/kotlin/app/revanced/api/configuration/repository/ConfigurationRepository.kt b/src/main/kotlin/app/revanced/api/configuration/repository/ConfigurationRepository.kt index 752bccd..c70b412 100644 --- a/src/main/kotlin/app/revanced/api/configuration/repository/ConfigurationRepository.kt +++ b/src/main/kotlin/app/revanced/api/configuration/repository/ConfigurationRepository.kt @@ -21,6 +21,7 @@ import java.io.File * @property apiVersion The version to use for the API. * @property corsAllowedHosts The hosts allowed to make requests to the API. * @property endpoint The endpoint of the API. + * @property oldApiEndpoint The endpoint of the old API to proxy requests to. */ @Serializable internal class ConfigurationRepository( @@ -34,6 +35,8 @@ internal class ConfigurationRepository( @SerialName("cors-allowed-hosts") val corsAllowedHosts: Set, val endpoint: String, + @SerialName("old-api-endpoint") + val oldApiEndpoint: String, ) { /** * An asset configuration. From e92fbdf1f4849fbe05aa48e444a3b031771ca30b Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Thu, 11 Jul 2024 02:28:50 +0000 Subject: [PATCH 090/102] chore(release): 1.0.0-dev.3 [skip ci] # [1.0.0-dev.3](https://github.com/ReVanced/revanced-api/compare/v1.0.0-dev.2...v1.0.0-dev.3) (2024-07-11) ### Bug Fixes * Move old API endpoint configuration from env to configuration file ([7e99e49](https://github.com/ReVanced/revanced-api/commit/7e99e49af202c4ec0a0d7e61dd0182dd2097e867)) --- CHANGELOG.md | 7 +++++++ gradle.properties | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 23280d9..6e1dc48 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +# [1.0.0-dev.3](https://github.com/ReVanced/revanced-api/compare/v1.0.0-dev.2...v1.0.0-dev.3) (2024-07-11) + + +### Bug Fixes + +* Move old API endpoint configuration from env to configuration file ([7e99e49](https://github.com/ReVanced/revanced-api/commit/7e99e49af202c4ec0a0d7e61dd0182dd2097e867)) + # [1.0.0-dev.2](https://github.com/ReVanced/revanced-api/compare/v1.0.0-dev.1...v1.0.0-dev.2) (2024-07-11) diff --git a/gradle.properties b/gradle.properties index 1518c00..f376d14 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ org.gradle.parallel = true org.gradle.caching = true kotlin.code.style = official -version = 1.0.0-dev.2 +version = 1.0.0-dev.3 From 19ebc827bfb54a597dd06f9d99bdc820ee9977ee Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Thu, 11 Jul 2024 22:53:27 +0200 Subject: [PATCH 091/102] feat: List more repository contributors --- .../api/configuration/repository/GitHubBackendRepository.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/app/revanced/api/configuration/repository/GitHubBackendRepository.kt b/src/main/kotlin/app/revanced/api/configuration/repository/GitHubBackendRepository.kt index a032f4d..fed063f 100644 --- a/src/main/kotlin/app/revanced/api/configuration/repository/GitHubBackendRepository.kt +++ b/src/main/kotlin/app/revanced/api/configuration/repository/GitHubBackendRepository.kt @@ -16,6 +16,7 @@ import kotlinx.coroutines.* import kotlinx.datetime.Instant import kotlinx.datetime.TimeZone import kotlinx.datetime.toLocalDateTime +import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable class GitHubBackendRepository(client: HttpClient) : BackendRepository(client) { @@ -191,10 +192,9 @@ class Organization { class Repository { @Resource("/repos/{owner}/{repo}/contributors") - class Contributors(val owner: String, val repo: String) + class Contributors(val owner: String, val repo: String, @SerialName("per_page") val perPage: Int = 100) - @Resource("/repos/{owner}/{repo}/releases") - class Releases(val owner: String, val repo: String) { + class Releases { @Resource("/repos/{owner}/{repo}/releases/tags/{tag}") class Tag(val owner: String, val repo: String, val tag: String) From f754ebb25c9c15cd4b47592d0984674603e4cbaa Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Thu, 11 Jul 2024 20:55:17 +0000 Subject: [PATCH 092/102] chore(release): 1.0.0-dev.4 [skip ci] # [1.0.0-dev.4](https://github.com/ReVanced/revanced-api/compare/v1.0.0-dev.3...v1.0.0-dev.4) (2024-07-11) ### Features * List more repository contributors ([19ebc82](https://github.com/ReVanced/revanced-api/commit/19ebc827bfb54a597dd06f9d99bdc820ee9977ee)) --- CHANGELOG.md | 7 +++++++ gradle.properties | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6e1dc48..de537e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +# [1.0.0-dev.4](https://github.com/ReVanced/revanced-api/compare/v1.0.0-dev.3...v1.0.0-dev.4) (2024-07-11) + + +### Features + +* List more repository contributors ([19ebc82](https://github.com/ReVanced/revanced-api/commit/19ebc827bfb54a597dd06f9d99bdc820ee9977ee)) + # [1.0.0-dev.3](https://github.com/ReVanced/revanced-api/compare/v1.0.0-dev.2...v1.0.0-dev.3) (2024-07-11) diff --git a/gradle.properties b/gradle.properties index f376d14..d3d8810 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ org.gradle.parallel = true org.gradle.caching = true kotlin.code.style = official -version = 1.0.0-dev.3 +version = 1.0.0-dev.4 From 2ade550d58c0e4b53fa7417bef0064f4f476aed8 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Fri, 12 Jul 2024 02:46:03 +0200 Subject: [PATCH 093/102] fix: Move robots.txt to root --- .../app/revanced/api/configuration/Routing.kt | 13 ++++++++++--- .../revanced/api/configuration/routes/ApiRoute.kt | 4 ++-- .../app/revanced/api/static/{ => root}/robots.txt | 0 .../revanced/api/static/{ => versioned}/about.json | 0 4 files changed, 12 insertions(+), 5 deletions(-) rename src/main/resources/app/revanced/api/static/{ => root}/robots.txt (100%) rename src/main/resources/app/revanced/api/static/{ => versioned}/about.json (100%) diff --git a/src/main/kotlin/app/revanced/api/configuration/Routing.kt b/src/main/kotlin/app/revanced/api/configuration/Routing.kt index 2ac2109..074f926 100644 --- a/src/main/kotlin/app/revanced/api/configuration/Routing.kt +++ b/src/main/kotlin/app/revanced/api/configuration/Routing.kt @@ -2,12 +2,14 @@ package app.revanced.api.configuration import app.revanced.api.configuration.repository.ConfigurationRepository import app.revanced.api.configuration.routes.announcementsRoute +import app.revanced.api.configuration.routes.apiRoute import app.revanced.api.configuration.routes.oldApiRoute import app.revanced.api.configuration.routes.patchesRoute -import app.revanced.api.configuration.routes.rootRoute import io.bkbn.kompendium.core.routes.redoc import io.bkbn.kompendium.core.routes.swagger +import io.ktor.http.* import io.ktor.server.application.* +import io.ktor.server.http.content.* import io.ktor.server.routing.* import kotlin.time.Duration.Companion.minutes import org.koin.ktor.ext.get as koinGet @@ -18,9 +20,14 @@ internal fun Application.configureRouting() = routing { installCache(5.minutes) route("/v${configuration.apiVersion}") { - patchesRoute() announcementsRoute() - rootRoute() + patchesRoute() + apiRoute() + } + + staticResources("/", "/app/revanced/api/static/root") { + contentType { ContentType.Application.Json } + extensions("json") } swagger(pageTitle = "ReVanced API", path = "/") diff --git a/src/main/kotlin/app/revanced/api/configuration/routes/ApiRoute.kt b/src/main/kotlin/app/revanced/api/configuration/routes/ApiRoute.kt index 7cb56f2..7cb5d82 100644 --- a/src/main/kotlin/app/revanced/api/configuration/routes/ApiRoute.kt +++ b/src/main/kotlin/app/revanced/api/configuration/routes/ApiRoute.kt @@ -20,7 +20,7 @@ import io.ktor.server.routing.* import kotlin.time.Duration.Companion.days import org.koin.ktor.ext.get as koinGet -internal fun Route.rootRoute() { +internal fun Route.apiRoute() { val apiService = koinGet() val authService = koinGet() @@ -75,7 +75,7 @@ internal fun Route.rootRoute() { } } - staticResources("/", "/app/revanced/api/static") { + staticResources("/", "/app/revanced/api/static/versioned") { contentType { ContentType.Application.Json } extensions("json") } diff --git a/src/main/resources/app/revanced/api/static/robots.txt b/src/main/resources/app/revanced/api/static/root/robots.txt similarity index 100% rename from src/main/resources/app/revanced/api/static/robots.txt rename to src/main/resources/app/revanced/api/static/root/robots.txt diff --git a/src/main/resources/app/revanced/api/static/about.json b/src/main/resources/app/revanced/api/static/versioned/about.json similarity index 100% rename from src/main/resources/app/revanced/api/static/about.json rename to src/main/resources/app/revanced/api/static/versioned/about.json From 97a5d119ec415f9c25fbc50cb240603047defc73 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Sat, 13 Jul 2024 00:26:59 +0200 Subject: [PATCH 094/102] fix: Only list public members --- .../configuration/repository/GitHubBackendRepository.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/kotlin/app/revanced/api/configuration/repository/GitHubBackendRepository.kt b/src/main/kotlin/app/revanced/api/configuration/repository/GitHubBackendRepository.kt index fed063f..d5d9148 100644 --- a/src/main/kotlin/app/revanced/api/configuration/repository/GitHubBackendRepository.kt +++ b/src/main/kotlin/app/revanced/api/configuration/repository/GitHubBackendRepository.kt @@ -67,10 +67,10 @@ class GitHubBackendRepository(client: HttpClient) : BackendRepository(client) { override suspend fun members(organization: String): List { // Get the list of members of the organization. - val members: List = client.get(Organization.Members(organization)).body() + val publicMembers: List = client.get(Organization.PublicMembers(organization)).body() return coroutineScope { - members.map { member -> + publicMembers.map { member -> async { awaitAll( async { @@ -187,8 +187,8 @@ class User(val login: String) { } class Organization { - @Resource("/orgs/{org}/members") - class Members(val org: String) + @Resource("/orgs/{org}/public_members") + class PublicMembers(val org: String) class Repository { @Resource("/repos/{owner}/{repo}/contributors") From ad7d4b226f71fd965421dc2f2a51825c8e8b3036 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Sat, 13 Jul 2024 00:27:54 +0200 Subject: [PATCH 095/102] feat: Add configuration to specify public key id --- .../repository/ConfigurationRepository.kt | 3 ++ .../configuration/services/PatchesService.kt | 1 + .../services/SignatureService.kt | 45 +++++++++---------- 3 files changed, 24 insertions(+), 25 deletions(-) diff --git a/src/main/kotlin/app/revanced/api/configuration/repository/ConfigurationRepository.kt b/src/main/kotlin/app/revanced/api/configuration/repository/ConfigurationRepository.kt index c70b412..bb567f3 100644 --- a/src/main/kotlin/app/revanced/api/configuration/repository/ConfigurationRepository.kt +++ b/src/main/kotlin/app/revanced/api/configuration/repository/ConfigurationRepository.kt @@ -51,6 +51,7 @@ internal class ConfigurationRepository( * @property assetRegex The regex matching the asset name. * @property signatureAssetRegex The regex matching the signature asset name to verify the asset. * @property publicKeyFile The public key file to verify the signature of the asset. + * @property publicKeyId The ID of the public key to verify the signature of the asset. */ @Serializable internal class AssetConfiguration( @@ -64,6 +65,8 @@ internal class ConfigurationRepository( @Serializable(with = FileSerializer::class) @SerialName("public-key-file") val publicKeyFile: File, + @SerialName("public-key-id") + val publicKeyId: Long, ) } diff --git a/src/main/kotlin/app/revanced/api/configuration/services/PatchesService.kt b/src/main/kotlin/app/revanced/api/configuration/services/PatchesService.kt index 31a0670..e24eb26 100644 --- a/src/main/kotlin/app/revanced/api/configuration/services/PatchesService.kt +++ b/src/main/kotlin/app/revanced/api/configuration/services/PatchesService.kt @@ -92,6 +92,7 @@ internal class PatchesService( patchesFile, signatureDownloadUrl, configurationRepository.patches.publicKeyFile, + configurationRepository.patches.publicKeyId, ) ) { PatchBundleLoader.Jar(patchesFile) diff --git a/src/main/kotlin/app/revanced/api/configuration/services/SignatureService.kt b/src/main/kotlin/app/revanced/api/configuration/services/SignatureService.kt index 89ea40e..80eb2d1 100644 --- a/src/main/kotlin/app/revanced/api/configuration/services/SignatureService.kt +++ b/src/main/kotlin/app/revanced/api/configuration/services/SignatureService.kt @@ -1,7 +1,6 @@ package app.revanced.api.configuration.services import com.github.benmanes.caffeine.cache.Caffeine -import org.bouncycastle.jce.provider.BouncyCastleProvider import org.bouncycastle.openpgp.* import org.bouncycastle.openpgp.operator.bc.BcKeyFingerprintCalculator import org.bouncycastle.openpgp.operator.bc.BcPGPContentVerifierBuilderProvider @@ -9,7 +8,6 @@ import java.io.File import java.io.InputStream import java.net.URL import java.security.MessageDigest -import java.security.Security internal class SignatureService { private val signatureCache = Caffeine @@ -21,6 +19,7 @@ internal class SignatureService { file: File, signatureDownloadUrl: String, publicKeyFile: File, + publicKeyId: Long, ): Boolean { val fileBytes = file.readBytes() @@ -28,7 +27,8 @@ internal class SignatureService { verify( fileBytes = fileBytes, signatureInputStream = URL(signatureDownloadUrl).openStream(), - publicKeyInputStream = publicKeyFile.inputStream(), + publicKeyFileInputStream = publicKeyFile.inputStream(), + publicKeyId = publicKeyId, ) } } @@ -36,37 +36,32 @@ internal class SignatureService { private fun verify( fileBytes: ByteArray, signatureInputStream: InputStream, - publicKeyInputStream: InputStream, + publicKeyFileInputStream: InputStream, + publicKeyId: Long, ) = getSignature(signatureInputStream).apply { - init(BcPGPContentVerifierBuilderProvider(), getPublicKey(publicKeyInputStream)) + init(BcPGPContentVerifierBuilderProvider(), getPublicKey(publicKeyFileInputStream, publicKeyId)) update(fileBytes) }.verify() - private fun getPublicKey(publicKeyInputStream: InputStream): PGPPublicKey { - val decoderStream = PGPUtil.getDecoderStream(publicKeyInputStream) + private fun getPublicKey( + publicKeyFileInputStream: InputStream, + publicKeyId: Long, + ): PGPPublicKey { + val decoderStream = PGPUtil.getDecoderStream(publicKeyFileInputStream) + val pgpPublicKeyRingCollection = PGPPublicKeyRingCollection(decoderStream, BcKeyFingerprintCalculator()) + val publicKeyRing = pgpPublicKeyRingCollection.getPublicKeyRing(publicKeyId) + ?: throw IllegalArgumentException("Can't find public key ring with ID $publicKeyId.") - PGPPublicKeyRingCollection(decoderStream, BcKeyFingerprintCalculator()).forEach { keyRing -> - keyRing.publicKeys.forEach { publicKey -> - if (publicKey.isEncryptionKey) { - return publicKey - } - } - } - - throw IllegalArgumentException("Can't find encryption key in key ring.") + return publicKeyRing.getPublicKey(publicKeyId) + ?: throw IllegalArgumentException("Can't find public key with ID $publicKeyId.") } private fun getSignature(inputStream: InputStream): PGPSignature { val decoderStream = PGPUtil.getDecoderStream(inputStream) - val pgpObjectFactory = PGPObjectFactory(decoderStream, BcKeyFingerprintCalculator()) - val signatureList = pgpObjectFactory.nextObject() as PGPSignatureList + val pgpSignatureList = PGPObjectFactory(decoderStream, BcKeyFingerprintCalculator()).first { + it is PGPSignatureList + } as PGPSignatureList - return signatureList.first() - } - - private companion object { - init { - Security.addProvider(BouncyCastleProvider()) - } + return pgpSignatureList.first() } } From a34b7c8c313850e9a59df046421958537c589fba Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Sat, 13 Jul 2024 00:46:19 +0200 Subject: [PATCH 096/102] ci: Correct usage of repository variable --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 85c381c..df90459 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -45,7 +45,7 @@ jobs: with: gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }} passphrase: ${{ secrets.GPG_PASSPHRASE }} - fingerprint: ${{ env.GPG_FINGERPRINT }} + fingerprint: ${{ vars.GPG_FINGERPRINT }} - name: Setup QEMU uses: docker/setup-qemu-action@v3 From f814fe5825eb8b864f2b0cba53ff721eba577eb4 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Sat, 13 Jul 2024 01:34:42 +0200 Subject: [PATCH 097/102] feat: Add manager route --- configuration.example.toml | 1 + .../api/configuration/Dependencies.kt | 1 + .../app/revanced/api/configuration/Routing.kt | 2 + .../repository/ConfigurationRepository.kt | 33 ++++++++-- .../api/configuration/routes/ApiRoute.kt | 10 +-- .../api/configuration/routes/ManagerRoute.kt | 66 +++++++++++++++++++ .../api/configuration/routes/PatchesRoute.kt | 11 ++-- .../api/configuration/schema/APISchema.kt | 25 ++++--- .../configuration/services/ManagerService.kt | 38 +++++++++++ .../configuration/services/PatchesService.kt | 10 +-- 10 files changed, 167 insertions(+), 30 deletions(-) create mode 100644 src/main/kotlin/app/revanced/api/configuration/routes/ManagerRoute.kt create mode 100644 src/main/kotlin/app/revanced/api/configuration/services/ManagerService.kt diff --git a/configuration.example.toml b/configuration.example.toml index f2b1795..0ad3059 100644 --- a/configuration.example.toml +++ b/configuration.example.toml @@ -1,6 +1,7 @@ organization = "revanced" patches = { repository = "revanced-patches", asset-regex = "jar$", signature-asset-regex = "asc$", public-key-file = "patches-public-key.asc" } integrations = { repository = "revanced-integrations", asset-regex = "apk$", signature-asset-regex = "asc$", public-key-file = "integrations-public-key.asc" } +manager = { repository = "revanced-manager", asset-regex = "apk$" } contributors-repositories = [ "revanced-patcher", "revanced-patches", diff --git a/src/main/kotlin/app/revanced/api/configuration/Dependencies.kt b/src/main/kotlin/app/revanced/api/configuration/Dependencies.kt index 559ac14..a8ed717 100644 --- a/src/main/kotlin/app/revanced/api/configuration/Dependencies.kt +++ b/src/main/kotlin/app/revanced/api/configuration/Dependencies.kt @@ -134,6 +134,7 @@ fun Application.configureDependencies( singleOf(::AnnouncementService) singleOf(::SignatureService) singleOf(::PatchesService) + singleOf(::ManagerService) singleOf(::ApiService) } diff --git a/src/main/kotlin/app/revanced/api/configuration/Routing.kt b/src/main/kotlin/app/revanced/api/configuration/Routing.kt index 074f926..fe60ebc 100644 --- a/src/main/kotlin/app/revanced/api/configuration/Routing.kt +++ b/src/main/kotlin/app/revanced/api/configuration/Routing.kt @@ -1,6 +1,7 @@ package app.revanced.api.configuration import app.revanced.api.configuration.repository.ConfigurationRepository +import app.revanced.api.configuration.routes.* import app.revanced.api.configuration.routes.announcementsRoute import app.revanced.api.configuration.routes.apiRoute import app.revanced.api.configuration.routes.oldApiRoute @@ -22,6 +23,7 @@ internal fun Application.configureRouting() = routing { route("/v${configuration.apiVersion}") { announcementsRoute() patchesRoute() + managerRoute() apiRoute() } diff --git a/src/main/kotlin/app/revanced/api/configuration/repository/ConfigurationRepository.kt b/src/main/kotlin/app/revanced/api/configuration/repository/ConfigurationRepository.kt index bb567f3..4e13cf6 100644 --- a/src/main/kotlin/app/revanced/api/configuration/repository/ConfigurationRepository.kt +++ b/src/main/kotlin/app/revanced/api/configuration/repository/ConfigurationRepository.kt @@ -1,5 +1,6 @@ package app.revanced.api.configuration.repository +import app.revanced.api.configuration.services.ManagerService import app.revanced.api.configuration.services.PatchesService import kotlinx.serialization.KSerializer import kotlinx.serialization.SerialName @@ -17,6 +18,7 @@ import java.io.File * @property organization The API backends organization name where the repositories for the patches and integrations are. * @property patches The source of the patches. * @property integrations The source of the integrations. + * @property manager The source of the manager. * @property contributorsRepositoryNames The names of the repositories to get contributors from. * @property apiVersion The version to use for the API. * @property corsAllowedHosts The hosts allowed to make requests to the API. @@ -26,8 +28,9 @@ import java.io.File @Serializable internal class ConfigurationRepository( val organization: String, - val patches: AssetConfiguration, - val integrations: AssetConfiguration, + val patches: SignedAssetConfiguration, + val integrations: SignedAssetConfiguration, + val manager: AssetConfiguration, @SerialName("contributors-repositories") val contributorsRepositoryNames: Set, @SerialName("api-version") @@ -39,9 +42,9 @@ internal class ConfigurationRepository( val oldApiEndpoint: String, ) { /** - * An asset configuration. + * Am asset configuration whose asset is signed. * - * [PatchesService] uses [BackendRepository] to get assets from its releases. + * [PatchesService] for example uses [BackendRepository] to get assets from its releases. * A release contains multiple assets. * * This configuration is used in [ConfigurationRepository] @@ -54,7 +57,7 @@ internal class ConfigurationRepository( * @property publicKeyId The ID of the public key to verify the signature of the asset. */ @Serializable - internal class AssetConfiguration( + internal class SignedAssetConfiguration( val repository: String, @Serializable(with = RegexSerializer::class) @SerialName("asset-regex") @@ -68,6 +71,26 @@ internal class ConfigurationRepository( @SerialName("public-key-id") val publicKeyId: Long, ) + + /** + * Am asset configuration. + * + * [ManagerService] for example uses [BackendRepository] to get assets from its releases. + * A release contains multiple assets. + * + * This configuration is used in [ConfigurationRepository] + * to determine which release assets from repositories to get and to verify them. + * + * @property repository The repository in which releases are made to get an asset. + * @property assetRegex The regex matching the asset name. + */ + @Serializable + internal class AssetConfiguration( + val repository: String, + @Serializable(with = RegexSerializer::class) + @SerialName("asset-regex") + val assetRegex: Regex, + ) } private object RegexSerializer : KSerializer { diff --git a/src/main/kotlin/app/revanced/api/configuration/routes/ApiRoute.kt b/src/main/kotlin/app/revanced/api/configuration/routes/ApiRoute.kt index 7cb5d82..5152d65 100644 --- a/src/main/kotlin/app/revanced/api/configuration/routes/ApiRoute.kt +++ b/src/main/kotlin/app/revanced/api/configuration/routes/ApiRoute.kt @@ -82,7 +82,7 @@ internal fun Route.apiRoute() { } } -fun Route.installRateLimitRouteDocumentation() = installNotarizedRoute { +private fun Route.installRateLimitRouteDocumentation() = installNotarizedRoute { tags = setOf("API") get = GetInfo.builder { @@ -97,7 +97,7 @@ fun Route.installRateLimitRouteDocumentation() = installNotarizedRoute { } } -fun Route.installPingRouteDocumentation() = installNotarizedRoute { +private fun Route.installPingRouteDocumentation() = installNotarizedRoute { tags = setOf("API") head = HeadInfo.builder { @@ -111,7 +111,7 @@ fun Route.installPingRouteDocumentation() = installNotarizedRoute { } } -fun Route.installTeamRouteDocumentation() = installNotarizedRoute { +private fun Route.installTeamRouteDocumentation() = installNotarizedRoute { tags = setOf("API") get = GetInfo.builder { @@ -126,7 +126,7 @@ fun Route.installTeamRouteDocumentation() = installNotarizedRoute { } } -fun Route.installContributorsRouteDocumentation() = installNotarizedRoute { +private fun Route.installContributorsRouteDocumentation() = installNotarizedRoute { tags = setOf("API") get = GetInfo.builder { @@ -141,7 +141,7 @@ fun Route.installContributorsRouteDocumentation() = installNotarizedRoute { } } -fun Route.installTokenRouteDocumentation() = installNotarizedRoute { +private fun Route.installTokenRouteDocumentation() = installNotarizedRoute { tags = setOf("API") get = GetInfo.builder { diff --git a/src/main/kotlin/app/revanced/api/configuration/routes/ManagerRoute.kt b/src/main/kotlin/app/revanced/api/configuration/routes/ManagerRoute.kt new file mode 100644 index 0000000..e9b9cb8 --- /dev/null +++ b/src/main/kotlin/app/revanced/api/configuration/routes/ManagerRoute.kt @@ -0,0 +1,66 @@ +package app.revanced.api.configuration.routes + +import app.revanced.api.configuration.installNotarizedRoute +import app.revanced.api.configuration.schema.APIManagerAsset +import app.revanced.api.configuration.schema.APIRelease +import app.revanced.api.configuration.schema.APIReleaseVersion +import app.revanced.api.configuration.services.ManagerService +import io.bkbn.kompendium.core.metadata.GetInfo +import io.ktor.http.* +import io.ktor.server.application.* +import io.ktor.server.plugins.ratelimit.* +import io.ktor.server.response.* +import io.ktor.server.routing.* +import org.koin.ktor.ext.get as koinGet + +internal fun Route.managerRoute() = route("manager") { + val managerService = koinGet() + + route("latest") { + installLatestManagerRouteDocumentation() + + rateLimit(RateLimitName("weak")) { + get { + call.respond(managerService.latestRelease()) + } + + route("version") { + installLatestManagerVersionRouteDocumentation() + + get { + call.respond(managerService.latestVersion()) + } + } + } + } +} + +private fun Route.installLatestManagerRouteDocumentation() = installNotarizedRoute { + tags = setOf("Manager") + + get = GetInfo.builder { + description("Get the latest manager release") + summary("Get latest Manager release") + response { + description("The latest manager release") + mediaTypes("application/json") + responseCode(HttpStatusCode.OK) + responseType>() + } + } +} + +private fun Route.installLatestManagerVersionRouteDocumentation() = installNotarizedRoute { + tags = setOf("Manager") + + get = GetInfo.builder { + description("Get the latest manager release version") + summary("Get latest manager release version") + response { + description("The latest manager release version") + mediaTypes("application/json") + responseCode(HttpStatusCode.OK) + responseType() + } + } +} diff --git a/src/main/kotlin/app/revanced/api/configuration/routes/PatchesRoute.kt b/src/main/kotlin/app/revanced/api/configuration/routes/PatchesRoute.kt index 8419e6a..b1ff841 100644 --- a/src/main/kotlin/app/revanced/api/configuration/routes/PatchesRoute.kt +++ b/src/main/kotlin/app/revanced/api/configuration/routes/PatchesRoute.kt @@ -3,6 +3,7 @@ package app.revanced.api.configuration.routes import app.revanced.api.configuration.installCache import app.revanced.api.configuration.installNotarizedRoute import app.revanced.api.configuration.schema.APIAssetPublicKeys +import app.revanced.api.configuration.schema.APIPatchesAsset import app.revanced.api.configuration.schema.APIRelease import app.revanced.api.configuration.schema.APIReleaseVersion import app.revanced.api.configuration.services.PatchesService @@ -59,7 +60,7 @@ internal fun Route.patchesRoute() = route("patches") { } } -fun Route.installLatestPatchesRouteDocumentation() = installNotarizedRoute { +private fun Route.installLatestPatchesRouteDocumentation() = installNotarizedRoute { tags = setOf("Patches") get = GetInfo.builder { @@ -69,12 +70,12 @@ fun Route.installLatestPatchesRouteDocumentation() = installNotarizedRoute { description("The latest patches release") mediaTypes("application/json") responseCode(HttpStatusCode.OK) - responseType() + responseType>() } } } -fun Route.installLatestPatchesVersionRouteDocumentation() = installNotarizedRoute { +private fun Route.installLatestPatchesVersionRouteDocumentation() = installNotarizedRoute { tags = setOf("Patches") get = GetInfo.builder { @@ -89,7 +90,7 @@ fun Route.installLatestPatchesVersionRouteDocumentation() = installNotarizedRout } } -fun Route.installLatestPatchesListRouteDocumentation() = installNotarizedRoute { +private fun Route.installLatestPatchesListRouteDocumentation() = installNotarizedRoute { tags = setOf("Patches") get = GetInfo.builder { @@ -104,7 +105,7 @@ fun Route.installLatestPatchesListRouteDocumentation() = installNotarizedRoute { } } -fun Route.installPatchesPublicKeyRouteDocumentation() = installNotarizedRoute { +private fun Route.installPatchesPublicKeyRouteDocumentation() = installNotarizedRoute { tags = setOf("Patches") get = GetInfo.builder { diff --git a/src/main/kotlin/app/revanced/api/configuration/schema/APISchema.kt b/src/main/kotlin/app/revanced/api/configuration/schema/APISchema.kt index 888f811..a335bff 100644 --- a/src/main/kotlin/app/revanced/api/configuration/schema/APISchema.kt +++ b/src/main/kotlin/app/revanced/api/configuration/schema/APISchema.kt @@ -3,15 +3,6 @@ package app.revanced.api.configuration.schema import kotlinx.datetime.LocalDateTime import kotlinx.serialization.Serializable -@Serializable -class APIRelease( - val version: String, - val createdAt: LocalDateTime, - val description: String, - // Using a list instead of a set because set semantics are unnecessary here. - val assets: List, -) - interface APIUser { val name: String val avatarUrl: String @@ -48,7 +39,21 @@ class APIContributable( ) @Serializable -class APIAsset( +class APIRelease( + val version: String, + val createdAt: LocalDateTime, + val description: String, + // Using a list instead of a set because set semantics are unnecessary here. + val assets: List, +) + +@Serializable +class APIManagerAsset( + val downloadUrl: String, +) + +@Serializable +class APIPatchesAsset( val downloadUrl: String, val signatureDownloadUrl: String, // TODO: Remove this eventually when integrations are merged into patches. diff --git a/src/main/kotlin/app/revanced/api/configuration/services/ManagerService.kt b/src/main/kotlin/app/revanced/api/configuration/services/ManagerService.kt new file mode 100644 index 0000000..17b0665 --- /dev/null +++ b/src/main/kotlin/app/revanced/api/configuration/services/ManagerService.kt @@ -0,0 +1,38 @@ +package app.revanced.api.configuration.services + +import app.revanced.api.configuration.repository.BackendRepository +import app.revanced.api.configuration.repository.BackendRepository.BackendOrganization.BackendRepository.BackendRelease.Companion.first +import app.revanced.api.configuration.repository.ConfigurationRepository +import app.revanced.api.configuration.schema.* + +internal class ManagerService( + private val backendRepository: BackendRepository, + private val configurationRepository: ConfigurationRepository, +) { + suspend fun latestRelease(): APIRelease { + val managerRelease = backendRepository.release( + configurationRepository.organization, + configurationRepository.manager.repository, + ) + + val managerAsset = APIManagerAsset( + managerRelease.assets.first(configurationRepository.manager.assetRegex).downloadUrl, + ) + + return APIRelease( + managerRelease.tag, + managerRelease.createdAt, + managerRelease.releaseNote, + listOf(managerAsset), + ) + } + + suspend fun latestVersion(): APIReleaseVersion { + val managerRelease = backendRepository.release( + configurationRepository.organization, + configurationRepository.manager.repository, + ) + + return APIReleaseVersion(managerRelease.tag) + } +} diff --git a/src/main/kotlin/app/revanced/api/configuration/services/PatchesService.kt b/src/main/kotlin/app/revanced/api/configuration/services/PatchesService.kt index e24eb26..618fc33 100644 --- a/src/main/kotlin/app/revanced/api/configuration/services/PatchesService.kt +++ b/src/main/kotlin/app/revanced/api/configuration/services/PatchesService.kt @@ -18,7 +18,7 @@ internal class PatchesService( private val backendRepository: BackendRepository, private val configurationRepository: ConfigurationRepository, ) { - suspend fun latestRelease(): APIRelease { + suspend fun latestRelease(): APIRelease { val patchesRelease = backendRepository.release( configurationRepository.organization, configurationRepository.patches.repository, @@ -29,10 +29,10 @@ internal class PatchesService( configurationRepository.integrations.repository, ) - fun ConfigurationRepository.AssetConfiguration.asset( + fun ConfigurationRepository.SignedAssetConfiguration.asset( release: BackendRepository.BackendOrganization.BackendRepository.BackendRelease, assetName: APIAssetName, - ) = APIAsset( + ) = APIPatchesAsset( release.assets.first(assetRegex).downloadUrl, release.assets.first(signatureAssetRegex).downloadUrl, assetName, @@ -113,8 +113,8 @@ internal class PatchesService( } fun publicKeys(): APIAssetPublicKeys { - fun publicKeyBase64(getAssetConfiguration: ConfigurationRepository.() -> ConfigurationRepository.AssetConfiguration) = - configurationRepository.getAssetConfiguration().publicKeyFile.readBytes().encodeBase64() + fun publicKeyBase64(getSignedAssetConfiguration: ConfigurationRepository.() -> ConfigurationRepository.SignedAssetConfiguration) = + configurationRepository.getSignedAssetConfiguration().publicKeyFile.readBytes().encodeBase64() return APIAssetPublicKeys( publicKeyBase64 { patches }, From b3c82535ebf3a04c07e4462eeed13dc7d0932661 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Fri, 12 Jul 2024 23:36:46 +0000 Subject: [PATCH 098/102] chore(release): 1.0.0-dev.5 [skip ci] # [1.0.0-dev.5](https://github.com/ReVanced/revanced-api/compare/v1.0.0-dev.4...v1.0.0-dev.5) (2024-07-12) ### Bug Fixes * Move robots.txt to root ([2ade550](https://github.com/ReVanced/revanced-api/commit/2ade550d58c0e4b53fa7417bef0064f4f476aed8)) * Only list public members ([97a5d11](https://github.com/ReVanced/revanced-api/commit/97a5d119ec415f9c25fbc50cb240603047defc73)) ### Features * Add configuration to specify public key id ([ad7d4b2](https://github.com/ReVanced/revanced-api/commit/ad7d4b226f71fd965421dc2f2a51825c8e8b3036)) * Add manager route ([f814fe5](https://github.com/ReVanced/revanced-api/commit/f814fe5825eb8b864f2b0cba53ff721eba577eb4)) --- CHANGELOG.md | 14 ++++++++++++++ gradle.properties | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index de537e4..49f17f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,17 @@ +# [1.0.0-dev.5](https://github.com/ReVanced/revanced-api/compare/v1.0.0-dev.4...v1.0.0-dev.5) (2024-07-12) + + +### Bug Fixes + +* Move robots.txt to root ([2ade550](https://github.com/ReVanced/revanced-api/commit/2ade550d58c0e4b53fa7417bef0064f4f476aed8)) +* Only list public members ([97a5d11](https://github.com/ReVanced/revanced-api/commit/97a5d119ec415f9c25fbc50cb240603047defc73)) + + +### Features + +* Add configuration to specify public key id ([ad7d4b2](https://github.com/ReVanced/revanced-api/commit/ad7d4b226f71fd965421dc2f2a51825c8e8b3036)) +* Add manager route ([f814fe5](https://github.com/ReVanced/revanced-api/commit/f814fe5825eb8b864f2b0cba53ff721eba577eb4)) + # [1.0.0-dev.4](https://github.com/ReVanced/revanced-api/compare/v1.0.0-dev.3...v1.0.0-dev.4) (2024-07-11) diff --git a/gradle.properties b/gradle.properties index d3d8810..f344234 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ org.gradle.parallel = true org.gradle.caching = true kotlin.code.style = official -version = 1.0.0-dev.4 +version = 1.0.0-dev.5 From c40d50c01368cb6b9ab06857694ec51d27aba2cb Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Sat, 13 Jul 2024 02:46:57 +0200 Subject: [PATCH 099/102] feat: Add bio field for team members --- .../kotlin/app/revanced/api/configuration/schema/APISchema.kt | 1 + .../app/revanced/api/configuration/services/ApiService.kt | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/app/revanced/api/configuration/schema/APISchema.kt b/src/main/kotlin/app/revanced/api/configuration/schema/APISchema.kt index a335bff..d6d28ef 100644 --- a/src/main/kotlin/app/revanced/api/configuration/schema/APISchema.kt +++ b/src/main/kotlin/app/revanced/api/configuration/schema/APISchema.kt @@ -14,6 +14,7 @@ class APIMember( override val name: String, override val avatarUrl: String, override val url: String, + val bio: String?, val gpgKey: APIGpgKey?, ) : APIUser diff --git a/src/main/kotlin/app/revanced/api/configuration/services/ApiService.kt b/src/main/kotlin/app/revanced/api/configuration/services/ApiService.kt index 7834362..0a73fff 100644 --- a/src/main/kotlin/app/revanced/api/configuration/services/ApiService.kt +++ b/src/main/kotlin/app/revanced/api/configuration/services/ApiService.kt @@ -30,6 +30,7 @@ internal class ApiService( member.name, member.avatarUrl, member.url, + member.bio, if (member.gpgKeys.ids.isNotEmpty()) { APIGpgKey( // Must choose one of the GPG keys, because it does not make sense to have multiple GPG keys for the API. @@ -39,7 +40,6 @@ internal class ApiService( } else { null }, - ) } From db41081155c84435e215ef6f713045d6f1e5cc5e Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sat, 13 Jul 2024 00:48:57 +0000 Subject: [PATCH 100/102] chore(release): 1.0.0-dev.6 [skip ci] # [1.0.0-dev.6](https://github.com/ReVanced/revanced-api/compare/v1.0.0-dev.5...v1.0.0-dev.6) (2024-07-13) ### Features * Add bio field for team members ([c40d50c](https://github.com/ReVanced/revanced-api/commit/c40d50c01368cb6b9ab06857694ec51d27aba2cb)) --- CHANGELOG.md | 7 +++++++ gradle.properties | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 49f17f7..67793c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +# [1.0.0-dev.6](https://github.com/ReVanced/revanced-api/compare/v1.0.0-dev.5...v1.0.0-dev.6) (2024-07-13) + + +### Features + +* Add bio field for team members ([c40d50c](https://github.com/ReVanced/revanced-api/commit/c40d50c01368cb6b9ab06857694ec51d27aba2cb)) + # [1.0.0-dev.5](https://github.com/ReVanced/revanced-api/compare/v1.0.0-dev.4...v1.0.0-dev.5) (2024-07-12) diff --git a/gradle.properties b/gradle.properties index f344234..58f1b45 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ org.gradle.parallel = true org.gradle.caching = true kotlin.code.style = official -version = 1.0.0-dev.5 +version = 1.0.0-dev.6 From 541783d9599c257f184d1b244e1b857b7c200227 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Sat, 13 Jul 2024 15:38:28 +0200 Subject: [PATCH 101/102] fix: Fix OpenAPI docs casing of a word --- .../app/revanced/api/configuration/routes/ManagerRoute.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/app/revanced/api/configuration/routes/ManagerRoute.kt b/src/main/kotlin/app/revanced/api/configuration/routes/ManagerRoute.kt index e9b9cb8..b87024c 100644 --- a/src/main/kotlin/app/revanced/api/configuration/routes/ManagerRoute.kt +++ b/src/main/kotlin/app/revanced/api/configuration/routes/ManagerRoute.kt @@ -40,7 +40,7 @@ private fun Route.installLatestManagerRouteDocumentation() = installNotarizedRou get = GetInfo.builder { description("Get the latest manager release") - summary("Get latest Manager release") + summary("Get latest manager release") response { description("The latest manager release") mediaTypes("application/json") From 236e7e7907ad45ccca3109779ca7e7a5f0c87e71 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sat, 13 Jul 2024 13:40:16 +0000 Subject: [PATCH 102/102] chore(release): 1.0.0-dev.7 [skip ci] # [1.0.0-dev.7](https://github.com/ReVanced/revanced-api/compare/v1.0.0-dev.6...v1.0.0-dev.7) (2024-07-13) ### Bug Fixes * Fix OpenAPI docs casing of a word ([541783d](https://github.com/ReVanced/revanced-api/commit/541783d9599c257f184d1b244e1b857b7c200227)) --- CHANGELOG.md | 7 +++++++ gradle.properties | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 67793c5..1fa9448 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +# [1.0.0-dev.7](https://github.com/ReVanced/revanced-api/compare/v1.0.0-dev.6...v1.0.0-dev.7) (2024-07-13) + + +### Bug Fixes + +* Fix OpenAPI docs casing of a word ([541783d](https://github.com/ReVanced/revanced-api/commit/541783d9599c257f184d1b244e1b857b7c200227)) + # [1.0.0-dev.6](https://github.com/ReVanced/revanced-api/compare/v1.0.0-dev.5...v1.0.0-dev.6) (2024-07-13) diff --git a/gradle.properties b/gradle.properties index 58f1b45..dd50e33 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ org.gradle.parallel = true org.gradle.caching = true kotlin.code.style = official -version = 1.0.0-dev.6 +version = 1.0.0-dev.7
-
- -
-
-
-

Application API

-
-
-
- -
-
-

Default

-
-
-
-

rootGet

-

-
-
-
-

-

Hello World!

-

-
-
/
-

-

Usage and SDK Samples

-

- - -
-
-
curl -X GET\
--H "Accept: text/plain"\
-"http://0.0.0.0:8080/"
-
-
-
import io.swagger.client.*;
-import io.swagger.client.auth.*;
-import io.swagger.client.model.*;
-import io.swagger.client.api.DefaultApi;
-
-import java.io.File;
-import java.util.*;
-
-public class DefaultApiExample {
-
-    public static void main(String[] args) {
-        
-        DefaultApi apiInstance = new DefaultApi();
-        try {
-            'String' result = apiInstance.rootGet();
-            System.out.println(result);
-        } catch (ApiException e) {
-            System.err.println("Exception when calling DefaultApi#rootGet");
-            e.printStackTrace();
-        }
-    }
-}
-
- -
-
import io.swagger.client.api.DefaultApi;
-
-public class DefaultApiExample {
-
-    public static void main(String[] args) {
-        DefaultApi apiInstance = new DefaultApi();
-        try {
-            'String' result = apiInstance.rootGet();
-            System.out.println(result);
-        } catch (ApiException e) {
-            System.err.println("Exception when calling DefaultApi#rootGet");
-            e.printStackTrace();
-        }
-    }
-}
-
- -
-

-DefaultApi *apiInstance = [[DefaultApi alloc] init];
-
-[apiInstance rootGetWithCompletionHandler: 
-              ^('String' output, NSError* error) {
-                            if (output) {
-                                NSLog(@"%@", output);
-                            }
-                            if (error) {
-                                NSLog(@"Error: %@", error);
-                            }
-                        }];
-
-
- -
-
var ApplicationApi = require('application_api');
-
-var api = new ApplicationApi.DefaultApi()
-var callback = function(error, data, response) {
-  if (error) {
-    console.error(error);
-  } else {
-    console.log('API called successfully. Returned data: ' + data);
-  }
-};
-api.rootGet(callback);
-
-
- - -
-
using System;
-using System.Diagnostics;
-using IO.Swagger.Api;
-using IO.Swagger.Client;
-using IO.Swagger.Model;
-
-namespace Example
-{
-    public class rootGetExample
-    {
-        public void main()
-        {
-
-            var apiInstance = new DefaultApi();
-
-            try
-            {
-                'String' result = apiInstance.rootGet();
-                Debug.WriteLine(result);
-            }
-            catch (Exception e)
-            {
-                Debug.Print("Exception when calling DefaultApi.rootGet: " + e.Message );
-            }
-        }
-    }
-}
-
-
- -
-
<?php
-require_once(__DIR__ . '/vendor/autoload.php');
-
-$api_instance = new Swagger\Client\ApiDefaultApi();
-
-try {
-    $result = $api_instance->rootGet();
-    print_r($result);
-} catch (Exception $e) {
-    echo 'Exception when calling DefaultApi->rootGet: ', $e->getMessage(), PHP_EOL;
-}
-?>
-
- -
-
use Data::Dumper;
-use WWW::SwaggerClient::Configuration;
-use WWW::SwaggerClient::DefaultApi;
-
-my $api_instance = WWW::SwaggerClient::DefaultApi->new();
-
-eval { 
-    my $result = $api_instance->rootGet();
-    print Dumper($result);
-};
-if ($@) {
-    warn "Exception when calling DefaultApi->rootGet: $@\n";
-}
-
- -
-
from __future__ import print_statement
-import time
-import swagger_client
-from swagger_client.rest import ApiException
-from pprint import pprint
-
-# create an instance of the API class
-api_instance = swagger_client.DefaultApi()
-
-try: 
-    api_response = api_instance.root_get()
-    pprint(api_response)
-except ApiException as e:
-    print("Exception when calling DefaultApi->rootGet: %s\n" % e)
-
-
- -

Parameters

- - - - - - -

Responses

-

Status: 200 - OK

- - - -
-
-
- -
- -
-
- -
-
-
-
-
- -
-
-