Add tamilultra scrapy & add support for trigger scrapers & consolidate apscheduler, background worker & bugfixes

This commit is contained in:
mhdzumair
2024-03-17 17:41:03 +05:30
parent 4be8eb8cda
commit f04d537ee2
28 changed files with 648 additions and 362 deletions
Generated
+119 -127
View File
@@ -153,14 +153,6 @@
"markers": "python_version >= '3.6'",
"version": "==3.10.4"
},
"async-timeout": {
"hashes": [
"sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f",
"sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028"
],
"markers": "python_version >= '3.7'",
"version": "==4.0.3"
},
"attrs": {
"hashes": [
"sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30",
@@ -1219,11 +1211,11 @@
},
"packaging": {
"hashes": [
"sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5",
"sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"
"sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5",
"sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"
],
"markers": "python_version >= '3.7'",
"version": "==23.2"
"version": "==24.0"
},
"parse-torrent-title": {
"git": "git+https://github.com/mhdzumair/parse-torrent-title",
@@ -1231,11 +1223,11 @@
},
"parsel": {
"hashes": [
"sha256:2708fc74daeeb4ce471e2c2e9089b650ec940c7a218053e57421e69b5b00f82c",
"sha256:aff28e68c9b3f1a901db2a4e3f158d8480a38724d7328ee751c1a4e1c1801e39"
"sha256:1272baeda331cbc7f3a9a91df4c15c39276dc94b4ff2e8d5cffdf8bb6c382ea1",
"sha256:a5a6bcb0c5fc741540ba3075636ff5cb968852a78cc16ae82250e5d486fa7d48"
],
"markers": "python_version >= '3.7'",
"version": "==1.8.1"
"markers": "python_version >= '3.8'",
"version": "==1.9.0"
},
"pikpakapi": {
"git": "git+https://github.com/mhdzumair/PikPakAPI.git",
@@ -1420,12 +1412,12 @@
},
"pydantic": {
"hashes": [
"sha256:72c6034df47f46ccdf81869fddb81aade68056003900a8724a4f160700016a2a",
"sha256:e07805c4c7f5c6826e33a1d4c9d47950d7eaf34868e2690f8594d2e30241f11f"
"sha256:b1704e0847db01817624a6b86766967f552dd9dbf3afba4004409f908dcc84e6",
"sha256:cc46fce86607580867bdc3361ad462bab9c222ef042d3da86f2fb333e1d916c5"
],
"index": "pypi",
"markers": "python_version >= '3.8'",
"version": "==2.6.3"
"version": "==2.6.4"
},
"pydantic-core": {
"hashes": [
@@ -1629,19 +1621,19 @@
},
"pyopenssl": {
"hashes": [
"sha256:6aa33039a93fffa4563e655b61d11364d01264be8ccb49906101e02a334530bf",
"sha256:ba07553fb6fd6a7a2259adb9b84e12302a9a8a75c44046e8bb5d3e5ee887e3c3"
"sha256:17ed5be5936449c5418d1cd269a1a9e9081bc54c17aed272b45856a3d3dc86ad",
"sha256:cabed4bfaa5df9f1a16c0ef64a0cb65318b5cd077a7eda7d6970131ca2f41a6f"
],
"markers": "python_version >= '3.7'",
"version": "==24.0.0"
"version": "==24.1.0"
},
"pyparsing": {
"hashes": [
"sha256:32c7c0b711493c72ff18a981d24f28aaf9c1fb7ed5e9667c9e84e3db623bdbfb",
"sha256:ede28a1a32462f5a9705e07aea48001a08f7cf81a021585011deba701581a0db"
"sha256:a1bac0ce561155ecc3ed78ca94d3c9378656ad4c94c1270de543f621420f94ad",
"sha256:f9db75911801ed778fe61bb643079ff86601aca99fcae6345aa67292038fb742"
],
"markers": "python_full_version >= '3.6.8'",
"version": "==3.1.1"
"version": "==3.1.2"
},
"python-dateutil": {
"hashes": [
@@ -1733,110 +1725,110 @@
},
"rapidfuzz": {
"hashes": [
"sha256:01835d02acd5d95c1071e1da1bb27fe213c84a013b899aba96380ca9962364bc",
"sha256:01eb03cd880a294d1bf1a583fdd00b87169b9cc9c9f52587411506658c864d73",
"sha256:03f73b381bdeccb331a12c3c60f1e41943931461cdb52987f2ecf46bfc22f50d",
"sha256:0402f1629e91a4b2e4aee68043a30191e5e1b7cd2aa8dacf50b1a1bcf6b7d3ab",
"sha256:060bd7277dc794279fa95522af355034a29c90b42adcb7aa1da358fc839cdb11",
"sha256:064c1d66c40b3a0f488db1f319a6e75616b2e5fe5430a59f93a9a5e40a656d15",
"sha256:06e98ff000e2619e7cfe552d086815671ed09b6899408c2c1b5103658261f6f3",
"sha256:08b6fb47dd889c69fbc0b915d782aaed43e025df6979b6b7f92084ba55edd526",
"sha256:0a9fc714b8c290261669f22808913aad49553b686115ad0ee999d1cb3df0cd66",
"sha256:0bbfae35ce4de4c574b386c43c78a0be176eeddfdae148cb2136f4605bebab89",
"sha256:12ff8eaf4a9399eb2bebd838f16e2d1ded0955230283b07376d68947bbc2d33d",
"sha256:1936d134b6c513fbe934aeb668b0fee1ffd4729a3c9d8d373f3e404fbb0ce8a0",
"sha256:1c47d592e447738744905c18dda47ed155620204714e6df20eb1941bb1ba315e",
"sha256:1dfc557c0454ad22382373ec1b7df530b4bbd974335efe97a04caec936f2956a",
"sha256:1e12319c6b304cd4c32d5db00b7a1e36bdc66179c44c5707f6faa5a889a317c0",
"sha256:23de71e7f05518b0bbeef55d67b5dbce3bcd3e2c81e7e533051a2e9401354eb0",
"sha256:266dd630f12696ea7119f31d8b8e4959ef45ee2cbedae54417d71ae6f47b9848",
"sha256:2963f4a3f763870a16ee076796be31a4a0958fbae133dbc43fc55c3968564cf5",
"sha256:2a791168e119cfddf4b5a40470620c872812042f0621e6a293983a2d52372db0",
"sha256:2b155e67fff215c09f130555002e42f7517d0ea72cbd58050abb83cb7c880cec",
"sha256:2b19795b26b979c845dba407fe79d66975d520947b74a8ab6cee1d22686f7967",
"sha256:2e03038bfa66d2d7cffa05d81c2f18fd6acbb25e7e3c068d52bb7469e07ff382",
"sha256:3028ee8ecc48250607fa8a0adce37b56275ec3b1acaccd84aee1f68487c8557b",
"sha256:35660bee3ce1204872574fa041c7ad7ec5175b3053a4cb6e181463fc07013de7",
"sha256:3c772d04fb0ebeece3109d91f6122b1503023086a9591a0b63d6ee7326bd73d9",
"sha256:3c84294f4470fcabd7830795d754d808133329e0a81d62fcc2e65886164be83b",
"sha256:40cced1a8852652813f30fb5d4b8f9b237112a0bbaeebb0f4cc3611502556764",
"sha256:4243a9c35667a349788461aae6471efde8d8800175b7db5148a6ab929628047f",
"sha256:42f211e366e026de110a4246801d43a907cd1a10948082f47e8a4e6da76fef52",
"sha256:4381023fa1ff32fd5076f5d8321249a9aa62128eb3f21d7ee6a55373e672b261",
"sha256:484759b5dbc5559e76fefaa9170147d1254468f555fd9649aea3bad46162a88b",
"sha256:49b9ed2472394d306d5dc967a7de48b0aab599016aa4477127b20c2ed982dbf9",
"sha256:53251e256017e2b87f7000aee0353ba42392c442ae0bafd0f6b948593d3f68c6",
"sha256:588c4b20fa2fae79d60a4e438cf7133d6773915df3cc0a7f1351da19eb90f720",
"sha256:5a2f3e9df346145c2be94e4d9eeffb82fab0cbfee85bd4a06810e834fe7c03fa",
"sha256:5d82b9651e3d34b23e4e8e201ecd3477c2baa17b638979deeabbb585bcb8ba74",
"sha256:5dd95b6b7bfb1584f806db89e1e0c8dbb9d25a30a4683880c195cc7f197eaf0c",
"sha256:692c9a50bea7a8537442834f9bc6b7d29d8729a5b6379df17c31b6ab4df948c2",
"sha256:6b0ccc2ec1781c7e5370d96aef0573dd1f97335343e4982bdb3a44c133e27786",
"sha256:6dede83a6b903e3ebcd7e8137e7ff46907ce9316e9d7e7f917d7e7cdc570ee05",
"sha256:7142ee354e9c06e29a2636b9bbcb592bb00600a88f02aa5e70e4f230347b373e",
"sha256:7183157edf0c982c0b8592686535c8b3e107f13904b36d85219c77be5cefd0d8",
"sha256:7420e801b00dee4a344ae2ee10e837d603461eb180e41d063699fb7efe08faf0",
"sha256:757dfd7392ec6346bd004f8826afb3bf01d18a723c97cbe9958c733ab1a51791",
"sha256:76c23ceaea27e790ddd35ef88b84cf9d721806ca366199a76fd47cfc0457a81b",
"sha256:7fec74c234d3097612ea80f2a80c60720eec34947066d33d34dc07a3092e8105",
"sha256:82300e5f8945d601c2daaaac139d5524d7c1fdf719aa799a9439927739917460",
"sha256:841eafba6913c4dfd53045835545ba01a41e9644e60920c65b89c8f7e60c00a9",
"sha256:8d7a072f10ee57c8413c8ab9593086d42aaff6ee65df4aa6663eecdb7c398dca",
"sha256:8e4da90e4c2b444d0a171d7444ea10152e07e95972bb40b834a13bdd6de1110c",
"sha256:96cd19934f76a1264e8ecfed9d9f5291fde04ecb667faef5f33bdbfd95fe2d1f",
"sha256:a03863714fa6936f90caa7b4b50ea59ea32bb498cc91f74dc25485b3f8fccfe9",
"sha256:a1788ebb5f5b655a15777e654ea433d198f593230277e74d51a2a1e29a986283",
"sha256:a3ee4f8f076aa92184e80308fc1a079ac356b99c39408fa422bbd00145be9854",
"sha256:a490cd645ef9d8524090551016f05f052e416c8adb2d8b85d35c9baa9d0428ab",
"sha256:a553cc1a80d97459d587529cc43a4c7c5ecf835f572b671107692fe9eddf3e24",
"sha256:a59472b43879012b90989603aa5a6937a869a72723b1bf2ff1a0d1edee2cc8e6",
"sha256:ac434fc71edda30d45db4a92ba5e7a42c7405e1a54cb4ec01d03cc668c6dcd40",
"sha256:ad9d74ef7c619b5b0577e909582a1928d93e07d271af18ba43e428dc3512c2a1",
"sha256:ae598a172e3a95df3383634589660d6b170cc1336fe7578115c584a99e0ba64d",
"sha256:b2ef4c0fd3256e357b70591ffb9e8ed1d439fb1f481ba03016e751a55261d7c1",
"sha256:b3e5af946f419c30f5cb98b69d40997fe8580efe78fc83c2f0f25b60d0e56efb",
"sha256:b53137d81e770c82189e07a8f32722d9e4260f13a0aec9914029206ead38cac3",
"sha256:b7e3375e4f2bfec77f907680328e4cd16cc64e137c84b1886d547ab340ba6928",
"sha256:bcc957c0a8bde8007f1a8a413a632a1a409890f31f73fe764ef4eac55f59ca87",
"sha256:be156f51f3a4f369e758505ed4ae64ea88900dcb2f89d5aabb5752676d3f3d7e",
"sha256:be368573255f8fbb0125a78330a1a40c65e9ba3c5ad129a426ff4289099bfb41",
"sha256:c1a23eee225dfb21c07f25c9fcf23eb055d0056b48e740fe241cbb4b22284379",
"sha256:c65f92881753aa1098c77818e2b04a95048f30edbe9c3094dc3707d67df4598b",
"sha256:ca3dfcf74f2b6962f411c33dd95b0adf3901266e770da6281bc96bb5a8b20de9",
"sha256:cd4ba4c18b149da11e7f1b3584813159f189dc20833709de5f3df8b1342a9759",
"sha256:d056e342989248d2bdd67f1955bb7c3b0ecfa239d8f67a8dfe6477b30872c607",
"sha256:d2f0274595cc5b2b929c80d4e71b35041104b577e118cf789b3fe0a77b37a4c5",
"sha256:d73dcfe789d37c6c8b108bf1e203e027714a239e50ad55572ced3c004424ed3b",
"sha256:d79aec8aeee02ab55d0ddb33cea3ecd7b69813a48e423c966a26d7aab025cdfe",
"sha256:da3e8c9f7e64bb17faefda085ff6862ecb3ad8b79b0f618a6cf4452028aa2222",
"sha256:dad55a514868dae4543ca48c4e1fc0fac704ead038dafedf8f1fc0cc263746c1",
"sha256:dec307b57ec2d5054d77d03ee4f654afcd2c18aee00c48014cb70bfed79597d6",
"sha256:e06c4242a1354cf9d48ee01f6f4e6e19c511d50bb1e8d7d20bcadbb83a2aea90",
"sha256:e19d519386e9db4a5335a4b29f25b8183a1c3f78cecb4c9c3112e7f86470e37f",
"sha256:e49b9575d16c56c696bc7b06a06bf0c3d4ef01e89137b3ddd4e2ce709af9fe06",
"sha256:ebcfb5bfd0a733514352cfc94224faad8791e576a80ffe2fd40b2177bf0e7198",
"sha256:ed0f712e0bb5fea327e92aec8a937afd07ba8de4c529735d82e4c4124c10d5a0",
"sha256:edf97c321fd641fea2793abce0e48fa4f91f3c202092672f8b5b4e781960b891",
"sha256:eef8b346ab331bec12bbc83ac75641249e6167fab3d84d8f5ca37fd8e6c7a08c",
"sha256:f056ba42fd2f32e06b2c2ba2443594873cfccc0c90c8b6327904fc2ddf6d5799",
"sha256:f382f7ffe384ce34345e1c0b2065451267d3453cadde78946fbd99a59f0cc23c",
"sha256:f59d19078cc332dbdf3b7b210852ba1f5db8c0a2cd8cc4c0ed84cc00c76e6802",
"sha256:fbc07e2e4ac696497c5f66ec35c21ddab3fc7a406640bffed64c26ab2f7ce6d6",
"sha256:fde9b14302a31af7bdafbf5cfbb100201ba21519be2b9dedcf4f1048e4fbe65d"
"sha256:012221629d54d3bee954148247f711eb86d4d390b589ebfe03172ea0b37a7531",
"sha256:080cb71b50cb6aff11d1c6aeb157f273e2da0b2bdb3f9d7b01257e49e69a8576",
"sha256:153d065e353371cc0aeff32b99999a5758266a64e958d1364189367c1c9f6814",
"sha256:16270b5529de83b7bae7457e952e4d9cf3fbf029a837dd32d415bb9e0eb8e599",
"sha256:1b86b93d93020c2b3edc1665d75c8855784845fc0a739b312c26c3a4bf0c80d5",
"sha256:1c54d3c85e522d3ac9ee39415f183c8fa184c4f87e7e5a37938f15a6d50e853a",
"sha256:1df2faf80201952e252413b6fac6f3e146080dcebb87bb1bb722508e67558ed8",
"sha256:1dfe4c24957474ce0ac75d886387e30e292b4be39228a6d71f76de414dc187db",
"sha256:259364199cbfeca33b1af369fc7951f71717aa285184a3fa5a7b1772da1b89db",
"sha256:28243086ed0e50808bb56632e5442c457241646aeafafd501ac87901f40a3237",
"sha256:28c011fb31f2c3f82f503aedd6097d3d3854e574e327a119a3b7eb2cf90b79ca",
"sha256:2a46220f86a5f9cb016af31525e0d0865cad437d02239aa0d8aed2ab8bff1f1c",
"sha256:2acd2514defce81e6ff4bbff50252d5e7df8e85a731442c4b83e44c86cf1c916",
"sha256:2d580d937146e803c8e5e1b87916cab8d6f84013b6392713e201efcda335c7d8",
"sha256:3168ff565d4b8c239cf11fb604dd2507d30e9bcaac76a4077c0ac23cf2c866ed",
"sha256:337e357f693130c4c6be740652542b260e36f622c59e01fa33d58f1d2750c930",
"sha256:3549123fca5bb817341025f98e8e49ca99f84596c7c4f92b658f8e5836040d4a",
"sha256:354ad5fe655beb7b279390cb58334903931c5452ecbad1b1666ffb06786498e2",
"sha256:35aeac852bca06023d6bbd50c1fc504ca5a9a3613d5e75a140f0be7601fa34ef",
"sha256:35bd4bc9c40e6994c5d6edea4b9319388b4d9711c13c66d543bb4c37624b4184",
"sha256:36ce7b68a7b90b787cdd73480a68d2f1ca63c31a3a9d5a79a8736f978e1e9344",
"sha256:380586664f2f63807050ddb95e7702888b4f0b425abf17655940c411f39287ad",
"sha256:3ae7c86914cb6673e97e187ba431b9c4cf4177d9ae77f8a1e5b2ba9a5628839e",
"sha256:3c1dc078ef371fce09f9f3eec2ca4eaa2a8cd412ec53941015b4f39f14d34407",
"sha256:4209816626d8d6ff8ae7dc248061c6059e618b70c6e6f6e4d7444ae3740b2b85",
"sha256:423c7c588b09d618601097b7a0017dfcb91132a2076bef29023c5f3cd2dc3de1",
"sha256:48e1eaea8fcd522fca7f04f0480663f0f0cfb77957092cce60a93f4462864996",
"sha256:495c0d8e14e6f12520eb7fc71b9ba9fcaafb47fc23a654e6e89b6c7985ec0020",
"sha256:4c279928651ce0e9e5220dcb25a00cc53b65e592a0861336a38299bcdca3a596",
"sha256:4edcceebb85ebfa49a3ddcde20ad891d36c08dc0fd592efdab0e7d313a4e36af",
"sha256:4fcfa23b5553b27f4016df77c53172ea743454cf12c28cfa7c35a309a2be93b3",
"sha256:53597fd72a9340bcdd80d3620f4957c2b92f9b569313b969a3abdaffd193aae6",
"sha256:5378c04102c7f084cde30a100154fa6d7e2baf0d51a6bdd2f912545559c1fb35",
"sha256:617949a70150e6fffdaed19253dd49f7a53528411dc8bf7663d499ba21e0f61e",
"sha256:62df2136068e2515ed8beb01756381ff62c29384d785e3bf46e3111d4ea3ba1e",
"sha256:6440ed0b3007c1c9286b0b88fe2ab2d9e83edd60cd62293b3dfabb732b4e8a30",
"sha256:6468f8bc8c3c50604f43631550ef9cfec873515dba5023ca34d461be94669fc8",
"sha256:6478f7803efebf5f644d0b758439c5b25728550fdfbb19783d150004c46a75a9",
"sha256:66b008bf2972740cd2dda5d382eb8bdb87265cd88198e71c7797bdc0d1f79d20",
"sha256:673ba2c343644805acdae1cb949c6a4de71aa2f62a998978551ebea59603af3f",
"sha256:6d67b649bf3e1b1722d04eca44d37919aef88305ce7ad05564502d013cf550fd",
"sha256:6ee98d88ae9ccc77ff61992ed33b2496478def5dc0da55c9a9aa06fcb725a352",
"sha256:7382c90170f60c846c81a07ddd80bb2e8c43c8383754486fa37f67391a571897",
"sha256:74c6773b11445b5e5cf93ca383171cd0ac0cdeafea11a7b2a5688f8bf8d813e6",
"sha256:7d830bc7a9b586a374147ec60b08b1f9ae5996b43f75cc514f37faef3866b519",
"sha256:7f18397c8d6a65fc0b288d2fc29bc7baeea6ba91eeb95163a3cd98f23cd3bc85",
"sha256:7f9fa14136a5b0cba1ec42531f7c3e0b0d3edb7fd6bc5e5ae7b498541f3855ab",
"sha256:80e51b22a7da83f9c87a97e92df07ed0612c74c35496590255f4b5d5b4212dfe",
"sha256:81a630ed2fc3ec5fc7400eb66bab1f87e282b4d47f0abe3e48c6634dfa13b5e4",
"sha256:824cc381cf81cbf8d158f6935664ec2a69e6ac3b1d39fa201988bf81a257f775",
"sha256:84c1032ae42628465b7a5cc35249906061e18a8193c9c27cbd2db54e9823a9a6",
"sha256:85a5b6e026393fe39fb61146b9c17c5af66fffbe1410e992c4bb06d9ec327bd3",
"sha256:87ac3a87f2251ae2e95fc9478ca5c759de6d141d04c84d3fec9f9cdcfc167b33",
"sha256:95a49c6b8bf1229743ae585dd5b7d57f0d15a7eb6e826866d5c9965ba958503c",
"sha256:9a07dffac439223b4f1025dbfc68f4445a3460a859309c9858c2a3fa29617cdc",
"sha256:9a74102fc5a2534fe91f7507838623e1f3a149d8e05648389c42bb42e14b1c3f",
"sha256:9bcc91ebd8fc69a6bd3b5711c8250f5f4e70606b4da75ef415f57ad209978205",
"sha256:9d457c89bac1471442002e70551e8268e639b3870b4a4521eae363c07253be87",
"sha256:a5637e6bf11b15b5aff6ee818c76bdec99ad208511b78985e6209ba648a6e3ee",
"sha256:a7895e04a22d6515bc91a850e0831f2405547605aa311d1ffec51e4818abc3c1",
"sha256:a945567c2b0b6e069454c9782d5234b0b6795718adf7a9f868bd3144afa6a023",
"sha256:ab269adfc64480f209e99f253391a10735edd5c09046e04899adab5fb132f20e",
"sha256:af7c19ec86e11488539380d3db1755be5d561a3c0e7b04ff9d07abd7f9a8e9d8",
"sha256:b593cc51aed887e93b78c2f94dfae9008be2b23d17afd3b1f1d3eb3913b58f26",
"sha256:bd82f9838519136b7083dd1e3149ee80344521f3dc37f744f227505ff0883efb",
"sha256:be69f7fd46b5c6467fe5e2fd4cff3816b0c03048eed8a4becb9a73e6000960e7",
"sha256:bec353f022011e6e5cd28ccb8700fbd2a33918197af0d4e0abb3c3f4845cc864",
"sha256:c17c5efee347a40a6f4c1eec59e3d7d1e22f7613a97f8b8a07733ef723483a04",
"sha256:cbd5894f23fdf5697499cf759523639838ac822bd1600e343fdce7313baa02ae",
"sha256:cda81d0e0ce0c13abfa46b24e10c1e85f9c6acb628f0a9a948f5779f9c2076a2",
"sha256:cf911e792ab0c431694c9bf2648afabfd92099103f2e31492893e078ddca5e1a",
"sha256:d07899506a5a8760448d9df036d528b55a554bf571714173635c79eef4a86e58",
"sha256:d41dd59a70decfce6595315367a2fea2af660d92a9d144acc6479030501014d7",
"sha256:d4f6de745fe6ce46a422d353ee10599013631d7d714a36d025f164b2d4e8c000",
"sha256:d57b98013b802621bbc8b12a46bfc9d36ac552ab51ca207f7ce167ad46adabeb",
"sha256:d8e5a437b9089df6242a718d9c31ab1742989e9400a0977af012ef483b63b4c2",
"sha256:dbee7f5ff11872b76505cbd87c814abc823e8757f11c69062eb3b25130a283da",
"sha256:ddc380ffaa90f204cc9ddcb779114b9ab6f015246d549de9d47871a97ef9f18a",
"sha256:de8adc12161bf282c60f12dc9233bb31632f71d446a010fe7469a69b8153427f",
"sha256:e06f6d270112f5db001f1cba5a97e1a48aee3d3dbdcbea3ec027c230462dbf9b",
"sha256:e1507fc5769aa109dda4de3a15f822a0f6a03e18d627bd0ba3ddbb253cf70e07",
"sha256:e48dde8ca83d11daa00900cf6a5d281a1297aef9b7bfa73801af6e8822be5019",
"sha256:e706f302c6a3ae0d74edd0d6ace46aee1ae07c563b436ccf5ff04db2b3571e60",
"sha256:eb2e51d01b9c6d6954a3e055c57a80d4685b4fc82719db5519fc153566bcd6bb",
"sha256:ed52461ae5a9ea4c400d38e2649c74a413f1a6d8fb8308b66f1fbd122514732f",
"sha256:ef3925daaa93eed20401012e219f569ff0c039ed5bf4ce2d3737b4f75d441622",
"sha256:f31314fd2e2f3dc3e519e6f93669462ce7953df2def1c344aa8f5345976d0eb2",
"sha256:f3a70f341c4c111bad910d2df69c78577a98af140319a996af24c9385939335d",
"sha256:f8b77779174b1b40aa70827692571ab457061897846255ad7d5d559e2edb1932",
"sha256:fe2a68be734e8e88af23385c68d6467e15818b6b1df1cbfebf7bff577226c957"
],
"markers": "python_version >= '3.8'",
"version": "==3.6.1"
"version": "==3.6.2"
},
"redis": {
"extras": [
"hiredis"
],
"hashes": [
"sha256:3f82cc80d350e93042c8e6e7a5d0596e4dd68715babffba79492733e1f367037",
"sha256:4caa8e1fcb6f3c0ef28dba99535101d80934b7d4cd541bbb47f4a3826ee472d1"
"sha256:4973bae7444c0fbed64a06b87446f79361cb7e4ec1538c022d696ed7a5015580",
"sha256:5da9b8fe9e1254293756c16c008e8620b3d15fcc6dde6babde9541850e72a32d"
],
"markers": "python_version >= '3.7'",
"version": "==5.0.2"
"version": "==5.0.3"
},
"requests": {
"hashes": [
@@ -1899,11 +1891,11 @@
},
"setuptools": {
"hashes": [
"sha256:02fa291a0471b3a18b2b2481ed902af520c69e8ae0919c13da936542754b4c56",
"sha256:5c0806c7d9af348e6dd3777b4f4dbb42c7ad85b190104837488eab9a7c945cf8"
"sha256:0ff4183f8f42cd8fa3acea16c45205521a4ef28f73c6391d8a25e92893134f2e",
"sha256:c21c49fb1042386df081cb5d86759792ab89efca84cf114889191cd09aacc80c"
],
"markers": "python_version >= '3.8'",
"version": "==69.1.1"
"version": "==69.2.0"
},
"six": {
"hashes": [
@@ -2019,11 +2011,11 @@
},
"torf": {
"hashes": [
"sha256:e84ec73cd81cb2d56e36b9f4604806f16ee0c70aeafadfe2f31ca3266ca03a87",
"sha256:f846ef58b6b00bc3b6a5c25bf225c59149ee871738d87a20a8953f620b77671c"
"sha256:261e861683f4d15cd8ae43c9db4828ef9da08f8efdf3b2878f82c0385d1e3707",
"sha256:9892b68c414c48a6dc0d5127cc6c76088fef3f9ffa91851cb0b4334d72e9565f"
],
"markers": "python_version >= '3.7'",
"version": "==4.2.4"
"version": "==4.2.5"
},
"twisted": {
"hashes": [
@@ -2062,11 +2054,11 @@
"standard"
],
"hashes": [
"sha256:3d9a267296243532db80c83a959a3400502165ade2c1338dea4e67915fd4745a",
"sha256:5c89da2f3895767472a35556e539fd59f7edbe9b1e9c0e1c99eebeadc61838e4"
"sha256:6623abbbe6176204a4226e67607b4d52cc60ff62cda0ff177613645cefa2ece1",
"sha256:cab4473b5d1eaeb5a0f6375ac4bc85007ffc75c3cc1768816d9e5d589857b067"
],
"markers": "python_version >= '3.8'",
"version": "==0.27.1"
"version": "==0.28.0"
},
"uvloop": {
"hashes": [
+41 -2
View File
@@ -18,8 +18,12 @@ from fastapi.responses import FileResponse, StreamingResponse
from fastapi.templating import Jinja2Templates
from api import middleware
from api.scheduler import setup_scheduler
from db import database, crud, schemas
from db.config import settings
from mediafusion_scrapy.task import run_spider
from scrapers.tamilmv import run_tamilmv_scraper
from scrapers.tamil_blasters import run_tamil_blasters_scraper
from streaming_providers.routes import router as streaming_provider_router
from utils import crypto, torrent, poster, const, wrappers
from utils.parser import generate_manifest
@@ -64,6 +68,8 @@ async def start_scheduler():
scheduler.add_job(
crud.delete_search_history, CronTrigger(day="*/1"), name="delete_search_history"
)
setup_scheduler(scheduler)
scheduler.start()
app.state.scheduler = scheduler
@@ -96,7 +102,7 @@ async def get_home(request: Request):
@app.get("/health", tags=["health"])
@wrappers.exclude
@wrappers.exclude_rate_limit
async def health(request: Request):
return {"status": "healthy"}
@@ -378,6 +384,7 @@ async def get_streams(
user_data, secret_str, request.app.state.redis, video_id, season, episode
)
else:
response.headers.update(const.NO_CACHE_HEADERS)
fetched_streams = await crud.get_tv_streams(video_id)
return {"streams": fetched_streams}
@@ -391,7 +398,7 @@ async def encrypt_user_data(user_data: schemas.UserData):
@app.get("/poster/{catalog_type}/{mediafusion_id}.jpg", tags=["poster"])
@wrappers.exclude
@wrappers.exclude_rate_limit
async def get_poster(
catalog_type: Literal["movie", "series", "tv"],
mediafusion_id: str,
@@ -437,6 +444,38 @@ async def get_poster(
raise HTTPException(status_code=404, detail="Failed to create poster.")
@app.get("/scraper", tags=["scraper"])
async def get_scraper(request: Request):
return TEMPLATES.TemplateResponse(
"html/scraper.html",
{
"request": request,
"authentication_required": settings.api_password is not None,
},
)
@app.post("/scraper/run", tags=["scraper"])
async def run_scraper_task(task: schemas.ScraperTask):
if settings.api_password and task.api_password != settings.api_password:
raise HTTPException(status_code=401, detail="Invalid API password.")
if task.scraper_type == "tamilmv":
run_tamilmv_scraper.send(task.pages, task.start_page)
elif task.scraper_type == "tamilblasters":
run_tamil_blasters_scraper.send(task.pages, task.start_page)
elif task.scraper_type == "scrapy":
if not task.spider_name:
raise HTTPException(
status_code=400, detail="Spider name is required for scrapy tasks."
)
run_spider.send(task.spider_name)
else:
raise HTTPException(status_code=400, detail="Invalid scraper type.")
return {"status": f"Scraping {task.scraper_type} task has been scheduled."}
app.include_router(
streaming_provider_router, prefix="/streaming_provider", tags=["streaming_provider"]
)
+1 -1
View File
@@ -102,7 +102,7 @@ class RateLimitMiddleware(BaseHTTPMiddleware):
if not endpoint:
return await call_next(request)
is_exclude = getattr(endpoint, "exclude", False)
is_exclude = getattr(endpoint, "exclude_rate_limit", False)
if is_exclude:
return await call_next(request)
+50 -57
View File
@@ -1,73 +1,66 @@
import asyncio
import logging
from apscheduler.schedulers.asyncio import AsyncIOScheduler
from apscheduler.triggers.cron import CronTrigger
from scrapy.crawler import CrawlerProcess
from scrapy.utils.project import get_project_settings
from db.config import settings
from scrapers import tamil_blasters, tamilmv
# Configure logging
logging.basicConfig(
level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s"
)
from mediafusion_scrapy.task import run_spider
from utils.validation_helper import validate_tv_streams_in_db
def run_formula_tgx_spider(spider_name: str):
def setup_scheduler(scheduler: AsyncIOScheduler):
"""
Function to start the formula_tgx spider.
Set up the scheduler with the required jobs.
"""
process = CrawlerProcess(get_project_settings())
process.crawl(spider_name)
process.start()
# Setup tamil blasters scraper
scheduler.add_job(
tamil_blasters.run_tamil_blasters_scraper.send,
CronTrigger.from_crontab(settings.tamil_blasters_scheduler_crontab),
name="tamil_blasters",
)
scheduler = AsyncIOScheduler()
# Setup tamilmv scraper
scheduler.add_job(
tamilmv.run_tamilmv_scraper.send,
CronTrigger.from_crontab(settings.tamilmv_scheduler_crontab),
name="tamilmv",
)
# Setup tamil blasters scraper
scheduler.add_job(
tamil_blasters.run_schedule_scrape,
CronTrigger.from_crontab(settings.tamil_blasters_scheduler_crontab),
name="tamil_blasters",
)
# Setup formula_tgx scraper
scheduler.add_job(
run_spider.send,
CronTrigger.from_crontab(settings.formula_tgx_scheduler_crontab),
name="formula_tgx",
kwargs={"spider_name": "formula_tgx"},
)
# Setup tamilmv scraper
scheduler.add_job(
tamilmv.run_schedule_scrape,
CronTrigger.from_crontab(settings.tamilmv_scheduler_crontab),
name="tamilmv",
)
# Setup mhdtvworld scraper
scheduler.add_job(
run_spider.send,
CronTrigger.from_crontab(settings.mhdtvworld_scheduler_crontab),
name="mhdtvworld",
kwargs={"spider_name": "mhdtvworld"},
)
# Setup formula_tgx scraper
scheduler.add_job(
run_formula_tgx_spider,
CronTrigger.from_crontab(settings.formula_tgx_scheduler_crontab),
name="formula_tgx",
kwargs={"spider_name": "formula_tgx"},
)
# Setup mhdtvsports scraper
scheduler.add_job(
run_spider.send,
CronTrigger.from_crontab(settings.mhdtvsports_scheduler_crontab),
name="mhdtvsports",
kwargs={"spider_name": "mhdtvsports"},
)
# Setup mhdtvworld scraper
scheduler.add_job(
run_formula_tgx_spider,
CronTrigger.from_crontab(settings.mhdtvworld_scheduler_crontab),
name="mhdtvworld",
kwargs={"spider_name": "mhdtvworld"},
)
# Setup tamilultra scraper
scheduler.add_job(
run_spider.send,
CronTrigger.from_crontab(settings.tamilultra_scheduler_crontab),
name="tamilultra",
kwargs={"spider_name": "tamilultra"},
)
# Setup mhdtvsports scraper
scheduler.add_job(
run_formula_tgx_spider,
CronTrigger.from_crontab(settings.mhdtvsports_scheduler_crontab),
name="mhdtvsports",
kwargs={"spider_name": "mhdtvsports"},
)
# Start the scheduler
scheduler.start()
try:
asyncio.get_event_loop().run_forever()
except Exception as e:
logging.error(f"Error occurred: {e}")
# Schedule validate_tv_streams_in_db
scheduler.add_job(
validate_tv_streams_in_db.send,
CronTrigger.from_crontab(settings.validate_tv_streams_in_db_crontab),
name="validate_tv_streams_in_db",
)
+7 -3
View File
@@ -1,10 +1,14 @@
# import background actors
import asyncio
from db import database
from scrapers.helpers import update_torrent_movie_streams_metadata # noqa: F401
from scrapers.prowlarr import parse_and_store_movie_stream_data # noqa: F401
# import background actors
from scrapers import helpers # noqa: F401
from scrapers import prowlarr # noqa: F401
from mediafusion_scrapy import task # noqa: F401
from utils import torrent
from utils import validation_helper # noqa: F401
from scrapers import tamil_blasters, tamilmv # noqa: F401
async def async_setup():
+2
View File
@@ -29,6 +29,8 @@ class Settings(BaseSettings):
formula_tgx_scheduler_crontab: str = "0 */12 * * *"
mhdtvworld_scheduler_crontab: str = "0 0 * * 5"
mhdtvsports_scheduler_crontab: str = "0 10 * * *"
tamilultra_scheduler_crontab: str = "0 8 * * *"
validate_tv_streams_in_db_crontab: str = "0 */2 * * *"
class Config:
env_file = ".env"
+17 -4
View File
@@ -6,6 +6,7 @@ from uuid import uuid4
from beanie import WriteRules
from beanie.operators import In, Set
from pydantic import ValidationError
from pymongo.errors import DuplicateKeyError
from redis.asyncio import Redis
@@ -78,7 +79,9 @@ async def get_meta_list(
async def get_tv_meta_list(
genre: Optional[str] = None, skip: int = 0, limit: int = 25
) -> list[schemas.Meta]:
query = MediaFusionTVMetaData.find(fetch_links=True)
query = MediaFusionTVMetaData.find(
MediaFusionMovieMetaData.streams.is_working == True, fetch_links=True
)
if genre:
query = query.find(In(MediaFusionTVMetaData.genres, [genre]))
@@ -238,11 +241,21 @@ async def get_series_streams(
async def get_tv_streams(video_id: str) -> list[Stream]:
tv_data = await get_tv_data_by_id(video_id, True)
if not tv_data:
tv_streams = await TVStreams.find(
{"meta_id": video_id, "is_working": True}
).to_list()
if not tv_streams:
return []
return parse_tv_stream_data(tv_data)
return await parse_tv_stream_data(tv_streams)
async def get_tv_stream_by_id(stream_id: str) -> TVStreams | None:
try:
stream = await TVStreams.get(stream_id)
except ValidationError:
return None
return stream
async def get_movie_meta(meta_id: str):
+1
View File
@@ -63,6 +63,7 @@ class TVStreams(Document):
created_at: datetime = Field(default_factory=datetime.now)
meta_id: Optional[str] = None
country: str | None = None
is_working: Optional[bool] = True
class MediaFusionMetaData(Document):
+8
View File
@@ -195,3 +195,11 @@ class TVMetaData(BaseModel):
class TorrentStreamsList(BaseModel):
streams: list[TorrentStreams]
class ScraperTask(BaseModel):
scraper_type: str
pages: int = 1
start_page: int = 1
spider_name: str = None
api_password: str = None
@@ -59,14 +59,6 @@ services:
volumes:
- prowlar-config:/config
scraper:
image: mhdzumair/mediafusion:v3.7.4
command: ["pipenv", "run", "python", "-m", "api.scheduler"]
env_file:
- .env
depends_on:
- mongodb
volumes:
mongo-data:
prowlar-config:
-50
View File
@@ -129,56 +129,6 @@ spec:
memory: "1Gi"
cpu: "200m"
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: scraper-deployment
spec:
replicas: 1
selector:
matchLabels:
app: scraper
template:
metadata:
labels:
app: scraper
spec:
containers:
- name: scraper
image: mhdzumair/mediafusion:v3.7.4
command: ["pipenv", "run", "python", "-m", "api.scheduler"]
env:
- name: MONGO_URI
valueFrom:
secretKeyRef:
name: mediafusion-secrets
key: MONGO_URI
- name: SECRET_KEY
valueFrom:
secretKeyRef:
name: mediafusion-secrets
key: SECRET_KEY
- name: PROWLARR_API_KEY
valueFrom:
secretKeyRef:
name: mediafusion-secrets
key: PROWLARR_API_KEY
- name: REDIS_URL
valueFrom:
secretKeyRef:
name: mediafusion-secrets
key: REDIS_URL
- name: db_max_connections
value: "30"
resources:
requests:
memory: "500Mi"
cpu: "200m"
limits:
memory: "1Gi"
cpu: "200m"
---
apiVersion: apps/v1
-51
View File
@@ -147,57 +147,6 @@ spec:
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: scraper-deployment
spec:
replicas: 1
selector:
matchLabels:
app: scraper
template:
metadata:
labels:
app: scraper
spec:
containers:
- name: scraper
image: mhdzumair/mediafusion:beta
command: ["pipenv", "run", "python", "-m", "api.scheduler"]
env:
- name: MONGO_URI
valueFrom:
secretKeyRef:
name: mediafusion-secrets
key: MONGO_URI
- name: SECRET_KEY
valueFrom:
secretKeyRef:
name: mediafusion-secrets
key: SECRET_KEY
- name: PROWLARR_API_KEY
valueFrom:
secretKeyRef:
name: mediafusion-secrets
key: PROWLARR_API_KEY
- name: REDIS_URL
valueFrom:
secretKeyRef:
name: mediafusion-secrets
key: REDIS_URL
- name: db_max_connections
value: "30"
resources:
requests:
memory: "500Mi"
cpu: "200m"
limits:
memory: "1Gi"
cpu: "200m"
---
apiVersion: apps/v1
kind: Deployment
metadata:
+18 -17
View File
@@ -50,9 +50,6 @@ class LiveTVSpider(scrapy.Spider):
)
def parse_categories(self, response):
# Process the current page immediately
yield from self.parse_page(response)
# Extract the total number of pages from the pagination text
pagination_text = response.css("div.pagination span::text").get()
total_pages = int(pagination_text.split(" ")[-1]) if pagination_text else 1
@@ -64,13 +61,16 @@ class LiveTVSpider(scrapy.Spider):
else:
base_url = base_url.rstrip("/")
# Since we've already processed the first page, start from the second page
page_urls = [f"{base_url}/page/{page}/" for page in range(2, total_pages + 1)]
# Generate URLs for all pages in reverse order, excluding the first page since it's already processed
page_urls = [f"{base_url}/page/{page}/" for page in range(total_pages, 1, -1)]
# Iterate over each subsequent page URL to scrape channels
# Iterate over each subsequent page URL in reverse order to scrape channels
for page_url in page_urls:
yield scrapy.Request(page_url, callback=self.parse_page)
# Process the first page last
yield from self.parse_page(response)
def parse_page(self, response):
channel_elements = response.css("article.item.movies")
source_name = response.css(".logo a img::attr(alt)").get(default="MHDTVWORLD")
@@ -217,13 +217,17 @@ class LiveTVSpider(scrapy.Spider):
},
)
def get_behavior_hints(self, response):
"""Generates behavior hints for requests."""
def extract_m3u8_urls(self, response):
"""Extracts M3U8 URLs using direct and fallback regex patterns."""
m3u8_urls = self.direct_pattern.findall(response.text)
if not m3u8_urls:
m3u8_urls = self.fallback_pattern.findall(response.text)
user_agent = response.request.headers.get("User-Agent").decode()
parsed_url = urlparse(response.url)
referer = f"{parsed_url.scheme}://{parsed_url.netloc}"
return {
behavior_hints = {
"notWebReady": True,
"proxyHeaders": {
"request": {
@@ -233,21 +237,15 @@ class LiveTVSpider(scrapy.Spider):
},
}
def extract_m3u8_urls(self, response):
"""Extracts M3U8 URLs using direct and fallback regex patterns."""
m3u8_urls = self.direct_pattern.findall(response.text)
if not m3u8_urls:
m3u8_urls = self.fallback_pattern.findall(response.text)
return m3u8_urls
return m3u8_urls, behavior_hints
def request_and_extract_video_url(self, response):
channel_data = response.meta.get("channel_data")
stream_title = response.meta.get("stream_title")
country_name = response.meta.get("country_name")
behavior_hints = self.get_behavior_hints(response)
# Extract M3U8 URLs
m3u8_urls = self.extract_m3u8_urls(response)
m3u8_urls, behavior_hints = self.extract_m3u8_urls(response)
if not m3u8_urls:
self.logger.error(
"No M3U8 URLs found for channel url: %s, stream title: %s",
@@ -281,6 +279,9 @@ class LiveTVSpider(scrapy.Spider):
if response.status == 200 and content_type in self.m3u8_valid_content_types:
# Content type is valid, proceed with adding the stream
if response.meta.get("redirect_times", 0) > 0:
meta["behavior_hints"]["is_redirect"] = True
stream_info = {
"name": f"{meta['stream_title']} - {meta['index']}"
if meta["index"]
+74
View File
@@ -0,0 +1,74 @@
from urllib.parse import urljoin, urlparse
import scrapy
from mediafusion_scrapy.spiders.live_tv import LiveTVSpider
class TamilUltraSpider(LiveTVSpider):
name = "tamilultra"
start_urls = ["https://tamilultra.team/"]
def extract_player_api_base(self, response):
"""Extracts the admin-ajax URL for POST requests."""
# Directly extract the URL used for admin-ajax POST requests
admin_ajax_url = response.xpath(
"//script[contains(text(), 'player_api')]/text()"
).re_first(r'"url":"([^"]+)"')
if admin_ajax_url:
# Correctly format and return the full URL
admin_ajax_full_url = urljoin(
response.url, admin_ajax_url.replace("\\/", "/")
)
return admin_ajax_full_url
else:
self.logger.error("Admin AJAX URL not found for TamilUltra.")
return None
def process_player_option(self, element, channel_data, player_api_post_url):
"""Processes each player option element to send a POST request."""
stream_title, country_name = self.extract_stream_details(element)
data_post, data_nume, data_type = (
element.attrib.get("data-post"),
element.attrib.get("data-nume"),
element.attrib.get("data-type"),
)
if all([data_post, data_nume, data_type]):
form_data = {
"action": "doo_player_ajax",
"post": data_post,
"nume": data_nume,
"type": data_type,
}
yield scrapy.FormRequest(
url=player_api_post_url,
formdata=form_data,
callback=self.parse_api_response,
meta={
"channel_data": channel_data,
"stream_title": stream_title,
"country_name": country_name,
},
)
def extract_m3u8_urls(self, response):
"""Extracts M3U8 URLs using direct and fallback regex patterns."""
query_string = urlparse(response.url).query
m3u8_urls = [urljoin(response.url, query_string)]
user_agent = response.request.headers.get("User-Agent").decode()
parsed_url = urlparse(response.url)
referer = f"{parsed_url.scheme}://{parsed_url.netloc}"
behavior_hints = {
"notWebReady": True,
"proxyHeaders": {
"request": {
"User-Agent": user_agent,
"Referer": referer,
}
},
}
return m3u8_urls, behavior_hints
+24
View File
@@ -0,0 +1,24 @@
from multiprocessing import Process
import dramatiq
from scrapy.crawler import CrawlerProcess
from scrapy.utils.project import get_project_settings
def run_spider_in_process(spider_name, *args, **kwargs):
"""
Function to start a scrapy spider in a new process.
"""
process = CrawlerProcess(get_project_settings())
process.crawl(spider_name, *args, **kwargs)
process.start()
@dramatiq.actor(priority=5, time_limit=60 * 60 * 1000)
def run_spider(spider_name: str, *args, **kwargs):
"""
Wrapper function to run the spider in a separate process.
"""
p = Process(target=run_spider_in_process, args=(spider_name, *args), kwargs=kwargs)
p.start()
p.join()
Binary file not shown.
+72
View File
@@ -0,0 +1,72 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Scraper Controls - MediaFusion Stremio Addon</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons/font/bootstrap-icons.css" rel="stylesheet">
<link href="/static/css/styles.css" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/toastr.js/latest/css/toastr.min.css" rel="stylesheet"/>
<link rel="shortcut icon" href="/static/images/mediafusion_logo.png" type="image/x-icon">
</head>
<body>
<div class="container">
<div class="row justify-content-center">
<div class="col-10 col-lg-10 col-md-8 col-sm-10 config-container">
<img class="logo" src="/static/images/mediafusion_logo.png">
<h3 class="text-center mb-5"><b>Scraper Controls</b> - Manage Scraping Tasks</h3>
<!-- Scraper Selection -->
<div class="section-container">
<h4 class="section-header">Scraper Selection</h4>
<hr class="section-divider">
<div class="mb-3">
<label for="scraperSelect" class="form-label">Select Scraper</label>
<select class="form-select" id="scraperSelect" onchange="updateFormFields()">
<option value="tamilmv">TamilMV</option>
<option value="tamilblasters">TamilBlasters</option>
<option value="scrapy">Scrapy Spider</option>
</select>
</div>
<!-- Dynamic Scraper Parameters -->
<div id="scraperParameters"></div>
</div>
<!-- API Password Configuration -->
{% if authentication_required %}
<div class="section-container">
<h4 class="section-header">API Security Configuration</h4>
<hr class="section-divider">
<div class="mb-3">
<label for="api_password">API Password:</label>
<div class="input-group">
<input class="form-control" type="password" id="api_password" name="api_password" placeholder="Enter API Password">
<button class="btn btn-outline-secondary" type="button" id="toggleApiPassword">
<span id="toggleApiPasswordIcon" class="bi bi-eye"></span>
</button>
</div>
</div>
</div>
{% endif %}
<div class="button-container mt-5">
<button type="button" class="btn btn-primary" onclick="submitScraperForm()">Run Scraper</button>
</div>
</div>
</div>
</div>
<!-- JS for Bootstrap -->
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.8/dist/umd/popper.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/toastr.js/latest/js/toastr.min.js"></script>
<script src="/static/js/scraperControl.js"></script>
</body>
</html>
+95
View File
@@ -0,0 +1,95 @@
// ---- Helper Functions ----
function showNotification(message, type = 'info') {
toastr.options = {
closeButton: true,
newestOnTop: true,
progressBar: true,
positionClass: "toast-top-center",
preventDuplicates: true,
onclick: null,
showDuration: "300",
hideDuration: "1000",
timeOut: "5000",
extendedTimeOut: "1000",
showEasing: "swing",
hideEasing: "linear",
showMethod: "fadeIn",
hideMethod: "fadeOut",
};
toastr[type](message);
}
// Function to update form fields based on scraper selection
function updateFormFields() {
const scraperType = document.getElementById('scraperSelect').value;
let parametersHtml = '';
const scraperParametersDiv = document.getElementById('scraperParameters');
if (scraperType === 'tamilmv' || scraperType === 'tamilblasters') {
parametersHtml += `
<div class="mb-3">
<label for="pages" class="form-label">Number of Pages</label>
<input type="number" class="form-control" id="pages" name="pages" value="1">
</div>
<div class="mb-3">
<label for="startPage" class="form-label">Start Page</label>
<input type="number" class="form-control" id="startPage" name="start_page" value="1">
</div>
`;
} else if (scraperType === 'scrapy') {
parametersHtml += `
<div class="mb-3">
<label for="spiderName" class="form-label">Spider Name</label>
<select class="form-select" id="spiderName" name="spider_name">
<option value="formula_tgx">Formula TGX</option>
<option value="mhdtvworld">MHDTV World</option>
<option value="mhdtvsports">MHDTV Sports</option>
<option value="tamilultra">Tamil Ultra</option>
</select>
</div>
`;
}
// Update the form fields based on the selection
scraperParametersDiv.innerHTML = parametersHtml;
}
// Function to submit the form and display the response using Toastr
async function submitScraperForm() {
const apiPassword = document.getElementById('api_password') ? document.getElementById('api_password').value : '';
const scraperType = document.getElementById('scraperSelect').value;
let payload = {
scraper_type: scraperType,
api_password: apiPassword,
pages: document.getElementById('pages') ? document.getElementById('pages').value : 1,
start_page: document.getElementById('startPage') ? document.getElementById('startPage').value : 1,
spider_name: document.getElementById('spiderName') ? document.getElementById('spiderName').value : ''
};
try {
const response = await fetch("/scraper/run", {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(payload)
});
const data = await response.json();
if (response.ok) {
showNotification(data.status, 'success');
} else {
showNotification(data.detail, 'error');
}
} catch (error) {
console.error('Error submitting scraper form:', error);
showNotification('Error submitting scraper form. Please check the console for more details.', 'error');
}
}
// Initial update for form fields on page load
document.addEventListener('DOMContentLoaded', updateFormFields);
+2 -2
View File
@@ -129,7 +129,7 @@ def get_scraper_config(site_name: str, get_key: str) -> dict:
return config.get(site_name, {}).get(get_key, {})
@dramatiq.actor(time_limit=30 * 60 * 1000)
@dramatiq.actor(time_limit=30 * 60 * 1000, priority=10)
async def update_torrent_movie_streams_metadata(info_hashes: list[str]):
"""Update torrent streams metadata."""
if not info_hashes:
@@ -153,7 +153,7 @@ async def update_torrent_movie_streams_metadata(info_hashes: list[str]):
logging.info(f"Updated {torrent_stream.id} metadata")
@dramatiq.actor(time_limit=30 * 60 * 1000)
@dramatiq.actor(time_limit=30 * 60 * 1000, priority=10)
async def update_torrent_series_streams_metadata(info_hashes: list[str]):
"""Update torrent streams metadata."""
if not info_hashes:
+2
View File
@@ -141,6 +141,7 @@ async def scrap_movies_streams_from_prowlarr(
min_backoff=2 * 60 * 1000, # 2 minutes
max_backoff=60 * 60 * 1000, # 60 minutes
retry_when=should_retry_prowlarr_scrap,
priority=100,
)
async def background_movie_title_search(video_id: str, title: str, year: str):
url = f"{settings.prowlarr_url}/api/v1/search"
@@ -189,6 +190,7 @@ async def scrap_series_streams_from_prowlarr(
min_backoff=2 * 60 * 1000, # 2 minutes
max_backoff=60 * 60 * 1000, # 60 minutes
retry_when=should_retry_prowlarr_scrap,
priority=100,
)
async def background_series_title_search(
video_id: str, title: str, season: int, episode: int
+28 -13
View File
@@ -6,7 +6,9 @@ import logging
import math
import random
import re
from multiprocessing import Process
import dramatiq
from bs4 import BeautifulSoup
from dateutil.parser import parse as dateparser
from playwright.async_api import async_playwright
@@ -221,7 +223,6 @@ async def run_scraper(
search_keyword: str = None,
scrap_with_playwright: bool = None,
):
await database.init()
if search_keyword:
await scrap_search_keyword(search_keyword)
return
@@ -242,19 +243,33 @@ async def run_scraper(
async def run_schedule_scrape(
pages: int = 1,
start_page: int = 1,
scrap_with_playwright: bool = None,
pages: int = 1, start_page: int = 1, scrap_with_playwright: bool = False
):
for language in TAMIL_BLASTER_CATALOGS:
for video_type in TAMIL_BLASTER_CATALOGS[language]:
await run_scraper(
language,
video_type,
pages=pages,
start_page=start_page,
scrap_with_playwright=scrap_with_playwright,
)
await database.init()
async with asyncio.TaskGroup() as tg:
for language in TAMIL_BLASTER_CATALOGS:
for video_type in TAMIL_BLASTER_CATALOGS[language]:
tg.create_task(
run_scraper(
language,
video_type,
pages=pages,
start_page=start_page,
scrap_with_playwright=scrap_with_playwright,
)
)
def run_schedule_scrape_sync(pages, start_page, scrap_with_playwright):
asyncio.run(run_schedule_scrape(pages, start_page, scrap_with_playwright))
@dramatiq.actor(priority=5, time_limit=60 * 60 * 1000)
def run_tamil_blasters_scraper(pages: int = 1, start_page: int = 1):
# Use a separate process to run the scraper
process = Process(target=run_schedule_scrape_sync, args=(pages, start_page, False))
process.start()
process.join()
if __name__ == "__main__":
+27 -10
View File
@@ -6,7 +6,9 @@ import logging
import math
import random
import re
from multiprocessing import Process
import dramatiq
from bs4 import BeautifulSoup
from dateutil.parser import parse as dateparser
from playwright.async_api import async_playwright
@@ -196,7 +198,6 @@ async def run_scraper(
search_keyword: str = None,
scrap_with_playwright: bool = None,
):
await database.init()
if search_keyword:
await scrap_search_keyword(search_keyword)
return
@@ -228,15 +229,31 @@ async def run_schedule_scrape(
start_page: int = 1,
scrap_with_playwright: bool = None,
):
for language in TAMIL_MV_CATALOGS:
for video_type in TAMIL_MV_CATALOGS[language]:
await run_scraper(
language,
video_type,
pages=pages,
start_page=start_page,
scrap_with_playwright=scrap_with_playwright,
)
await database.init()
async with asyncio.TaskGroup() as tg:
for language in TAMIL_MV_CATALOGS:
for video_type in TAMIL_MV_CATALOGS[language]:
tg.create_task(
run_scraper(
language,
video_type,
pages=pages,
start_page=start_page,
scrap_with_playwright=scrap_with_playwright,
)
)
def run_schedule_scrape_sync(pages, start_page, scrap_with_playwright):
asyncio.run(run_schedule_scrape(pages, start_page, scrap_with_playwright))
@dramatiq.actor(priority=5, time_limit=60 * 60 * 1000)
def run_tamilmv_scraper(pages: int = 1, start_page: int = 1):
# Use a separate process to run the scraper
process = Process(target=run_schedule_scrape_sync, args=(pages, start_page, False))
process.start()
process.join()
if __name__ == "__main__":
+4 -4
View File
@@ -177,10 +177,10 @@ async def retrieve_or_download_file(
pikpak, info_hash, stream.torrent_name, max_retries, retry_interval
)
selected_file = await find_file_in_folder_tree(pikpak, folder_id, filename)
if selected_file is None:
raise ProviderException(
"No matching file available for this torrent", "no_matching_file.mp4"
)
if selected_file is None:
raise ProviderException(
"No matching file available for this torrent", "no_matching_file.mp4"
)
return selected_file
+1 -1
View File
@@ -30,7 +30,7 @@ router = APIRouter()
@router.get("/{secret_str}/stream", tags=["streaming_provider"])
@wrappers.exclude
@wrappers.exclude_rate_limit
@wrappers.auth_required
async def streaming_provider_endpoint(
secret_str: str,
+12
View File
@@ -110,3 +110,15 @@ async def batch_process_with_circuit_breaker(
await asyncio.sleep(rate_limit_delay) # Always respect the rate limit
return results
async def get_redirector_url(url: str, headers: dict) -> str | None:
"""
Get the final URL after following all redirects.
"""
try:
async with httpx.AsyncClient() as client:
response = await client.head(url, headers=headers, follow_redirects=True)
return str(response.url)
except httpx.HTTPError as e:
return
+20 -9
View File
@@ -6,7 +6,7 @@ import requests
from imdb import Cinemagoer
from db.config import settings
from db.models import TorrentStreams, MediaFusionTVMetaData
from db.models import TorrentStreams, TVStreams
from db.schemas import Stream, UserData
from streaming_providers.alldebrid.utils import (
update_ad_cache_status,
@@ -45,6 +45,7 @@ from streaming_providers.torbox.utils import (
fetch_downloaded_info_hashes_from_torbox,
)
from utils import const
from utils.network import get_redirector_url
ia = Cinemagoer()
ADULT_CONTENT_KEYWORDS = re.compile(
@@ -311,27 +312,37 @@ def get_imdb_data(video_id: str) -> tuple[str, str]:
return movie.get("title"), movie.get("year")
def parse_tv_stream_data(tv_data: MediaFusionTVMetaData) -> list[Stream]:
async def parse_tv_stream_data(tv_streams: list[TVStreams]) -> list[Stream]:
stream_list = []
for stream in tv_data.streams:
for stream in tv_streams:
if stream.behaviorHints.get("is_redirect", False):
response = requests.get(
stream_link = await get_redirector_url(
stream.url,
headers=stream.behaviorHints["proxyHeaders"]["request"],
allow_redirects=False,
stream.behaviorHints.get("proxyHeaders", {}).get("request", {}),
)
if response.status_code == 302:
stream.url = response.headers["Location"]
if stream_link is None:
continue
stream.url = stream_link
stream_list.append(
Stream(
name="MediaFusion",
description=f"{stream.name}, {tv_data.tv_language}, {stream.source}",
description=f"📺 {stream.name}\n🌐 {stream.country}\n🔗 {stream.source}",
url=stream.url,
ytId=stream.ytId,
behaviorHints=stream.behaviorHints,
)
)
if not stream_list:
stream_list.append(
Stream(
name="MediaFusion",
description="No streams live at the moment.",
url=f"{settings.host_url}/static/exceptions/source_not_live.mp4",
behaviorHints={"notWebReady": True},
)
)
return stream_list
+21 -1
View File
@@ -3,6 +3,7 @@ import logging
from urllib.parse import urlparse
import aiohttp
import dramatiq
from aiohttp import ClientError
from db import schemas
@@ -38,7 +39,7 @@ async def validate_m3u8_url(url: str, behaviour_hint: dict) -> bool:
headers = behaviour_hint.get("proxyHeaders", {}).get("request", {})
async with aiohttp.ClientSession() as session:
try:
async with session.get(
async with session.head(
url,
allow_redirects=True,
headers=headers,
@@ -152,3 +153,22 @@ def is_video_file(filename: str) -> bool:
".yuv",
)
)
@dramatiq.actor(time_limit=30 * 60 * 1000, priority=5) # time limit is 30 minutes
async def validate_tv_streams_in_db():
"""Validate TV streams in the database."""
from db.models import TVStreams
async def validate_and_update_tv_stream(stream):
is_valid = await validate_m3u8_url(stream.url, stream.behaviorHints)
stream.is_working = is_valid
await stream.save()
logging.info(f"Stream: {stream.name}, Status: {is_valid}")
tv_streams = await TVStreams.all().to_list()
tasks = [
asyncio.create_task(validate_and_update_tv_stream(stream))
for stream in tv_streams
]
await asyncio.gather(*tasks)
+2 -2
View File
@@ -23,12 +23,12 @@ def rate_limit(limit: int, window: int, scope: str = None):
return decorator
def exclude(func):
def exclude_rate_limit(func):
@wraps(func)
async def wrapper(*args, **kwargs):
return await func(*args, **kwargs)
wrapper.exclude = True
wrapper.exclude_rate_limit = True
return wrapper