mirror of
https://github.com/Viren070/MediaFusion.git
synced 2025-12-01 23:21:11 +01:00
Add support for scraping movie streams from torrentio
This commit is contained in:
@@ -31,6 +31,7 @@ playwright-stealth = "*"
|
||||
pikpakapi = {git = "git+https://github.com/mhdzumair/PikPakAPI.git"}
|
||||
parse-torrent-title = {git = "git+https://github.com/mhdzumair/parse-torrent-title"}
|
||||
diskcache = "*"
|
||||
demagnetize = "*"
|
||||
|
||||
[dev-packages]
|
||||
pysocks = "*"
|
||||
|
||||
Generated
+224
-1
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"_meta": {
|
||||
"hash": {
|
||||
"sha256": "7c6de99d98df9c4bedf75371ac04dcf1b6073812835279f87784d5f80fe931da"
|
||||
"sha256": "b956a956a444c18919c0d7c01e8bb9cf50a3a54c987d0069f7b99d6d46afbe60"
|
||||
},
|
||||
"pipfile-spec": 6,
|
||||
"requires": {
|
||||
@@ -41,6 +41,14 @@
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==3.10.4"
|
||||
},
|
||||
"attrs": {
|
||||
"hashes": [
|
||||
"sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30",
|
||||
"sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1"
|
||||
],
|
||||
"markers": "python_version >= '3.7'",
|
||||
"version": "==23.2.0"
|
||||
},
|
||||
"beanie": {
|
||||
"hashes": [
|
||||
"sha256:2328f0f745ea8b4626a818c79ed71021a5c4362fc2c0adc48a59e6b40c5ce54b",
|
||||
@@ -183,6 +191,14 @@
|
||||
"markers": "python_version >= '3.7'",
|
||||
"version": "==8.1.7"
|
||||
},
|
||||
"click-loglevel": {
|
||||
"hashes": [
|
||||
"sha256:897070fd4bf5b503edb5a1ecc0551448647901f4fac83a01c9f00aa28ad86d60",
|
||||
"sha256:fcc98a136a96479b4768494df25017114ba1cb525dac0bed619209b3578fd4f3"
|
||||
],
|
||||
"markers": "python_version >= '3.7'",
|
||||
"version": "==0.5.0"
|
||||
},
|
||||
"cloudscraper": {
|
||||
"hashes": [
|
||||
"sha256:429c6e8aa6916d5bad5c8a5eac50f3ea53c9ac22616f6cb21b18dcc71517d0d3",
|
||||
@@ -191,6 +207,23 @@
|
||||
"index": "pypi",
|
||||
"version": "==1.2.71"
|
||||
},
|
||||
"colorlog": {
|
||||
"hashes": [
|
||||
"sha256:4ed23b05a1154294ac99f511fabe8c1d6d4364ec1f7fc989c7fb515ccc29d375",
|
||||
"sha256:fbb6fdf9d5685f2517f388fb29bb27d54e8654dd31f58bc2a3b217e967a95ca6"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==6.8.0"
|
||||
},
|
||||
"demagnetize": {
|
||||
"hashes": [
|
||||
"sha256:a3ac825bb8d949d3d1ea17fb67b48d41c22a22bf0eae6c70817042aa7db9f01c",
|
||||
"sha256:ab023ff72d03cc2ed369e94aa1d6d513e5774735c3adee9bc66e274e8207af4e"
|
||||
],
|
||||
"index": "pypi",
|
||||
"markers": "python_version >= '3.10'",
|
||||
"version": "==0.3.0"
|
||||
},
|
||||
"diskcache": {
|
||||
"hashes": [
|
||||
"sha256:2c3a3fa2743d8535d832ec61c2054a1641f41775aa7c556758a109941e33e4fc",
|
||||
@@ -218,6 +251,12 @@
|
||||
"markers": "python_version >= '3.8'",
|
||||
"version": "==0.109.0"
|
||||
},
|
||||
"flatbencode": {
|
||||
"hashes": [
|
||||
"sha256:77397d5d0108835404f6f7cad640d403c7025a11812afc7e1df4b0f92589ba77"
|
||||
],
|
||||
"version": "==0.2.1"
|
||||
},
|
||||
"greenlet": {
|
||||
"hashes": [
|
||||
"sha256:03a8f4f3430c3b3ff8d10a2a86028c660355ab637cee9333d63d66b56f09d52a",
|
||||
@@ -538,6 +577,86 @@
|
||||
"markers": "python_version >= '3.7'",
|
||||
"version": "==3.3.2"
|
||||
},
|
||||
"multidict": {
|
||||
"hashes": [
|
||||
"sha256:01a3a55bd90018c9c080fbb0b9f4891db37d148a0a18722b42f94694f8b6d4c9",
|
||||
"sha256:0b1a97283e0c85772d613878028fec909f003993e1007eafa715b24b377cb9b8",
|
||||
"sha256:0dfad7a5a1e39c53ed00d2dd0c2e36aed4650936dc18fd9a1826a5ae1cad6f03",
|
||||
"sha256:11bdf3f5e1518b24530b8241529d2050014c884cf18b6fc69c0c2b30ca248710",
|
||||
"sha256:1502e24330eb681bdaa3eb70d6358e818e8e8f908a22a1851dfd4e15bc2f8161",
|
||||
"sha256:16ab77bbeb596e14212e7bab8429f24c1579234a3a462105cda4a66904998664",
|
||||
"sha256:16d232d4e5396c2efbbf4f6d4df89bfa905eb0d4dc5b3549d872ab898451f569",
|
||||
"sha256:21a12c4eb6ddc9952c415f24eef97e3e55ba3af61f67c7bc388dcdec1404a067",
|
||||
"sha256:27c523fbfbdfd19c6867af7346332b62b586eed663887392cff78d614f9ec313",
|
||||
"sha256:281af09f488903fde97923c7744bb001a9b23b039a909460d0f14edc7bf59706",
|
||||
"sha256:33029f5734336aa0d4c0384525da0387ef89148dc7191aae00ca5fb23d7aafc2",
|
||||
"sha256:3601a3cece3819534b11d4efc1eb76047488fddd0c85a3948099d5da4d504636",
|
||||
"sha256:3666906492efb76453c0e7b97f2cf459b0682e7402c0489a95484965dbc1da49",
|
||||
"sha256:36c63aaa167f6c6b04ef2c85704e93af16c11d20de1d133e39de6a0e84582a93",
|
||||
"sha256:39ff62e7d0f26c248b15e364517a72932a611a9b75f35b45be078d81bdb86603",
|
||||
"sha256:43644e38f42e3af682690876cff722d301ac585c5b9e1eacc013b7a3f7b696a0",
|
||||
"sha256:4372381634485bec7e46718edc71528024fcdc6f835baefe517b34a33c731d60",
|
||||
"sha256:458f37be2d9e4c95e2d8866a851663cbc76e865b78395090786f6cd9b3bbf4f4",
|
||||
"sha256:45e1ecb0379bfaab5eef059f50115b54571acfbe422a14f668fc8c27ba410e7e",
|
||||
"sha256:4b9d9e4e2b37daddb5c23ea33a3417901fa7c7b3dee2d855f63ee67a0b21e5b1",
|
||||
"sha256:4ceef517eca3e03c1cceb22030a3e39cb399ac86bff4e426d4fc6ae49052cc60",
|
||||
"sha256:4d1a3d7ef5e96b1c9e92f973e43aa5e5b96c659c9bc3124acbbd81b0b9c8a951",
|
||||
"sha256:4dcbb0906e38440fa3e325df2359ac6cb043df8e58c965bb45f4e406ecb162cc",
|
||||
"sha256:509eac6cf09c794aa27bcacfd4d62c885cce62bef7b2c3e8b2e49d365b5003fe",
|
||||
"sha256:52509b5be062d9eafc8170e53026fbc54cf3b32759a23d07fd935fb04fc22d95",
|
||||
"sha256:52f2dffc8acaba9a2f27174c41c9e57f60b907bb9f096b36b1a1f3be71c6284d",
|
||||
"sha256:574b7eae1ab267e5f8285f0fe881f17efe4b98c39a40858247720935b893bba8",
|
||||
"sha256:5979b5632c3e3534e42ca6ff856bb24b2e3071b37861c2c727ce220d80eee9ed",
|
||||
"sha256:59d43b61c59d82f2effb39a93c48b845efe23a3852d201ed2d24ba830d0b4cf2",
|
||||
"sha256:5a4dcf02b908c3b8b17a45fb0f15b695bf117a67b76b7ad18b73cf8e92608775",
|
||||
"sha256:5cad9430ab3e2e4fa4a2ef4450f548768400a2ac635841bc2a56a2052cdbeb87",
|
||||
"sha256:5fc1b16f586f049820c5c5b17bb4ee7583092fa0d1c4e28b5239181ff9532e0c",
|
||||
"sha256:62501642008a8b9871ddfccbf83e4222cf8ac0d5aeedf73da36153ef2ec222d2",
|
||||
"sha256:64bdf1086b6043bf519869678f5f2757f473dee970d7abf6da91ec00acb9cb98",
|
||||
"sha256:64da238a09d6039e3bd39bb3aee9c21a5e34f28bfa5aa22518581f910ff94af3",
|
||||
"sha256:666daae833559deb2d609afa4490b85830ab0dfca811a98b70a205621a6109fe",
|
||||
"sha256:67040058f37a2a51ed8ea8f6b0e6ee5bd78ca67f169ce6122f3e2ec80dfe9b78",
|
||||
"sha256:6748717bb10339c4760c1e63da040f5f29f5ed6e59d76daee30305894069a660",
|
||||
"sha256:6b181d8c23da913d4ff585afd1155a0e1194c0b50c54fcfe286f70cdaf2b7176",
|
||||
"sha256:6ed5f161328b7df384d71b07317f4d8656434e34591f20552c7bcef27b0ab88e",
|
||||
"sha256:7582a1d1030e15422262de9f58711774e02fa80df0d1578995c76214f6954988",
|
||||
"sha256:7d18748f2d30f94f498e852c67d61261c643b349b9d2a581131725595c45ec6c",
|
||||
"sha256:7d6ae9d593ef8641544d6263c7fa6408cc90370c8cb2bbb65f8d43e5b0351d9c",
|
||||
"sha256:81a4f0b34bd92df3da93315c6a59034df95866014ac08535fc819f043bfd51f0",
|
||||
"sha256:8316a77808c501004802f9beebde51c9f857054a0c871bd6da8280e718444449",
|
||||
"sha256:853888594621e6604c978ce2a0444a1e6e70c8d253ab65ba11657659dcc9100f",
|
||||
"sha256:99b76c052e9f1bc0721f7541e5e8c05db3941eb9ebe7b8553c625ef88d6eefde",
|
||||
"sha256:a2e4369eb3d47d2034032a26c7a80fcb21a2cb22e1173d761a162f11e562caa5",
|
||||
"sha256:ab55edc2e84460694295f401215f4a58597f8f7c9466faec545093045476327d",
|
||||
"sha256:af048912e045a2dc732847d33821a9d84ba553f5c5f028adbd364dd4765092ac",
|
||||
"sha256:b1a2eeedcead3a41694130495593a559a668f382eee0727352b9a41e1c45759a",
|
||||
"sha256:b1e8b901e607795ec06c9e42530788c45ac21ef3aaa11dbd0c69de543bfb79a9",
|
||||
"sha256:b41156839806aecb3641f3208c0dafd3ac7775b9c4c422d82ee2a45c34ba81ca",
|
||||
"sha256:b692f419760c0e65d060959df05f2a531945af31fda0c8a3b3195d4efd06de11",
|
||||
"sha256:bc779e9e6f7fda81b3f9aa58e3a6091d49ad528b11ed19f6621408806204ad35",
|
||||
"sha256:bf6774e60d67a9efe02b3616fee22441d86fab4c6d335f9d2051d19d90a40063",
|
||||
"sha256:c048099e4c9e9d615545e2001d3d8a4380bd403e1a0578734e0d31703d1b0c0b",
|
||||
"sha256:c5cb09abb18c1ea940fb99360ea0396f34d46566f157122c92dfa069d3e0e982",
|
||||
"sha256:cc8e1d0c705233c5dd0c5e6460fbad7827d5d36f310a0fadfd45cc3029762258",
|
||||
"sha256:d5e3fc56f88cc98ef8139255cf8cd63eb2c586531e43310ff859d6bb3a6b51f1",
|
||||
"sha256:d6aa0418fcc838522256761b3415822626f866758ee0bc6632c9486b179d0b52",
|
||||
"sha256:d6c254ba6e45d8e72739281ebc46ea5eb5f101234f3ce171f0e9f5cc86991480",
|
||||
"sha256:d6d635d5209b82a3492508cf5b365f3446afb65ae7ebd755e70e18f287b0adf7",
|
||||
"sha256:dcfe792765fab89c365123c81046ad4103fcabbc4f56d1c1997e6715e8015461",
|
||||
"sha256:ddd3915998d93fbcd2566ddf9cf62cdb35c9e093075f862935573d265cf8f65d",
|
||||
"sha256:ddff9c4e225a63a5afab9dd15590432c22e8057e1a9a13d28ed128ecf047bbdc",
|
||||
"sha256:e41b7e2b59679edfa309e8db64fdf22399eec4b0b24694e1b2104fb789207779",
|
||||
"sha256:e69924bfcdda39b722ef4d9aa762b2dd38e4632b3641b1d9a57ca9cd18f2f83a",
|
||||
"sha256:ea20853c6dbbb53ed34cb4d080382169b6f4554d394015f1bef35e881bf83547",
|
||||
"sha256:ee2a1ece51b9b9e7752e742cfb661d2a29e7bcdba2d27e66e28a99f1890e4fa0",
|
||||
"sha256:eeb6dcc05e911516ae3d1f207d4b0520d07f54484c49dfc294d6e7d63b734171",
|
||||
"sha256:f70b98cd94886b49d91170ef23ec5c0e8ebb6f242d734ed7ed677b24d50c82cf",
|
||||
"sha256:fc35cb4676846ef752816d5be2193a1e8367b4c1397b74a565a9d0389c433a1d",
|
||||
"sha256:ff959bee35038c4624250473988b24f846cbeb2c6639de3602c073f10410ceba"
|
||||
],
|
||||
"markers": "python_version >= '3.7'",
|
||||
"version": "==6.0.4"
|
||||
},
|
||||
"parse-torrent-title": {
|
||||
"git": "git+https://github.com/mhdzumair/parse-torrent-title",
|
||||
"ref": "c3002b767de54ffdcf633e60a16567b49cc7e0e6"
|
||||
@@ -1225,6 +1344,14 @@
|
||||
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==0.10.2"
|
||||
},
|
||||
"torf": {
|
||||
"hashes": [
|
||||
"sha256:e84ec73cd81cb2d56e36b9f4604806f16ee0c70aeafadfe2f31ca3266ca03a87",
|
||||
"sha256:f846ef58b6b00bc3b6a5c25bf225c59149ee871738d87a20a8953f620b77671c"
|
||||
],
|
||||
"markers": "python_version >= '3.7'",
|
||||
"version": "==4.2.4"
|
||||
},
|
||||
"typing-extensions": {
|
||||
"hashes": [
|
||||
"sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783",
|
||||
@@ -1461,6 +1588,102 @@
|
||||
"sha256:ffefa1374cd508d633646d51a8e9277763a9b78ae71324183693959cf94635a7"
|
||||
],
|
||||
"version": "==12.0"
|
||||
},
|
||||
"yarl": {
|
||||
"hashes": [
|
||||
"sha256:008d3e808d03ef28542372d01057fd09168419cdc8f848efe2804f894ae03e51",
|
||||
"sha256:03caa9507d3d3c83bca08650678e25364e1843b484f19986a527630ca376ecce",
|
||||
"sha256:07574b007ee20e5c375a8fe4a0789fad26db905f9813be0f9fef5a68080de559",
|
||||
"sha256:09efe4615ada057ba2d30df871d2f668af661e971dfeedf0c159927d48bbeff0",
|
||||
"sha256:0d2454f0aef65ea81037759be5ca9947539667eecebca092733b2eb43c965a81",
|
||||
"sha256:0e9d124c191d5b881060a9e5060627694c3bdd1fe24c5eecc8d5d7d0eb6faabc",
|
||||
"sha256:18580f672e44ce1238b82f7fb87d727c4a131f3a9d33a5e0e82b793362bf18b4",
|
||||
"sha256:1f23e4fe1e8794f74b6027d7cf19dc25f8b63af1483d91d595d4a07eca1fb26c",
|
||||
"sha256:206a55215e6d05dbc6c98ce598a59e6fbd0c493e2de4ea6cc2f4934d5a18d130",
|
||||
"sha256:23d32a2594cb5d565d358a92e151315d1b2268bc10f4610d098f96b147370136",
|
||||
"sha256:26a1dc6285e03f3cc9e839a2da83bcbf31dcb0d004c72d0730e755b33466c30e",
|
||||
"sha256:29e0f83f37610f173eb7e7b5562dd71467993495e568e708d99e9d1944f561ec",
|
||||
"sha256:2b134fd795e2322b7684155b7855cc99409d10b2e408056db2b93b51a52accc7",
|
||||
"sha256:2d47552b6e52c3319fede1b60b3de120fe83bde9b7bddad11a69fb0af7db32f1",
|
||||
"sha256:357495293086c5b6d34ca9616a43d329317feab7917518bc97a08f9e55648455",
|
||||
"sha256:35a2b9396879ce32754bd457d31a51ff0a9d426fd9e0e3c33394bf4b9036b099",
|
||||
"sha256:3777ce5536d17989c91696db1d459574e9a9bd37660ea7ee4d3344579bb6f129",
|
||||
"sha256:3986b6f41ad22988e53d5778f91855dc0399b043fc8946d4f2e68af22ee9ff10",
|
||||
"sha256:44d8ffbb9c06e5a7f529f38f53eda23e50d1ed33c6c869e01481d3fafa6b8142",
|
||||
"sha256:49a180c2e0743d5d6e0b4d1a9e5f633c62eca3f8a86ba5dd3c471060e352ca98",
|
||||
"sha256:4aa9741085f635934f3a2583e16fcf62ba835719a8b2b28fb2917bb0537c1dfa",
|
||||
"sha256:4b21516d181cd77ebd06ce160ef8cc2a5e9ad35fb1c5930882baff5ac865eee7",
|
||||
"sha256:4b3c1ffe10069f655ea2d731808e76e0f452fc6c749bea04781daf18e6039525",
|
||||
"sha256:4c7d56b293cc071e82532f70adcbd8b61909eec973ae9d2d1f9b233f3d943f2c",
|
||||
"sha256:4e9035df8d0880b2f1c7f5031f33f69e071dfe72ee9310cfc76f7b605958ceb9",
|
||||
"sha256:54525ae423d7b7a8ee81ba189f131054defdb122cde31ff17477951464c1691c",
|
||||
"sha256:549d19c84c55d11687ddbd47eeb348a89df9cb30e1993f1b128f4685cd0ebbf8",
|
||||
"sha256:54beabb809ffcacbd9d28ac57b0db46e42a6e341a030293fb3185c409e626b8b",
|
||||
"sha256:566db86717cf8080b99b58b083b773a908ae40f06681e87e589a976faf8246bf",
|
||||
"sha256:5a2e2433eb9344a163aced6a5f6c9222c0786e5a9e9cac2c89f0b28433f56e23",
|
||||
"sha256:5aef935237d60a51a62b86249839b51345f47564208c6ee615ed2a40878dccdd",
|
||||
"sha256:604f31d97fa493083ea21bd9b92c419012531c4e17ea6da0f65cacdcf5d0bd27",
|
||||
"sha256:63b20738b5aac74e239622d2fe30df4fca4942a86e31bf47a81a0e94c14df94f",
|
||||
"sha256:686a0c2f85f83463272ddffd4deb5e591c98aac1897d65e92319f729c320eece",
|
||||
"sha256:6a962e04b8f91f8c4e5917e518d17958e3bdee71fd1d8b88cdce74dd0ebbf434",
|
||||
"sha256:6ad6d10ed9b67a382b45f29ea028f92d25bc0bc1daf6c5b801b90b5aa70fb9ec",
|
||||
"sha256:6f5cb257bc2ec58f437da2b37a8cd48f666db96d47b8a3115c29f316313654ff",
|
||||
"sha256:6fe79f998a4052d79e1c30eeb7d6c1c1056ad33300f682465e1b4e9b5a188b78",
|
||||
"sha256:7855426dfbddac81896b6e533ebefc0af2f132d4a47340cee6d22cac7190022d",
|
||||
"sha256:7d5aaac37d19b2904bb9dfe12cdb08c8443e7ba7d2852894ad448d4b8f442863",
|
||||
"sha256:801e9264d19643548651b9db361ce3287176671fb0117f96b5ac0ee1c3530d53",
|
||||
"sha256:81eb57278deb6098a5b62e88ad8281b2ba09f2f1147c4767522353eaa6260b31",
|
||||
"sha256:824d6c50492add5da9374875ce72db7a0733b29c2394890aef23d533106e2b15",
|
||||
"sha256:8397a3817d7dcdd14bb266283cd1d6fc7264a48c186b986f32e86d86d35fbac5",
|
||||
"sha256:848cd2a1df56ddbffeb375535fb62c9d1645dde33ca4d51341378b3f5954429b",
|
||||
"sha256:84fc30f71689d7fc9168b92788abc977dc8cefa806909565fc2951d02f6b7d57",
|
||||
"sha256:8619d6915b3b0b34420cf9b2bb6d81ef59d984cb0fde7544e9ece32b4b3043c3",
|
||||
"sha256:8a854227cf581330ffa2c4824d96e52ee621dd571078a252c25e3a3b3d94a1b1",
|
||||
"sha256:8be9e837ea9113676e5754b43b940b50cce76d9ed7d2461df1af39a8ee674d9f",
|
||||
"sha256:928cecb0ef9d5a7946eb6ff58417ad2fe9375762382f1bf5c55e61645f2c43ad",
|
||||
"sha256:957b4774373cf6f709359e5c8c4a0af9f6d7875db657adb0feaf8d6cb3c3964c",
|
||||
"sha256:992f18e0ea248ee03b5a6e8b3b4738850ae7dbb172cc41c966462801cbf62cf7",
|
||||
"sha256:9fc5fc1eeb029757349ad26bbc5880557389a03fa6ada41703db5e068881e5f2",
|
||||
"sha256:a00862fb23195b6b8322f7d781b0dc1d82cb3bcac346d1e38689370cc1cc398b",
|
||||
"sha256:a3a6ed1d525bfb91b3fc9b690c5a21bb52de28c018530ad85093cc488bee2dd2",
|
||||
"sha256:a6327976c7c2f4ee6816eff196e25385ccc02cb81427952414a64811037bbc8b",
|
||||
"sha256:a7409f968456111140c1c95301cadf071bd30a81cbd7ab829169fb9e3d72eae9",
|
||||
"sha256:a825ec844298c791fd28ed14ed1bffc56a98d15b8c58a20e0e08c1f5f2bea1be",
|
||||
"sha256:a8c1df72eb746f4136fe9a2e72b0c9dc1da1cbd23b5372f94b5820ff8ae30e0e",
|
||||
"sha256:a9bd00dc3bc395a662900f33f74feb3e757429e545d831eef5bb280252631984",
|
||||
"sha256:aa102d6d280a5455ad6a0f9e6d769989638718e938a6a0a2ff3f4a7ff8c62cc4",
|
||||
"sha256:aaaea1e536f98754a6e5c56091baa1b6ce2f2700cc4a00b0d49eca8dea471074",
|
||||
"sha256:ad4d7a90a92e528aadf4965d685c17dacff3df282db1121136c382dc0b6014d2",
|
||||
"sha256:b8477c1ee4bd47c57d49621a062121c3023609f7a13b8a46953eb6c9716ca392",
|
||||
"sha256:ba6f52cbc7809cd8d74604cce9c14868306ae4aa0282016b641c661f981a6e91",
|
||||
"sha256:bac8d525a8dbc2a1507ec731d2867025d11ceadcb4dd421423a5d42c56818541",
|
||||
"sha256:bef596fdaa8f26e3d66af846bbe77057237cb6e8efff8cd7cc8dff9a62278bbf",
|
||||
"sha256:c0ec0ed476f77db9fb29bca17f0a8fcc7bc97ad4c6c1d8959c507decb22e8572",
|
||||
"sha256:c38c9ddb6103ceae4e4498f9c08fac9b590c5c71b0370f98714768e22ac6fa66",
|
||||
"sha256:c7224cab95645c7ab53791022ae77a4509472613e839dab722a72abe5a684575",
|
||||
"sha256:c74018551e31269d56fab81a728f683667e7c28c04e807ba08f8c9e3bba32f14",
|
||||
"sha256:ca06675212f94e7a610e85ca36948bb8fc023e458dd6c63ef71abfd482481aa5",
|
||||
"sha256:d1d2532b340b692880261c15aee4dc94dd22ca5d61b9db9a8a361953d36410b1",
|
||||
"sha256:d25039a474c4c72a5ad4b52495056f843a7ff07b632c1b92ea9043a3d9950f6e",
|
||||
"sha256:d5ff2c858f5f6a42c2a8e751100f237c5e869cbde669a724f2062d4c4ef93551",
|
||||
"sha256:d7d7f7de27b8944f1fee2c26a88b4dabc2409d2fea7a9ed3df79b67277644e17",
|
||||
"sha256:d7eeb6d22331e2fd42fce928a81c697c9ee2d51400bd1a28803965883e13cead",
|
||||
"sha256:d8a1c6c0be645c745a081c192e747c5de06e944a0d21245f4cf7c05e457c36e0",
|
||||
"sha256:d8b889777de69897406c9fb0b76cdf2fd0f31267861ae7501d93003d55f54fbe",
|
||||
"sha256:d9e09c9d74f4566e905a0b8fa668c58109f7624db96a2171f21747abc7524234",
|
||||
"sha256:db8e58b9d79200c76956cefd14d5c90af54416ff5353c5bfd7cbe58818e26ef0",
|
||||
"sha256:ddb2a5c08a4eaaba605340fdee8fc08e406c56617566d9643ad8bf6852778fc7",
|
||||
"sha256:e0381b4ce23ff92f8170080c97678040fc5b08da85e9e292292aba67fdac6c34",
|
||||
"sha256:e23a6d84d9d1738dbc6e38167776107e63307dfc8ad108e580548d1f2c587f42",
|
||||
"sha256:e516dc8baf7b380e6c1c26792610230f37147bb754d6426462ab115a02944385",
|
||||
"sha256:ea65804b5dc88dacd4a40279af0cdadcfe74b3e5b4c897aa0d81cf86927fee78",
|
||||
"sha256:ec61d826d80fc293ed46c9dd26995921e3a82146feacd952ef0757236fc137be",
|
||||
"sha256:ee04010f26d5102399bd17f8df8bc38dc7ccd7701dc77f4a68c5b8d733406958",
|
||||
"sha256:f3bc6af6e2b8f92eced34ef6a96ffb248e863af20ef4fde9448cc8c9b858b749",
|
||||
"sha256:f7d6b36dd2e029b6bcb8a13cf19664c7b8e19ab3a58e0fefbb5b8461447ed5ec"
|
||||
],
|
||||
"markers": "python_version >= '3.7'",
|
||||
"version": "==1.9.4"
|
||||
}
|
||||
},
|
||||
"develop": {
|
||||
|
||||
+19
-5
@@ -5,7 +5,15 @@ from typing import Literal
|
||||
from apscheduler.schedulers.asyncio import AsyncIOScheduler
|
||||
from apscheduler.triggers.cron import CronTrigger
|
||||
from diskcache import Cache
|
||||
from fastapi import FastAPI, Request, Response, Depends, HTTPException, status
|
||||
from fastapi import (
|
||||
FastAPI,
|
||||
Request,
|
||||
Response,
|
||||
Depends,
|
||||
HTTPException,
|
||||
status,
|
||||
BackgroundTasks,
|
||||
)
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.responses import RedirectResponse, FileResponse, StreamingResponse
|
||||
from fastapi.templating import Jinja2Templates
|
||||
@@ -58,8 +66,9 @@ cache = Cache(
|
||||
|
||||
|
||||
@app.on_event("startup")
|
||||
async def init_db():
|
||||
async def init_server():
|
||||
await database.init()
|
||||
await torrent.init_best_trackers()
|
||||
|
||||
|
||||
@app.on_event("startup")
|
||||
@@ -325,6 +334,7 @@ async def get_streams(
|
||||
catalog_type: Literal["movie", "series", "tv"],
|
||||
video_id: str,
|
||||
response: Response,
|
||||
background_tasks: BackgroundTasks,
|
||||
secret_str: str = None,
|
||||
season: int = None,
|
||||
episode: int = None,
|
||||
@@ -333,7 +343,9 @@ async def get_streams(
|
||||
response.headers.update(headers)
|
||||
|
||||
if catalog_type == "movie":
|
||||
fetched_streams = await crud.get_movie_streams(user_data, secret_str, video_id)
|
||||
fetched_streams = await crud.get_movie_streams(
|
||||
user_data, secret_str, video_id, background_tasks
|
||||
)
|
||||
elif catalog_type == "series":
|
||||
fetched_streams = await crud.get_series_streams(
|
||||
user_data, secret_str, video_id, season, episode
|
||||
@@ -370,7 +382,9 @@ async def streaming_provider_endpoint(
|
||||
if not stream:
|
||||
raise HTTPException(status_code=400, detail="Stream not found.")
|
||||
|
||||
magnet_link = torrent.convert_info_hash_to_magnet(info_hash, stream.announce_list)
|
||||
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
|
||||
@@ -382,7 +396,7 @@ async def streaming_provider_endpoint(
|
||||
)
|
||||
elif user_data.streaming_provider.service == "realdebrid":
|
||||
video_url = get_direct_link_from_realdebrid(
|
||||
info_hash, magnet_link, user_data, filename, 1, 0
|
||||
info_hash, magnet_link, user_data, filename, stream.file_index, 1, 0
|
||||
)
|
||||
elif user_data.streaming_provider.service == "alldebrid":
|
||||
video_url = get_direct_link_from_alldebrid(
|
||||
|
||||
+44
-31
@@ -1,22 +1,25 @@
|
||||
import hashlib
|
||||
import logging
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Optional
|
||||
from uuid import uuid4
|
||||
|
||||
from beanie import WriteRules
|
||||
from beanie.operators import In
|
||||
from fastapi import BackgroundTasks
|
||||
|
||||
from db import schemas, models
|
||||
from db.config import settings
|
||||
from db.models import (
|
||||
MediaFusionMovieMetaData,
|
||||
MediaFusionSeriesMetaData,
|
||||
Streams,
|
||||
TorrentStreams,
|
||||
Season,
|
||||
Episode,
|
||||
MediaFusionTVMetaData,
|
||||
)
|
||||
from db.schemas import Stream, MetaIdProjection
|
||||
from scrappers.torrentio import scrap_streams_from_torrentio
|
||||
from utils.parser import (
|
||||
parse_stream_data,
|
||||
get_catalogs,
|
||||
@@ -113,12 +116,28 @@ async def get_tv_data_by_id(
|
||||
return tv_data
|
||||
|
||||
|
||||
async def get_movie_streams(user_data, secret_str: str, video_id: str) -> list[Stream]:
|
||||
movie_data = await get_movie_data_by_id(video_id, True)
|
||||
if not movie_data:
|
||||
return []
|
||||
async def get_movie_streams(
|
||||
user_data, secret_str: str, video_id: str, background_tasks: BackgroundTasks
|
||||
) -> list[Stream]:
|
||||
streams = (
|
||||
await TorrentStreams.find({"meta_id": video_id})
|
||||
.sort(-TorrentStreams.updated_at)
|
||||
.to_list()
|
||||
)
|
||||
last_torrentio_stream = next(
|
||||
(stream for stream in streams if stream.source == "Torrentio"), None
|
||||
)
|
||||
|
||||
return await parse_stream_data(movie_data.streams, user_data, secret_str)
|
||||
if video_id.startswith("tt") and "torrentio_streams" in user_data.selected_catalogs:
|
||||
if (
|
||||
last_torrentio_stream is None
|
||||
or last_torrentio_stream.updated_at < datetime.now() - timedelta(days=1)
|
||||
):
|
||||
streams.extend(
|
||||
await scrap_streams_from_torrentio(video_id, "movie", background_tasks)
|
||||
)
|
||||
|
||||
return await parse_stream_data(streams, user_data, secret_str)
|
||||
|
||||
|
||||
async def get_series_streams(
|
||||
@@ -181,7 +200,7 @@ async def get_series_meta(meta_id: str):
|
||||
|
||||
# Loop through streams to populate the videos list
|
||||
for stream in series_data.streams:
|
||||
stream: Streams
|
||||
stream: TorrentStreams
|
||||
if stream.season: # Ensure the stream has season data
|
||||
for episode in stream.season.episodes:
|
||||
stream_id = (
|
||||
@@ -256,9 +275,7 @@ async def save_movie_metadata(metadata: dict):
|
||||
meta_id = existing_movie.id
|
||||
|
||||
# Determine file index for the main movie file (largest file)
|
||||
largest_file = max(
|
||||
metadata["torrent_metadata"]["file_data"], key=lambda x: x["size"]
|
||||
)
|
||||
largest_file = max(metadata["file_data"], key=lambda x: x["size"])
|
||||
|
||||
if "language" in metadata:
|
||||
languages = (
|
||||
@@ -270,11 +287,11 @@ async def save_movie_metadata(metadata: dict):
|
||||
languages = [metadata["scrap_language"]]
|
||||
|
||||
# Create the stream object
|
||||
new_stream = Streams(
|
||||
id=metadata["torrent_metadata"]["info_hash"],
|
||||
torrent_name=metadata["torrent_metadata"]["torrent_name"],
|
||||
announce_list=metadata["torrent_metadata"]["announce_list"],
|
||||
size=metadata["torrent_metadata"]["total_size"],
|
||||
new_stream = TorrentStreams(
|
||||
id=metadata["info_hash"],
|
||||
torrent_name=metadata["torrent_name"],
|
||||
announce_list=metadata["announce_list"],
|
||||
size=metadata["total_size"],
|
||||
filename=largest_file["filename"],
|
||||
file_index=largest_file["index"],
|
||||
languages=languages,
|
||||
@@ -286,6 +303,7 @@ async def save_movie_metadata(metadata: dict):
|
||||
source=metadata["source"],
|
||||
catalog=get_catalogs(metadata["catalog"], languages),
|
||||
created_at=metadata["created_at"],
|
||||
meta_id=meta_id,
|
||||
)
|
||||
|
||||
if existing_movie:
|
||||
@@ -349,11 +367,7 @@ async def save_series_metadata(metadata: dict):
|
||||
logging.info("Added series %s", series.title)
|
||||
|
||||
existing_stream = next(
|
||||
(
|
||||
s
|
||||
for s in series.streams
|
||||
if s.id == metadata["torrent_metadata"]["info_hash"]
|
||||
),
|
||||
(s for s in series.streams if s.id == metadata["info_hash"]),
|
||||
None,
|
||||
)
|
||||
if existing_stream:
|
||||
@@ -361,8 +375,6 @@ async def save_series_metadata(metadata: dict):
|
||||
logging.info("Stream already exists for series %s", series.title)
|
||||
return
|
||||
|
||||
season_number = metadata["season"]
|
||||
|
||||
# Extract episodes
|
||||
episodes = [
|
||||
Episode(
|
||||
@@ -371,7 +383,7 @@ async def save_series_metadata(metadata: dict):
|
||||
size=file["size"],
|
||||
file_index=file["index"],
|
||||
)
|
||||
for file in metadata["torrent_metadata"]["file_data"]
|
||||
for file in metadata["file_data"]
|
||||
if file["episode"]
|
||||
]
|
||||
|
||||
@@ -386,11 +398,11 @@ async def save_series_metadata(metadata: dict):
|
||||
languages = [metadata["scrap_language"]]
|
||||
|
||||
# Create the stream
|
||||
stream = Streams(
|
||||
id=metadata["torrent_metadata"]["info_hash"],
|
||||
torrent_name=metadata["torrent_metadata"]["torrent_name"],
|
||||
announce_list=metadata["torrent_metadata"]["announce_list"],
|
||||
size=metadata["torrent_metadata"]["total_size"],
|
||||
stream = TorrentStreams(
|
||||
id=metadata["info_hash"],
|
||||
torrent_name=metadata["torrent_name"],
|
||||
announce_list=metadata["announce_list"],
|
||||
size=metadata["total_size"],
|
||||
languages=languages,
|
||||
resolution=metadata.get("resolution"),
|
||||
codec=metadata.get("codec"),
|
||||
@@ -400,7 +412,8 @@ async def save_series_metadata(metadata: dict):
|
||||
source=metadata["source"],
|
||||
catalog=get_catalogs(metadata["catalog"], languages),
|
||||
created_at=metadata["created_at"],
|
||||
season=Season(season_number=season_number, episodes=episodes),
|
||||
season=Season(season_number=metadata["season"], episodes=episodes),
|
||||
meta_id=series.id,
|
||||
)
|
||||
|
||||
# Add the stream to the series
|
||||
@@ -443,8 +456,8 @@ async def process_search_query(search_query: str, catalog_type: str) -> dict:
|
||||
return {"metas": metas}
|
||||
|
||||
|
||||
async def get_stream_by_info_hash(info_hash: str) -> Streams | None:
|
||||
stream = await Streams.get(info_hash)
|
||||
async def get_stream_by_info_hash(info_hash: str) -> TorrentStreams | None:
|
||||
stream = await TorrentStreams.get(info_hash)
|
||||
return stream
|
||||
|
||||
|
||||
|
||||
+2
-2
@@ -5,7 +5,7 @@ from db.config import settings
|
||||
from db.models import (
|
||||
MediaFusionSeriesMetaData,
|
||||
MediaFusionMovieMetaData,
|
||||
Streams,
|
||||
TorrentStreams,
|
||||
TVStreams,
|
||||
MediaFusionTVMetaData,
|
||||
)
|
||||
@@ -21,7 +21,7 @@ async def init():
|
||||
document_models=[
|
||||
MediaFusionMovieMetaData,
|
||||
MediaFusionSeriesMetaData,
|
||||
Streams,
|
||||
TorrentStreams,
|
||||
TVStreams,
|
||||
MediaFusionTVMetaData,
|
||||
],
|
||||
|
||||
+7
-5
@@ -1,10 +1,9 @@
|
||||
import hashlib
|
||||
from datetime import datetime
|
||||
from typing import Optional, Any
|
||||
|
||||
import pymongo
|
||||
from beanie import Document, Link
|
||||
from pydantic import BaseModel, Field, computed_field
|
||||
from beanie import Document, Link, BackLink
|
||||
from pydantic import BaseModel, Field
|
||||
from pymongo import IndexModel, ASCENDING
|
||||
|
||||
|
||||
@@ -20,7 +19,7 @@ class Season(BaseModel):
|
||||
episodes: list[Episode]
|
||||
|
||||
|
||||
class Streams(Document):
|
||||
class TorrentStreams(Document):
|
||||
id: str
|
||||
torrent_name: str
|
||||
size: int
|
||||
@@ -32,6 +31,7 @@ class Streams(Document):
|
||||
source: str
|
||||
catalog: list[str]
|
||||
created_at: datetime = Field(default_factory=datetime.now)
|
||||
updated_at: Optional[datetime] = None
|
||||
resolution: Optional[str]
|
||||
codec: Optional[str]
|
||||
quality: Optional[str]
|
||||
@@ -39,6 +39,7 @@ class Streams(Document):
|
||||
encoder: Optional[str]
|
||||
seeders: Optional[int] = None
|
||||
cached: Optional[bool] = None
|
||||
meta_id: Optional[str] = None
|
||||
|
||||
def get_episode(self, season_number: int, episode_number: int) -> Optional[Episode]:
|
||||
"""
|
||||
@@ -58,6 +59,7 @@ class TVStreams(Document):
|
||||
source: str
|
||||
behaviorHints: dict[str, Any] | None = None
|
||||
created_at: datetime = Field(default_factory=datetime.now)
|
||||
meta_id: Optional[str] = None
|
||||
|
||||
|
||||
class MediaFusionMetaData(Document):
|
||||
@@ -67,7 +69,7 @@ class MediaFusionMetaData(Document):
|
||||
poster: str
|
||||
is_poster_working: Optional[bool] = True
|
||||
background: Optional[str] = None
|
||||
streams: list[Link[Streams]]
|
||||
streams: list[Link[TorrentStreams]]
|
||||
type: str
|
||||
|
||||
class Settings:
|
||||
|
||||
Binary file not shown.
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"id": "mhdzumair.addons.mediafusion",
|
||||
"version": "3.6.5",
|
||||
"version": "3.6.6",
|
||||
"name": "Media Fusion",
|
||||
"contactEmail": "mhdzumair@gmail",
|
||||
"description": "Watch Tamil, Hindi, Malayalam, Kannada, English, and dubbed movies, series & live TV with the Media Fusion add-on. Source: https://github.com/mhdzumair/MediaFusion",
|
||||
"logo": "https://882b9915d0fe-mediafusion.baby-beamup.club/static/images/mediafusion_logo.png",
|
||||
"description": "Universal Stremio Add-on for Movies, Series & Live TV. Source: https://github.com/mhdzumair/MediaFusion",
|
||||
"logo": "https://mediafusion.fun/static/images/mediafusion_logo.png",
|
||||
"behaviorHints": {
|
||||
"configurable": true,
|
||||
"configurationRequired": false
|
||||
|
||||
@@ -75,8 +75,7 @@ async def download_and_save_torrent(
|
||||
logging.error(f"Info hash not found for {torrent_link}")
|
||||
return False
|
||||
|
||||
parsed_data = PTN.parse(torrent_metadata["torrent_name"])
|
||||
metadata.update({"torrent_metadata": torrent_metadata, **parsed_data})
|
||||
metadata.update(torrent_metadata)
|
||||
|
||||
if not metadata.get("year"):
|
||||
logging.error(f"Year not found for {page_link}")
|
||||
|
||||
@@ -0,0 +1,162 @@
|
||||
import re
|
||||
from datetime import datetime
|
||||
|
||||
import PTN
|
||||
import httpx
|
||||
from fastapi import BackgroundTasks
|
||||
|
||||
from db.models import TorrentStreams
|
||||
from utils.parser import convert_size_to_bytes
|
||||
from utils.torrent import info_hashes_to_torrent_metadata
|
||||
|
||||
|
||||
async def fetch_stream_data(url: str) -> dict:
|
||||
"""Fetch stream data asynchronously."""
|
||||
async with httpx.AsyncClient() as client:
|
||||
response = await client.get(url, timeout=10)
|
||||
response.raise_for_status() # Will raise an exception for 4xx/5xx responses
|
||||
return response.json()
|
||||
|
||||
|
||||
async def scrap_streams_from_torrentio(
|
||||
video_id: str, catalog_type: str, background_tasks: BackgroundTasks
|
||||
) -> list[TorrentStreams]:
|
||||
"""
|
||||
Get streams by IMDb ID from torrentio stremio addon.
|
||||
"""
|
||||
url = f"https://torrentio.strem.fun/stream/{catalog_type}/{video_id}.json"
|
||||
try:
|
||||
stream_data = await fetch_stream_data(url)
|
||||
return await store_and_parse_stream_data(
|
||||
video_id, stream_data.get("streams", []), background_tasks
|
||||
)
|
||||
except (httpx.HTTPError, httpx.TimeoutException):
|
||||
return [] # Return empty list in case of HTTP errors or timeouts
|
||||
|
||||
|
||||
def parse_stream_title(stream: dict) -> dict:
|
||||
"""Parse the stream title for metadata and other details."""
|
||||
torrent_name = stream["title"].splitlines()[0]
|
||||
metadata = PTN.parse(torrent_name)
|
||||
|
||||
# Initialize fields
|
||||
details = stream["title"]
|
||||
size = convert_size_to_bytes(extract_size_string(details))
|
||||
seeders = extract_seeders(details)
|
||||
languages = extract_languages(metadata, details)
|
||||
|
||||
return {
|
||||
"torrent_name": torrent_name,
|
||||
"size": size,
|
||||
"seeders": seeders,
|
||||
"languages": languages,
|
||||
"metadata": metadata,
|
||||
}
|
||||
|
||||
|
||||
async def store_and_parse_stream_data(
|
||||
video_id: str, stream_data: list, background_tasks: BackgroundTasks
|
||||
) -> list[TorrentStreams]:
|
||||
streams = []
|
||||
info_hashes = []
|
||||
for stream in stream_data:
|
||||
parsed_data = parse_stream_title(stream)
|
||||
if not parsed_data["seeders"]:
|
||||
continue
|
||||
|
||||
torrent_stream = await TorrentStreams.get(stream["infoHash"])
|
||||
|
||||
if torrent_stream:
|
||||
# Update existing stream
|
||||
torrent_stream.seeders = parsed_data["seeders"]
|
||||
torrent_stream.updated_at = datetime.now()
|
||||
await torrent_stream.save()
|
||||
else:
|
||||
# Create new stream
|
||||
torrent_stream = TorrentStreams(
|
||||
id=stream["infoHash"],
|
||||
torrent_name=parsed_data["torrent_name"],
|
||||
announce_list=[],
|
||||
size=parsed_data["size"],
|
||||
filename=None,
|
||||
file_index=stream.get("fileIdx"),
|
||||
languages=parsed_data["languages"],
|
||||
resolution=parsed_data["metadata"].get("resolution"),
|
||||
codec=parsed_data["metadata"].get("codec"),
|
||||
quality=parsed_data["metadata"].get("quality"),
|
||||
audio=parsed_data["metadata"].get("audio"),
|
||||
encoder=parsed_data["metadata"].get("encoder"),
|
||||
source="Torrentio",
|
||||
catalog=["torrentio_streams"],
|
||||
updated_at=datetime.now(),
|
||||
seeders=parsed_data["seeders"],
|
||||
meta_id=video_id,
|
||||
)
|
||||
await torrent_stream.save()
|
||||
|
||||
streams.append(torrent_stream)
|
||||
if torrent_stream.filename is None:
|
||||
info_hashes.append(stream["infoHash"])
|
||||
|
||||
background_tasks.add_task(update_torrent_streams_metadata, info_hashes)
|
||||
|
||||
return streams
|
||||
|
||||
|
||||
def extract_seeders(details: str) -> int:
|
||||
"""Extract seeders from details string."""
|
||||
seeders_match = re.search(r"👤 (\d+)", details)
|
||||
return int(seeders_match.group(1)) if seeders_match else None
|
||||
|
||||
|
||||
def extract_languages_from_title(title: str) -> list:
|
||||
"""Extract languages and country flags from the title string."""
|
||||
languages = []
|
||||
if "Multi Audio" in title or "Multi Language" in title:
|
||||
languages.append("Multi Language")
|
||||
elif "Dual Audio" in title or "Dual Language" in title:
|
||||
languages.append("Dual Language")
|
||||
|
||||
# Regex to match country flag emojis
|
||||
flag_emojis = re.findall(r"[\U0001F1E6-\U0001F1FF]{2}", title)
|
||||
if flag_emojis:
|
||||
languages.extend(flag_emojis)
|
||||
|
||||
return languages
|
||||
|
||||
|
||||
def extract_languages(metadata: dict, title: str) -> list:
|
||||
"""Extract languages from metadata or title."""
|
||||
language = metadata.get("language")
|
||||
if language:
|
||||
if isinstance(language, str):
|
||||
return [language]
|
||||
elif isinstance(language, list):
|
||||
return language
|
||||
return extract_languages_from_title(title)
|
||||
|
||||
|
||||
def extract_size_string(details: str) -> str:
|
||||
"""Extract the size string from the details."""
|
||||
size_match = re.search(r"💾 (\d+(?:\.\d+)?\s*(GB|MB))", details, re.IGNORECASE)
|
||||
return size_match.group(1) if size_match else ""
|
||||
|
||||
|
||||
async def update_torrent_streams_metadata(info_hashes: list[str]):
|
||||
"""Update torrent streams metadata."""
|
||||
streams_metadata = await info_hashes_to_torrent_metadata(info_hashes, [])
|
||||
|
||||
for stream_metadata in streams_metadata:
|
||||
if not stream_metadata:
|
||||
continue
|
||||
|
||||
torrent_stream = await TorrentStreams.get(stream_metadata["info_hash"])
|
||||
if torrent_stream:
|
||||
torrent_stream.torrent_name = stream_metadata["torrent_name"]
|
||||
torrent_stream.size = stream_metadata["total_size"]
|
||||
|
||||
largest_file = max(stream_metadata["file_data"], key=lambda x: x["size"])
|
||||
torrent_stream.filename = largest_file["filename"]
|
||||
torrent_stream.file_index = largest_file["index"]
|
||||
torrent_stream.updated_at = datetime.now()
|
||||
await torrent_stream.save()
|
||||
@@ -1,6 +1,6 @@
|
||||
from typing import Any
|
||||
|
||||
from db.models import Streams
|
||||
from db.models import TorrentStreams
|
||||
from db.schemas import UserData
|
||||
from streaming_providers.alldebrid.client import AllDebrid
|
||||
from streaming_providers.exceptions import ProviderException
|
||||
@@ -46,7 +46,7 @@ def get_direct_link_from_alldebrid(
|
||||
return response["data"]["link"]
|
||||
|
||||
|
||||
def update_ad_cache_status(streams: list[Streams], user_data: UserData):
|
||||
def update_ad_cache_status(streams: list[TorrentStreams], user_data: UserData):
|
||||
"""Updates the cache status of streams based on AllDebrid's instant availability."""
|
||||
|
||||
try:
|
||||
|
||||
@@ -2,7 +2,7 @@ import time
|
||||
|
||||
import PTN
|
||||
|
||||
from db.models import Streams, Episode
|
||||
from db.models import TorrentStreams, Episode
|
||||
from db.schemas import UserData
|
||||
from streaming_providers.debridlink.client import DebridLink
|
||||
from streaming_providers.exceptions import ProviderException
|
||||
@@ -12,7 +12,7 @@ def get_direct_link_from_debridlink(
|
||||
info_hash: str,
|
||||
magnet_link: str,
|
||||
user_data: UserData,
|
||||
stream: Streams,
|
||||
stream: TorrentStreams,
|
||||
episode_data: Episode = None,
|
||||
max_retries=5,
|
||||
retry_interval=5,
|
||||
@@ -128,7 +128,7 @@ def select_episode_file(torrent_files: list, episode: int, file_name_key: str) -
|
||||
)
|
||||
|
||||
|
||||
def update_dl_cache_status(streams: list[Streams], user_data: UserData):
|
||||
def update_dl_cache_status(streams: list[TorrentStreams], user_data: UserData):
|
||||
"""Updates the cache status of streams based on DebridLink's instant availability."""
|
||||
|
||||
try:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from db.models import Streams
|
||||
from db.models import TorrentStreams
|
||||
from db.schemas import UserData
|
||||
from streaming_providers.exceptions import ProviderException
|
||||
from streaming_providers.offcloud.client import OffCloud
|
||||
@@ -38,7 +38,7 @@ def get_direct_link_from_offcloud(
|
||||
return oc_client.create_download_link(request_id, torrent_info, filename)
|
||||
|
||||
|
||||
def update_oc_cache_status(streams: list[Streams], user_data: UserData):
|
||||
def update_oc_cache_status(streams: list[TorrentStreams], user_data: UserData):
|
||||
"""Updates the cache status of streams based on OffCloud's instant availability."""
|
||||
|
||||
try:
|
||||
|
||||
@@ -5,7 +5,7 @@ import httpx
|
||||
from pikpakapi import PikPakApi, PikpakException
|
||||
from thefuzz import fuzz
|
||||
|
||||
from db.models import Streams
|
||||
from db.models import TorrentStreams
|
||||
from db.schemas import UserData
|
||||
from streaming_providers.exceptions import ProviderException
|
||||
|
||||
@@ -101,7 +101,18 @@ async def find_file_in_folder_tree(
|
||||
# Fuzzy matching as a fallback
|
||||
for file in files:
|
||||
file["fuzzy_ratio"] = fuzz.ratio(filename, file["name"])
|
||||
return sorted(files, key=lambda x: x["fuzzy_ratio"], reverse=True)[0]
|
||||
selected_file = max(files, key=lambda x: x["fuzzy_ratio"])
|
||||
|
||||
# If the fuzzy ratio is less than 50, then select the largest file
|
||||
if selected_file["fuzzy_ratio"] < 50:
|
||||
selected_file = max(files, key=lambda x: x["size"])
|
||||
|
||||
if "video" not in selected_file["mime_type"]:
|
||||
raise ProviderException(
|
||||
"No matching file available for this torrent", "no_matching_file.mp4"
|
||||
)
|
||||
|
||||
return selected_file
|
||||
|
||||
|
||||
async def initialize_pikpak(user_data: UserData):
|
||||
@@ -154,7 +165,7 @@ async def retrieve_or_download_file(
|
||||
filename: str,
|
||||
magnet_link: str,
|
||||
info_hash: str,
|
||||
stream: Streams,
|
||||
stream: TorrentStreams,
|
||||
max_retries: int,
|
||||
retry_interval: int,
|
||||
):
|
||||
@@ -206,7 +217,7 @@ async def get_direct_link_from_pikpak(
|
||||
info_hash: str,
|
||||
magnet_link: str,
|
||||
user_data: UserData,
|
||||
stream: Streams,
|
||||
stream: TorrentStreams,
|
||||
filename: str,
|
||||
max_retries=5,
|
||||
retry_interval=5,
|
||||
@@ -220,7 +231,7 @@ async def get_direct_link_from_pikpak(
|
||||
selected_file = await retrieve_or_download_file(
|
||||
pikpak,
|
||||
folder_id,
|
||||
filename,
|
||||
filename or stream.torrent_name,
|
||||
magnet_link,
|
||||
info_hash,
|
||||
stream,
|
||||
@@ -232,7 +243,9 @@ async def get_direct_link_from_pikpak(
|
||||
return file_data["web_content_link"]
|
||||
|
||||
|
||||
async def update_pikpak_cache_status(streams: list[Streams], user_data: UserData):
|
||||
async def update_pikpak_cache_status(
|
||||
streams: list[TorrentStreams], user_data: UserData
|
||||
):
|
||||
"""Updates the cache status of streams based on PikPak's instant availability."""
|
||||
try:
|
||||
pikpak = await initialize_pikpak(user_data)
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
from typing import Any
|
||||
|
||||
from db.models import Streams
|
||||
from db.models import TorrentStreams
|
||||
from db.schemas import UserData
|
||||
from streaming_providers.exceptions import ProviderException
|
||||
from streaming_providers.realdebrid.client import RealDebrid
|
||||
|
||||
|
||||
def create_download_link(rd_client, torrent_id, filename):
|
||||
def create_download_link(rd_client, torrent_id, filename, file_index):
|
||||
torrent_info = rd_client.get_torrent_info(torrent_id)
|
||||
file_index = select_file_index_from_torrent(torrent_info, filename)
|
||||
file_index = select_file_index_from_torrent(torrent_info, filename, file_index)
|
||||
response = rd_client.create_download_link(torrent_info["links"][file_index])
|
||||
return response.get("download")
|
||||
|
||||
@@ -18,6 +18,7 @@ def get_direct_link_from_realdebrid(
|
||||
magnet_link: str,
|
||||
user_data: UserData,
|
||||
filename: str,
|
||||
file_index: int,
|
||||
max_retries=5,
|
||||
retry_interval=5,
|
||||
) -> str:
|
||||
@@ -28,12 +29,12 @@ def get_direct_link_from_realdebrid(
|
||||
if torrent_info:
|
||||
torrent_id = torrent_info.get("id")
|
||||
if torrent_info["status"] == "downloaded":
|
||||
return create_download_link(rd_client, torrent_id, filename)
|
||||
return create_download_link(rd_client, torrent_id, filename, file_index)
|
||||
elif torrent_info["status"] == "downloading":
|
||||
rd_client.wait_for_status(
|
||||
torrent_id, "downloaded", max_retries, retry_interval
|
||||
)
|
||||
return create_download_link(rd_client, torrent_id, filename)
|
||||
return create_download_link(rd_client, torrent_id, filename, file_index)
|
||||
elif torrent_info["status"] == "magnet_error":
|
||||
rd_client.delete_torrent(torrent_id)
|
||||
raise ProviderException(
|
||||
@@ -57,10 +58,10 @@ def get_direct_link_from_realdebrid(
|
||||
|
||||
# Wait for download completion and get the direct link
|
||||
rd_client.wait_for_status(torrent_id, "downloaded", max_retries, retry_interval)
|
||||
return create_download_link(rd_client, torrent_id, filename)
|
||||
return create_download_link(rd_client, torrent_id, filename, file_index)
|
||||
|
||||
|
||||
def update_rd_cache_status(streams: list[Streams], user_data: UserData):
|
||||
def update_rd_cache_status(streams: list[TorrentStreams], user_data: UserData):
|
||||
"""Updates the cache status of streams based on RealDebrid's instant availability."""
|
||||
|
||||
try:
|
||||
@@ -75,15 +76,15 @@ def update_rd_cache_status(streams: list[Streams], user_data: UserData):
|
||||
pass
|
||||
|
||||
|
||||
def select_file_index_from_torrent(torrent_info: dict[str, Any], filename: str) -> int:
|
||||
def select_file_index_from_torrent(
|
||||
torrent_info: dict[str, Any], filename: str, file_index: int
|
||||
) -> int:
|
||||
"""Select the file index from the torrent info."""
|
||||
selected_files = [file for file in torrent_info["files"] if file["selected"] == 1]
|
||||
for index, file in enumerate(selected_files):
|
||||
if file["path"] == "/" + filename:
|
||||
return index
|
||||
raise ProviderException(
|
||||
"No matching file available for this torrent", "api_error.mp4"
|
||||
)
|
||||
return file_index
|
||||
|
||||
|
||||
def fetch_downloaded_info_hashes_from_rd(user_data: UserData) -> list[str]:
|
||||
|
||||
@@ -5,7 +5,7 @@ from datetime import datetime
|
||||
from seedrcc import Seedr
|
||||
from thefuzz import fuzz
|
||||
|
||||
from db.models import Streams
|
||||
from db.models import TorrentStreams
|
||||
from db.schemas import UserData
|
||||
from streaming_providers.exceptions import ProviderException
|
||||
|
||||
@@ -91,13 +91,18 @@ def get_file_details_from_folder(seedr, folder_id: int, filename: str):
|
||||
|
||||
# If the file is not found with exact match, try to find it by fuzzy ratio
|
||||
for file in folder_content["files"]:
|
||||
# rename file to remove any special characters
|
||||
file["name"] = seedr_clean_name(file["name"])
|
||||
seedr.renameFile(file["folder_file_id"], file["name"])
|
||||
file["fuzzy_ratio"] = fuzz.ratio(filename, file["name"])
|
||||
return sorted(
|
||||
folder_content["files"], key=lambda x: x["fuzzy_ratio"], reverse=True
|
||||
)[0]
|
||||
selected_file = max(folder_content["files"], key=lambda x: x["fuzzy_ratio"])
|
||||
|
||||
# If the fuzzy ratio is less than 50, then select the largest file
|
||||
if selected_file["fuzzy_ratio"] < 50:
|
||||
selected_file = max(folder_content["files"], key=lambda x: x["size"])
|
||||
|
||||
if selected_file["play_video"] is False:
|
||||
raise ProviderException(
|
||||
"No matching file available for this torrent", "no_matching_file.mp4"
|
||||
)
|
||||
return selected_file
|
||||
|
||||
|
||||
def seedr_clean_name(name: str, replace: str = "") -> str:
|
||||
@@ -118,11 +123,20 @@ def get_seedr_client(user_data: UserData) -> Seedr:
|
||||
return seedr
|
||||
|
||||
|
||||
def rename_seedr_files(seedr, folder_id):
|
||||
"""rename filenames to fix stremio android client bug."""
|
||||
folder_content = seedr.listContents(folder_id)
|
||||
for file in folder_content["files"]:
|
||||
# rename file to remove any special characters
|
||||
if file["name"] != seedr_clean_name(file["name"]):
|
||||
seedr.renameFile(file["folder_file_id"], seedr_clean_name(file["name"]))
|
||||
|
||||
|
||||
async def get_direct_link_from_seedr(
|
||||
info_hash: str,
|
||||
magnet_link: str,
|
||||
user_data: UserData,
|
||||
stream: Streams,
|
||||
stream: TorrentStreams,
|
||||
filename: str,
|
||||
max_retries=5,
|
||||
retry_interval=5,
|
||||
@@ -144,10 +158,13 @@ async def get_direct_link_from_seedr(
|
||||
folder = check_folder_status(seedr, info_hash)
|
||||
folder_id = folder["id"]
|
||||
|
||||
# rename filenames to fix stremio android client bug.
|
||||
rename_seedr_files(seedr, folder_id)
|
||||
|
||||
selected_file = get_file_details_from_folder(
|
||||
seedr,
|
||||
folder_id,
|
||||
seedr_clean_name(filename),
|
||||
seedr_clean_name(filename or stream.torrent_name),
|
||||
)
|
||||
video_link = seedr.fetchFile(selected_file["folder_file_id"])["url"]
|
||||
|
||||
@@ -183,7 +200,7 @@ def free_up_space(seedr, required_space):
|
||||
available_space += folder["size"]
|
||||
|
||||
|
||||
def update_seedr_cache_status(streams: list[Streams], user_data: UserData):
|
||||
def update_seedr_cache_status(streams: list[TorrentStreams], user_data: UserData):
|
||||
"""Updates the cache status of the streams based on the user's Seedr account."""
|
||||
try:
|
||||
seedr = get_seedr_client(user_data)
|
||||
|
||||
@@ -1,27 +1,32 @@
|
||||
from typing import Any
|
||||
|
||||
from db.models import Streams
|
||||
from db.models import TorrentStreams
|
||||
from db.schemas import UserData
|
||||
from streaming_providers.exceptions import ProviderException
|
||||
from streaming_providers.torbox.client import Torbox
|
||||
|
||||
|
||||
def get_direct_link_from_torbox(
|
||||
info_hash: str,
|
||||
magnet_link: str,
|
||||
user_data: UserData,
|
||||
filename: str,
|
||||
max_retries=5,
|
||||
retry_interval=5,
|
||||
info_hash: str,
|
||||
magnet_link: str,
|
||||
user_data: UserData,
|
||||
filename: str,
|
||||
max_retries=5,
|
||||
retry_interval=5,
|
||||
) -> str:
|
||||
torbox_client = Torbox(token=user_data.streaming_provider.token)
|
||||
|
||||
# Check if the torrent already exists
|
||||
torrent_info = torbox_client.get_available_torrent(info_hash)
|
||||
if torrent_info:
|
||||
if torrent_info["download_finished"] is True and torrent_info["download_present"] is True:
|
||||
if (
|
||||
torrent_info["download_finished"] is True
|
||||
and torrent_info["download_present"] is True
|
||||
):
|
||||
file_id = select_file_id_from_torrent(torrent_info, filename)
|
||||
response = torbox_client.create_download_link(torrent_info.get("id"), file_id)
|
||||
response = torbox_client.create_download_link(
|
||||
torrent_info.get("id"), file_id
|
||||
)
|
||||
return response["data"]
|
||||
else:
|
||||
# If torrent doesn't exist, add it
|
||||
@@ -34,7 +39,7 @@ def get_direct_link_from_torbox(
|
||||
)
|
||||
|
||||
|
||||
def update_torbox_cache_status(streams: list[Streams], user_data: UserData):
|
||||
def update_torbox_cache_status(streams: list[TorrentStreams], user_data: UserData):
|
||||
"""Updates the cache status of streams based on Torbox's instant availability."""
|
||||
|
||||
try:
|
||||
|
||||
@@ -31,6 +31,7 @@ CATALOG_ID_DATA = [
|
||||
"english_series",
|
||||
"live_tv",
|
||||
"mediafusion_search_tv",
|
||||
"torrentio_streams",
|
||||
]
|
||||
|
||||
CATALOG_NAME_DATA = [
|
||||
@@ -66,6 +67,7 @@ CATALOG_NAME_DATA = [
|
||||
"English Series",
|
||||
"Live TV",
|
||||
"MediaFusion Search TV",
|
||||
"Torrentio Streams",
|
||||
]
|
||||
|
||||
RESOLUTIONS = [
|
||||
|
||||
+19
-5
@@ -1,11 +1,12 @@
|
||||
import asyncio
|
||||
import math
|
||||
import re
|
||||
|
||||
import requests
|
||||
from imdb import Cinemagoer
|
||||
|
||||
from db.config import settings
|
||||
from db.models import Streams, MediaFusionTVMetaData
|
||||
from db.models import TorrentStreams, MediaFusionTVMetaData
|
||||
from db.schemas import Stream, UserData
|
||||
from streaming_providers.alldebrid.utils import (
|
||||
update_ad_cache_status,
|
||||
@@ -40,8 +41,8 @@ ia = Cinemagoer()
|
||||
|
||||
|
||||
async def filter_and_sort_streams(
|
||||
streams: list[Streams], user_data: UserData
|
||||
) -> list[Streams]:
|
||||
streams: list[TorrentStreams], user_data: UserData
|
||||
) -> list[TorrentStreams]:
|
||||
# Filter streams by selected catalogs and resolutions
|
||||
filtered_streams = [
|
||||
stream
|
||||
@@ -81,7 +82,7 @@ async def filter_and_sort_streams(
|
||||
|
||||
|
||||
async def parse_stream_data(
|
||||
streams: list[Streams],
|
||||
streams: list[TorrentStreams],
|
||||
user_data: UserData,
|
||||
secret_str: str,
|
||||
season: int = None,
|
||||
@@ -115,11 +116,14 @@ async def parse_stream_data(
|
||||
else:
|
||||
streaming_provider = "Torrent ⏳"
|
||||
|
||||
seeders = f"👤 {stream_data.seeders}" if stream_data.seeders else None
|
||||
|
||||
description_parts = [
|
||||
quality_detail,
|
||||
convert_bytes_to_readable(
|
||||
episode_data.size if episode_data else stream_data.size
|
||||
),
|
||||
seeders,
|
||||
" + ".join(stream_data.languages),
|
||||
stream_data.source,
|
||||
]
|
||||
@@ -159,7 +163,17 @@ def convert_bytes_to_readable(size_bytes: int) -> str:
|
||||
i = int(math.floor(math.log(size_bytes, 1024)))
|
||||
p = math.pow(1024, i)
|
||||
s = round(size_bytes / p, 2)
|
||||
return f"{s} {size_name[i]}"
|
||||
return f"💾 {s} {size_name[i]}"
|
||||
|
||||
|
||||
def convert_size_to_bytes(size_str: str) -> int:
|
||||
"""Convert size string to bytes."""
|
||||
match = re.match(r"(\d+(?:\.\d+)?)\s*(GB|MB)", size_str, re.IGNORECASE)
|
||||
if match:
|
||||
size, unit = match.groups()
|
||||
size = float(size)
|
||||
return int(size * 1024**3) if "GB" in unit.upper() else int(size * 1024**2)
|
||||
return 0
|
||||
|
||||
|
||||
def get_catalogs(catalog: str, languages: list[str]) -> list[str]:
|
||||
|
||||
+130
-50
@@ -1,88 +1,168 @@
|
||||
import hashlib
|
||||
import io
|
||||
import logging
|
||||
from contextlib import AsyncExitStack, asynccontextmanager
|
||||
from typing import Awaitable, Iterable, AsyncIterator, Optional, TypeVar
|
||||
from urllib.parse import quote
|
||||
|
||||
import PTN
|
||||
import bencodepy
|
||||
import anyio
|
||||
import httpx
|
||||
from anyio import (
|
||||
create_task_group,
|
||||
create_memory_object_stream,
|
||||
CapacityLimiter,
|
||||
)
|
||||
from anyio.streams.memory import MemoryObjectSendStream
|
||||
from demagnetize.core import Demagnetizer
|
||||
from torf import Magnet, Torrent
|
||||
|
||||
TRACKERS = [
|
||||
"http://tracker3.itzmx.com:8080/announce",
|
||||
"udp://9.rarbg.me:2710/announce",
|
||||
"udp://9.rarbg.to:2710/announce",
|
||||
"udp://exodus.desync.com:6969/announce",
|
||||
"udp://ipv4.tracker.harry.lu:80/announce",
|
||||
"udp://tracker.coppersurfer.tk:6969/announce",
|
||||
"udp://tracker.internetwarriors.net:1337/announce",
|
||||
"udp://tracker.leechers-paradise.org:6969/announce",
|
||||
"udp://tracker.openbittorrent.com:80/announce",
|
||||
"udp://tracker.opentrackr.org:1337/announce",
|
||||
"udp://tracker.pomf.se:80/announce",
|
||||
"udp://exodus.desync.com:6969/announce",
|
||||
"udp://tracker.internetwarriors.net:1337/announce",
|
||||
"udp://tracker.tiny-vps.com:6969/announce",
|
||||
"udp://tracker.coppersurfer.tk:6969/announce",
|
||||
"udp://tracker.leechers-paradise.org:6969/announce",
|
||||
"udp://9.rarbg.to:2710/announce",
|
||||
"udp://9.rarbg.me:2710/announce",
|
||||
"http://tracker3.itzmx.com:8080/announce",
|
||||
"udp://ipv4.tracker.harry.lu:80/announce",
|
||||
]
|
||||
|
||||
|
||||
def extract_torrent_metadata(content) -> dict:
|
||||
def response_content_to_torrent(content) -> Torrent | None:
|
||||
try:
|
||||
torrent_data = bencodepy.decode(content)
|
||||
return Torrent.read_stream(io.BytesIO(content))
|
||||
except Exception as e:
|
||||
logging.error(f"Error occurred: {e}")
|
||||
return
|
||||
|
||||
info = torrent_data[b"info"]
|
||||
info_encoded = bencodepy.encode(info)
|
||||
|
||||
m = hashlib.sha1()
|
||||
m.update(info_encoded)
|
||||
info_hash = m.hexdigest()
|
||||
def extract_torrent_metadata(content: Torrent | bytes) -> dict:
|
||||
if isinstance(content, bytes):
|
||||
torrent = response_content_to_torrent(content)
|
||||
if not torrent:
|
||||
return {}
|
||||
else:
|
||||
torrent = content
|
||||
|
||||
# Extract file size, file list, and announce list
|
||||
if b"files" in info:
|
||||
total_size = sum(file[b"length"] for file in info[b"files"])
|
||||
file_data = []
|
||||
for idx, file in enumerate(info[b"files"]):
|
||||
filename = file[b"path"][0].decode()
|
||||
parsed_data = PTN.parse(filename)
|
||||
file_data.append(
|
||||
{
|
||||
"filename": filename,
|
||||
"size": file[b"length"],
|
||||
"index": idx,
|
||||
"season": parsed_data.get("season"),
|
||||
"episode": parsed_data.get("episode"),
|
||||
}
|
||||
)
|
||||
else:
|
||||
total_size = info[b"length"]
|
||||
filename = info[b"name"].decode()
|
||||
try:
|
||||
info_hash = torrent.infohash
|
||||
total_size = torrent.size
|
||||
file_data = []
|
||||
|
||||
for idx, file in enumerate(
|
||||
torrent.files if torrent.mode == "multifile" else [torrent.name]
|
||||
):
|
||||
filename = str(file).split("/")[-1]
|
||||
parsed_data = PTN.parse(filename)
|
||||
file_data = [
|
||||
file_data.append(
|
||||
{
|
||||
"filename": filename,
|
||||
"size": total_size,
|
||||
"index": 0,
|
||||
"size": file.size if torrent.mode == "multifile" else total_size,
|
||||
"index": idx,
|
||||
"season": parsed_data.get("season"),
|
||||
"episode": parsed_data.get("episode"),
|
||||
}
|
||||
]
|
||||
|
||||
announce_list = [
|
||||
tracker[0].decode() for tracker in torrent_data.get(b"announce-list", [])
|
||||
]
|
||||
torrent_name = info.get(b"name", b"").decode() or file_data[0]["filename"]
|
||||
)
|
||||
|
||||
return {
|
||||
**PTN.parse(torrent.name),
|
||||
"info_hash": info_hash,
|
||||
"announce_list": announce_list,
|
||||
"announce_list": sorted(list(torrent.trackers.flat)),
|
||||
"total_size": total_size,
|
||||
"file_data": file_data,
|
||||
"torrent_name": torrent_name,
|
||||
"torrent_name": torrent.name,
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"Error occurred: {e}")
|
||||
return {}
|
||||
|
||||
|
||||
def convert_info_hash_to_magnet(info_hash: str, trackers: list[str]) -> str:
|
||||
async def convert_info_hash_to_magnet(info_hash: str, trackers: list[str]) -> str:
|
||||
magnet_link = f"magnet:?xt=urn:btih:{info_hash}"
|
||||
for tracker in trackers or TRACKERS:
|
||||
for tracker in set(trackers) or TRACKERS:
|
||||
encoded_tracker = quote(tracker, safe="")
|
||||
magnet_link += f"&tr={encoded_tracker}"
|
||||
return magnet_link
|
||||
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def acollect(
|
||||
coros: Iterable[Awaitable[T]],
|
||||
limit: Optional[CapacityLimiter] = None,
|
||||
timeout: int = 30,
|
||||
) -> AsyncIterator[AsyncIterator[T]]:
|
||||
async with create_task_group() as tg:
|
||||
sender, receiver = create_memory_object_stream[T]()
|
||||
async with sender:
|
||||
for c in coros:
|
||||
tg.start_soon(_acollect_pipe, c, limit, sender.clone(), timeout)
|
||||
async with receiver:
|
||||
yield receiver
|
||||
|
||||
|
||||
async def _acollect_pipe(
|
||||
coro: Awaitable[T],
|
||||
limit: Optional[CapacityLimiter],
|
||||
sender: MemoryObjectSendStream[T],
|
||||
timeout: int,
|
||||
) -> None:
|
||||
async with AsyncExitStack() as stack:
|
||||
if limit is not None:
|
||||
await stack.enter_async_context(limit)
|
||||
await stack.enter_async_context(sender)
|
||||
try:
|
||||
with anyio.fail_after(timeout):
|
||||
value = await coro
|
||||
await sender.send(value)
|
||||
except Exception as e:
|
||||
# Send the exception instead of the value
|
||||
await sender.send(e)
|
||||
|
||||
|
||||
async def info_hashes_to_torrent_metadata(
|
||||
info_hashes: list[str], trackers: list[str]
|
||||
) -> list[dict]:
|
||||
magnets = [
|
||||
Magnet(xt=info_hash, tr=trackers or TRACKERS) for info_hash in info_hashes
|
||||
]
|
||||
torrents_data = []
|
||||
|
||||
demagnetizer = Demagnetizer()
|
||||
async with acollect(
|
||||
[demagnetizer.demagnetize(magnet) for magnet in magnets], timeout=60
|
||||
) as async_iterator:
|
||||
async for torrent_result in async_iterator:
|
||||
try:
|
||||
if isinstance(torrent_result, Exception):
|
||||
pass
|
||||
else:
|
||||
torrents_data.append(extract_torrent_metadata(torrent_result))
|
||||
except Exception as e:
|
||||
logging.error(f"Error processing torrent: {e}")
|
||||
|
||||
return torrents_data
|
||||
|
||||
|
||||
async def init_best_trackers():
|
||||
# get the best trackers from https://raw.githubusercontent.com/ngosang/trackerslist/master/trackers_best.txt
|
||||
global TRACKERS
|
||||
|
||||
async with httpx.AsyncClient() as client:
|
||||
response = await client.get(
|
||||
"https://raw.githubusercontent.com/ngosang/trackerslist/master/trackers_best.txt"
|
||||
)
|
||||
if response.status_code == 200:
|
||||
trackers = [tracker for tracker in response.text.split("\n") if tracker]
|
||||
TRACKERS.extend(trackers)
|
||||
TRACKERS = list(set(TRACKERS))
|
||||
|
||||
logging.info(f"Loaded {len(trackers)} trackers. Total: {len(TRACKERS)}")
|
||||
else:
|
||||
logging.error(f"Failed to load trackers: {response.status_code}")
|
||||
|
||||
Reference in New Issue
Block a user