mirror of
https://github.com/Viren070/MediaFusion.git
synced 2025-12-01 23:21:11 +01:00
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:
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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,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
@@ -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)
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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)
|
||||
)
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user