From 52d8956034cfd5d82e07b0124db7a2a80bd640dc Mon Sep 17 00:00:00 2001 From: Mohamed Zumair Date: Mon, 14 Oct 2024 05:58:23 +0530 Subject: [PATCH] Add ScrapeOps monitoring, Add zilean filtered endpoint, Fix Prowlarr scraping with download link & Fix PikPak login error etc (#319) * Refactor ZileanScraper to use parallel requests for searching and filtering streams with new endpoints * Fix DLHD scraping & enable DLHD without MediaFlow * Switch to httpx for async HTTP requests * Implement caching for PikPak token to reduce login error * Refactor torrent cleanup logic * Add inactivity monitor extension to close idle spiders Introduced `InactivityMonitor` to automatically close spiders that remain inactive for a specified time. This extension checks activity at regular intervals and uses configurable settings for check intervals and inactivity timeouts. If no items are scraped within the timeout period, the spider is closed to free resources. * Handle TypeError in dynamic sorting of streams * Refactor torrent info scraper to support pre-processing. Introduced a pre-processing function mapping to handle specific indexer requirements before parsing the HTML. Added a custom pre-processing function for "TheRARBG" to handle URL adjustments, improving modularity and readability in the `get_torrent_info` function. * Refine error logging and fix hash key in spider * Fix Prowlarr not stop on max process limit * #315: Integrate ScrapeOps logging into all scrapers & spiders Added ScrapeOps logging to Zilean, Torrentio, Prowlarr, and Prowlarr Feed scrapers to enhance request tracking and error handling. Configured ScrapeOps API key in settings and updated Pipfile/Pipfile.lock with scrapeops-python-requests and scrapeops-scrapy dependencies. * Refactor scraper cache status handler * verify torrent before parsing on prowlarr & prioritize magnet on badass_torrents * Add support for provide locally hosted mediaflow proxy public address and reduce the time leg on private ip address checking * handle RD exception cases * do not setup scrapeops when api key is none * Enhanced dynamic sorting of torrent streams Revised the dynamic_sort_key function to handle different key types more efficiently with match-case. Simplified error handling and improved logging to capture sorting data in the case of exceptions. * add missing last update date for metadata * update domain for nowmesports --- Pipfile | 2 + Pipfile.lock | 1169 ++++++++++------- api/scheduler.py | 13 +- db/config.py | 11 +- db/models.py | 1 + db/schemas.py | 1 + deployment/docker-compose/docker-compose.yml | 4 +- deployment/k8s/local-deployment.yaml | 4 +- kodi/plugin.video.mediafusion/addon.xml | 2 +- mediafusion_scrapy/extensions.py | 63 + .../live_stream_resolver_pipeline.py | 6 +- .../pipelines/sport_video_parser_pipeline.py | 2 +- mediafusion_scrapy/settings.py | 26 +- mediafusion_scrapy/spiders/dlhd.py | 27 +- mediafusion_scrapy/spiders/tgx.py | 2 +- resources/exceptions/content_infringing.mp4 | Bin 0 -> 100260 bytes resources/exceptions/ip_not_allowed.mp4 | Bin 0 -> 68203 bytes resources/exceptions/too_many_requests.mp4 | Bin 0 -> 87705 bytes resources/html/configure.html | 10 +- resources/js/config_script.js | 1 + resources/json/scraper_config.json | 7 +- resources/manifest.json | 2 +- scrapers/base_scraper.py | 36 +- scrapers/prowlarr.py | 78 +- scrapers/prowlarr_feed.py | 47 +- scrapers/torrent_info.py | 18 +- scrapers/torrentio.py | 35 +- scrapers/utils.py | 24 + scrapers/zilean.py | 98 +- streaming_providers/pikpak/utils.py | 34 + streaming_providers/realdebrid/client.py | 27 +- utils/cleanup_torrents.py | 149 ++- utils/crypto.py | 43 +- utils/network.py | 26 +- utils/parser.py | 82 +- utils/runtime_const.py | 12 + utils/torrent.py | 2 +- utils/validation_helper.py | 62 +- 38 files changed, 1394 insertions(+), 732 deletions(-) create mode 100644 mediafusion_scrapy/extensions.py create mode 100644 resources/exceptions/content_infringing.mp4 create mode 100644 resources/exceptions/ip_not_allowed.mp4 create mode 100644 resources/exceptions/too_many_requests.mp4 diff --git a/Pipfile b/Pipfile index a7b4ffe..e944e45 100644 --- a/Pipfile +++ b/Pipfile @@ -47,6 +47,8 @@ parsett = "*" tenacity = "*" ratelimit = "*" qrcode = "*" +scrapeops-scrapy = "*" +scrapeops-python-requests = "*" [dev-packages] pysocks = "*" diff --git a/Pipfile.lock b/Pipfile.lock index b613e13..65c223b 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "1cd404d7a48decb6239cae9c230197eb0437451f506b80cf25c4a745092c726f" + "sha256": "247ef524a84735405140690139bd5d6a2cf3e662163deaaa874cc2f3dc42ad8e" }, "pipfile-spec": 6, "requires": { @@ -41,100 +41,100 @@ }, "aiohttp": { "hashes": [ - "sha256:02d1d6610588bcd743fae827bd6f2e47e0d09b346f230824b4c6fb85c6065f9c", - "sha256:03690541e4cc866eef79626cfa1ef4dd729c5c1408600c8cb9e12e1137eed6ab", - "sha256:0bc059ecbce835630e635879f5f480a742e130d9821fbe3d2f76610a6698ee25", - "sha256:0c21c82df33b264216abffff9f8370f303dab65d8eee3767efbbd2734363f677", - "sha256:1298b854fd31d0567cbb916091be9d3278168064fca88e70b8468875ef9ff7e7", - "sha256:1321658f12b6caffafdc35cfba6c882cb014af86bef4e78c125e7e794dfb927b", - "sha256:143b0026a9dab07a05ad2dd9e46aa859bffdd6348ddc5967b42161168c24f857", - "sha256:16e6a51d8bc96b77f04a6764b4ad03eeef43baa32014fce71e882bd71302c7e4", - "sha256:172ad884bb61ad31ed7beed8be776eb17e7fb423f1c1be836d5cb357a096bf12", - "sha256:17c272cfe7b07a5bb0c6ad3f234e0c336fb53f3bf17840f66bd77b5815ab3d16", - "sha256:1a0ee6c0d590c917f1b9629371fce5f3d3f22c317aa96fbdcce3260754d7ea21", - "sha256:2746d8994ebca1bdc55a1e998feff4e94222da709623bb18f6e5cfec8ec01baf", - "sha256:2914caa46054f3b5ff910468d686742ff8cff54b8a67319d75f5d5945fd0a13d", - "sha256:2bbf94d4a0447705b7775417ca8bb8086cc5482023a6e17cdc8f96d0b1b5aba6", - "sha256:2bd9f3eac515c16c4360a6a00c38119333901b8590fe93c3257a9b536026594d", - "sha256:2c33fa6e10bb7ed262e3ff03cc69d52869514f16558db0626a7c5c61dde3c29f", - "sha256:2d37f4718002863b82c6f391c8efd4d3a817da37030a29e2682a94d2716209de", - "sha256:3668d0c2a4d23fb136a753eba42caa2c0abbd3d9c5c87ee150a716a16c6deec1", - "sha256:36d4fba838be5f083f5490ddd281813b44d69685db910907636bc5dca6322316", - "sha256:40ff5b7660f903dc587ed36ef08a88d46840182d9d4b5694e7607877ced698a1", - "sha256:42775de0ca04f90c10c5c46291535ec08e9bcc4756f1b48f02a0657febe89b10", - "sha256:482c85cf3d429844396d939b22bc2a03849cb9ad33344689ad1c85697bcba33a", - "sha256:4e6cb75f8ddd9c2132d00bc03c9716add57f4beff1263463724f6398b813e7eb", - "sha256:4edc3fd701e2b9a0d605a7b23d3de4ad23137d23fc0dbab726aa71d92f11aaaf", - "sha256:4fd16b30567c5b8e167923be6e027eeae0f20cf2b8a26b98a25115f28ad48ee0", - "sha256:5002a02c17fcfd796d20bac719981d2fca9c006aac0797eb8f430a58e9d12431", - "sha256:51d0a4901b27272ae54e42067bc4b9a90e619a690b4dc43ea5950eb3070afc32", - "sha256:558b3d223fd631ad134d89adea876e7fdb4c93c849ef195049c063ada82b7d08", - "sha256:5c070430fda1a550a1c3a4c2d7281d3b8cfc0c6715f616e40e3332201a253067", - "sha256:5f392ef50e22c31fa49b5a46af7f983fa3f118f3eccb8522063bee8bfa6755f8", - "sha256:60555211a006d26e1a389222e3fab8cd379f28e0fbf7472ee55b16c6c529e3a6", - "sha256:608cecd8d58d285bfd52dbca5b6251ca8d6ea567022c8a0eaae03c2589cd9af9", - "sha256:60ad5b8a7452c0f5645c73d4dad7490afd6119d453d302cd5b72b678a85d6044", - "sha256:63649309da83277f06a15bbdc2a54fbe75efb92caa2c25bb57ca37762789c746", - "sha256:6ebdc3b3714afe1b134b3bbeb5f745eed3ecbcff92ab25d80e4ef299e83a5465", - "sha256:6f3c6648aa123bcd73d6f26607d59967b607b0da8ffcc27d418a4b59f4c98c7c", - "sha256:7003f33f5f7da1eb02f0446b0f8d2ccf57d253ca6c2e7a5732d25889da82b517", - "sha256:776e9f3c9b377fcf097c4a04b241b15691e6662d850168642ff976780609303c", - "sha256:85711eec2d875cd88c7eb40e734c4ca6d9ae477d6f26bd2b5bb4f7f60e41b156", - "sha256:87d1e4185c5d7187684d41ebb50c9aeaaaa06ca1875f4c57593071b0409d2444", - "sha256:8a3f063b41cc06e8d0b3fcbbfc9c05b7420f41287e0cd4f75ce0a1f3d80729e6", - "sha256:8b3fb28a9ac8f2558760d8e637dbf27aef1e8b7f1d221e8669a1074d1a266bb2", - "sha256:8bd9125dd0cc8ebd84bff2be64b10fdba7dc6fd7be431b5eaf67723557de3a31", - "sha256:8be1a65487bdfc285bd5e9baf3208c2132ca92a9b4020e9f27df1b16fab998a9", - "sha256:8cc0d13b4e3b1362d424ce3f4e8c79e1f7247a00d792823ffd640878abf28e56", - "sha256:8d9d10d10ec27c0d46ddaecc3c5598c4db9ce4e6398ca872cdde0525765caa2f", - "sha256:8debb45545ad95b58cc16c3c1cc19ad82cffcb106db12b437885dbee265f0ab5", - "sha256:91aa966858593f64c8a65cdefa3d6dc8fe3c2768b159da84c1ddbbb2c01ab4ef", - "sha256:9331dd34145ff105177855017920dde140b447049cd62bb589de320fd6ddd582", - "sha256:99f9678bf0e2b1b695e8028fedac24ab6770937932eda695815d5a6618c37e04", - "sha256:9fdf5c839bf95fc67be5794c780419edb0dbef776edcfc6c2e5e2ffd5ee755fa", - "sha256:a14e4b672c257a6b94fe934ee62666bacbc8e45b7876f9dd9502d0f0fe69db16", - "sha256:a19caae0d670771ea7854ca30df76f676eb47e0fd9b2ee4392d44708f272122d", - "sha256:a35ed3d03910785f7d9d6f5381f0c24002b2b888b298e6f941b2fc94c5055fcd", - "sha256:a61df62966ce6507aafab24e124e0c3a1cfbe23c59732987fc0fd0d71daa0b88", - "sha256:a6e00c8a92e7663ed2be6fcc08a2997ff06ce73c8080cd0df10cc0321a3168d7", - "sha256:ac3196952c673822ebed8871cf8802e17254fff2a2ed4835d9c045d9b88c5ec7", - "sha256:ac74e794e3aee92ae8f571bfeaa103a141e409863a100ab63a253b1c53b707eb", - "sha256:ad3675c126f2a95bde637d162f8231cff6bc0bc9fbe31bd78075f9ff7921e322", - "sha256:aeebd3061f6f1747c011e1d0b0b5f04f9f54ad1a2ca183e687e7277bef2e0da2", - "sha256:ba1a599255ad6a41022e261e31bc2f6f9355a419575b391f9655c4d9e5df5ff5", - "sha256:bbdb8def5268f3f9cd753a265756f49228a20ed14a480d151df727808b4531dd", - "sha256:c2555e4949c8d8782f18ef20e9d39730d2656e218a6f1a21a4c4c0b56546a02e", - "sha256:c2695c61cf53a5d4345a43d689f37fc0f6d3a2dc520660aec27ec0f06288d1f9", - "sha256:c2b627d3c8982691b06d89d31093cee158c30629fdfebe705a91814d49b554f8", - "sha256:c46131c6112b534b178d4e002abe450a0a29840b61413ac25243f1291613806a", - "sha256:c54dc329cd44f7f7883a9f4baaefe686e8b9662e2c6c184ea15cceee587d8d69", - "sha256:c7d7cafc11d70fdd8801abfc2ff276744ae4cb39d8060b6b542c7e44e5f2cfc2", - "sha256:cb0b2d5d51f96b6cc19e6ab46a7b684be23240426ae951dcdac9639ab111b45e", - "sha256:d15a29424e96fad56dc2f3abed10a89c50c099f97d2416520c7a543e8fddf066", - "sha256:d1f5c9169e26db6a61276008582d945405b8316aae2bb198220466e68114a0f5", - "sha256:d271f770b52e32236d945911b2082f9318e90ff835d45224fa9e28374303f729", - "sha256:d646fdd74c25bbdd4a055414f0fe32896c400f38ffbdfc78c68e62812a9e0257", - "sha256:d6e395c3d1f773cf0651cd3559e25182eb0c03a2777b53b4575d8adc1149c6e9", - "sha256:d7c071235a47d407b0e93aa6262b49422dbe48d7d8566e1158fecc91043dd948", - "sha256:d97273a52d7f89a75b11ec386f786d3da7723d7efae3034b4dda79f6f093edc1", - "sha256:dcf354661f54e6a49193d0b5653a1b011ba856e0b7a76bda2c33e4c6892f34ea", - "sha256:e3e7fabedb3fe06933f47f1538df7b3a8d78e13d7167195f51ca47ee12690373", - "sha256:e525b69ee8a92c146ae5b4da9ecd15e518df4d40003b01b454ad694a27f498b5", - "sha256:e709d6ac598c5416f879bb1bae3fd751366120ac3fa235a01de763537385d036", - "sha256:e83dfefb4f7d285c2d6a07a22268344a97d61579b3e0dce482a5be0251d672ab", - "sha256:e86260b76786c28acf0b5fe31c8dca4c2add95098c709b11e8c35b424ebd4f5b", - "sha256:e883b61b75ca6efc2541fcd52a5c8ccfe288b24d97e20ac08fdf343b8ac672ea", - "sha256:f0a44bb40b6aaa4fb9a5c1ee07880570ecda2065433a96ccff409c9c20c1624a", - "sha256:f82ace0ec57c94aaf5b0e118d4366cff5889097412c75aa14b4fd5fc0c44ee3e", - "sha256:f9ca09414003c0e96a735daa1f071f7d7ed06962ef4fa29ceb6c80d06696d900", - "sha256:fa430b871220dc62572cef9c69b41e0d70fcb9d486a4a207a5de4c1f25d82593", - "sha256:fc262c3df78c8ff6020c782d9ce02e4bcffe4900ad71c0ecdad59943cba54442", - "sha256:fcd546782d03181b0b1d20b43d612429a90a68779659ba8045114b867971ab71", - "sha256:fd4ceeae2fb8cabdd1b71c82bfdd39662473d3433ec95b962200e9e752fb70d0", - "sha256:fec5fac7aea6c060f317f07494961236434928e6f4374e170ef50b3001e14581" + "sha256:007ec22fbc573e5eb2fb7dec4198ef8f6bf2fe4ce20020798b2eb5d0abda6138", + "sha256:00819de9e45d42584bed046314c40ea7e9aea95411b38971082cad449392b08c", + "sha256:01948b1d570f83ee7bbf5a60ea2375a89dfb09fd419170e7f5af029510033d24", + "sha256:038f514fe39e235e9fef6717fbf944057bfa24f9b3db9ee551a7ecf584b5b480", + "sha256:03a42ac7895406220124c88911ebee31ba8b2d24c98507f4a8bf826b2937c7f2", + "sha256:05646ebe6b94cc93407b3bf34b9eb26c20722384d068eb7339de802154d61bc5", + "sha256:0631dd7c9f0822cc61c88586ca76d5b5ada26538097d0f1df510b082bad3411a", + "sha256:0b00807e2605f16e1e198f33a53ce3c4523114059b0c09c337209ae55e3823a8", + "sha256:0e1b370d8007c4ae31ee6db7f9a2fe801a42b146cec80a86766e7ad5c4a259cf", + "sha256:15ecd889a709b0080f02721255b3f80bb261c2293d3c748151274dfea93ac871", + "sha256:1b66ccafef7336a1e1f0e389901f60c1d920102315a56df85e49552308fc0486", + "sha256:1bbb122c557a16fafc10354b9d99ebf2f2808a660d78202f10ba9d50786384b9", + "sha256:1eb89d3d29adaf533588f209768a9c02e44e4baf832b08118749c5fad191781d", + "sha256:258c5dd01afc10015866114e210fb7365f0d02d9d059c3c3415382ab633fcbcb", + "sha256:2609e9ab08474702cc67b7702dbb8a80e392c54613ebe80db7e8dbdb79837c68", + "sha256:274cfa632350225ce3fdeb318c23b4a10ec25c0e2c880eff951a3842cf358ac1", + "sha256:28529e08fde6f12eba8677f5a8608500ed33c086f974de68cc65ab218713a59d", + "sha256:2b606353da03edcc71130b52388d25f9a30a126e04caef1fd637e31683033abd", + "sha256:30ca7c3b94708a9d7ae76ff281b2f47d8eaf2579cd05971b5dc681db8caac6e1", + "sha256:333cf6cf8e65f6a1e06e9eb3e643a0c515bb850d470902274239fea02033e9a8", + "sha256:3455522392fb15ff549d92fbf4b73b559d5e43dc522588f7eb3e54c3f38beee7", + "sha256:362f641f9071e5f3ee6f8e7d37d5ed0d95aae656adf4ef578313ee585b585959", + "sha256:3bcd391d083f636c06a68715e69467963d1f9600f85ef556ea82e9ef25f043f7", + "sha256:3dffb610a30d643983aeb185ce134f97f290f8935f0abccdd32c77bed9388b42", + "sha256:3fe407bf93533a6fa82dece0e74dbcaaf5d684e5a51862887f9eaebe6372cd79", + "sha256:413251f6fcf552a33c981c4709a6bba37b12710982fec8e558ae944bfb2abd38", + "sha256:438cd072f75bb6612f2aca29f8bd7cdf6e35e8f160bc312e49fbecab77c99e3a", + "sha256:4470c73c12cd9109db8277287d11f9dd98f77fc54155fc71a7738a83ffcc8ea8", + "sha256:45c3b868724137f713a38376fef8120c166d1eadd50da1855c112fe97954aed8", + "sha256:486f7aabfa292719a2753c016cc3a8f8172965cabb3ea2e7f7436c7f5a22a151", + "sha256:4f05e9727ce409358baa615dbeb9b969db94324a79b5a5cea45d39bdb01d82e6", + "sha256:50aed5155f819873d23520919e16703fc8925e509abbb1a1491b0087d1cd969e", + "sha256:50edbcad60d8f0e3eccc68da67f37268b5144ecc34d59f27a02f9611c1d4eec7", + "sha256:54ca74df1be3c7ca1cf7f4c971c79c2daf48d9aa65dea1a662ae18926f5bc8ce", + "sha256:578a4b875af3e0daaf1ac6fa983d93e0bbfec3ead753b6d6f33d467100cdc67b", + "sha256:597a079284b7ee65ee102bc3a6ea226a37d2b96d0418cc9047490f231dc09fe8", + "sha256:59bb3c54aa420521dc4ce3cc2c3fe2ad82adf7b09403fa1f48ae45c0cbde6628", + "sha256:5c6a5b8c7926ba5d8545c7dd22961a107526562da31a7a32fa2456baf040939f", + "sha256:64f6c17757251e2b8d885d728b6433d9d970573586a78b78ba8929b0f41d045a", + "sha256:679abe5d3858b33c2cf74faec299fda60ea9de62916e8b67e625d65bf069a3b7", + "sha256:741a46d58677d8c733175d7e5aa618d277cd9d880301a380fd296975a9cdd7bc", + "sha256:7789050d9e5d0c309c706953e5e8876e38662d57d45f936902e176d19f1c58ab", + "sha256:77abf6665ae54000b98b3c742bc6ea1d1fb31c394bcabf8b5d2c1ac3ebfe7f3b", + "sha256:79019094f87c9fb44f8d769e41dbb664d6e8fcfd62f665ccce36762deaa0e911", + "sha256:7b06b7843929e41a94ea09eb1ce3927865387e3e23ebe108e0d0d09b08d25be9", + "sha256:7e338c0523d024fad378b376a79faff37fafb3c001872a618cde1d322400a572", + "sha256:7ea7ffc6d6d6f8a11e6f40091a1040995cdff02cfc9ba4c2f30a516cb2633554", + "sha256:8105fd8a890df77b76dd3054cddf01a879fc13e8af576805d667e0fa0224c35d", + "sha256:84afcdea18eda514c25bc68b9af2a2b1adea7c08899175a51fe7c4fb6d551257", + "sha256:9294bbb581f92770e6ed5c19559e1e99255e4ca604a22c5c6397b2f9dd3ee42c", + "sha256:93429602396f3383a797a2a70e5f1de5df8e35535d7806c9f91df06f297e109b", + "sha256:9627cc1a10c8c409b5822a92d57a77f383b554463d1884008e051c32ab1b3742", + "sha256:998f3bd3cfc95e9424a6acd7840cbdd39e45bc09ef87533c006f94ac47296090", + "sha256:9c72109213eb9d3874f7ac8c0c5fa90e072d678e117d9061c06e30c85b4cf0e6", + "sha256:9fc1500fd2a952c5c8e3b29aaf7e3cc6e27e9cfc0a8819b3bce48cc1b849e4cc", + "sha256:a3f00003de6eba42d6e94fabb4125600d6e484846dbf90ea8e48a800430cc142", + "sha256:a45d85cf20b5e0d0aa5a8dca27cce8eddef3292bc29d72dcad1641f4ed50aa16", + "sha256:a7d8d14fe962153fc681f6366bdec33d4356f98a3e3567782aac1b6e0e40109a", + "sha256:a8fa23fe62c436ccf23ff930149c047f060c7126eae3ccea005f0483f27b2e28", + "sha256:aa6658732517ddabe22c9036479eabce6036655ba87a0224c612e1ae6af2087e", + "sha256:aafc8ee9b742ce75044ae9a4d3e60e3d918d15a4c2e08a6c3c3e38fa59b92d94", + "sha256:ab5a5a0c7a7991d90446a198689c0535be89bbd6b410a1f9a66688f0880ec026", + "sha256:acd48d5b80ee80f9432a165c0ac8cbf9253eaddb6113269a5e18699b33958dbb", + "sha256:ad7593bb24b2ab09e65e8a1d385606f0f47c65b5a2ae6c551db67d6653e78c28", + "sha256:baa42524a82f75303f714108fea528ccacf0386af429b69fff141ffef1c534f9", + "sha256:bdfcf6443637c148c4e1a20c48c566aa694fa5e288d34b20fcdc58507882fed3", + "sha256:be7443669ae9c016b71f402e43208e13ddf00912f47f623ee5994e12fc7d4b3f", + "sha256:c02a30b904282777d872266b87b20ed8cc0d1501855e27f831320f471d54d983", + "sha256:c1277cd707c465cd09572a774559a3cc7c7a28802eb3a2a9472588f062097205", + "sha256:c30a0eafc89d28e7f959281b58198a9fa5e99405f716c0289b7892ca345fe45f", + "sha256:c5ce2ce7c997e1971b7184ee37deb6ea9922ef5163c6ee5aa3c274b05f9e12fa", + "sha256:c823bc3971c44ab93e611ab1a46b1eafeae474c0c844aff4b7474287b75fe49c", + "sha256:ce0cdc074d540265bfeb31336e678b4e37316849d13b308607efa527e981f5c2", + "sha256:d1720b4f14c78a3089562b8875b53e36b51c97c51adc53325a69b79b4b48ebcb", + "sha256:d183cf9c797a5291e8301790ed6d053480ed94070637bfaad914dd38b0981f67", + "sha256:d9010c31cd6fa59438da4e58a7f19e4753f7f264300cd152e7f90d4602449762", + "sha256:d9e5e4a85bdb56d224f412d9c98ae4cbd032cc4f3161818f692cd81766eee65a", + "sha256:da1dee8948d2137bb51fbb8a53cce6b1bcc86003c6b42565f008438b806cccd8", + "sha256:df9270660711670e68803107d55c2b5949c2e0f2e4896da176e1ecfc068b974a", + "sha256:e00e3505cd80440f6c98c6d69269dcc2a119f86ad0a9fd70bccc59504bebd68a", + "sha256:e48d5021a84d341bcaf95c8460b152cfbad770d28e5fe14a768988c461b821bc", + "sha256:e7f8b04d83483577fd9200461b057c9f14ced334dcb053090cea1da9c8321a91", + "sha256:edfe3341033a6b53a5c522c802deb2079eee5cbfbb0af032a55064bd65c73a23", + "sha256:ef9c33cc5cbca35808f6c74be11eb7f5f6b14d2311be84a15b594bd3e58b5527", + "sha256:f2d4324a98062be0525d16f768a03e0bbb3b9fe301ceee99611dc9a7953124e6", + "sha256:f3935f82f6f4a3820270842e90456ebad3af15810cf65932bd24da4463bc0a4c", + "sha256:f614ab0c76397661b90b6851a030004dac502e48260ea10f2441abd2207fbcc7", + "sha256:f7db54c7914cc99d901d93a34704833568d86c20925b2762f9fa779f9cd2e70f", + "sha256:fbc6264158392bad9df19537e872d476f7c57adf718944cc1e4495cbabf38e2a", + "sha256:fe2fb38c2ed905a2582948e2de560675e9dfbee94c6d5ccdb1301c6d0a5bf092", + "sha256:ffe595f10566f8276b76dc3a11ae4bb7eba1aac8ddd75811736a15b0d5311414" ], "markers": "python_version >= '3.8'", - "version": "==3.10.9" + "version": "==3.10.10" }, "aioqbt": { "git": "git+https://github.com/mhdzumair/aioqbt.git", @@ -209,12 +209,12 @@ }, "beanie": { "hashes": [ - "sha256:54016f4ec71ed0ea6ce0c7946a395090c45687f254dbbe1cf06eec608383f790", - "sha256:b45926c01d4a899c519c665c2a5f230990717e99f7fd68172a389ca33e7693b9" + "sha256:2cc6762bdd59b9040dd004ecbc7d4fd5ddd22e52743915e38d1f0f92f276bcaf", + "sha256:a5eee40f1e52214afeb8558c0823d7504856884770c3d56fc3cd5765efb87314" ], "index": "pypi", "markers": "python_version >= '3.7' and python_version < '4.0'", - "version": "==1.26.0" + "version": "==1.27.0" }, "beautifulsoup4": { "hashes": [ @@ -315,99 +315,114 @@ }, "charset-normalizer": { "hashes": [ - "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027", - "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087", - "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786", - "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8", - "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09", - "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185", - "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574", - "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e", - "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519", - "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898", - "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269", - "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3", - "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f", - "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6", - "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8", - "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a", - "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73", - "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc", - "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714", - "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2", - "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc", - "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce", - "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d", - "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e", - "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6", - "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269", - "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96", - "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d", - "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a", - "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4", - "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77", - "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d", - "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0", - "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed", - "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068", - "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac", - "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25", - "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8", - "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab", - "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26", - "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2", - "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db", - "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f", - "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5", - "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99", - "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c", - "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d", - "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811", - "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa", - "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a", - "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03", - "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b", - "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04", - "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c", - "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001", - "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458", - "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389", - "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99", - "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985", - "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537", - "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238", - "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f", - "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d", - "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796", - "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a", - "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143", - "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8", - "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c", - "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5", - "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5", - "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711", - "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4", - "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6", - "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c", - "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7", - "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4", - "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b", - "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae", - "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12", - "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c", - "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae", - "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8", - "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887", - "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b", - "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4", - "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f", - "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5", - "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33", - "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519", - "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561" + "sha256:0099d79bdfcf5c1f0c2c72f91516702ebf8b0b8ddd8905f97a8aecf49712c621", + "sha256:0713f3adb9d03d49d365b70b84775d0a0d18e4ab08d12bc46baa6132ba78aaf6", + "sha256:07afec21bbbbf8a5cc3651aa96b980afe2526e7f048fdfb7f1014d84acc8b6d8", + "sha256:0b309d1747110feb25d7ed6b01afdec269c647d382c857ef4663bbe6ad95a912", + "sha256:0d99dd8ff461990f12d6e42c7347fd9ab2532fb70e9621ba520f9e8637161d7c", + "sha256:0de7b687289d3c1b3e8660d0741874abe7888100efe14bd0f9fd7141bcbda92b", + "sha256:1110e22af8ca26b90bd6364fe4c763329b0ebf1ee213ba32b68c73de5752323d", + "sha256:130272c698667a982a5d0e626851ceff662565379baf0ff2cc58067b81d4f11d", + "sha256:136815f06a3ae311fae551c3df1f998a1ebd01ddd424aa5603a4336997629e95", + "sha256:14215b71a762336254351b00ec720a8e85cada43b987da5a042e4ce3e82bd68e", + "sha256:1db4e7fefefd0f548d73e2e2e041f9df5c59e178b4c72fbac4cc6f535cfb1565", + "sha256:1ffd9493de4c922f2a38c2bf62b831dcec90ac673ed1ca182fe11b4d8e9f2a64", + "sha256:2006769bd1640bdf4d5641c69a3d63b71b81445473cac5ded39740a226fa88ab", + "sha256:20587d20f557fe189b7947d8e7ec5afa110ccf72a3128d61a2a387c3313f46be", + "sha256:223217c3d4f82c3ac5e29032b3f1c2eb0fb591b72161f86d93f5719079dae93e", + "sha256:27623ba66c183eca01bf9ff833875b459cad267aeeb044477fedac35e19ba907", + "sha256:285e96d9d53422efc0d7a17c60e59f37fbf3dfa942073f666db4ac71e8d726d0", + "sha256:2de62e8801ddfff069cd5c504ce3bc9672b23266597d4e4f50eda28846c322f2", + "sha256:2f6c34da58ea9c1a9515621f4d9ac379871a8f21168ba1b5e09d74250de5ad62", + "sha256:309a7de0a0ff3040acaebb35ec45d18db4b28232f21998851cfa709eeff49d62", + "sha256:35c404d74c2926d0287fbd63ed5d27eb911eb9e4a3bb2c6d294f3cfd4a9e0c23", + "sha256:3710a9751938947e6327ea9f3ea6332a09bf0ba0c09cae9cb1f250bd1f1549bc", + "sha256:3d59d125ffbd6d552765510e3f31ed75ebac2c7470c7274195b9161a32350284", + "sha256:40d3ff7fc90b98c637bda91c89d51264a3dcf210cade3a2c6f838c7268d7a4ca", + "sha256:425c5f215d0eecee9a56cdb703203dda90423247421bf0d67125add85d0c4455", + "sha256:43193c5cda5d612f247172016c4bb71251c784d7a4d9314677186a838ad34858", + "sha256:44aeb140295a2f0659e113b31cfe92c9061622cadbc9e2a2f7b8ef6b1e29ef4b", + "sha256:47334db71978b23ebcf3c0f9f5ee98b8d65992b65c9c4f2d34c2eaf5bcaf0594", + "sha256:4796efc4faf6b53a18e3d46343535caed491776a22af773f366534056c4e1fbc", + "sha256:4a51b48f42d9358460b78725283f04bddaf44a9358197b889657deba38f329db", + "sha256:4b67fdab07fdd3c10bb21edab3cbfe8cf5696f453afce75d815d9d7223fbe88b", + "sha256:4ec9dd88a5b71abfc74e9df5ebe7921c35cbb3b641181a531ca65cdb5e8e4dea", + "sha256:4f9fc98dad6c2eaa32fc3af1417d95b5e3d08aff968df0cd320066def971f9a6", + "sha256:54b6a92d009cbe2fb11054ba694bc9e284dad30a26757b1e372a1fdddaf21920", + "sha256:55f56e2ebd4e3bc50442fbc0888c9d8c94e4e06a933804e2af3e89e2f9c1c749", + "sha256:5726cf76c982532c1863fb64d8c6dd0e4c90b6ece9feb06c9f202417a31f7dd7", + "sha256:5d447056e2ca60382d460a604b6302d8db69476fd2015c81e7c35417cfabe4cd", + "sha256:5ed2e36c3e9b4f21dd9422f6893dec0abf2cca553af509b10cd630f878d3eb99", + "sha256:5ff2ed8194587faf56555927b3aa10e6fb69d931e33953943bc4f837dfee2242", + "sha256:62f60aebecfc7f4b82e3f639a7d1433a20ec32824db2199a11ad4f5e146ef5ee", + "sha256:63bc5c4ae26e4bc6be6469943b8253c0fd4e4186c43ad46e713ea61a0ba49129", + "sha256:6b40e8d38afe634559e398cc32b1472f376a4099c75fe6299ae607e404c033b2", + "sha256:6b493a043635eb376e50eedf7818f2f322eabbaa974e948bd8bdd29eb7ef2a51", + "sha256:6dba5d19c4dfab08e58d5b36304b3f92f3bd5d42c1a3fa37b5ba5cdf6dfcbcee", + "sha256:6fd30dc99682dc2c603c2b315bded2799019cea829f8bf57dc6b61efde6611c8", + "sha256:707b82d19e65c9bd28b81dde95249b07bf9f5b90ebe1ef17d9b57473f8a64b7b", + "sha256:7706f5850360ac01d80c89bcef1640683cc12ed87f42579dab6c5d3ed6888613", + "sha256:7782afc9b6b42200f7362858f9e73b1f8316afb276d316336c0ec3bd73312742", + "sha256:79983512b108e4a164b9c8d34de3992f76d48cadc9554c9e60b43f308988aabe", + "sha256:7f683ddc7eedd742e2889d2bfb96d69573fde1d92fcb811979cdb7165bb9c7d3", + "sha256:82357d85de703176b5587dbe6ade8ff67f9f69a41c0733cf2425378b49954de5", + "sha256:84450ba661fb96e9fd67629b93d2941c871ca86fc38d835d19d4225ff946a631", + "sha256:86f4e8cca779080f66ff4f191a685ced73d2f72d50216f7112185dc02b90b9b7", + "sha256:8cda06946eac330cbe6598f77bb54e690b4ca93f593dee1568ad22b04f347c15", + "sha256:8ce7fd6767a1cc5a92a639b391891bf1c268b03ec7e021c7d6d902285259685c", + "sha256:8ff4e7cdfdb1ab5698e675ca622e72d58a6fa2a8aa58195de0c0061288e6e3ea", + "sha256:9289fd5dddcf57bab41d044f1756550f9e7cf0c8e373b8cdf0ce8773dc4bd417", + "sha256:92a7e36b000bf022ef3dbb9c46bfe2d52c047d5e3f3343f43204263c5addc250", + "sha256:92db3c28b5b2a273346bebb24857fda45601aef6ae1c011c0a997106581e8a88", + "sha256:95c3c157765b031331dd4db3c775e58deaee050a3042fcad72cbc4189d7c8dca", + "sha256:980b4f289d1d90ca5efcf07958d3eb38ed9c0b7676bf2831a54d4f66f9c27dfa", + "sha256:9ae4ef0b3f6b41bad6366fb0ea4fc1d7ed051528e113a60fa2a65a9abb5b1d99", + "sha256:9c98230f5042f4945f957d006edccc2af1e03ed5e37ce7c373f00a5a4daa6149", + "sha256:9fa2566ca27d67c86569e8c85297aaf413ffab85a8960500f12ea34ff98e4c41", + "sha256:a14969b8691f7998e74663b77b4c36c0337cb1df552da83d5c9004a93afdb574", + "sha256:a8aacce6e2e1edcb6ac625fb0f8c3a9570ccc7bfba1f63419b3769ccf6a00ed0", + "sha256:a8e538f46104c815be19c975572d74afb53f29650ea2025bbfaef359d2de2f7f", + "sha256:aa41e526a5d4a9dfcfbab0716c7e8a1b215abd3f3df5a45cf18a12721d31cb5d", + "sha256:aa693779a8b50cd97570e5a0f343538a8dbd3e496fa5dcb87e29406ad0299654", + "sha256:ab22fbd9765e6954bc0bcff24c25ff71dcbfdb185fcdaca49e81bac68fe724d3", + "sha256:ab2e5bef076f5a235c3774b4f4028a680432cded7cad37bba0fd90d64b187d19", + "sha256:ab973df98fc99ab39080bfb0eb3a925181454d7c3ac8a1e695fddfae696d9e90", + "sha256:af73657b7a68211996527dbfeffbb0864e043d270580c5aef06dc4b659a4b578", + "sha256:b197e7094f232959f8f20541ead1d9862ac5ebea1d58e9849c1bf979255dfac9", + "sha256:b295729485b06c1a0683af02a9e42d2caa9db04a373dc38a6a58cdd1e8abddf1", + "sha256:b8831399554b92b72af5932cdbbd4ddc55c55f631bb13ff8fe4e6536a06c5c51", + "sha256:b8dcd239c743aa2f9c22ce674a145e0a25cb1566c495928440a181ca1ccf6719", + "sha256:bcb4f8ea87d03bc51ad04add8ceaf9b0f085ac045ab4d74e73bbc2dc033f0236", + "sha256:bd7af3717683bea4c87acd8c0d3d5b44d56120b26fd3f8a692bdd2d5260c620a", + "sha256:bf4475b82be41b07cc5e5ff94810e6a01f276e37c2d55571e3fe175e467a1a1c", + "sha256:c3e446d253bd88f6377260d07c895816ebf33ffffd56c1c792b13bff9c3e1ade", + "sha256:c57516e58fd17d03ebe67e181a4e4e2ccab1168f8c2976c6a334d4f819fe5944", + "sha256:c94057af19bc953643a33581844649a7fdab902624d2eb739738a30e2b3e60fc", + "sha256:cab5d0b79d987c67f3b9e9c53f54a61360422a5a0bc075f43cab5621d530c3b6", + "sha256:ce031db0408e487fd2775d745ce30a7cd2923667cf3b69d48d219f1d8f5ddeb6", + "sha256:cee4373f4d3ad28f1ab6290684d8e2ebdb9e7a1b74fdc39e4c211995f77bec27", + "sha256:d5b054862739d276e09928de37c79ddeec42a6e1bfc55863be96a36ba22926f6", + "sha256:dbe03226baf438ac4fda9e2d0715022fd579cb641c4cf639fa40d53b2fe6f3e2", + "sha256:dc15e99b2d8a656f8e666854404f1ba54765871104e50c8e9813af8a7db07f12", + "sha256:dcaf7c1524c0542ee2fc82cc8ec337f7a9f7edee2532421ab200d2b920fc97cf", + "sha256:dd4eda173a9fcccb5f2e2bd2a9f423d180194b1bf17cf59e3269899235b2a114", + "sha256:dd9a8bd8900e65504a305bf8ae6fa9fbc66de94178c420791d0293702fce2df7", + "sha256:de7376c29d95d6719048c194a9cf1a1b0393fbe8488a22008610b0361d834ecf", + "sha256:e7fdd52961feb4c96507aa649550ec2a0d527c086d284749b2f582f2d40a2e0d", + "sha256:e91f541a85298cf35433bf66f3fab2a4a2cff05c127eeca4af174f6d497f0d4b", + "sha256:e9e3c4c9e1ed40ea53acf11e2a386383c3304212c965773704e4603d589343ed", + "sha256:ee803480535c44e7f5ad00788526da7d85525cfefaf8acf8ab9a310000be4b03", + "sha256:f09cb5a7bbe1ecae6e87901a2eb23e0256bb524a79ccc53eb0b7629fbe7677c4", + "sha256:f19c1585933c82098c2a520f8ec1227f20e339e33aca8fa6f956f6691b784e67", + "sha256:f1a2f519ae173b5b6a2c9d5fa3116ce16e48b3462c8b96dfdded11055e3d6365", + "sha256:f28f891ccd15c514a0981f3b9db9aa23d62fe1a99997512b0491d2ed323d229a", + "sha256:f3e73a4255342d4eb26ef6df01e3962e73aa29baa3124a8e824c5d3364a65748", + "sha256:f606a1881d2663630ea5b8ce2efe2111740df4b687bd78b34a8131baa007f79b", + "sha256:fe9f97feb71aa9896b81973a7bbada8c49501dc73e58a10fcef6663af95e5079", + "sha256:ffc519621dce0c767e96b9c53f09c5d215578e10b02c285809f76509a3931482" ], "markers": "python_full_version >= '3.7.0'", - "version": "==3.3.2" + "version": "==3.4.0" }, "cinemagoer": { "editable": true, @@ -494,20 +509,20 @@ }, "curl-cffi": { "hashes": [ - "sha256:24b084e5b0046c76a58902c2b985bdb2638d9b16af130b33b521a4c6d59792d6", - "sha256:2caa3cd83ec2cb3ad5729311184324b03db181a0f1ca6a3abd25ba9319562396", - "sha256:3756d31635f62004cc3b2280f9001b03294eacb58b4017f27cc2b645b2251880", - "sha256:3ca68890e839b35134e063d7c57684e14a6bca5f68948f8a689e64569abe5a40", - "sha256:3ec154c1f516364d9c52e6045be78724fc3eef711b6721cc855d32f1ad6a69c5", - "sha256:4671f367671038b14ea2c25f49e27402686c602d0d640ac7d484ac5c1f66b538", - "sha256:83f22b65fddbc46d330e49bd0a44f73112d3f0a10378e8b31d5123df9db9fc72", - "sha256:a4f58b83eab8a77f788444e347a0b4c877cf327ecb60299b5fa01e5c73b294ee", - "sha256:afa1230a4833246f45deb404b13676ac29c37476ec94bae0005dbde85f96be35", - "sha256:ea2ebed4ff052fcd7417cfd20fdab9415eb47ea06ca0c31896971a1c768c9bf3" + "sha256:39e195a13b95bd7e4a1e8eb7804807b4504c462f6886fde01dfe4b7552fd965e", + "sha256:3f9373ce27b20e65fb1e6cbea6e4411edca1f3b4440be68090b262eece728544", + "sha256:43367efb8d48e9997cf7591084ed7529409ad98bb67e284ae152f2a15e4ee68e", + "sha256:890f1b0e1454977ff6bd388d29eb1eafb76bb8ef63d3c8b7539aafd4808a6cec", + "sha256:901012e21af899bdf1278fd9fcee5aad6931ed56e1f5a620ffa90220c9e79f10", + "sha256:9a90bfd0bcc7bf0f30ef6de6f4e612ea1bdc238a48928845532109a0273f6275", + "sha256:a357d3da6aa8ccd41f1e6e422b9ff09ad4cc99b23b8f6299998cbfee6e690f01", + "sha256:b5520bcf6284417e66c82728512e344b50bc0e9d8bd7949923b30558746b49a3", + "sha256:ec6718bc5151b3e0ecc35aaf40078a39cc239405182c63fc95933eb7bff572dd", + "sha256:fe01be42c27667353028ee06802cc32e3936496e645a39175db4cf7bea45bb10" ], "index": "pypi", "markers": "python_version >= '3.8'", - "version": "==0.7.2" + "version": "==0.7.3" }, "defusedxml": { "hashes": [ @@ -537,12 +552,12 @@ }, "dnspython": { "hashes": [ - "sha256:5ef3b9680161f6fa89daf8ad451b5f1a33b18ae8a1c6778cdf4b43f08c0a6e50", - "sha256:e8f0f9c23a7b7cb99ded64e6c3a6f3e701d78f50c55e002b839dea7225cff7cc" + "sha256:b4c34b7d10b51bcc3a5071e7b8dee77939f1e878477eeecc965e9835f63c6c86", + "sha256:ce9c432eda0dc91cf618a5cedf1a4e142651196bbcd2c80e89ed5a907e5cfaf1" ], "index": "pypi", - "markers": "python_version >= '3.8'", - "version": "==2.6.1" + "markers": "python_version >= '3.9'", + "version": "==2.7.0" }, "dramatiq": { "extras": [ @@ -949,8 +964,8 @@ }, "humanize": { "git": "git+https://github.com/python-humanize/humanize.git", - "markers": "python_version >= '3.8'", - "ref": "19b43d3b8d80959d75097d8e145690be811465bd" + "markers": "python_version >= '3.9'", + "ref": "073a00b4cae648cb79e494eefb0389f5b038ffce" }, "hyperlink": { "hashes": [ @@ -1008,6 +1023,14 @@ "markers": "python_version >= '3.7'", "version": "==1.0.1" }, + "json5": { + "hashes": [ + "sha256:34ed7d834b1341a86987ed52f3f76cd8ee184394906b6e22a1e0deb9ab294e8f", + "sha256:548e41b9be043f9426776f05df8635a00fe06104ea51ed24b67f908856e151ae" + ], + "markers": "python_version >= '3.8'", + "version": "==0.9.25" + }, "jsonschema": { "hashes": [ "sha256:d71497fef26351a33265337fa77ffeb82423f3ea21283cd9467bb03999266bc4", @@ -1018,11 +1041,11 @@ }, "jsonschema-specifications": { "hashes": [ - "sha256:48a76787b3e70f5ed53f1160d2b81f586e4ca6d1548c5de7085d1682674764cc", - "sha256:87e4fdf3a94858b8a2ba2778d9ba57d8a9cafca7c7489c46ba0d30a8bc6a9c3c" + "sha256:0f38b83639958ce1152d02a7f062902c41c8fd20d558b0c34344292d417ae272", + "sha256:a09a0680616357d9a0ecf05c12ad234479f549239d0f5b55f3deea67475da9bf" ], - "markers": "python_version >= '3.8'", - "version": "==2023.12.1" + "markers": "python_version >= '3.9'", + "version": "==2024.10.1" }, "lazy-model": { "hashes": [ @@ -1186,69 +1209,70 @@ }, "markupsafe": { "hashes": [ - "sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf", - "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff", - "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f", - "sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3", - "sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532", - "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f", - "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617", - "sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df", - "sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4", - "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906", - "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f", - "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4", - "sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8", - "sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371", - "sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2", - "sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465", - "sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52", - "sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6", - "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169", - "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad", - "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2", - "sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0", - "sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029", - "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f", - "sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a", - "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced", - "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5", - "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c", - "sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf", - "sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9", - "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb", - "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad", - "sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3", - "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1", - "sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46", - "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc", - "sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a", - "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee", - "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900", - "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5", - "sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea", - "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f", - "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5", - "sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e", - "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a", - "sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f", - "sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50", - "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a", - "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b", - "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4", - "sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff", - "sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2", - "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46", - "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b", - "sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf", - "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5", - "sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5", - "sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab", - "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd", - "sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68" + "sha256:0778de17cff1acaeccc3ff30cd99a3fd5c50fc58ad3d6c0e0c4c58092b859396", + "sha256:0f84af7e813784feb4d5e4ff7db633aba6c8ca64a833f61d8e4eade234ef0c38", + "sha256:17b2aea42a7280db02ac644db1d634ad47dcc96faf38ab304fe26ba2680d359a", + "sha256:242d6860f1fd9191aef5fae22b51c5c19767f93fb9ead4d21924e0bcb17619d8", + "sha256:244dbe463d5fb6d7ce161301a03a6fe744dac9072328ba9fc82289238582697b", + "sha256:26627785a54a947f6d7336ce5963569b5d75614619e75193bdb4e06e21d447ad", + "sha256:2a4b34a8d14649315c4bc26bbfa352663eb51d146e35eef231dd739d54a5430a", + "sha256:2ae99f31f47d849758a687102afdd05bd3d3ff7dbab0a8f1587981b58a76152a", + "sha256:312387403cd40699ab91d50735ea7a507b788091c416dd007eac54434aee51da", + "sha256:3341c043c37d78cc5ae6e3e305e988532b072329639007fd408a476642a89fd6", + "sha256:33d1c36b90e570ba7785dacd1faaf091203d9942bc036118fab8110a401eb1a8", + "sha256:3e683ee4f5d0fa2dde4db77ed8dd8a876686e3fc417655c2ece9a90576905344", + "sha256:3ffb4a8e7d46ed96ae48805746755fadd0909fea2306f93d5d8233ba23dda12a", + "sha256:40621d60d0e58aa573b68ac5e2d6b20d44392878e0bfc159012a5787c4e35bc8", + "sha256:40f1e10d51c92859765522cbd79c5c8989f40f0419614bcdc5015e7b6bf97fc5", + "sha256:45d42d132cff577c92bfba536aefcfea7e26efb975bd455db4e6602f5c9f45e7", + "sha256:48488d999ed50ba8d38c581d67e496f955821dc183883550a6fbc7f1aefdc170", + "sha256:4935dd7883f1d50e2ffecca0aa33dc1946a94c8f3fdafb8df5c330e48f71b132", + "sha256:4c2d64fdba74ad16138300815cfdc6ab2f4647e23ced81f59e940d7d4a1469d9", + "sha256:4c8817557d0de9349109acb38b9dd570b03cc5014e8aabf1cbddc6e81005becd", + "sha256:4ffaaac913c3f7345579db4f33b0020db693f302ca5137f106060316761beea9", + "sha256:5a4cb365cb49b750bdb60b846b0c0bc49ed62e59a76635095a179d440540c346", + "sha256:62fada2c942702ef8952754abfc1a9f7658a4d5460fabe95ac7ec2cbe0d02abc", + "sha256:67c519635a4f64e495c50e3107d9b4075aec33634272b5db1cde839e07367589", + "sha256:6a54c43d3ec4cf2a39f4387ad044221c66a376e58c0d0e971d47c475ba79c6b5", + "sha256:7044312a928a66a4c2a22644147bc61a199c1709712069a344a3fb5cfcf16915", + "sha256:730d86af59e0e43ce277bb83970530dd223bf7f2a838e086b50affa6ec5f9295", + "sha256:800100d45176652ded796134277ecb13640c1a537cad3b8b53da45aa96330453", + "sha256:80fcbf3add8790caddfab6764bde258b5d09aefbe9169c183f88a7410f0f6dea", + "sha256:82b5dba6eb1bcc29cc305a18a3c5365d2af06ee71b123216416f7e20d2a84e5b", + "sha256:852dc840f6d7c985603e60b5deaae1d89c56cb038b577f6b5b8c808c97580f1d", + "sha256:8ad4ad1429cd4f315f32ef263c1342166695fad76c100c5d979c45d5570ed58b", + "sha256:8ae369e84466aa70f3154ee23c1451fda10a8ee1b63923ce76667e3077f2b0c4", + "sha256:93e8248d650e7e9d49e8251f883eed60ecbc0e8ffd6349e18550925e31bd029b", + "sha256:973a371a55ce9ed333a3a0f8e0bcfae9e0d637711534bcb11e130af2ab9334e7", + "sha256:9ba25a71ebf05b9bb0e2ae99f8bc08a07ee8e98c612175087112656ca0f5c8bf", + "sha256:a10860e00ded1dd0a65b83e717af28845bb7bd16d8ace40fe5531491de76b79f", + "sha256:a4792d3b3a6dfafefdf8e937f14906a51bd27025a36f4b188728a73382231d91", + "sha256:a7420ceda262dbb4b8d839a4ec63d61c261e4e77677ed7c66c99f4e7cb5030dd", + "sha256:ad91738f14eb8da0ff82f2acd0098b6257621410dcbd4df20aaa5b4233d75a50", + "sha256:b6a387d61fe41cdf7ea95b38e9af11cfb1a63499af2759444b99185c4ab33f5b", + "sha256:b954093679d5750495725ea6f88409946d69cfb25ea7b4c846eef5044194f583", + "sha256:bbde71a705f8e9e4c3e9e33db69341d040c827c7afa6789b14c6e16776074f5a", + "sha256:beeebf760a9c1f4c07ef6a53465e8cfa776ea6a2021eda0d0417ec41043fe984", + "sha256:c91b394f7601438ff79a4b93d16be92f216adb57d813a78be4446fe0f6bc2d8c", + "sha256:c97ff7fedf56d86bae92fa0a646ce1a0ec7509a7578e1ed238731ba13aabcd1c", + "sha256:cb53e2a99df28eee3b5f4fea166020d3ef9116fdc5764bc5117486e6d1211b25", + "sha256:cbf445eb5628981a80f54087f9acdbf84f9b7d862756110d172993b9a5ae81aa", + "sha256:d06b24c686a34c86c8c1fba923181eae6b10565e4d80bdd7bc1c8e2f11247aa4", + "sha256:d98e66a24497637dd31ccab090b34392dddb1f2f811c4b4cd80c230205c074a3", + "sha256:db15ce28e1e127a0013dfb8ac243a8e392db8c61eae113337536edb28bdc1f97", + "sha256:db842712984e91707437461930e6011e60b39136c7331e971952bb30465bc1a1", + "sha256:e24bfe89c6ac4c31792793ad9f861b8f6dc4546ac6dc8f1c9083c7c4f2b335cd", + "sha256:e81c52638315ff4ac1b533d427f50bc0afc746deb949210bc85f05d4f15fd772", + "sha256:e9393357f19954248b00bed7c56f29a25c930593a77630c719653d51e7669c2a", + "sha256:ee3941769bd2522fe39222206f6dd97ae83c442a94c90f2b7a25d847d40f4729", + "sha256:f31ae06f1328595d762c9a2bf29dafd8621c7d3adc130cbb46278079758779ca", + "sha256:f94190df587738280d544971500b9cafc9b950d32efcb1fba9ac10d84e6aa4e6", + "sha256:fa7d686ed9883f3d664d39d5a8e74d3c5f63e603c2e3ff0abcba23eac6542635", + "sha256:fb532dd9900381d2e8f48172ddc5a59db4c445a11b9fab40b3b786da40d3b56b", + "sha256:fe32482b37b4b00c7a52a07211b479653b7fe4f22b2e481b9a9b099d8a430f2f" ], - "markers": "python_version >= '3.7'", - "version": "==2.1.5" + "markers": "python_version >= '3.9'", + "version": "==3.0.1" }, "motor": { "hashes": [ @@ -1385,7 +1409,7 @@ "pikpakapi": { "git": "git+https://github.com/mhdzumair/PikPakAPI.git", "markers": "python_full_version >= '3.8.3' and python_full_version < '4.0.0'", - "ref": "28b291ad835fdba87e7395dadb08e36b4a90baba" + "ref": "32a6533c945248fe0319997ac90b266ee2fbe8a3" }, "pillow": { "hashes": [ @@ -1496,6 +1520,110 @@ "markers": "python_version >= '3.8'", "version": "==0.21.0" }, + "propcache": { + "hashes": [ + "sha256:00181262b17e517df2cd85656fcd6b4e70946fe62cd625b9d74ac9977b64d8d9", + "sha256:0e53cb83fdd61cbd67202735e6a6687a7b491c8742dfc39c9e01e80354956763", + "sha256:1235c01ddaa80da8235741e80815ce381c5267f96cc49b1477fdcf8c047ef325", + "sha256:140fbf08ab3588b3468932974a9331aff43c0ab8a2ec2c608b6d7d1756dbb6cb", + "sha256:191db28dc6dcd29d1a3e063c3be0b40688ed76434622c53a284e5427565bbd9b", + "sha256:1e41d67757ff4fbc8ef2af99b338bfb955010444b92929e9e55a6d4dcc3c4f09", + "sha256:1ec43d76b9677637a89d6ab86e1fef70d739217fefa208c65352ecf0282be957", + "sha256:20a617c776f520c3875cf4511e0d1db847a076d720714ae35ffe0df3e440be68", + "sha256:218db2a3c297a3768c11a34812e63b3ac1c3234c3a086def9c0fee50d35add1f", + "sha256:22aa8f2272d81d9317ff5756bb108021a056805ce63dd3630e27d042c8092798", + "sha256:25a1f88b471b3bc911d18b935ecb7115dff3a192b6fef46f0bfaf71ff4f12418", + "sha256:25c8d773a62ce0451b020c7b29a35cfbc05de8b291163a7a0f3b7904f27253e6", + "sha256:2a60ad3e2553a74168d275a0ef35e8c0a965448ffbc3b300ab3a5bb9956c2162", + "sha256:2a66df3d4992bc1d725b9aa803e8c5a66c010c65c741ad901e260ece77f58d2f", + "sha256:2ccc28197af5313706511fab3a8b66dcd6da067a1331372c82ea1cb74285e036", + "sha256:2e900bad2a8456d00a113cad8c13343f3b1f327534e3589acc2219729237a2e8", + "sha256:2ee7606193fb267be4b2e3b32714f2d58cad27217638db98a60f9efb5efeccc2", + "sha256:33ac8f098df0585c0b53009f039dfd913b38c1d2edafed0cedcc0c32a05aa110", + "sha256:3444cdba6628accf384e349014084b1cacd866fbb88433cd9d279d90a54e0b23", + "sha256:363ea8cd3c5cb6679f1c2f5f1f9669587361c062e4899fce56758efa928728f8", + "sha256:375a12d7556d462dc64d70475a9ee5982465fbb3d2b364f16b86ba9135793638", + "sha256:388f3217649d6d59292b722d940d4d2e1e6a7003259eb835724092a1cca0203a", + "sha256:3947483a381259c06921612550867b37d22e1df6d6d7e8361264b6d037595f44", + "sha256:39e104da444a34830751715f45ef9fc537475ba21b7f1f5b0f4d71a3b60d7fe2", + "sha256:3c997f8c44ec9b9b0bcbf2d422cc00a1d9b9c681f56efa6ca149a941e5560da2", + "sha256:3dfafb44f7bb35c0c06eda6b2ab4bfd58f02729e7c4045e179f9a861b07c9850", + "sha256:3ebbcf2a07621f29638799828b8d8668c421bfb94c6cb04269130d8de4fb7136", + "sha256:3f88a4095e913f98988f5b338c1d4d5d07dbb0b6bad19892fd447484e483ba6b", + "sha256:439e76255daa0f8151d3cb325f6dd4a3e93043e6403e6491813bcaaaa8733887", + "sha256:4569158070180c3855e9c0791c56be3ceeb192defa2cdf6a3f39e54319e56b89", + "sha256:466c219deee4536fbc83c08d09115249db301550625c7fef1c5563a584c9bc87", + "sha256:4a9d9b4d0a9b38d1c391bb4ad24aa65f306c6f01b512e10a8a34a2dc5675d348", + "sha256:4c7dde9e533c0a49d802b4f3f218fa9ad0a1ce21f2c2eb80d5216565202acab4", + "sha256:53d1bd3f979ed529f0805dd35ddaca330f80a9a6d90bc0121d2ff398f8ed8861", + "sha256:55346705687dbd7ef0d77883ab4f6fabc48232f587925bdaf95219bae072491e", + "sha256:56295eb1e5f3aecd516d91b00cfd8bf3a13991de5a479df9e27dd569ea23959c", + "sha256:56bb5c98f058a41bb58eead194b4db8c05b088c93d94d5161728515bd52b052b", + "sha256:5a5b3bb545ead161be780ee85a2b54fdf7092815995661947812dde94a40f6fb", + "sha256:5f2564ec89058ee7c7989a7b719115bdfe2a2fb8e7a4543b8d1c0cc4cf6478c1", + "sha256:608cce1da6f2672a56b24a015b42db4ac612ee709f3d29f27a00c943d9e851de", + "sha256:63f13bf09cc3336eb04a837490b8f332e0db41da66995c9fd1ba04552e516354", + "sha256:662dd62358bdeaca0aee5761de8727cfd6861432e3bb828dc2a693aa0471a563", + "sha256:676135dcf3262c9c5081cc8f19ad55c8a64e3f7282a21266d05544450bffc3a5", + "sha256:67aeb72e0f482709991aa91345a831d0b707d16b0257e8ef88a2ad246a7280bf", + "sha256:67b69535c870670c9f9b14a75d28baa32221d06f6b6fa6f77a0a13c5a7b0a5b9", + "sha256:682a7c79a2fbf40f5dbb1eb6bfe2cd865376deeac65acf9beb607505dced9e12", + "sha256:6994984550eaf25dd7fc7bd1b700ff45c894149341725bb4edc67f0ffa94efa4", + "sha256:69d3a98eebae99a420d4b28756c8ce6ea5a29291baf2dc9ff9414b42676f61d5", + "sha256:6e2e54267980349b723cff366d1e29b138b9a60fa376664a157a342689553f71", + "sha256:73e4b40ea0eda421b115248d7e79b59214411109a5bc47d0d48e4c73e3b8fcf9", + "sha256:74acd6e291f885678631b7ebc85d2d4aec458dd849b8c841b57ef04047833bed", + "sha256:7665f04d0c7f26ff8bb534e1c65068409bf4687aa2534faf7104d7182debb336", + "sha256:7735e82e3498c27bcb2d17cb65d62c14f1100b71723b68362872bca7d0913d90", + "sha256:77a86c261679ea5f3896ec060be9dc8e365788248cc1e049632a1be682442063", + "sha256:7cf18abf9764746b9c8704774d8b06714bcb0a63641518a3a89c7f85cc02c2ad", + "sha256:83928404adf8fb3d26793665633ea79b7361efa0287dfbd372a7e74311d51ee6", + "sha256:8e40876731f99b6f3c897b66b803c9e1c07a989b366c6b5b475fafd1f7ba3fb8", + "sha256:8f188cfcc64fb1266f4684206c9de0e80f54622c3f22a910cbd200478aeae61e", + "sha256:91997d9cb4a325b60d4e3f20967f8eb08dfcb32b22554d5ef78e6fd1dda743a2", + "sha256:91ee8fc02ca52e24bcb77b234f22afc03288e1dafbb1f88fe24db308910c4ac7", + "sha256:92fe151145a990c22cbccf9ae15cae8ae9eddabfc949a219c9f667877e40853d", + "sha256:945db8ee295d3af9dbdbb698cce9bbc5c59b5c3fe328bbc4387f59a8a35f998d", + "sha256:9517d5e9e0731957468c29dbfd0f976736a0e55afaea843726e887f36fe017df", + "sha256:952e0d9d07609d9c5be361f33b0d6d650cd2bae393aabb11d9b719364521984b", + "sha256:97a58a28bcf63284e8b4d7b460cbee1edaab24634e82059c7b8c09e65284f178", + "sha256:97e48e8875e6c13909c800fa344cd54cc4b2b0db1d5f911f840458a500fde2c2", + "sha256:9e0f07b42d2a50c7dd2d8675d50f7343d998c64008f1da5fef888396b7f84630", + "sha256:a3dc1a4b165283bd865e8f8cb5f0c64c05001e0718ed06250d8cac9bec115b48", + "sha256:a3ebe9a75be7ab0b7da2464a77bb27febcb4fab46a34f9288f39d74833db7f61", + "sha256:a64e32f8bd94c105cc27f42d3b658902b5bcc947ece3c8fe7bc1b05982f60e89", + "sha256:a6ed8db0a556343d566a5c124ee483ae113acc9a557a807d439bcecc44e7dfbb", + "sha256:ad9c9b99b05f163109466638bd30ada1722abb01bbb85c739c50b6dc11f92dc3", + "sha256:b33d7a286c0dc1a15f5fc864cc48ae92a846df287ceac2dd499926c3801054a6", + "sha256:bc092ba439d91df90aea38168e11f75c655880c12782facf5cf9c00f3d42b562", + "sha256:c436130cc779806bdf5d5fae0d848713105472b8566b75ff70048c47d3961c5b", + "sha256:c5869b8fd70b81835a6f187c5fdbe67917a04d7e52b6e7cc4e5fe39d55c39d58", + "sha256:c5ecca8f9bab618340c8e848d340baf68bcd8ad90a8ecd7a4524a81c1764b3db", + "sha256:cfac69017ef97db2438efb854edf24f5a29fd09a536ff3a992b75990720cdc99", + "sha256:d2f0d0f976985f85dfb5f3d685697ef769faa6b71993b46b295cdbbd6be8cc37", + "sha256:d5bed7f9805cc29c780f3aee05de3262ee7ce1f47083cfe9f77471e9d6777e83", + "sha256:d6a21ef516d36909931a2967621eecb256018aeb11fc48656e3257e73e2e247a", + "sha256:d9b6ddac6408194e934002a69bcaadbc88c10b5f38fb9307779d1c629181815d", + "sha256:db47514ffdbd91ccdc7e6f8407aac4ee94cc871b15b577c1c324236b013ddd04", + "sha256:df81779732feb9d01e5d513fad0122efb3d53bbc75f61b2a4f29a020bc985e70", + "sha256:e4a91d44379f45f5e540971d41e4626dacd7f01004826a18cb048e7da7e96544", + "sha256:e63e3e1e0271f374ed489ff5ee73d4b6e7c60710e1f76af5f0e1a6117cd26394", + "sha256:e70fac33e8b4ac63dfc4c956fd7d85a0b1139adcfc0d964ce288b7c527537fea", + "sha256:ecddc221a077a8132cf7c747d5352a15ed763b674c0448d811f408bf803d9ad7", + "sha256:f45eec587dafd4b2d41ac189c2156461ebd0c1082d2fe7013571598abb8505d1", + "sha256:f52a68c21363c45297aca15561812d542f8fc683c85201df0bebe209e349f793", + "sha256:f571aea50ba5623c308aa146eb650eebf7dbe0fd8c5d946e28343cb3b5aad577", + "sha256:f60f0ac7005b9f5a6091009b09a419ace1610e163fa5deaba5ce3484341840e7", + "sha256:f6475a1b2ecb310c98c28d271a30df74f9dd436ee46d09236a6b750a7599ce57", + "sha256:f6d5749fdd33d90e34c2efb174c7e236829147a2713334d708746e94c4bde40d", + "sha256:f902804113e032e2cdf8c71015651c97af6418363bea8d78dc0911d56c335032", + "sha256:fa1076244f54bb76e65e22cb6910365779d5c3d71d1f18b275f1dfc7b0d71b4d", + "sha256:fc2db02409338bf36590aa985a461b2c96fce91f8e7e0f14c50c5fcc4f229016", + "sha256:ffcad6c564fe6b9b8916c1aefbb37a362deebf9394bd2974e9d84232e3e08504" + ], + "markers": "python_version >= '3.8'", + "version": "==0.2.0" + }, "protego": { "hashes": [ "sha256:2fbe8e9b7a7dbc5016a932b14c98d236aad4c29290bbe457b8d2779666ef7a41", @@ -2290,6 +2418,29 @@ "markers": "python_version >= '3.8'", "version": "==0.20.0" }, + "scrapeops-python-logger": { + "hashes": [ + "sha256:faf58d35a6f043e90cd9680c070046f6617ceff725528ab66a98657c7cdef8f3" + ], + "markers": "python_version >= '3.6'", + "version": "==0.4.7" + }, + "scrapeops-python-requests": { + "hashes": [ + "sha256:2c7b6e2ecd7690efff1f0b0f506df1f189f07534bf9f9eb6f5b8e8cf604e5198" + ], + "index": "pypi", + "markers": "python_version >= '3.6'", + "version": "==0.4.7" + }, + "scrapeops-scrapy": { + "hashes": [ + "sha256:42a78c2e48c395895a2145d57ee912dbdf14c675738187fa08c191fb637f6a2a" + ], + "index": "pypi", + "markers": "python_version >= '3.8'", + "version": "==0.5.6" + }, "scrapy": { "hashes": [ "sha256:4be353d6abbb942a9f7e7614ca8b5f3d9037381176ac8d8859c8cac676e74fa0", @@ -2438,6 +2589,14 @@ "markers": "python_version >= '3.8'", "version": "==0.22.1" }, + "tld": { + "hashes": [ + "sha256:93dde5e1c04bdf1844976eae440706379d21f4ab235b73c05d7483e074fb5629", + "sha256:f75b2be080f767ed17c2338a339eaa4fab5792586319ca819119da252f9f3749" + ], + "markers": "python_version >= '3.7' and python_version < '4'", + "version": "==0.13" + }, "tldextract": { "hashes": [ "sha256:4dfc4c277b6b97fa053899fcdb892d2dc27295851ab5fac4e07797b6a21b2e46", @@ -2524,11 +2683,11 @@ "standard" ], "hashes": [ - "sha256:13bc21373d103859f68fe739608e2eb054a816dea79189bc3ca08ea89a275906", - "sha256:cac7be4dd4d891c363cd942160a7b02e69150dcbc7a36be04d5f4af4b17c8ced" + "sha256:adc42d9cac80cf3e51af97c1851648066841e7cfb6993a4ca8de29ac1548ed41", + "sha256:f5167919867b161b7bcaf32646c6a94cdbd4c3aa2eb5c17d36bb9aa5cfd8c493" ], "markers": "python_version >= '3.8'", - "version": "==0.31.0" + "version": "==0.31.1" }, "uvloop": { "hashes": [ @@ -2806,101 +2965,107 @@ }, "yarl": { "hashes": [ - "sha256:08d7148ff11cb8e886d86dadbfd2e466a76d5dd38c7ea8ebd9b0e07946e76e4b", - "sha256:098b870c18f1341786f290b4d699504e18f1cd050ed179af8123fd8232513424", - "sha256:11b3ca8b42a024513adce810385fcabdd682772411d95bbbda3b9ed1a4257644", - "sha256:1891d69a6ba16e89473909665cd355d783a8a31bc84720902c5911dbb6373465", - "sha256:1bbb418f46c7f7355084833051701b2301092e4611d9e392360c3ba2e3e69f88", - "sha256:1d0828e17fa701b557c6eaed5edbd9098eb62d8838344486248489ff233998b8", - "sha256:1d8e3ca29f643dd121f264a7c89f329f0fcb2e4461833f02de6e39fef80f89da", - "sha256:1fa56f34b2236f5192cb5fceba7bbb09620e5337e0b6dfe2ea0ddbd19dd5b154", - "sha256:216a6785f296169ed52cd7dcdc2612f82c20f8c9634bf7446327f50398732a51", - "sha256:22b739f99c7e4787922903f27a892744189482125cc7b95b747f04dd5c83aa9f", - "sha256:2430cf996113abe5aee387d39ee19529327205cda975d2b82c0e7e96e5fdabdc", - "sha256:269c201bbc01d2cbba5b86997a1e0f73ba5e2f471cfa6e226bcaa7fd664b598d", - "sha256:298c1eecfd3257aa16c0cb0bdffb54411e3e831351cd69e6b0739be16b1bdaa8", - "sha256:2a93a4557f7fc74a38ca5a404abb443a242217b91cd0c4840b1ebedaad8919d4", - "sha256:2b2442a415a5f4c55ced0fade7b72123210d579f7d950e0b5527fc598866e62c", - "sha256:2db874dd1d22d4c2c657807562411ffdfabec38ce4c5ce48b4c654be552759dc", - "sha256:309c104ecf67626c033845b860d31594a41343766a46fa58c3309c538a1e22b2", - "sha256:31497aefd68036d8e31bfbacef915826ca2e741dbb97a8d6c7eac66deda3b606", - "sha256:373f16f38721c680316a6a00ae21cc178e3a8ef43c0227f88356a24c5193abd6", - "sha256:396e59b8de7e4d59ff5507fb4322d2329865b909f29a7ed7ca37e63ade7f835c", - "sha256:3bb83a0f12701c0b91112a11148b5217617982e1e466069d0555be9b372f2734", - "sha256:3de86547c820e4f4da4606d1c8ab5765dd633189791f15247706a2eeabc783ae", - "sha256:3fdbf0418489525231723cdb6c79e7738b3cbacbaed2b750cb033e4ea208f220", - "sha256:40c6e73c03a6befb85b72da213638b8aaa80fe4136ec8691560cf98b11b8ae6e", - "sha256:44a4c40a6f84e4d5955b63462a0e2a988f8982fba245cf885ce3be7618f6aa7d", - "sha256:44b07e1690f010c3c01d353b5790ec73b2f59b4eae5b0000593199766b3f7a5c", - "sha256:45d23c4668d4925688e2ea251b53f36a498e9ea860913ce43b52d9605d3d8177", - "sha256:45f209fb4bbfe8630e3d2e2052535ca5b53d4ce2d2026bed4d0637b0416830da", - "sha256:4afdf84610ca44dcffe8b6c22c68f309aff96be55f5ea2fa31c0c225d6b83e23", - "sha256:4feaaa4742517eaceafcbe74595ed335a494c84634d33961214b278126ec1485", - "sha256:576365c9f7469e1f6124d67b001639b77113cfd05e85ce0310f5f318fd02fe85", - "sha256:5820bd4178e6a639b3ef1db8b18500a82ceab6d8b89309e121a6859f56585b05", - "sha256:5989a38ba1281e43e4663931a53fbf356f78a0325251fd6af09dd03b1d676a09", - "sha256:5a9bacedbb99685a75ad033fd4de37129449e69808e50e08034034c0bf063f99", - "sha256:5b66c87da3c6da8f8e8b648878903ca54589038a0b1e08dde2c86d9cd92d4ac9", - "sha256:5c5e32fef09ce101fe14acd0f498232b5710effe13abac14cd95de9c274e689e", - "sha256:658e8449b84b92a4373f99305de042b6bd0d19bf2080c093881e0516557474a5", - "sha256:6a2acde25be0cf9be23a8f6cbd31734536a264723fca860af3ae5e89d771cd71", - "sha256:6a5185ad722ab4dd52d5fb1f30dcc73282eb1ed494906a92d1a228d3f89607b0", - "sha256:6b7f6e699304717fdc265a7e1922561b02a93ceffdaefdc877acaf9b9f3080b8", - "sha256:703b0f584fcf157ef87816a3c0ff868e8c9f3c370009a8b23b56255885528f10", - "sha256:7055bbade838d68af73aea13f8c86588e4bcc00c2235b4b6d6edb0dbd174e246", - "sha256:78f271722423b2d4851cf1f4fa1a1c4833a128d020062721ba35e1a87154a049", - "sha256:7addd26594e588503bdef03908fc207206adac5bd90b6d4bc3e3cf33a829f57d", - "sha256:81bad32c8f8b5897c909bf3468bf601f1b855d12f53b6af0271963ee67fff0d2", - "sha256:82e692fb325013a18a5b73a4fed5a1edaa7c58144dc67ad9ef3d604eccd451ad", - "sha256:84bbcdcf393139f0abc9f642bf03f00cac31010f3034faa03224a9ef0bb74323", - "sha256:86c438ce920e089c8c2388c7dcc8ab30dfe13c09b8af3d306bcabb46a053d6f7", - "sha256:8be8cdfe20787e6a5fcbd010f8066227e2bb9058331a4eccddec6c0db2bb85b2", - "sha256:8c723c91c94a3bc8033dd2696a0f53e5d5f8496186013167bddc3fb5d9df46a3", - "sha256:8ca53632007c69ddcdefe1e8cbc3920dd88825e618153795b57e6ebcc92e752a", - "sha256:8f722f30366474a99745533cc4015b1781ee54b08de73260b2bbe13316079851", - "sha256:942c80a832a79c3707cca46bd12ab8aa58fddb34b1626d42b05aa8f0bcefc206", - "sha256:94a993f976cdcb2dc1b855d8b89b792893220db8862d1a619efa7451817c836b", - "sha256:95c6737f28069153c399d875317f226bbdea939fd48a6349a3b03da6829fb550", - "sha256:9915300fe5a0aa663c01363db37e4ae8e7c15996ebe2c6cce995e7033ff6457f", - "sha256:9a18595e6a2ee0826bf7dfdee823b6ab55c9b70e8f80f8b77c37e694288f5de1", - "sha256:9c8854b9f80693d20cec797d8e48a848c2fb273eb6f2587b57763ccba3f3bd4b", - "sha256:9cec42a20eae8bebf81e9ce23fb0d0c729fc54cf00643eb251ce7c0215ad49fe", - "sha256:9d2e1626be8712333a9f71270366f4a132f476ffbe83b689dd6dc0d114796c74", - "sha256:9d74f3c335cfe9c21ea78988e67f18eb9822f5d31f88b41aec3a1ec5ecd32da5", - "sha256:9fb4134cc6e005b99fa29dbc86f1ea0a298440ab6b07c6b3ee09232a3b48f495", - "sha256:a0ae6637b173d0c40b9c1462e12a7a2000a71a3258fa88756a34c7d38926911c", - "sha256:a31d21089894942f7d9a8df166b495101b7258ff11ae0abec58e32daf8088813", - "sha256:a3442c31c11088e462d44a644a454d48110f0588de830921fd201060ff19612a", - "sha256:ab9524e45ee809a083338a749af3b53cc7efec458c3ad084361c1dbf7aaf82a2", - "sha256:b1481c048fe787f65e34cb06f7d6824376d5d99f1231eae4778bbe5c3831076d", - "sha256:b8c837ab90c455f3ea8e68bee143472ee87828bff19ba19776e16ff961425b57", - "sha256:bbf2c3f04ff50f16404ce70f822cdc59760e5e2d7965905f0e700270feb2bbfc", - "sha256:bbf9c2a589be7414ac4a534d54e4517d03f1cbb142c0041191b729c2fa23f320", - "sha256:bcd5bf4132e6a8d3eb54b8d56885f3d3a38ecd7ecae8426ecf7d9673b270de43", - "sha256:c14c16831b565707149c742d87a6203eb5597f4329278446d5c0ae7a1a43928e", - "sha256:c49f3e379177f4477f929097f7ed4b0622a586b0aa40c07ac8c0f8e40659a1ac", - "sha256:c92b89bffc660f1274779cb6fbb290ec1f90d6dfe14492523a0667f10170de26", - "sha256:cd66152561632ed4b2a9192e7f8e5a1d41e28f58120b4761622e0355f0fe034c", - "sha256:cf1ad338620249f8dd6d4b6a91a69d1f265387df3697ad5dc996305cf6c26fb2", - "sha256:d07b52c8c450f9366c34aa205754355e933922c79135125541daae6cbf31c799", - "sha256:d0d12fe78dcf60efa205e9a63f395b5d343e801cf31e5e1dda0d2c1fb618073d", - "sha256:d4ee1d240b84e2f213565f0ec08caef27a0e657d4c42859809155cf3a29d1735", - "sha256:d959fe96e5c2712c1876d69af0507d98f0b0e8d81bee14cfb3f6737470205419", - "sha256:dcaef817e13eafa547cdfdc5284fe77970b891f731266545aae08d6cce52161e", - "sha256:df4e82e68f43a07735ae70a2d84c0353e58e20add20ec0af611f32cd5ba43fb4", - "sha256:ec8cfe2295f3e5e44c51f57272afbd69414ae629ec7c6b27f5a410efc78b70a0", - "sha256:ec9dd328016d8d25702a24ee274932aebf6be9787ed1c28d021945d264235b3c", - "sha256:ef9b85fa1bc91c4db24407e7c4da93a5822a73dd4513d67b454ca7064e8dc6a3", - "sha256:f3bf60444269345d712838bb11cc4eadaf51ff1a364ae39ce87a5ca8ad3bb2c8", - "sha256:f452cc1436151387d3d50533523291d5f77c6bc7913c116eb985304abdbd9ec9", - "sha256:f7917697bcaa3bc3e83db91aa3a0e448bf5cde43c84b7fc1ae2427d2417c0224", - "sha256:f90575e9fe3aae2c1e686393a9689c724cd00045275407f71771ae5d690ccf38", - "sha256:fb382fd7b4377363cc9f13ba7c819c3c78ed97c36a82f16f3f92f108c787cbbf", - "sha256:fb9f59f3848edf186a76446eb8bcf4c900fe147cb756fbbd730ef43b2e67c6a7", - "sha256:fc2931ac9ce9c61c9968989ec831d3a5e6fcaaff9474e7cfa8de80b7aff5a093" + "sha256:0127bc2ea72c1eaae6808ace661f0edf222f32ffa987d37f2dbb4798288f2656", + "sha256:0358b697abdf1f2d68038bd02ef8ddcc4813835744f79c755f8743aa485585e7", + "sha256:06306c74f0775621a70fa5acd292119bbb6961d1f9a5f3d657a4c8c15b86f7b9", + "sha256:06b5b462cadf59c1df28ffbb0a3971fa16b60cf0c9d59a38bf5679a986d18685", + "sha256:097094a979af7b31520517c59179f6817b8426724343cecbec0eb3af1f8fb6cf", + "sha256:0c791a2d42da20ac568e5c0cc9b8af313188becd203a936ad959b578dafbcebb", + "sha256:1656a8b531a96427f26f498b7d0f19931166ff30e4344eca99bdb27faca14fc5", + "sha256:18614630533ac37ec373bd8035aec8fa4dd9aedac641209c06de7e082622ff77", + "sha256:1e5fa4c4e55cdacef1844f609bc9a02c8cd29c324a71ca1d3ee454701d4bb496", + "sha256:1edaf4171fc1582352ac5d9b2783966fa0f4ff86187279ef2a491613d23b894a", + "sha256:2124c642b8cc9b68e5981e429842dadc32bb850b010cccec9d24236253a19f60", + "sha256:229f222bb47cd7ab225648efd1ae47fe6943f18e4c91bce66471faf09fe33128", + "sha256:2429a651a2191c3fb8c9de21546c6046da539034d51dcb8df52302748004593d", + "sha256:25a4e29ee758596b2a0daffa4814714e9b464077ca862baf78ed0e8698e46b61", + "sha256:27c323b28723faed046f906c70466144c4dd12046a0128a301b29a65cfeff758", + "sha256:2add8ed2acf42398dfaa7dffd32e4d18ffbae341d62c8d4765bd9929336379b5", + "sha256:2bece7fdc13e23db005879b67190db0d397f6ba89c81dc7e3c77e9f5819aff7f", + "sha256:2eafb4e92f72a3b6c27f1d5e921d046e2728850af8887f86857c3fe868a5b5c0", + "sha256:32840ff92c713053591ff0e66845d4e9f4bea8fd5fba3da00f8d92e77722f24e", + "sha256:33896afca6fb4e1988c099534c52823870dfc8730bc6f96a3831f24c1e0ab814", + "sha256:350b468a217d433cbb4482e9414a14dfd360a3d5ab92013175925abb234364cc", + "sha256:38cab8f91b1085f1fd0765d40c46c8f43282f109018d5fcd017c46ac3eaba0cf", + "sha256:3e24a778470f3a9e9c11250d09daf5dea93369bc51aefca6605dbc963737a117", + "sha256:4224bbbc8a2e9b9a3828d36c1bab7458441d7fb9fb3af321eb735732ba8ee89d", + "sha256:4424082edff76fe46ff08851e91865097c0ad780fa79b87063dc5d5b80efc9d6", + "sha256:454707fb16f180984da6338d1f51897f0b8d8c4c2e0592d9d1e9fa02a5bb8218", + "sha256:4b1ab96a1ac91bd1233706d638ade35f663684deaa4e5e5f190858cba044afb9", + "sha256:4c5ff3e7609c214667c7d7e00d5f4f3576fefde47ebcb7e492c015117dafebbf", + "sha256:5107d89c9edec6ee077970a95fb9eeb4776ea8c2337b6a39c0ade9a58f50f3e4", + "sha256:5156c12a97405339ec93facbc7860566db381af2de1bec338195563fb64f37ef", + "sha256:553a1e3537aeeb29c0eb29ef28b80e0e801697fa71d96ac60675b284ff8e582a", + "sha256:5e1cc7823f43781390965c4762b54262cfcf76b6f152e489d00a5a1ac63063e4", + "sha256:5eef9804e65eb292e9c5587e88fe6a27a11f121d358312ac47211e8f42876751", + "sha256:6237637b496bc04819190e724a4e61ff2f251abf432f70cf491b3bc4a3f2f253", + "sha256:627bb5bc4ed3d3ebceb9fb55717cec6cd58bb47fdb5669169ebbc248e9bf156c", + "sha256:676d7356bb30825b7dbdad4fdd7a9feac379d074e5d4a36299767d72857ded42", + "sha256:6960b0d2e713e726cb2914e3051e080b12412f70dcb8731cf7a8fb52c37931bb", + "sha256:6b93a666cd8cfd43f605d1b81a32b9e290bf45c74c2bfd51ba705449c78448c7", + "sha256:6ca160b4c649f0d56daef04751eef4571de46ed4b80f9051a87d090fef32f08e", + "sha256:70ac7893e67a81ed1346ee3e71203ca4b0c3550c005b1d1cf87bc1e61eecd04b", + "sha256:73c4af08e9bb9a9aa7df6c789b05b924b9a0c6a368bb0e418d0b85181b64b631", + "sha256:748dcacc19c69957f7063ea4fb359fa2180735b1a638c81a4a96b86a382a6f29", + "sha256:75d9762f65205a86381298eb9079f27c60b84de0c262e402dcf45c6cbc385234", + "sha256:7711d83dafe52cda16ff2dd205cd83c05e4c06d5aaac596ae2cf7d50d094a530", + "sha256:7aa9f9af452c3e8486a0b88fddd58352e6cea17b691b18861d26e46cf65ffff0", + "sha256:7f713d8f3c4e2eac0d91b741e8ef2e1082022de244685601ec83e899b445d86a", + "sha256:81edbd9bf9f25cd995e6d51c307e1d279587d40b7473e258fef6d5e548560cd2", + "sha256:83363a5789f128618041b9a737c7b146f1965abddf4294b0444591406b437c1e", + "sha256:85e273e59b8b1a5f60a89df82cddeaf918181abd7ae7a2f2f899b68b0c774ff1", + "sha256:8ad2e487824ba4cda87851a371139e255410e45d3bf2e334194789278d709cec", + "sha256:8b7f902f13a230686f01bcff17cd9ba045653069811c8fd5027f0f414b417e2f", + "sha256:8f074a24aa9a6a3d406474ec889ebb5d661f329349068e05e8dfcb3c4be67752", + "sha256:9084d99933824ed8d665f10f4ce62d08fed714e7678d5ff11a8c2c98b2dc18f9", + "sha256:928f7a61c4311f3dd003af19bb779f99683f97a0559b765c80fdb8846dab0452", + "sha256:97fcaf530318369da3cfd6ff52f5ab38daf8cb10ecee9a76efebf8031de09eef", + "sha256:994d27b24b61b1870f3571395c840433faabec5dcd239bd11ff6af7e34234bb6", + "sha256:9ae454916aa3abe28d0ef1c21ca1e8e36a14ccf52183d465dfaccffaa7ed462c", + "sha256:9fac5416c44e8e1d8ea9440096f88e1a7273257f3157184c5c715060e0c448a1", + "sha256:a2fe45c1143eefb680a4589c55e671fabd482a7f8c7791f311ea3bcc20139246", + "sha256:a3f8be3e785009ffa148e66474fea5c787ccb203b3d0bd1f22e1e22f7da0f3b3", + "sha256:a616c2e4b60cb8cdd9eb3b0c6fda4ab5f3e26244b427aaade560dcf63c5754fb", + "sha256:a94c9058c5703c172904103d7b479f7e23dd4e5f8e67b49f6cd256d35ff169cb", + "sha256:b1208f2e081d34832f509cbe311237a0543effe23d60b2fa14c0d3f86e6d1d07", + "sha256:b4b25de7e85ba90b2ff230153123b6b000a7f69c41d84a3a0dc3f878334c8509", + "sha256:bbe72c41cdd55c88b238a8925849fde4069c0cdcdef83f8d967f8f3982659326", + "sha256:c0a86dd3e85c6aa3fc73236eb5cf7ce69dd8ad7abcd23f8ae1126831c8e40c2f", + "sha256:c3b08d9e98d1a15338fcfbd52c02003704322c2d460c9b9be7df08f2952bdce6", + "sha256:c4d9c221cc8e32b14196498679bf2b324bec1d1127c4ba934d98e19298faa661", + "sha256:c4f882e42c6cea89488b9a16919edde8c0b1a98f307c05abdd3dd3bc4368af40", + "sha256:c5cc25cbd9ae01d49ac7b504ef5f3cbdcc8d139f9750dcfa0b80d405b4645cc2", + "sha256:c7f2deac59dc3e0528bdded248e637e789e5111ba1723a8d7a262eb93e133e15", + "sha256:c8b034b60e74fb29064f765851e77e5910055e1c4a3cb75c32eccf2b470fc00f", + "sha256:c9b9159eeeb7cd1c7131dc7f5878454f97a4dc20cd157e6474174ccac448b844", + "sha256:c9c405ca78c70c3599d8956e53be0c9def9c51ad949964a49ad96c79729a5b1a", + "sha256:ceb200918c9bd163bd390cc169b254b23b4be121026b003be93a4f2f5b554b4b", + "sha256:d06040266b5e6512a37b4703684d1798124764b43328254799e9678c588882a6", + "sha256:d3f5e201bd170fb97c643e84df58e221372cd053fbb291ebbd878b165ea5057e", + "sha256:d4aa7cca009817789fd5b8e52e8122f9e85dc580c88b816a93321c00a8acbced", + "sha256:d772ae3c12d3b8629db656050c86ee66924eaa98f7125a889175a59cfaafdb19", + "sha256:d816969b55a970b3accc7f9e4ea8f60043e3f7de96f21c06063d747ffc2f18ba", + "sha256:d885dcdca7bae42bc9a2f6cbf766abcb2a6cc043b1905fc3782c6ea1f74a2b95", + "sha256:db903458a457a53ee0f764ed11c5b5368398e216b442c42dca9d90fbd2bbf31c", + "sha256:dc63bb79e896d6ce6aaf672ac304b54969280e949c45727867fc154a17ec7ab2", + "sha256:dd042e6c3bf36448e3e3ed302b12ce79762480f4aff8e7a167cdf8c35dc93297", + "sha256:ddea4abc4606c10dddb70651b210b7ab5b663148d6d7bc85d76963c923629891", + "sha256:df57f3c3ef760489f2e82192e6c93286c2bc80d6d854ef940e5345ae7153cd4b", + "sha256:e1ddf05eeb422810b1aa919095db0691493442eebbf9cfb0f1e478a7b2fbdf3d", + "sha256:e2e3cb74684ff357e6b3c82dd71031d3c1fd7ee9f9b0a5205e5568c963e074f9", + "sha256:e4f64c8c52dde564bf3251b41d7a6746564b0fc0516cebe9c9e6695224440d22", + "sha256:e4f7efb38331e8327c1cc7fb2a2905a7db03d1a7fdb04706bf6465d0e44d41d4", + "sha256:e61b2019ebb5345510b833c4dd1f4afb1f0c07753f86f184c63836ffc3fb08ba", + "sha256:e7e38bf6e52797084c5c396db5bb519615727e491e9003e2449631457bf77738", + "sha256:eae041f535fe2e57681954f7cccb81854d777ce4c2a87749428ebe6c71c02ec0", + "sha256:eb964d18c01b7a1263a6f07b88d63711fcd564fc429d934279cf12f4b467bf53", + "sha256:ef780f9d480ffb423380abeb4cfcad66ecb8f93526dfa367d322fdad9ec7c25f", + "sha256:efc0430b80ed834c80c99c32946cfc6ee29dfcd7c62ad3c8f15657322ade7942", + "sha256:f2508ee2bad8381b5254eadc35d32fe800d12eb2c63b744183341f3a66e435a7", + "sha256:fee9acd5e39c8611957074dfba06552e430020eea831caf5eb2cea30f10e06bd" ], "markers": "python_version >= '3.8'", - "version": "==1.13.1" + "version": "==1.15.0" }, "zope.event": { "hashes": [ @@ -2912,43 +3077,46 @@ }, "zope.interface": { "hashes": [ - "sha256:01e6e58078ad2799130c14a1d34ec89044ada0e1495329d72ee0407b9ae5100d", - "sha256:064ade95cb54c840647205987c7b557f75d2b2f7d1a84bfab4cf81822ef6e7d1", - "sha256:11fa1382c3efb34abf16becff8cb214b0b2e3144057c90611621f2d186b7e1b7", - "sha256:1bee1b722077d08721005e8da493ef3adf0b7908e0cd85cc7dc836ac117d6f32", - "sha256:1eeeb92cb7d95c45e726e3c1afe7707919370addae7ed14f614e22217a536958", - "sha256:21a207c6b2c58def5011768140861a73f5240f4f39800625072ba84e76c9da0b", - "sha256:2545d6d7aac425d528cd9bf0d9e55fcd47ab7fd15f41a64b1c4bf4c6b24946dc", - "sha256:2c4316a30e216f51acbd9fb318aa5af2e362b716596d82cbb92f9101c8f8d2e7", - "sha256:35062d93bc49bd9b191331c897a96155ffdad10744ab812485b6bad5b588d7e4", - "sha256:382d31d1e68877061daaa6499468e9eb38eb7625d4369b1615ac08d3860fe896", - "sha256:3aa8fcbb0d3c2be1bfd013a0f0acd636f6ed570c287743ae2bbd467ee967154d", - "sha256:3d4b91821305c8d8f6e6207639abcbdaf186db682e521af7855d0bea3047c8ca", - "sha256:3de1d553ce72868b77a7e9d598c9bff6d3816ad2b4cc81c04f9d8914603814f3", - "sha256:3fcdc76d0cde1c09c37b7c6b0f8beba2d857d8417b055d4f47df9c34ec518bdd", - "sha256:5112c530fa8aa2108a3196b9c2f078f5738c1c37cfc716970edc0df0414acda8", - "sha256:53d678bb1c3b784edbfb0adeebfeea6bf479f54da082854406a8f295d36f8386", - "sha256:6195c3c03fef9f87c0dbee0b3b6451df6e056322463cf35bca9a088e564a3c58", - "sha256:6d04b11ea47c9c369d66340dbe51e9031df2a0de97d68f442305ed7625ad6493", - "sha256:6dd647fcd765030638577fe6984284e0ebba1a1008244c8a38824be096e37fe3", - "sha256:799ef7a444aebbad5a145c3b34bff012b54453cddbde3332d47ca07225792ea4", - "sha256:7d92920416f31786bc1b2f34cc4fc4263a35a407425319572cbf96b51e835cd3", - "sha256:7e0c151a6c204f3830237c59ee4770cc346868a7a1af6925e5e38650141a7f05", - "sha256:84f8794bd59ca7d09d8fce43ae1b571be22f52748169d01a13d3ece8394d8b5b", - "sha256:95e5913ec718010dc0e7c215d79a9683b4990e7026828eedfda5268e74e73e11", - "sha256:9b9369671a20b8d039b8e5a1a33abd12e089e319a3383b4cc0bf5c67bd05fe7b", - "sha256:ab985c566a99cc5f73bc2741d93f1ed24a2cc9da3890144d37b9582965aff996", - "sha256:af94e429f9d57b36e71ef4e6865182090648aada0cb2d397ae2b3f7fc478493a", - "sha256:c96b3e6b0d4f6ddfec4e947130ec30bd2c7b19db6aa633777e46c8eecf1d6afd", - "sha256:cd2690d4b08ec9eaf47a85914fe513062b20da78d10d6d789a792c0b20307fb1", - "sha256:d3b7ce6d46fb0e60897d62d1ff370790ce50a57d40a651db91a3dde74f73b738", - "sha256:d976fa7b5faf5396eb18ce6c132c98e05504b52b60784e3401f4ef0b2e66709b", - "sha256:db6237e8fa91ea4f34d7e2d16d74741187e9105a63bbb5686c61fea04cdbacca", - "sha256:ecd32f30f40bfd8511b17666895831a51b532e93fc106bfa97f366589d3e4e0e", - "sha256:f418c88f09c3ba159b95a9d1cfcdbe58f208443abb1f3109f4b9b12fd60b187c" + "sha256:07add15de0cc7e69917f7d286b64d54125c950aeb43efed7a5ea7172f000fbc1", + "sha256:0ac20581fc6cd7c754f6dff0ae06fedb060fa0e9ea6309d8be8b2701d9ea51c4", + "sha256:124149e2d42067b9c6597f4dafdc7a0983d0163868f897b7bb5dc850b14f9a87", + "sha256:27cfb5205d68b12682b6e55ab8424662d96e8ead19550aad0796b08dd2c9a45e", + "sha256:2a29ac607e970b5576547f0e3589ec156e04de17af42839eedcf478450687317", + "sha256:2b6a4924f5bad9fe21d99f66a07da60d75696a136162427951ec3cb223a5570d", + "sha256:2bd9e9f366a5df08ebbdc159f8224904c1c5ce63893984abb76954e6fbe4381a", + "sha256:3bcff5c09d0215f42ba64b49205a278e44413d9bf9fa688fd9e42bfe472b5f4f", + "sha256:3f005869a1a05e368965adb2075f97f8ee9a26c61898a9e52a9764d93774f237", + "sha256:4a00ead2e24c76436e1b457a5132d87f83858330f6c923640b7ef82d668525d1", + "sha256:4af4a12b459a273b0b34679a5c3dc5e34c1847c3dd14a628aa0668e19e638ea2", + "sha256:5501e772aff595e3c54266bc1bfc5858e8f38974ce413a8f1044aae0f32a83a3", + "sha256:5e28ea0bc4b084fc93a483877653a033062435317082cdc6388dec3438309faf", + "sha256:5e956b1fd7f3448dd5e00f273072e73e50dfafcb35e4227e6d5af208075593c9", + "sha256:5fcf379b875c610b5a41bc8a891841533f98de0520287d7f85e25386cd10d3e9", + "sha256:6159e767d224d8f18deff634a1d3722e68d27488c357f62ebeb5f3e2f5288b1f", + "sha256:661d5df403cd3c5b8699ac480fa7f58047a3253b029db690efa0c3cf209993ef", + "sha256:711eebc77f2092c6a8b304bad0b81a6ce3cf5490b25574e7309fbc07d881e3af", + "sha256:80a3c00b35f6170be5454b45abe2719ea65919a2f09e8a6e7b1362312a872cd3", + "sha256:848b6fa92d7c8143646e64124ed46818a0049a24ecc517958c520081fd147685", + "sha256:91b6c30689cfd87c8f264acb2fc16ad6b3c72caba2aec1bf189314cf1a84ca33", + "sha256:9733a9a0f94ef53d7aa64661811b20875b5bc6039034c6e42fb9732170130573", + "sha256:9940d5bc441f887c5f375ec62bcf7e7e495a2d5b1da97de1184a88fb567f06af", + "sha256:9e3e48f3dea21c147e1b10c132016cb79af1159facca9736d231694ef5a740a8", + "sha256:a14c9decf0eb61e0892631271d500c1e306c7b6901c998c7035e194d9150fdd1", + "sha256:a735f82d2e3ed47ca01a20dfc4c779b966b16352650a8036ab3955aad151ed8a", + "sha256:a99240b1d02dc469f6afbe7da1bf617645e60290c272968f4e53feec18d7dce8", + "sha256:b7b25db127db3e6b597c5f74af60309c4ad65acd826f89609662f0dc33a54728", + "sha256:b936d61dbe29572fd2cfe13e30b925e5383bed1aba867692670f5a2a2eb7b4e9", + "sha256:bec001798ab62c3fc5447162bf48496ae9fba02edc295a9e10a0b0c639a6452e", + "sha256:cc8a318162123eddbdf22fcc7b751288ce52e4ad096d3766ff1799244352449d", + "sha256:d0a45b5af9f72c805ee668d1479480ca85169312211bed6ed18c343e39307d5f", + "sha256:e53c291debef523b09e1fe3dffe5f35dde164f1c603d77f770b88a1da34b7ed6", + "sha256:ec1ef1fdb6f014d5886b97e52b16d0f852364f447d2ab0f0c6027765777b6667", + "sha256:ec59fe53db7d32abb96c6d4efeed84aab4a7c38c62d7a901a9b20c09dd936e7a", + "sha256:f245d039f72e6f802902375755846f5de1ee1e14c3e8736c078565599bcab621", + "sha256:ff115ef91c0eeac69cd92daeba36a9d8e14daee445b504eeea2b1c0b55821984" ], "markers": "python_version >= '3.8'", - "version": "==7.0.3" + "version": "==7.1.0" } }, "develop": { @@ -2978,99 +3146,114 @@ }, "charset-normalizer": { "hashes": [ - "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027", - "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087", - "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786", - "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8", - "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09", - "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185", - "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574", - "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e", - "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519", - "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898", - "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269", - "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3", - "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f", - "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6", - "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8", - "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a", - "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73", - "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc", - "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714", - "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2", - "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc", - "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce", - "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d", - "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e", - "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6", - "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269", - "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96", - "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d", - "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a", - "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4", - "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77", - "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d", - "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0", - "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed", - "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068", - "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac", - "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25", - "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8", - "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab", - "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26", - "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2", - "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db", - "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f", - "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5", - "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99", - "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c", - "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d", - "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811", - "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa", - "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a", - "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03", - "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b", - "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04", - "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c", - "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001", - "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458", - "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389", - "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99", - "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985", - "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537", - "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238", - "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f", - "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d", - "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796", - "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a", - "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143", - "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8", - "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c", - "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5", - "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5", - "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711", - "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4", - "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6", - "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c", - "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7", - "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4", - "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b", - "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae", - "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12", - "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c", - "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae", - "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8", - "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887", - "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b", - "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4", - "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f", - "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5", - "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33", - "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519", - "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561" + "sha256:0099d79bdfcf5c1f0c2c72f91516702ebf8b0b8ddd8905f97a8aecf49712c621", + "sha256:0713f3adb9d03d49d365b70b84775d0a0d18e4ab08d12bc46baa6132ba78aaf6", + "sha256:07afec21bbbbf8a5cc3651aa96b980afe2526e7f048fdfb7f1014d84acc8b6d8", + "sha256:0b309d1747110feb25d7ed6b01afdec269c647d382c857ef4663bbe6ad95a912", + "sha256:0d99dd8ff461990f12d6e42c7347fd9ab2532fb70e9621ba520f9e8637161d7c", + "sha256:0de7b687289d3c1b3e8660d0741874abe7888100efe14bd0f9fd7141bcbda92b", + "sha256:1110e22af8ca26b90bd6364fe4c763329b0ebf1ee213ba32b68c73de5752323d", + "sha256:130272c698667a982a5d0e626851ceff662565379baf0ff2cc58067b81d4f11d", + "sha256:136815f06a3ae311fae551c3df1f998a1ebd01ddd424aa5603a4336997629e95", + "sha256:14215b71a762336254351b00ec720a8e85cada43b987da5a042e4ce3e82bd68e", + "sha256:1db4e7fefefd0f548d73e2e2e041f9df5c59e178b4c72fbac4cc6f535cfb1565", + "sha256:1ffd9493de4c922f2a38c2bf62b831dcec90ac673ed1ca182fe11b4d8e9f2a64", + "sha256:2006769bd1640bdf4d5641c69a3d63b71b81445473cac5ded39740a226fa88ab", + "sha256:20587d20f557fe189b7947d8e7ec5afa110ccf72a3128d61a2a387c3313f46be", + "sha256:223217c3d4f82c3ac5e29032b3f1c2eb0fb591b72161f86d93f5719079dae93e", + "sha256:27623ba66c183eca01bf9ff833875b459cad267aeeb044477fedac35e19ba907", + "sha256:285e96d9d53422efc0d7a17c60e59f37fbf3dfa942073f666db4ac71e8d726d0", + "sha256:2de62e8801ddfff069cd5c504ce3bc9672b23266597d4e4f50eda28846c322f2", + "sha256:2f6c34da58ea9c1a9515621f4d9ac379871a8f21168ba1b5e09d74250de5ad62", + "sha256:309a7de0a0ff3040acaebb35ec45d18db4b28232f21998851cfa709eeff49d62", + "sha256:35c404d74c2926d0287fbd63ed5d27eb911eb9e4a3bb2c6d294f3cfd4a9e0c23", + "sha256:3710a9751938947e6327ea9f3ea6332a09bf0ba0c09cae9cb1f250bd1f1549bc", + "sha256:3d59d125ffbd6d552765510e3f31ed75ebac2c7470c7274195b9161a32350284", + "sha256:40d3ff7fc90b98c637bda91c89d51264a3dcf210cade3a2c6f838c7268d7a4ca", + "sha256:425c5f215d0eecee9a56cdb703203dda90423247421bf0d67125add85d0c4455", + "sha256:43193c5cda5d612f247172016c4bb71251c784d7a4d9314677186a838ad34858", + "sha256:44aeb140295a2f0659e113b31cfe92c9061622cadbc9e2a2f7b8ef6b1e29ef4b", + "sha256:47334db71978b23ebcf3c0f9f5ee98b8d65992b65c9c4f2d34c2eaf5bcaf0594", + "sha256:4796efc4faf6b53a18e3d46343535caed491776a22af773f366534056c4e1fbc", + "sha256:4a51b48f42d9358460b78725283f04bddaf44a9358197b889657deba38f329db", + "sha256:4b67fdab07fdd3c10bb21edab3cbfe8cf5696f453afce75d815d9d7223fbe88b", + "sha256:4ec9dd88a5b71abfc74e9df5ebe7921c35cbb3b641181a531ca65cdb5e8e4dea", + "sha256:4f9fc98dad6c2eaa32fc3af1417d95b5e3d08aff968df0cd320066def971f9a6", + "sha256:54b6a92d009cbe2fb11054ba694bc9e284dad30a26757b1e372a1fdddaf21920", + "sha256:55f56e2ebd4e3bc50442fbc0888c9d8c94e4e06a933804e2af3e89e2f9c1c749", + "sha256:5726cf76c982532c1863fb64d8c6dd0e4c90b6ece9feb06c9f202417a31f7dd7", + "sha256:5d447056e2ca60382d460a604b6302d8db69476fd2015c81e7c35417cfabe4cd", + "sha256:5ed2e36c3e9b4f21dd9422f6893dec0abf2cca553af509b10cd630f878d3eb99", + "sha256:5ff2ed8194587faf56555927b3aa10e6fb69d931e33953943bc4f837dfee2242", + "sha256:62f60aebecfc7f4b82e3f639a7d1433a20ec32824db2199a11ad4f5e146ef5ee", + "sha256:63bc5c4ae26e4bc6be6469943b8253c0fd4e4186c43ad46e713ea61a0ba49129", + "sha256:6b40e8d38afe634559e398cc32b1472f376a4099c75fe6299ae607e404c033b2", + "sha256:6b493a043635eb376e50eedf7818f2f322eabbaa974e948bd8bdd29eb7ef2a51", + "sha256:6dba5d19c4dfab08e58d5b36304b3f92f3bd5d42c1a3fa37b5ba5cdf6dfcbcee", + "sha256:6fd30dc99682dc2c603c2b315bded2799019cea829f8bf57dc6b61efde6611c8", + "sha256:707b82d19e65c9bd28b81dde95249b07bf9f5b90ebe1ef17d9b57473f8a64b7b", + "sha256:7706f5850360ac01d80c89bcef1640683cc12ed87f42579dab6c5d3ed6888613", + "sha256:7782afc9b6b42200f7362858f9e73b1f8316afb276d316336c0ec3bd73312742", + "sha256:79983512b108e4a164b9c8d34de3992f76d48cadc9554c9e60b43f308988aabe", + "sha256:7f683ddc7eedd742e2889d2bfb96d69573fde1d92fcb811979cdb7165bb9c7d3", + "sha256:82357d85de703176b5587dbe6ade8ff67f9f69a41c0733cf2425378b49954de5", + "sha256:84450ba661fb96e9fd67629b93d2941c871ca86fc38d835d19d4225ff946a631", + "sha256:86f4e8cca779080f66ff4f191a685ced73d2f72d50216f7112185dc02b90b9b7", + "sha256:8cda06946eac330cbe6598f77bb54e690b4ca93f593dee1568ad22b04f347c15", + "sha256:8ce7fd6767a1cc5a92a639b391891bf1c268b03ec7e021c7d6d902285259685c", + "sha256:8ff4e7cdfdb1ab5698e675ca622e72d58a6fa2a8aa58195de0c0061288e6e3ea", + "sha256:9289fd5dddcf57bab41d044f1756550f9e7cf0c8e373b8cdf0ce8773dc4bd417", + "sha256:92a7e36b000bf022ef3dbb9c46bfe2d52c047d5e3f3343f43204263c5addc250", + "sha256:92db3c28b5b2a273346bebb24857fda45601aef6ae1c011c0a997106581e8a88", + "sha256:95c3c157765b031331dd4db3c775e58deaee050a3042fcad72cbc4189d7c8dca", + "sha256:980b4f289d1d90ca5efcf07958d3eb38ed9c0b7676bf2831a54d4f66f9c27dfa", + "sha256:9ae4ef0b3f6b41bad6366fb0ea4fc1d7ed051528e113a60fa2a65a9abb5b1d99", + "sha256:9c98230f5042f4945f957d006edccc2af1e03ed5e37ce7c373f00a5a4daa6149", + "sha256:9fa2566ca27d67c86569e8c85297aaf413ffab85a8960500f12ea34ff98e4c41", + "sha256:a14969b8691f7998e74663b77b4c36c0337cb1df552da83d5c9004a93afdb574", + "sha256:a8aacce6e2e1edcb6ac625fb0f8c3a9570ccc7bfba1f63419b3769ccf6a00ed0", + "sha256:a8e538f46104c815be19c975572d74afb53f29650ea2025bbfaef359d2de2f7f", + "sha256:aa41e526a5d4a9dfcfbab0716c7e8a1b215abd3f3df5a45cf18a12721d31cb5d", + "sha256:aa693779a8b50cd97570e5a0f343538a8dbd3e496fa5dcb87e29406ad0299654", + "sha256:ab22fbd9765e6954bc0bcff24c25ff71dcbfdb185fcdaca49e81bac68fe724d3", + "sha256:ab2e5bef076f5a235c3774b4f4028a680432cded7cad37bba0fd90d64b187d19", + "sha256:ab973df98fc99ab39080bfb0eb3a925181454d7c3ac8a1e695fddfae696d9e90", + "sha256:af73657b7a68211996527dbfeffbb0864e043d270580c5aef06dc4b659a4b578", + "sha256:b197e7094f232959f8f20541ead1d9862ac5ebea1d58e9849c1bf979255dfac9", + "sha256:b295729485b06c1a0683af02a9e42d2caa9db04a373dc38a6a58cdd1e8abddf1", + "sha256:b8831399554b92b72af5932cdbbd4ddc55c55f631bb13ff8fe4e6536a06c5c51", + "sha256:b8dcd239c743aa2f9c22ce674a145e0a25cb1566c495928440a181ca1ccf6719", + "sha256:bcb4f8ea87d03bc51ad04add8ceaf9b0f085ac045ab4d74e73bbc2dc033f0236", + "sha256:bd7af3717683bea4c87acd8c0d3d5b44d56120b26fd3f8a692bdd2d5260c620a", + "sha256:bf4475b82be41b07cc5e5ff94810e6a01f276e37c2d55571e3fe175e467a1a1c", + "sha256:c3e446d253bd88f6377260d07c895816ebf33ffffd56c1c792b13bff9c3e1ade", + "sha256:c57516e58fd17d03ebe67e181a4e4e2ccab1168f8c2976c6a334d4f819fe5944", + "sha256:c94057af19bc953643a33581844649a7fdab902624d2eb739738a30e2b3e60fc", + "sha256:cab5d0b79d987c67f3b9e9c53f54a61360422a5a0bc075f43cab5621d530c3b6", + "sha256:ce031db0408e487fd2775d745ce30a7cd2923667cf3b69d48d219f1d8f5ddeb6", + "sha256:cee4373f4d3ad28f1ab6290684d8e2ebdb9e7a1b74fdc39e4c211995f77bec27", + "sha256:d5b054862739d276e09928de37c79ddeec42a6e1bfc55863be96a36ba22926f6", + "sha256:dbe03226baf438ac4fda9e2d0715022fd579cb641c4cf639fa40d53b2fe6f3e2", + "sha256:dc15e99b2d8a656f8e666854404f1ba54765871104e50c8e9813af8a7db07f12", + "sha256:dcaf7c1524c0542ee2fc82cc8ec337f7a9f7edee2532421ab200d2b920fc97cf", + "sha256:dd4eda173a9fcccb5f2e2bd2a9f423d180194b1bf17cf59e3269899235b2a114", + "sha256:dd9a8bd8900e65504a305bf8ae6fa9fbc66de94178c420791d0293702fce2df7", + "sha256:de7376c29d95d6719048c194a9cf1a1b0393fbe8488a22008610b0361d834ecf", + "sha256:e7fdd52961feb4c96507aa649550ec2a0d527c086d284749b2f582f2d40a2e0d", + "sha256:e91f541a85298cf35433bf66f3fab2a4a2cff05c127eeca4af174f6d497f0d4b", + "sha256:e9e3c4c9e1ed40ea53acf11e2a386383c3304212c965773704e4603d589343ed", + "sha256:ee803480535c44e7f5ad00788526da7d85525cfefaf8acf8ab9a310000be4b03", + "sha256:f09cb5a7bbe1ecae6e87901a2eb23e0256bb524a79ccc53eb0b7629fbe7677c4", + "sha256:f19c1585933c82098c2a520f8ec1227f20e339e33aca8fa6f956f6691b784e67", + "sha256:f1a2f519ae173b5b6a2c9d5fa3116ce16e48b3462c8b96dfdded11055e3d6365", + "sha256:f28f891ccd15c514a0981f3b9db9aa23d62fe1a99997512b0491d2ed323d229a", + "sha256:f3e73a4255342d4eb26ef6df01e3962e73aa29baa3124a8e824c5d3364a65748", + "sha256:f606a1881d2663630ea5b8ce2efe2111740df4b687bd78b34a8131baa007f79b", + "sha256:fe9f97feb71aa9896b81973a7bbada8c49501dc73e58a10fcef6663af95e5079", + "sha256:ffc519621dce0c767e96b9c53f09c5d215578e10b02c285809f76509a3931482" ], "markers": "python_full_version >= '3.7.0'", - "version": "==3.3.2" + "version": "==3.4.0" }, "idna": { "hashes": [ diff --git a/api/scheduler.py b/api/scheduler.py index d05f3bc..5daa379 100644 --- a/api/scheduler.py +++ b/api/scheduler.py @@ -1,11 +1,13 @@ from apscheduler.schedulers.asyncio import AsyncIOScheduler from apscheduler.triggers.cron import CronTrigger + from db.config import settings from mediafusion_scrapy.task import run_spider from scrapers.imdb_data import fetch_movie_ids_to_update +from scrapers.prowlarr_feed import run_prowlarr_feed_scraper from scrapers.trackers import update_torrent_seeders from scrapers.tv import validate_tv_streams_in_db -from scrapers.prowlarr_feed import run_prowlarr_feed_scraper +from scrapers.utils import cleanup_expired_scraper_task def setup_scheduler(scheduler: AsyncIOScheduler): @@ -218,3 +220,12 @@ def setup_scheduler(scheduler: AsyncIOScheduler): "crontab_expression": settings.prowlarr_feed_scraper_crontab, }, ) + + scheduler.add_job( + cleanup_expired_scraper_task.send, + CronTrigger.from_crontab(settings.cleanup_expired_scraper_task_crontab), + name="cleanup_expired_scraper_task", + kwargs={ + "crontab_expression": settings.cleanup_expired_scraper_task_crontab, + }, + ) diff --git a/db/config.py b/db/config.py index d1ca4d2..0356449 100644 --- a/db/config.py +++ b/db/config.py @@ -23,10 +23,12 @@ class Settings(BaseSettings): # External Service URLs scraper_proxy_url: str | None = None - zilean_url: str = "http://zilean.zilean:8181" playwright_cdp_url: str = "ws://browserless:3000?blockAds=true&stealth=true" flaresolverr_url: str = "http://flaresolverr:8191/v1" + # External Service API Keys + scrapeops_api_key: str | None = None + # Prowlarr Settings prowlarr_url: str = "http://prowlarr-service:9696" prowlarr_api_key: str | None = None @@ -42,6 +44,10 @@ class Settings(BaseSettings): torrentio_search_interval_days: int = 3 torrentio_url: str = "https://torrentio.strem.fun" + # Zilean Settings + zilean_search_interval_hour: int = 24 + zilean_url: str = "http://zilean.zilean:8181" + # Premiumize Settings premiumize_oauth_client_id: str | None = None premiumize_oauth_client_secret: str | None = None @@ -93,7 +99,7 @@ class Settings(BaseSettings): streamed_scheduler_crontab: str = "*/30 * * * *" disable_streamed_scheduler: bool = False streambtw_scheduler_crontab: str = "*/15 * * * *" - disable_streambtw_scheduler: bool = False + disable_streambtw_scheduler: bool = True dlhd_scheduler_crontab: str = "25 * * * *" disable_dlhd_scheduler: bool = False update_imdb_data_crontab: str = "0 2 * * *" @@ -108,6 +114,7 @@ class Settings(BaseSettings): disable_ufc_tgx_scheduler: bool = False prowlarr_feed_scraper_crontab: str = "0 */3 * * *" disable_prowlarr_feed_scraper: bool = False + cleanup_expired_scraper_task_crontab: str = "0 * * * *" @model_validator(mode="after") def default_poster_host_url(self) -> "Settings": diff --git a/db/models.py b/db/models.py index 41bb9fb..3cd35fa 100644 --- a/db/models.py +++ b/db/models.py @@ -154,6 +154,7 @@ class MediaFusionMetaData(Document): runtime: Optional[str] = None website: Optional[str] = None genres: Optional[list[str]] = Field(default_factory=list) + last_updated_at: datetime = Field(default_factory=datetime.now) class Settings: is_root = True diff --git a/db/schemas.py b/db/schemas.py index 8eb4740..4117b07 100644 --- a/db/schemas.py +++ b/db/schemas.py @@ -109,6 +109,7 @@ class QBittorrentConfig(BaseModel): class MediaFlowConfig(BaseModel): proxy_url: str | None = Field(alias="pu") api_password: str | None = Field(alias="ap") + public_ip: str | None = Field(alias="pip") proxy_live_streams: bool = Field(default=False, alias="pls") proxy_debrid_streams: bool = Field(default=False, alias="pds") diff --git a/deployment/docker-compose/docker-compose.yml b/deployment/docker-compose/docker-compose.yml index cd7e223..d2c3652 100644 --- a/deployment/docker-compose/docker-compose.yml +++ b/deployment/docker-compose/docker-compose.yml @@ -2,7 +2,7 @@ version: '3.8' services: mediafusion: - image: mhdzumair/mediafusion:v4.0.1 + image: mhdzumair/mediafusion:v4.0.2 ports: - "8000:8000" env_file: @@ -41,7 +41,7 @@ services: - "6379:6379" dramatiq-worker: - image: mhdzumair/mediafusion:v4.0.1 + image: mhdzumair/mediafusion:v4.0.2 command: ["pipenv", "run", "dramatiq", "api.task", "-p", "1", "-t", "1", "--queues", "scrapy"] env_file: - .env diff --git a/deployment/k8s/local-deployment.yaml b/deployment/k8s/local-deployment.yaml index 875bae8..a0fc270 100644 --- a/deployment/k8s/local-deployment.yaml +++ b/deployment/k8s/local-deployment.yaml @@ -19,7 +19,7 @@ spec: spec: containers: - name: mediafusion - image: mhdzumair/mediafusion:v4.0.1 + image: mhdzumair/mediafusion:v4.0.2 ports: - containerPort: 8000 resources: @@ -124,7 +124,7 @@ spec: spec: containers: - name: dramatiq-worker - image: mhdzumair/mediafusion:v4.0.1 + image: mhdzumair/mediafusion:v4.0.2 command: ["pipenv", "run", "dramatiq", "api.task", "-p", "1", "-t", "1"] env: - name: HOST_URL diff --git a/kodi/plugin.video.mediafusion/addon.xml b/kodi/plugin.video.mediafusion/addon.xml index 208d683..d7068cf 100644 --- a/kodi/plugin.video.mediafusion/addon.xml +++ b/kodi/plugin.video.mediafusion/addon.xml @@ -1,5 +1,5 @@ - + diff --git a/mediafusion_scrapy/extensions.py b/mediafusion_scrapy/extensions.py new file mode 100644 index 0000000..9e06b35 --- /dev/null +++ b/mediafusion_scrapy/extensions.py @@ -0,0 +1,63 @@ +import time +from datetime import datetime, timedelta + +from scrapy import signals +from scrapy.exceptions import NotConfigured, CloseSpider +from scrapy.utils.log import logger +from twisted.internet import task + + +class InactivityMonitor: + """Monitor spider for inactivity and close if inactive for too long""" + + def __init__(self, crawler, interval=60.0, inactivity_timeout=15): + self.crawler = crawler + self.stats = crawler.stats + self.interval = interval + self.inactivity_timeout = inactivity_timeout + self.task = None + self.last_scraped_time = time.monotonic() + + @classmethod + def from_crawler(cls, crawler): + interval = crawler.settings.getfloat("INACTIVITY_CHECK_INTERVAL", 60.0) + inactivity_timeout = crawler.settings.getint("INACTIVITY_TIMEOUT_MINUTES", 15) + + if not interval or not inactivity_timeout: + raise NotConfigured + + ext = cls(crawler, interval, inactivity_timeout) + + crawler.signals.connect(ext.spider_opened, signal=signals.spider_opened) + crawler.signals.connect(ext.spider_closed, signal=signals.spider_closed) + crawler.signals.connect(ext.item_scraped, signal=signals.item_scraped) + + return ext + + def spider_opened(self, spider): + self.last_scraped_time = time.monotonic() + self.task = task.LoopingCall(self.check_inactivity, spider) + self.task.start(self.interval) + + def check_inactivity(self, spider): + time_since_last_scrape_seconds = time.monotonic() - self.last_scraped_time + time_since_last_scrape = timedelta(seconds=time_since_last_scrape_seconds) + logger.debug( + "Checking inactivity for spider %s, last scraped %s ago", + spider.name, + time_since_last_scrape, + ) + if time_since_last_scrape > timedelta(minutes=self.inactivity_timeout): + msg = f"No items scraped in the last {self.inactivity_timeout} minutes. Closing spider." + logger.info(msg, extra={"spider": spider}) + self.crawler.engine.close_spider( + spider, reason=f"Inactivity timeout: {msg}" + ) + raise CloseSpider(f"Inactivity timeout: {msg}") + + def item_scraped(self, item, response, spider): + self.last_scraped_time = datetime.now() + + def spider_closed(self, spider, reason): + if self.task and self.task.running: + self.task.stop() diff --git a/mediafusion_scrapy/pipelines/live_stream_resolver_pipeline.py b/mediafusion_scrapy/pipelines/live_stream_resolver_pipeline.py index 8a97772..94272db 100644 --- a/mediafusion_scrapy/pipelines/live_stream_resolver_pipeline.py +++ b/mediafusion_scrapy/pipelines/live_stream_resolver_pipeline.py @@ -12,6 +12,7 @@ class LiveStreamResolverPipeline: adapter = ItemAdapter(item) stream_url = adapter.get("stream_url") stream_headers = adapter.get("stream_headers") + response_headers = adapter.get("response_headers", {}) if not stream_headers: referer = adapter.get("referer") stream_headers = {"Referer": referer} if referer else {} @@ -30,7 +31,9 @@ class LiveStreamResolverPipeline: ) ) ) - content_type = response.headers.get("Content-Type", b"").decode().lower() + content_type = response_headers.get( + "Content-Type", response.headers.get("Content-Type", b"").decode().lower() + ) if response.status == 200 and content_type in const.IPTV_VALID_CONTENT_TYPES: stream_headers.update( @@ -49,6 +52,7 @@ class LiveStreamResolverPipeline: "notWebReady": True, "proxyHeaders": { "request": stream_headers, + "response": response_headers, }, }, } diff --git a/mediafusion_scrapy/pipelines/sport_video_parser_pipeline.py b/mediafusion_scrapy/pipelines/sport_video_parser_pipeline.py index 6ffdf95..02a8401 100644 --- a/mediafusion_scrapy/pipelines/sport_video_parser_pipeline.py +++ b/mediafusion_scrapy/pipelines/sport_video_parser_pipeline.py @@ -22,7 +22,7 @@ class SportVideoParserPipeline: def process_item(self, item, spider): adapter = ItemAdapter(item) if "title" not in adapter or "torrent_name" not in adapter: - raise DropItem(f"title not found in item: {item}") + raise DropItem(f"title or torrent_name not found in item: {item}") match = self.title_regex.search(adapter["title"]) or self.title_regex.search( adapter["torrent_name"] diff --git a/mediafusion_scrapy/settings.py b/mediafusion_scrapy/settings.py index 7513d32..dd67e18 100644 --- a/mediafusion_scrapy/settings.py +++ b/mediafusion_scrapy/settings.py @@ -51,16 +51,30 @@ SPIDER_MIDDLEWARES = { # Enable or disable downloader middlewares # See https://docs.scrapy.org/en/latest/topics/downloader-middleware.html + DOWNLOADER_MIDDLEWARES = { "mediafusion_scrapy.middlewares.FlaresolverrMiddleware": 542, "mediafusion_scrapy.middlewares.TooManyRequestsRetryMiddleware": 543, + "scrapy.downloadermiddlewares.retry.RetryMiddleware": 550, } -# Enable or disable extensions -# See https://docs.scrapy.org/en/latest/topics/extensions.html -# EXTENSIONS = { -# "scrapy.extensions.telnet.TelnetConsole": None, -# } +EXTENSIONS = { + "mediafusion_scrapy.extensions.InactivityMonitor": 100, +} + +if settings.scrapeops_api_key: + SCRAPEOPS_API_KEY = settings.scrapeops_api_key + DOWNLOADER_MIDDLEWARES.update( + { + "scrapeops_scrapy.middleware.retry.RetryMiddleware": 550, + "scrapy.downloadermiddlewares.retry.RetryMiddleware": None, + } + ) + EXTENSIONS.update( + { + "scrapeops_scrapy.extension.ScrapeOpsMonitor": 500, # ScrapeOps Monitor + } + ) # Configure item pipelines # See https://docs.scrapy.org/en/latest/topics/item-pipeline.html @@ -114,3 +128,5 @@ RETRY_HTTP_CODES = [ RETRY_TIMES = 5 FLARESOLVERR_URL = settings.flaresolverr_url + +INACTIVITY_TIMEOUT_MINUTES = 15 diff --git a/mediafusion_scrapy/spiders/dlhd.py b/mediafusion_scrapy/spiders/dlhd.py index c848049..3d3a4b1 100644 --- a/mediafusion_scrapy/spiders/dlhd.py +++ b/mediafusion_scrapy/spiders/dlhd.py @@ -96,33 +96,16 @@ class DaddyLiveHDSpider(scrapy.Spider): item_copy.update( { "stream_name": channel["channel_name"], + "stream_url": m3u8_url, "stream_headers": { "Referer": self.referer, "Origin": self.referer.rstrip("/"), }, + "response_headers": { + "Content-Type": "application/vnd.apple.mpegurl", + }, "channel_id": channel["channel_id"], } ) - yield scrapy.Request( - m3u8_url, - self.parse_stream_link, - meta={ - "item": item_copy, - "dont_redirect": True, - "handle_httpstatus_list": [301], - }, - headers={"Referer": self.referer}, - dont_filter=True, - ) - - def parse_stream_link(self, response): - item = response.meta["item"] - stream_url = response.headers.get("Location", b"").decode("utf-8") - if not stream_url: - self.logger.error( - f"Failed to get stream URL for {item['stream_name']} channel_id: {item['channel_id']}" - ) - return - item["stream_url"] = stream_url - yield item + yield item_copy diff --git a/mediafusion_scrapy/spiders/tgx.py b/mediafusion_scrapy/spiders/tgx.py index cbb9ea5..1472ec8 100644 --- a/mediafusion_scrapy/spiders/tgx.py +++ b/mediafusion_scrapy/spiders/tgx.py @@ -327,7 +327,7 @@ class FormulaTgxSpider(TgxSpider): logo_image = "https://i.postimg.cc/Sqf4V8tj/f1logo.png?dl=1" keyword_patterns = re.compile(r"formula[ .+]*[1234e]+", re.IGNORECASE) - scraped_info_hash_key = "formula_tgx_scraped_info_hash3" + scraped_info_hash_key = "formula_tgx_scraped_info_hash" custom_settings = { "ITEM_PIPELINES": { diff --git a/resources/exceptions/content_infringing.mp4 b/resources/exceptions/content_infringing.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..e651f306531aaa9a2e87997d10f26e70bc50c101 GIT binary patch literal 100260 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 j9P0k;WmL*XsljN0vObmKyyRaNcV5 literal 0 HcmV?d00001 diff --git a/resources/exceptions/ip_not_allowed.mp4 b/resources/exceptions/ip_not_allowed.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..524a3509642db8756158f475988b46ba5843f62d GIT binary patch literal 68203 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 AtpET3 literal 0 HcmV?d00001 diff --git a/resources/exceptions/too_many_requests.mp4 b/resources/exceptions/too_many_requests.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..1e027af51337ef12b2ba184b9d4feccbab543e13 GIT binary patch literal 87705 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
Please enter a valid MediaFlow Proxy URL. @@ -528,6 +528,14 @@ Please enter the MediaFlow API password.
+
+ + + +
diff --git a/resources/js/config_script.js b/resources/js/config_script.js index 02f704b..20129d7 100644 --- a/resources/js/config_script.js +++ b/resources/js/config_script.js @@ -313,6 +313,7 @@ function getUserData() { mediaflowConfig = { proxy_url: document.getElementById('mediaflow_proxy_url').value, api_password: document.getElementById('mediaflow_api_password').value, + mediaflow_public_ip: document.getElementById('mediaflow_public_ip').value, proxy_live_streams: document.getElementById('proxy_live_streams').checked, proxy_debrid_streams: document.getElementById('proxy_debrid_streams').checked }; diff --git a/resources/json/scraper_config.json b/resources/json/scraper_config.json index 1a491cc..fbc4717 100644 --- a/resources/json/scraper_config.json +++ b/resources/json/scraper_config.json @@ -1,6 +1,6 @@ { "start_urls": { - "nowsports": "https://nowmesports.nl/", + "nowsports": "https://nowmesport.com/", "nowmetv": "https://nowmelive.com/", "tamilultra": "https://tamilultra.tv/", "tamilbulb": "https://tamilbulb.tv/", @@ -283,8 +283,9 @@ }, "dlhd": { "schedule_url": "https://dlhd.so/schedule/schedule-generated.json", - "m3u8_base_url": "https://webhdrunns.mizhls.ru/lb/premium{channel_id}/index.m3u8", - "referer": "https://cookiewebplay.xyz/", + "m3u8_base_url": "https://xyzdddd.mizhls.ru/lb/premium{channel_id}/index.m3u8", + "key_url": "https://key.keylocking.ru", + "referer": "https://ilovetoplay.xyz/", "category_mapping": { "Tv Shows": "Other Sports", "Soccer": "Football", diff --git a/resources/manifest.json b/resources/manifest.json index df60db9..f7098fa 100644 --- a/resources/manifest.json +++ b/resources/manifest.json @@ -1,6 +1,6 @@ { "id": "stremio.addons", - "version": "4.0.1", + "version": "4.0.2", "name": "Media Fusion", "contactEmail": "mhdzumair@gmail.com", "description": "Universal Stremio Add-on for Movies, Series, Live TV & Sports Events. Source: https://github.com/mhdzumair/MediaFusion", diff --git a/scrapers/base_scraper.py b/scrapers/base_scraper.py index 47b4aa2..11e1e39 100644 --- a/scrapers/base_scraper.py +++ b/scrapers/base_scraper.py @@ -1,5 +1,6 @@ import abc import logging +import time from datetime import timedelta from functools import wraps from typing import List, Any, Dict @@ -40,7 +41,7 @@ class BaseScraper(abc.ABC): @staticmethod def cache(ttl: int = 3600): """ - Decorator for caching the results of a method. + Decorator for caching the scraping status using Redis Sorted Sets with timestamps. :param ttl: Time to live for the cache in seconds """ @@ -48,12 +49,23 @@ class BaseScraper(abc.ABC): @wraps(func) async def wrapper(self, *args, **kwargs): cache_key = self.get_cache_key(*args, **kwargs) - cached_result = await REDIS_ASYNC_CLIENT.get(cache_key) - if cached_result: - return [] + current_time = int(time.time()) + + # Check if the item has been scraped recently + score = await REDIS_ASYNC_CLIENT.zscore( + self.cache_key_prefix, cache_key + ) + + if score and current_time - score < ttl: + return [] # Item has been scraped recently, no need to scrape again result = await func(self, *args, **kwargs) - await REDIS_ASYNC_CLIENT.set(cache_key, "True", ex=ttl) + + # Mark the item as scraped with the current timestamp + await REDIS_ASYNC_CLIENT.zadd( + self.cache_key_prefix, {cache_key: current_time} + ) + return result return wrapper @@ -145,11 +157,9 @@ class BaseScraper(abc.ABC): :return: Cache key string """ if catalog_type == "movie": - return f"{self.cache_key_prefix}:{catalog_type}:{metadata.id}" + return f"{catalog_type}:{metadata.id}" - return ( - f"{self.cache_key_prefix}:{catalog_type}:{metadata.id}:{season}:{episode}" - ) + return f"{catalog_type}:{metadata.id}:{season}:{episode}" def validate_title_and_year( self, @@ -223,3 +233,11 @@ class BaseScraper(abc.ABC): from db.crud import store_new_torrent_streams await store_new_torrent_streams(streams) + + @staticmethod + async def remove_expired_items(scraper_prefix: str, ttl: int = 3600): + """ + Remove expired items from the cache. + """ + current_time = int(time.time()) + await REDIS_ASYNC_CLIENT.zremrangebyscore(scraper_prefix, 0, current_time - ttl) diff --git a/scrapers/prowlarr.py b/scrapers/prowlarr.py index da80f95..a34da26 100644 --- a/scrapers/prowlarr.py +++ b/scrapers/prowlarr.py @@ -5,6 +5,7 @@ from typing import List, Dict, Any, AsyncGenerator, Literal, AsyncIterable import PTT import dramatiq import httpx +from scrapeops_python_requests.scrapeops_requests import ScrapeOpsRequests from torf import Magnet, MagnetError from db.config import settings @@ -21,7 +22,7 @@ from scrapers.base_scraper import BaseScraper from scrapers.imdb_data import get_episode_by_date, get_season_episodes from utils.network import CircuitBreaker, batch_process_with_circuit_breaker from utils.parser import is_contain_18_plus_keywords -from utils.runtime_const import REDIS_ASYNC_CLIENT +from utils.runtime_const import REDIS_ASYNC_CLIENT, PROWLARR_SEARCH_TTL from utils.torrent import extract_torrent_metadata from utils.wrappers import minimum_run_interval @@ -47,6 +48,10 @@ allowlist_keywords = [ # fmt: on +class MaxProcessLimitReached(Exception): + pass + + class ProwlarrScraper(BaseScraper): MOVIE_SEARCH_QUERY_TEMPLATES = [ "{title} ({year})", # Exact match with year @@ -60,16 +65,17 @@ class ProwlarrScraper(BaseScraper): "{title} S{season:02d}", # Short season search with leading zeros "{title}", # Title-only fallback ] + cache_key_prefix = "prowlarr" def __init__(self): super().__init__( - cache_key_prefix="prowlarr", logger_name=self.__class__.__name__ + cache_key_prefix=self.cache_key_prefix, logger_name=self.__class__.__name__ ) self.base_url = f"{settings.prowlarr_url}/api/v1/search" + self.scrapeops_logger = None + self.scrape_response = None - @BaseScraper.cache( - ttl=int(timedelta(hours=settings.prowlarr_search_interval_hour).total_seconds()) - ) + @BaseScraper.cache(ttl=PROWLARR_SEARCH_TTL) @BaseScraper.rate_limit(calls=5, period=timedelta(seconds=1)) async def scrape_and_parse( self, @@ -80,6 +86,16 @@ class ProwlarrScraper(BaseScraper): ) -> List[TorrentStreams]: results = [] processed_info_hashes: set[str] = set() + job_name = f"{metadata.title}:{metadata.id}" + if catalog_type == "series": + job_name += f":{season}:{episode}" + + if settings.scrapeops_api_key: + self.scrapeops_logger = ScrapeOpsRequests( + scrapeops_api_key=settings.scrapeops_api_key, + spider_name="Prowlarr Scraper", + job_name=job_name, + ) try: async for stream in self._scrape_and_parse( @@ -90,6 +106,11 @@ class ProwlarrScraper(BaseScraper): episode, ): results.append(stream) + if settings.scrapeops_api_key: + self.scrapeops_logger.item_scraped( + item=stream.model_dump(include={"id"}), + response=self.scrape_response, + ) except httpx.ReadTimeout: self.logger.warning("Timeout while fetching search results") except httpx.HTTPStatusError as e: @@ -98,6 +119,9 @@ class ProwlarrScraper(BaseScraper): ) except Exception as e: self.logger.exception(f"An error occurred during scraping: {str(e)}") + finally: + if settings.scrapeops_api_key: + self.scrapeops_logger.logger.close_sdk() self.logger.info( f"Returning {len(results)} scraped streams for {metadata.title}" @@ -260,7 +284,7 @@ class ProwlarrScraper(BaseScraper): if max_process and streams_processed >= max_process: self.logger.info(f"Reached max process limit of {max_process}") - break + raise MaxProcessLimitReached("Max process limit reached") try: async with asyncio.timeout(max_process_time): @@ -279,6 +303,10 @@ class ProwlarrScraper(BaseScraper): f"Stream processing timed out after {max_process_time} seconds. " f"Processed {streams_processed} streams" ) + except MaxProcessLimitReached: + self.logger.info( + f"Stream processing cancelled after reaching max process limit of {max_process}" + ) except Exception as e: self.logger.error(f"An error occurred during stream processing: {e}") self.logger.info( @@ -295,6 +323,7 @@ class ProwlarrScraper(BaseScraper): params=params, timeout=settings.prowlarr_search_query_timeout, ) + self.scrape_response = response response.raise_for_status() return response.json() @@ -655,7 +684,12 @@ class ProwlarrScraper(BaseScraper): redirect_url = response.headers.get("Location") return await self.get_torrent_data(redirect_url, indexer) response.raise_for_status() - return extract_torrent_metadata(response.content, is_parse_ptt=False), True + if response.headers.get("Content-Type") == "application/x-bittorrent": + return ( + extract_torrent_metadata(response.content, is_parse_ptt=False), + True, + ) + return {}, False @staticmethod def parse_title_data(title: str) -> dict: @@ -713,6 +747,13 @@ async def background_movie_title_search( if not metadata: return + if settings.scrapeops_api_key: + scraper.scrapeops_logger = ScrapeOpsRequests( + scrapeops_api_key=settings.scrapeops_api_key, + spider_name="Prowlarr Scraper", + job_name=f"background:{metadata.title}:{metadata.id}", + ) + title_streams_generators = [ scraper.scrape_movie_by_title( processed_info_hashes, @@ -725,6 +766,11 @@ async def background_movie_title_search( try: async for stream in scraper.process_streams(*title_streams_generators): await scraper.store_streams([stream]) + if settings.scrapeops_api_key: + scraper.scrapeops_logger.item_scraped( + item=stream.model_dump(include={"id"}), + response=scraper.scrape_response, + ) except httpx.ReadTimeout: scraper.logger.warning( f"Timeout while fetching search results for movie {metadata.title} ({metadata.year}), retrying later" @@ -738,6 +784,9 @@ async def background_movie_title_search( scraper.logger.error( f"Error fetching search results: {e.response.text}, status code: {e.response.status_code}" ) + finally: + if settings.scrapeops_api_key: + scraper.scrapeops_logger.logger.close_sdk() scraper.logger.info( f"Background title search completed for {metadata.title} ({metadata.year})" @@ -762,6 +811,13 @@ async def background_series_title_search( if not metadata: return + if settings.scrapeops_api_key: + scraper.scrapeops_logger = ScrapeOpsRequests( + scrapeops_api_key=settings.scrapeops_api_key, + spider_name="Prowlarr Scraper", + job_name=f"background:{metadata.title}:{metadata.id}:{season}:{episode}", + ) + title_streams_generators = [ scraper.scrape_series_by_title( processed_info_hashes, @@ -778,6 +834,11 @@ async def background_series_title_search( try: async for stream in scraper.process_streams(*title_streams_generators): await scraper.store_streams([stream]) + if settings.scrapeops_api_key: + scraper.scrapeops_logger.item_scraped( + item=stream.model_dump(include={"id"}), + response=scraper.scrape_response, + ) except httpx.ReadTimeout: scraper.logger.warning( f"Timeout while fetching search results for {metadata.title} S{season}E{episode}, retrying later" @@ -792,6 +853,9 @@ async def background_series_title_search( scraper.logger.error( f"Error fetching search results: {e.response.text}, status code: {e.response.status_code}" ) + finally: + if settings.scrapeops_api_key: + scraper.scrapeops_logger.logger.close_sdk() scraper.logger.info( f"Background title search completed for {metadata.title} S{season}E{episode}" diff --git a/scrapers/prowlarr_feed.py b/scrapers/prowlarr_feed.py index 423119a..7cccf6e 100644 --- a/scrapers/prowlarr_feed.py +++ b/scrapers/prowlarr_feed.py @@ -2,6 +2,7 @@ import logging import dramatiq import httpx +from scrapeops_python_requests.scrapeops_requests import ScrapeOpsRequests from db.config import settings from db.crud import ( @@ -40,6 +41,13 @@ async def mark_item_as_processed(item_id: str): async def scrape_prowlarr_feed(): scraper = ProwlarrScraper() + if settings.scrapeops_api_key: + scraper.scrapeops_logger = ScrapeOpsRequests( + scrapeops_api_key=settings.scrapeops_api_key, + spider_name="Prowlarr Scraper", + job_name="Prowlarr Feed Scraper", + ) + params = { "type": "search", "categories": [2000, 5000, 8000], # Movies, TV, and Other categories @@ -70,6 +78,9 @@ async def scrape_prowlarr_feed(): except Exception as e: logger.exception(f"Error scraping Prowlarr feed: {e}") + finally: + if scraper.scrapeops_logger: + scraper.scrapeops_logger.logger.close_sdk() async def process_feed_item(item: dict, scraper: ProwlarrScraper): @@ -83,6 +94,12 @@ async def process_feed_item(item: dict, scraper: ProwlarrScraper): parsed_title_data = scraper.parse_title_data(item["title"]) if is_contain_18_plus_keywords(item["title"]): logger.warning(f"Item {item['title']} contains black listed keywords") + if settings.scrapeops_api_key: + scraper.scrapeops_logger.logger.item_dropped( + item={"info_hash": item_id}, + response=scraper.scrape_response, + message="Contains blacklisted keywords", + ) return item_id # Determine media type @@ -97,10 +114,22 @@ async def process_feed_item(item: dict, scraper: ProwlarrScraper): logger.warning( f"Category 8000 item {item['title']} does not match expected format" ) + if settings.scrapeops_api_key: + scraper.scrapeops_logger.logger.item_dropped( + item={"info_hash": item_id}, + response=scraper.scrape_response, + message="Does not match expected format", + ) return item_id media_type = "series" if parsed_title_data.get("seasons") else "movie" else: logger.warning(f"Unsupported category {category_ids} for item {item['title']}") + if settings.scrapeops_api_key: + scraper.scrapeops_logger.logger.item_dropped( + item={"info_hash": item_id}, + response=scraper.scrape_response, + message="Unsupported category", + ) return item_id # Fetch or create metadata @@ -111,6 +140,12 @@ async def process_feed_item(item: dict, scraper: ProwlarrScraper): if not metadata: logger.warning(f"Unable to find or create metadata for {item['title']}") + if settings.scrapeops_api_key: + scraper.scrapeops_logger.logger.item_dropped( + item={"info_hash": item_id}, + response=scraper.scrape_response, + message="Unable to find or create metadata", + ) return item_id # Process the stream @@ -119,9 +154,19 @@ async def process_feed_item(item: dict, scraper: ProwlarrScraper): ) if stream: await store_new_torrent_streams([stream]) + if settings.scrapeops_api_key: + scraper.scrapeops_logger.item_scraped( + item={"info_hash": item_id}, response=scraper.scrape_response + ) return item_id else: logger.warning(f"Failed to process stream for {item['title']}") + if settings.scrapeops_api_key: + scraper.scrapeops_logger.logger.item_dropped( + item={"info_hash": item_id}, + response=scraper.scrape_response, + message="Failed to process stream", + ) return item_id @@ -150,6 +195,6 @@ async def search_and_create_metadata(metadata: dict, media_type: str): max_backoff=3600000, priority=50, ) -async def run_prowlarr_feed_scraper(): +async def run_prowlarr_feed_scraper(**kwargs): logger.info("Running Prowlarr feed scraper") await scrape_prowlarr_feed() diff --git a/scrapers/torrent_info.py b/scrapers/torrent_info.py index 05b8a98..317ea4c 100644 --- a/scrapers/torrent_info.py +++ b/scrapers/torrent_info.py @@ -7,9 +7,10 @@ from utils.torrent import get_info_hash_from_magnet async def get_torrent_info(url: str, indexer: str) -> dict: torrent_info = {} + pre_process_func = pre_process_map.get(indexer, get_page_bs4) parse_func = parser_function_map.get(indexer, parse_common_torrents) - soup = await get_page_bs4(url) + soup = await pre_process_func(url) if not soup: return torrent_info @@ -43,7 +44,7 @@ def parse_torrent_downloads(soup, torrent_info, url): info_hash = ( info_hash_span.find_parent("p").text.replace("Infohash:", "").strip() ) - torrent_info["infoHash"] = info_hash.strip() + torrent_info["infoHash"] = info_hash return torrent_info @@ -74,10 +75,8 @@ def parse_badass_torrents(soup, torrent_info, url): torrent_link = soup.find("a", string="Torrent Download") if torrent_link: torrent_info["downloadUrl"] = urljoin(url, torrent_link.get("href")) - torrent_info["magnetUrl"] = None - elif magnet_link: + if magnet_link: torrent_info["magnetUrl"] = magnet_link.get("href") - torrent_info["downloadUrl"] = None return torrent_info @@ -116,6 +115,15 @@ def parse_gktorrent(soup, torrent_info, url): return torrent_info +async def pre_process_therarbg(url): + url = url.replace("?format=json", "") + return await get_page_bs4(url) + + +pre_process_map = { + "TheRARBG": pre_process_therarbg, +} + parser_function_map = { "1337x": parse_1337x, "TheRARBG": parse_common_torrents, diff --git a/scrapers/torrentio.py b/scrapers/torrentio.py index 446497d..c04bbf8 100644 --- a/scrapers/torrentio.py +++ b/scrapers/torrentio.py @@ -5,6 +5,7 @@ from os import path from typing import List, Dict, Any import PTT +from scrapeops_python_requests.scrapeops_requests import ScrapeOpsRequests from tenacity import RetryError from db.config import settings @@ -14,22 +15,21 @@ from utils.parser import ( convert_size_to_bytes, is_contain_18_plus_keywords, ) +from utils.runtime_const import TORRENTIO_SEARCH_TTL from utils.validation_helper import is_video_file -from scrapeops_python_requests.scrapeops_requests import ScrapeOpsRequests - class TorrentioScraper(BaseScraper): + cache_key_prefix = "torrentio" + def __init__(self): super().__init__( - cache_key_prefix="torrentio", logger_name=self.__class__.__name__ + cache_key_prefix=self.cache_key_prefix, logger_name=self.__class__.__name__ ) self.base_url = settings.torrentio_url self.semaphore = asyncio.Semaphore(10) - @BaseScraper.cache( - ttl=int(timedelta(days=settings.torrentio_search_interval_days).total_seconds()) - ) + @BaseScraper.cache(ttl=TORRENTIO_SEARCH_TTL) @BaseScraper.rate_limit(calls=5, period=timedelta(seconds=1)) async def scrape_and_parse( self, @@ -44,14 +44,17 @@ class TorrentioScraper(BaseScraper): url = f"{self.base_url}/stream/{catalog_type}/{metadata.id}:{season}:{episode}.json" job_name += f":{season}:{episode}" - scrapeops_logger = ScrapeOpsRequests( - scrapeops_api_key=settings.scrapeops_api_key, - spider_name="Torrentio Scraper", - job_name=job_name, - ) + scrapeops_logger = None + if settings.scrapeops_api_key: + scrapeops_logger = ScrapeOpsRequests( + scrapeops_api_key=settings.scrapeops_api_key, + spider_name="Torrentio Scraper", + job_name=job_name, + ) try: response = await self.make_request(url) + response.raise_for_status() data = response.json() if not self.validate_response(data): @@ -62,9 +65,10 @@ class TorrentioScraper(BaseScraper): data, metadata, catalog_type, season, episode ) for stream in stream_data: - scrapeops_logger.item_scraped( - item=stream.model_dump(include={"id", "meta_id"}), response=response - ) + if scrapeops_logger: + scrapeops_logger.item_scraped( + item=stream.model_dump(include={"id"}), response=response + ) return stream_data except (ScraperError, RetryError): return [] @@ -72,7 +76,8 @@ class TorrentioScraper(BaseScraper): self.logger.exception(f"Error occurred while fetching {url}: {e}") return [] finally: - scrapeops_logger.logger.close_sdk() + if scrapeops_logger: + scrapeops_logger.logger.close_sdk() def validate_response(self, response: Dict[str, Any]) -> bool: return "streams" in response and isinstance(response["streams"], list) diff --git a/scrapers/utils.py b/scrapers/utils.py index d0fe5e5..64369de 100644 --- a/scrapers/utils.py +++ b/scrapers/utils.py @@ -1,11 +1,19 @@ import asyncio import logging +import dramatiq + from db.config import settings from db.models import TorrentStreams, MediaFusionMetaData +from scrapers.base_scraper import BaseScraper from scrapers.prowlarr import ProwlarrScraper from scrapers.torrentio import TorrentioScraper from scrapers.zilean import ZileanScraper +from utils.runtime_const import ( + ZILEAN_SEARCH_TTL, + TORRENTIO_SEARCH_TTL, + PROWLARR_SEARCH_TTL, +) async def run_scrapers( @@ -46,3 +54,19 @@ async def run_scrapers( unique_streams = set(scraped_streams) logging.info(f"Scraped {len(scraped_streams)} streams for {metadata.title}") return unique_streams + + +@dramatiq.actor( + time_limit=5 * 60 * 1000, # 5 minutes + priority=20, +) +async def cleanup_expired_scraper_task(**kwargs): + await BaseScraper.remove_expired_items( + ProwlarrScraper.cache_key_prefix, PROWLARR_SEARCH_TTL + ) + await BaseScraper.remove_expired_items( + TorrentioScraper.cache_key_prefix, TORRENTIO_SEARCH_TTL + ) + await BaseScraper.remove_expired_items( + ZileanScraper.cache_key_prefix, ZILEAN_SEARCH_TTL + ) diff --git a/scrapers/zilean.py b/scrapers/zilean.py index f8a7357..2970655 100644 --- a/scrapers/zilean.py +++ b/scrapers/zilean.py @@ -3,25 +3,28 @@ from datetime import timedelta from typing import List, Dict, Any import PTT +from httpx import Response from tenacity import RetryError - +from scrapeops_python_requests.scrapeops_requests import ScrapeOpsRequests from db.config import settings from db.models import TorrentStreams, Season, Episode, MediaFusionMetaData from scrapers.base_scraper import BaseScraper, ScraperError from utils.parser import ( is_contain_18_plus_keywords, ) +from utils.runtime_const import ZILEAN_SEARCH_TTL class ZileanScraper(BaseScraper): + cache_key_prefix = "zilean" + def __init__(self): - super().__init__(cache_key_prefix="zilean", logger_name=self.__class__.__name__) - self.base_url = f"{settings.zilean_url}/dmm/search" + super().__init__( + cache_key_prefix=self.cache_key_prefix, logger_name=self.__class__.__name__ + ) self.semaphore = asyncio.Semaphore(10) - @BaseScraper.cache( - ttl=int(timedelta(hours=settings.prowlarr_search_interval_hour).total_seconds()) - ) + @BaseScraper.cache(ttl=ZILEAN_SEARCH_TTL) @BaseScraper.rate_limit(calls=5, period=timedelta(seconds=1)) async def scrape_and_parse( self, @@ -30,22 +33,86 @@ class ZileanScraper(BaseScraper): season: int = None, episode: int = None, ) -> List[TorrentStreams]: - try: - stream_response = await self.make_request( - self.base_url, + job_name = f"{metadata.title}:{metadata.id}" + if catalog_type == "series": + job_name += f":{season}:{episode}" + + scrapeops_logger = None + if settings.scrapeops_api_key: + scrapeops_logger = ScrapeOpsRequests( + scrapeops_api_key=settings.scrapeops_api_key, + spider_name="Zilean Scraper", + job_name=job_name, + ) + + search_task = asyncio.create_task( + self.make_request( + f"{settings.zilean_url}/dmm/search", method="POST", json={"queryText": metadata.title}, timeout=10, ) + ) - stream_data = stream_response.json() - if not self.validate_response(stream_data): - self.logger.warning(f"Invalid response received for {metadata.title}") - return [] + if metadata.type == "movie": + params = { + "Query": metadata.title, + "Year": metadata.year, + } + else: + params = { + "Query": metadata.title, + "Season": season, + "Episode": episode, + } - return await self.parse_response( + filtered_task = asyncio.create_task( + self.make_request( + f"{settings.zilean_url}/dmm/filtered", + method="GET", + params=params, + timeout=10, + ) + ) + + search_response, filtered_response = await asyncio.gather( + search_task, filtered_task, return_exceptions=True + ) + + stream_data = [] + response = None + if isinstance(search_response, Response): + response = search_response + stream_data.extend(search_response.json()) + else: + self.logger.error( + f"Error occurred while search {metadata.title}: {search_response}" + ) + if isinstance(filtered_response, Response): + response = filtered_response + stream_data.extend(filtered_response.json()) + else: + self.logger.error( + f"Error occurred while filtering {metadata.title}: {filtered_response}" + ) + + if not self.validate_response(stream_data): + self.logger.error(f"No valid streams found for {metadata.title}") + if scrapeops_logger: + scrapeops_logger.logger.close_sdk() + return [] + + try: + streams = await self.parse_response( stream_data, metadata, catalog_type, season, episode ) + if scrapeops_logger: + for stream in streams: + scrapeops_logger.item_scraped( + item=stream.model_dump(include={"id"}), + response=response, + ) + return streams except (ScraperError, RetryError): return [] except Exception as e: @@ -53,6 +120,9 @@ class ZileanScraper(BaseScraper): f"Error occurred while fetching {metadata.title}: {e}" ) return [] + finally: + if scrapeops_logger: + scrapeops_logger.logger.close_sdk() async def parse_response( self, diff --git a/streaming_providers/pikpak/utils.py b/streaming_providers/pikpak/utils.py index f02d743..4c5163e 100644 --- a/streaming_providers/pikpak/utils.py +++ b/streaming_providers/pikpak/utils.py @@ -10,6 +10,8 @@ from db.models import TorrentStreams from db.schemas import UserData from streaming_providers.exceptions import ProviderException from streaming_providers.parser import select_file_index_from_torrent +from utils import crypto +from utils.runtime_const import REDIS_ASYNC_CLIENT async def get_torrent_file_by_info_hash( @@ -141,6 +143,22 @@ async def find_file_in_folder_tree( async def initialize_pikpak(user_data: UserData): + cache_key = f"pikpak:{crypto.get_text_hash(user_data.streaming_provider.email + user_data.streaming_provider.password, full_hash=True)}" + if pikpak_encrypted_token := await REDIS_ASYNC_CLIENT.get(cache_key): + pikpak_encoded_token = crypto.decrypt_text( + pikpak_encrypted_token, user_data.streaming_provider.password + ) + pikpak = PikPakApi( + encoded_token=pikpak_encoded_token, + httpx_client_args={ + "transport": httpx.AsyncHTTPTransport(retries=3), + "timeout": 10, + }, + token_refresh_callback=store_pikpak_token_in_cache, + token_refresh_callback_kwargs={"user_data": user_data}, + ) + return pikpak + pikpak = PikPakApi( username=user_data.streaming_provider.email, password=user_data.streaming_provider.password, @@ -148,7 +166,10 @@ async def initialize_pikpak(user_data: UserData): "transport": httpx.AsyncHTTPTransport(retries=3), "timeout": 10, }, + token_refresh_callback=store_pikpak_token_in_cache, + token_refresh_callback_kwargs={"user_data": user_data}, ) + try: await pikpak.login() except PikpakException as error: @@ -166,9 +187,22 @@ async def initialize_pikpak(user_data: UserData): "Failed to connect to PikPak. Please try again later.", "debrid_service_down_error.mp4", ) + + await store_pikpak_token_in_cache(pikpak, user_data) return pikpak +async def store_pikpak_token_in_cache(pikpak: PikPakApi, user_data: UserData): + cache_key = f"pikpak:{crypto.get_text_hash(user_data.streaming_provider.email + user_data.streaming_provider.password, full_hash=True)}" + await REDIS_ASYNC_CLIENT.set( + cache_key, + crypto.encrypt_text( + pikpak.encoded_token, user_data.streaming_provider.password + ), + ex=7 * 24 * 60 * 60, # Store for 7 days + ) + + async def handle_torrent_status( pikpak: PikPakApi, info_hash: str, diff --git a/streaming_providers/realdebrid/client.py b/streaming_providers/realdebrid/client.py index be543e5..6a18954 100644 --- a/streaming_providers/realdebrid/client.py +++ b/streaming_providers/realdebrid/client.py @@ -18,13 +18,26 @@ class RealDebrid(DebridClient): super().__init__(token) def _handle_service_specific_errors(self, error): - if ( - error.response.status_code == 403 - and error.response.json().get("error_code") == 9 - ): - raise ProviderException( - "Real-Debrid Permission denied for free account", "need_premium.mp4" - ) + if error.response.status_code == 403: + error_code = error.response.json().get("error_code") + match error_code: + case 9: + raise ProviderException( + "Real-Debrid Permission denied for free account", + "need_premium.mp4", + ) + case 22: + raise ProviderException( + "IP address not allowed", "ip_not_allowed.mp4" + ) + case 34: + raise ProviderException( + "Too many requests", "too_many_requests.mp4" + ) + case 35: + raise ProviderException( + "Content marked as infringing", "content_infringing.mp4" + ) def _make_request( self, diff --git a/utils/cleanup_torrents.py b/utils/cleanup_torrents.py index 165b892..577b5a7 100644 --- a/utils/cleanup_torrents.py +++ b/utils/cleanup_torrents.py @@ -1,6 +1,7 @@ import asyncio -import sys import logging +import sys +from datetime import datetime, timezone, timedelta from typing import Dict import PTT @@ -12,10 +13,13 @@ from db.models import ( TorrentStreams, MediaFusionSeriesMetaData, MediaFusionMovieMetaData, + MediaFusionMetaData, ) from utils.parser import calculate_max_similarity_ratio, is_contain_18_plus_keywords BATCH_SIZE = 1000 # Adjust the batch size as needed +SKIP_TORRENTS = 0 # Skip the first n torrents +LAST_UPDATED = datetime.now(tz=timezone.utc) - timedelta(days=1) # Configure logging logging.basicConfig( @@ -26,49 +30,63 @@ logger = logging.getLogger(__name__) async def cleanup_torrents(dry_run: bool = True) -> Dict[str, int]: metrics = { - "total_torrents": 0, - "removed_torrents": 0, - "updated_torrents": 0, + "total": 0, + "removed": 0, "no_metadata": 0, "valid": 0, - "ratio_error": 0, - "adult_content": 0, + "ratio": 0, + "adult": 0, "year_mismatch": 0, "removed_non_imdb": 0, + "non_movie": 0, } lock = asyncio.Lock() - total_torrents = await TorrentStreams.count() - progress_bar = tqdm( - range(0, total_torrents, BATCH_SIZE), desc="Processing torrents" + + # Create a cursor for efficient pagination + cursor = TorrentStreams.find(TorrentStreams.updated_at < LAST_UPDATED).sort( + -TorrentStreams.updated_at ) + total_torrents = await cursor.count() + total_torrents = max(0, total_torrents - SKIP_TORRENTS) + + progress_bar = tqdm(total=total_torrents, desc="Processing torrents") torrent_bulk_writer = BulkWriter() movies_bulk_writer = BulkWriter() series_bulk_writer = BulkWriter() - for skip in progress_bar: - torrents = await TorrentStreams.find().skip(skip).limit(BATCH_SIZE).to_list() - tasks = [ - process_torrent( - torrent, - dry_run, - metrics, - lock, - torrent_bulk_writer, - movies_bulk_writer, - series_bulk_writer, - ) - for torrent in torrents - ] - await asyncio.gather(*tasks) - await torrent_bulk_writer.commit() - await movies_bulk_writer.commit() - await series_bulk_writer.commit() - torrent_bulk_writer.operations.clear() - movies_bulk_writer.operations.clear() - series_bulk_writer.operations.clear() + cursor = cursor.skip(SKIP_TORRENTS) + processed_count = 0 + async for torrent in cursor: + await process_torrent( + torrent, + dry_run, + metrics, + lock, + torrent_bulk_writer, + movies_bulk_writer, + series_bulk_writer, + ) + + processed_count += 1 + if processed_count >= BATCH_SIZE: + await torrent_bulk_writer.commit() + await movies_bulk_writer.commit() + await series_bulk_writer.commit() + torrent_bulk_writer.operations.clear() + movies_bulk_writer.operations.clear() + series_bulk_writer.operations.clear() + processed_count = 0 + + progress_bar.update(1) progress_bar.set_postfix(metrics) + # Commit any remaining operations + await torrent_bulk_writer.commit() + await movies_bulk_writer.commit() + await series_bulk_writer.commit() + + progress_bar.close() return metrics @@ -82,20 +100,28 @@ async def process_torrent( series_bulk_writer: BulkWriter, ) -> str: async with lock: - metrics["total_torrents"] += 1 + metrics["total"] += 1 - meta_data = None - if any("series" in catalog for catalog in torrent.catalog): - meta_data = await MediaFusionSeriesMetaData.get(torrent.meta_id) - meta_data_bulk_writer = series_bulk_writer - else: - meta_data = await MediaFusionMovieMetaData.get(torrent.meta_id) - meta_data_bulk_writer = movies_bulk_writer + meta_data = await MediaFusionMetaData.get_motor_collection().find_one( + {"_id": torrent.meta_id} + ) if not meta_data: + await torrent.delete(bulk_writer=torrent_bulk_writer) async with lock: metrics["no_metadata"] += 1 - return "no_metadata" + metrics["removed"] += 1 + logger.debug( + f"No metadata found for torrent: {torrent.torrent_name}, {torrent.meta_id}" + ) + return "removed" + + if meta_data["type"] == "movie": + meta_data = MediaFusionMovieMetaData(**meta_data) + meta_data_bulk_writer = movies_bulk_writer + else: + meta_data = MediaFusionSeriesMetaData(**meta_data) + meta_data_bulk_writer = series_bulk_writer if ( not dry_run @@ -109,16 +135,17 @@ async def process_torrent( await torrent.delete(bulk_writer=torrent_bulk_writer) await meta_data.delete(bulk_writer=meta_data_bulk_writer) async with lock: - metrics["removed_torrents"] += 1 + metrics["removed"] += 1 metrics["removed_non_imdb"] += 1 + logger.debug(f"Removed non-IMDb torrent: {torrent.torrent_name}") return "removed" if is_contain_18_plus_keywords(torrent.torrent_name): if not dry_run: await torrent.delete(bulk_writer=torrent_bulk_writer) async with lock: - metrics["removed_torrents"] += 1 - metrics["adult_content"] += 1 + metrics["removed"] += 1 + metrics["adult"] += 1 logger.debug(f"Removed torrent due to adult content: {torrent.torrent_name}") return "removed" @@ -128,26 +155,41 @@ async def process_torrent( parsed_data.get("title", ""), meta_data.title, meta_data.aka_titles ) expected_ratio = 70 if torrent.source in ["TamilMV", "TamilBlasters"] else 85 - if max_ratio < expected_ratio and torrent.meta_id.startswith("tt"): + if ( + max_ratio < expected_ratio + and torrent.meta_id.startswith("tt") + and not any(x in torrent.catalog for x in ["wwe_tgx", "ufc_tgx"]) + ): if not dry_run: await torrent.delete(bulk_writer=torrent_bulk_writer) async with lock: - metrics["removed_torrents"] += 1 - metrics["ratio_error"] += 1 + metrics["removed"] += 1 + metrics["ratio"] += 1 logger.debug( f"Removed torrent due to low similarity ratio: {parsed_data.get('title')} != {meta_data.title} (ratio: {max_ratio}%) (full title: {torrent.torrent_name})" ) return "removed" - if "year" in parsed_data and torrent.meta_id.startswith("tt"): - if meta_data.type == "movie" and parsed_data["year"] != meta_data.year: + if torrent.meta_id.startswith("tt") and meta_data.type == "movie": + if parsed_data.get("seasons"): if not dry_run: await torrent.delete(bulk_writer=torrent_bulk_writer) async with lock: - metrics["removed_torrents"] += 1 + metrics["removed"] += 1 + metrics["non_movie"] += 1 + logger.debug( + f"Removed non-movie torrent: {torrent.torrent_name}, found season: {parsed_data['seasons']}. Expected movie." + ) + return "removed" + + if meta_data.type == "movie" and parsed_data.get("year") != meta_data.year: + if not dry_run: + await torrent.delete(bulk_writer=torrent_bulk_writer) + async with lock: + metrics["removed"] += 1 metrics["year_mismatch"] += 1 logger.debug( - f"Removed torrent due to year mismatch: {torrent.torrent_name} (torrent year: {parsed_data['year']}, meta year: {meta_data.year})" + f"Removed torrent due to year mismatch: {torrent.torrent_name} ({parsed_data.get('year')} != {meta_data.year}) ({meta_data.id}) ({meta_data.type})" ) return "removed" @@ -163,14 +205,13 @@ async def process_torrent( torrent.audio = ( parsed_data.get("audio")[0] if parsed_data.get("audio") else None ) + torrent.updated_at = datetime.now(tz=timezone.utc) await torrent.save(bulk_writer=torrent_bulk_writer) - async with lock: - metrics["updated_torrents"] += 1 return "updated" - async with lock: - metrics["valid"] += 1 - return "valid" + torrent.updated_at = datetime.now(tz=timezone.utc) + await torrent.save(bulk_writer=torrent_bulk_writer) + return "updated" # Initialize the database connection and start the cleanup process diff --git a/utils/crypto.py b/utils/crypto.py index 1b57ac8..81ef793 100644 --- a/utils/crypto.py +++ b/utils/crypto.py @@ -14,6 +14,32 @@ from db.schemas import UserData from utils.runtime_const import SECRET_KEY +def encrypt_text(text: str, secret_key: str | bytes) -> str: + iv = get_random_bytes(16) + if isinstance(secret_key, str): + secret_key = secret_key.encode("utf-8") + cipher = AES.new(secret_key.ljust(32)[:32], AES.MODE_CBC, iv) + encoded_text = text.encode("utf-8") + encrypted_data = cipher.encrypt( + encoded_text + b"\0" * (16 - len(encoded_text) % 16) + ) + compressed_data = zlib.compress(iv + encrypted_data) + encrypted_str = urlsafe_b64encode(compressed_data).decode("utf-8") + return encrypted_str + + +def decrypt_text(secret_str: str, secret_key: str | bytes) -> str: + decoded_data = urlsafe_b64decode(secret_str) + encrypted_data = zlib.decompress(decoded_data) + iv = encrypted_data[:16] + if isinstance(secret_key, str): + secret_key = secret_key.encode("utf-8") + cipher = AES.new(secret_key.ljust(32)[:32], AES.MODE_CBC, iv) + decrypted_data = cipher.decrypt(encrypted_data[16:]) + decrypted_data = decrypted_data.rstrip(b"\0") + return decrypted_data.decode("utf-8") + + def encrypt_user_data(user_data: UserData) -> str: data = user_data.model_dump_json( exclude_none=True, @@ -22,26 +48,15 @@ def encrypt_user_data(user_data: UserData) -> str: round_trip=True, by_alias=True, ) - dump_data = data.encode("utf-8") - iv = get_random_bytes(16) - cipher = AES.new(SECRET_KEY, AES.MODE_CBC, iv) - encrypted_data = cipher.encrypt(dump_data + b"\0" * (16 - len(dump_data) % 16)) - compressed_data = zlib.compress(iv + encrypted_data) - encrypted_str = urlsafe_b64encode(compressed_data).decode("utf-8") - return encrypted_str + return encrypt_text(data, SECRET_KEY) def decrypt_user_data(secret_str: str | None = None) -> UserData: if not secret_str: return UserData() try: - decoded_data = urlsafe_b64decode(secret_str) - encrypted_data = zlib.decompress(decoded_data) - iv = encrypted_data[:16] - cipher = AES.new(SECRET_KEY, AES.MODE_CBC, iv) - decrypted_data = cipher.decrypt(encrypted_data[16:]) - decrypted_data = decrypted_data.rstrip(b"\0") - user_data = UserData.model_validate_json(decrypted_data.decode("utf-8")) + decrypted_data = decrypt_text(secret_str, SECRET_KEY) + user_data = UserData.model_validate_json(decrypted_data) except Exception: user_data = UserData() return user_data diff --git a/utils/network.py b/utils/network.py index 83efa00..14566a8 100644 --- a/utils/network.py +++ b/utils/network.py @@ -2,7 +2,7 @@ import asyncio import logging from typing import Callable, AsyncGenerator, Any, Tuple from urllib import parse -from urllib.parse import urlencode +from urllib.parse import urlencode, urlparse import httpx from fastapi.requests import Request @@ -169,14 +169,21 @@ def get_client_ip(request: Request) -> str | None: return request.client.host if request.client else "127.0.0.1" -async def get_mediaflow_proxy_public_ip( - mediaflow_proxy_url: str, api_password -) -> str | None: +async def get_mediaflow_proxy_public_ip(mediaflow_config) -> str | None: """ Get the public IP address of the MediaFlow proxy server. """ + if mediaflow_config.public_ip: + return mediaflow_config.public_ip + + parsed_url = urlparse(mediaflow_config.mediaflow_proxy_url) + if PRIVATE_CIDR.match(parsed_url.netloc): + # MediaFlow proxy URL is a private IP address + return None + cache_key = crypto.get_text_hash( - f"{mediaflow_proxy_url}:{api_password}", full_hash=True + f"{mediaflow_config.mediaflow_proxy_url}:{mediaflow_config.api_password}", + full_hash=True, ) if public_ip := await REDIS_ASYNC_CLIENT.getex(cache_key, ex=300): return public_ip.decode() @@ -184,8 +191,8 @@ async def get_mediaflow_proxy_public_ip( try: async with httpx.AsyncClient() as client: response = await client.get( - parse.urljoin(mediaflow_proxy_url, "/proxy/ip"), - params={"api_password": api_password}, + parse.urljoin(mediaflow_config.mediaflow_proxy_url, "/proxy/ip"), + params={"api_password": mediaflow_config.api_password}, timeout=10, ) response.raise_for_status() @@ -211,10 +218,7 @@ async def get_user_public_ip( and user_data.mediaflow_config and user_data.mediaflow_config.proxy_debrid_streams ): - public_ip = await get_mediaflow_proxy_public_ip( - user_data.mediaflow_config.proxy_url, - user_data.mediaflow_config.api_password, - ) + public_ip = await get_mediaflow_proxy_public_ip(user_data.mediaflow_config) if public_ip: return public_ip # Get the user's public IP address diff --git a/utils/parser.py b/utils/parser.py index d299131..7531507 100644 --- a/utils/parser.py +++ b/utils/parser.py @@ -1,7 +1,8 @@ import asyncio import functools import logging -from typing import Optional, List +from datetime import datetime +from typing import Optional, List, Any import math import re @@ -13,6 +14,7 @@ from db.models import TorrentStreams, TVStreams from db.schemas import Stream, UserData from streaming_providers import mapper from utils import const +from utils.config import config_manager from utils.const import STREAMING_PROVIDERS_SHORT_NAMES from utils.network import encode_mediaflow_proxy_url from utils.runtime_const import ADULT_CONTENT_KEYWORDS, TRACKERS @@ -94,13 +96,29 @@ async def filter_and_sort_streams( ) # Step 3: Dynamically sort streams based on user preferences - def dynamic_sort_key(stream): - return tuple( - ( - const.RESOLUTION_RANKING.get(stream.filtered_resolution, 0) - if key == "resolution" - else ( - -min( + def dynamic_sort_key(stream: TorrentStreams) -> tuple: + def key_value(key: str) -> Any: + match key: + case "cached": + return stream.cached or False + case "resolution": + return const.RESOLUTION_RANKING.get(stream.filtered_resolution, 0) + case "quality": + return const.QUALITY_RANKING.get(stream.filtered_quality, 0) + case "size": + return stream.size + case "seeders": + return stream.seeders or 0 + case "created_at": + created_at = stream.created_at + if isinstance(created_at, datetime): + return created_at + elif isinstance(created_at, (int, float)): + return datetime.fromtimestamp(created_at) + else: + return datetime.min + case "language": + return -min( ( user_data.language_sorting.index(lang) for lang in stream.filtered_languages @@ -108,28 +126,22 @@ async def filter_and_sort_streams( ), default=len(user_data.language_sorting), ) - if key == "language" - else ( - const.QUALITY_RANKING.get(stream.filtered_quality, 0) - if key == "quality" - else ( - getattr(stream, key, 0) - if key in stream.model_fields_set - else 0 - ) - ) - ) - ) - for key in user_data.torrent_sorting_priority + case _ if key in stream.model_fields_set: + return getattr(stream, key, 0) + case _: + return 0 + + return tuple(key_value(key) for key in user_data.torrent_sorting_priority) + + try: + dynamically_sorted_streams = sorted( + filtered_streams, key=dynamic_sort_key, reverse=True ) - - def safe_sort_key(stream): - raw_key = dynamic_sort_key(stream) - return tuple(0 if item is None else item for item in raw_key) - - dynamically_sorted_streams = sorted( - filtered_streams, key=safe_sort_key, reverse=True - ) + except (TypeError, Exception): + logging.exception( + f"torrent_sorting_priority: {user_data.torrent_sorting_priority}: sort data: {[dynamic_sort_key(stream) for stream in filtered_streams]}" + ) + dynamically_sorted_streams = filtered_streams # Step 4: Limit streams per resolution based on user preference, after dynamic sorting limited_streams = [] @@ -390,7 +402,7 @@ async def process_stream( stream_url, behavior_hints = stream.url, stream.behaviorHints behavior_hints = behavior_hints if behavior_hints else {} - if stream.drm_key or "dlhd" in stream.source: + if stream.drm_key: if not is_mediaflow_proxy_enabled: return "MEDIAFLOW_NEEDED" stream_url = get_proxy_url(stream, mediaflow_config) @@ -418,7 +430,10 @@ def get_proxy_url(stream: TVStreams, mediaflow_config) -> str: if stream.drm_key: query_params = {"key_id": stream.drm_key_id, "key": stream.drm_key} elif "dlhd" in stream.source: - query_params = {"use_request_proxy": False} + query_params = { + "use_request_proxy": False, + "key_url": config_manager.get_scraper_config("dlhd", "key_url"), + } return encode_mediaflow_proxy_url( mediaflow_config.proxy_url, @@ -426,6 +441,9 @@ def get_proxy_url(stream: TVStreams, mediaflow_config) -> str: stream.url, query_params=query_params, request_headers=stream.behaviorHints.get("proxyHeaders", {}).get("request", {}), + response_headers=stream.behaviorHints.get("proxyHeaders", {}).get( + "response", {} + ), encryption_api_password=mediaflow_config.api_password, ) @@ -460,7 +478,7 @@ async def fetch_downloaded_info_hashes( return downloaded_info_hashes except Exception as error: - logging.error( + logging.exception( f"Failed to fetch downloaded info hashes for {user_data.streaming_provider.service}: {error}" ) pass diff --git a/utils/runtime_const.py b/utils/runtime_const.py index ba1f4dd..5e6992d 100644 --- a/utils/runtime_const.py +++ b/utils/runtime_const.py @@ -1,4 +1,5 @@ import re +from datetime import timedelta from fastapi.templating import Jinja2Templates import redis @@ -41,3 +42,14 @@ REDIS_SYNC_CLIENT: redis.Redis = redis.Redis( REDIS_ASYNC_CLIENT: redis.asyncio.Redis = redis.asyncio.Redis( connection_pool=redis.asyncio.ConnectionPool.from_url(settings.redis_url) ) + + +PROWLARR_SEARCH_TTL = int( + timedelta(hours=settings.prowlarr_search_interval_hour).total_seconds() +) +TORRENTIO_SEARCH_TTL = int( + timedelta(days=settings.torrentio_search_interval_days).total_seconds() +) +ZILEAN_SEARCH_TTL = int( + timedelta(hours=settings.zilean_search_interval_hour).total_seconds() +) diff --git a/utils/torrent.py b/utils/torrent.py index b6750b5..1a6d342 100644 --- a/utils/torrent.py +++ b/utils/torrent.py @@ -80,7 +80,7 @@ def extract_torrent_metadata(content: bytes, is_parse_ptt: bool = True) -> dict: metadata.update(PTT.parse_title(torrent_name, True)) return metadata except Exception as e: - logging.error(f"Error occurred: {e}") + logging.exception(f"Error occurred: {e}") return {} diff --git a/utils/validation_helper.py b/utils/validation_helper.py index f113aa1..46e2a12 100644 --- a/utils/validation_helper.py +++ b/utils/validation_helper.py @@ -4,8 +4,7 @@ import logging from urllib import parse from urllib.parse import urlparse -import aiohttp -from aiohttp import ClientError +import httpx from db import schemas from utils import const @@ -18,14 +17,17 @@ def is_valid_url(url: str) -> bool: async def does_url_exist(url: str) -> bool: - async with aiohttp.ClientSession() as session: + async with httpx.AsyncClient() as client: try: - async with session.head( - url, allow_redirects=True, timeout=10, headers=const.UA_HEADER - ) as response: - logging.info("URL: %s, Status: %s", url, response.status) - return response.status == 200 - except (ClientError, asyncio.TimeoutError) as err: + response = await client.head( + url, timeout=10, headers=const.UA_HEADER, follow_redirects=True + ) + response.raise_for_status() + return response.status_code == 200 + except httpx.HTTPStatusError as err: + logging.error("URL: %s, Status: %s", url, err.response.status_code) + return False + except (httpx.RequestError, httpx.TimeoutException) as err: logging.error("URL: %s, Status: %s", url, err) return False @@ -41,20 +43,29 @@ async def validate_live_stream_url( return False headers = behaviour_hint.get("proxyHeaders", {}).get("request", {}) - async with aiohttp.ClientSession() as session: + async with httpx.AsyncClient() as client: try: - async with session.head( - url, - allow_redirects=True, - headers=headers, - timeout=aiohttp.ClientTimeout(total=20), - ) as response: - content_type = response.headers.get("content-type", "").lower() - is_valid = content_type in const.IPTV_VALID_CONTENT_TYPES - return is_valid - except (ClientError, asyncio.TimeoutError) as err: + response = await client.head( + url, timeout=10, headers=headers, follow_redirects=True + ) + response.raise_for_status() + content_type = ( + behaviour_hint.get("proxyHeaders", {}) + .get("response", {}) + .get("Content-Type", response.headers.get("content-type", "").lower()) + ) + is_valid = content_type in const.IPTV_VALID_CONTENT_TYPES + return is_valid + except ( + httpx.RequestError, + httpx.TimeoutException, + httpx.HTTPStatusError, + ) as err: logging.error(err) return False + except Exception as e: + logging.exception(e) + return False async def validate_m3u8_or_mpd_url_with_cache(url: str, behaviour_hint: dict): @@ -78,13 +89,12 @@ class ValidationError(Exception): async def validate_yt_id(yt_id: str) -> bool: image_url = f"https://img.youtube.com/vi/{yt_id}/mqdefault.jpg" - async with aiohttp.ClientSession() as session: + async with httpx.AsyncClient() as client: try: - async with session.head( - image_url, allow_redirects=True, timeout=aiohttp.ClientTimeout(total=5) - ) as response: - return response.status == 200 - except (ClientError, asyncio.TimeoutError): + response = await client.head(image_url, timeout=10, follow_redirects=True) + response.raise_for_status() + return response.status_code == 200 + except (httpx.HTTPStatusError, httpx.TimeoutException, Exception): return False