mirror of
https://github.com/Viren070/MediaFusion.git
synced 2025-12-01 23:21:11 +01:00
Added qBittorrent - WebDav support
This commit is contained in:
@@ -36,6 +36,8 @@ redis = {extras = ["hiredis"], version = "*"}
|
||||
dramatiq = {extras = ["redis", "watch"], version = "*"}
|
||||
gunicorn = "*"
|
||||
scrapy = "*"
|
||||
aioqbt = {git = "git+https://github.com/mhdzumair/aioqbt.git"}
|
||||
aiowebdav = "*"
|
||||
|
||||
[dev-packages]
|
||||
pysocks = "*"
|
||||
|
||||
Generated
+222
-19
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"_meta": {
|
||||
"hash": {
|
||||
"sha256": "30880a6cb492bb705c7671889e64ef1113f718c771ed9c6545b3431e37e1f396"
|
||||
"sha256": "c4f0cccc167e5c86cc664635228cca7445feea11d2c58ebefd1d1a3cbbadb7b9"
|
||||
},
|
||||
"pipfile-spec": 6,
|
||||
"requires": {
|
||||
@@ -16,6 +16,118 @@
|
||||
]
|
||||
},
|
||||
"default": {
|
||||
"aiofiles": {
|
||||
"hashes": [
|
||||
"sha256:19297512c647d4b27a2cf7c34caa7e405c0d60b5560618a29a9fe027b18b0107",
|
||||
"sha256:84ec2218d8419404abcb9f0c02df3f34c6e0a68ed41072acfb1cef5cbc29051a"
|
||||
],
|
||||
"markers": "python_version >= '3.7'",
|
||||
"version": "==23.2.1"
|
||||
},
|
||||
"aiohttp": {
|
||||
"hashes": [
|
||||
"sha256:017a21b0df49039c8f46ca0971b3a7fdc1f56741ab1240cb90ca408049766168",
|
||||
"sha256:039df344b45ae0b34ac885ab5b53940b174530d4dd8a14ed8b0e2155b9dddccb",
|
||||
"sha256:055ce4f74b82551678291473f66dc9fb9048a50d8324278751926ff0ae7715e5",
|
||||
"sha256:06a9b2c8837d9a94fae16c6223acc14b4dfdff216ab9b7202e07a9a09541168f",
|
||||
"sha256:07b837ef0d2f252f96009e9b8435ec1fef68ef8b1461933253d318748ec1acdc",
|
||||
"sha256:0ed621426d961df79aa3b963ac7af0d40392956ffa9be022024cd16297b30c8c",
|
||||
"sha256:0fa43c32d1643f518491d9d3a730f85f5bbaedcbd7fbcae27435bb8b7a061b29",
|
||||
"sha256:1f5a71d25cd8106eab05f8704cd9167b6e5187bcdf8f090a66c6d88b634802b4",
|
||||
"sha256:1f5cd333fcf7590a18334c90f8c9147c837a6ec8a178e88d90a9b96ea03194cc",
|
||||
"sha256:27468897f628c627230dba07ec65dc8d0db566923c48f29e084ce382119802bc",
|
||||
"sha256:298abd678033b8571995650ccee753d9458dfa0377be4dba91e4491da3f2be63",
|
||||
"sha256:2c895a656dd7e061b2fd6bb77d971cc38f2afc277229ce7dd3552de8313a483e",
|
||||
"sha256:361a1026c9dd4aba0109e4040e2aecf9884f5cfe1b1b1bd3d09419c205e2e53d",
|
||||
"sha256:363afe77cfcbe3a36353d8ea133e904b108feea505aa4792dad6585a8192c55a",
|
||||
"sha256:38a19bc3b686ad55804ae931012f78f7a534cce165d089a2059f658f6c91fa60",
|
||||
"sha256:38f307b41e0bea3294a9a2a87833191e4bcf89bb0365e83a8be3a58b31fb7f38",
|
||||
"sha256:3e59c23c52765951b69ec45ddbbc9403a8761ee6f57253250c6e1536cacc758b",
|
||||
"sha256:4b4af9f25b49a7be47c0972139e59ec0e8285c371049df1a63b6ca81fdd216a2",
|
||||
"sha256:504b6981675ace64c28bf4a05a508af5cde526e36492c98916127f5a02354d53",
|
||||
"sha256:50fca156d718f8ced687a373f9e140c1bb765ca16e3d6f4fe116e3df7c05b2c5",
|
||||
"sha256:522a11c934ea660ff8953eda090dcd2154d367dec1ae3c540aff9f8a5c109ab4",
|
||||
"sha256:52df73f14ed99cee84865b95a3d9e044f226320a87af208f068ecc33e0c35b96",
|
||||
"sha256:595f105710293e76b9dc09f52e0dd896bd064a79346234b521f6b968ffdd8e58",
|
||||
"sha256:59c26c95975f26e662ca78fdf543d4eeaef70e533a672b4113dd888bd2423caa",
|
||||
"sha256:5bce0dc147ca85caa5d33debc4f4d65e8e8b5c97c7f9f660f215fa74fc49a321",
|
||||
"sha256:5eafe2c065df5401ba06821b9a054d9cb2848867f3c59801b5d07a0be3a380ae",
|
||||
"sha256:5ed3e046ea7b14938112ccd53d91c1539af3e6679b222f9469981e3dac7ba1ce",
|
||||
"sha256:5fe9ce6c09668063b8447f85d43b8d1c4e5d3d7e92c63173e6180b2ac5d46dd8",
|
||||
"sha256:648056db9a9fa565d3fa851880f99f45e3f9a771dd3ff3bb0c048ea83fb28194",
|
||||
"sha256:69361bfdca5468c0488d7017b9b1e5ce769d40b46a9f4a2eed26b78619e9396c",
|
||||
"sha256:6b0e029353361f1746bac2e4cc19b32f972ec03f0f943b390c4ab3371840aabf",
|
||||
"sha256:6b88f9386ff1ad91ace19d2a1c0225896e28815ee09fc6a8932fded8cda97c3d",
|
||||
"sha256:770d015888c2a598b377bd2f663adfd947d78c0124cfe7b959e1ef39f5b13869",
|
||||
"sha256:7943c414d3a8d9235f5f15c22ace69787c140c80b718dcd57caaade95f7cd93b",
|
||||
"sha256:7cf5c9458e1e90e3c390c2639f1017a0379a99a94fdfad3a1fd966a2874bba52",
|
||||
"sha256:7f46acd6a194287b7e41e87957bfe2ad1ad88318d447caf5b090012f2c5bb528",
|
||||
"sha256:82e6aa28dd46374f72093eda8bcd142f7771ee1eb9d1e223ff0fa7177a96b4a5",
|
||||
"sha256:835a55b7ca49468aaaac0b217092dfdff370e6c215c9224c52f30daaa735c1c1",
|
||||
"sha256:84871a243359bb42c12728f04d181a389718710129b36b6aad0fc4655a7647d4",
|
||||
"sha256:8aacb477dc26797ee089721536a292a664846489c49d3ef9725f992449eda5a8",
|
||||
"sha256:8e2c45c208c62e955e8256949eb225bd8b66a4c9b6865729a786f2aa79b72e9d",
|
||||
"sha256:90842933e5d1ff760fae6caca4b2b3edba53ba8f4b71e95dacf2818a2aca06f7",
|
||||
"sha256:938a9653e1e0c592053f815f7028e41a3062e902095e5a7dc84617c87267ebd5",
|
||||
"sha256:939677b61f9d72a4fa2a042a5eee2a99a24001a67c13da113b2e30396567db54",
|
||||
"sha256:9d3c9b50f19704552f23b4eaea1fc082fdd82c63429a6506446cbd8737823da3",
|
||||
"sha256:a6fe5571784af92b6bc2fda8d1925cccdf24642d49546d3144948a6a1ed58ca5",
|
||||
"sha256:a78ed8a53a1221393d9637c01870248a6f4ea5b214a59a92a36f18151739452c",
|
||||
"sha256:ab40e6251c3873d86ea9b30a1ac6d7478c09277b32e14745d0d3c6e76e3c7e29",
|
||||
"sha256:abf151955990d23f84205286938796c55ff11bbfb4ccfada8c9c83ae6b3c89a3",
|
||||
"sha256:acef0899fea7492145d2bbaaaec7b345c87753168589cc7faf0afec9afe9b747",
|
||||
"sha256:b40670ec7e2156d8e57f70aec34a7216407848dfe6c693ef131ddf6e76feb672",
|
||||
"sha256:b791a3143681a520c0a17e26ae7465f1b6f99461a28019d1a2f425236e6eedb5",
|
||||
"sha256:b955ed993491f1a5da7f92e98d5dad3c1e14dc175f74517c4e610b1f2456fb11",
|
||||
"sha256:ba39e9c8627edc56544c8628cc180d88605df3892beeb2b94c9bc857774848ca",
|
||||
"sha256:bca77a198bb6e69795ef2f09a5f4c12758487f83f33d63acde5f0d4919815768",
|
||||
"sha256:c3452ea726c76e92f3b9fae4b34a151981a9ec0a4847a627c43d71a15ac32aa6",
|
||||
"sha256:c46956ed82961e31557b6857a5ca153c67e5476972e5f7190015018760938da2",
|
||||
"sha256:c7c8b816c2b5af5c8a436df44ca08258fc1a13b449393a91484225fcb7545533",
|
||||
"sha256:cd73265a9e5ea618014802ab01babf1940cecb90c9762d8b9e7d2cc1e1969ec6",
|
||||
"sha256:dad46e6f620574b3b4801c68255492e0159d1712271cc99d8bdf35f2043ec266",
|
||||
"sha256:dc9b311743a78043b26ffaeeb9715dc360335e5517832f5a8e339f8a43581e4d",
|
||||
"sha256:df822ee7feaaeffb99c1a9e5e608800bd8eda6e5f18f5cfb0dc7eeb2eaa6bbec",
|
||||
"sha256:e083c285857b78ee21a96ba1eb1b5339733c3563f72980728ca2b08b53826ca5",
|
||||
"sha256:e5e46b578c0e9db71d04c4b506a2121c0cb371dd89af17a0586ff6769d4c58c1",
|
||||
"sha256:e99abf0bba688259a496f966211c49a514e65afa9b3073a1fcee08856e04425b",
|
||||
"sha256:ee43080e75fc92bf36219926c8e6de497f9b247301bbf88c5c7593d931426679",
|
||||
"sha256:f033d80bc6283092613882dfe40419c6a6a1527e04fc69350e87a9df02bbc283",
|
||||
"sha256:f1088fa100bf46e7b398ffd9904f4808a0612e1d966b4aa43baa535d1b6341eb",
|
||||
"sha256:f56455b0c2c7cc3b0c584815264461d07b177f903a04481dfc33e08a89f0c26b",
|
||||
"sha256:f59dfe57bb1ec82ac0698ebfcdb7bcd0e99c255bd637ff613760d5f33e7c81b3",
|
||||
"sha256:f7217af2e14da0856e082e96ff637f14ae45c10a5714b63c77f26d8884cf1051",
|
||||
"sha256:f734e38fd8666f53da904c52a23ce517f1b07722118d750405af7e4123933511",
|
||||
"sha256:f95511dd5d0e05fd9728bac4096319f80615aaef4acbecb35a990afebe953b0e",
|
||||
"sha256:fdd215b7b7fd4a53994f238d0f46b7ba4ac4c0adb12452beee724ddd0743ae5d",
|
||||
"sha256:feeb18a801aacb098220e2c3eea59a512362eb408d4afd0c242044c33ad6d542",
|
||||
"sha256:ff30218887e62209942f91ac1be902cc80cddb86bf00fbc6783b7a43b2bea26f"
|
||||
],
|
||||
"markers": "python_version >= '3.8'",
|
||||
"version": "==3.9.3"
|
||||
},
|
||||
"aioqbt": {
|
||||
"git": "git+https://github.com/mhdzumair/aioqbt.git",
|
||||
"markers": "python_version >= '3.7'",
|
||||
"ref": "4be694b899a13df86d420b88ab777ab559bef8e9"
|
||||
},
|
||||
"aiosignal": {
|
||||
"hashes": [
|
||||
"sha256:54cd96e15e1649b75d6c87526a6ff0b6c1b0dd3459f43d9ca11d48c339b68cfc",
|
||||
"sha256:f8376fb07dd1e86a584e4fcdec80b36b7f81aac666ebc724e2c090300dd83b17"
|
||||
],
|
||||
"markers": "python_version >= '3.7'",
|
||||
"version": "==1.3.1"
|
||||
},
|
||||
"aiowebdav": {
|
||||
"hashes": [
|
||||
"sha256:1b7be6122ab69f06f1639bfea9fd3beb43a7d375b5e1ced560b832e04c000026",
|
||||
"sha256:8345f8a55a5eea4e029de0b5c7ba38fbeaaee3e0fdc1e0ca29e60d997643cc2f"
|
||||
],
|
||||
"index": "pypi",
|
||||
"markers": "python_version >= '3.8'",
|
||||
"version": "==0.1.0rc5"
|
||||
},
|
||||
"annotated-types": {
|
||||
"hashes": [
|
||||
"sha256:0641064de18ba7a25dee8f96403ebc39113d0cb953a01429249d5c7564666a43",
|
||||
@@ -41,6 +153,14 @@
|
||||
"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",
|
||||
@@ -396,6 +516,89 @@
|
||||
],
|
||||
"version": "==0.2.1"
|
||||
},
|
||||
"frozenlist": {
|
||||
"hashes": [
|
||||
"sha256:04ced3e6a46b4cfffe20f9ae482818e34eba9b5fb0ce4056e4cc9b6e212d09b7",
|
||||
"sha256:0633c8d5337cb5c77acbccc6357ac49a1770b8c487e5b3505c57b949b4b82e98",
|
||||
"sha256:068b63f23b17df8569b7fdca5517edef76171cf3897eb68beb01341131fbd2ad",
|
||||
"sha256:0c250a29735d4f15321007fb02865f0e6b6a41a6b88f1f523ca1596ab5f50bd5",
|
||||
"sha256:1979bc0aeb89b33b588c51c54ab0161791149f2461ea7c7c946d95d5f93b56ae",
|
||||
"sha256:1a4471094e146b6790f61b98616ab8e44f72661879cc63fa1049d13ef711e71e",
|
||||
"sha256:1b280e6507ea8a4fa0c0a7150b4e526a8d113989e28eaaef946cc77ffd7efc0a",
|
||||
"sha256:1d0ce09d36d53bbbe566fe296965b23b961764c0bcf3ce2fa45f463745c04701",
|
||||
"sha256:20b51fa3f588ff2fe658663db52a41a4f7aa6c04f6201449c6c7c476bd255c0d",
|
||||
"sha256:23b2d7679b73fe0e5a4560b672a39f98dfc6f60df63823b0a9970525325b95f6",
|
||||
"sha256:23b701e65c7b36e4bf15546a89279bd4d8675faabc287d06bbcfac7d3c33e1e6",
|
||||
"sha256:2471c201b70d58a0f0c1f91261542a03d9a5e088ed3dc6c160d614c01649c106",
|
||||
"sha256:27657df69e8801be6c3638054e202a135c7f299267f1a55ed3a598934f6c0d75",
|
||||
"sha256:29acab3f66f0f24674b7dc4736477bcd4bc3ad4b896f5f45379a67bce8b96868",
|
||||
"sha256:32453c1de775c889eb4e22f1197fe3bdfe457d16476ea407472b9442e6295f7a",
|
||||
"sha256:3a670dc61eb0d0eb7080890c13de3066790f9049b47b0de04007090807c776b0",
|
||||
"sha256:3e0153a805a98f5ada7e09826255ba99fb4f7524bb81bf6b47fb702666484ae1",
|
||||
"sha256:410478a0c562d1a5bcc2f7ea448359fcb050ed48b3c6f6f4f18c313a9bdb1826",
|
||||
"sha256:442acde1e068288a4ba7acfe05f5f343e19fac87bfc96d89eb886b0363e977ec",
|
||||
"sha256:48f6a4533887e189dae092f1cf981f2e3885175f7a0f33c91fb5b7b682b6bab6",
|
||||
"sha256:4f57dab5fe3407b6c0c1cc907ac98e8a189f9e418f3b6e54d65a718aaafe3950",
|
||||
"sha256:4f9c515e7914626b2a2e1e311794b4c35720a0be87af52b79ff8e1429fc25f19",
|
||||
"sha256:55fdc093b5a3cb41d420884cdaf37a1e74c3c37a31f46e66286d9145d2063bd0",
|
||||
"sha256:5667ed53d68d91920defdf4035d1cdaa3c3121dc0b113255124bcfada1cfa1b8",
|
||||
"sha256:590344787a90ae57d62511dd7c736ed56b428f04cd8c161fcc5e7232c130c69a",
|
||||
"sha256:5a7d70357e7cee13f470c7883a063aae5fe209a493c57d86eb7f5a6f910fae09",
|
||||
"sha256:5c3894db91f5a489fc8fa6a9991820f368f0b3cbdb9cd8849547ccfab3392d86",
|
||||
"sha256:5c849d495bf5154cd8da18a9eb15db127d4dba2968d88831aff6f0331ea9bd4c",
|
||||
"sha256:64536573d0a2cb6e625cf309984e2d873979709f2cf22839bf2d61790b448ad5",
|
||||
"sha256:693945278a31f2086d9bf3df0fe8254bbeaef1fe71e1351c3bd730aa7d31c41b",
|
||||
"sha256:6db4667b187a6742b33afbbaf05a7bc551ffcf1ced0000a571aedbb4aa42fc7b",
|
||||
"sha256:6eb73fa5426ea69ee0e012fb59cdc76a15b1283d6e32e4f8dc4482ec67d1194d",
|
||||
"sha256:722e1124aec435320ae01ee3ac7bec11a5d47f25d0ed6328f2273d287bc3abb0",
|
||||
"sha256:7268252af60904bf52c26173cbadc3a071cece75f873705419c8681f24d3edea",
|
||||
"sha256:74fb4bee6880b529a0c6560885fce4dc95936920f9f20f53d99a213f7bf66776",
|
||||
"sha256:780d3a35680ced9ce682fbcf4cb9c2bad3136eeff760ab33707b71db84664e3a",
|
||||
"sha256:82e8211d69a4f4bc360ea22cd6555f8e61a1bd211d1d5d39d3d228b48c83a897",
|
||||
"sha256:89aa2c2eeb20957be2d950b85974b30a01a762f3308cd02bb15e1ad632e22dc7",
|
||||
"sha256:8aefbba5f69d42246543407ed2461db31006b0f76c4e32dfd6f42215a2c41d09",
|
||||
"sha256:96ec70beabbd3b10e8bfe52616a13561e58fe84c0101dd031dc78f250d5128b9",
|
||||
"sha256:9750cc7fe1ae3b1611bb8cfc3f9ec11d532244235d75901fb6b8e42ce9229dfe",
|
||||
"sha256:9acbb16f06fe7f52f441bb6f413ebae6c37baa6ef9edd49cdd567216da8600cd",
|
||||
"sha256:9d3e0c25a2350080e9319724dede4f31f43a6c9779be48021a7f4ebde8b2d742",
|
||||
"sha256:a06339f38e9ed3a64e4c4e43aec7f59084033647f908e4259d279a52d3757d09",
|
||||
"sha256:a0cb6f11204443f27a1628b0e460f37fb30f624be6051d490fa7d7e26d4af3d0",
|
||||
"sha256:a7496bfe1da7fb1a4e1cc23bb67c58fab69311cc7d32b5a99c2007b4b2a0e932",
|
||||
"sha256:a828c57f00f729620a442881cc60e57cfcec6842ba38e1b19fd3e47ac0ff8dc1",
|
||||
"sha256:a9b2de4cf0cdd5bd2dee4c4f63a653c61d2408055ab77b151c1957f221cabf2a",
|
||||
"sha256:b46c8ae3a8f1f41a0d2ef350c0b6e65822d80772fe46b653ab6b6274f61d4a49",
|
||||
"sha256:b7e3ed87d4138356775346e6845cccbe66cd9e207f3cd11d2f0b9fd13681359d",
|
||||
"sha256:b7f2f9f912dca3934c1baec2e4585a674ef16fe00218d833856408c48d5beee7",
|
||||
"sha256:ba60bb19387e13597fb059f32cd4d59445d7b18b69a745b8f8e5db0346f33480",
|
||||
"sha256:beee944ae828747fd7cb216a70f120767fc9f4f00bacae8543c14a6831673f89",
|
||||
"sha256:bfa4a17e17ce9abf47a74ae02f32d014c5e9404b6d9ac7f729e01562bbee601e",
|
||||
"sha256:c037a86e8513059a2613aaba4d817bb90b9d9b6b69aace3ce9c877e8c8ed402b",
|
||||
"sha256:c302220494f5c1ebeb0912ea782bcd5e2f8308037b3c7553fad0e48ebad6ad82",
|
||||
"sha256:c6321c9efe29975232da3bd0af0ad216800a47e93d763ce64f291917a381b8eb",
|
||||
"sha256:c757a9dd70d72b076d6f68efdbb9bc943665ae954dad2801b874c8c69e185068",
|
||||
"sha256:c99169d4ff810155ca50b4da3b075cbde79752443117d89429595c2e8e37fed8",
|
||||
"sha256:c9c92be9fd329ac801cc420e08452b70e7aeab94ea4233a4804f0915c14eba9b",
|
||||
"sha256:cc7b01b3754ea68a62bd77ce6020afaffb44a590c2289089289363472d13aedb",
|
||||
"sha256:db9e724bebd621d9beca794f2a4ff1d26eed5965b004a97f1f1685a173b869c2",
|
||||
"sha256:dca69045298ce5c11fd539682cff879cc1e664c245d1c64da929813e54241d11",
|
||||
"sha256:dd9b1baec094d91bf36ec729445f7769d0d0cf6b64d04d86e45baf89e2b9059b",
|
||||
"sha256:e02a0e11cf6597299b9f3bbd3f93d79217cb90cfd1411aec33848b13f5c656cc",
|
||||
"sha256:e6a20a581f9ce92d389a8c7d7c3dd47c81fd5d6e655c8dddf341e14aa48659d0",
|
||||
"sha256:e7004be74cbb7d9f34553a5ce5fb08be14fb33bc86f332fb71cbe5216362a497",
|
||||
"sha256:e774d53b1a477a67838a904131c4b0eef6b3d8a651f8b138b04f748fccfefe17",
|
||||
"sha256:edb678da49d9f72c9f6c609fbe41a5dfb9a9282f9e6a2253d5a91e0fc382d7c0",
|
||||
"sha256:f146e0911cb2f1da549fc58fc7bcd2b836a44b79ef871980d605ec392ff6b0d2",
|
||||
"sha256:f56e2333dda1fe0f909e7cc59f021eba0d2307bc6f012a1ccf2beca6ba362439",
|
||||
"sha256:f9a3ea26252bd92f570600098783d1371354d89d5f6b7dfd87359d669f2109b5",
|
||||
"sha256:f9aa1878d1083b276b0196f2dfbe00c9b7e752475ed3b682025ff20c1c1f51ac",
|
||||
"sha256:fb3c2db03683b5767dedb5769b8a40ebb47d6f7f45b1b3e3b4b51ec8ad9d9825",
|
||||
"sha256:fbeb989b5cc29e8daf7f976b421c220f1b8c731cbf22b9130d8815418ea45887",
|
||||
"sha256:fde5bd59ab5357e3853313127f4d3565fc7dad314a74d7b5d43c22c6a5ed2ced",
|
||||
"sha256:fe1a06da377e3a1062ae5fe0926e12b84eceb8a50b350ddca72dc85015873f74"
|
||||
],
|
||||
"markers": "python_version >= '3.8'",
|
||||
"version": "==1.4.1"
|
||||
},
|
||||
"gevent": {
|
||||
"hashes": [
|
||||
"sha256:17ebb6f981389c17321b95bc59ff6a65edeb98f3205884babaec9cb514aaa0d3",
|
||||
@@ -1217,12 +1420,12 @@
|
||||
},
|
||||
"pydantic": {
|
||||
"hashes": [
|
||||
"sha256:37a5432e54b12fecaa1049c5195f3d860a10e01bdfd24f1840ef14bd0d3aeab3",
|
||||
"sha256:a09be1c3d28f3abe37f8a78af58284b236a92ce520105ddc91a6d29ea1176ba7"
|
||||
"sha256:72c6034df47f46ccdf81869fddb81aade68056003900a8724a4f160700016a2a",
|
||||
"sha256:e07805c4c7f5c6826e33a1d4c9d47950d7eaf34868e2690f8594d2e30241f11f"
|
||||
],
|
||||
"index": "pypi",
|
||||
"markers": "python_version >= '3.8'",
|
||||
"version": "==2.6.2"
|
||||
"version": "==2.6.3"
|
||||
},
|
||||
"pydantic-core": {
|
||||
"hashes": [
|
||||
@@ -1442,12 +1645,12 @@
|
||||
},
|
||||
"python-dateutil": {
|
||||
"hashes": [
|
||||
"sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86",
|
||||
"sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"
|
||||
"sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3",
|
||||
"sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"
|
||||
],
|
||||
"index": "pypi",
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==2.8.2"
|
||||
"version": "==2.9.0.post0"
|
||||
},
|
||||
"python-dotenv": {
|
||||
"hashes": [
|
||||
@@ -1629,11 +1832,11 @@
|
||||
"hiredis"
|
||||
],
|
||||
"hashes": [
|
||||
"sha256:0dab495cd5753069d3bc650a0dde8a8f9edde16fc5691b689a566eda58100d0f",
|
||||
"sha256:ed4802971884ae19d640775ba3b03aa2e7bd5e8fb8dfaed2decce4d0fc48391f"
|
||||
"sha256:3f82cc80d350e93042c8e6e7a5d0596e4dd68715babffba79492733e1f367037",
|
||||
"sha256:4caa8e1fcb6f3c0ef28dba99535101d80934b7d4cd541bbb47f4a3826ee472d1"
|
||||
],
|
||||
"markers": "python_version >= '3.7'",
|
||||
"version": "==5.0.1"
|
||||
"version": "==5.0.2"
|
||||
},
|
||||
"requests": {
|
||||
"hashes": [
|
||||
@@ -1703,11 +1906,11 @@
|
||||
},
|
||||
"sniffio": {
|
||||
"hashes": [
|
||||
"sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101",
|
||||
"sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"
|
||||
"sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2",
|
||||
"sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"
|
||||
],
|
||||
"markers": "python_version >= '3.7'",
|
||||
"version": "==1.3.0"
|
||||
"version": "==1.3.1"
|
||||
},
|
||||
"soupsieve": {
|
||||
"hashes": [
|
||||
@@ -1815,19 +2018,19 @@
|
||||
},
|
||||
"twisted": {
|
||||
"hashes": [
|
||||
"sha256:4ae8bce12999a35f7fe6443e7f1893e6fe09588c8d2bed9c35cdce8ff2d5b444",
|
||||
"sha256:987847a0790a2c597197613686e2784fd54167df3a55d0fb17c8412305d76ce5"
|
||||
"sha256:039f2e6a49ab5108abd94de187fa92377abe5985c7a72d68d0ad266ba19eae63",
|
||||
"sha256:6b38b6ece7296b5e122c9eb17da2eeab3d98a198f50ca9efd00fb03e5b4fd4ae"
|
||||
],
|
||||
"markers": "python_full_version >= '3.8.0'",
|
||||
"version": "==23.10.0"
|
||||
"version": "==24.3.0"
|
||||
},
|
||||
"typing-extensions": {
|
||||
"hashes": [
|
||||
"sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783",
|
||||
"sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd"
|
||||
"sha256:69b1a937c3a517342112fb4c6df7e72fc39a38e7891a5730ed4985b5214b5475",
|
||||
"sha256:b0abd7c89e8fb96f98db18d86106ff1d90ab692004eb746cf6eda2682f91b3cb"
|
||||
],
|
||||
"markers": "python_version >= '3.8'",
|
||||
"version": "==4.9.0"
|
||||
"version": "==4.10.0"
|
||||
},
|
||||
"tzlocal": {
|
||||
"hashes": [
|
||||
|
||||
+27
-3
@@ -62,6 +62,18 @@ class Streams(BaseModel):
|
||||
streams: Optional[list[Stream]] = []
|
||||
|
||||
|
||||
class QBittorrentConfig(BaseModel):
|
||||
qbittorrent_url: str
|
||||
qbittorrent_username: str
|
||||
qbittorrent_password: str
|
||||
seeding_time_limit: int = 1440 # 24 hours
|
||||
seeding_ratio_limit: float = 1.0
|
||||
play_video_after: int = Field(default=100, le=100, ge=0)
|
||||
webdav_url: str
|
||||
webdav_username: str
|
||||
webdav_password: str
|
||||
|
||||
|
||||
class StreamingProvider(BaseModel):
|
||||
service: Literal[
|
||||
"realdebrid",
|
||||
@@ -72,17 +84,29 @@ class StreamingProvider(BaseModel):
|
||||
"pikpak",
|
||||
"torbox",
|
||||
"premiumize",
|
||||
"qbittorrent",
|
||||
]
|
||||
token: str | None = None
|
||||
username: str | None = None
|
||||
password: str | None = None
|
||||
enable_watchlist_catalogs: bool = True
|
||||
qbittorrent_config: QBittorrentConfig | None = None
|
||||
|
||||
@model_validator(mode="after")
|
||||
def validate_token_or_username_password(self) -> "StreamingProvider":
|
||||
# validating the token or username and password
|
||||
if not self.token and not self.username and not self.password:
|
||||
raise ValueError("Either token or username and password must be present")
|
||||
# validating the token or (username and password) or qbittorrent_config
|
||||
if self.service is None:
|
||||
return self
|
||||
if self.service == "pikpak":
|
||||
if not self.username or not self.password:
|
||||
raise ValueError("Username and password are required")
|
||||
elif self.service == "qbittorrent":
|
||||
if not self.qbittorrent_config:
|
||||
raise ValueError("qbittorrent_config is required")
|
||||
else:
|
||||
if not self.token or not (self.username and self.password):
|
||||
raise ValueError("Token or username and password are required")
|
||||
|
||||
return self
|
||||
|
||||
class Config:
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -39,6 +39,7 @@
|
||||
<option value="debridlink" {% if user_data.streaming_provider.service=='debridlink' %}selected{% endif %}>Debrid-Link (Premium)</option>
|
||||
<option value="premiumize" {% if user_data.streaming_provider.service=='premiumize' %}selected{% endif %}>Premiumize (Premium)</option>
|
||||
<option value="alldebrid" {% if user_data.streaming_provider.service=='alldebrid' %}selected{% endif %}>AllDebrid - Local Only - (Premium)</option>
|
||||
<option value="qbittorrent" {% if user_data.streaming_provider.service=='qbittorrent' %}selected{% endif %}>qBittorrent - WebDav (Free/Premium)</option>
|
||||
</select>
|
||||
|
||||
<!-- Affiliate Signup Links -->
|
||||
@@ -100,6 +101,74 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- qBittorrent Configuration -->
|
||||
<div id="qbittorrent_config" class="mt-3" style="display:none">
|
||||
<h5 class="mt-3">qBittorrent Configuration</h5>
|
||||
<div class="mb-3">
|
||||
<label for="qbittorrent_url">qBittorrent URL:</label>
|
||||
<input class="form-control" type="text" id="qbittorrent_url" name="qbittorrent_url" placeholder="Enter qBittorrent URL"
|
||||
value="{{ user_data.streaming_provider.qbittorrent_config.qbittorrent_url if user_data.streaming_provider and user_data.streaming_provider.qbittorrent_config else '' }}">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="qbittorrent_username">qBittorrent Username: <span class="bi bi-question-circle" data-bs-toggle="tooltip" data-bs-placement="top"
|
||||
title="Enter the qBittorrent username for the qBittorrent server. if Authentication not required, leave it blank."></span></label>
|
||||
<input class="form-control" type="text" id="qbittorrent_username" name="qbittorrent_username" placeholder="Enter Username"
|
||||
value="{{ user_data.streaming_provider.qbittorrent_config.qbittorrent_username if user_data.streaming_provider.qbittorrent_config else '' }}">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="qbittorrent_password">qBittorrent Password: <span class="bi bi-question-circle" data-bs-toggle="tooltip" data-bs-placement="top"
|
||||
title="Enter the qBittorrent password for the qBittorrent server. if Authentication not required, leave it blank."></span></label>
|
||||
<div class="input-group">
|
||||
<input class="form-control" type="password" id="qbittorrent_password" name="qbittorrent_password" placeholder="Enter qBittorrent Password">
|
||||
<button class="btn btn-outline-secondary" type="button" id="toggleQbittorrentPassword">
|
||||
<span id="toggleQbittorrentPasswordIcon" class="bi bi-eye"></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="seeding_time_limit">Seeding Time Limit (minutes): <span class="bi bi-question-circle" data-bs-toggle="tooltip" data-bs-placement="top"
|
||||
title="Set the seeding time limit for torrents in minutes."></span></label>
|
||||
<input class="form-control" type="number" id="seeding_time_limit" name="seeding_time_limit" placeholder="Enter Seeding Time Limit"
|
||||
value="{{ user_data.streaming_provider.qbittorrent_config.seeding_time_limit if user_data.streaming_provider.qbittorrent_config else 1440 }}">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="seeding_ratio_limit">Seeding Ratio Limit: <span class="bi bi-question-circle" data-bs-toggle="tooltip" data-bs-placement="top"
|
||||
title="Set the seeding ratio limit for torrents."></span></label>
|
||||
<input class="form-control" type="text" id="seeding_ratio_limit" name="seeding_ratio_limit" placeholder="Enter Seeding Ratio Limit"
|
||||
value="{{ user_data.streaming_provider.qbittorrent_config.seeding_ratio_limit if user_data.streaming_provider.qbittorrent_config else 1.0 }}">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="play_video_after_download">Play Video After Download: <span class="bi bi-question-circle" data-bs-toggle="tooltip" data-bs-placement="top"
|
||||
title="Enter the percentage of the video to be downloaded before playing."></span></label>
|
||||
<input class="form-control" type="number" id="play_video_after_download" name="play_video_after_download" placeholder="Enter Play Video After Download"
|
||||
value="{{ user_data.streaming_provider.qbittorrent_config.play_video_after_download if user_data.streaming_provider.qbittorrent_config else 100 }}">
|
||||
|
||||
<!-- WebDAV Configuration -->
|
||||
<h5 class="mt-3">WebDAV Configuration</h5>
|
||||
<div class="mb-3">
|
||||
<label for="webdav_url">WebDAV URL: <span class="bi bi-question-circle" data-bs-toggle="tooltip" data-bs-placement="top"
|
||||
title="Enter the WebDAV URL for the qBittorrent WebDAV server with the format: http{s}://host/path."></span></label>
|
||||
<input class="form-control" type="text" id="webdav_url" name="webdav_url" placeholder="Enter WebDAV URL"
|
||||
value="{{ user_data.streaming_provider.qbittorrent_config.webdav_url if user_data.streaming_provider.qbittorrent_config else '' }}">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="webdav_username">WebDAV Username: <span class="bi bi-question-circle" data-bs-toggle="tooltip" data-bs-placement="top"
|
||||
title="Enter the WebDAV username for the WebDAV server. if Authentication not required, leave it blank."></span></label>
|
||||
<input class="form-control" type="text" id="webdav_username" name="webdav_username" placeholder="Enter WebDAV Username"
|
||||
value="{{ user_data.streaming_provider.qbittorrent_config.webdav_username if user_data.streaming_provider.qbittorrent_config else '' }}">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="webdav_password">WebDAV Password: <span class="bi bi-question-circle" data-bs-toggle="tooltip" data-bs-placement="top"
|
||||
title="Enter the WebDAV password for the WebDAV server. if Authentication not required, leave it blank."></span></label>
|
||||
<div class="input-group">
|
||||
<input class="form-control" type="password" id="webdav_password" name="webdav_password" placeholder="Enter WebDAV Password">
|
||||
<button class="btn btn-outline-secondary" type="button" id="toggleWebdavPassword">
|
||||
<span id="toggleWebdavPasswordIcon" class="bi bi-eye"></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Catalog Configuration -->
|
||||
|
||||
@@ -177,9 +177,15 @@ function updateProviderFields(isChangeEvent = false) {
|
||||
if (servicesRequiringCredentials.includes(provider)) {
|
||||
setElementDisplay('credentials', 'block');
|
||||
setElementDisplay('token_input', 'none');
|
||||
setElementDisplay('qbittorrent_config', 'none');
|
||||
} else if (provider === 'qbittorrent') {
|
||||
setElementDisplay('qbittorrent_config', 'block');
|
||||
setElementDisplay('credentials', 'none');
|
||||
setElementDisplay('token_input', 'none');
|
||||
} else {
|
||||
setElementDisplay('credentials', 'none');
|
||||
setElementDisplay('token_input', 'block');
|
||||
setElementDisplay('qbittorrent_config', 'none');
|
||||
}
|
||||
setElementDisplay('watchlist_section', 'block');
|
||||
watchlistLabel.textContent = `Enable ${provider.charAt(0).toUpperCase() + provider.slice(1)} Watchlist`;
|
||||
@@ -187,6 +193,7 @@ function updateProviderFields(isChangeEvent = false) {
|
||||
setElementDisplay('credentials', 'none');
|
||||
setElementDisplay('token_input', 'none');
|
||||
setElementDisplay('watchlist_section', 'none');
|
||||
setElementDisplay('qbittorrent_config', 'none');
|
||||
}
|
||||
|
||||
// Reset the fields only if this is triggered by an onchange event
|
||||
@@ -260,6 +267,23 @@ function getUserData() {
|
||||
validateInput('password', document.getElementById('password').value);
|
||||
streamingProviderData.username = document.getElementById('username').value;
|
||||
streamingProviderData.password = document.getElementById('password').value;
|
||||
} else if (provider === 'qbittorrent') {
|
||||
streamingProviderData.qbittorrent_config = {
|
||||
qbittorrent_url: document.getElementById('qbittorrent_url').value,
|
||||
qbittorrent_username: document.getElementById('qbittorrent_username').value,
|
||||
qbittorrent_password: document.getElementById('qbittorrent_password').value,
|
||||
seeding_time_limit: parseInt(document.getElementById('seeding_time_limit').value, 10),
|
||||
seeding_ratio_limit: parseFloat(document.getElementById('seeding_ratio_limit').value),
|
||||
play_video_after: parseInt(document.getElementById('play_video_after_download').value, 10),
|
||||
webdav_url: document.getElementById('webdav_url').value,
|
||||
webdav_username: document.getElementById('webdav_username').value,
|
||||
webdav_password: document.getElementById('webdav_password').value,
|
||||
};
|
||||
// Validate qBittorrent-specific inputs
|
||||
validateInput('qbittorrent_url', streamingProviderData.qbittorrent_config.qbittorrent_url);
|
||||
validateInput('seeding_time_limit', !isNaN(streamingProviderData.qbittorrent_config.seeding_time_limit));
|
||||
validateInput('seeding_ratio_limit', !isNaN(streamingProviderData.qbittorrent_config.seeding_ratio_limit));
|
||||
validateInput('play_video_after_download', !isNaN(streamingProviderData.qbittorrent_config.play_video_after));
|
||||
} else {
|
||||
validateInput('provider_token', document.getElementById('provider_token').value);
|
||||
streamingProviderData.token = document.getElementById('provider_token').value;
|
||||
@@ -413,6 +437,8 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
|
||||
setupPasswordToggle('password', 'togglePassword', 'togglePasswordIcon');
|
||||
setupPasswordToggle('api_password', 'toggleApiPassword', 'toggleApiPasswordIcon');
|
||||
setupPasswordToggle('qbittorrent_password', 'toggleQbittorrentPassword', 'toggleQbittorrentPasswordIcon');
|
||||
setupPasswordToggle('webdav_password', 'toggleWebdavPassword', 'toggleWebdavPasswordIcon');
|
||||
|
||||
if (navigator.share) {
|
||||
document.getElementById('shareBtn').style.display = 'block';
|
||||
|
||||
@@ -0,0 +1,332 @@
|
||||
import asyncio
|
||||
from datetime import timedelta
|
||||
from os import path
|
||||
from urllib.parse import urljoin, urlparse, quote
|
||||
|
||||
from aiohttp import ClientConnectorError
|
||||
from aioqbt.api import AddFormBuilder, TorrentInfo
|
||||
from aioqbt.client import create_client, APIClient
|
||||
from aioqbt.exc import LoginError, AddTorrentError
|
||||
from aiowebdav.client import Client as WebDavClient
|
||||
from aiowebdav.exceptions import RemoteResourceNotFound, NoConnection
|
||||
from thefuzz import fuzz
|
||||
|
||||
from db.models import TorrentStreams
|
||||
from db.schemas import UserData
|
||||
from streaming_providers.exceptions import ProviderException
|
||||
|
||||
|
||||
async def check_torrent_status(
|
||||
qbittorrent: APIClient, info_hash: str
|
||||
) -> TorrentInfo | None:
|
||||
"""Checks the status of a torrent with a given info_hash or torrent_name."""
|
||||
available_torrents = await qbittorrent.torrents.info(hashes=[info_hash])
|
||||
return available_torrents[0] if available_torrents else None
|
||||
|
||||
|
||||
async def add_magnet(
|
||||
qbittorrent: APIClient, magnet_link: str, info_hash: str, user_data: UserData
|
||||
):
|
||||
"""Adds a magnet link to the qbittorrent server."""
|
||||
try:
|
||||
await qbittorrent.torrents.add(
|
||||
AddFormBuilder.with_client(qbittorrent)
|
||||
.include_url(magnet_link)
|
||||
.savepath(info_hash)
|
||||
.seeding_time_limit(
|
||||
timedelta(
|
||||
minutes=user_data.streaming_provider.qbittorrent_config.seeding_time_limit
|
||||
)
|
||||
)
|
||||
.ratio_limit(
|
||||
user_data.streaming_provider.qbittorrent_config.seeding_ratio_limit
|
||||
)
|
||||
.build()
|
||||
)
|
||||
except AddTorrentError:
|
||||
raise ProviderException(
|
||||
"Failed to add the torrent to qBittorrent", "add_torrent_failed.mp4"
|
||||
)
|
||||
|
||||
|
||||
async def wait_for_torrent_to_complete(
|
||||
qbittorrent: APIClient,
|
||||
info_hash: str,
|
||||
play_video_after: int,
|
||||
max_retries: int,
|
||||
retry_interval: int,
|
||||
):
|
||||
"""Waits for a torrent with the given info_hash to complete downloading."""
|
||||
retries = 0
|
||||
while retries < max_retries:
|
||||
torrent = await check_torrent_status(qbittorrent, info_hash)
|
||||
if torrent and torrent.progress * 100 >= play_video_after:
|
||||
return
|
||||
await asyncio.sleep(retry_interval)
|
||||
retries += 1
|
||||
|
||||
raise ProviderException("Torrent not downloaded yet.", "torrent_not_downloaded.mp4")
|
||||
|
||||
|
||||
async def get_files_from_folder(
|
||||
webdav: WebDavClient, base_url_path: str, root_path: str
|
||||
) -> list[dict]:
|
||||
"""Helper function to recursively get files from a folder including subfolders."""
|
||||
try:
|
||||
contents = await webdav.list(root_path, get_info=True)
|
||||
except RemoteResourceNotFound:
|
||||
return []
|
||||
|
||||
files = []
|
||||
for item in contents:
|
||||
# skip the root folder
|
||||
if item["path"].endswith(root_path.removesuffix("/") + "/"):
|
||||
continue
|
||||
|
||||
if item["isdir"]:
|
||||
files.extend(
|
||||
await get_files_from_folder(
|
||||
webdav, base_url_path, item["path"].removeprefix(base_url_path)
|
||||
)
|
||||
)
|
||||
else:
|
||||
item.update(
|
||||
{
|
||||
"name": path.basename(item["path"]),
|
||||
"size": int(item["size"]),
|
||||
}
|
||||
)
|
||||
files.append(item)
|
||||
|
||||
return files
|
||||
|
||||
|
||||
async def find_file_in_folder_tree(
|
||||
webdav: WebDavClient, user_data: UserData, info_hash: str, filename: str
|
||||
) -> dict | None:
|
||||
base_url_path = urlparse(
|
||||
user_data.streaming_provider.qbittorrent_config.webdav_url
|
||||
).path
|
||||
|
||||
files = await get_files_from_folder(webdav, base_url_path, info_hash)
|
||||
if not files:
|
||||
return None
|
||||
|
||||
exact_match = next((file for file in files if file["name"] == filename), None)
|
||||
if exact_match:
|
||||
return exact_match
|
||||
|
||||
# Fuzzy matching as a fallback
|
||||
for file in files:
|
||||
file["fuzzy_ratio"] = fuzz.ratio(filename, file["name"])
|
||||
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["content_type"]:
|
||||
raise ProviderException(
|
||||
"No matching file available for this torrent", "no_matching_file.mp4"
|
||||
)
|
||||
|
||||
return selected_file
|
||||
|
||||
|
||||
async def initialize_qbittorrent(user_data: UserData):
|
||||
try:
|
||||
qbittorrent = await create_client(
|
||||
user_data.streaming_provider.qbittorrent_config.qbittorrent_url
|
||||
+ "/api/v2/",
|
||||
username=user_data.streaming_provider.qbittorrent_config.qbittorrent_username,
|
||||
password=user_data.streaming_provider.qbittorrent_config.qbittorrent_password,
|
||||
)
|
||||
except LoginError:
|
||||
raise ProviderException(
|
||||
"Invalid qBittorrent credentials", "invalid_credentials.mp4"
|
||||
)
|
||||
except ClientConnectorError:
|
||||
raise ProviderException(
|
||||
"Failed to connect to qBittorrent", "qbittorrent_error.mp4"
|
||||
)
|
||||
return qbittorrent
|
||||
|
||||
|
||||
async def initialize_webdav(user_data: UserData):
|
||||
webdav_options = {
|
||||
"webdav_hostname": user_data.streaming_provider.qbittorrent_config.webdav_url,
|
||||
"webdav_login": user_data.streaming_provider.qbittorrent_config.webdav_username,
|
||||
"webdav_password": user_data.streaming_provider.qbittorrent_config.webdav_password,
|
||||
}
|
||||
|
||||
webdav = WebDavClient(webdav_options)
|
||||
|
||||
try:
|
||||
if not await webdav.check():
|
||||
raise ProviderException(
|
||||
"Invalid WebDAV credentials", "invalid_credentials.mp4"
|
||||
)
|
||||
except NoConnection:
|
||||
raise ProviderException(
|
||||
"Failed to connect to the WebDAV server", "webdav_error.mp4"
|
||||
)
|
||||
return webdav
|
||||
|
||||
|
||||
async def handle_torrent_status(
|
||||
qbittorrent: APIClient,
|
||||
info_hash: str,
|
||||
play_video_after: int,
|
||||
max_retries: int,
|
||||
retry_interval: int,
|
||||
):
|
||||
torrent = await check_torrent_status(qbittorrent, info_hash)
|
||||
if not torrent:
|
||||
return
|
||||
|
||||
if torrent.progress * 100 >= play_video_after:
|
||||
return torrent
|
||||
|
||||
await wait_for_torrent_to_complete(
|
||||
qbittorrent, info_hash, play_video_after, max_retries, retry_interval
|
||||
)
|
||||
return torrent
|
||||
|
||||
|
||||
async def retrieve_or_download_file(
|
||||
qbittorrent: APIClient,
|
||||
webdav: WebDavClient,
|
||||
user_data: UserData,
|
||||
play_video_after: int,
|
||||
filename: str,
|
||||
magnet_link: str,
|
||||
info_hash: str,
|
||||
max_retries: int,
|
||||
retry_interval: int,
|
||||
):
|
||||
selected_file = await find_file_in_folder_tree(
|
||||
webdav, user_data, info_hash, filename
|
||||
)
|
||||
if not selected_file:
|
||||
await add_magnet(qbittorrent, magnet_link, info_hash, user_data)
|
||||
await wait_for_torrent_to_complete(
|
||||
qbittorrent, info_hash, play_video_after, max_retries, retry_interval
|
||||
)
|
||||
selected_file = await find_file_in_folder_tree(
|
||||
webdav, user_data, info_hash, filename
|
||||
)
|
||||
if selected_file is None:
|
||||
raise ProviderException(
|
||||
"No matching file available for this torrent", "no_matching_file.mp4"
|
||||
)
|
||||
return selected_file
|
||||
|
||||
|
||||
def generate_webdav_url(user_data: UserData, selected_file: dict) -> str:
|
||||
webdav_url = user_data.streaming_provider.qbittorrent_config.webdav_url
|
||||
webdav_username = user_data.streaming_provider.qbittorrent_config.webdav_username
|
||||
webdav_password = user_data.streaming_provider.qbittorrent_config.webdav_password
|
||||
|
||||
# Check if credentials are provided
|
||||
if webdav_username and webdav_password:
|
||||
# URL encode the username and password
|
||||
webdav_username = quote(webdav_username, safe="")
|
||||
webdav_password = quote(webdav_password, safe="")
|
||||
parsed_url = urlparse(webdav_url)
|
||||
netloc = f"{webdav_username}:{webdav_password}@{parsed_url.netloc}"
|
||||
base_url = parsed_url._replace(netloc=netloc).geturl()
|
||||
else:
|
||||
base_url = webdav_url
|
||||
|
||||
return urljoin(
|
||||
base_url,
|
||||
quote(selected_file["path"]), # Ensure the path is URL encoded
|
||||
)
|
||||
|
||||
|
||||
async def get_direct_link_from_qbittorrent(
|
||||
info_hash: str,
|
||||
magnet_link: str,
|
||||
user_data: UserData,
|
||||
stream: TorrentStreams,
|
||||
filename: str,
|
||||
max_retries=5,
|
||||
retry_interval=5,
|
||||
) -> str:
|
||||
"""Get the direct link from the qBittorrent server."""
|
||||
qbittorrent = webdav = None
|
||||
play_video_after = user_data.streaming_provider.qbittorrent_config.play_video_after
|
||||
try:
|
||||
qbittorrent = await initialize_qbittorrent(user_data)
|
||||
webdav = await initialize_webdav(user_data)
|
||||
await handle_torrent_status(
|
||||
qbittorrent,
|
||||
info_hash,
|
||||
play_video_after,
|
||||
max_retries,
|
||||
retry_interval,
|
||||
)
|
||||
|
||||
selected_file = await retrieve_or_download_file(
|
||||
qbittorrent,
|
||||
webdav,
|
||||
user_data,
|
||||
play_video_after,
|
||||
filename or stream.torrent_name,
|
||||
magnet_link,
|
||||
info_hash,
|
||||
max_retries,
|
||||
retry_interval,
|
||||
)
|
||||
|
||||
return generate_webdav_url(user_data, selected_file)
|
||||
finally:
|
||||
if qbittorrent:
|
||||
await qbittorrent.close()
|
||||
if webdav:
|
||||
await webdav.close()
|
||||
|
||||
|
||||
async def update_qbittorrent_cache_status(
|
||||
streams: list[TorrentStreams], user_data: UserData
|
||||
):
|
||||
"""Updates the cache status of streams based on qBittorrent's instant availability."""
|
||||
qbittorrent = None
|
||||
try:
|
||||
qbittorrent = await initialize_qbittorrent(user_data)
|
||||
torrents_info = await qbittorrent.torrents.info(
|
||||
hashes=[stream.id for stream in streams]
|
||||
)
|
||||
torrents_dict = {torrent.hash: torrent.progress for torrent in torrents_info}
|
||||
except ProviderException:
|
||||
return
|
||||
finally:
|
||||
if qbittorrent:
|
||||
await qbittorrent.close()
|
||||
|
||||
for stream in streams:
|
||||
stream.cached = torrents_dict.get(stream.id, 0) == 1
|
||||
|
||||
|
||||
async def fetch_info_hashes_from_webdav(
|
||||
user_data: UserData,
|
||||
) -> list[str]:
|
||||
"""Fetches the info_hashes from directories in the WebDAV server that are named after the torrent's info hashes."""
|
||||
webdav = None
|
||||
try:
|
||||
webdav = await initialize_webdav(user_data)
|
||||
directories = await webdav.list()
|
||||
|
||||
# Filter out directory names that match the length of an info hash (40 chars) plus the trailing slash
|
||||
info_hashes = [
|
||||
dirname.removesuffix("/")
|
||||
for dirname in directories
|
||||
if len(dirname) == 41 and dirname.endswith("/")
|
||||
]
|
||||
|
||||
return info_hashes
|
||||
except ProviderException:
|
||||
return []
|
||||
finally:
|
||||
if webdav:
|
||||
await webdav.close()
|
||||
@@ -23,6 +23,7 @@ from streaming_providers.realdebrid.utils import get_direct_link_from_realdebrid
|
||||
from streaming_providers.seedr.api import router as seedr_router
|
||||
from streaming_providers.seedr.utils import get_direct_link_from_seedr
|
||||
from streaming_providers.torbox.utils import get_direct_link_from_torbox
|
||||
from streaming_providers.qbittorrent.utils import get_direct_link_from_qbittorrent
|
||||
from utils import crypto, torrent, wrappers, const
|
||||
|
||||
router = APIRouter()
|
||||
@@ -85,6 +86,10 @@ async def streaming_provider_endpoint(
|
||||
video_url = get_direct_link_from_premiumize(
|
||||
info_hash, magnet_link, user_data, stream.torrent_name, filename, 1, 0
|
||||
)
|
||||
elif user_data.streaming_provider.service == "qbittorrent":
|
||||
video_url = await get_direct_link_from_qbittorrent(
|
||||
info_hash, magnet_link, user_data, stream, filename, 1, 0
|
||||
)
|
||||
else:
|
||||
video_url = get_direct_link_from_debridlink(
|
||||
info_hash, magnet_link, user_data, filename, stream.file_index, 1, 0
|
||||
|
||||
@@ -28,6 +28,10 @@ from streaming_providers.premiumize.utils import (
|
||||
update_pm_cache_status,
|
||||
fetch_downloaded_info_hashes_from_premiumize,
|
||||
)
|
||||
from streaming_providers.qbittorrent.utils import (
|
||||
update_qbittorrent_cache_status,
|
||||
fetch_info_hashes_from_webdav,
|
||||
)
|
||||
from streaming_providers.realdebrid.utils import (
|
||||
update_rd_cache_status,
|
||||
fetch_downloaded_info_hashes_from_rd,
|
||||
@@ -57,6 +61,7 @@ CACHE_UPDATE_FUNCTIONS = {
|
||||
"seedr": update_seedr_cache_status,
|
||||
"torbox": update_torbox_cache_status,
|
||||
"premiumize": update_pm_cache_status,
|
||||
"qbittorrent": update_qbittorrent_cache_status,
|
||||
}
|
||||
|
||||
# Define provider-specific downloaded info hashes fetch functions
|
||||
@@ -69,6 +74,7 @@ FETCH_DOWNLOADED_INFO_HASHES_FUNCTIONS = {
|
||||
"seedr": fetch_downloaded_info_hashes_from_seedr,
|
||||
"torbox": fetch_downloaded_info_hashes_from_torbox,
|
||||
"premiumize": fetch_downloaded_info_hashes_from_premiumize,
|
||||
"qbittorrent": fetch_info_hashes_from_webdav,
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user