Update Python version & refactror Redis client usage and streaming provider route endpoints (#277)

* Update Python version

* Refactor Redis client

* Refactor streaming routes endpoint functions
This commit is contained in:
Mohamed Zumair
2024-09-05 06:46:07 +05:30
committed by GitHub
parent 644df9bf82
commit 649de9e503
28 changed files with 668 additions and 653 deletions
+1 -1
View File
@@ -49,4 +49,4 @@ parsett = {git = "git+https://github.com/mhdzumair/PTT"}
pysocks = "*"
[requires]
python_version = "3.11"
python_version = "3.12"
Generated
+247 -256
View File
@@ -1,11 +1,11 @@
{
"_meta": {
"hash": {
"sha256": "acd2974921d2b7abd8338e957965bb1ec961072cdf5178e68fe0b9c6e0b72fd2"
"sha256": "239b00e457ce2de7b45a803ff935eff1c405444b6ebba30435661c32f15df61f"
},
"pipfile-spec": 6,
"requires": {
"python_version": "3.11"
"python_version": "3.12"
},
"sources": [
{
@@ -940,12 +940,12 @@
},
"httpx": {
"hashes": [
"sha256:71d5465162c13681bff01ad59b2cc68dd838ea1f10e51574bac27103f00c91a5",
"sha256:a0cb88a46f32dc874e04ee956e4c2764aba2aa228f650b06788ba6bda2962ab5"
"sha256:7bb2708e112d8fdd7829cd4243970f0c223274051cb35ee80c03301ee29a3df0",
"sha256:f7c2be1d2f3c3c3160d441802406b206c2b76f5947b11115e6df10c6c65e66c2"
],
"index": "pypi",
"markers": "python_version >= '3.8'",
"version": "==0.27.0"
"version": "==0.27.2"
},
"humanize": {
"git": "git+https://github.com/python-humanize/humanize.git",
@@ -1374,7 +1374,7 @@
"parsett": {
"git": "git+https://github.com/mhdzumair/PTT",
"markers": "python_version >= '3.11' and python_version < '4.0'",
"ref": "a9da5866b234a8d956f6fecce9703e5902912930"
"ref": "80783658cf72afbc387a790cc8cf3d4ad04626cf"
},
"pikpakapi": {
"git": "git+https://github.com/mhdzumair/PikPakAPI.git",
@@ -2316,11 +2316,11 @@
},
"setuptools": {
"hashes": [
"sha256:b208925fcb9f7af924ed2dc04708ea89791e24bde0d3020b27df0e116088b34e",
"sha256:d59a3e788ab7e012ab2c4baed1b376da6366883ee20d7a5fc426816e3d7b1193"
"sha256:0274581a0037b638b9fc1c6883cc71c0210865aaa76073f7882376b641b84e8f",
"sha256:a85e96b8be2b906f3e3e789adec6a9323abf79758ecfa3065bd740d81158b11e"
],
"markers": "python_version >= '3.8'",
"version": "==73.0.1"
"version": "==74.0.0"
},
"six": {
"hashes": [
@@ -2480,7 +2480,7 @@
"sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d",
"sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"
],
"markers": "python_version < '3.13'",
"markers": "python_version >= '3.8'",
"version": "==4.12.2"
},
"tzlocal": {
@@ -2565,43 +2565,38 @@
},
"watchdog": {
"hashes": [
"sha256:0b4359067d30d5b864e09c8597b112fe0a0a59321a0f331498b013fb097406b4",
"sha256:0d8a7e523ef03757a5aa29f591437d64d0d894635f8a50f370fe37f913ce4e19",
"sha256:0e83619a2d5d436a7e58a1aea957a3c1ccbf9782c43c0b4fed80580e5e4acd1a",
"sha256:10b6683df70d340ac3279eff0b2766813f00f35a1d37515d2c99959ada8f05fa",
"sha256:132937547a716027bd5714383dfc40dc66c26769f1ce8a72a859d6a48f371f3a",
"sha256:1cdcfd8142f604630deef34722d695fb455d04ab7cfe9963055df1fc69e6727a",
"sha256:2d468028a77b42cc685ed694a7a550a8d1771bb05193ba7b24006b8241a571a1",
"sha256:32be97f3b75693a93c683787a87a0dc8db98bb84701539954eef991fb35f5fbc",
"sha256:770eef5372f146997638d737c9a3c597a3b41037cfbc5c41538fc27c09c3a3f9",
"sha256:7c7d4bf585ad501c5f6c980e7be9c4f15604c7cc150e942d82083b31a7548930",
"sha256:88456d65f207b39f1981bf772e473799fcdc10801062c36fd5ad9f9d1d463a73",
"sha256:914285126ad0b6eb2258bbbcb7b288d9dfd655ae88fa28945be05a7b475a800b",
"sha256:936acba76d636f70db8f3c66e76aa6cb5136a936fc2a5088b9ce1c7a3508fc83",
"sha256:980b71510f59c884d684b3663d46e7a14b457c9611c481e5cef08f4dd022eed7",
"sha256:984306dc4720da5498b16fc037b36ac443816125a3705dfde4fd90652d8028ef",
"sha256:a2cffa171445b0efa0726c561eca9a27d00a1f2b83846dbd5a4f639c4f8ca8e1",
"sha256:aa160781cafff2719b663c8a506156e9289d111d80f3387cf3af49cedee1f040",
"sha256:b2c45f6e1e57ebb4687690c05bc3a2c1fb6ab260550c4290b8abb1335e0fd08b",
"sha256:b4dfbb6c49221be4535623ea4474a4d6ee0a9cef4a80b20c28db4d858b64e270",
"sha256:baececaa8edff42cd16558a639a9b0ddf425f93d892e8392a56bf904f5eff22c",
"sha256:bcfd02377be80ef3b6bc4ce481ef3959640458d6feaae0bd43dd90a43da90a7d",
"sha256:c0b14488bd336c5b1845cee83d3e631a1f8b4e9c5091ec539406e4a324f882d8",
"sha256:c100d09ac72a8a08ddbf0629ddfa0b8ee41740f9051429baa8e31bb903ad7508",
"sha256:c344453ef3bf875a535b0488e3ad28e341adbd5a9ffb0f7d62cefacc8824ef2b",
"sha256:c50f148b31b03fbadd6d0b5980e38b558046b127dc483e5e4505fcef250f9503",
"sha256:c82253cfc9be68e3e49282831afad2c1f6593af80c0daf1287f6a92657986757",
"sha256:cd67c7df93eb58f360c43802acc945fa8da70c675b6fa37a241e17ca698ca49b",
"sha256:d7ab624ff2f663f98cd03c8b7eedc09375a911794dfea6bf2a359fcc266bff29",
"sha256:e252f8ca942a870f38cf785aef420285431311652d871409a64e2a0a52a2174c",
"sha256:ede7f010f2239b97cc79e6cb3c249e72962404ae3865860855d5cbe708b0fd22",
"sha256:eeea812f38536a0aa859972d50c76e37f4456474b02bd93674d1947cf1e39578",
"sha256:f15edcae3830ff20e55d1f4e743e92970c847bcddc8b7509bcd172aa04de506e",
"sha256:f5315a8c8dd6dd9425b974515081fc0aadca1d1d61e078d2246509fd756141ee",
"sha256:f6ee8dedd255087bc7fe82adf046f0b75479b989185fb0bdf9a98b612170eac7",
"sha256:f7c739888c20f99824f7aa9d31ac8a97353e22d0c0e54703a547a218f6637eb3"
"sha256:0120b2fa65732797ffa65fa8ee5540c288aa861d91447df298626d6385a24658",
"sha256:01ab36cddc836a0f202c66267daaef92ba5c17c7d6436deff0587bb61234c5c9",
"sha256:0710e9502727f688a7e06d48078545c54485b3d6eb53b171810879d8223c362a",
"sha256:0834c21efa3e767849b09e667274604c7cdfe30b49eb95d794565c53f4db3c1e",
"sha256:109daafc5b0f2a98d1fa9475ff9737eb3559d57b18129a36495e20c71de0b44f",
"sha256:1228cb097e855d1798b550be8f0e9f0cfbac4384f9a3e91f66d250d03e11294e",
"sha256:16c1aa3377bb1f82c5e24277fcbf4e2cac3c4ce46aaaf7212d53caa9076eb7b7",
"sha256:1d17ec7e022c34fa7ddc72aa41bf28c9d1207ffb193df18ba4f6fde453725b3c",
"sha256:1e26f570dd7f5178656affb24d6f0e22ce66c8daf88d4061a27bfb9ac866b40d",
"sha256:22fcad6168fc43cf0e709bd854be5b8edbb0b260f0a6f28f1ea9baa53c6907f7",
"sha256:2aa59fab7ff75281778c649557275ca3085eccbdf825a0e2a5ca3810e977afe5",
"sha256:3c177085c3d210d1c73cb4569442bdaef706ebebc423bd7aed9e90fc12b2e553",
"sha256:3c2d50fdb86aa6df3973313272f5a17eb26eab29ff5a0bf54b6d34597b4dc4e4",
"sha256:4fe6780915000743074236b21b6c37419aea71112af62237881bc265589fe463",
"sha256:663b096368ed7831ac42259919fdb9e0a1f0a8994d972675dfbcca0225e74de1",
"sha256:685931412978d00a91a193d9018fc9e394e565e8e7a0c275512a80e59c6e85f8",
"sha256:6c96b1706430839872a3e33b9370ee3f7a0079f6b828129d88498ad1f96a0f45",
"sha256:6e58eafe9cc5ceebe1562cdb89bacdcd0ef470896e8b0139fe677a5abec243da",
"sha256:78db0fe0336958fc0e1269545c980b6f33d04d184ba191b2800a8b71d3e971a9",
"sha256:7e6b0e9b8a9dc3865d65888b5f5222da4ba9c4e09eab13cff5e305e7b7e7248f",
"sha256:990aedb9e2f336b45a70aed9c014450e7c4a70fd99c5f5b1834d57e1453a177e",
"sha256:b8d747bf6d8fe5ce89cb1a36c3724d1599bd4cde3f90fcba518e6260c7058a52",
"sha256:bc16d448a74a929b896ed9578c25756b2125400b19b3258be8d9a681c7ae8e71",
"sha256:bf3216ec994eabb2212df9861f19056ca0d4cd3516d56cb95801933876519bfe",
"sha256:c2b4d90962639ae7cee371ea3a8da506831945d4418eee090c53bc38e6648dc6",
"sha256:cb59ad83a1700304fc1ac7bc53ae9e5cbe9d60a52ed9bba8e2e2d782a201bb2b",
"sha256:d146331e6b206baa9f6dd40f72b5783ad2302c240df68e7fce196d30588ccf7b",
"sha256:d1acef802916083f2ad7988efc7decf07e46e266916c0a09d8fb9d387288ea12",
"sha256:d76efab5248aafbf8a2c2a63cd7b9545e6b346ad1397af8b862a3bb3140787d8",
"sha256:ff4e957c45c446de34c513eadce01d0b65da7eee47c01dce472dd136124552c9"
],
"version": "==4.0.2"
"version": "==5.0.0"
},
"watchdog-gevent": {
"hashes": [
@@ -2612,186 +2607,182 @@
},
"watchfiles": {
"hashes": [
"sha256:02b7ba9d4557149410747353e7325010d48edcfe9d609a85cb450f17fd50dc3d",
"sha256:02ff5d7bd066c6a7673b17c8879cd8ee903078d184802a7ee851449c43521bdd",
"sha256:0e01bcb8d767c58865207a6c2f2792ad763a0fe1119fb0a430f444f5b02a5ea0",
"sha256:0eff099a4df36afaa0eea7a913aa64dcf2cbd4e7a4f319a73012210af4d23810",
"sha256:109a61763e7318d9f821b878589e71229f97366fa6a5c7720687d367f3ab9eef",
"sha256:11698bb2ea5e991d10f1f4f83a39a02f91e44e4bd05f01b5c1ec04c9342bf63c",
"sha256:130a896d53b48a1cecccfa903f37a1d87dbb74295305f865a3e816452f6e49e4",
"sha256:1733b9bc2c8098c6bdb0ff7a3d7cb211753fecb7bd99bdd6df995621ee1a574b",
"sha256:18e2de19801b0eaa4c5292a223effb7cfb43904cb742c5317a0ac686ed604765",
"sha256:1cf7f486169986c4b9d34087f08ce56a35126600b6fef3028f19ca16d5889071",
"sha256:1d636c8aeb28cdd04a4aa89030c4b48f8b2954d8483e5f989774fa441c0ed57b",
"sha256:1db691bad0243aed27c8354b12d60e8e266b75216ae99d33e927ff5238d270b5",
"sha256:1e5f3ca0ff47940ce0a389457b35d6df601c317c1e1a9615981c474452f98de1",
"sha256:1ebaebb53b34690da0936c256c1cdb0914f24fb0e03da76d185806df9328abed",
"sha256:20b423b58f5fdde704a226b598a2d78165fe29eb5621358fe57ea63f16f165c4",
"sha256:2368c5371c17fdcb5a2ea71c5c9d49f9b128821bfee69503cc38eae00feb3220",
"sha256:24655e8c1c9c114005c3868a3d432c8aa595a786b8493500071e6a52f3d09217",
"sha256:2537ef60596511df79b91613a5bb499b63f46f01a11a81b0a2b0dedf645d0a9c",
"sha256:296e0b29ab0276ca59d82d2da22cbbdb39a23eed94cca69aed274595fb3dfe42",
"sha256:2aec5c29915caf08771d2507da3ac08e8de24a50f746eb1ed295584ba1820330",
"sha256:2dddc2487d33e92f8b6222b5fb74ae2cfde5e8e6c44e0248d24ec23befdc5366",
"sha256:37fd826dac84c6441615aa3f04077adcc5cac7194a021c9f0d69af20fb9fa788",
"sha256:3af1b05361e1cc497bf1be654a664750ae61f5739e4bb094a2be86ec8c6db9b6",
"sha256:40cb8fa00028908211eb9f8d47744dca21a4be6766672e1ff3280bee320436f1",
"sha256:46f1d8069a95885ca529645cdbb05aea5837d799965676e1b2b1f95a4206313e",
"sha256:486bda18be5d25ab5d932699ceed918f68eb91f45d018b0343e3502e52866e5e",
"sha256:48a1b05c0afb2cd2f48c1ed2ae5487b116e34b93b13074ed3c22ad5c743109f0",
"sha256:4ccd3011cc7ee2f789af9ebe04745436371d36afe610028921cab9f24bb2987b",
"sha256:4ea756e425ab2dfc8ef2a0cb87af8aa7ef7dfc6fc46c6f89bcf382121d4fff75",
"sha256:524fcb8d59b0dbee2c9b32207084b67b2420f6431ed02c18bd191e6c575f5c48",
"sha256:532e1f2c491274d1333a814e4c5c2e8b92345d41b12dc806cf07aaff786beb66",
"sha256:556347b0abb4224c5ec688fc58214162e92a500323f50182f994f3ad33385dcb",
"sha256:62d2b18cb1edaba311fbbfe83fb5e53a858ba37cacb01e69bc20553bb70911b8",
"sha256:6991e3a78f642368b8b1b669327eb6751439f9f7eaaa625fae67dd6070ecfa0b",
"sha256:6a9265cf87a5b70147bfb2fec14770ed5b11a5bb83353f0eee1c25a81af5abfe",
"sha256:6b1a950ab299a4a78fd6369a97b8763732bfb154fdb433356ec55a5bce9515c1",
"sha256:6bb91fa4d0b392f0f7e27c40981e46dda9eb0fbc84162c7fb478fe115944f491",
"sha256:6c21a5467f35c61eafb4e394303720893066897fca937bade5b4f5877d350ff8",
"sha256:7ca6b71dcc50d320c88fb2d88ecd63924934a8abc1673683a242a7ca7d39e781",
"sha256:7cf12ac34c444362f3261fb3ff548f0037ddd4c5bb85f66c4be30d2936beb3c5",
"sha256:7f7252f52a09f8fa5435dc82b6af79483118ce6bd51eb74e6269f05ee22a7b9f",
"sha256:85042ab91814fca99cec4678fc063fb46df4cbb57b4835a1cc2cb7a51e10250e",
"sha256:857af85d445b9ba9178db95658c219dbd77b71b8264e66836a6eba4fbf49c320",
"sha256:87f889f6e58849ddb7c5d2cb19e2e074917ed1c6e3ceca50405775166492cca8",
"sha256:8ada449e22198c31fb013ae7e9add887e8d2bd2335401abd3cbc55f8c5083647",
"sha256:8e56fbcdd27fce061854ddec99e015dd779cae186eb36b14471fc9ae713b118c",
"sha256:8f48c917ffd36ff9a5212614c2d0d585fa8b064ca7e66206fb5c095015bc8207",
"sha256:9338ade39ff24f8086bb005d16c29f8e9f19e55b18dcb04dfa26fcbc09da497b",
"sha256:9837edf328b2805346f91209b7e660f65fb0e9ca18b7459d075d58db082bf981",
"sha256:9d183e3888ada88185ab17064079c0db8c17e32023f5c278d7bf8014713b1b5b",
"sha256:9f02a259fcbbb5fcfe7a0805b1097ead5ba7a043e318eef1db59f93067f0b49b",
"sha256:9f8e6bb5ac007d4a4027b25f09827ed78cbbd5b9700fd6c54429278dacce05d1",
"sha256:9ff785af8bacdf0be863ec0c428e3288b817e82f3d0c1d652cd9c6d509020dd0",
"sha256:a0b2c25040a3c0ce0e66c7779cc045fdfbbb8d59e5aabfe033000b42fe44b53e",
"sha256:a753993635eccf1ecb185dedcc69d220dab41804272f45e4aef0a67e790c3eb3",
"sha256:a8323daae27ea290ba3350c70c836c0d2b0fb47897fa3b0ca6a5375b952b90d3",
"sha256:a8f195338a5a7b50a058522b39517c50238358d9ad8284fd92943643144c0c03",
"sha256:a96ac14e184aa86dc43b8a22bb53854760a58b2966c2b41580de938e9bf26ed0",
"sha256:aafea64a3ae698695975251f4254df2225e2624185a69534e7fe70581066bc1b",
"sha256:aba037c1310dd108411d27b3d5815998ef0e83573e47d4219f45753c710f969f",
"sha256:b1f67312efa3902a8e8496bfa9824d3bec096ff83c4669ea555c6bdd213aa516",
"sha256:b4ac73b02ca1824ec0a7351588241fd3953748d3774694aa7ddb5e8e46aef3e3",
"sha256:b8d3c5cd327dd6ce0edfc94374fb5883d254fe78a5e9d9dfc237a1897dc73cd1",
"sha256:b98732ec893975455708d6fc9a6daab527fc8bbe65be354a3861f8c450a632a4",
"sha256:ba31c32f6b4dceeb2be04f717811565159617e28d61a60bb616b6442027fd4b9",
"sha256:bd3e2d64500a6cad28bcd710ee6269fbeb2e5320525acd0cfab5f269ade68581",
"sha256:bee8ce357a05c20db04f46c22be2d1a2c6a8ed365b325d08af94358e0688eeb4",
"sha256:c5e7803a65eb2d563c73230e9d693c6539e3c975ccfe62526cadde69f3fda0cf",
"sha256:c846884b2e690ba62a51048a097acb6b5cd263d8bd91062cd6137e2880578472",
"sha256:d1aa4cc85202956d1a65c88d18c7b687b8319dbe6b1aec8969784ef7a10e7d1a",
"sha256:d2d42254b189a346249424fb9bb39182a19289a2409051ee432fb2926bad966a",
"sha256:dccc858372a56080332ea89b78cfb18efb945da858fabeb67f5a44fa0bcb4ebb",
"sha256:dd41d5c72417b87c00b1b635738f3c283e737d75c5fa5c3e1c60cd03eac3af77",
"sha256:e087e8fdf1270d000913c12e6eca44edd02aad3559b3e6b8ef00f0ce76e0636f",
"sha256:e397b64f7aaf26915bf2ad0f1190f75c855d11eb111cc00f12f97430153c2eab",
"sha256:e495ed2a7943503766c5d1ff05ae9212dc2ce1c0e30a80d4f0d84889298fa304",
"sha256:e75695cc952e825fa3e0684a7f4a302f9128721f13eedd8dbd3af2ba450932b8",
"sha256:eb99c954291b2fad0eff98b490aa641e128fbc4a03b11c8a0086de8b7077fb75",
"sha256:ecf2be4b9eece4f3da8ba5f244b9e51932ebc441c0867bd6af46a3d97eb068d6",
"sha256:ee1f5fcbf5bc33acc0be9dd31130bcba35d6d2302e4eceafafd7d9018c7755ab",
"sha256:ee7db6e36e7a2c15923072e41ea24d9a0cf39658cb0637ecc9307b09d28827e1",
"sha256:efadd40fca3a04063d40c4448c9303ce24dd6151dc162cfae4a2a060232ebdcb",
"sha256:f18de0f82c62c4197bea5ecf4389288ac755896aac734bd2cc44004c56e4ac47",
"sha256:f449afbb971df5c6faeb0a27bca0427d7b600dd8f4a068492faec18023f0dcff",
"sha256:f46c6f0aec8d02a52d97a583782d9af38c19a29900747eb048af358a9c1d8e5b",
"sha256:fb02d41c33be667e6135e6686f1bb76104c88a312a18faa0ef0262b5bf7f1a0f",
"sha256:fd257f98cff9c6cb39eee1a83c7c3183970d8a8d23e8cf4f47d9a21329285cee"
"sha256:01550ccf1d0aed6ea375ef259706af76ad009ef5b0203a3a4cce0f6024f9b68a",
"sha256:01def80eb62bd5db99a798d5e1f5f940ca0a05986dcfae21d833af7a46f7ee22",
"sha256:07cdef0c84c03375f4e24642ef8d8178e533596b229d32d2bbd69e5128ede02a",
"sha256:083dc77dbdeef09fa44bb0f4d1df571d2e12d8a8f985dccde71ac3ac9ac067a0",
"sha256:1cf1f6dd7825053f3d98f6d33f6464ebdd9ee95acd74ba2c34e183086900a827",
"sha256:21ab23fdc1208086d99ad3f69c231ba265628014d4aed31d4e8746bd59e88cd1",
"sha256:2dadf8a8014fde6addfd3c379e6ed1a981c8f0a48292d662e27cabfe4239c83c",
"sha256:2e28d91ef48eab0afb939fa446d8ebe77e2f7593f5f463fd2bb2b14132f95b6e",
"sha256:2efec17819b0046dde35d13fb8ac7a3ad877af41ae4640f4109d9154ed30a188",
"sha256:30bbd525c3262fd9f4b1865cb8d88e21161366561cd7c9e1194819e0a33ea86b",
"sha256:316449aefacf40147a9efaf3bd7c9bdd35aaba9ac5d708bd1eb5763c9a02bef5",
"sha256:327763da824817b38ad125dcd97595f942d720d32d879f6c4ddf843e3da3fe90",
"sha256:32aa53a9a63b7f01ed32e316e354e81e9da0e6267435c7243bf8ae0f10b428ef",
"sha256:34e19e56d68b0dad5cff62273107cf5d9fbaf9d75c46277aa5d803b3ef8a9e9b",
"sha256:3770e260b18e7f4e576edca4c0a639f704088602e0bc921c5c2e721e3acb8d15",
"sha256:3d2e3ab79a1771c530233cadfd277fcc762656d50836c77abb2e5e72b88e3a48",
"sha256:41face41f036fee09eba33a5b53a73e9a43d5cb2c53dad8e61fa6c9f91b5a51e",
"sha256:43e3e37c15a8b6fe00c1bce2473cfa8eb3484bbeecf3aefbf259227e487a03df",
"sha256:449f43f49c8ddca87c6b3980c9284cab6bd1f5c9d9a2b00012adaaccd5e7decd",
"sha256:4933a508d2f78099162da473841c652ad0de892719043d3f07cc83b33dfd9d91",
"sha256:49d617df841a63b4445790a254013aea2120357ccacbed00253f9c2b5dc24e2d",
"sha256:49fb58bcaa343fedc6a9e91f90195b20ccb3135447dc9e4e2570c3a39565853e",
"sha256:4a7fa2bc0efef3e209a8199fd111b8969fe9db9c711acc46636686331eda7dd4",
"sha256:4abf4ad269856618f82dee296ac66b0cd1d71450fc3c98532d93798e73399b7a",
"sha256:4b8693502d1967b00f2fb82fc1e744df128ba22f530e15b763c8d82baee15370",
"sha256:4d28cea3c976499475f5b7a2fec6b3a36208656963c1a856d328aeae056fc5c1",
"sha256:5148c2f1ea043db13ce9b0c28456e18ecc8f14f41325aa624314095b6aa2e9ea",
"sha256:54ca90a9ae6597ae6dc00e7ed0a040ef723f84ec517d3e7ce13e63e4bc82fa04",
"sha256:551ec3ee2a3ac9cbcf48a4ec76e42c2ef938a7e905a35b42a1267fa4b1645896",
"sha256:5c51749f3e4e269231510da426ce4a44beb98db2dce9097225c338f815b05d4f",
"sha256:632676574429bee8c26be8af52af20e0c718cc7f5f67f3fb658c71928ccd4f7f",
"sha256:6509ed3f467b79d95fc62a98229f79b1a60d1b93f101e1c61d10c95a46a84f43",
"sha256:6bdcfa3cd6fdbdd1a068a52820f46a815401cbc2cb187dd006cb076675e7b735",
"sha256:7138eff8baa883aeaa074359daabb8b6c1e73ffe69d5accdc907d62e50b1c0da",
"sha256:7211b463695d1e995ca3feb38b69227e46dbd03947172585ecb0588f19b0d87a",
"sha256:73bde715f940bea845a95247ea3e5eb17769ba1010efdc938ffcb967c634fa61",
"sha256:78470906a6be5199524641f538bd2c56bb809cd4bf29a566a75051610bc982c3",
"sha256:7ae3e208b31be8ce7f4c2c0034f33406dd24fbce3467f77223d10cd86778471c",
"sha256:7e4bd963a935aaf40b625c2499f3f4f6bbd0c3776f6d3bc7c853d04824ff1c9f",
"sha256:82ae557a8c037c42a6ef26c494d0631cacca040934b101d001100ed93d43f361",
"sha256:82b2509f08761f29a0fdad35f7e1638b8ab1adfa2666d41b794090361fb8b855",
"sha256:8360f7314a070c30e4c976b183d1d8d1585a4a50c5cb603f431cebcbb4f66327",
"sha256:85d5f0c7771dcc7a26c7a27145059b6bb0ce06e4e751ed76cdf123d7039b60b5",
"sha256:88bcd4d0fe1d8ff43675360a72def210ebad3f3f72cabfeac08d825d2639b4ab",
"sha256:9301c689051a4857d5b10777da23fafb8e8e921bcf3abe6448a058d27fb67633",
"sha256:951088d12d339690a92cef2ec5d3cfd957692834c72ffd570ea76a6790222777",
"sha256:95cf3b95ea665ab03f5a54765fa41abf0529dbaf372c3b83d91ad2cfa695779b",
"sha256:96619302d4374de5e2345b2b622dc481257a99431277662c30f606f3e22f42be",
"sha256:999928c6434372fde16c8f27143d3e97201160b48a614071261701615a2a156f",
"sha256:9a60e2bf9dc6afe7f743e7c9b149d1fdd6dbf35153c78fe3a14ae1a9aee3d98b",
"sha256:9f895d785eb6164678ff4bb5cc60c5996b3ee6df3edb28dcdeba86a13ea0465e",
"sha256:a2a9891723a735d3e2540651184be6fd5b96880c08ffe1a98bae5017e65b544b",
"sha256:a974231b4fdd1bb7f62064a0565a6b107d27d21d9acb50c484d2cdba515b9366",
"sha256:aa0fd7248cf533c259e59dc593a60973a73e881162b1a2f73360547132742823",
"sha256:acbfa31e315a8f14fe33e3542cbcafc55703b8f5dcbb7c1eecd30f141df50db3",
"sha256:afb72325b74fa7a428c009c1b8be4b4d7c2afedafb2982827ef2156646df2fe1",
"sha256:b3ef2c69c655db63deb96b3c3e587084612f9b1fa983df5e0c3379d41307467f",
"sha256:b52a65e4ea43c6d149c5f8ddb0bef8d4a1e779b77591a458a893eb416624a418",
"sha256:b665caeeda58625c3946ad7308fbd88a086ee51ccb706307e5b1fa91556ac886",
"sha256:b74fdffce9dfcf2dc296dec8743e5b0332d15df19ae464f0e249aa871fc1c571",
"sha256:b995bfa6bf01a9e09b884077a6d37070464b529d8682d7691c2d3b540d357a0c",
"sha256:bd82010f8ab451dabe36054a1622870166a67cf3fce894f68895db6f74bbdc94",
"sha256:bdcd5538e27f188dd3c804b4a8d5f52a7fc7f87e7fd6b374b8e36a4ca03db428",
"sha256:c79d7719d027b7a42817c5d96461a99b6a49979c143839fc37aa5748c322f234",
"sha256:cdab9555053399318b953a1fe1f586e945bc8d635ce9d05e617fd9fe3a4687d6",
"sha256:ce72dba6a20e39a0c628258b5c308779b8697f7676c254a845715e2a1039b968",
"sha256:d337193bbf3e45171c8025e291530fb7548a93c45253897cd764a6a71c937ed9",
"sha256:d3dcb774e3568477275cc76554b5a565024b8ba3a0322f77c246bc7111c5bb9c",
"sha256:d64ba08db72e5dfd5c33be1e1e687d5e4fcce09219e8aee893a4862034081d4e",
"sha256:d7a2e3b7f5703ffbd500dabdefcbc9eafeff4b9444bbdd5d83d79eedf8428fab",
"sha256:d831ee0a50946d24a53821819b2327d5751b0c938b12c0653ea5be7dea9c82ec",
"sha256:d9018153cf57fc302a2a34cb7564870b859ed9a732d16b41a9b5cb2ebed2d444",
"sha256:e5171ef898299c657685306d8e1478a45e9303ddcd8ac5fed5bd52ad4ae0b69b",
"sha256:e94e98c7cb94cfa6e071d401ea3342767f28eb5a06a58fafdc0d2a4974f4f35c",
"sha256:ec39698c45b11d9694a1b635a70946a5bad066b593af863460a8e600f0dff1ca",
"sha256:ed9aba6e01ff6f2e8285e5aa4154e2970068fe0fc0998c4380d0e6278222269b",
"sha256:edf71b01dec9f766fb285b73930f95f730bb0943500ba0566ae234b5c1618c18",
"sha256:ee82c98bed9d97cd2f53bdb035e619309a098ea53ce525833e26b93f673bc318",
"sha256:f4c96283fca3ee09fb044f02156d9570d156698bc3734252175a38f0e8975f07",
"sha256:f7d9b87c4c55e3ea8881dfcbf6d61ea6775fffed1fedffaa60bd047d3c08c430",
"sha256:f83df90191d67af5a831da3a33dd7628b02a95450e168785586ed51e6d28943c",
"sha256:fca9433a45f18b7c779d2bae7beeec4f740d28b788b117a48368d95a3233ed83",
"sha256:fd92bbaa2ecdb7864b7600dcdb6f2f1db6e0346ed425fbd01085be04c63f0b05"
],
"version": "==0.23.0"
"version": "==0.24.0"
},
"websockets": {
"hashes": [
"sha256:02cc9bb1a887dac0e08bf657c5d00aa3fac0d03215d35a599130c2034ae6663a",
"sha256:038e7a0f1bfafc7bf52915ab3506b7a03d1e06381e9f60440c856e8918138151",
"sha256:05c25f7b849702950b6fd0e233989bb73a0d2bc83faa3b7233313ca395205f6d",
"sha256:06b3186e97bf9a33921fa60734d5ed90f2a9b407cce8d23c7333a0984049ef61",
"sha256:06df8306c241c235075d2ae77367038e701e53bc8c1bb4f6644f4f53aa6dedd0",
"sha256:0a8f7d65358a25172db00c69bcc7df834155ee24229f560d035758fd6613111a",
"sha256:1f661a4205741bdc88ac9c2b2ec003c72cee97e4acd156eb733662ff004ba429",
"sha256:265e1f0d3f788ce8ef99dca591a1aec5263b26083ca0934467ad9a1d1181067c",
"sha256:2be1382a4daa61e2f3e2be3b3c86932a8db9d1f85297feb6e9df22f391f94452",
"sha256:2e1cf4e1eb84b4fd74a47688e8b0940c89a04ad9f6937afa43d468e71128cd68",
"sha256:337837ac788d955728b1ab01876d72b73da59819a3388e1c5e8e05c3999f1afa",
"sha256:358d37c5c431dd050ffb06b4b075505aae3f4f795d7fff9794e5ed96ce99b998",
"sha256:35c2221b539b360203f3f9ad168e527bf16d903e385068ae842c186efb13d0ea",
"sha256:3670def5d3dfd5af6f6e2b3b243ea8f1f72d8da1ef927322f0703f85c90d9603",
"sha256:372f46a0096cfda23c88f7e42349a33f8375e10912f712e6b496d3a9a557290f",
"sha256:376a43a4fd96725f13450d3d2e98f4f36c3525c562ab53d9a98dd2950dca9a8a",
"sha256:384129ad0490e06bab2b98c1da9b488acb35bb11e2464c728376c6f55f0d45f3",
"sha256:3a20cf14ba7b482c4a1924b5e061729afb89c890ca9ed44ac4127c6c5986e424",
"sha256:3e6566e79c8c7cbea75ec450f6e1828945fc5c9a4769ceb1c7b6e22470539712",
"sha256:4782ec789f059f888c1e8fdf94383d0e64b531cffebbf26dd55afd53ab487ca4",
"sha256:4d70c89e3d3b347a7c4d3c33f8d323f0584c9ceb69b82c2ef8a174ca84ea3d4a",
"sha256:516062a0a8ef5ecbfa4acbaec14b199fc070577834f9fe3d40800a99f92523ca",
"sha256:5575031472ca87302aeb2ce2c2349f4c6ea978c86a9d1289bc5d16058ad4c10a",
"sha256:587245f0704d0bb675f919898d7473e8827a6d578e5a122a21756ca44b811ec8",
"sha256:602cbd010d8c21c8475f1798b705bb18567eb189c533ab5ef568bc3033fdf417",
"sha256:6058b6be92743358885ad6dcdecb378fde4a4c74d4dd16a089d07580c75a0e80",
"sha256:63b702fb31e3f058f946ccdfa551f4d57a06f7729c369e8815eb18643099db37",
"sha256:6ad684cb7efce227d756bae3e8484f2e56aa128398753b54245efdfbd1108f2c",
"sha256:6fd757f313c13c34dae9f126d3ba4cf97175859c719e57c6a614b781c86b617e",
"sha256:7334752052532c156d28b8eaf3558137e115c7871ea82adff69b6d94a7bee273",
"sha256:788bc841d250beccff67a20a5a53a15657a60111ef9c0c0a97fbdd614fae0fe2",
"sha256:7d14901fdcf212804970c30ab9ee8f3f0212e620c7ea93079d6534863444fb4e",
"sha256:7ea9c9c7443a97ea4d84d3e4d42d0e8c4235834edae652993abcd2aff94affd7",
"sha256:81a11a1ddd5320429db47c04d35119c3e674d215173d87aaeb06ae80f6e9031f",
"sha256:851fd0afb3bc0b73f7c5b5858975d42769a5fdde5314f4ef2c106aec63100687",
"sha256:85a1f92a02f0b8c1bf02699731a70a8a74402bb3f82bee36e7768b19a8ed9709",
"sha256:89d795c1802d99a643bf689b277e8604c14b5af1bc0a31dade2cd7a678087212",
"sha256:9202c0010c78fad1041e1c5285232b6508d3633f92825687549540a70e9e5901",
"sha256:939a16849d71203628157a5e4a495da63967c744e1e32018e9b9e2689aca64d4",
"sha256:93b8c2008f372379fb6e5d2b3f7c9ec32f7b80316543fd3a5ace6610c5cde1b0",
"sha256:94c1c02721139fe9940b38d28fb15b4b782981d800d5f40f9966264fbf23dcc8",
"sha256:9895df6cd0bfe79d09bcd1dbdc03862846f26fbd93797153de954306620c1d00",
"sha256:9cc7f35dcb49a4e32db82a849fcc0714c4d4acc9d2273aded2d61f87d7f660b7",
"sha256:9ed02c604349068d46d87ef4c2012c112c791f2bec08671903a6bb2bd9c06784",
"sha256:a00e1e587c655749afb5b135d8d3edcfe84ec6db864201e40a882e64168610b3",
"sha256:a1ab8f0e0cadc5be5f3f9fa11a663957fecbf483d434762c8dfb8aa44948944a",
"sha256:a4de299c947a54fca9ce1c5fd4a08eb92ffce91961becb13bd9195f7c6e71b47",
"sha256:a7fbf2a8fe7556a8f4e68cb3e736884af7bf93653e79f6219f17ebb75e97d8f0",
"sha256:ad4fa707ff9e2ffee019e946257b5300a45137a58f41fbd9a4db8e684ab61528",
"sha256:ad818cdac37c0ad4c58e51cb4964eae4f18b43c4a83cb37170b0d90c31bd80cf",
"sha256:addf0a16e4983280efed272d8cb3b2e05f0051755372461e7d966b80a6554e16",
"sha256:ae7a519a56a714f64c3445cabde9fc2fc927e7eae44f413eae187cddd9e54178",
"sha256:b32f38bc81170fd56d0482d505b556e52bf9078b36819a8ba52624bd6667e39e",
"sha256:b5407c34776b9b77bd89a5f95eb0a34aaf91889e3f911c63f13035220eb50107",
"sha256:b7bf950234a482b7461afdb2ec99eee3548ec4d53f418c7990bb79c620476602",
"sha256:b89849171b590107f6724a7b0790736daead40926ddf47eadf998b4ff51d6414",
"sha256:bcea3eb58c09c3a31cc83b45c06d5907f02ddaf10920aaa6443975310f699b95",
"sha256:bd4ba86513430513e2aa25a441bb538f6f83734dc368a2c5d18afdd39097aa33",
"sha256:bf8eb5dca4f484a60f5327b044e842e0d7f7cdbf02ea6dc4a4f811259f1f1f0b",
"sha256:c026ee729c4ce55708a14b839ba35086dfae265fc12813b62d34ce33f4980c1c",
"sha256:c210d1460dc8d326ffdef9703c2f83269b7539a1690ad11ae04162bc1878d33d",
"sha256:c8feb8e19ef65c9994e652c5b0324abd657bedd0abeb946fb4f5163012c1e730",
"sha256:cbac2eb7ce0fac755fb983c9247c4a60c4019bcde4c0e4d167aeb17520cc7ef1",
"sha256:cbfe82a07596a044de78bb7a62519e71690c5812c26c5f1d4b877e64e4f46309",
"sha256:d3f3d2e20c442b58dbac593cb1e02bc02d149a86056cc4126d977ad902472e3b",
"sha256:d42a818e634f789350cd8fb413a3f5eec1cf0400a53d02062534c41519f5125c",
"sha256:d4b83cf7354cbbc058e97b3e545dceb75b8d9cf17fd5a19db419c319ddbaaf7a",
"sha256:d9726d2c9bd6aed8cb994d89b3910ca0079406edce3670886ec828a73e7bdd53",
"sha256:da7e501e59857e8e3e9d10586139dc196b80445a591451ca9998aafba1af5278",
"sha256:da7e918d82e7bdfc6f66d31febe1b2e28a1ca3387315f918de26f5e367f61572",
"sha256:dbbac01e80aee253d44c4f098ab3cc17c822518519e869b284cfbb8cd16cc9de",
"sha256:df5c0eff91f61b8205a6c9f7b255ff390cdb77b61c7b41f79ca10afcbb22b6cb",
"sha256:e07e76c49f39c5b45cbd7362b94f001ae209a3ea4905ae9a09cfd53b3c76373d",
"sha256:e1e10b3fbed7be4a59831d3a939900e50fcd34d93716e433d4193a4d0d1d335d",
"sha256:e39d393e0ab5b8bd01717cc26f2922026050188947ff54fe6a49dc489f7750b7",
"sha256:e5ba5e9b332267d0f2c33ede390061850f1ac3ee6cd1bdcf4c5ea33ead971966",
"sha256:e7a1963302947332c3039e3f66209ec73b1626f8a0191649e0713c391e9f5b0d",
"sha256:e7fcad070dcd9ad37a09d89a4cbc2a5e3e45080b88977c0da87b3090f9f55ead",
"sha256:eae368cac85adc4c7dc3b0d5f84ffcca609d658db6447387300478e44db70796",
"sha256:ede95125a30602b1691a4b1da88946bf27dae283cf30f22cd2cb8ca4b2e0d119",
"sha256:f5737c53eb2c8ed8f64b50d3dafd3c1dae739f78aa495a288421ac1b3de82717",
"sha256:f5f9d23fbbf96eefde836d9692670bfc89e2d159f456d499c5efcf6a6281c1af",
"sha256:f66e00e42f25ca7e91076366303e11c82572ca87cc5aae51e6e9c094f315ab41",
"sha256:f9af457ed593e35f467140d8b61d425495b127744a9d65d45a366f8678449a23",
"sha256:fa0839f35322f7b038d8adcf679e2698c3a483688cc92e3bd15ee4fb06669e9a",
"sha256:fd038bc9e2c134847f1e0ce3191797fad110756e690c2fdd9702ed34e7a43abb"
"sha256:00fd961943b6c10ee6f0b1130753e50ac5dcd906130dcd77b0003c3ab797d026",
"sha256:03d3f9ba172e0a53e37fa4e636b86cc60c3ab2cfee4935e66ed1d7acaa4625ad",
"sha256:0513c727fb8adffa6d9bf4a4463b2bade0186cbd8c3604ae5540fae18a90cb99",
"sha256:05e70fec7c54aad4d71eae8e8cab50525e899791fc389ec6f77b95312e4e9920",
"sha256:0617fd0b1d14309c7eab6ba5deae8a7179959861846cbc5cb528a7531c249448",
"sha256:06c0a667e466fcb56a0886d924b5f29a7f0886199102f0a0e1c60a02a3751cb4",
"sha256:0f52504023b1480d458adf496dc1c9e9811df4ba4752f0bc1f89ae92f4f07d0c",
"sha256:10a0dc7242215d794fb1918f69c6bb235f1f627aaf19e77f05336d147fce7c37",
"sha256:11f9976ecbc530248cf162e359a92f37b7b282de88d1d194f2167b5e7ad80ce3",
"sha256:132511bfd42e77d152c919147078460c88a795af16b50e42a0bd14f0ad71ddd2",
"sha256:139add0f98206cb74109faf3611b7783ceafc928529c62b389917a037d4cfdf4",
"sha256:14b9c006cac63772b31abbcd3e3abb6228233eec966bf062e89e7fa7ae0b7333",
"sha256:15c7d62ee071fa94a2fc52c2b472fed4af258d43f9030479d9c4a2de885fd543",
"sha256:165bedf13556f985a2aa064309baa01462aa79bf6112fbd068ae38993a0e1f1b",
"sha256:17118647c0ea14796364299e942c330d72acc4b248e07e639d34b75067b3cdd8",
"sha256:1841c9082a3ba4a05ea824cf6d99570a6a2d8849ef0db16e9c826acb28089e8f",
"sha256:1a678532018e435396e37422a95e3ab87f75028ac79570ad11f5bf23cd2a7d8c",
"sha256:1ee4cc030a4bdab482a37462dbf3ffb7e09334d01dd37d1063be1136a0d825fa",
"sha256:1f3cf6d6ec1142412d4535adabc6bd72a63f5f148c43fe559f06298bc21953c9",
"sha256:1f613289f4a94142f914aafad6c6c87903de78eae1e140fa769a7385fb232fdf",
"sha256:1fa082ea38d5de51dd409434edc27c0dcbd5fed2b09b9be982deb6f0508d25bc",
"sha256:249aab278810bee585cd0d4de2f08cfd67eed4fc75bde623be163798ed4db2eb",
"sha256:254ecf35572fca01a9f789a1d0f543898e222f7b69ecd7d5381d8d8047627bdb",
"sha256:2a02b0161c43cc9e0232711eff846569fad6ec836a7acab16b3cf97b2344c060",
"sha256:30d3a1f041360f029765d8704eae606781e673e8918e6b2c792e0775de51352f",
"sha256:3624fd8664f2577cf8de996db3250662e259bfbc870dd8ebdcf5d7c6ac0b5185",
"sha256:3f55b36d17ac50aa8a171b771e15fbe1561217510c8768af3d546f56c7576cdc",
"sha256:46af561eba6f9b0848b2c9d2427086cabadf14e0abdd9fde9d72d447df268418",
"sha256:47236c13be337ef36546004ce8c5580f4b1150d9538b27bf8a5ad8edf23ccfab",
"sha256:4a365bcb7be554e6e1f9f3ed64016e67e2fa03d7b027a33e436aecf194febb63",
"sha256:4d6ece65099411cfd9a48d13701d7438d9c34f479046b34c50ff60bb8834e43e",
"sha256:4e85f46ce287f5c52438bb3703d86162263afccf034a5ef13dbe4318e98d86e7",
"sha256:4f0426d51c8f0926a4879390f53c7f5a855e42d68df95fff6032c82c888b5f36",
"sha256:518f90e6dd089d34eaade01101fd8a990921c3ba18ebbe9b0165b46ebff947f0",
"sha256:52aed6ef21a0f1a2a5e310fb5c42d7555e9c5855476bbd7173c3aa3d8a0302f2",
"sha256:556e70e4f69be1082e6ef26dcb70efcd08d1850f5d6c5f4f2bcb4e397e68f01f",
"sha256:56a952fa2ae57a42ba7951e6b2605e08a24801a4931b5644dfc68939e041bc7f",
"sha256:59197afd478545b1f73367620407b0083303569c5f2d043afe5363676f2697c9",
"sha256:5df891c86fe68b2c38da55b7aea7095beca105933c697d719f3f45f4220a5e0e",
"sha256:63848cdb6fcc0bf09d4a155464c46c64ffdb5807ede4fb251da2c2692559ce75",
"sha256:64a11aae1de4c178fa653b07d90f2fb1a2ed31919a5ea2361a38760192e1858b",
"sha256:6724b554b70d6195ba19650fef5759ef11346f946c07dbbe390e039bcaa7cc3d",
"sha256:67494e95d6565bf395476e9d040037ff69c8b3fa356a886b21d8422ad86ae075",
"sha256:67648f5e50231b5a7f6d83b32f9c525e319f0ddc841be0de64f24928cd75a603",
"sha256:68264802399aed6fe9652e89761031acc734fc4c653137a5911c2bfa995d6d6d",
"sha256:699ba9dd6a926f82a277063603fc8d586b89f4cb128efc353b749b641fcddda7",
"sha256:6aa74a45d4cdc028561a7d6ab3272c8b3018e23723100b12e58be9dfa5a24491",
"sha256:6b41a1b3b561f1cba8321fb32987552a024a8f67f0d05f06fcf29f0090a1b956",
"sha256:71e6e5a3a3728886caee9ab8752e8113670936a193284be9d6ad2176a137f376",
"sha256:7d20516990d8ad557b5abeb48127b8b779b0b7e6771a265fa3e91767596d7d97",
"sha256:80e4ba642fc87fa532bac07e5ed7e19d56940b6af6a8c61d4429be48718a380f",
"sha256:872afa52a9f4c414d6955c365b6588bc4401272c629ff8321a55f44e3f62b553",
"sha256:8eb2b9a318542153674c6e377eb8cb9ca0fc011c04475110d3477862f15d29f0",
"sha256:9bbc525f4be3e51b89b2a700f5746c2a6907d2e2ef4513a8daafc98198b92237",
"sha256:a1a2e272d067030048e1fe41aa1ec8cfbbaabce733b3d634304fa2b19e5c897f",
"sha256:a5dc0c42ded1557cc7c3f0240b24129aefbad88af4f09346164349391dea8e58",
"sha256:acab3539a027a85d568c2573291e864333ec9d912675107d6efceb7e2be5d980",
"sha256:acbebec8cb3d4df6e2488fbf34702cbc37fc39ac7abf9449392cefb3305562e9",
"sha256:ad327ac80ba7ee61da85383ca8822ff808ab5ada0e4a030d66703cc025b021c4",
"sha256:b448a0690ef43db5ef31b3a0d9aea79043882b4632cfc3eaab20105edecf6097",
"sha256:b5a06d7f60bc2fc378a333978470dfc4e1415ee52f5f0fce4f7853eb10c1e9df",
"sha256:b74593e9acf18ea5469c3edaa6b27fa7ecf97b30e9dabd5a94c4c940637ab96e",
"sha256:b79915a1179a91f6c5f04ece1e592e2e8a6bd245a0e45d12fd56b2b59e559a32",
"sha256:b80f0c51681c517604152eb6a572f5a9378f877763231fddb883ba2f968e8817",
"sha256:b8ac5b46fd798bbbf2ac6620e0437c36a202b08e1f827832c4bf050da081b501",
"sha256:c3c493d0e5141ec055a7d6809a28ac2b88d5b878bb22df8c621ebe79a61123d0",
"sha256:c44ca9ade59b2e376612df34e837013e2b273e6c92d7ed6636d0556b6f4db93d",
"sha256:c4a6343e3b0714e80da0b0893543bf9a5b5fa71b846ae640e56e9abc6fbc4c83",
"sha256:c5870b4a11b77e4caa3937142b650fbbc0914a3e07a0cf3131f35c0587489c1c",
"sha256:ca48914cdd9f2ccd94deab5bcb5ac98025a5ddce98881e5cce762854a5de330b",
"sha256:cf2fae6d85e5dc384bf846f8243ddaa9197f3a1a70044f59399af001fd1f51d4",
"sha256:d450f5a7a35662a9b91a64aefa852f0c0308ee256122f5218a42f1d13577d71e",
"sha256:d6716c087e4aa0b9260c4e579bb82e068f84faddb9bfba9906cb87726fa2e870",
"sha256:d93572720d781331fb10d3da9ca1067817d84ad1e7c31466e9f5e59965618096",
"sha256:dbb0b697cc0655719522406c059eae233abaa3243821cfdfab1215d02ac10231",
"sha256:e33505534f3f673270dd67f81e73550b11de5b538c56fe04435d63c02c3f26b5",
"sha256:e801ca2f448850685417d723ec70298feff3ce4ff687c6f20922c7474b4746ae",
"sha256:e82db3756ccb66266504f5a3de05ac6b32f287faacff72462612120074103329",
"sha256:ef48e4137e8799998a343706531e656fdec6797b80efd029117edacb74b0a10a",
"sha256:f1d3d1f2eb79fe7b0fb02e599b2bf76a7619c79300fc55f0b5e2d382881d4f7f",
"sha256:f3fea72e4e6edb983908f0db373ae0732b275628901d909c382aae3b592589f2",
"sha256:f40de079779acbcdbb6ed4c65af9f018f8b77c5ec4e17a4b737c05c2db554491",
"sha256:f73e676a46b0fe9426612ce8caeca54c9073191a77c3e9d5c94697aef99296af",
"sha256:f9c9e258e3d5efe199ec23903f5da0eeaad58cf6fccb3547b74fd4750e5ac47a",
"sha256:fac2d146ff30d9dd2fcf917e5d147db037a5c573f0446c564f16f1f94cf87462",
"sha256:faef9ec6354fe4f9a2c0bbb52fb1ff852effc897e2a4501e25eb3a47cb0a4f89"
],
"version": "==13.0"
"version": "==13.0.1"
},
"yarl": {
"hashes": [
@@ -2899,43 +2890,43 @@
},
"zope.interface": {
"hashes": [
"sha256:03bd5c0db82237bbc47833a8b25f1cc090646e212f86b601903d79d7e6b37031",
"sha256:03f1452d5d1f279184d5bdb663a3dc39902d9320eceb63276240791e849054b6",
"sha256:10ebac566dd0cec66f942dc759d46a994a2b3ba7179420f0e2130f88f8a5f400",
"sha256:192b7a792e3145ed880ff6b1a206fdb783697cfdb4915083bfca7065ec845e60",
"sha256:19c829d52e921b9fe0b2c0c6a8f9a2508c49678ee1be598f87d143335b6a35dc",
"sha256:3f3495462bc0438b76536a0e10d765b168ae636092082531b88340dc40dcd118",
"sha256:3f52050c6a10d4a039ec6f2c58e5b3ade5cc570d16cf9d102711e6b8413c90e6",
"sha256:400d06c9ec8dbcc96f56e79376297e7be07a315605c9a2208720da263d44d76f",
"sha256:4ec212037becf6d2f705b7ed4538d56980b1e7bba237df0d8995cbbed29961dc",
"sha256:51d5713e8e38f2d3ec26e0dfdca398ed0c20abda2eb49ffc15a15a23eb8e5f6d",
"sha256:52f5253cca1b35eaeefa51abd366b87f48f8714097c99b131ba61f3fdbbb58e7",
"sha256:5566fd9271c89ad03d81b0831c37d46ae5e2ed211122c998637130159a120cf1",
"sha256:55bbcc74dc0c7ab489c315c28b61d7a1d03cf938cc99cc58092eb065f120c3a5",
"sha256:696c2a381fc7876b3056711717dba5eddd07c2c9e5ccd50da54029a1293b6e43",
"sha256:6ba4b3638d014918b918aa90a9c8370bd74a03abf8fcf9deb353b3a461a59a84",
"sha256:7039e624bcb820f77cc2ff3d1adcce531932990eee16121077eb51d9c76b6c14",
"sha256:88d108d004e0df25224de77ce349a7e73494ea2cb194031f7c9687e68a88ec9b",
"sha256:8c1dff87b30fd150c61367d0e2cdc49bb55f8b9fd2a303560bbc24b951573ae1",
"sha256:9a8195b99e650e6f329ce4e5eb22d448bdfef0406404080812bc96e2a05674cb",
"sha256:af0b33f04677b57843d529b9257a475d2865403300b48c67654c40abac2f9f24",
"sha256:b419f2144e1762ab845f20316f1df36b15431f2622ebae8a6d5f7e8e712b413c",
"sha256:b59deb0ddc7b431e41d720c00f99d68b52cb9bd1d5605a085dc18f502fe9c47f",
"sha256:bc0615351221926a36a0fbcb2520fb52e0b23e8c22a43754d9cb8f21358c33c0",
"sha256:c203d82069ba31e1f3bc7ba530b2461ec86366cd4bfc9b95ec6ce58b1b559c34",
"sha256:ce6cbb852fb8f2f9bb7b9cdca44e2e37bce783b5f4c167ff82cb5f5128163c8f",
"sha256:d33cb526efdc235a2531433fc1287fcb80d807d5b401f9b801b78bf22df560dd",
"sha256:da0cef4d7e3f19c3bd1d71658d6900321af0492fee36ec01b550a10924cffb9c",
"sha256:da21e7eec49252df34d426c2ee9cf0361c923026d37c24728b0fa4cc0599fd03",
"sha256:ea8d51e5eb29e57d34744369cd08267637aa5a0fefc9b5d33775ab7ff2ebf2e3",
"sha256:ec4e87e6fdc511a535254daa122c20e11959ce043b4e3425494b237692a34f1c",
"sha256:f0f5fda7cbf890371a59ab1d06512da4f2c89a6ea194e595808123c863c38eff",
"sha256:f32ca483e6ade23c7caaee9d5ee5d550cf4146e9b68d2fb6c68bac183aa41c37",
"sha256:f749ca804648d00eda62fe1098f229b082dfca930d8bad8386e572a6eafa7525",
"sha256:f89a420cf5a6f2aa7849dd59e1ff0e477f562d97cf8d6a1ee03461e1eec39887"
"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"
],
"markers": "python_version >= '3.8'",
"version": "==7.0.1"
"version": "==7.0.3"
}
},
"develop": {
+67 -83
View File
@@ -1,11 +1,11 @@
import asyncio
import json
import logging
from contextlib import asynccontextmanager
from io import BytesIO
from typing import Literal
import aiohttp
import redis.asyncio as redis
from apscheduler.schedulers.asyncio import AsyncIOScheduler
from fastapi import (
Depends,
@@ -34,15 +34,56 @@ from utils.lock import (
)
from utils.network import get_request_namespace, get_user_public_ip, get_user_data
from utils.parser import generate_manifest
from utils.runtime_const import DELETE_ALL_META, DELETE_ALL_META_ITEM, TEMPLATES
from utils.runtime_const import (
DELETE_ALL_META,
DELETE_ALL_META_ITEM,
TEMPLATES,
REDIS_ASYNC_CLIENT,
)
logging.basicConfig(
format="%(levelname)s::%(asctime)s - %(message)s",
datefmt="%d-%b-%y %H:%M:%S",
level=settings.logging_level,
)
app = FastAPI()
@asynccontextmanager
async def lifespan(fastapi_app: FastAPI):
# Startup logic
await database.init()
await torrent.init_best_trackers()
if not settings.disable_all_scheduler:
acquired, lock = await acquire_scheduler_lock()
if acquired:
try:
scheduler = AsyncIOScheduler()
setup_scheduler(scheduler)
scheduler.start()
fastapi_app.state.scheduler = scheduler
fastapi_app.state.scheduler_lock = lock
await asyncio.create_task(maintain_heartbeat())
except Exception as e:
await release_scheduler_lock(lock)
raise e
yield
# Shutdown logic
if hasattr(fastapi_app.state, "scheduler"):
fastapi_app.state.scheduler.shutdown(wait=False)
if (
hasattr(fastapi_app.state, "scheduler_lock")
and fastapi_app.state.scheduler_lock
):
await release_scheduler_lock(fastapi_app.state.scheduler_lock)
await REDIS_ASYNC_CLIENT.aclose()
app = FastAPI(lifespan=lifespan)
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
@@ -62,10 +103,7 @@ async def add_cors_header(request: Request, call_next):
return response
app.state.redis = redis.Redis(
connection_pool=redis.ConnectionPool.from_url(settings.redis_url)
)
app.add_middleware(middleware.RateLimitMiddleware, redis_client=app.state.redis)
app.add_middleware(middleware.RateLimitMiddleware)
app.add_middleware(middleware.TimingMiddleware)
app.add_middleware(middleware.SecureLoggingMiddleware)
app.add_middleware(middleware.UserDataMiddleware)
@@ -73,46 +111,6 @@ app.add_middleware(middleware.UserDataMiddleware)
app.mount("/static", StaticFiles(directory="resources"), name="static")
@app.on_event("startup")
async def init_server():
await database.init()
await torrent.init_best_trackers()
@app.on_event("startup")
async def start_scheduler():
if settings.disable_all_scheduler:
logging.info("All Schedulers are disabled. Not setting up any jobs.")
return
acquired, lock = await acquire_scheduler_lock(app.state.redis)
if acquired:
try:
scheduler = AsyncIOScheduler()
setup_scheduler(scheduler)
scheduler.start()
app.state.scheduler = scheduler
app.state.scheduler_lock = lock
await asyncio.create_task(maintain_heartbeat(app.state.redis))
except Exception as e:
await release_scheduler_lock(app.state.redis, lock)
raise e
@app.on_event("shutdown")
async def stop_scheduler():
if hasattr(app.state, "scheduler"):
app.state.scheduler.shutdown(wait=False)
if hasattr(app.state, "scheduler_lock") and app.state.scheduler_lock:
await release_scheduler_lock(app.state.redis, app.state.scheduler_lock)
@app.on_event("shutdown")
async def shutdown_event():
await app.state.redis.aclose()
@app.get("/", tags=["home"])
async def get_home(request: Request):
manifest = get_json_data("resources/manifest.json")
@@ -130,7 +128,7 @@ async def get_home(request: Request):
@app.get("/health", tags=["health"])
@wrappers.exclude_rate_limit
async def health(request: Request):
async def health():
return {"status": "healthy"}
@@ -156,9 +154,11 @@ async def configure(
# Prepare catalogs based on user preferences or default order
sorted_catalogs = sorted(
const.CATALOG_DATA.items(),
key=lambda x: user_data.selected_catalogs.index(x[0])
if x[0] in user_data.selected_catalogs
else len(user_data.selected_catalogs),
key=lambda x: (
user_data.selected_catalogs.index(x[0])
if x[0] in user_data.selected_catalogs
else len(user_data.selected_catalogs)
),
)
sorted_sorting_options = user_data.torrent_sorting_priority + [
@@ -197,13 +197,12 @@ async def configure(
@wrappers.auth_required
async def get_manifest(
response: Response,
request: Request,
user_data: schemas.UserData = Depends(get_user_data),
):
response.headers.update(const.NO_CACHE_HEADERS)
manifest = get_json_data("resources/manifest.json")
return await generate_manifest(manifest, user_data, request.app.state.redis)
return await generate_manifest(manifest, user_data)
@app.get(
@@ -282,7 +281,7 @@ async def get_catalog(
# Try retrieving the cached data
if cache_key:
if cached_data := await request.app.state.redis.get(cache_key):
if cached_data := await REDIS_ASYNC_CLIENT.get(cache_key):
return json.loads(cached_data)
metas = schemas.Metas()
@@ -293,9 +292,7 @@ async def get_catalog(
)
)
elif catalog_type == "events":
metas.metas.extend(
await crud.get_events_meta_list(request.app.state.redis, genre, skip)
)
metas.metas.extend(await crud.get_events_meta_list(genre, skip))
else:
user_ip = await get_user_public_ip(request, user_data)
metas.metas.extend(
@@ -324,7 +321,7 @@ async def get_catalog(
metas.metas.insert(0, delete_all_meta)
if cache_key:
await request.app.state.redis.set(
await REDIS_ASYNC_CLIENT.set(
cache_key,
metas.model_dump_json(exclude_none=True, by_alias=True),
ex=settings.meta_cache_ttl,
@@ -387,7 +384,6 @@ async def search_meta(
async def get_meta(
catalog_type: Literal["movie", "series", "tv", "events"],
meta_id: str,
request: Request,
user_data: schemas.UserData = Depends(get_user_data),
):
cache_key = f"{catalog_type}_{meta_id}_meta"
@@ -398,7 +394,7 @@ async def get_meta(
)
# Try retrieving the cached data
cached_data = await request.app.state.redis.get(cache_key)
cached_data = await REDIS_ASYNC_CLIENT.get(cache_key)
if cached_data:
meta_data = json.loads(cached_data)
if not meta_data:
@@ -411,19 +407,17 @@ async def get_meta(
delete_all_meta_item["meta"]["_id"] = meta_id
data = delete_all_meta_item
else:
data = await crud.get_movie_meta(
meta_id, request.app.state.redis, user_data
)
data = await crud.get_movie_meta(meta_id, user_data)
elif catalog_type == "series":
data = await crud.get_series_meta(meta_id, user_data)
elif catalog_type == "events":
data = await crud.get_event_meta(request.app.state.redis, meta_id)
data = await crud.get_event_meta(meta_id)
else:
data = await crud.get_tv_meta(meta_id)
# Cache the data with a TTL of 30 minutes
# If the data is not found, cached the empty data to avoid db query.
await request.app.state.redis.set(cache_key, json.dumps(data, default=str), ex=1800)
await REDIS_ASYNC_CLIENT.set(cache_key, json.dumps(data, default=str), ex=1800)
if not data:
raise HTTPException(status_code=404, detail="Meta ID not found.")
@@ -507,14 +501,13 @@ async def get_streams(
raise HTTPException(status_code=404, detail="Meta ID not found.")
else:
fetched_streams = await crud.get_movie_streams(
user_data, secret_str, request.app.state.redis, video_id, user_ip
user_data, secret_str, video_id, user_ip
)
fetched_streams.extend(user_feeds)
elif catalog_type == "series":
fetched_streams = await crud.get_series_streams(
user_data,
secret_str,
request.app.state.redis,
video_id,
season,
episode,
@@ -522,14 +515,12 @@ async def get_streams(
)
fetched_streams.extend(user_feeds)
elif catalog_type == "events":
fetched_streams = await crud.get_event_streams(
request.app.state.redis, video_id, user_data
)
fetched_streams = await crud.get_event_streams(video_id, user_data)
response.headers.update(const.NO_CACHE_HEADERS)
else:
response.headers.update(const.NO_CACHE_HEADERS)
fetched_streams = await crud.get_tv_streams(
request.app.state.redis, video_id, get_request_namespace(request), user_data
video_id, get_request_namespace(request), user_data
)
return {"streams": fetched_streams}
@@ -547,27 +538,22 @@ async def encrypt_user_data(user_data: schemas.UserData):
async def get_poster(
catalog_type: Literal["movie", "series", "tv", "events"],
mediafusion_id: str,
request: Request,
):
cache_key = f"{catalog_type}_{mediafusion_id}.jpg"
# Check if the poster is cached in Redis
cached_image = await request.app.state.redis.get(cache_key)
cached_image = await REDIS_ASYNC_CLIENT.get(cache_key)
if cached_image:
image_byte_io = BytesIO(cached_image)
return StreamingResponse(image_byte_io, media_type="image/jpeg")
# Query the MediaFusion data
if catalog_type == "movie":
mediafusion_data = await crud.get_movie_data_by_id(
mediafusion_id, request.app.state.redis
)
mediafusion_data = await crud.get_movie_data_by_id(mediafusion_id)
elif catalog_type == "series":
mediafusion_data = await crud.get_series_data_by_id(mediafusion_id)
elif catalog_type == "events":
mediafusion_data = await crud.get_event_data_by_id(
request.app.state.redis, mediafusion_id
)
mediafusion_data = await crud.get_event_data_by_id(mediafusion_id)
else:
mediafusion_data = await crud.get_tv_data_by_id(mediafusion_id)
@@ -578,13 +564,11 @@ async def get_poster(
raise HTTPException(status_code=404, detail="Poster not found.")
try:
image_byte_io = await poster.create_poster(
mediafusion_data, request.app.state.redis
)
image_byte_io = await poster.create_poster(mediafusion_data)
# Convert BytesIO to bytes for Redis
image_bytes = image_byte_io.getvalue()
# Save the generated image to Redis. expire in 7 days
await request.app.state.redis.set(cache_key, image_bytes, ex=604800)
await REDIS_ASYNC_CLIENT.set(cache_key, image_bytes, ex=604800)
image_byte_io.seek(0)
return StreamingResponse(image_byte_io, media_type="image/jpeg")
+8 -13
View File
@@ -8,12 +8,10 @@ from threading import Lock
from typing import Callable, Optional
import dramatiq
import redis
from apscheduler.triggers.cron import CronTrigger
from dramatiq.middleware import Retries as OriginalRetries, Shutdown, SkipMessage
from fastapi.requests import Request
from fastapi.responses import Response
from redis.asyncio import Redis
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.routing import Match
@@ -21,6 +19,7 @@ from db.config import settings
from db.schemas import UserData
from utils import crypto, const
from utils.network import get_client_ip
from utils.runtime_const import REDIS_ASYNC_CLIENT, REDIS_SYNC_CLIENT
async def find_route_handler(app, request: Request) -> Optional[Callable]:
@@ -81,9 +80,8 @@ class UserDataMiddleware(BaseHTTPMiddleware):
class RateLimitMiddleware(BaseHTTPMiddleware):
def __init__(self, app, redis_client: Redis):
def __init__(self, app):
super().__init__(app)
self.redis = redis_client
async def dispatch(self, request: Request, call_next: Callable):
# Skip rate limiting for exempt paths
@@ -133,12 +131,11 @@ class RateLimitMiddleware(BaseHTTPMiddleware):
raw_identifier += f"-{provider_profile}"
return hashlib.md5(raw_identifier.encode()).hexdigest()
async def check_rate_limit_with_redis(
self, key: str, limit: int, window: int
) -> bool:
@staticmethod
async def check_rate_limit_with_redis(key: str, limit: int, window: int) -> bool:
try:
results = await (
self.redis.pipeline(transaction=True)
REDIS_ASYNC_CLIENT.pipeline(transaction=True)
.incr(key)
.expire(key, window)
.execute()
@@ -190,8 +187,6 @@ class Retries(OriginalRetries):
class TaskManager(dramatiq.Middleware):
def __init__(self):
self.redis: redis.Redis = redis.Redis.from_url(settings.redis_url)
@staticmethod
def calculate_interval_from_crontab(crontab_expression: str) -> timedelta:
@@ -247,7 +242,7 @@ class TaskManager(dramatiq.Middleware):
# Subtract 10 seconds to account for processing time
min_interval = min_interval - timedelta(seconds=10)
last_run = self.redis.get(task_key)
last_run = REDIS_SYNC_CLIENT.get(task_key)
if last_run is not None:
last_run = datetime.fromtimestamp(float(last_run))
difference = datetime.now() - last_run
@@ -259,7 +254,7 @@ class TaskManager(dramatiq.Middleware):
# Set the cache expiry for the task
ex_time = int(min_interval.total_seconds()) if set_cache_expiry else None
self.redis.set(
REDIS_SYNC_CLIENT.set(
task_key,
datetime.now().timestamp(),
ex=ex_time,
@@ -276,7 +271,7 @@ class TaskManager(dramatiq.Middleware):
task_name, min_interval, set_cache_expiry, task_key = task_data
# Update the cache with the latest run time
self.redis.set(
REDIS_SYNC_CLIENT.set(
task_key,
datetime.now().timestamp(),
ex=int(min_interval.total_seconds()) if set_cache_expiry else None,
+46 -54
View File
@@ -10,7 +10,6 @@ from apscheduler.triggers.cron import CronTrigger
from beanie.exceptions import RevisionIdWasChanged
from beanie.operators import Set
from pymongo.errors import DuplicateKeyError
from redis.asyncio import Redis
from db import schemas
from db.config import settings
@@ -36,6 +35,7 @@ from utils.parser import (
parse_stream_data,
parse_tv_stream_data,
)
from utils.runtime_const import REDIS_ASYNC_CLIENT
from utils.validation_helper import (
validate_parent_guide_nudity,
get_filter_certification_values,
@@ -152,11 +152,9 @@ async def get_tv_meta_list(
return tv_meta_list
async def get_movie_data_by_id(
movie_id: str, redis: Redis
) -> Optional[MediaFusionMovieMetaData]:
async def get_movie_data_by_id(movie_id: str) -> Optional[MediaFusionMovieMetaData]:
# Check if the movie data is already in the cache
cached_data = await redis.get(f"movie_data:{movie_id}")
cached_data = await REDIS_ASYNC_CLIENT.get(f"movie_data:{movie_id}")
if cached_data:
return MediaFusionMovieMetaData.model_validate_json(cached_data)
@@ -194,7 +192,7 @@ async def get_movie_data_by_id(
# Serialize the data and store it in the Redis cache for 1 day
if movie_data:
await redis.set(
await REDIS_ASYNC_CLIENT.set(
f"movie_data:{movie_id}",
movie_data.model_dump_json(exclude_none=True),
ex=86400,
@@ -251,7 +249,6 @@ async def get_tv_data_by_id(
async def get_cached_torrent_streams(
redis: Redis,
video_id: str,
season: Optional[int] = None,
episode: Optional[int] = None,
@@ -260,7 +257,7 @@ async def get_cached_torrent_streams(
cache_key = f"torrent_streams:{video_id}:{season}:{episode}"
# Try to get the data from the Redis cache
cached_data = await redis.get(cache_key)
cached_data = await REDIS_ASYNC_CLIENT.get(cache_key)
if cached_data is not None:
# If the data is in the cache, deserialize it and return it
@@ -289,7 +286,7 @@ async def get_cached_torrent_streams(
torrent_streams = TorrentStreamsList(streams=streams)
# Serialize the data and store it in the Redis cache for 30 minutes
await redis.set(
await REDIS_ASYNC_CLIENT.set(
cache_key, torrent_streams.model_dump_json(exclude_none=True), ex=1800
)
@@ -297,7 +294,7 @@ async def get_cached_torrent_streams(
async def get_movie_streams(
user_data, secret_str: str, redis: Redis, video_id: str, user_ip: str | None = None
user_data, secret_str: str, video_id: str, user_ip: str | None = None
) -> list[Stream]:
if video_id.startswith("dl"):
if not video_id.endswith(user_data.streaming_provider.service):
@@ -309,11 +306,11 @@ async def get_movie_streams(
url=f"{settings.host_url}/streaming_provider/{secret_str}/delete_all",
)
]
movie_metadata = await get_movie_data_by_id(video_id, redis)
movie_metadata = await get_movie_data_by_id(video_id)
if not (movie_metadata and validate_parent_guide_nudity(movie_metadata, user_data)):
return []
streams = await get_cached_torrent_streams(redis, video_id)
streams = await get_cached_torrent_streams(video_id)
if video_id.startswith("tt"):
if (
@@ -321,7 +318,6 @@ async def get_movie_streams(
and "torrentio_streams" in user_data.selected_catalogs
):
streams = await get_streams_from_torrentio(
redis,
streams,
video_id,
catalog_type="movie",
@@ -333,7 +329,6 @@ async def get_movie_streams(
and "prowlarr_streams" in user_data.selected_catalogs
):
streams = await get_streams_from_prowlarr(
redis,
streams,
video_id,
"movie",
@@ -346,7 +341,6 @@ async def get_movie_streams(
and "zilean_dmm_streams" in user_data.selected_catalogs
):
streams = await get_streams_from_zilean(
redis,
streams,
video_id,
catalog_type="movie",
@@ -360,7 +354,6 @@ async def get_movie_streams(
async def get_series_streams(
user_data,
secret_str: str,
redis: Redis,
video_id: str,
season: int,
episode: int,
@@ -372,7 +365,7 @@ async def get_series_streams(
):
return []
streams = await get_cached_torrent_streams(redis, video_id, season, episode)
streams = await get_cached_torrent_streams(video_id, season, episode)
if video_id.startswith("tt"):
if (
@@ -380,7 +373,6 @@ async def get_series_streams(
and "torrentio_streams" in user_data.selected_catalogs
):
streams = await get_streams_from_torrentio(
redis,
streams,
video_id,
catalog_type="series",
@@ -395,7 +387,6 @@ async def get_series_streams(
and "prowlarr_streams" in user_data.selected_catalogs
):
streams = await get_streams_from_prowlarr(
redis,
streams,
video_id,
"series",
@@ -410,7 +401,6 @@ async def get_series_streams(
and "zilean_dmm_streams" in user_data.selected_catalogs
):
streams = await get_streams_from_zilean(
redis,
streams,
video_id,
catalog_type="series",
@@ -425,9 +415,7 @@ async def get_series_streams(
)
async def get_tv_streams(
redis: Redis, video_id: str, namespace: str, user_data
) -> list[Stream]:
async def get_tv_streams(video_id: str, namespace: str, user_data) -> list[Stream]:
tv_streams = await TVStreams.find(
{
"meta_id": video_id,
@@ -436,11 +424,11 @@ async def get_tv_streams(
},
).to_list()
return await parse_tv_stream_data(tv_streams, redis, user_data)
return await parse_tv_stream_data(tv_streams, user_data)
async def get_movie_meta(meta_id: str, redis: Redis, user_data: schemas.UserData):
movie_data = await get_movie_data_by_id(meta_id, redis)
async def get_movie_meta(meta_id: str, user_data: schemas.UserData):
movie_data = await get_movie_data_by_id(meta_id)
if not (movie_data and validate_parent_guide_nudity(movie_data, user_data)):
return {}
@@ -940,9 +928,11 @@ async def save_tv_channel_metadata(tv_metadata: schemas.TVMetaData) -> str:
stream_doc = TVStreams(
url=stream.url,
name=stream.name,
behaviorHints=stream.behaviorHints.model_dump(exclude_none=True)
if stream.behaviorHints
else None,
behaviorHints=(
stream.behaviorHints.model_dump(exclude_none=True)
if stream.behaviorHints
else None
),
ytId=stream.ytId,
source=stream.source,
country=stream.country,
@@ -982,13 +972,13 @@ async def save_tv_channel_metadata(tv_metadata: schemas.TVMetaData) -> str:
return channel_id
async def save_events_data(redis: Redis, metadata: dict) -> str:
async def save_events_data(metadata: dict) -> str:
# Generate a unique event key
meta_id = "mf" + crypto.get_text_hash(metadata["title"])
event_key = f"event:{meta_id}"
# Attempt to fetch existing event data
existing_event_json = await redis.get(event_key)
existing_event_json = await REDIS_ASYNC_CLIENT.get(event_key)
if existing_event_json:
# Deserialize the existing event data
@@ -1036,37 +1026,39 @@ async def save_events_data(redis: Redis, metadata: dict) -> str:
# Set or update the event data in Redis with an appropriate TTL
cache_ttl = 86400 if events_data.event_start_timestamp == 0 else 3600
await redis.set(event_key, events_json, ex=cache_ttl)
await REDIS_ASYNC_CLIENT.set(event_key, events_json, ex=cache_ttl)
logging.info(
f"{'Updating' if existing_event_json else 'Inserting'} event data for {events_data.title} with event key {event_key}"
)
# Add the event key to a set of all events
await redis.zadd("events:all", {event_key: event_start_timestamp})
await REDIS_ASYNC_CLIENT.zadd("events:all", {event_key: event_start_timestamp})
# Index the event by genre
for genre in events_data.genres:
await redis.zadd(f"events:genre:{genre}", {event_key: event_start_timestamp})
await REDIS_ASYNC_CLIENT.zadd(
f"events:genre:{genre}", {event_key: event_start_timestamp}
)
return event_key
async def get_events_meta_list(
redis, genre=None, skip=0, limit=25
) -> list[schemas.Meta]:
async def get_events_meta_list(genre=None, skip=0, limit=25) -> list[schemas.Meta]:
if genre:
key_pattern = f"events:genre:{genre}"
else:
key_pattern = "events:all"
# Fetch event keys sorted by timestamp in descending order
events_keys = await redis.zrevrange(key_pattern, skip, skip + limit - 1)
events_keys = await REDIS_ASYNC_CLIENT.zrevrange(
key_pattern, skip, skip + limit - 1
)
events = []
# Iterate over event keys, fetching and decoding JSON data
for key in events_keys:
events_json = await redis.get(key)
events_json = await REDIS_ASYNC_CLIENT.get(key)
if events_json:
meta_data = schemas.Meta.model_validate_json(events_json)
meta_data.poster = (
@@ -1075,14 +1067,14 @@ async def get_events_meta_list(
events.append(meta_data)
else:
# Cleanup: Remove expired or missing event key from the index
await redis.zrem(key_pattern, key)
await REDIS_ASYNC_CLIENT.zrem(key_pattern, key)
return events
async def get_event_meta(redis, meta_id: str) -> dict:
async def get_event_meta(meta_id: str) -> dict:
events_key = f"event:{meta_id}"
events_json = await redis.get(events_key)
events_json = await REDIS_ASYNC_CLIENT.get(events_key)
if not events_json:
return {}
@@ -1095,26 +1087,26 @@ async def get_event_meta(redis, meta_id: str) -> dict:
}
async def get_event_data_by_id(redis, meta_id: str) -> MediaFusionEventsMetaData | None:
async def get_event_data_by_id(meta_id: str) -> MediaFusionEventsMetaData | None:
event_key = f"event:{meta_id}"
events_json = await redis.get(event_key)
events_json = await REDIS_ASYNC_CLIENT.get(event_key)
if not events_json:
return None
return MediaFusionEventsMetaData.model_validate_json(events_json)
async def get_event_streams(redis, meta_id: str, user_data) -> list[Stream]:
async def get_event_streams(meta_id: str, user_data) -> list[Stream]:
event_key = f"event:{meta_id}"
event_json = await redis.get(event_key)
event_json = await REDIS_ASYNC_CLIENT.get(event_key)
if not event_json:
return await parse_tv_stream_data([], redis, user_data)
return await parse_tv_stream_data([], user_data)
event_data = MediaFusionEventsMetaData.model_validate_json(event_json)
return await parse_tv_stream_data(event_data.streams, redis, user_data)
return await parse_tv_stream_data(event_data.streams, user_data)
async def get_genres(catalog_type: str, redis: Redis) -> list[str]:
async def get_genres(catalog_type: str) -> list[str]:
if catalog_type == "movie":
meta_class = MediaFusionMovieMetaData
elif catalog_type == "tv":
@@ -1122,22 +1114,22 @@ async def get_genres(catalog_type: str, redis: Redis) -> list[str]:
else:
meta_class = MediaFusionSeriesMetaData
genres = await redis.get(f"{catalog_type}_genres")
genres = await REDIS_ASYNC_CLIENT.get(f"{catalog_type}_genres")
if genres:
return json.loads(genres)
genres = await meta_class.distinct("genres", {"genres": {"$ne": ""}})
# cache the genres for 30 minutes
await redis.set(f"{catalog_type}_genres", json.dumps(genres), ex=1800)
await REDIS_ASYNC_CLIENT.set(f"{catalog_type}_genres", json.dumps(genres), ex=1800)
return genres
async def fetch_last_run(redis: Redis, spider_id: str, spider_name: str):
async def fetch_last_run(spider_id: str, spider_name: str):
task_key = f"background_tasks:run_spider:spider_name={spider_id}"
state_key = f"scrapy_stats:{spider_id}"
last_run_timestamp = await redis.get(task_key)
last_run_state = await redis.get(state_key)
last_run_timestamp = await REDIS_ASYNC_CLIENT.get(task_key)
last_run_state = await REDIS_ASYNC_CLIENT.get(state_key)
if settings.disable_all_scheduler:
next_schedule_in = None
+2 -2
View File
@@ -1,4 +1,4 @@
FROM python:3.11.9-slim-bullseye AS builder
FROM python:3.12-slim-bookworm AS builder
WORKDIR /mediafusion
@@ -24,7 +24,7 @@ COPY --chown=mediafusion:mediafusion Pipfile Pipfile.lock ./
RUN pip install --user pipenv && \
pipenv install --deploy --ignore-pipfile
FROM python:3.11.9-slim-bullseye
FROM python:3.12-slim-bookworm
WORKDIR /mediafusion
+2 -3
View File
@@ -1,18 +1,17 @@
import json
import logging
import redis
from scrapy import signals
from scrapy.statscollectors import StatsCollector
from db.config import settings
from utils.runtime_const import REDIS_SYNC_CLIENT
class RedisStatsCollector(StatsCollector):
def __init__(self, crawler):
super().__init__(crawler)
crawler.signals.connect(self.spider_closed, signal=signals.spider_closed)
self.redis_client = redis.Redis.from_url(settings.redis_url)
self.redis_client = REDIS_SYNC_CLIENT
def spider_closed(self, spider, reason):
# Access the stats dictionary
@@ -1,13 +1,12 @@
import redis.asyncio as redis_async
from scrapy import signals
from scrapy.exceptions import DropItem
from db.config import settings
from utils.runtime_const import REDIS_ASYNC_CLIENT
class RedisCacheURLPipeline:
def __init__(self):
self.redis = redis_async.Redis.from_url(settings.redis_url)
self.redis = REDIS_ASYNC_CLIENT
async def close(self):
await self.redis.aclose()
@@ -2,22 +2,18 @@ import asyncio
import logging
from uuid import uuid4
import logging
from uuid import uuid4
import redis.asyncio as redis_async
from beanie import WriteRules
from scrapy import signals
from scrapy.exceptions import DropItem
from db import crud
from db.config import settings
from db.models import (
TorrentStreams,
Season,
MediaFusionSeriesMetaData,
)
from db.schemas import TVMetaData
from utils.runtime_const import REDIS_ASYNC_CLIENT
class QueueBasedPipeline:
@@ -62,7 +58,7 @@ class QueueBasedPipeline:
class EventSeriesStorePipeline(QueueBasedPipeline):
def __init__(self):
super().__init__()
self.redis = redis_async.Redis.from_url(settings.redis_url)
self.redis = REDIS_ASYNC_CLIENT
async def close(self):
await super().close()
@@ -201,7 +197,7 @@ class SeriesStorePipeline(QueueBasedPipeline):
class LiveEventStorePipeline(QueueBasedPipeline):
def __init__(self):
super().__init__()
self.redis = redis_async.Redis.from_url(settings.redis_url)
self.redis = REDIS_ASYNC_CLIENT
async def close(self):
await super().close()
@@ -211,5 +207,5 @@ class LiveEventStorePipeline(QueueBasedPipeline):
if "title" not in item:
raise DropItem(f"name not found in item: {item}")
await crud.save_events_data(self.redis, item)
await crud.save_events_data(item)
return item
+6 -8
View File
@@ -1,10 +1,10 @@
import logging
import re
import redis
import scrapy
from db.config import settings
from scrapers.helpers import get_scraper_config
from utils.runtime_const import REDIS_SYNC_CLIENT
class CommonTamilSpider(scrapy.Spider):
@@ -43,9 +43,7 @@ class CommonTamilSpider(scrapy.Spider):
return
self.scrap_catalog_id = scrap_catalog_id
logging.info(f"Scraping catalog ID: {self.scrap_catalog_id}")
self.redis = redis.Redis(
connection_pool=redis.ConnectionPool.from_url(settings.redis_url)
)
self.redis = REDIS_SYNC_CLIENT
self.scraped_urls_key = f"{self.name}_scraped_urls"
self.catalogs = get_scraper_config(self.name, "catalogs")
self.homepage = get_scraper_config(self.name, "homepage")
@@ -194,9 +192,9 @@ class CommonTamilSpider(scrapy.Spider):
torrent_item = item.copy()
torrent_item.update(
{
"type": "series"
if torrent_item["video_type"] == "series"
else "movie",
"type": (
"series" if torrent_item["video_type"] == "series" else "movie"
),
"poster": poster,
"created_at": created_at,
"language": torrent_item["language"].title(),
+2 -6
View File
@@ -1,11 +1,9 @@
import random
import re
import redis
import scrapy
from db.config import settings
from utils.runtime_const import SPORTS_ARTIFACTS
from utils.runtime_const import SPORTS_ARTIFACTS, REDIS_SYNC_CLIENT
class CricTimeSpider(scrapy.Spider):
@@ -22,9 +20,7 @@ class CricTimeSpider(scrapy.Spider):
def __init__(self, *args, **kwargs):
super(CricTimeSpider, self).__init__(*args, **kwargs)
self.redis = redis.Redis(
connection_pool=redis.ConnectionPool.from_url(settings.redis_url)
)
self.redis = REDIS_SYNC_CLIENT
def __del__(self):
self.redis.close()
@@ -3,11 +3,9 @@ import re
from datetime import datetime
import pytz
import redis
import scrapy
from db.config import settings
from utils.runtime_const import SPORTS_ARTIFACTS
from utils.runtime_const import SPORTS_ARTIFACTS, REDIS_SYNC_CLIENT
class MrGamingStreamsSpider(scrapy.Spider):
@@ -35,9 +33,7 @@ class MrGamingStreamsSpider(scrapy.Spider):
def __init__(self, *args, **kwargs):
super(MrGamingStreamsSpider, self).__init__(*args, **kwargs)
self.redis = redis.Redis(
connection_pool=redis.ConnectionPool.from_url(settings.redis_url)
)
self.redis = REDIS_SYNC_CLIENT
def __del__(self):
self.redis.close()
+2 -5
View File
@@ -1,9 +1,8 @@
from urllib.parse import urlparse
import redis
import scrapy
from db.config import settings
from utils.runtime_const import REDIS_SYNC_CLIENT
class SportVideoSpider(scrapy.Spider):
@@ -31,9 +30,7 @@ class SportVideoSpider(scrapy.Spider):
def __init__(self, scrape_all: str = "True", *args, **kwargs):
super(SportVideoSpider, self).__init__(*args, **kwargs)
self.scrape_all = scrape_all.lower() == "true"
self.redis = redis.Redis(
connection_pool=redis.ConnectionPool.from_url(settings.redis_url)
)
self.redis = REDIS_SYNC_CLIENT
self.scraped_urls_key = "sport_video_scraped_urls"
def __del__(self):
+2 -6
View File
@@ -1,11 +1,9 @@
import random
import re
import redis
import scrapy
from db.config import settings
from utils.runtime_const import SPORTS_ARTIFACTS
from utils.runtime_const import SPORTS_ARTIFACTS, REDIS_SYNC_CLIENT
class StreamBTWSpider(scrapy.Spider):
@@ -32,9 +30,7 @@ class StreamBTWSpider(scrapy.Spider):
def __init__(self, *args, **kwargs):
super(StreamBTWSpider, self).__init__(*args, **kwargs)
self.redis = redis.Redis(
connection_pool=redis.ConnectionPool.from_url(settings.redis_url)
)
self.redis = REDIS_SYNC_CLIENT
def __del__(self):
self.redis.close()
+2 -5
View File
@@ -2,14 +2,13 @@ import random
import re
from datetime import datetime
import redis.asyncio as redis
import scrapy
from scrapy_playwright.page import PageMethod
from db.config import settings
from db.models import TorrentStreams
from utils.parser import convert_size_to_bytes
from utils.runtime_const import SPORTS_ARTIFACTS
from utils.runtime_const import SPORTS_ARTIFACTS, REDIS_ASYNC_CLIENT
from utils.torrent import parse_magnet
@@ -27,9 +26,7 @@ class TgxSpider(scrapy.Spider):
def __init__(self, scrape_all: str = "True", *args, **kwargs):
super(TgxSpider, self).__init__(*args, **kwargs)
self.scrape_all = scrape_all.lower() == "true"
self.redis = redis.Redis(
connection_pool=redis.ConnectionPool.from_url(settings.redis_url)
)
self.redis = REDIS_ASYNC_CLIENT
async def __aexit__(self, exc_type, exc_val, exc_tb):
await self.redis.aclose()
+2 -2
View File
@@ -95,10 +95,10 @@ async def get_total_metadata(response: Response):
@metrics_router.get("/scrapy-schedulers", tags=["metrics"])
async def get_schedulers_last_run(request: Request, response: Response):
async def get_schedulers_last_run(response: Response):
response.headers.update(const.NO_CACHE_HEADERS)
tasks = [
fetch_last_run(request.app.state.redis, spider_id, spider_name)
fetch_last_run(spider_id, spider_name)
for spider_id, spider_name in const.SCRAPY_SPIDERS.items()
]
results = await asyncio.gather(*tasks)
+3 -4
View File
@@ -7,7 +7,6 @@ import PTT
import dramatiq
import httpx
from pymongo.errors import DuplicateKeyError
from redis.asyncio import Redis
from torf import Magnet, MagnetError
from db.config import settings
@@ -20,12 +19,12 @@ from scrapers.helpers import (
from utils.const import UA_HEADER
from utils.network import CircuitBreaker, batch_process_with_circuit_breaker
from utils.parser import is_contain_18_plus_keywords, calculate_max_similarity_ratio
from utils.runtime_const import REDIS_ASYNC_CLIENT
from utils.torrent import extract_torrent_metadata
from utils.wrappers import minimum_run_interval
async def get_streams_from_prowlarr(
redis: Redis,
streams: list[TorrentStreams],
video_id: str,
catalog_type: str,
@@ -36,7 +35,7 @@ async def get_streams_from_prowlarr(
episode: int = None,
):
cache_key = f"{catalog_type}_{video_id}_{year}_{season}_{episode}_prowlarr_streams"
cached_data = await redis.get(cache_key)
cached_data = await REDIS_ASYNC_CLIENT.get(cache_key)
if cached_data:
return streams
@@ -101,7 +100,7 @@ async def get_streams_from_prowlarr(
season=season,
episode=episode,
)
await redis.set(
await REDIS_ASYNC_CLIENT.set(
cache_key,
"True",
ex=int(timedelta(hours=settings.prowlarr_search_interval_hour).total_seconds()),
+4 -8
View File
@@ -23,7 +23,7 @@ from scrapers.tv import add_tv_metadata, parse_m3u_playlist
from utils import const, torrent
from utils.network import get_request_namespace
from utils.parser import calculate_max_similarity_ratio
from utils.runtime_const import TEMPLATES
from utils.runtime_const import TEMPLATES, REDIS_ASYNC_CLIENT
router = APIRouter()
@@ -110,12 +110,11 @@ async def upload_m3u_playlist(
if m3u_playlist_file:
content = await m3u_playlist_file.read()
redis_key = f"m3u_playlist_{uuid4().hex[:10]}"
await request.app.state.redis.set(redis_key, content)
await REDIS_ASYNC_CLIENT.set(redis_key, content)
background_tasks.add_task(
parse_m3u_playlist,
namespace=get_request_namespace(request),
playlist_source=m3u_playlist_source,
redis_client=request.app.state.redis,
playlist_redis_key=redis_key,
playlist_url=None,
)
@@ -126,7 +125,6 @@ async def upload_m3u_playlist(
parse_m3u_playlist,
namespace=get_request_namespace(request),
playlist_source=m3u_playlist_source,
redis_client=request.app.state.redis,
playlist_url=m3u_playlist_url,
playlist_redis_key=None,
)
@@ -156,7 +154,6 @@ async def update_imdb_data(
@router.post("/torrent", tags=["scraper"])
async def add_torrent(
request: Request,
meta_id: str = Form(...),
meta_type: Literal["movie", "series"] = Form(...),
source: str = Form(...),
@@ -170,7 +167,6 @@ async def add_torrent(
torrent_data: dict = {}
error_msg = None
info_hash = None
title = None
catalogs = catalogs.split(",")
if not magnet_link and not torrent_file:
@@ -227,7 +223,7 @@ async def add_torrent(
catalogs.append("user_upload")
if meta_type == "movie":
movie_data = await get_movie_data_by_id(meta_id, request.app.state.redis)
movie_data = await get_movie_data_by_id(meta_id)
title = movie_data.title
max_similarity_ratio = calculate_max_similarity_ratio(
@@ -253,7 +249,7 @@ async def add_torrent(
)
else:
series_data = await get_series_data_by_id(meta_id, request.app.state.redis)
series_data = await get_series_data_by_id(meta_id)
title = series_data.title
max_similarity_ratio = calculate_max_similarity_ratio(
+11 -11
View File
@@ -6,7 +6,6 @@ from os import path
import PTT
import httpx
from pymongo.errors import DuplicateKeyError
from redis.asyncio import Redis
from db.config import settings
from db.models import TorrentStreams, Season, Episode
@@ -20,11 +19,11 @@ from utils.parser import (
is_contain_18_plus_keywords,
calculate_max_similarity_ratio,
)
from utils.runtime_const import REDIS_ASYNC_CLIENT
from utils.validation_helper import is_video_file
async def get_streams_from_torrentio(
redis: Redis,
streams: list[TorrentStreams],
video_id: str,
catalog_type: str,
@@ -34,7 +33,7 @@ async def get_streams_from_torrentio(
episode: int = None,
):
cache_key = f"{catalog_type}_{video_id}_{season}_{episode}_torrentio_streams"
cached_data = await redis.get(cache_key)
cached_data = await REDIS_ASYNC_CLIENT.get(cache_key)
if cached_data:
return streams
@@ -52,7 +51,7 @@ async def get_streams_from_torrentio(
)
# Cache the data for 24 hours
await redis.set(
await REDIS_ASYNC_CLIENT.set(
cache_key,
"True",
ex=int(timedelta(days=settings.torrentio_search_interval_days).total_seconds()),
@@ -126,10 +125,11 @@ def parse_stream_title(stream: dict) -> dict:
"seeders": extract_seeders(stream["title"]),
"languages": extract_languages(metadata, stream["title"]),
"metadata": metadata,
"file_name": stream.get("behaviorHints", {}).get("filename")
or path.basename(file_name)
if is_video_file(file_name)
else None,
"file_name": (
stream.get("behaviorHints", {}).get("filename") or path.basename(file_name)
if is_video_file(file_name)
else None
),
}
@@ -247,9 +247,9 @@ async def store_and_parse_series_stream_data(
episode_data = [
Episode(
episode_number=episode_number,
file_index=stream.get("fileIdx")
if episode_number == episode
else None,
file_index=(
stream.get("fileIdx") if episode_number == episode else None
),
)
for episode_number in parsed_data["metadata"]["episodes"]
]
+3 -5
View File
@@ -3,16 +3,15 @@ import logging
import re
import dramatiq
from redis.asyncio import Redis
from beanie import BulkWriter
from ipytv import playlist
from ipytv.channel import IPTVAttr
from db import schemas, crud
from db.config import settings
from db.models import TVStreams
from utils import validation_helper
from utils.parser import is_contain_18_plus_keywords
from utils.runtime_const import REDIS_ASYNC_CLIENT
from utils.validation_helper import validate_m3u8_url
@@ -40,20 +39,19 @@ async def add_tv_metadata(batch, namespace: str):
async def parse_m3u_playlist(
namespace: str,
playlist_source: str,
redis_client: Redis,
playlist_url: str = None,
playlist_redis_key: str = None,
):
logging.info(f"Parsing M3U playlist: {playlist_url}")
if playlist_redis_key:
playlist_content = await redis_client.get(playlist_redis_key)
playlist_content = await REDIS_ASYNC_CLIENT.get(playlist_redis_key)
if not playlist_content:
logging.error(f"Playlist not found in Redis: {playlist_redis_key}")
return
playlist_content = playlist_content.decode("utf-8")
iptv_playlist = playlist.loads(playlist_content)
await redis_client.delete(playlist_redis_key)
await REDIS_ASYNC_CLIENT.delete(playlist_redis_key)
else:
iptv_playlist = playlist.loadu(playlist_url)
+3 -4
View File
@@ -4,7 +4,6 @@ from datetime import datetime, timedelta
import PTT
import httpx
from pymongo.errors import DuplicateKeyError
from redis.asyncio import Redis
from db.config import settings
from db.models import TorrentStreams, Season, Episode
@@ -17,12 +16,12 @@ from utils.parser import (
is_contain_18_plus_keywords,
calculate_max_similarity_ratio,
)
from utils.runtime_const import REDIS_ASYNC_CLIENT
ZILEAN_SEARCH_URL = f"{settings.zilean_url}/dmm/search"
async def get_streams_from_zilean(
redis: Redis,
streams: list[TorrentStreams],
video_id: str,
catalog_type: str,
@@ -32,7 +31,7 @@ async def get_streams_from_zilean(
episode: int = None,
) -> list[TorrentStreams]:
cache_key = f"{catalog_type}_{video_id}_{season}_{episode}_zilean_dmm_streams"
cached_data = await redis.get(cache_key)
cached_data = await REDIS_ASYNC_CLIENT.get(cache_key)
if cached_data:
return streams
@@ -48,7 +47,7 @@ async def get_streams_from_zilean(
)
# Cache the data for 24 hours
await redis.set(
await REDIS_ASYNC_CLIENT.set(
cache_key,
"True",
ex=int(timedelta(hours=settings.prowlarr_search_interval_hour).total_seconds()),
+171 -107
View File
@@ -9,7 +9,6 @@ from fastapi import (
Depends,
)
from fastapi.responses import RedirectResponse
from redis.asyncio import Redis
from db import crud, schemas
from db.config import settings
@@ -22,6 +21,7 @@ from streaming_providers.seedr.api import router as seedr_router
from utils import crypto, torrent, wrappers, const
from utils.lock import acquire_redis_lock, release_redis_lock
from utils.network import get_user_public_ip, get_user_data, encode_mediaflow_proxy_url
from utils.runtime_const import REDIS_ASYNC_CLIENT
# Seconds until when the Video URLs are cached
URL_CACHE_EXP = 3600
@@ -29,42 +29,22 @@ URL_CACHE_EXP = 3600
router = APIRouter()
async def get_cached_stream_url(redis: Redis, cached_stream_url_key):
if cached_stream_url := await redis.getex(cached_stream_url_key, ex=URL_CACHE_EXP):
cached_stream_url = cached_stream_url.decode("utf-8")
return cached_stream_url
return None
@router.head("/{secret_str}/stream", tags=["streaming_provider"])
@router.get("/{secret_str}/stream", tags=["streaming_provider"])
@wrappers.exclude_rate_limit
@wrappers.auth_required
async def streaming_provider_endpoint(
secret_str: str,
info_hash: str,
response: Response,
request: Request,
season: int = None,
episode: int = None,
user_data: schemas.UserData = Depends(get_user_data),
):
response.headers.update(const.NO_CACHE_HEADERS)
if not user_data.streaming_provider:
raise HTTPException(status_code=400, detail="No streaming provider set.")
user_ip = await get_user_public_ip(request, user_data)
redirect_status_code = 302
cached_stream_url_key = "streaming_provider_" + crypto.get_text_hash(
f"{user_ip}_{secret_str}_{info_hash}_{season}_{episode}",
full_hash=True,
def generate_cache_key(user_ip, secret_str, info_hash, season, episode):
"""
Generates a cache key based on user IP, secret string, info hash, season, and episode.
"""
return "streaming_provider_" + crypto.get_text_hash(
f"{user_ip}_{secret_str}_{info_hash}_{season}_{episode}", full_hash=True
)
# Check for cached URL before any database operations
if cached_stream_url := await get_cached_stream_url(
request.app.state.redis, cached_stream_url_key
):
async def get_cached_stream_url_and_redirect(
cached_stream_url_key, user_data, response
):
"""
Checks for cached stream URL and returns a RedirectResponse if available.
"""
if cached_stream_url := await get_cached_stream_url(cached_stream_url_key):
if (
user_data.mediaflow_config
and user_data.mediaflow_config.proxy_debrid_streams
@@ -76,33 +56,33 @@ async def streaming_provider_endpoint(
query_params={"api_password": user_data.mediaflow_config.api_password},
)
return RedirectResponse(
url=cached_stream_url,
headers=response.headers,
status_code=redirect_status_code,
url=cached_stream_url, headers=response.headers, status_code=302
)
return None
# Fetch stream data after checking cache
async def fetch_stream_or_404(info_hash):
"""
Fetches stream by info hash, raises a 404 error if not found.
"""
stream = await crud.get_stream_by_info_hash(info_hash)
if not stream:
raise HTTPException(status_code=400, detail="Stream not found.")
return stream
async def get_or_create_video_url(
stream, user_data, info_hash, season, episode, user_ip
):
"""
Retrieves or generates the video URL based on stream data and user info.
"""
magnet_link = await torrent.convert_info_hash_to_magnet(
info_hash, stream.announce_list
)
episode_data = stream.get_episode(season, episode)
filename = episode_data.filename if episode_data else stream.filename
# Create a Redis lock to prevent multiple requests from initiating a download task.
acquired, lock = await acquire_redis_lock(
request.app.state.redis,
f"{cached_stream_url_key}_locked",
timeout=60,
block=True,
)
if not acquired:
raise HTTPException(status_code=429, detail="Too many requests.")
get_video_url = mapper.GET_VIDEO_URL_FUNCTIONS.get(
user_data.streaming_provider.service
)
@@ -120,44 +100,126 @@ async def streaming_provider_endpoint(
torrent_name=stream.torrent_name,
)
if asyncio.iscoroutinefunction(get_video_url):
return await get_video_url(**kwargs)
else:
return await asyncio.to_thread(get_video_url, **kwargs)
async def cache_stream_url(cached_stream_url_key, video_url):
"""
Caches the streaming URL in Redis for future use.
"""
await REDIS_ASYNC_CLIENT.set(
cached_stream_url_key, video_url.encode("utf-8"), ex=URL_CACHE_EXP
)
def apply_mediaflow_proxy_if_needed(video_url, user_data):
"""
Applies mediaflow proxy to the video URL if user config requires it.
"""
if user_data.mediaflow_config and user_data.mediaflow_config.proxy_debrid_streams:
return encode_mediaflow_proxy_url(
user_data.mediaflow_config.proxy_url,
"/proxy/stream",
video_url,
query_params={"api_password": user_data.mediaflow_config.api_password},
)
return video_url
def handle_provider_exception(error, usage):
"""
Handles exceptions raised by the provider and logs them.
"""
logging.error(
"Provider exception occurred for %s: %s",
usage,
error.message,
exc_info=error.video_file_name == "api_error.mp4",
)
return f"{settings.host_url}/static/exceptions/{error.video_file_name}"
def handle_generic_exception(exception, info_hash):
"""
Handles generic exceptions and logs them.
"""
logging.error(
"Generic exception occurred for %s: %s", info_hash, exception, exc_info=True
)
return f"{settings.host_url}/static/exceptions/api_error.mp4"
async def get_cached_stream_url(cached_stream_url_key):
if cached_stream_url := await REDIS_ASYNC_CLIENT.getex(
cached_stream_url_key, ex=URL_CACHE_EXP
):
cached_stream_url = cached_stream_url.decode("utf-8")
return cached_stream_url
return None
@router.head("/{secret_str}/stream", tags=["streaming_provider"])
@router.get("/{secret_str}/stream", tags=["streaming_provider"])
@wrappers.exclude_rate_limit
@wrappers.auth_required
async def streaming_provider_endpoint(
secret_str: str,
info_hash: str,
response: Response,
request: Request,
season: int = None,
episode: int = None,
user_data: schemas.UserData = Depends(get_user_data),
):
"""
Handles streaming provider requests, using caching for performance and
locking mechanisms to prevent duplicate tasks.
"""
response.headers.update(const.NO_CACHE_HEADERS)
if not user_data.streaming_provider:
raise HTTPException(status_code=400, detail="No streaming provider set.")
user_ip = await get_user_public_ip(request, user_data)
cached_stream_url_key = generate_cache_key(
user_ip, secret_str, info_hash, season, episode
)
# Check for cached stream URL
cached_stream_url = await get_cached_stream_url_and_redirect(
cached_stream_url_key, user_data, response
)
if cached_stream_url:
return cached_stream_url
# Fetch stream from DB
stream = await fetch_stream_or_404(info_hash)
# Acquire Redis lock to prevent duplicate download tasks
acquired, lock = await acquire_redis_lock(
f"{cached_stream_url_key}_locked", timeout=60, block=True
)
if not acquired:
raise HTTPException(status_code=429, detail="Too many requests.")
redirect_status_code = 307
try:
if asyncio.iscoroutinefunction(get_video_url):
video_url = await get_video_url(**kwargs)
else:
# video_url = get_video_url(**kwargs) # Use this, In case of debugging api calls
video_url = await asyncio.to_thread(get_video_url, **kwargs)
# Cache the streaming URL for URL_CACHE_EXP
await request.app.state.redis.set(
cached_stream_url_key, video_url.encode("utf-8"), ex=URL_CACHE_EXP
video_url = await get_or_create_video_url(
stream, user_data, info_hash, season, episode, user_ip
)
if (
user_data.mediaflow_config
and user_data.mediaflow_config.proxy_debrid_streams
):
video_url = encode_mediaflow_proxy_url(
user_data.mediaflow_config.proxy_url,
"/proxy/stream",
video_url,
query_params={"api_password": user_data.mediaflow_config.api_password},
)
await cache_stream_url(cached_stream_url_key, video_url)
video_url = apply_mediaflow_proxy_if_needed(video_url, user_data)
redirect_status_code = 302
except ProviderException as error:
logging.error(
"Exception occurred for %s: %s",
info_hash,
error.message,
exc_info=True if error.video_file_name == "api_error.mp4" else False,
)
video_url = f"{settings.host_url}/static/exceptions/{error.video_file_name}"
redirect_status_code = 307
video_url = handle_provider_exception(error, info_hash)
except Exception as e:
logging.error("Exception occurred for %s: %s", info_hash, e, exc_info=True)
video_url = f"{settings.host_url}/static/exceptions/api_error.mp4"
redirect_status_code = 307
# Release the lock
await release_redis_lock(lock)
video_url = handle_generic_exception(e, info_hash)
finally:
await release_redis_lock(lock)
return RedirectResponse(
url=video_url, headers=response.headers, status_code=redirect_status_code
@@ -172,41 +234,43 @@ async def delete_all_watchlist(
response: Response,
user_data: schemas.UserData = Depends(get_user_data),
):
"""
Deletes the entire watchlist for the given user, based on the streaming provider.
"""
response.headers.update(const.NO_CACHE_HEADERS)
user_ip = get_user_public_ip(request, user_data)
if not user_data.streaming_provider:
raise HTTPException(status_code=400, detail="No streaming provider set.")
user_ip = await get_user_public_ip(request, user_data)
kwargs = dict(user_data=user_data, user_ip=user_ip)
if delete_all_watchlist_function := mapper.DELETE_ALL_WATCHLIST_FUNCTIONS.get(
# Get the delete watchlist function for the user's streaming provider
delete_all_watchlist_function = mapper.DELETE_ALL_WATCHLIST_FUNCTIONS.get(
user_data.streaming_provider.service
):
try:
if asyncio.iscoroutinefunction(delete_all_watchlist_function):
await delete_all_watchlist_function(**kwargs)
else:
await asyncio.to_thread(delete_all_watchlist_function, **kwargs)
video_url = f"{settings.host_url}/static/exceptions/watchlist_deleted.mp4"
except ProviderException as error:
logging.error(
"Exception occurred while deleting watchlist: %s",
error.message,
exc_info=True,
)
video_url = f"{settings.host_url}/static/exceptions/{error.video_file_name}"
except Exception as e:
logging.error(
"Exception occurred while deleting watchlist: %s", e, exc_info=True
)
video_url = f"{settings.host_url}/static/exceptions/api_error.mp4"
else:
)
if not delete_all_watchlist_function:
raise HTTPException(
status_code=400, detail="Provider does not support this action."
)
try:
# Call the appropriate delete function asynchronously
if asyncio.iscoroutinefunction(delete_all_watchlist_function):
await delete_all_watchlist_function(**kwargs)
else:
await asyncio.to_thread(delete_all_watchlist_function, **kwargs)
video_url = f"{settings.host_url}/static/exceptions/watchlist_deleted.mp4"
except ProviderException as error:
# Handle provider-specific exceptions
video_url, _ = handle_provider_exception(error, "delete_watchlist")
except Exception as e:
# Handle generic exceptions
video_url, _ = handle_generic_exception(e, "delete_watchlist")
return RedirectResponse(url=video_url, headers=response.headers)
+12 -12
View File
@@ -5,47 +5,47 @@ import time
from redis.asyncio import Redis
from redis.exceptions import LockNotOwnedError
from utils.runtime_const import REDIS_ASYNC_CLIENT
scheduler_lock_key = "mediafusion_scheduler_lock"
heartbeat_key = "mediafusion_scheduler_heartbeat"
heartbeat_timeout = 300 # 5 minutes
async def acquire_scheduler_lock(redis: Redis):
async def acquire_scheduler_lock():
current_time = int(time.time())
# Check if the current scheduler is active
last_heartbeat = await redis.get(heartbeat_key)
last_heartbeat = await REDIS_ASYNC_CLIENT.get(heartbeat_key)
if last_heartbeat and (current_time - int(last_heartbeat) <= heartbeat_timeout):
logging.info("Scheduler is still active, not acquiring lock")
return False, None # Scheduler is still active, do not acquire lock
# Attempt to acquire the lock
acquired, lock = await acquire_redis_lock(
redis, scheduler_lock_key, timeout=heartbeat_timeout, block=False
scheduler_lock_key, timeout=heartbeat_timeout, block=False
)
if acquired:
logging.info("Acquired scheduler lock")
await redis.set(heartbeat_key, current_time)
await REDIS_ASYNC_CLIENT.set(heartbeat_key, current_time)
return True, lock
logging.info("Failed to acquire scheduler lock")
return False, None
async def release_scheduler_lock(redis: Redis, lock):
async def release_scheduler_lock(lock):
logging.info("Releasing scheduler lock")
await release_redis_lock(lock)
await redis.delete(heartbeat_key)
await REDIS_ASYNC_CLIENT.delete(heartbeat_key)
async def maintain_heartbeat(redis: Redis):
async def maintain_heartbeat():
while True:
await asyncio.sleep(heartbeat_timeout // 2)
await redis.set(heartbeat_key, int(time.time()))
await REDIS_ASYNC_CLIENT.set(heartbeat_key, int(time.time()))
async def acquire_redis_lock(
redis: Redis, key: str, timeout: int = 60, block: bool = False
):
lock = redis.lock(key, timeout=timeout)
async def acquire_redis_lock(key: str, timeout: int = 60, block: bool = False):
lock = REDIS_ASYNC_CLIENT.lock(key, timeout=timeout)
acquired = await lock.acquire(blocking=block)
return acquired, lock
+3 -3
View File
@@ -8,7 +8,7 @@ from fastapi.requests import Request
from db.schemas import UserData
from utils import crypto
from utils.runtime_const import PRIVATE_CIDR, REDIS_CLIENT
from utils.runtime_const import PRIVATE_CIDR, REDIS_ASYNC_CLIENT
class CircuitBreakerOpenException(Exception):
@@ -169,7 +169,7 @@ async def get_mediaflow_proxy_public_ip(
cache_key = crypto.get_text_hash(
f"{mediaflow_proxy_url}:{api_password}", full_hash=True
)
if public_ip := await REDIS_CLIENT.getex(cache_key, ex=300):
if public_ip := await REDIS_ASYNC_CLIENT.getex(cache_key, ex=300):
return public_ip
try:
@@ -182,7 +182,7 @@ async def get_mediaflow_proxy_public_ip(
response.raise_for_status()
public_ip = response.json().get("ip")
if public_ip:
await REDIS_CLIENT.set(cache_key, public_ip, ex=300)
await REDIS_ASYNC_CLIENT.set(cache_key, public_ip, ex=300)
return public_ip
except httpx.HTTPStatusError as e:
logging.error(f"HTTP error occurred: {e}")
+30 -20
View File
@@ -95,22 +95,30 @@ 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(
(
user_data.language_sorting.index(lang)
for lang in stream.filtered_languages
if lang in language_filter_set
),
default=len(user_data.language_sorting),
(
const.RESOLUTION_RANKING.get(stream.filtered_resolution, 0)
if key == "resolution"
else (
-min(
(
user_data.language_sorting.index(lang)
for lang in stream.filtered_languages
if lang in language_filter_set
),
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
)
)
)
)
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
)
@@ -184,7 +192,9 @@ async def parse_stream_data(
if episode_data
else stream_data.torrent_name
)
torrent_name = "📂 " + torrent_name.replace(".torrent", "").replace(".", " ")
torrent_name = "📂 " + torrent_name.replace(".torrent", "").replace(
".", " "
)
else:
torrent_name = None
@@ -291,7 +301,7 @@ def convert_size_to_bytes(size_str: str) -> int:
async def parse_tv_stream_data(
tv_streams: list[TVStreams], redis: Redis, user_data: UserData
tv_streams: list[TVStreams], user_data: UserData
) -> list[Stream]:
stream_list = []
is_mediaflow_proxy_enabled = (
@@ -303,7 +313,7 @@ async def parse_tv_stream_data(
for stream in tv_streams[::-1]:
if settings.validate_m3u8_urls_liveness:
is_working = await validate_m3u8_url_with_cache(
redis, stream.url, stream.behaviorHints or {}
stream.url, stream.behaviorHints or {}
)
if not is_working:
continue
@@ -372,7 +382,7 @@ async def fetch_downloaded_info_hashes(
return []
async def generate_manifest(manifest: dict, user_data: UserData, redis: Redis) -> dict:
async def generate_manifest(manifest: dict, user_data: UserData) -> dict:
from db.crud import get_genres
resources = manifest.get("resources", [])
@@ -390,7 +400,7 @@ async def generate_manifest(manifest: dict, user_data: UserData, redis: Redis) -
if catalog_id == "live_tv":
# Add the available genres to the live TV catalog
catalog["extra"][1]["options"] = await get_genres(
catalog_type="tv", redis=redis
catalog_type="tv"
)
ordered_catalogs.append(catalog)
break
+16 -9
View File
@@ -11,15 +11,16 @@ from redis.asyncio import Redis
from db.models import MediaFusionMetaData
from scrapers.imdb_data import get_imdb_rating
from utils import const
from utils.runtime_const import REDIS_ASYNC_CLIENT
ia = Cinemagoer()
font_cache = {}
executor = ThreadPoolExecutor(max_workers=4)
async def fetch_poster_image(url: str, redis: Redis) -> bytes:
async def fetch_poster_image(url: str) -> bytes:
# Check if the image is cached in Redis
cached_image = await redis.get(url)
cached_image = await REDIS_ASYNC_CLIENT.get(url)
if cached_image:
logging.info(f"Using cached image for URL: {url}")
return cached_image
@@ -35,7 +36,7 @@ async def fetch_poster_image(url: str, redis: Redis) -> bytes:
# Cache the image in Redis for 1 hour
logging.info(f"Caching image for URL: {url}")
await redis.set(url, content, ex=3600)
await REDIS_ASYNC_CLIENT.set(url, content, ex=3600)
return content
@@ -68,8 +69,8 @@ def process_poster_image(
raise ValueError(f"Cannot identify image from URL: {mediafusion_data.poster}")
async def create_poster(mediafusion_data: MediaFusionMetaData, redis: Redis) -> BytesIO:
content = await fetch_poster_image(mediafusion_data.poster, redis)
async def create_poster(mediafusion_data: MediaFusionMetaData) -> BytesIO:
content = await fetch_poster_image(mediafusion_data.poster)
loop = asyncio.get_event_loop()
byte_io = await asyncio.wait_for(
@@ -83,7 +84,7 @@ async def create_poster(mediafusion_data: MediaFusionMetaData, redis: Redis) ->
def add_elements_to_poster(
image: Image.Image, imdb_rating: float = None
) -> Image.Image:
draw = ImageDraw.Draw(image, 'RGBA')
draw = ImageDraw.Draw(image, "RGBA")
margin = 10
padding = 5
@@ -109,15 +110,21 @@ def add_elements_to_poster(
rectangle_y1 = image.height - margin
draw.rounded_rectangle(
(rectangle_x0, rectangle_y0, rectangle_x1, rectangle_y1),
fill=(0, 0, 0, 176), radius=8
fill=(0, 0, 0, 176),
radius=8,
)
# Place the IMDb Logo
image.paste(imdb_logo, (rectangle_x0 + padding, rectangle_y0 + padding), imdb_logo)
image.paste(
imdb_logo, (rectangle_x0 + padding, rectangle_y0 + padding), imdb_logo
)
# Now draw the rating text
draw.text(
(rectangle_x0 + padding + imdb_logo.width, rectangle_y0), imdb_text, font=font, fill="#F5C518"
(rectangle_x0 + padding + imdb_logo.width, rectangle_y0),
imdb_text,
font=font,
fill="#F5C518",
)
# Add MediaFusion watermark at the top right
+6 -2
View File
@@ -1,7 +1,7 @@
import re
from fastapi.templating import Jinja2Templates
import redis.asyncio as redis
import redis
from db import schemas
from db.config import settings
@@ -34,6 +34,10 @@ TRACKERS = get_json_data("resources/json/trackers.json")
SECRET_KEY = settings.secret_key.encode("utf-8")
REDIS_CLIENT = redis.Redis(
REDIS_SYNC_CLIENT: redis.Redis = redis.Redis(
connection_pool=redis.ConnectionPool.from_url(settings.redis_url)
)
REDIS_ASYNC_CLIENT: redis.asyncio.Redis = redis.asyncio.Redis(
connection_pool=redis.asyncio.ConnectionPool.from_url(settings.redis_url)
)
+9 -7
View File
@@ -6,10 +6,10 @@ from urllib.parse import urlparse
import aiohttp
from aiohttp import ClientError
from redis.asyncio import Redis
from db import schemas
from utils import const
from utils.runtime_const import REDIS_ASYNC_CLIENT
def is_valid_url(url: str) -> bool:
@@ -58,14 +58,14 @@ async def validate_m3u8_url(
return False
async def validate_m3u8_url_with_cache(redis: Redis, url: str, behaviour_hint: dict):
async def validate_m3u8_url_with_cache(url: str, behaviour_hint: dict):
cache_key = f"m3u8_url:{parse.urlparse(url).netloc}"
cache_data = await redis.get(cache_key)
cache_data = await REDIS_ASYNC_CLIENT.get(cache_key)
if cache_data:
return json.loads(cache_data)
is_valid = await validate_m3u8_url(url, behaviour_hint)
await redis.set(cache_key, json.dumps(is_valid), ex=180)
await REDIS_ASYNC_CLIENT.set(cache_key, json.dumps(is_valid), ex=180)
return is_valid
@@ -93,9 +93,11 @@ async def validate_tv_metadata(metadata: schemas.TVMetaData) -> list[schemas.TVS
stream_validation_tasks.append(
validate_m3u8_url(
stream.url,
stream.behaviorHints.model_dump(exclude_none=True)
if stream.behaviorHints
else {},
(
stream.behaviorHints.model_dump(exclude_none=True)
if stream.behaviorHints
else {}
),
validate_url=True,
)
)