diff --git a/poetry.lock b/poetry.lock index 5a00378d..43ae4778 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,122 +1,124 @@ -# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. [[package]] name = "certifi" -version = "2023.7.22" +version = "2023.11.17" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" files = [ - {file = "certifi-2023.7.22-py3-none-any.whl", hash = "sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9"}, - {file = "certifi-2023.7.22.tar.gz", hash = "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082"}, + {file = "certifi-2023.11.17-py3-none-any.whl", hash = "sha256:e036ab49d5b79556f99cfc2d9320b34cfbe5be05c5871b51de9329f0603b0474"}, + {file = "certifi-2023.11.17.tar.gz", hash = "sha256:9b469f3a900bf28dc19b8cfbf8019bf47f7fdd1a65a1d4ffb98fc14166beb4d1"}, ] [[package]] name = "charset-normalizer" -version = "3.0.1" +version = "3.3.2" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false -python-versions = "*" +python-versions = ">=3.7.0" files = [ - {file = "charset-normalizer-3.0.1.tar.gz", hash = "sha256:ebea339af930f8ca5d7a699b921106c6e29c617fe9606fa7baa043c1cdae326f"}, - {file = "charset_normalizer-3.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:88600c72ef7587fe1708fd242b385b6ed4b8904976d5da0893e31df8b3480cb6"}, - {file = "charset_normalizer-3.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c75ffc45f25324e68ab238cb4b5c0a38cd1c3d7f1fb1f72b5541de469e2247db"}, - {file = "charset_normalizer-3.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:db72b07027db150f468fbada4d85b3b2729a3db39178abf5c543b784c1254539"}, - {file = "charset_normalizer-3.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62595ab75873d50d57323a91dd03e6966eb79c41fa834b7a1661ed043b2d404d"}, - {file = "charset_normalizer-3.0.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ff6f3db31555657f3163b15a6b7c6938d08df7adbfc9dd13d9d19edad678f1e8"}, - {file = "charset_normalizer-3.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:772b87914ff1152b92a197ef4ea40efe27a378606c39446ded52c8f80f79702e"}, - {file = "charset_normalizer-3.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70990b9c51340e4044cfc394a81f614f3f90d41397104d226f21e66de668730d"}, - {file = "charset_normalizer-3.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:292d5e8ba896bbfd6334b096e34bffb56161c81408d6d036a7dfa6929cff8783"}, - {file = "charset_normalizer-3.0.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:2edb64ee7bf1ed524a1da60cdcd2e1f6e2b4f66ef7c077680739f1641f62f555"}, - {file = "charset_normalizer-3.0.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:31a9ddf4718d10ae04d9b18801bd776693487cbb57d74cc3458a7673f6f34639"}, - {file = "charset_normalizer-3.0.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:44ba614de5361b3e5278e1241fda3dc1838deed864b50a10d7ce92983797fa76"}, - {file = "charset_normalizer-3.0.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:12db3b2c533c23ab812c2b25934f60383361f8a376ae272665f8e48b88e8e1c6"}, - {file = "charset_normalizer-3.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c512accbd6ff0270939b9ac214b84fb5ada5f0409c44298361b2f5e13f9aed9e"}, - {file = "charset_normalizer-3.0.1-cp310-cp310-win32.whl", hash = "sha256:502218f52498a36d6bf5ea77081844017bf7982cdbe521ad85e64cabee1b608b"}, - {file = "charset_normalizer-3.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:601f36512f9e28f029d9481bdaf8e89e5148ac5d89cffd3b05cd533eeb423b59"}, - {file = "charset_normalizer-3.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0298eafff88c99982a4cf66ba2efa1128e4ddaca0b05eec4c456bbc7db691d8d"}, - {file = "charset_normalizer-3.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a8d0fc946c784ff7f7c3742310cc8a57c5c6dc31631269876a88b809dbeff3d3"}, - {file = "charset_normalizer-3.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:87701167f2a5c930b403e9756fab1d31d4d4da52856143b609e30a1ce7160f3c"}, - {file = "charset_normalizer-3.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:14e76c0f23218b8f46c4d87018ca2e441535aed3632ca134b10239dfb6dadd6b"}, - {file = "charset_normalizer-3.0.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0c0a590235ccd933d9892c627dec5bc7511ce6ad6c1011fdf5b11363022746c1"}, - {file = "charset_normalizer-3.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c7fe7afa480e3e82eed58e0ca89f751cd14d767638e2550c77a92a9e749c317"}, - {file = "charset_normalizer-3.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:79909e27e8e4fcc9db4addea88aa63f6423ebb171db091fb4373e3312cb6d603"}, - {file = "charset_normalizer-3.0.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8ac7b6a045b814cf0c47f3623d21ebd88b3e8cf216a14790b455ea7ff0135d18"}, - {file = "charset_normalizer-3.0.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:72966d1b297c741541ca8cf1223ff262a6febe52481af742036a0b296e35fa5a"}, - {file = "charset_normalizer-3.0.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:f9d0c5c045a3ca9bedfc35dca8526798eb91a07aa7a2c0fee134c6c6f321cbd7"}, - {file = "charset_normalizer-3.0.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:5995f0164fa7df59db4746112fec3f49c461dd6b31b841873443bdb077c13cfc"}, - {file = "charset_normalizer-3.0.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4a8fcf28c05c1f6d7e177a9a46a1c52798bfe2ad80681d275b10dcf317deaf0b"}, - {file = "charset_normalizer-3.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:761e8904c07ad053d285670f36dd94e1b6ab7f16ce62b9805c475b7aa1cffde6"}, - {file = "charset_normalizer-3.0.1-cp311-cp311-win32.whl", hash = "sha256:71140351489970dfe5e60fc621ada3e0f41104a5eddaca47a7acb3c1b851d6d3"}, - {file = "charset_normalizer-3.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:9ab77acb98eba3fd2a85cd160851816bfce6871d944d885febf012713f06659c"}, - {file = "charset_normalizer-3.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:84c3990934bae40ea69a82034912ffe5a62c60bbf6ec5bc9691419641d7d5c9a"}, - {file = "charset_normalizer-3.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:74292fc76c905c0ef095fe11e188a32ebd03bc38f3f3e9bcb85e4e6db177b7ea"}, - {file = "charset_normalizer-3.0.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c95a03c79bbe30eec3ec2b7f076074f4281526724c8685a42872974ef4d36b72"}, - {file = "charset_normalizer-3.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f4c39b0e3eac288fedc2b43055cfc2ca7a60362d0e5e87a637beac5d801ef478"}, - {file = "charset_normalizer-3.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:df2c707231459e8a4028eabcd3cfc827befd635b3ef72eada84ab13b52e1574d"}, - {file = "charset_normalizer-3.0.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:93ad6d87ac18e2a90b0fe89df7c65263b9a99a0eb98f0a3d2e079f12a0735837"}, - {file = "charset_normalizer-3.0.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:59e5686dd847347e55dffcc191a96622f016bc0ad89105e24c14e0d6305acbc6"}, - {file = "charset_normalizer-3.0.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:cd6056167405314a4dc3c173943f11249fa0f1b204f8b51ed4bde1a9cd1834dc"}, - {file = "charset_normalizer-3.0.1-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:083c8d17153ecb403e5e1eb76a7ef4babfc2c48d58899c98fcaa04833e7a2f9a"}, - {file = "charset_normalizer-3.0.1-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:f5057856d21e7586765171eac8b9fc3f7d44ef39425f85dbcccb13b3ebea806c"}, - {file = "charset_normalizer-3.0.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:7eb33a30d75562222b64f569c642ff3dc6689e09adda43a082208397f016c39a"}, - {file = "charset_normalizer-3.0.1-cp36-cp36m-win32.whl", hash = "sha256:95dea361dd73757c6f1c0a1480ac499952c16ac83f7f5f4f84f0658a01b8ef41"}, - {file = "charset_normalizer-3.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:eaa379fcd227ca235d04152ca6704c7cb55564116f8bc52545ff357628e10602"}, - {file = "charset_normalizer-3.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3e45867f1f2ab0711d60c6c71746ac53537f1684baa699f4f668d4c6f6ce8e14"}, - {file = "charset_normalizer-3.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cadaeaba78750d58d3cc6ac4d1fd867da6fc73c88156b7a3212a3cd4819d679d"}, - {file = "charset_normalizer-3.0.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:911d8a40b2bef5b8bbae2e36a0b103f142ac53557ab421dc16ac4aafee6f53dc"}, - {file = "charset_normalizer-3.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:503e65837c71b875ecdd733877d852adbc465bd82c768a067badd953bf1bc5a3"}, - {file = "charset_normalizer-3.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a60332922359f920193b1d4826953c507a877b523b2395ad7bc716ddd386d866"}, - {file = "charset_normalizer-3.0.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:16a8663d6e281208d78806dbe14ee9903715361cf81f6d4309944e4d1e59ac5b"}, - {file = "charset_normalizer-3.0.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:a16418ecf1329f71df119e8a65f3aa68004a3f9383821edcb20f0702934d8087"}, - {file = "charset_normalizer-3.0.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:9d9153257a3f70d5f69edf2325357251ed20f772b12e593f3b3377b5f78e7ef8"}, - {file = "charset_normalizer-3.0.1-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:02a51034802cbf38db3f89c66fb5d2ec57e6fe7ef2f4a44d070a593c3688667b"}, - {file = "charset_normalizer-3.0.1-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:2e396d70bc4ef5325b72b593a72c8979999aa52fb8bcf03f701c1b03e1166918"}, - {file = "charset_normalizer-3.0.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:11b53acf2411c3b09e6af37e4b9005cba376c872503c8f28218c7243582df45d"}, - {file = "charset_normalizer-3.0.1-cp37-cp37m-win32.whl", hash = "sha256:0bf2dae5291758b6f84cf923bfaa285632816007db0330002fa1de38bfcb7154"}, - {file = "charset_normalizer-3.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:2c03cc56021a4bd59be889c2b9257dae13bf55041a3372d3295416f86b295fb5"}, - {file = "charset_normalizer-3.0.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:024e606be3ed92216e2b6952ed859d86b4cfa52cd5bc5f050e7dc28f9b43ec42"}, - {file = "charset_normalizer-3.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4b0d02d7102dd0f997580b51edc4cebcf2ab6397a7edf89f1c73b586c614272c"}, - {file = "charset_normalizer-3.0.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:358a7c4cb8ba9b46c453b1dd8d9e431452d5249072e4f56cfda3149f6ab1405e"}, - {file = "charset_normalizer-3.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:81d6741ab457d14fdedc215516665050f3822d3e56508921cc7239f8c8e66a58"}, - {file = "charset_normalizer-3.0.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8b8af03d2e37866d023ad0ddea594edefc31e827fee64f8de5611a1dbc373174"}, - {file = "charset_normalizer-3.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9cf4e8ad252f7c38dd1f676b46514f92dc0ebeb0db5552f5f403509705e24753"}, - {file = "charset_normalizer-3.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e696f0dd336161fca9adbb846875d40752e6eba585843c768935ba5c9960722b"}, - {file = "charset_normalizer-3.0.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c22d3fe05ce11d3671297dc8973267daa0f938b93ec716e12e0f6dee81591dc1"}, - {file = "charset_normalizer-3.0.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:109487860ef6a328f3eec66f2bf78b0b72400280d8f8ea05f69c51644ba6521a"}, - {file = "charset_normalizer-3.0.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:37f8febc8ec50c14f3ec9637505f28e58d4f66752207ea177c1d67df25da5aed"}, - {file = "charset_normalizer-3.0.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:f97e83fa6c25693c7a35de154681fcc257c1c41b38beb0304b9c4d2d9e164479"}, - {file = "charset_normalizer-3.0.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:a152f5f33d64a6be73f1d30c9cc82dfc73cec6477ec268e7c6e4c7d23c2d2291"}, - {file = "charset_normalizer-3.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:39049da0ffb96c8cbb65cbf5c5f3ca3168990adf3551bd1dee10c48fce8ae820"}, - {file = "charset_normalizer-3.0.1-cp38-cp38-win32.whl", hash = "sha256:4457ea6774b5611f4bed5eaa5df55f70abde42364d498c5134b7ef4c6958e20e"}, - {file = "charset_normalizer-3.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:e62164b50f84e20601c1ff8eb55620d2ad25fb81b59e3cd776a1902527a788af"}, - {file = "charset_normalizer-3.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8eade758719add78ec36dc13201483f8e9b5d940329285edcd5f70c0a9edbd7f"}, - {file = "charset_normalizer-3.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8499ca8f4502af841f68135133d8258f7b32a53a1d594aa98cc52013fff55678"}, - {file = "charset_normalizer-3.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3fc1c4a2ffd64890aebdb3f97e1278b0cc72579a08ca4de8cd2c04799a3a22be"}, - {file = "charset_normalizer-3.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:00d3ffdaafe92a5dc603cb9bd5111aaa36dfa187c8285c543be562e61b755f6b"}, - {file = "charset_normalizer-3.0.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c2ac1b08635a8cd4e0cbeaf6f5e922085908d48eb05d44c5ae9eabab148512ca"}, - {file = "charset_normalizer-3.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f6f45710b4459401609ebebdbcfb34515da4fc2aa886f95107f556ac69a9147e"}, - {file = "charset_normalizer-3.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ae1de54a77dc0d6d5fcf623290af4266412a7c4be0b1ff7444394f03f5c54e3"}, - {file = "charset_normalizer-3.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3b590df687e3c5ee0deef9fc8c547d81986d9a1b56073d82de008744452d6541"}, - {file = "charset_normalizer-3.0.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ab5de034a886f616a5668aa5d098af2b5385ed70142090e2a31bcbd0af0fdb3d"}, - {file = "charset_normalizer-3.0.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9cb3032517f1627cc012dbc80a8ec976ae76d93ea2b5feaa9d2a5b8882597579"}, - {file = "charset_normalizer-3.0.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:608862a7bf6957f2333fc54ab4399e405baad0163dc9f8d99cb236816db169d4"}, - {file = "charset_normalizer-3.0.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:0f438ae3532723fb6ead77e7c604be7c8374094ef4ee2c5e03a3a17f1fca256c"}, - {file = "charset_normalizer-3.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:356541bf4381fa35856dafa6a965916e54bed415ad8a24ee6de6e37deccf2786"}, - {file = "charset_normalizer-3.0.1-cp39-cp39-win32.whl", hash = "sha256:39cf9ed17fe3b1bc81f33c9ceb6ce67683ee7526e65fde1447c772afc54a1bb8"}, - {file = "charset_normalizer-3.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:0a11e971ed097d24c534c037d298ad32c6ce81a45736d31e0ff0ad37ab437d59"}, - {file = "charset_normalizer-3.0.1-py3-none-any.whl", hash = "sha256:7e189e2e1d3ed2f4aebabd2d5b0f931e883676e51c7624826e0a4e5fe8a0bf24"}, + {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, + {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, ] [[package]] name = "idna" -version = "3.4" +version = "3.6" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.5" files = [ - {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, - {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, + {file = "idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"}, + {file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"}, ] [[package]] @@ -163,112 +165,105 @@ signedtoken = ["cryptography (>=3.0.0)", "pyjwt (>=2.0.0,<3)"] [[package]] name = "pillow" -version = "9.4.0" +version = "9.5.0" description = "Python Imaging Library (Fork)" optional = true python-versions = ">=3.7" files = [ - {file = "Pillow-9.4.0-1-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:1b4b4e9dda4f4e4c4e6896f93e84a8f0bcca3b059de9ddf67dac3c334b1195e1"}, - {file = "Pillow-9.4.0-1-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:fb5c1ad6bad98c57482236a21bf985ab0ef42bd51f7ad4e4538e89a997624e12"}, - {file = "Pillow-9.4.0-1-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:f0caf4a5dcf610d96c3bd32932bfac8aee61c96e60481c2a0ea58da435e25acd"}, - {file = "Pillow-9.4.0-1-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:3f4cc516e0b264c8d4ccd6b6cbc69a07c6d582d8337df79be1e15a5056b258c9"}, - {file = "Pillow-9.4.0-1-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:b8c2f6eb0df979ee99433d8b3f6d193d9590f735cf12274c108bd954e30ca858"}, - {file = "Pillow-9.4.0-1-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:b70756ec9417c34e097f987b4d8c510975216ad26ba6e57ccb53bc758f490dab"}, - {file = "Pillow-9.4.0-1-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:43521ce2c4b865d385e78579a082b6ad1166ebed2b1a2293c3be1d68dd7ca3b9"}, - {file = "Pillow-9.4.0-2-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:9d9a62576b68cd90f7075876f4e8444487db5eeea0e4df3ba298ee38a8d067b0"}, - {file = "Pillow-9.4.0-2-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:87708d78a14d56a990fbf4f9cb350b7d89ee8988705e58e39bdf4d82c149210f"}, - {file = "Pillow-9.4.0-2-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:8a2b5874d17e72dfb80d917213abd55d7e1ed2479f38f001f264f7ce7bae757c"}, - {file = "Pillow-9.4.0-2-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:83125753a60cfc8c412de5896d10a0a405e0bd88d0470ad82e0869ddf0cb3848"}, - {file = "Pillow-9.4.0-2-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:9e5f94742033898bfe84c93c831a6f552bb629448d4072dd312306bab3bd96f1"}, - {file = "Pillow-9.4.0-2-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:013016af6b3a12a2f40b704677f8b51f72cb007dac785a9933d5c86a72a7fe33"}, - {file = "Pillow-9.4.0-2-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:99d92d148dd03fd19d16175b6d355cc1b01faf80dae93c6c3eb4163709edc0a9"}, - {file = "Pillow-9.4.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:2968c58feca624bb6c8502f9564dd187d0e1389964898f5e9e1fbc8533169157"}, - {file = "Pillow-9.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c5c1362c14aee73f50143d74389b2c158707b4abce2cb055b7ad37ce60738d47"}, - {file = "Pillow-9.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd752c5ff1b4a870b7661234694f24b1d2b9076b8bf337321a814c612665f343"}, - {file = "Pillow-9.4.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9a3049a10261d7f2b6514d35bbb7a4dfc3ece4c4de14ef5876c4b7a23a0e566d"}, - {file = "Pillow-9.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:16a8df99701f9095bea8a6c4b3197da105df6f74e6176c5b410bc2df2fd29a57"}, - {file = "Pillow-9.4.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:94cdff45173b1919350601f82d61365e792895e3c3a3443cf99819e6fbf717a5"}, - {file = "Pillow-9.4.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:ed3e4b4e1e6de75fdc16d3259098de7c6571b1a6cc863b1a49e7d3d53e036070"}, - {file = "Pillow-9.4.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d5b2f8a31bd43e0f18172d8ac82347c8f37ef3e0b414431157718aa234991b28"}, - {file = "Pillow-9.4.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:09b89ddc95c248ee788328528e6a2996e09eaccddeeb82a5356e92645733be35"}, - {file = "Pillow-9.4.0-cp310-cp310-win32.whl", hash = "sha256:f09598b416ba39a8f489c124447b007fe865f786a89dbfa48bb5cf395693132a"}, - {file = "Pillow-9.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:f6e78171be3fb7941f9910ea15b4b14ec27725865a73c15277bc39f5ca4f8391"}, - {file = "Pillow-9.4.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:3fa1284762aacca6dc97474ee9c16f83990b8eeb6697f2ba17140d54b453e133"}, - {file = "Pillow-9.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:eaef5d2de3c7e9b21f1e762f289d17b726c2239a42b11e25446abf82b26ac132"}, - {file = "Pillow-9.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a4dfdae195335abb4e89cc9762b2edc524f3c6e80d647a9a81bf81e17e3fb6f0"}, - {file = "Pillow-9.4.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6abfb51a82e919e3933eb137e17c4ae9c0475a25508ea88993bb59faf82f3b35"}, - {file = "Pillow-9.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:451f10ef963918e65b8869e17d67db5e2f4ab40e716ee6ce7129b0cde2876eab"}, - {file = "Pillow-9.4.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:6663977496d616b618b6cfa43ec86e479ee62b942e1da76a2c3daa1c75933ef4"}, - {file = "Pillow-9.4.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:60e7da3a3ad1812c128750fc1bc14a7ceeb8d29f77e0a2356a8fb2aa8925287d"}, - {file = "Pillow-9.4.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:19005a8e58b7c1796bc0167862b1f54a64d3b44ee5d48152b06bb861458bc0f8"}, - {file = "Pillow-9.4.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f715c32e774a60a337b2bb8ad9839b4abf75b267a0f18806f6f4f5f1688c4b5a"}, - {file = "Pillow-9.4.0-cp311-cp311-win32.whl", hash = "sha256:b222090c455d6d1a64e6b7bb5f4035c4dff479e22455c9eaa1bdd4c75b52c80c"}, - {file = "Pillow-9.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:ba6612b6548220ff5e9df85261bddc811a057b0b465a1226b39bfb8550616aee"}, - {file = "Pillow-9.4.0-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:5f532a2ad4d174eb73494e7397988e22bf427f91acc8e6ebf5bb10597b49c493"}, - {file = "Pillow-9.4.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5dd5a9c3091a0f414a963d427f920368e2b6a4c2f7527fdd82cde8ef0bc7a327"}, - {file = "Pillow-9.4.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ef21af928e807f10bf4141cad4746eee692a0dd3ff56cfb25fce076ec3cc8abe"}, - {file = "Pillow-9.4.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:847b114580c5cc9ebaf216dd8c8dbc6b00a3b7ab0131e173d7120e6deade1f57"}, - {file = "Pillow-9.4.0-cp37-cp37m-manylinux_2_28_aarch64.whl", hash = "sha256:653d7fb2df65efefbcbf81ef5fe5e5be931f1ee4332c2893ca638c9b11a409c4"}, - {file = "Pillow-9.4.0-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:46f39cab8bbf4a384ba7cb0bc8bae7b7062b6a11cfac1ca4bc144dea90d4a9f5"}, - {file = "Pillow-9.4.0-cp37-cp37m-win32.whl", hash = "sha256:7ac7594397698f77bce84382929747130765f66406dc2cd8b4ab4da68ade4c6e"}, - {file = "Pillow-9.4.0-cp37-cp37m-win_amd64.whl", hash = "sha256:46c259e87199041583658457372a183636ae8cd56dbf3f0755e0f376a7f9d0e6"}, - {file = "Pillow-9.4.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:0e51f608da093e5d9038c592b5b575cadc12fd748af1479b5e858045fff955a9"}, - {file = "Pillow-9.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:765cb54c0b8724a7c12c55146ae4647e0274a839fb6de7bcba841e04298e1011"}, - {file = "Pillow-9.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:519e14e2c49fcf7616d6d2cfc5c70adae95682ae20f0395e9280db85e8d6c4df"}, - {file = "Pillow-9.4.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d197df5489004db87d90b918033edbeee0bd6df3848a204bca3ff0a903bef837"}, - {file = "Pillow-9.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0845adc64fe9886db00f5ab68c4a8cd933ab749a87747555cec1c95acea64b0b"}, - {file = "Pillow-9.4.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:e1339790c083c5a4de48f688b4841f18df839eb3c9584a770cbd818b33e26d5d"}, - {file = "Pillow-9.4.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:a96e6e23f2b79433390273eaf8cc94fec9c6370842e577ab10dabdcc7ea0a66b"}, - {file = "Pillow-9.4.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:7cfc287da09f9d2a7ec146ee4d72d6ea1342e770d975e49a8621bf54eaa8f30f"}, - {file = "Pillow-9.4.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d7081c084ceb58278dd3cf81f836bc818978c0ccc770cbbb202125ddabec6628"}, - {file = "Pillow-9.4.0-cp38-cp38-win32.whl", hash = "sha256:df41112ccce5d47770a0c13651479fbcd8793f34232a2dd9faeccb75eb5d0d0d"}, - {file = "Pillow-9.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:7a21222644ab69ddd9967cfe6f2bb420b460dae4289c9d40ff9a4896e7c35c9a"}, - {file = "Pillow-9.4.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:0f3269304c1a7ce82f1759c12ce731ef9b6e95b6df829dccd9fe42912cc48569"}, - {file = "Pillow-9.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cb362e3b0976dc994857391b776ddaa8c13c28a16f80ac6522c23d5257156bed"}, - {file = "Pillow-9.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a2e0f87144fcbbe54297cae708c5e7f9da21a4646523456b00cc956bd4c65815"}, - {file = "Pillow-9.4.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:28676836c7796805914b76b1837a40f76827ee0d5398f72f7dcc634bae7c6264"}, - {file = "Pillow-9.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0884ba7b515163a1a05440a138adeb722b8a6ae2c2b33aea93ea3118dd3a899e"}, - {file = "Pillow-9.4.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:53dcb50fbdc3fb2c55431a9b30caeb2f7027fcd2aeb501459464f0214200a503"}, - {file = "Pillow-9.4.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:e8c5cf126889a4de385c02a2c3d3aba4b00f70234bfddae82a5eaa3ee6d5e3e6"}, - {file = "Pillow-9.4.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:6c6b1389ed66cdd174d040105123a5a1bc91d0aa7059c7261d20e583b6d8cbd2"}, - {file = "Pillow-9.4.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0dd4c681b82214b36273c18ca7ee87065a50e013112eea7d78c7a1b89a739153"}, - {file = "Pillow-9.4.0-cp39-cp39-win32.whl", hash = "sha256:6d9dfb9959a3b0039ee06c1a1a90dc23bac3b430842dcb97908ddde05870601c"}, - {file = "Pillow-9.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:54614444887e0d3043557d9dbc697dbb16cfb5a35d672b7a0fcc1ed0cf1c600b"}, - {file = "Pillow-9.4.0-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:b9b752ab91e78234941e44abdecc07f1f0d8f51fb62941d32995b8161f68cfe5"}, - {file = "Pillow-9.4.0-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d3b56206244dc8711f7e8b7d6cad4663917cd5b2d950799425076681e8766286"}, - {file = "Pillow-9.4.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aabdab8ec1e7ca7f1434d042bf8b1e92056245fb179790dc97ed040361f16bfd"}, - {file = "Pillow-9.4.0-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:db74f5562c09953b2c5f8ec4b7dfd3f5421f31811e97d1dbc0a7c93d6e3a24df"}, - {file = "Pillow-9.4.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:e9d7747847c53a16a729b6ee5e737cf170f7a16611c143d95aa60a109a59c336"}, - {file = "Pillow-9.4.0-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:b52ff4f4e002f828ea6483faf4c4e8deea8d743cf801b74910243c58acc6eda3"}, - {file = "Pillow-9.4.0-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:575d8912dca808edd9acd6f7795199332696d3469665ef26163cd090fa1f8bfa"}, - {file = "Pillow-9.4.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c3c4ed2ff6760e98d262e0cc9c9a7f7b8a9f61aa4d47c58835cdaf7b0b8811bb"}, - {file = "Pillow-9.4.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e621b0246192d3b9cb1dc62c78cfa4c6f6d2ddc0ec207d43c0dedecb914f152a"}, - {file = "Pillow-9.4.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:8f127e7b028900421cad64f51f75c051b628db17fb00e099eb148761eed598c9"}, - {file = "Pillow-9.4.0.tar.gz", hash = "sha256:a1c2d7780448eb93fbcc3789bf3916aa5720d942e37945f4056680317f1cd23e"}, + {file = "Pillow-9.5.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:ace6ca218308447b9077c14ea4ef381ba0b67ee78d64046b3f19cf4e1139ad16"}, + {file = "Pillow-9.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d3d403753c9d5adc04d4694d35cf0391f0f3d57c8e0030aac09d7678fa8030aa"}, + {file = "Pillow-9.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ba1b81ee69573fe7124881762bb4cd2e4b6ed9dd28c9c60a632902fe8db8b38"}, + {file = "Pillow-9.5.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fe7e1c262d3392afcf5071df9afa574544f28eac825284596ac6db56e6d11062"}, + {file = "Pillow-9.5.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f36397bf3f7d7c6a3abdea815ecf6fd14e7fcd4418ab24bae01008d8d8ca15e"}, + {file = "Pillow-9.5.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:252a03f1bdddce077eff2354c3861bf437c892fb1832f75ce813ee94347aa9b5"}, + {file = "Pillow-9.5.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:85ec677246533e27770b0de5cf0f9d6e4ec0c212a1f89dfc941b64b21226009d"}, + {file = "Pillow-9.5.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b416f03d37d27290cb93597335a2f85ed446731200705b22bb927405320de903"}, + {file = "Pillow-9.5.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:1781a624c229cb35a2ac31cc4a77e28cafc8900733a864870c49bfeedacd106a"}, + {file = "Pillow-9.5.0-cp310-cp310-win32.whl", hash = "sha256:8507eda3cd0608a1f94f58c64817e83ec12fa93a9436938b191b80d9e4c0fc44"}, + {file = "Pillow-9.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:d3c6b54e304c60c4181da1c9dadf83e4a54fd266a99c70ba646a9baa626819eb"}, + {file = "Pillow-9.5.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:7ec6f6ce99dab90b52da21cf0dc519e21095e332ff3b399a357c187b1a5eee32"}, + {file = "Pillow-9.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:560737e70cb9c6255d6dcba3de6578a9e2ec4b573659943a5e7e4af13f298f5c"}, + {file = "Pillow-9.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:96e88745a55b88a7c64fa49bceff363a1a27d9a64e04019c2281049444a571e3"}, + {file = "Pillow-9.5.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d9c206c29b46cfd343ea7cdfe1232443072bbb270d6a46f59c259460db76779a"}, + {file = "Pillow-9.5.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cfcc2c53c06f2ccb8976fb5c71d448bdd0a07d26d8e07e321c103416444c7ad1"}, + {file = "Pillow-9.5.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:a0f9bb6c80e6efcde93ffc51256d5cfb2155ff8f78292f074f60f9e70b942d99"}, + {file = "Pillow-9.5.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:8d935f924bbab8f0a9a28404422da8af4904e36d5c33fc6f677e4c4485515625"}, + {file = "Pillow-9.5.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:fed1e1cf6a42577953abbe8e6cf2fe2f566daebde7c34724ec8803c4c0cda579"}, + {file = "Pillow-9.5.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c1170d6b195555644f0616fd6ed929dfcf6333b8675fcca044ae5ab110ded296"}, + {file = "Pillow-9.5.0-cp311-cp311-win32.whl", hash = "sha256:54f7102ad31a3de5666827526e248c3530b3a33539dbda27c6843d19d72644ec"}, + {file = "Pillow-9.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:cfa4561277f677ecf651e2b22dc43e8f5368b74a25a8f7d1d4a3a243e573f2d4"}, + {file = "Pillow-9.5.0-cp311-cp311-win_arm64.whl", hash = "sha256:965e4a05ef364e7b973dd17fc765f42233415974d773e82144c9bbaaaea5d089"}, + {file = "Pillow-9.5.0-cp312-cp312-win32.whl", hash = "sha256:22baf0c3cf0c7f26e82d6e1adf118027afb325e703922c8dfc1d5d0156bb2eeb"}, + {file = "Pillow-9.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:432b975c009cf649420615388561c0ce7cc31ce9b2e374db659ee4f7d57a1f8b"}, + {file = "Pillow-9.5.0-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:5d4ebf8e1db4441a55c509c4baa7a0587a0210f7cd25fcfe74dbbce7a4bd1906"}, + {file = "Pillow-9.5.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:375f6e5ee9620a271acb6820b3d1e94ffa8e741c0601db4c0c4d3cb0a9c224bf"}, + {file = "Pillow-9.5.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:99eb6cafb6ba90e436684e08dad8be1637efb71c4f2180ee6b8f940739406e78"}, + {file = "Pillow-9.5.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2dfaaf10b6172697b9bceb9a3bd7b951819d1ca339a5ef294d1f1ac6d7f63270"}, + {file = "Pillow-9.5.0-cp37-cp37m-manylinux_2_28_aarch64.whl", hash = "sha256:763782b2e03e45e2c77d7779875f4432e25121ef002a41829d8868700d119392"}, + {file = "Pillow-9.5.0-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:35f6e77122a0c0762268216315bf239cf52b88865bba522999dc38f1c52b9b47"}, + {file = "Pillow-9.5.0-cp37-cp37m-win32.whl", hash = "sha256:aca1c196f407ec7cf04dcbb15d19a43c507a81f7ffc45b690899d6a76ac9fda7"}, + {file = "Pillow-9.5.0-cp37-cp37m-win_amd64.whl", hash = "sha256:322724c0032af6692456cd6ed554bb85f8149214d97398bb80613b04e33769f6"}, + {file = "Pillow-9.5.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:a0aa9417994d91301056f3d0038af1199eb7adc86e646a36b9e050b06f526597"}, + {file = "Pillow-9.5.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f8286396b351785801a976b1e85ea88e937712ee2c3ac653710a4a57a8da5d9c"}, + {file = "Pillow-9.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c830a02caeb789633863b466b9de10c015bded434deb3ec87c768e53752ad22a"}, + {file = "Pillow-9.5.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fbd359831c1657d69bb81f0db962905ee05e5e9451913b18b831febfe0519082"}, + {file = "Pillow-9.5.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8fc330c3370a81bbf3f88557097d1ea26cd8b019d6433aa59f71195f5ddebbf"}, + {file = "Pillow-9.5.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:7002d0797a3e4193c7cdee3198d7c14f92c0836d6b4a3f3046a64bd1ce8df2bf"}, + {file = "Pillow-9.5.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:229e2c79c00e85989a34b5981a2b67aa079fd08c903f0aaead522a1d68d79e51"}, + {file = "Pillow-9.5.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9adf58f5d64e474bed00d69bcd86ec4bcaa4123bfa70a65ce72e424bfb88ed96"}, + {file = "Pillow-9.5.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:662da1f3f89a302cc22faa9f14a262c2e3951f9dbc9617609a47521c69dd9f8f"}, + {file = "Pillow-9.5.0-cp38-cp38-win32.whl", hash = "sha256:6608ff3bf781eee0cd14d0901a2b9cc3d3834516532e3bd673a0a204dc8615fc"}, + {file = "Pillow-9.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:e49eb4e95ff6fd7c0c402508894b1ef0e01b99a44320ba7d8ecbabefddcc5569"}, + {file = "Pillow-9.5.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:482877592e927fd263028c105b36272398e3e1be3269efda09f6ba21fd83ec66"}, + {file = "Pillow-9.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3ded42b9ad70e5f1754fb7c2e2d6465a9c842e41d178f262e08b8c85ed8a1d8e"}, + {file = "Pillow-9.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c446d2245ba29820d405315083d55299a796695d747efceb5717a8b450324115"}, + {file = "Pillow-9.5.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8aca1152d93dcc27dc55395604dcfc55bed5f25ef4c98716a928bacba90d33a3"}, + {file = "Pillow-9.5.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:608488bdcbdb4ba7837461442b90ea6f3079397ddc968c31265c1e056964f1ef"}, + {file = "Pillow-9.5.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:60037a8db8750e474af7ffc9faa9b5859e6c6d0a50e55c45576bf28be7419705"}, + {file = "Pillow-9.5.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:07999f5834bdc404c442146942a2ecadd1cb6292f5229f4ed3b31e0a108746b1"}, + {file = "Pillow-9.5.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:a127ae76092974abfbfa38ca2d12cbeddcdeac0fb71f9627cc1135bedaf9d51a"}, + {file = "Pillow-9.5.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:489f8389261e5ed43ac8ff7b453162af39c3e8abd730af8363587ba64bb2e865"}, + {file = "Pillow-9.5.0-cp39-cp39-win32.whl", hash = "sha256:9b1af95c3a967bf1da94f253e56b6286b50af23392a886720f563c547e48e964"}, + {file = "Pillow-9.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:77165c4a5e7d5a284f10a6efaa39a0ae8ba839da344f20b111d62cc932fa4e5d"}, + {file = "Pillow-9.5.0-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:833b86a98e0ede388fa29363159c9b1a294b0905b5128baf01db683672f230f5"}, + {file = "Pillow-9.5.0-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aaf305d6d40bd9632198c766fb64f0c1a83ca5b667f16c1e79e1661ab5060140"}, + {file = "Pillow-9.5.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0852ddb76d85f127c135b6dd1f0bb88dbb9ee990d2cd9aa9e28526c93e794fba"}, + {file = "Pillow-9.5.0-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:91ec6fe47b5eb5a9968c79ad9ed78c342b1f97a091677ba0e012701add857829"}, + {file = "Pillow-9.5.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:cb841572862f629b99725ebaec3287fc6d275be9b14443ea746c1dd325053cbd"}, + {file = "Pillow-9.5.0-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:c380b27d041209b849ed246b111b7c166ba36d7933ec6e41175fd15ab9eb1572"}, + {file = "Pillow-9.5.0-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7c9af5a3b406a50e313467e3565fc99929717f780164fe6fbb7704edba0cebbe"}, + {file = "Pillow-9.5.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5671583eab84af046a397d6d0ba25343c00cd50bce03787948e0fff01d4fd9b1"}, + {file = "Pillow-9.5.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:84a6f19ce086c1bf894644b43cd129702f781ba5751ca8572f08aa40ef0ab7b7"}, + {file = "Pillow-9.5.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:1e7723bd90ef94eda669a3c2c19d549874dd5badaeefabefd26053304abe5799"}, + {file = "Pillow-9.5.0.tar.gz", hash = "sha256:bf548479d336726d7a0eceb6e767e179fbde37833ae42794602631a070d630f1"}, ] [package.extras] -docs = ["furo", "olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinx-issues (>=3.0.1)", "sphinx-removed-in", "sphinxext-opengraph"] +docs = ["furo", "olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinx-removed-in", "sphinxext-opengraph"] tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"] [[package]] name = "pycairo" -version = "1.23.0" +version = "1.25.1" description = "Python interface for cairo" optional = true -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "pycairo-1.23.0-cp310-cp310-win32.whl", hash = "sha256:564601e5f528531c6caec1c0177c3d0709081e1a2a5cccc13561f715080ae535"}, - {file = "pycairo-1.23.0-cp310-cp310-win_amd64.whl", hash = "sha256:e7cde633986435d87a86b6118b7b6109c384266fd719ef959883e2729f6eafae"}, - {file = "pycairo-1.23.0-cp311-cp311-win32.whl", hash = "sha256:3a71f758e461180d241e62ef52e85499c843bd2660fd6d87cec99c9833792bfa"}, - {file = "pycairo-1.23.0-cp311-cp311-win_amd64.whl", hash = "sha256:2dec5378133778961993fb59d66df16070e03f4d491b67eb695ca9ad7a696008"}, - {file = "pycairo-1.23.0-cp37-cp37m-win32.whl", hash = "sha256:d6bacff15d688ed135b4567965a4b664d9fb8de7417a7865bb138ad612043c9f"}, - {file = "pycairo-1.23.0-cp37-cp37m-win_amd64.whl", hash = "sha256:ec305fc7f2f0299df78aadec0eaf6eb9accb90eda242b5d3492544d3f2b28027"}, - {file = "pycairo-1.23.0-cp38-cp38-win32.whl", hash = "sha256:1a6d8e0f353062ad92954784e33dbbaf66c880c9c30e947996c542ed9748aaaf"}, - {file = "pycairo-1.23.0-cp38-cp38-win_amd64.whl", hash = "sha256:82e335774a17870bc038e0c2fb106c1e5e7ad0c764662023886dfcfce5bb5a52"}, - {file = "pycairo-1.23.0-cp39-cp39-win32.whl", hash = "sha256:a4b1f525bbdf637c40f4d91378de36c01ec2b7f8ecc585b700a079b9ff83298e"}, - {file = "pycairo-1.23.0-cp39-cp39-win_amd64.whl", hash = "sha256:87efd62a7b7afad9a0a420f05b6008742a6cfc59077697be65afe8dc73ae15ad"}, - {file = "pycairo-1.23.0.tar.gz", hash = "sha256:9b61ac818723adc04367301317eb2e814a83522f07bbd1f409af0dada463c44c"}, + {file = "pycairo-1.25.1-cp310-cp310-win32.whl", hash = "sha256:cacb5c2abbfdfc79c728ab261ff791511e4957b606c660f9b380975b678b728f"}, + {file = "pycairo-1.25.1-cp310-cp310-win_amd64.whl", hash = "sha256:109ebbeb5bbc510b726fc31251071264dec241e5084d0668f846d7e17e5af8e0"}, + {file = "pycairo-1.25.1-cp310-cp310-win_arm64.whl", hash = "sha256:b19269a8bf9ab5e3c617f2699bed00977fd02ff304339a233654456c0236f7c6"}, + {file = "pycairo-1.25.1-cp311-cp311-win32.whl", hash = "sha256:b10e58a3ce41e487aae15050b630742e880d4135cee7a69cee2c0ea2a0b4bd0a"}, + {file = "pycairo-1.25.1-cp311-cp311-win_amd64.whl", hash = "sha256:fcf5511b05a652a0ef87f626bf26bfc1b796a67f0d1bd40781c62986fb41c356"}, + {file = "pycairo-1.25.1-cp311-cp311-win_arm64.whl", hash = "sha256:4133ba3ef6d875aa1b16643dc0801846f463b8e78750f5308c41902dfeac5b9a"}, + {file = "pycairo-1.25.1-cp312-cp312-win32.whl", hash = "sha256:56fee2837a07ecd914f4fbf78ff59445f78becd658fe36125101925dd489eb94"}, + {file = "pycairo-1.25.1-cp312-cp312-win_amd64.whl", hash = "sha256:fb31eec2c41ec74e23dc0fc9feb4007b4c37f78ec76220ed92530b342e09821a"}, + {file = "pycairo-1.25.1-cp312-cp312-win_arm64.whl", hash = "sha256:27011d822952d7817130fc17f490de94328590bc8d45bdbca9ec4a47039fca22"}, + {file = "pycairo-1.25.1-cp38-cp38-win32.whl", hash = "sha256:9a7c5ed92fe87f60e9796777d5255f2df2deeb8ab1e3c296e67a1d8c9790808c"}, + {file = "pycairo-1.25.1-cp38-cp38-win_amd64.whl", hash = "sha256:48603ad31616140ad6fa097f13086d0ce8f29ead35ad6a215962f3b0496a5a70"}, + {file = "pycairo-1.25.1-cp39-cp39-win32.whl", hash = "sha256:97666c084e9eb1c08c7fd6d306d153767acdf03c0d80349ec55863cecd4138e0"}, + {file = "pycairo-1.25.1-cp39-cp39-win_amd64.whl", hash = "sha256:ac5437d140eccd97af12a618cc1ace0d9a85f1269f29e963751949f132828b21"}, + {file = "pycairo-1.25.1-cp39-cp39-win_arm64.whl", hash = "sha256:bda5d10adbf1f5eba6b524b5a70ccf7f659680b77e691ff94b312f25a6fcc91f"}, + {file = "pycairo-1.25.1.tar.gz", hash = "sha256:7e2be4fbc3b4536f16db7a11982cbf713e75069a4d73d44fe5a49b68423f5c0c"}, ] [[package]] @@ -284,12 +279,12 @@ files = [ [[package]] name = "pygobject" -version = "3.42.2" +version = "3.46.0" description = "Python bindings for GObject Introspection" optional = true -python-versions = ">=3.6, <4" +python-versions = ">=3.8, <4" files = [ - {file = "PyGObject-3.42.2.tar.gz", hash = "sha256:21524cef33100c8fd59dc135948b703d79d303e368ce71fa60521cc971cd8aa7"}, + {file = "PyGObject-3.46.0.tar.gz", hash = "sha256:481437b05af0a66b7c366ea052710eb3aacbb979d22d30b797f7ec29347ab1e6"}, ] [package.dependencies] @@ -307,74 +302,73 @@ files = [ [[package]] name = "pypresence" -version = "4.2.1" +version = "4.3.0" description = "Discord RPC client written in Python" optional = true -python-versions = ">=3.5" +python-versions = ">=3.8" files = [ - {file = "pypresence-4.2.1-py2.py3-none-any.whl", hash = "sha256:12197b5f51c21e3e555b17f85d3e55023f4ad83b6fff72cd6387659ffd484a02"}, - {file = "pypresence-4.2.1.tar.gz", hash = "sha256:691daf98c8189fd216d988ebfc67779e0f664211512d9843f37ab0d51d4de066"}, + {file = "pypresence-4.3.0-py2.py3-none-any.whl", hash = "sha256:af878c6d49315084f1b108aec86b31915080614d9421d6dd3a44737aba9ff13f"}, + {file = "pypresence-4.3.0.tar.gz", hash = "sha256:a6191a3af33a9667f2a4ef0185577c86b962ee70aa82643c472768a6fed1fbf3"}, ] [[package]] name = "pyqt5" -version = "5.15.9" +version = "5.15.10" description = "Python bindings for the Qt cross platform application toolkit" optional = true python-versions = ">=3.7" files = [ - {file = "PyQt5-5.15.9-cp37-abi3-macosx_10_13_x86_64.whl", hash = "sha256:883ba5c8a348be78c8be6a3d3ba014c798e679503bce00d76c666c2dc6afe828"}, - {file = "PyQt5-5.15.9-cp37-abi3-manylinux_2_17_x86_64.whl", hash = "sha256:dd5ce10e79fbf1df29507d2daf99270f2057cdd25e4de6fbf2052b46c652e3a5"}, - {file = "PyQt5-5.15.9-cp37-abi3-win32.whl", hash = "sha256:e45c5cc15d4fd26ab5cb0e5cdba60691a3e9086411f8e3662db07a5a4222a696"}, - {file = "PyQt5-5.15.9-cp37-abi3-win_amd64.whl", hash = "sha256:e030d795df4cbbfcf4f38b18e2e119bcc9e177ef658a5094b87bb16cac0ce4c5"}, - {file = "PyQt5-5.15.9.tar.gz", hash = "sha256:dc41e8401a90dc3e2b692b411bd5492ab559ae27a27424eed4bd3915564ec4c0"}, + {file = "PyQt5-5.15.10-cp37-abi3-macosx_10_13_x86_64.whl", hash = "sha256:93288d62ebd47b1933d80c27f5d43c7c435307b84d480af689cef2474e87e4c8"}, + {file = "PyQt5-5.15.10-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:862cea3be95b4b0a2b9678003b3a18edf7bd5eafd673860f58820f246d4bf616"}, + {file = "PyQt5-5.15.10-cp37-abi3-manylinux_2_17_x86_64.whl", hash = "sha256:b89478d16d4118664ff58ed609e0a804d002703c9420118de7e4e70fa1cb5486"}, + {file = "PyQt5-5.15.10-cp37-abi3-win32.whl", hash = "sha256:ff99b4f91aa8eb60510d5889faad07116d3340041916e46c07d519f7cad344e1"}, + {file = "PyQt5-5.15.10-cp37-abi3-win_amd64.whl", hash = "sha256:501355f327e9a2c38db0428e1a236d25ebcb99304cd6e668c05d1188d514adec"}, + {file = "PyQt5-5.15.10.tar.gz", hash = "sha256:d46b7804b1b10a4ff91753f8113e5b5580d2b4462f3226288e2d84497334898a"}, ] [package.dependencies] PyQt5-Qt5 = ">=5.15.2" -PyQt5-sip = ">=12.11,<13" +PyQt5-sip = ">=12.13,<13" [[package]] name = "pyqt5-qt5" -version = "5.15.2" +version = "5.15.12" description = "The subset of a Qt installation needed by PyQt5." optional = true python-versions = "*" files = [ - {file = "PyQt5_Qt5-5.15.2-py3-none-macosx_10_13_intel.whl", hash = "sha256:76980cd3d7ae87e3c7a33bfebfaee84448fd650bad6840471d6cae199b56e154"}, - {file = "PyQt5_Qt5-5.15.2-py3-none-manylinux2014_x86_64.whl", hash = "sha256:1988f364ec8caf87a6ee5d5a3a5210d57539988bf8e84714c7d60972692e2f4a"}, - {file = "PyQt5_Qt5-5.15.2-py3-none-win32.whl", hash = "sha256:9cc7a768b1921f4b982ebc00a318ccb38578e44e45316c7a4a850e953e1dd327"}, - {file = "PyQt5_Qt5-5.15.2-py3-none-win_amd64.whl", hash = "sha256:750b78e4dba6bdf1607febedc08738e318ea09e9b10aea9ff0d73073f11f6962"}, + {file = "PyQt5_Qt5-5.15.12-py3-none-macosx_10_13_x86_64.whl", hash = "sha256:7adff02a33f2f82b409ca115d57fc7e39b22e673d29e35c9d2594164039f0f71"}, + {file = "PyQt5_Qt5-5.15.12-py3-none-macosx_11_0_arm64.whl", hash = "sha256:b4c56566683ae905d5f04dd6ab925f02a384d6ce5ff4986eea0720d453666c22"}, ] [[package]] name = "pyqt5-sip" -version = "12.11.1" +version = "12.13.0" description = "The sip module support for PyQt5" optional = true python-versions = ">=3.7" files = [ - {file = "PyQt5_sip-12.11.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a40a39a6136a90e10c31510295c2be924564fc6260691501cdde669bdc5edea5"}, - {file = "PyQt5_sip-12.11.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:19b06164793177146c7f7604fe8389f44221a7bde196f2182457eb3e4229fa88"}, - {file = "PyQt5_sip-12.11.1-cp310-cp310-win32.whl", hash = "sha256:3afb1d1c07adcfef5c8bb12356a2ec2ec094f324af4417735d43b1ecaf1bb1a4"}, - {file = "PyQt5_sip-12.11.1-cp310-cp310-win_amd64.whl", hash = "sha256:54dad6c2e5dab14e46f6822a889bbb1515bbd2061762273af10d26566d649bd9"}, - {file = "PyQt5_sip-12.11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7218f6a1cefeb0b2fc26b89f15011f841aa4cd77786ccd863bf9792347fa38a8"}, - {file = "PyQt5_sip-12.11.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6b1113538082a7dd63b908587f61ce28ba4c7b8341e801fdf305d53a50a878ab"}, - {file = "PyQt5_sip-12.11.1-cp311-cp311-win32.whl", hash = "sha256:ac5f7ed06213d3bb203e33037f7c1a0716584c21f4f0922dcc044750e3659b80"}, - {file = "PyQt5_sip-12.11.1-cp311-cp311-win_amd64.whl", hash = "sha256:4f0497e2f5eeaea9f5a67b0e55c501168efa86df4e53aace2a46498b87bc55c1"}, - {file = "PyQt5_sip-12.11.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b355d56483edc79dcba30be947a6b700856bb74beb90539e14cc4d92b9bad152"}, - {file = "PyQt5_sip-12.11.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:dd163d9cffc4a56ebb9dd6908c0f0cb0caff8080294d41f4fb60fc3be63ca434"}, - {file = "PyQt5_sip-12.11.1-cp37-cp37m-win32.whl", hash = "sha256:b714f550ea6ddae94fd7acae531971e535f4a4e7277b62eb44e7c649cf3f03d0"}, - {file = "PyQt5_sip-12.11.1-cp37-cp37m-win_amd64.whl", hash = "sha256:d09b2586235deab7a5f2e28e4bde9a70c0b3730fa84f2590804a9932414136a3"}, - {file = "PyQt5_sip-12.11.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9a6f9c058564d0ac573561036299f54c452ae78b7d2a65d7c2d01685e6dca50d"}, - {file = "PyQt5_sip-12.11.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:fc920c0e0d5050474d2d6282b478e4957548bf1dce58e1b0678914514dc70064"}, - {file = "PyQt5_sip-12.11.1-cp38-cp38-win32.whl", hash = "sha256:3358c584832f0ac9fd1ad3623d8a346c705f43414df1fcd0cb285a6ef51fec08"}, - {file = "PyQt5_sip-12.11.1-cp38-cp38-win_amd64.whl", hash = "sha256:f9691c6f4d899ca762dd54442a1be158c3e52017f583183da6ef37d5bae86595"}, - {file = "PyQt5_sip-12.11.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:0bc81cb9e171d29302d393775f95cfa01b7a15f61b199ab1812976e5c4cb2cb9"}, - {file = "PyQt5_sip-12.11.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:b077fb4383536f51382f5516f0347328a4f338c6ccc4c268cc358643bef1b838"}, - {file = "PyQt5_sip-12.11.1-cp39-cp39-win32.whl", hash = "sha256:5c152878443c3e951d5db7df53509d444708dc06a121c267b548146be06b87f8"}, - {file = "PyQt5_sip-12.11.1-cp39-cp39-win_amd64.whl", hash = "sha256:bd935cc46dfdbb89c21042c1db2e46a71f25693af57272f146d6d9418e2934f1"}, - {file = "PyQt5_sip-12.11.1.tar.gz", hash = "sha256:97d3fbda0f61edb1be6529ec2d5c7202ae83aee4353e4b264a159f8c9ada4369"}, + {file = "PyQt5_sip-12.13.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a7e3623b2c743753625c4650ec7696362a37fb36433b61824cf257f6d3d43cca"}, + {file = "PyQt5_sip-12.13.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6e4ac714252370ca037c7d609da92388057165edd4f94e63354f6d65c3ed9d53"}, + {file = "PyQt5_sip-12.13.0-cp310-cp310-win32.whl", hash = "sha256:d5032da3fff62da055104926ffe76fd6044c1221f8ad35bb60804bcb422fe866"}, + {file = "PyQt5_sip-12.13.0-cp310-cp310-win_amd64.whl", hash = "sha256:9a8cdd6cb66adcbe5c941723ed1544eba05cf19b6c961851b58ccdae1c894afb"}, + {file = "PyQt5_sip-12.13.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0f85fb633a522f04e48008de49dce1ff1d947011b48885b8428838973fbca412"}, + {file = "PyQt5_sip-12.13.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:ec60162e034c42fb99859206d62b83b74f987d58937b3a82bdc07b5c3d190dec"}, + {file = "PyQt5_sip-12.13.0-cp311-cp311-win32.whl", hash = "sha256:205cd449d08a2b024a468fb6100cd7ed03e946b4f49706f508944006f955ae1a"}, + {file = "PyQt5_sip-12.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:1c8371682f77852256f1f2d38c41e2e684029f43330f0635870895ab01c02f6c"}, + {file = "PyQt5_sip-12.13.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:7fe3375b508c5bc657d73b9896bba8a768791f1f426c68053311b046bcebdddf"}, + {file = "PyQt5_sip-12.13.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:773731b1b5ab1a7cf5621249f2379c95e3d2905e9bd96ff3611b119586daa876"}, + {file = "PyQt5_sip-12.13.0-cp312-cp312-win32.whl", hash = "sha256:fb4a5271fa3f6bc2feb303269a837a95a6d8dd16be553aa40e530de7fb81bfdf"}, + {file = "PyQt5_sip-12.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:3a4498f3b1b15f43f5d12963accdce0fd652b0bcaae6baf8008663365827444c"}, + {file = "PyQt5_sip-12.13.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9b984c2620a7a7eaf049221b09ae50a345317add2624c706c7d2e9e6632a9587"}, + {file = "PyQt5_sip-12.13.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:3188a06956aef86f604fb0d14421a110fad70d2a9e943dbacbfc3303f651dade"}, + {file = "PyQt5_sip-12.13.0-cp38-cp38-win32.whl", hash = "sha256:108a15f603e1886988c4b0d9d41cb74c9f9815bf05cefc843d559e8c298a10ce"}, + {file = "PyQt5_sip-12.13.0-cp38-cp38-win_amd64.whl", hash = "sha256:db228cd737f5cbfc66a3c3e50042140cb80b30b52edc5756dbbaa2346ec73137"}, + {file = "PyQt5_sip-12.13.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5338773bbaedaa4f16a73c142fb23cc18c327be6c338813af70260b756c7bc92"}, + {file = "PyQt5_sip-12.13.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:29fa9cc964517c9fc3f94f072b9a2aeef4e7a2eda1879cb835d9e06971161cdf"}, + {file = "PyQt5_sip-12.13.0-cp39-cp39-win32.whl", hash = "sha256:96414c93f3d33963887cf562d50d88b955121fbfd73f937c8eca46643e77bf61"}, + {file = "PyQt5_sip-12.13.0-cp39-cp39-win_amd64.whl", hash = "sha256:bbc7cd498bf19e0862097be1ad2243e824dea56726f00c11cff1b547c2d31d01"}, + {file = "PyQt5_sip-12.13.0.tar.gz", hash = "sha256:7f321daf84b9c9dbca61b80e1ef37bdaffc0e93312edae2cd7da25b953971d91"}, ] [[package]] @@ -432,30 +426,73 @@ certifi = "*" [[package]] name = "urllib3" -version = "1.26.14" +version = "2.1.0" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" +python-versions = ">=3.8" files = [ - {file = "urllib3-1.26.14-py2.py3-none-any.whl", hash = "sha256:75edcdc2f7d85b137124a6c3c9fc3933cdeaa12ecb9a6a959f22797a0feca7e1"}, - {file = "urllib3-1.26.14.tar.gz", hash = "sha256:076907bf8fd355cde77728471316625a4d2f7e713c125f51953bb5b3eecf4f72"}, + {file = "urllib3-2.1.0-py3-none-any.whl", hash = "sha256:55901e917a5896a349ff771be919f8bd99aff50b79fe58fec595eb37bbc56bb3"}, + {file = "urllib3-2.1.0.tar.gz", hash = "sha256:df7aa8afb0148fa78488e7899b2c59b5f4ffcfa82e6c54ccb9dd37c1d7b52d54"}, ] [package.extras] -brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] -secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] -socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] +zstd = ["zstandard (>=0.18.0)"] [[package]] name = "urwid" -version = "2.1.2" +version = "2.3.4" description = "A full-featured console (xterm et al.) user interface library" optional = true -python-versions = "*" +python-versions = ">=3.7.0" files = [ - {file = "urwid-2.1.2.tar.gz", hash = "sha256:588bee9c1cb208d0906a9f73c613d2bd32c3ed3702012f51efe318a3f2127eae"}, + {file = "urwid-2.3.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:232b64678248c489e0dddadccfc2483a54567b74fcf74d140f3f4b4e9d15ddba"}, + {file = "urwid-2.3.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ce538f0e5c8ee2341f3e38239a1c65a5a042ee993577093067a4419c10615030"}, + {file = "urwid-2.3.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:81a17afedc1f0ec7ee6af9ebae0c21f5caddf050831ffc5dfbd7e61a4966388a"}, + {file = "urwid-2.3.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bbbb8c21920be76630d1ae03eb67dced3d362a56eabf05d619a0ce3f0488cb0d"}, + {file = "urwid-2.3.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d1a0e6bbd4e70805519e843fa982ba322d819939eb7049e6ece5ed6aa132c96b"}, + {file = "urwid-2.3.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b0802494b1baaf691313cd24aaf5e4ec00bf8a6f4ba664bdf088119517db0d4"}, + {file = "urwid-2.3.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0be1a86fa279850bd167a031dd71aa401b26f7e9fdcc99360785f1a292938c10"}, + {file = "urwid-2.3.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:de28459d08020a07fe4d918a9ca5ff069102fa5455356cc5a1695b55ebb5bafe"}, + {file = "urwid-2.3.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4dbeb404751341f354f7d7bf5857cacb7ba415335427c78ee00991cdcc1b5bb0"}, + {file = "urwid-2.3.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45e61a5be847ab36a5188aacd96554e0f354367dcae2f6cbc284de7fbbb3f075"}, + {file = "urwid-2.3.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0d518d3cb428c9e0c03076dc6b83996bbfe0595d4612c3c2f572d8edbc260e9c"}, + {file = "urwid-2.3.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67bdc9c3f8a834b848e4400d91b5ab82620e1f963e4736600a304930433ea8fe"}, + {file = "urwid-2.3.4-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:32d91a4f2abaa022d6bcd8f4d7b179a1bb03afcb83be4707e5599131f322dbce"}, + {file = "urwid-2.3.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:278ffe0c8366c03da533a983eb85ac80e325eec09f78988fd37fc830dc563eb2"}, + {file = "urwid-2.3.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0080ad86d37792faeda2ddaf7eaab711861c34f19996876dde649e28139a741d"}, + {file = "urwid-2.3.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c141f5e6a32e03e78ef28083691588d37f60fb5ffb2d96aecba3cda7fa38bb1"}, + {file = "urwid-2.3.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:085b4b1ff4c0df96e2e82331a2353a56abbd5b7456e838baa995be4f12644347"}, + {file = "urwid-2.3.4-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ca3e5fbdddc3b4394cc93835a79358c56a54b05538edc7e8b66d2ff13c4689f"}, + {file = "urwid-2.3.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c79f55558dc50f8c19d1e1468e63942fc7577cbdb2ff948768931ef28548a5af"}, + {file = "urwid-2.3.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c692bb9a314216c0cfe1fdd8787858e2e916e25f97c95e8411ba5933c0fe3c39"}, + {file = "urwid-2.3.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:90440ef37b50ad5e947a095bfec7a048dfae938daa0d2355274accd886474932"}, + {file = "urwid-2.3.4-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:205c7aa020c92797f65465e1a226fbf2122159b1565b936a5fd6ed6bd34b4440"}, + {file = "urwid-2.3.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0a263d8a90450166e0a195cf751b46d5081a48a3c00afa45a4ae582a34a6785e"}, + {file = "urwid-2.3.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5a6fc3651d5aff40d53b52cae1951d17c62126ffcc11c7f0d53583d28198b0a4"}, + {file = "urwid-2.3.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4e4a3500b7166f27ca830df8bcfb9969d97a98f340cd4b0f0299557dee8c39b3"}, + {file = "urwid-2.3.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:963e89099b0438416b550161750a1e2fb52a328008732197d29e3675baacb150"}, + {file = "urwid-2.3.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:448d248bd3cbe34f0422108db0cecb7d24336703677bce06b7ed67dc4892d925"}, + {file = "urwid-2.3.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bae83944dc78bd178b0ad8cab9ff7d72b03ac01dcd0e1e01725142e241196f04"}, + {file = "urwid-2.3.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:6cc8db27989f6166602a45f6382357827cba966e3bd607a9e409cf867f7b0ec6"}, + {file = "urwid-2.3.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0c7e3f21b4427ecfffd6588fe5119eab7e12abd03576e8ba111fdfe2a78e3fdc"}, + {file = "urwid-2.3.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9dae4dbccae166ee9ff03bda58c36369a566d03274c1fbf559cff11117539bc7"}, + {file = "urwid-2.3.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c4a213be475ae81b250e401e670d74d5d4ffb3b034ff6fc52e721759788eb05"}, + {file = "urwid-2.3.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7d38afb5bbcbca365fbd86746990c22167fbeb7f85d756bad33892f364028975"}, + {file = "urwid-2.3.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5fbb765783120d5dc835424f6870190b6d73c020b44ab650b682fd9ffbc41a85"}, + {file = "urwid-2.3.4.tar.gz", hash = "sha256:18b9f84cc80a4fcda55ea29f0ea260d31f8c1c721ff6a0396a396020e6667738"}, ] +[package.extras] +glib = ["PyGObject"] +lcd = ["pyserial"] +serial = ["pyserial"] +tornado = ["tornado"] +trio = ["exceptiongroup", "trio (>=0.22.0)"] +twisted = ["twisted"] +zmq = ["zmq"] + [extras] curses = ["urwid"] discord-rpc = ["pypresence"] @@ -467,5 +504,5 @@ ui = ["pillow", "pycairo", "pygobject", "pyqt5", "urwid"] [metadata] lock-version = "2.0" -python-versions = "^3.8" -content-hash = "a2397940e92875df0212b6bdda7eaf3848d76aff0a97178f8ff81f06d43ef681" +python-versions = "^3.10" +content-hash = "c3cc208b5a57da93409b8c4eb3c1c0f3e7de4263d547a126e8b440375f4c0914" diff --git a/pyproject.toml b/pyproject.toml index 1b22b5b0..f404e929 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,8 +17,6 @@ classifiers = [ "Topic :: Internet", "Topic :: Multimedia", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Operating System :: POSIX", @@ -27,7 +25,7 @@ classifiers = [ [tool.poetry.dependencies] -python = "^3.8" +python = "^3.10" requests = "^2.28.1" inotify = { version = "^0.2.10", optional = true } pillow = { version = "^9.2.0", optional = true } diff --git a/trackma/lib/libmal.py b/trackma/lib/libmal.py index 2ecada8b..d173a2d9 100644 --- a/trackma/lib/libmal.py +++ b/trackma/lib/libmal.py @@ -53,7 +53,7 @@ class libmal(lib): 'statuses_start': ['watching'], 'statuses_finish': ['completed'], 'statuses_library': ['watching', 'on_hold', 'plan_to_watch'], - 'statuses': ['watching', 'completed', 'on_hold', 'dropped', 'plan_to_watch'], + 'statuses': ['watching', 'completed', 'on_hold', 'dropped', 'plan_to_watch'], 'statuses_dict': { 'watching': 'Watching', 'completed': 'Completed', @@ -76,7 +76,7 @@ class libmal(lib): 'can_date': True, 'statuses_start': ['reading'], 'statuses_finish': ['completed'], - 'statuses': ['reading', 'completed', 'on_hold', 'dropped', 'plan_to_read'], + 'statuses': ['reading', 'completed', 'on_hold', 'dropped', 'plan_to_read'], 'statuses_dict': { 'reading': 'Reading', 'completed': 'Completed', @@ -108,13 +108,13 @@ class libmal(lib): 'cm':utils.Type.OTHER, 'tv_special': utils.Type.SPECIAL } - + status_translate = { 'currently_airing': utils.Status.AIRING, 'finished_airing': utils.Status.FINISHED, 'not_yet_aired': utils.Status.NOTYET, } - + season_translate = { utils.Season.WINTER: 'winter', utils.Season.SPRING: 'spring', @@ -129,7 +129,7 @@ class libmal(lib): query_url = "https://api.myanimelist.net/v2" client_id = "32c510ab2f47a1048a8dd24de266dc0c" user_agent = 'Trackma/{}'.format(utils.VERSION) - + library_page_limit = 1000 search_page_limit = 100 season_page_limit = 500 @@ -138,15 +138,15 @@ def __init__(self, messenger, account, userconfig): super(libmal, self).__init__(messenger, account, userconfig) self.pin = account['password'].strip() - + if 'extra' not in account or 'code_verifier' not in account['extra']: raise utils.APIFatal( "This account seems to be using the old MyAnimeList API." "Please re-create and authorize the account.") - + self.code_verifier = account['extra']['code_verifier'] self.userid = self._get_userconfig('userid') - + if self.mediatype == 'manga': self.total_str = "num_chapters" self.watched_str = self.watched_send_str = "num_chapters_read" @@ -157,10 +157,10 @@ def __init__(self, messenger, account, userconfig): self.opener = urllib.request.build_opener() self.opener.addheaders = [ - ('User-Agent', self.user_agent), - ('Accept', 'application/json'), + ('User-Agent', self.user_agent), + ('Accept', 'application/json'), ('Accept-Encoding', 'gzip'), - ('Accept-Charset', 'utf-8'), + ('Accept-Charset', 'utf-8'), ] def _request(self, method, url, get=None, post=None, auth=False): @@ -193,7 +193,7 @@ def _request(self, method, url, get=None, post=None, auth=False): response = gzip.GzipFile(fileobj=response).read().decode('utf-8') else: response = response.read().decode('utf-8') - + return json.loads(response) except urllib.error.HTTPError as e: raise utils.APIError("Connection error: %s" % e) @@ -201,13 +201,13 @@ def _request(self, method, url, get=None, post=None, auth=False): raise utils.APIError("URL error: %s" % e) except socket.timeout: raise utils.APIError("Operation timed out.") - + def _request_access_token(self, refresh=False): """ Requests or refreshes the access token through OAuth2 """ params = { - 'client_id': self.client_id, + 'client_id': self.client_id, } if refresh: @@ -221,25 +221,25 @@ def _request_access_token(self, refresh=False): params['code'] = self.pin params['code_verifier'] = self.code_verifier params['grant_type'] = 'authorization_code' - + data = self._request('POST', self.auth_url, post=params) timestamp = int(time.time()) - self._set_userconfig('access_token', data['access_token']) - self._set_userconfig('token_type', data['token_type']) - self._set_userconfig('expires', timestamp + data['expires_in']) + self._set_userconfig('access_token', data['access_token']) + self._set_userconfig('token_type', data['token_type']) + self._set_userconfig('expires', timestamp + data['expires_in']) self._set_userconfig('refresh_token', data['refresh_token']) self.logged_in = True self._emit_signal('userconfig_changed') - + def check_credentials(self): timestamp = int(time.time()) - + if not self._get_userconfig('access_token'): self._request_access_token(False) - elif (timestamp+60) > self._get_userconfig('expires'): + elif (timestamp + 60) > self._get_userconfig('expires'): try: self._request_access_token(True) except utils.APIError: @@ -255,18 +255,18 @@ def check_credentials(self): def fetch_list(self): self.check_credentials() shows = {} - - fields = 'id,alternative_titles,title,start_date,main_picture,status,' + self.total_str + + fields = 'id,alternative_titles,title,start_date,main_picture,status,broadcast,' + self.total_str listfields = 'score,status,start_date,finish_date,' + self.watched_str params = { 'fields': '%s,list_status{%s}' % (fields, listfields), 'limit': self.library_page_limit, 'nsfw': 'true' } - + url = "{}/users/@me/{}list?{}".format(self.query_url, self.mediatype, urllib.parse.urlencode(params)) i = 1 - + while url: self.msg.info('Downloading list (page %d)...' % i) data = self._request('GET', url, auth=True) @@ -288,8 +288,9 @@ def fetch_list(self): 'my_status': item['list_status']['status'], 'my_start_date': self._str2date(item['list_status'].get('start_date')), 'my_finish_date': self._str2date(item['list_status'].get('finish_date')), + 'next_ep_time': self._next_episode_to_utc_datetime(item['node']) }) - + url = data['paging'].get('next') i += 1 @@ -304,49 +305,50 @@ def update_show(self, item): self.check_credentials() self.msg.info("Updating item %s..." % item['title']) self._update_entry(item) - + def delete_show(self, item): self.check_credentials() self.msg.info("Deleting item %s..." % item['title']) - data = self._request('DELETE', self.query_url + '/%s/%d/my_list_status' % (self.mediatype, item['id']), auth=True) - + data = self._request('DELETE', self.query_url + '/%s/%d/my_list_status' % (self.mediatype, item['id']), + auth=True) + def search(self, criteria, method): self.check_credentials() self.msg.info("Searching for {}...".format(criteria)) - + fields = 'alternative_titles,end_date,genres,id,main_picture,mean,media_type,' + self.total_str + ',popularity,rating,start_date,status,studios,synopsis,title' params = {'fields': fields, 'nsfw': 'true'} - + if method == utils.SearchMethod.KW: url = '/%s' % self.mediatype params['q'] = criteria params['limit'] = self.search_page_limit elif method == utils.SearchMethod.SEASON: - season, season_year = criteria + season, season_year = criteria url = '/%s/season/%d/%s' % (self.mediatype, season_year, self.season_translate[season]) params['limit'] = self.season_page_limit else: raise utils.APIError("Invalid search method.") - + results = [] data = self._request('GET', self.query_url + url, get=params, auth=True) for item in data['data']: results.append(self._parse_info(item['node'])) - + self._emit_signal('show_info_changed', results) return results - + def request_info(self, itemlist): self.check_credentials() infolist = [] - + fields = 'alternative_titles,end_date,genres,id,main_picture,mean,media_type,' + self.total_str + ',popularity,rating,start_date,status,studios,synopsis,title' params = {'fields': fields, 'nsfw': 'true'} for item in itemlist: data = self._request('GET', self.query_url + '/%s/%d' % (self.mediatype, item['id']), get=params, auth=True) infolist.append(self._parse_info(data)) - + self._emit_signal('show_info_changed', infolist) return infolist @@ -363,17 +365,19 @@ def _update_entry(self, item): if 'my_finish_date' in item: values['finish_date'] = item['my_finish_date'] or "" - data = self._request('PATCH', self.query_url + '/%s/%d/my_list_status' % (self.mediatype, item['id']), post=values, auth=True) + data = self._request('PATCH', self.query_url + '/%s/%d/my_list_status' % (self.mediatype, item['id']), + post=values, auth=True) def _get_aliases(self, item): - aliases = [item['alternative_titles']['en'], item['alternative_titles']['ja']] + item['alternative_titles']['synonyms'] - + aliases = [item['alternative_titles']['en'], item['alternative_titles']['ja']] + item['alternative_titles'][ + 'synonyms'] + return aliases - + def _parse_info(self, item): info = utils.show() showid = item['id'] - + info.update({ 'id': showid, 'title': item['title'], @@ -386,27 +390,95 @@ def _parse_info(self, item): 'start_date': self._str2date(item.get('start_date')), 'end_date': self._str2date(item.get('end_date')), 'extra': [ - ('English', item['alternative_titles'].get('en')), - ('Japanese', item['alternative_titles'].get('ja')), - ('Synonyms', item['alternative_titles'].get('synonyms')), - ('Synopsis', item.get('synopsis')), - ('Type', item.get('media_type')), - ('Mean score', item.get('mean')), - ('Status', self._translate_status(item['status'])), + ('English', item['alternative_titles'].get('en')), + ('Japanese', item['alternative_titles'].get('ja')), + ('Synonyms', item['alternative_titles'].get('synonyms')), + ('Synopsis', item.get('synopsis')), + ('Type', item.get('media_type')), + ('Mean score', item.get('mean')), + ('Status', self._translate_status(item['status'])), ] }) - + return info - + def _translate_status(self, orig_status): return self.status_translate.get(orig_status, utils.Status.UNKNOWN) - def _str2date(self, string): + @staticmethod + def _str2date(string): if string is None: return None try: return datetime.datetime.strptime(string, "%Y-%m-%d") - except Exception: - self.msg.debug('Invalid date {}'.format(string)) + except ValueError: return None # Ignore date if it's invalid + + @classmethod + def _next_episode_to_utc_datetime(cls, mal_response_node: dict) -> datetime: + """MAL is pretty rubbish, and does not provide an actual date for the next episode. + Instead, it gives a date for when the show started airing, and the general broadcast schedule as a string. + E.g.: {'day_of_the_week': 'friday', 'start_time': '23:00'} — *this is assumed to be JST* + It does not update for when a show goes on hiatus or anything like that. + + This function takes that string and assumes that the 'day of the week' is the upcoming weekday for an airing + show. There is no other way to do this without a 3rd party site. If the show is not airing, this does not + matter, as MAL doesn't return anything anyway.""" + + airing_status = mal_response_node['status'] # Get the status of the show + next_broadcast_utc = None # Default 'Next episode' value, in case MAL doesn't return anything + + # Define JST time-zone and current datetime in JST (there is no DST) + jst_timezone = datetime.timezone(datetime.timedelta(hours=9)) + current_jst_time = datetime.datetime.now(tz=jst_timezone) + + # Potentially return the 'Next episode' time in UTC depending on the status + match airing_status: + case 'finished_airing': # There are no new episodes + pass + + case 'currently_airing': # The UTC datetime object needs to be stitched together + broadcast_response = mal_response_node.get('broadcast') # Returned by MAL + if broadcast_response is not None: + # Read and strip MAL response, generally looks like this (given in JST): 'friday 23:00' + jst_broadcast_time_day_str = broadcast_response['day_of_the_week'] # 'friday' + jst_broadcast_time_time_str = broadcast_response['start_time'] # '23:00' + + if jst_broadcast_time_time_str is not None: + jst_broadcast_time_hour, jst_broadcast_time_minute = ( + map(int, jst_broadcast_time_time_str.split(':'))) + + else: # Sometimes MAL doesn't have an exact time entry + jst_broadcast_time_hour, jst_broadcast_time_minute = ('0', '0') + + # Map weekday names to corresponding indices. Lowercase because MAL uses lowercase for some reason + weekday_indices = {'monday': 1, 'tuesday': 2, 'wednesday': 3, 'thursday': 4, 'friday': 5, + 'saturday': 6, 'sunday': 7} + + # Calculate the days until the next broadcast weekday + days_until_next_broadcast = (weekday_indices[jst_broadcast_time_day_str] + - current_jst_time.isoweekday() + 7) % 7 + + # Set the correct day and time + next_broadcast_jst = current_jst_time + datetime.timedelta(days=days_until_next_broadcast) + next_broadcast_jst = next_broadcast_jst.replace(hour=jst_broadcast_time_hour, + minute=jst_broadcast_time_minute, + second=0, microsecond=0) + + # Convert to UTC + next_broadcast_utc = next_broadcast_jst.astimezone(datetime.timezone.utc) + + # These shows are a mess and return one of the following formats: 'None'/'2024'/'2024 04'/'2024 04 12' + case 'not_yet_aired': + # _str2date returns 'None' for anything that's not '%Y-%m-%d' compliant, so we don't have to check + next_broadcast_jst = cls._str2date(mal_response_node.get('start_date')) + if next_broadcast_jst is not None: + # Assign JST timezone, set the broadcast time to 12pm, then convert to UTC + next_broadcast_jst = next_broadcast_jst.replace(tzinfo=jst_timezone, hour=12, microsecond=0) + next_broadcast_utc = next_broadcast_jst.astimezone(tz=datetime.timezone.utc) + + case _: + raise ValueError + + return next_broadcast_utc diff --git a/trackma/ui/gtk/mainview.py b/trackma/ui/gtk/mainview.py index 0a7897ba..59b6a2a1 100644 --- a/trackma/ui/gtk/mainview.py +++ b/trackma/ui/gtk/mainview.py @@ -30,12 +30,11 @@ @Gtk.Template.from_file(os.path.join(gtk_dir, 'data/mainview.ui')) class MainView(Gtk.Box): - __gtype_name__ = 'MainView' __gsignals__ = { 'error': (GObject.SignalFlags.RUN_FIRST, None, - (str, )), + (str,)), 'success': (GObject.SignalFlags.RUN_CLEANUP, None, ()), 'error-fatal': (GObject.SignalFlags.RUN_FIRST, None, @@ -98,6 +97,7 @@ def load_account_mediatype(self, account, mediatype, extern_widget): self._engine_reload(account, mediatype, extern_widget) def _init_widgets(self): + self._visible_column_reset() self.image_box = ImageBox(100, 150) self.image_box.show() self.image_container_box.pack_start(self.image_box, False, False, 0) @@ -535,7 +535,8 @@ def _on_show_action(self, page, event_type, data): def get_current_status(self): print(self._engine.mediainfo['statuses']) - return self._current_page.status if self._current_page.status is not None else self._engine.mediainfo['statuses'][-1] + return self._current_page.status if self._current_page.status is not None else \ + self._engine.mediainfo['statuses'][-1] def get_selected_show(self): if not self._current_page: @@ -559,13 +560,26 @@ def _on_column_toggled(self, page, column_name, visible): utils.save_config(self._config, self._configfile) + def _visible_column_reset(self): + """Should be called when column naming scheme changes to make sure that the default columns are + visible by default, regardless of config + + In 1.1: + Default column 'Progress' was renamed to 'Watched', + Default column 'Percent' was renamed to 'Progress'. + 'New episode' added to default columns""" + column_version = '1.1' # Column naming version number + if self._config['column_version'] != column_version: + self._config['visible_columns'] = utils.gtk_defaults['visible_columns'] + self._config['column_version'] = column_version + class NotebookPage(Gtk.ScrolledWindow): __gtype_name__ = 'NotebookPage' __gsignals__ = { 'show-selected': (GObject.SignalFlags.RUN_FIRST, None, - (int, )), + (int,)), 'show-action': (GObject.SignalFlags.RUN_FIRST, None, (int, object)), 'column-toggled': (GObject.SignalFlags.RUN_FIRST, None, @@ -580,7 +594,8 @@ def __init__(self, engine, page_num, status, config, _list=None, title=None): self._selected_show = 0 self._list = _list self._title = title - self._title_text = self._engine.mediainfo['statuses_dict'][status] if status in self._engine.mediainfo['statuses_dict'].keys( + self._title_text = self._engine.mediainfo['statuses_dict'][status] if status in self._engine.mediainfo[ + 'statuses_dict'].keys( ) else 'All' self._init_widgets(page_num, status, config) @@ -592,14 +607,9 @@ def _init_widgets(self, page_num, status, config): self._show_tree_view = ShowTreeView( config['colors'], config['visible_columns'], - config['episodebar_style']) - self._show_tree_view.set_model( - Gtk.TreeModelSort( - model=ShowListFilter( - status=self.status, - child_model=self._list - ) - ) + self.status, + self._list, + config['episodebar_style'], ) self._title.set_text('%s (%d)' % ( self._title_text, @@ -687,17 +697,17 @@ def _view_context_menu(self, event): show = self._engine.get_show_info(self._selected_show) menu = Gtk.Menu() - mb_play = Gtk.ImageMenuItem('Play Next', + mb_play = Gtk.ImageMenuItem('Play next episode', Gtk.Image.new_from_icon_name( "media-playback-start", Gtk.IconSize.MENU)) mb_play.connect("activate", self._on_mb_activate, ShowEventType.PLAY_NEXT) - mb_info = Gtk.MenuItem("Show details...") + mb_info = Gtk.MenuItem("Show details") mb_info.connect("activate", self._on_mb_activate, ShowEventType.DETAILS) - mb_web = Gtk.MenuItem("Open web site") + mb_web = Gtk.MenuItem("Open on " + self._engine.api_info['name']) mb_web.connect("activate", self._on_mb_activate, ShowEventType.OPEN_WEBSITE) @@ -709,7 +719,7 @@ def _view_context_menu(self, event): mb_copy.connect("activate", self._on_mb_activate, ShowEventType.COPY_TITLE) - mb_alt_title = Gtk.MenuItem("Set alternate title...") + mb_alt_title = Gtk.MenuItem("Set alternate title") mb_alt_title.connect("activate", self._on_mb_activate, ShowEventType.CHANGE_ALTERNATIVE_TITLE) diff --git a/trackma/ui/gtk/showtreeview.py b/trackma/ui/gtk/showtreeview.py index 652fd6dd..bc13650d 100644 --- a/trackma/ui/gtk/showtreeview.py +++ b/trackma/ui/gtk/showtreeview.py @@ -19,32 +19,61 @@ from trackma import utils +# Declare named constants for the tree references, so it's more unified and easier to read +# Putting them in their own class also enables whatever we might want to do with it in the future. +class TreeConstants: + SHOW_ID = 0 # Show ID + TITLE = 1 # Show title + MY_PROGRESS = 2 # Number of watched episodes + MY_SCORE = 3 # User given score + WATCHED_EPISODES_FRACTION = 4 # Watched episodes / total episodes. E.g: 7 / 13 + MY_SCORE_STRING = 5 # User given score (string), with 'self.decimals' decimals + TOTAL_EPS = 6 # Total number of episodes + AIRED_EPS = 7 # (Estimated) number of episodes aired. + # If no number is provided by the lib, 1 episode / week is assumed and calculated accordingly + AVAILABLE_EPS = 8 # Number of available episodes in the local library + COLOR = 9 # Used with the _get_color method to return the color preset for a show + PROGRESS_PERCENTAGE = 10 # % of episodes watched. 7 / 13 -> 53 + START_DATE = 11 # Start date of the show + END_DATE = 12 # End date of the show + MY_START_DATE = 13 # Date when the user started watching the show + MY_FINISH_DATE = 14 # Date when the user finished the show + MY_STATUS = 15 # User's show status (watching, paused, completed etc.) + SHOW_STATUS = 16 # Show's status (airing, upcoming etc.) + NEXT_EPISODE_AIR_TIME_RELATIVE = 17 # Relative time until the next episode as 'X days / hours / minutes' + + class ShowListStore(Gtk.ListStore): + # Determines the structure of the tree and holds the actual data after it is appended from row[]. + # Entry order must match with row[]. __cols = ( - ('id', int), - ('title', str), - ('stat', int), - ('score', float), - ('stat-text', str), - ('score-text', str), - ('total-eps', int), - ('subvalue', int), - ('avail-eps', GObject.TYPE_PYOBJECT), - ('color', str), - ('stat-pcent', int), - ('start', str), - ('end', str), - ('my-start', str), - ('my-end', str), - ('my-status', str), - ('status', int), + (TreeConstants.SHOW_ID, int), + (TreeConstants.TITLE, str), + (TreeConstants.MY_PROGRESS, int), + (TreeConstants.MY_SCORE, float), + (TreeConstants.WATCHED_EPISODES_FRACTION, str), + (TreeConstants.MY_SCORE_STRING, str), + (TreeConstants.TOTAL_EPS, int), + (TreeConstants.AIRED_EPS, int), + (TreeConstants.AVAILABLE_EPS, GObject.TYPE_PYOBJECT), + (TreeConstants.COLOR, str), + (TreeConstants.PROGRESS_PERCENTAGE, int), + (TreeConstants.START_DATE, str), + (TreeConstants.END_DATE, str), + (TreeConstants.MY_START_DATE, str), + (TreeConstants.MY_FINISH_DATE, str), + (TreeConstants.MY_STATUS, str), + (TreeConstants.SHOW_STATUS, int), + (TreeConstants.NEXT_EPISODE_AIR_TIME_RELATIVE, str), ) - def __init__(self, decimals=0, colors=dict()): + def __init__(self, decimals=0, colors=None): super().__init__(*self.__class__.__columns__()) + if colors is None: + colors = dict() self.colors = colors self.decimals = decimals - self.set_sort_column_id(1, Gtk.SortType.ASCENDING) + self.set_sort_column_id(TreeConstants.TITLE, Gtk.SortType.ASCENDING) @staticmethod def format_date(date): @@ -63,7 +92,7 @@ def __columns__(cls): @classmethod def column(cls, key): try: - return cls.__cols.index(next(i for i in cls.__cols if i[0] == key)) + return cls.__cols.index(next(i for i in cls.__cols if i[TreeConstants.SHOW_ID] == key)) except ValueError: return None @@ -80,10 +109,11 @@ def _get_color(self, show, eps): return None def append(self, show, altname=None, eps=None): - episodes_str = "{} / {}".format(show['my_progress'], - show['total'] or '?') + watched_episodes_fraction = "{} / {}".format(show['my_progress'], + show['total'] or '?') if show['total'] and show['my_progress'] <= show['total']: - progress = (float(show['my_progress']) / show['total']) * 100 + progress_float = (show['my_progress'] / show['total']) * 100 + progress = int(progress_float) else: progress = 0 @@ -104,29 +134,35 @@ def append(self, show, altname=None, eps=None): my_start_date = self.format_date(show['my_start_date']) my_finish_date = self.format_date(show['my_finish_date']) - row = [show['id'], - title_str, - show['my_progress'], - show['my_score'], - episodes_str, - score_str, - show['total'], - aired_eps, - available_eps, - self._get_color(show, available_eps), - progress, - start_date, - end_date, - my_start_date, - my_finish_date, - show['my_status'], - show['status'] + # Gets the (short) relative airing time of the next episode compared to UTC + next_episode_air_time_relative = utils.calculate_relative_time(show['next_ep_time'], + utc=True, fulltime=False) + + # Corresponds to __cols, but is used locally to store the data before appending. Comments are TreeConstants + row = [show['id'], # SHOW_ID + title_str, # TITLE + show['my_progress'], # MY_PROGRESS + show['my_score'], # MY_SCORE + watched_episodes_fraction, # WATCHED_EPISODES_FRACTION + score_str, # MY_SCORE_STRING + show['total'], # TOTAL_EPS + aired_eps, # AIRED_EPS + available_eps, # AVAILABLE_EPS + self._get_color(show, available_eps), # COLOR + progress, # PROGRESS_PERCENTAGE + start_date, # START_DATE + end_date, # END_DATE + my_start_date, # MY_START_DATE + my_finish_date, # MY_FINISH_DATE + show['my_status'], # MY_STATUS + show['status'], # SHOW_STATUS + next_episode_air_time_relative, # NEXT_EPISODE_AIR_TIME_RELATIVE ] super().append(row) def update_or_append(self, show): for row in self: - if int(row[0]) == show['id']: + if int(row[TreeConstants.SHOW_ID]) == show['id']: self.update(show, row) return self.append(show) @@ -134,49 +170,47 @@ def update_or_append(self, show): def update(self, show, row=None): if not row: for row in self: - if int(row[0]) == show['id']: + if int(row[TreeConstants.SHOW_ID]) == show['id']: break - if row and int(row[0]) == show['id']: + if row and int(row[TreeConstants.SHOW_ID]) == show['id']: episodes_str = "{} / {}".format(show['my_progress'], show['total'] or '?') - row[2] = show['my_progress'] - row[4] = episodes_str + row[TreeConstants.MY_PROGRESS] = show['my_progress'] + row[TreeConstants.WATCHED_EPISODES_FRACTION] = episodes_str score_str = "%0.*f" % (self.decimals, show['my_score']) - row[3] = show['my_score'] - row[5] = score_str - row[9] = self._get_color(show, row[8]) - row[15] = show['my_status'] + row[TreeConstants.MY_SCORE] = show['my_score'] + row[TreeConstants.MY_SCORE_STRING] = score_str + row[TreeConstants.COLOR] = self._get_color(show, row[TreeConstants.AVAILABLE_EPS]) + row[TreeConstants.MY_STATUS] = show['my_status'] return - # print("Warning: Show ID not found in ShowView (%d)" % show['id']) - def update_title(self, show, altname=None): for row in self: - if int(row[0]) == show['id']: + if int(row[TreeConstants.SHOW_ID]) == show['id']: if altname: title_str = "%s [%s]" % (show['title'], altname) else: title_str = show['title'] - row[1] = title_str + row[TreeConstants.SHOW_ID] = title_str return def remove(self, show=None, show_id=None): for row in self: - if int(row[0]) == (show['id'] if show is not None else show_id): + if int(row[TreeConstants.SHOW_ID]) == (show['id'] if show is not None else show_id): Gtk.ListStore.remove(self, row.iter) return def playing(self, show, is_playing): # Change the color if the show is currently playing for row in self: - if int(row[0]) == show['id']: + if int(row[TreeConstants.SHOW_ID]) == show['id']: if is_playing: - row[9] = self.colors['is_playing'] + row[TreeConstants.COLOR] = self.colors['is_playing'] else: - row[9] = self._get_color(show, row[8]) + row[TreeConstants.COLOR] = self._get_color(show, row[TreeConstants.AVAILABLE_EPS]) return @@ -190,7 +224,7 @@ def __init__(self, status=None, *args, **kwargs): self._status = status def status_filter(self, model, iterator, data): - return self._status is None or model[iterator][15] == self._status + return self._status is None or model[iterator][TreeConstants.MY_STATUS] == self._status def get_value(self, obj, key='id'): try: @@ -207,102 +241,132 @@ class ShowTreeView(Gtk.TreeView): __gsignals__ = {'column-toggled': (GObject.SignalFlags.RUN_LAST, GObject.TYPE_PYOBJECT, (GObject.TYPE_STRING, GObject.TYPE_BOOLEAN))} - def __init__(self, colors, visible_columns, progress_style=1): + def __init__(self, colors, visible_columns, status, _list, progress_style=1): + # Sets up the tree Gtk.TreeView.__init__(self) self.colors = colors self.visible_columns = visible_columns self.progress_style = progress_style + self.status = status + self._list = _list + + self.set_model( + Gtk.TreeModelSort( + model=ShowListFilter( + status=self.status, + child_model=self._list + ) + ) + ) self.set_enable_search(True) - self.set_search_column(1) + self.set_search_column(TreeConstants.TITLE) self.set_property('has-tooltip', True) self.connect('query-tooltip', self.show_tooltip) + self.previous_sort_column = 'Title' # Sets the default "0th" previous column for sorting self.cols = dict() + + # Defines the default column order as well. If the default visible columns are renamed or otherwise changed, + # _default_column_reset() in mainview.py should be changed to accommodate the new names. self.available_columns = ( - ('Title', 1), - ('Progress', 2), - ('Score', 3), - ('Percent', 10), - ('Start', 11), - ('End', 12), - ('My start', 13), - ('My end', 14), + ('Title', TreeConstants.TITLE), + ('Watched', TreeConstants.MY_PROGRESS), + ('Score', TreeConstants.MY_SCORE), + ('Next episode', TreeConstants.NEXT_EPISODE_AIR_TIME_RELATIVE), + ('Start', TreeConstants.START_DATE), + ('End', TreeConstants.END_DATE), + ('My start', TreeConstants.MY_START_DATE), + ('My end', TreeConstants.MY_FINISH_DATE), + ('Progress', TreeConstants.PROGRESS_PERCENTAGE), ) - for (name, sort) in self.available_columns: - self.cols[name] = Gtk.TreeViewColumn(name) - self.cols[name].set_sort_column_id(sort) + # Creates pre-defined columns + for (name, key) in self.available_columns: + self.cols[name] = Gtk.TreeViewColumn() + self.cols[name].set_clickable(True) + self.cols[name].connect("clicked", lambda _, column_key=key, column=self.cols[name]: + self._on_column_clicked(column_key, column)) + self.cols[name].set_alignment(0.5) + self.cols[name].set_title(name) + + # Set up the percent / progress bar + if name == 'Progress': + if self.progress_style == 0: + renderer = Gtk.CellRendererProgress() + self.cols[name].pack_start(renderer, False) + self.cols[name].add_attribute(renderer, 'value', TreeConstants.PROGRESS_PERCENTAGE) + else: + renderer = ProgressCellRenderer(self.colors) + self.cols[name].pack_start(renderer, False) + self.cols[name].add_attribute(renderer, 'value', TreeConstants.MY_PROGRESS) + self.cols[name].add_attribute(renderer, 'total', TreeConstants.TOTAL_EPS) + self.cols[name].add_attribute(renderer, 'subvalue', TreeConstants.AIRED_EPS) + self.cols[name].add_attribute(renderer, 'eps', TreeConstants.AVAILABLE_EPS) + else: + renderer = Gtk.CellRendererText() + self.cols[name].pack_start(renderer, False) + renderer.set_alignment(0.5, 0.5) + + if name not in self.visible_columns: + self.cols[name].set_visible(False) + + # Populate columns + match name: + case 'Title': + self.cols[name].set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE) + self.cols[name].set_resizable(True) + self.cols[name].set_expand(True) + self.cols[name].add_attribute(renderer, 'text', TreeConstants.TITLE) + self.cols[name].add_attribute(renderer, 'foreground', TreeConstants.COLOR) + renderer.set_property('ellipsize', Pango.EllipsizeMode.END) + self.cols[name].set_alignment(0) + renderer.set_alignment(0, 0.5) + self.cols[name].set_sort_indicator(True) + + case 'Watched': + self.cols[name].add_attribute(renderer, 'text', TreeConstants.WATCHED_EPISODES_FRACTION) + + case 'Progress': + self.cols[name].set_min_width(200) + + case 'Score': + self.cols[name].add_attribute(renderer, 'text', TreeConstants.MY_SCORE_STRING) + + case 'Start': + self.cols[name].add_attribute(renderer, 'text', TreeConstants.START_DATE) + + case 'End': + self.cols[name].add_attribute(renderer, 'text', TreeConstants.END_DATE) + + case 'My start': + self.cols[name].add_attribute(renderer, 'text', TreeConstants.MY_START_DATE) + + case 'My end': + self.cols[name].add_attribute(renderer, 'text', TreeConstants.MY_FINISH_DATE) + + case 'Next episode': + self.cols[name].add_attribute(renderer, 'text', TreeConstants.NEXT_EPISODE_AIR_TIME_RELATIVE) + self.get_model().set_sort_func(sort_column_id=TreeConstants.NEXT_EPISODE_AIR_TIME_RELATIVE, + sort_func=self._next_episode_sort_func, user_data=self) + + case _: + pass # This is a hack to allow for right-clickable header label = Gtk.Label(name) label.show() self.cols[name].set_widget(label) - - self.append_column(self.cols[name]) - w = self.cols[name].get_widget() while not isinstance(w, Gtk.Button): w = w.get_parent() + w.connect('button-press-event', self._header_right_click) - w.connect('button-press-event', self._header_button_press) - - if name not in self.visible_columns: - self.cols[name].set_visible(False) + # Appends populated columns + self.append_column(self.cols[name]) - # renderer_id = Gtk.CellRendererText() - # self.cols['ID'].pack_start(renderer_id, False, True, 0) - # self.cols['ID'].set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE) - # self.cols['ID'].set_expand(False) - # self.cols['ID'].add_attribute(renderer_id, 'text', 0) - - renderer_title = Gtk.CellRendererText() - self.cols['Title'].pack_start(renderer_title, False) - self.cols['Title'].set_resizable(True) - self.cols['Title'].set_sizing(Gtk.TreeViewColumnSizing.FIXED) - self.cols['Title'].set_expand(True) - self.cols['Title'].add_attribute(renderer_title, 'text', 1) - # Using foreground-gdk does not work, possibly due to the timing of it being set - self.cols['Title'].add_attribute(renderer_title, 'foreground', 9) - renderer_title.set_property('ellipsize', Pango.EllipsizeMode.END) - - renderer_progress = Gtk.CellRendererText() - self.cols['Progress'].pack_start(renderer_progress, False) - self.cols['Progress'].add_attribute(renderer_progress, 'text', 4) - self.cols['Progress'].set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE) - self.cols['Progress'].set_expand(False) - - if self.progress_style == 0: - renderer_percent = Gtk.CellRendererProgress() - self.cols['Percent'].pack_start(renderer_percent, False) - self.cols['Percent'].add_attribute(renderer_percent, 'value', 10) - else: - renderer_percent = ProgressCellRenderer(self.colors) - self.cols['Percent'].pack_start(renderer_percent, False) - self.cols['Percent'].add_attribute(renderer_percent, 'value', 2) - self.cols['Percent'].add_attribute(renderer_percent, 'total', 6) - self.cols['Percent'].add_attribute(renderer_percent, 'subvalue', 7) - self.cols['Percent'].add_attribute(renderer_percent, 'eps', 8) - renderer_percent.set_fixed_size(100, -1) - - renderer = Gtk.CellRendererText() - self.cols['Score'].pack_start(renderer, False) - self.cols['Score'].add_attribute(renderer, 'text', 5) - renderer = Gtk.CellRendererText() - self.cols['Start'].pack_start(renderer, False) - self.cols['Start'].add_attribute(renderer, 'text', 11) - renderer = Gtk.CellRendererText() - self.cols['End'].pack_start(renderer, False) - self.cols['End'].add_attribute(renderer, 'text', 12) - renderer = Gtk.CellRendererText() - self.cols['My start'].pack_start(renderer, False) - self.cols['My start'].add_attribute(renderer, 'text', 13) - renderer = Gtk.CellRendererText() - self.cols['My end'].pack_start(renderer, False) - self.cols['My end'].add_attribute(renderer, 'text', 14) - - def _header_button_press(self, button, event): + def _header_right_click(self, button, event): if event.button == 3: menu = Gtk.Menu() for name, sort in self.available_columns: @@ -320,6 +384,112 @@ def _header_button_press(self, button, event): return False + @staticmethod + def _next_episode_sort_func(model, iter1, iter2, user_data) -> int: + """Time based sort function for the "Next episode" column. Always sorts "-" and "?" below everything.""" + + # Get the values from the "Next episode" column for the two rows + value1 = model.get_value(iter1, TreeConstants.NEXT_EPISODE_AIR_TIME_RELATIVE) + value2 = model.get_value(iter2, TreeConstants.NEXT_EPISODE_AIR_TIME_RELATIVE) + + sort_id, sort_order = user_data.get_model().get_sort_column_id() # Get the current sort order of the model + + special_cases = ('-', '?') + if value1 in special_cases: + return 1 if sort_order == Gtk.SortType.ASCENDING else -1 + elif value2 in special_cases: + return -1 if sort_order == Gtk.SortType.ASCENDING else 1 + + # Parse the time intervals, convert everything to minutes and sort accordingly + days1, hours1, minutes1 = utils.parse_time_interval(value1) + days2, hours2, minutes2 = utils.parse_time_interval(value2) + + total_minutes1 = days1 * 24 * 60 + hours1 * 60 + minutes1 + total_minutes2 = days2 * 24 * 60 + hours2 * 60 + minutes2 + + if total_minutes1 < total_minutes2: + return -1 + elif total_minutes1 == total_minutes2: + return 0 + else: + return 1 + + def _on_column_clicked(self, key, column): + # Todo: "Title sort" should sort shows with available episodes first -> custom sort function needed + """Sets up sorting for the clicked column, based on what the previous sorting was. + We can't simply use self.cols[name].set_sort_order_column_id(key) because that inevitably allocates screen + space for the sort indicator arrow. This way, the sort indicator is only visible (and takes up space) for the + currently sorted column. + + In order to make the list look uniform while keeping the default sort direction logical, + e.g: highest score first, we need to dynamically change the sort indicator directions. + + We're saving the sorted column because it's not possible to get the column purely from the ID without + iterating through the columns, so this is more efficient""" + + sort_column_id_model, sort_order_model = self.get_model().get_sort_column_id() + sort_column_id_model = sort_column_id_model if sort_column_id_model is not None else TreeConstants.TITLE + sort_order_model = sort_order_model if sort_order_model is not None else Gtk.SortType.ASCENDING + # Sort order and ID passed by the model. Order and ID default to none and correspond to the previously sorted + # column ID, NOT the clicked column. Set sort_column_id_model to 'TreeConstants.TITLE', + # as that's the default sort column. + + column_title = column.get_title() # Gets the clicked column title + sort_order_column = column.get_sort_order() # Sort order passed by the column (indicator direction) + + if sort_column_id_model == key: # Check if we're trying to sort the same column that's already being sorted + self._reverse_sort_order(column, sort_order_model, sort_order_column, key) # Reverse sorting + + else: # We're trying to sort a new column + match column_title: # Check which column and set the sort indicator accordingly + case 'Score': # Default should be large -> small, e.g: 10, 8, 7, 3 + column.set_sort_order(Gtk.SortType.ASCENDING) + self.get_model().set_sort_column_id(key, Gtk.SortType.DESCENDING) + + case 'Watched': # Default should be large -> small, e.g: 18 / 23, 12 / 13, 7 / 13 + column.set_sort_order(Gtk.SortType.ASCENDING) + self.get_model().set_sort_column_id(key, Gtk.SortType.DESCENDING) + + case 'Progress': # Default should be large -> small + column.set_sort_order(Gtk.SortType.ASCENDING) + self.get_model().set_sort_column_id(key, Gtk.SortType.DESCENDING) + + case _: # Everything else should be normal + column.set_sort_order(Gtk.SortType.ASCENDING) + self.get_model().set_sort_column_id(key, Gtk.SortType.ASCENDING) + + self.cols[self.previous_sort_column].set_sort_indicator(False) # Disable the previous sort indicator + column.set_sort_indicator(True) # Enable the new sort indicator + self.previous_sort_column = column_title # Save the new sorting column + + def _reverse_sort_order(self, column, sort_order_model, sort_order_column, key): + """Reverses both the actual sort order and the visual sorting indicator arrow direction""" + + if sort_order_model == Gtk.SortType.ASCENDING: + self._reverse_sort_indicator(column, sort_order_column) + self.get_model().set_sort_column_id(key, Gtk.SortType.DESCENDING) + + elif sort_order_model == Gtk.SortType.DESCENDING: + self._reverse_sort_indicator(column, sort_order_column) + self.get_model().set_sort_column_id(key, Gtk.SortType.ASCENDING) + + else: + raise ValueError("Invalid sort order. Must be Gtk.SortType.ASCENDING or Gtk.SortType.DESCENDING") + + @staticmethod + def _reverse_sort_indicator(column, sort_order_column): + """Reverses the visual sorting indicator direction""" + if sort_order_column == Gtk.SortType.ASCENDING: + column.set_sort_order(Gtk.SortType.DESCENDING) + + elif sort_order_column == Gtk.SortType.DESCENDING: + column.set_sort_order(Gtk.SortType.ASCENDING) + + else: + raise ValueError("Invalid sort order. Must be Gtk.SortType.ASCENDING or Gtk.SortType.DESCENDING") + + + @property def filter(self): return self.props.model.props.model @@ -331,26 +501,25 @@ def show_tooltip(self, view, x, y, kbd, tip): return False _, col, _, _ = view.get_path_at_pos(tx, ty) - if col != self.cols['Percent']: + if col != self.cols['Progress']: return False def gv(key): return model.get_value(tree_iter, ShowListStore.column(key)) - lines = [] - lines.append("Watched: %d" % gv('stat')) + lines = ["Watched: %d" % gv(TreeConstants.MY_PROGRESS)] - aired = gv('subvalue') - status = gv('status') + aired = gv(TreeConstants.AIRED_EPS) + status = gv(TreeConstants.SHOW_STATUS) if aired and not status == utils.Status.NOTYET: lines.append("Aired%s: %d" % ( ' (estimated)' if status == utils.Status.AIRING else '', aired)) - avail_eps = gv('avail-eps') + avail_eps = gv(TreeConstants.AVAILABLE_EPS) if len(avail_eps) > 0: lines.append("Available: %d" % max(avail_eps)) - lines.append("Total: %s" % (gv('total-eps') or '?')) + lines.append("Total: %s" % (gv(TreeConstants.TOTAL_EPS) or '?')) tip.set_markup('\n'.join(lines)) renderer = next(iter(col.get_cells())) @@ -363,7 +532,7 @@ def _header_menu_item(self, w, column_name, visible): def select(self, show): """Select specified row or first if not found""" for row in self.get_model(): - if int(row[0]) == show['id']: + if int(row[TreeConstants.SHOW_ID]) == show['id']: selection = self.get_selection() selection.select_iter(row.iter) return @@ -419,7 +588,7 @@ def do_get_property(self, pspec): return getattr(self, pspec.name) def do_render(self, cr, widget, background_area, cell_area, flags): - (x, y, w, h) = self.do_get_size(widget, cell_area) + (x, y, w, h) = self._do_get_size(widget, cell_area) # set_source_rgb(0.9, 0.9, 0.9) cr.set_source_rgb(*self.__get_color(self.colors['progress_bg'])) @@ -438,7 +607,7 @@ def do_render(self, cr, widget, background_area, cell_area, flags): # set_source_rgb(0.7, 0.7, 0.7) cr.set_source_rgb( *self.__get_color(self.colors['progress_sub_bg'])) - cr.rectangle(x, y+h-self._subheight, mid, h-(h-self._subheight)) + cr.rectangle(x, y + h - self._subheight, mid, h - (h - self._subheight)) cr.fill() if self.value: @@ -463,11 +632,12 @@ def do_render(self, cr, widget, background_area, cell_area, flags): if 0 < episode <= self.total: start = int(w / float(self.total) * (episode - 1)) finish = int(w / float(self.total) * episode) - cr.rectangle(x+start, y+h-self._subheight, - finish-start, h-(h-self._subheight)) + cr.rectangle(x + start, y + h - self._subheight, + finish - start, h - (h - self._subheight)) cr.fill() - def do_get_size(self, widget, cell_area): + @classmethod + def _do_get_size(cls, widget, cell_area): if cell_area is None: return 0, 0, 0, 0 x = cell_area.x diff --git a/trackma/ui/qt/models.py b/trackma/ui/qt/models.py index f5610451..a1640ba7 100644 --- a/trackma/ui/qt/models.py +++ b/trackma/ui/qt/models.py @@ -83,13 +83,13 @@ def _calculate_color(self, row, show): del self.colors[row] def _calculate_next_ep(self, row, show): - if self.mediainfo.get('date_next_ep'): - if 'next_ep_time' in show: - delta = show['next_ep_time'] - datetime.datetime.utcnow() - self.next_ep[row] = "%i days, %02d hrs." % ( - delta.days, delta.seconds/3600) - elif row in self.next_ep: - del self.next_ep[row] + if self.mediainfo.get('date_next_ep') and show['next_ep_time'] is not None: + delta = show['next_ep_time'].replace(tzinfo=datetime.timezone.utc) - \ + datetime.datetime.now(tz=datetime.timezone.utc) # 'next_ep_time has to be UTC' by definition + self.next_ep[row] = "%i days, %02d hrs." % ( + delta.days, delta.seconds / 3600) + elif row in self.next_ep: + del self.next_ep[row] def _calculate_eps(self, row, show): aired_eps = utils.estimate_aired_episodes(show) @@ -138,7 +138,7 @@ def update(self, showid, is_playing=None): self._calculate_color(row, show) self.dataChanged.emit(self.index( - row, 0), self.index(row, len(self.columns)-1)) + row, 0), self.index(row, len(self.columns) - 1)) def rowCount(self, parent): if self.showlist: @@ -185,8 +185,8 @@ def data(self, index, role): if show['total']: total = show['total'] else: - total = (int(show['my_progress']/12)+1) * \ - 12 # Round up to the next cour + total = (int(show['my_progress'] / 12) + 1) * \ + 12 # Round up to the next cour if row in self.eps: return (show['my_progress'], total, self.eps[row][0], self.eps[row][1]) @@ -395,7 +395,8 @@ def setFilterColumns(self, columns): self.invalidateFilter() def filterAcceptsRow(self, source_row, source_parent): - if self.filter_status is not None and self.sourceModel().showlist[source_row]['my_status'] != self.filter_status: + if self.filter_status is not None and self.sourceModel().showlist[source_row]['my_status'] \ + != self.filter_status: return False if self.filter_columns: diff --git a/trackma/utils.py b/trackma/utils.py index c4ddd309..45e4d160 100644 --- a/trackma/utils.py +++ b/trackma/utils.py @@ -510,9 +510,77 @@ def show(): 'image': '', 'image_thumb': '', 'queued': False, + 'next_ep_time': None # Must be a time-zone aware datetime object in UTC } +def calculate_relative_time(time_end: datetime, utc: bool, fulltime: bool = True) -> str: + """Function that calculates the relative time between 2 datetime objects. + If full=False, it returns only the greatest nonzero time unit""" + + if time_end: + try: + if utc: + time_end = time_end.replace(tzinfo=datetime.timezone.utc) + # Make sure that time_end is time-zone aware in UTC + current_time = datetime.datetime.now(tz=datetime.timezone.utc) + else: + current_time = datetime.datetime.now() + time_difference = time_end - current_time + days = time_difference.days + hours, remainder = divmod(time_difference.seconds, 3600) + minutes, seconds = divmod(remainder, 60) + + time_units = [("days", days), ("hours", hours), ("minutes", minutes)] + + # Filter out units with a value of 0 + non_zero_units = [(unit, value) for unit, value in time_units if value != 0] + + if fulltime: + result = ", ".join([ + f"in {value + (1 if remainder > 0 else 0)} " + f"{unit if value + (1 if remainder > 0 else 0) != 1 else unit[:-1]}" + for unit, value in non_zero_units + ]) + else: + # Display only the greatest non-zero unit + result = next( + ( + f"in {value + (1 if remainder > 0 else 0)} " + f"{unit if value + (1 if remainder > 0 else 0) != 1 else unit[:-1]}" + for unit, value in non_zero_units + ), + "error" + ) + return result + except ValueError: + return '?' + else: + return '-' + + +def parse_time_interval(value): + """Parse the time interval string and return it as a tuple (days, hours, minutes)""" + + pattern = ( + r'in\s*' + r'(?:(\d+) day(?:s)?)?' + r'(?:,\s*)?' + r'(?:(\d+) hour(?:s)?)?' + r'(?:,\s*)?' + r'(?:(\d+) minute(?:s)?)?' + r'|[-?]' + ) + + match = re.match(pattern, value) + if match: + days = int(match.group(1) or 0) + hours = int(match.group(2) or 0) + minutes = int(match.group(3) or 0) + return days, hours, minutes + return 0, 0, 0 # Return a default value if the format is not matched + + class TrackmaError(Exception): pass @@ -663,9 +731,9 @@ class APIFatal(TrackmaFatal): 'start_in_tray': False, 'tray_api_icon': False, 'remember_geometry': False, - 'last_width': 740, - 'last_height': 480, - 'visible_columns': ['Title', 'Progress', 'Score', 'Percent'], + 'last_width': 1080, + 'last_height': 720, + 'visible_columns': ['Title', 'Watched', 'Score', 'Progress', 'Next episode'], 'episodebar_style': 1, 'colors': { 'is_airing': '#0099CC', @@ -679,6 +747,7 @@ class APIFatal(TrackmaFatal): 'progress_sub_fg': '#668099', 'progress_complete': '#99CCB3', }, + 'column_version': ['1.1'] } qt_defaults = { @@ -691,8 +760,8 @@ class APIFatal(TrackmaFatal): 'remember_columns': False, 'last_x': 0, 'last_y': 0, - 'last_width': 740, - 'last_height': 480, + 'last_width': 1080, + 'last_height': 720, 'visible_columns': ['Title', 'Progress', 'Score', 'Percent'], 'inline_edit': True, 'columns_state': None,