diff --git a/Dockerfile b/Dockerfile
index a7adc0c..2bcc30a 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -44,6 +44,11 @@ ENV LANG='en_US.UTF-8' LANGUAGE='en_US:en' LC_ALL='en_US.UTF-8' TZ='Asia/Kolkata
ENV DEBIAN_FRONTEND='noninteractive'
RUN apt-get update && apt-get upgrade -y && \
apt-get install -y libcrypto++-dev libfreeimage-dev && \
+ apt-get install -y software-properties-common && \
+ add-apt-repository ppa:qbittorrent-team/qbittorrent-stable && \
+ apt-get install -y qbittorrent-nox && \
+ apt-get purge -y software-properties-common && \
+ apt-get autoremove -y && \
apt-get clean && rm -rf /var/lib/apt/lists/*
RUN pip3 install --no-cache-dir /root/mega-sdk/bindings/python/dist/megasdk-*.whl
WORKDIR /usr/src/app
diff --git a/Pipfile b/Pipfile
index 164a51d..1d3659c 100644
--- a/Pipfile
+++ b/Pipfile
@@ -11,6 +11,8 @@ google-auth-oauthlib = "==0.4.4"
psutil = "==5.8.0"
python-magic = "==0.4.24"
python-telegram-bot = "==13.5"
+qbittorrent-api = "==2021.8.23"
+torrentool = "==1.1.1"
youtube_dl = "==2021.6.6"
[dev-packages]
diff --git a/Pipfile.lock b/Pipfile.lock
index dd32fcf..6591bf5 100644
--- a/Pipfile.lock
+++ b/Pipfile.lock
@@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
- "sha256": "bb71303345265a6ced1d3002a7f1be6e1a9f69aad5c9116895db167bc0bf8157"
+ "sha256": "6e3dadabbe124dd4569f9cdfad3ea9905ebcabf9e6a2763a5e79f9dfff6452ee"
},
"pipfile-spec": 6,
"requires": {
@@ -62,34 +62,34 @@
},
"cachetools": {
"hashes": [
- "sha256:2cc0b89715337ab6dbba85b5b50effe2b0c74e035d83ee8ed637cf52f12ae001",
- "sha256:61b5ed1e22a0924aed1d23b478f37e8d52549ff8a961de2909c69bf950020cff"
+ "sha256:89ea6f1b638d5a73a4f9226be57ac5e4f399d22770b92355f92dcb0f7f001693",
+ "sha256:92971d3cb7d2a97efff7c7bb1657f21a8f5fb309a37530537c71b1774189f2d1"
],
"markers": "python_version ~= '3.5'",
- "version": "==4.2.2"
+ "version": "==4.2.4"
},
"certifi": {
"hashes": [
- "sha256:2bbf76fd432960138b3ef6dda3dde0544f27cbf8546c458e60baf371917ba9ee",
- "sha256:50b1e4f8446b06f41be7dd6338db18e0990601dce795c2b1686458aa7e8fa7d8"
+ "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872",
+ "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"
],
- "version": "==2021.5.30"
+ "version": "==2021.10.8"
},
"charset-normalizer": {
"hashes": [
- "sha256:0c8911edd15d19223366a194a513099a302055a962bca2cec0f54b8b63175d8b",
- "sha256:f23667ebe1084be45f6ae0538e4a5a865206544097e4e8bbcacf42cd02a348f3"
+ "sha256:1eecaa09422db5be9e29d7fc65664e6c33bd06f9ced7838578ba40d58bdf3721",
+ "sha256:b0b883e8e874edfdece9c28f314e3dd5badf067342e42fb162203335ae61aa2c"
],
"markers": "python_version >= '3'",
- "version": "==2.0.4"
+ "version": "==2.0.9"
},
"google-api-core": {
"hashes": [
- "sha256:108cf94336aed7e614eafc53933ef02adf63b9f0fd87e8f8212acaa09eaca456",
- "sha256:1d63e2b28057d79d64795c9a70abcecb5b7e96da732d011abf09606a39b48701"
+ "sha256:c77ffc8b4981b44efdb9d68431fd96d21dbd39545c29552bbe79b9b7dd2c3689",
+ "sha256:ed59c6a695a81f601e4ba0f37ca9dbde3c43b3309e161a1a8946f266db4a0c4e"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'",
- "version": "==1.31.1"
+ "version": "==1.31.4"
},
"google-api-python-client": {
"hashes": [
@@ -101,11 +101,11 @@
},
"google-auth": {
"hashes": [
- "sha256:bd6aa5916970a823e76ffb3d5c3ad3f0bedafca0a7fa53bc15149ab21cb71e05",
- "sha256:f1094088bae046fb06f3d1a3d7df14717e8d959e9105b79c57725bd4e17597a2"
+ "sha256:997516b42ecb5b63e8d80f5632c1a61dddf41d2a4c2748057837e06e00014258",
+ "sha256:b7033be9028c188ee30200b204ea00ed82ea1162e8ac1df4aa6ded19a191d88e"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'",
- "version": "==1.34.0"
+ "version": "==1.35.0"
},
"google-auth-httplib2": {
"hashes": [
@@ -133,18 +133,19 @@
},
"httplib2": {
"hashes": [
- "sha256:0b12617eeca7433d4c396a100eaecfa4b08ee99aa881e6df6e257a7aad5d533d",
- "sha256:2ad195faf9faf079723f6714926e9a9061f694d07724b846658ce08d40f522b4"
+ "sha256:6b937120e7d786482881b44b8eec230c1ee1c5c1d06bce8cc865f25abbbf713b",
+ "sha256:e404681d2fbcec7506bcb52c503f2b021e95bee0ef7d01e5c221468a2406d8dc"
],
- "version": "==0.19.1"
+ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
+ "version": "==0.20.2"
},
"idna": {
"hashes": [
- "sha256:14475042e284991034cb48e06f6851428fb14c4dc953acd9be9a5e95c7b6dd7a",
- "sha256:467fbad99067910785144ce333826c71fb0e63a425657295239737f7ecd125f3"
+ "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff",
+ "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"
],
"markers": "python_version >= '3'",
- "version": "==3.2"
+ "version": "==3.3"
},
"loguru": {
"hashes": [
@@ -164,43 +165,41 @@
},
"packaging": {
"hashes": [
- "sha256:7dc96269f53a4ccec5c0670940a4281106dd0bb343f47b7471f779df49c2fbe7",
- "sha256:c86254f9220d55e31cc94d69bade760f0847da8000def4dfe1c6b872fd14ff14"
+ "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb",
+ "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"
],
"markers": "python_version >= '3.6'",
- "version": "==21.0"
+ "version": "==21.3"
},
"protobuf": {
"hashes": [
- "sha256:13ee7be3c2d9a5d2b42a1030976f760f28755fcf5863c55b1460fd205e6cd637",
- "sha256:145ce0af55c4259ca74993ddab3479c78af064002ec8227beb3d944405123c71",
- "sha256:14c1c9377a7ffbeaccd4722ab0aa900091f52b516ad89c4b0c3bb0a4af903ba5",
- "sha256:1556a1049ccec58c7855a78d27e5c6e70e95103b32de9142bae0576e9200a1b0",
- "sha256:26010f693b675ff5a1d0e1bdb17689b8b716a18709113288fead438703d45539",
- "sha256:2ae692bb6d1992afb6b74348e7bb648a75bb0d3565a3f5eea5bec8f62bd06d87",
- "sha256:2bfb815216a9cd9faec52b16fd2bfa68437a44b67c56bee59bc3926522ecb04e",
- "sha256:4ffbd23640bb7403574f7aff8368e2aeb2ec9a5c6306580be48ac59a6bac8bde",
- "sha256:59e5cf6b737c3a376932fbfb869043415f7c16a0cf176ab30a5bbc419cd709c1",
- "sha256:6902a1e4b7a319ec611a7345ff81b6b004b36b0d2196ce7a748b3493da3d226d",
- "sha256:6ce4d8bf0321e7b2d4395e253f8002a1a5ffbcfd7bcc0a6ba46712c07d47d0b4",
- "sha256:6d847c59963c03fd7a0cd7c488cadfa10cda4fff34d8bc8cba92935a91b7a037",
- "sha256:72804ea5eaa9c22a090d2803813e280fb273b62d5ae497aaf3553d141c4fdd7b",
- "sha256:7a4c97961e9e5b03a56f9a6c82742ed55375c4a25f2692b625d4087d02ed31b9",
- "sha256:85d6303e4adade2827e43c2b54114d9a6ea547b671cb63fafd5011dc47d0e13d",
- "sha256:8727ee027157516e2c311f218ebf2260a18088ffb2d29473e82add217d196b1c",
- "sha256:99938f2a2d7ca6563c0ade0c5ca8982264c484fdecf418bd68e880a7ab5730b1",
- "sha256:9b7a5c1022e0fa0dbde7fd03682d07d14624ad870ae52054849d8960f04bc764",
- "sha256:a22b3a0dbac6544dacbafd4c5f6a29e389a50e3b193e2c70dae6bbf7930f651d",
- "sha256:a38bac25f51c93e4be4092c88b2568b9f407c27217d3dd23c7a57fa522a17554",
- "sha256:a981222367fb4210a10a929ad5983ae93bd5a050a0824fc35d6371c07b78caf6",
- "sha256:ab6bb0e270c6c58e7ff4345b3a803cc59dbee19ddf77a4719c5b635f1d547aa8",
- "sha256:c56c050a947186ba51de4f94ab441d7f04fcd44c56df6e922369cc2e1a92d683",
- "sha256:e76d9686e088fece2450dbc7ee905f9be904e427341d289acbe9ad00b78ebd47",
- "sha256:ebcb546f10069b56dc2e3da35e003a02076aaa377caf8530fe9789570984a8d2",
- "sha256:f0e59430ee953184a703a324b8ec52f571c6c4259d496a19d1cabcdc19dabc62",
- "sha256:ffea251f5cd3c0b9b43c7a7a912777e0bc86263436a87c2555242a348817221b"
- ],
- "version": "==3.17.3"
+ "sha256:038daf4fa38a7e818dd61f51f22588d61755160a98db087a046f80d66b855942",
+ "sha256:28ccea56d4dc38d35cd70c43c2da2f40ac0be0a355ef882242e8586c6d66666f",
+ "sha256:36d90676d6f426718463fe382ec6274909337ca6319d375eebd2044e6c6ac560",
+ "sha256:3cd0458870ea7d1c58e948ac8078f6ba8a7ecc44a57e03032ed066c5bb318089",
+ "sha256:5935c8ce02e3d89c7900140a8a42b35bc037ec07a6aeb61cc108be8d3c9438a6",
+ "sha256:615b426a177780ce381ecd212edc1e0f70db8557ed72560b82096bd36b01bc04",
+ "sha256:62a8e4baa9cb9e064eb62d1002eca820857ab2138440cb4b3ea4243830f94ca7",
+ "sha256:655264ed0d0efe47a523e2255fc1106a22f6faab7cc46cfe99b5bae085c2a13e",
+ "sha256:6e8ea9173403219239cdfd8d946ed101f2ab6ecc025b0fda0c6c713c35c9981d",
+ "sha256:71b0250b0cfb738442d60cab68abc166de43411f2a4f791d31378590bfb71bd7",
+ "sha256:74f33edeb4f3b7ed13d567881da8e5a92a72b36495d57d696c2ea1ae0cfee80c",
+ "sha256:77d2fadcf369b3f22859ab25bd12bb8e98fb11e05d9ff9b7cd45b711c719c002",
+ "sha256:8b30a7de128c46b5ecb343917d9fa737612a6e8280f440874e5cc2ba0d79b8f6",
+ "sha256:8e51561d72efd5bd5c91490af1f13e32bcba8dab4643761eb7de3ce18e64a853",
+ "sha256:a529e7df52204565bcd33738a7a5f288f3d2d37d86caa5d78c458fa5fabbd54d",
+ "sha256:b691d996c6d0984947c4cf8b7ae2fe372d99b32821d0584f0b90277aa36982d3",
+ "sha256:d80f80eb175bf5f1169139c2e0c5ada98b1c098e2b3c3736667f28cbbea39fc8",
+ "sha256:d83e1ef8cb74009bebee3e61cc84b1c9cd04935b72bca0cbc83217d140424995",
+ "sha256:d8919368410110633717c406ab5c97e8df5ce93020cfcf3012834f28b1fab1ea",
+ "sha256:db3532d9f7a6ebbe2392041350437953b6d7a792de10e629c1e4f5a6b1fe1ac6",
+ "sha256:e7b24c11df36ee8e0c085e5b0dc560289e4b58804746fb487287dda51410f1e2",
+ "sha256:e7e8d2c20921f8da0dea277dfefc6abac05903ceac8e72839b2da519db69206b",
+ "sha256:e813b1c9006b6399308e917ac5d298f345d95bb31f46f02b60cd92970a9afa17",
+ "sha256:fd390367fc211cc0ffcf3a9e149dfeca78fecc62adb911371db0cec5c8b7472d"
+ ],
+ "markers": "python_version >= '3.1'",
+ "version": "==3.19.1"
},
"psutil": {
"hashes": [
@@ -274,11 +273,11 @@
},
"pyparsing": {
"hashes": [
- "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1",
- "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"
+ "sha256:04ff808a5b90911829c55c4e26f75fa5ca8a2f5f36aa3a51f68e27033341d3e4",
+ "sha256:d9bdec0013ef1eb5a84ab39a3b3868911598afa494f5faa038647101504e2b81"
],
- "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
- "version": "==2.4.7"
+ "markers": "python_version >= '3.1'",
+ "version": "==3.0.6"
},
"python-magic": {
"hashes": [
@@ -298,10 +297,26 @@
},
"pytz": {
"hashes": [
- "sha256:83a4a90894bf38e243cf052c8b58f381bfe9a7a483f6a9cab140bc7f702ac4da",
- "sha256:eb10ce3e7736052ed3623d49975ce333bcd712c7bb19a58b9e2089d4057d0798"
+ "sha256:3672058bc3453457b622aab7a1c3bfd5ab0bdae451512f6cf25f64ed37f5b87c",
+ "sha256:acad2d8b20a1af07d4e4c9d2e9285c5ed9104354062f275f3fcd88dcef4f1326"
+ ],
+ "version": "==2021.3"
+ },
+ "pytz-deprecation-shim": {
+ "hashes": [
+ "sha256:8314c9692a636c8eb3bda879b9f119e350e93223ae83e70e80c31675a0fdc1a6",
+ "sha256:af097bae1b616dde5c5744441e2ddc69e74dfdcb0c263129610d85b87445a59d"
+ ],
+ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'",
+ "version": "==0.1.0.post0"
+ },
+ "qbittorrent-api": {
+ "hashes": [
+ "sha256:470f9deb539853f952707372ad840306c88b65c4ae761a7566500018e863fc94",
+ "sha256:596ad4ee47e0a9f2be8af17fba62caf2ee666b41f256b26155aca0487e10df11"
],
- "version": "==2021.1"
+ "index": "pypi",
+ "version": "==2021.8.23"
},
"requests": {
"hashes": [
@@ -321,11 +336,19 @@
},
"rsa": {
"hashes": [
- "sha256:78f9a9bf4e7be0c5ded4583326e7461e3a3c5aae24073648b4bdfa797d78c9d2",
- "sha256:9d689e6ca1b3038bc82bf8d23e944b6b6037bc02301a574935b2dd946e0353b9"
+ "sha256:5c6bd9dc7a543b7fe4304a631f8a8a3b674e2bbfc49c2ae96200cdbe55df6b17",
+ "sha256:95c5d300c4e879ee69708c428ba566c59478fd653cc3a22243eeb8ed846950bb"
+ ],
+ "markers": "python_version >= '3.6'",
+ "version": "==4.8"
+ },
+ "setuptools": {
+ "hashes": [
+ "sha256:b4c634615a0cf5b02cf83c7bedffc8da0ca439f00e79452699454da6fbd4153d",
+ "sha256:feb5ff19b354cde9efd2344ef6d5e79880ce4be643037641b49508bbb850d060"
],
"markers": "python_version >= '3.6'",
- "version": "==4.7.2"
+ "version": "==59.4.0"
},
"six": {
"hashes": [
@@ -390,13 +413,29 @@
"markers": "python_version >= '3.5'",
"version": "==6.1"
},
+ "torrentool": {
+ "hashes": [
+ "sha256:11e647c4f40c14b7e9d6087602b2a91b54743a6ad0e6cc41904b196e975f0d56",
+ "sha256:2d892e8a02749fa9ea57fb2dcf69c1794305c069e973f4726a0a51dc66283e9c"
+ ],
+ "index": "pypi",
+ "version": "==1.1.1"
+ },
+ "tzdata": {
+ "hashes": [
+ "sha256:3eee491e22ebfe1e5cfcc97a4137cd70f092ce59144d81f8924a844de05ba8f5",
+ "sha256:68dbe41afd01b867894bbdfd54fa03f468cfa4f0086bfb4adcd8de8f24f3ee21"
+ ],
+ "markers": "python_version >= '3.6'",
+ "version": "==2021.5"
+ },
"tzlocal": {
"hashes": [
- "sha256:c736f2540713deb5938d789ca7c3fc25391e9a20803f05b60ec64987cf086559",
- "sha256:f4e6e36db50499e0d92f79b67361041f048e2609d166e93456b50746dc4aef12"
+ "sha256:0f28015ac68a5c067210400a9197fc5d36ba9bc3f8eaf1da3cbd59acdfed9e09",
+ "sha256:28ba8d9fcb6c9a782d6e0078b4f6627af1ea26aeaa32b4eab5324abc7df4149f"
],
"markers": "python_version >= '3.6'",
- "version": "==3.0"
+ "version": "==4.1"
},
"uritemplate": {
"hashes": [
@@ -408,11 +447,11 @@
},
"urllib3": {
"hashes": [
- "sha256:39fb8672126159acb139a7718dd10806104dec1e2f0f6c88aab05d17df10c8d4",
- "sha256:f57b4c16c62fa2760b7e3d97c35b255512fb6b59a259730f36ba32ce9f8e342f"
+ "sha256:4987c65554f7a2dbf30c18fd48778ef124af6fab771a377103da0585e2336ece",
+ "sha256:c4fdf4019605b6e5423637e01bc9fe4daef873709a7973e195ceba0a62bbc844"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4.0'",
- "version": "==1.26.6"
+ "version": "==1.26.7"
},
"websocket-client": {
"hashes": [
@@ -434,84 +473,89 @@
"develop": {
"bleach": {
"hashes": [
- "sha256:c1685a132e6a9a38bf93752e5faab33a9517a6c0bb2f37b785e47bf253bdb51d",
- "sha256:ffa9221c6ac29399cc50fcc33473366edd0cf8d5e2cbbbb63296dc327fb67cc8"
+ "sha256:0900d8b37eba61a802ee40ac0061f8c2b5dee29c1927dd1d233e075ebf5a71da",
+ "sha256:4d2651ab93271d1129ac9cbc679f524565cc8a1b791909c4a51eac4446a15994"
],
"markers": "python_version >= '3.6'",
- "version": "==4.0.0"
+ "version": "==4.1.0"
},
"build": {
"hashes": [
- "sha256:32290592c8ccf70ce84107962f6129407abf52cedaa752af28c0c95d99dfa2e7",
- "sha256:d8d8417caff47888274d677f984de509554637dd1ea952d467b027849b06d83b"
+ "sha256:1aaadcd69338252ade4f7ec1265e1a19184bf916d84c9b7df095f423948cb89f",
+ "sha256:21b7ebbd1b22499c4dac536abc7606696ea4d909fd755e00f09f3c0f2c05e3c8"
],
"index": "pypi",
- "version": "==0.6.0.post1"
+ "version": "==0.7.0"
},
"certifi": {
"hashes": [
- "sha256:2bbf76fd432960138b3ef6dda3dde0544f27cbf8546c458e60baf371917ba9ee",
- "sha256:50b1e4f8446b06f41be7dd6338db18e0990601dce795c2b1686458aa7e8fa7d8"
+ "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872",
+ "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"
],
- "version": "==2021.5.30"
+ "version": "==2021.10.8"
},
"cffi": {
"hashes": [
- "sha256:06c54a68935738d206570b20da5ef2b6b6d92b38ef3ec45c5422c0ebaf338d4d",
- "sha256:0c0591bee64e438883b0c92a7bed78f6290d40bf02e54c5bf0978eaf36061771",
- "sha256:19ca0dbdeda3b2615421d54bef8985f72af6e0c47082a8d26122adac81a95872",
- "sha256:22b9c3c320171c108e903d61a3723b51e37aaa8c81255b5e7ce102775bd01e2c",
- "sha256:26bb2549b72708c833f5abe62b756176022a7b9a7f689b571e74c8478ead51dc",
- "sha256:33791e8a2dc2953f28b8d8d300dde42dd929ac28f974c4b4c6272cb2955cb762",
- "sha256:3c8d896becff2fa653dc4438b54a5a25a971d1f4110b32bd3068db3722c80202",
- "sha256:4373612d59c404baeb7cbd788a18b2b2a8331abcc84c3ba40051fcd18b17a4d5",
- "sha256:487d63e1454627c8e47dd230025780e91869cfba4c753a74fda196a1f6ad6548",
- "sha256:48916e459c54c4a70e52745639f1db524542140433599e13911b2f329834276a",
- "sha256:4922cd707b25e623b902c86188aca466d3620892db76c0bdd7b99a3d5e61d35f",
- "sha256:55af55e32ae468e9946f741a5d51f9896da6b9bf0bbdd326843fec05c730eb20",
- "sha256:57e555a9feb4a8460415f1aac331a2dc833b1115284f7ded7278b54afc5bd218",
- "sha256:5d4b68e216fc65e9fe4f524c177b54964af043dde734807586cf5435af84045c",
- "sha256:64fda793737bc4037521d4899be780534b9aea552eb673b9833b01f945904c2e",
- "sha256:6d6169cb3c6c2ad50db5b868db6491a790300ade1ed5d1da29289d73bbe40b56",
- "sha256:7bcac9a2b4fdbed2c16fa5681356d7121ecabf041f18d97ed5b8e0dd38a80224",
- "sha256:80b06212075346b5546b0417b9f2bf467fea3bfe7352f781ffc05a8ab24ba14a",
- "sha256:818014c754cd3dba7229c0f5884396264d51ffb87ec86e927ef0be140bfdb0d2",
- "sha256:8eb687582ed7cd8c4bdbff3df6c0da443eb89c3c72e6e5dcdd9c81729712791a",
- "sha256:99f27fefe34c37ba9875f224a8f36e31d744d8083e00f520f133cab79ad5e819",
- "sha256:9f3e33c28cd39d1b655ed1ba7247133b6f7fc16fa16887b120c0c670e35ce346",
- "sha256:a8661b2ce9694ca01c529bfa204dbb144b275a31685a075ce123f12331be790b",
- "sha256:a9da7010cec5a12193d1af9872a00888f396aba3dc79186604a09ea3ee7c029e",
- "sha256:aedb15f0a5a5949ecb129a82b72b19df97bbbca024081ed2ef88bd5c0a610534",
- "sha256:b315d709717a99f4b27b59b021e6207c64620790ca3e0bde636a6c7f14618abb",
- "sha256:ba6f2b3f452e150945d58f4badd92310449876c4c954836cfb1803bdd7b422f0",
- "sha256:c33d18eb6e6bc36f09d793c0dc58b0211fccc6ae5149b808da4a62660678b156",
- "sha256:c9a875ce9d7fe32887784274dd533c57909b7b1dcadcc128a2ac21331a9765dd",
- "sha256:c9e005e9bd57bc987764c32a1bee4364c44fdc11a3cc20a40b93b444984f2b87",
- "sha256:d2ad4d668a5c0645d281dcd17aff2be3212bc109b33814bbb15c4939f44181cc",
- "sha256:d950695ae4381ecd856bcaf2b1e866720e4ab9a1498cba61c602e56630ca7195",
- "sha256:e22dcb48709fc51a7b58a927391b23ab37eb3737a98ac4338e2448bef8559b33",
- "sha256:e8c6a99be100371dbb046880e7a282152aa5d6127ae01783e37662ef73850d8f",
- "sha256:e9dc245e3ac69c92ee4c167fbdd7428ec1956d4e754223124991ef29eb57a09d",
- "sha256:eb687a11f0a7a1839719edd80f41e459cc5366857ecbed383ff376c4e3cc6afd",
- "sha256:eb9e2a346c5238a30a746893f23a9535e700f8192a68c07c0258e7ece6ff3728",
- "sha256:ed38b924ce794e505647f7c331b22a693bee1538fdf46b0222c4717b42f744e7",
- "sha256:f0010c6f9d1a4011e429109fda55a225921e3206e7f62a0c22a35344bfd13cca",
- "sha256:f0c5d1acbfca6ebdd6b1e3eded8d261affb6ddcf2186205518f1428b8569bb99",
- "sha256:f10afb1004f102c7868ebfe91c28f4a712227fe4cb24974350ace1f90e1febbf",
- "sha256:f174135f5609428cc6e1b9090f9268f5c8935fddb1b25ccb8255a2d50de6789e",
- "sha256:f3ebe6e73c319340830a9b2825d32eb6d8475c1dac020b4f0aa774ee3b898d1c",
- "sha256:f627688813d0a4140153ff532537fbe4afea5a3dffce1f9deb7f91f848a832b5",
- "sha256:fd4305f86f53dfd8cd3522269ed7fc34856a8ee3709a5e28b2836b2db9d4cd69"
- ],
- "version": "==1.14.6"
+ "sha256:00c878c90cb53ccfaae6b8bc18ad05d2036553e6d9d1d9dbcf323bbe83854ca3",
+ "sha256:0104fb5ae2391d46a4cb082abdd5c69ea4eab79d8d44eaaf79f1b1fd806ee4c2",
+ "sha256:06c48159c1abed75c2e721b1715c379fa3200c7784271b3c46df01383b593636",
+ "sha256:0808014eb713677ec1292301ea4c81ad277b6cdf2fdd90fd540af98c0b101d20",
+ "sha256:10dffb601ccfb65262a27233ac273d552ddc4d8ae1bf93b21c94b8511bffe728",
+ "sha256:14cd121ea63ecdae71efa69c15c5543a4b5fbcd0bbe2aad864baca0063cecf27",
+ "sha256:17771976e82e9f94976180f76468546834d22a7cc404b17c22df2a2c81db0c66",
+ "sha256:181dee03b1170ff1969489acf1c26533710231c58f95534e3edac87fff06c443",
+ "sha256:23cfe892bd5dd8941608f93348c0737e369e51c100d03718f108bf1add7bd6d0",
+ "sha256:263cc3d821c4ab2213cbe8cd8b355a7f72a8324577dc865ef98487c1aeee2bc7",
+ "sha256:2756c88cbb94231c7a147402476be2c4df2f6078099a6f4a480d239a8817ae39",
+ "sha256:27c219baf94952ae9d50ec19651a687b826792055353d07648a5695413e0c605",
+ "sha256:2a23af14f408d53d5e6cd4e3d9a24ff9e05906ad574822a10563efcef137979a",
+ "sha256:31fb708d9d7c3f49a60f04cf5b119aeefe5644daba1cd2a0fe389b674fd1de37",
+ "sha256:3415c89f9204ee60cd09b235810be700e993e343a408693e80ce7f6a40108029",
+ "sha256:3773c4d81e6e818df2efbc7dd77325ca0dcb688116050fb2b3011218eda36139",
+ "sha256:3b96a311ac60a3f6be21d2572e46ce67f09abcf4d09344c49274eb9e0bf345fc",
+ "sha256:3f7d084648d77af029acb79a0ff49a0ad7e9d09057a9bf46596dac9514dc07df",
+ "sha256:41d45de54cd277a7878919867c0f08b0cf817605e4eb94093e7516505d3c8d14",
+ "sha256:4238e6dab5d6a8ba812de994bbb0a79bddbdf80994e4ce802b6f6f3142fcc880",
+ "sha256:45db3a33139e9c8f7c09234b5784a5e33d31fd6907800b316decad50af323ff2",
+ "sha256:45e8636704eacc432a206ac7345a5d3d2c62d95a507ec70d62f23cd91770482a",
+ "sha256:4958391dbd6249d7ad855b9ca88fae690783a6be9e86df65865058ed81fc860e",
+ "sha256:4a306fa632e8f0928956a41fa8e1d6243c71e7eb59ffbd165fc0b41e316b2474",
+ "sha256:57e9ac9ccc3101fac9d6014fba037473e4358ef4e89f8e181f8951a2c0162024",
+ "sha256:59888172256cac5629e60e72e86598027aca6bf01fa2465bdb676d37636573e8",
+ "sha256:5e069f72d497312b24fcc02073d70cb989045d1c91cbd53979366077959933e0",
+ "sha256:64d4ec9f448dfe041705426000cc13e34e6e5bb13736e9fd62e34a0b0c41566e",
+ "sha256:6dc2737a3674b3e344847c8686cf29e500584ccad76204efea14f451d4cc669a",
+ "sha256:74fdfdbfdc48d3f47148976f49fab3251e550a8720bebc99bf1483f5bfb5db3e",
+ "sha256:75e4024375654472cc27e91cbe9eaa08567f7fbdf822638be2814ce059f58032",
+ "sha256:786902fb9ba7433aae840e0ed609f45c7bcd4e225ebb9c753aa39725bb3e6ad6",
+ "sha256:8b6c2ea03845c9f501ed1313e78de148cd3f6cad741a75d43a29b43da27f2e1e",
+ "sha256:91d77d2a782be4274da750752bb1650a97bfd8f291022b379bb8e01c66b4e96b",
+ "sha256:91ec59c33514b7c7559a6acda53bbfe1b283949c34fe7440bcf917f96ac0723e",
+ "sha256:920f0d66a896c2d99f0adbb391f990a84091179542c205fa53ce5787aff87954",
+ "sha256:a5263e363c27b653a90078143adb3d076c1a748ec9ecc78ea2fb916f9b861962",
+ "sha256:abb9a20a72ac4e0fdb50dae135ba5e77880518e742077ced47eb1499e29a443c",
+ "sha256:c2051981a968d7de9dd2d7b87bcb9c939c74a34626a6e2f8181455dd49ed69e4",
+ "sha256:c21c9e3896c23007803a875460fb786118f0cdd4434359577ea25eb556e34c55",
+ "sha256:c2502a1a03b6312837279c8c1bd3ebedf6c12c4228ddbad40912d671ccc8a962",
+ "sha256:d4d692a89c5cf08a8557fdeb329b82e7bf609aadfaed6c0d79f5a449a3c7c023",
+ "sha256:da5db4e883f1ce37f55c667e5c0de439df76ac4cb55964655906306918e7363c",
+ "sha256:e7022a66d9b55e93e1a845d8c9eba2a1bebd4966cd8bfc25d9cd07d515b33fa6",
+ "sha256:ef1f279350da2c586a69d32fc8733092fd32cc8ac95139a00377841f59a3f8d8",
+ "sha256:f54a64f8b0c8ff0b64d18aa76675262e1700f3995182267998c31ae974fbc382",
+ "sha256:f5c7150ad32ba43a07c4479f40241756145a1f03b43480e058cfd862bf5041c7",
+ "sha256:f6f824dc3bce0edab5f427efcfb1d63ee75b6fcb7282900ccaf925be84efb0fc",
+ "sha256:fd8a250edc26254fe5b33be00402e6d287f562b6a5b2152dec302fa15bb3e997",
+ "sha256:ffaa5c925128e29efbde7301d8ecaf35c8c60ffbcd6a1ffd3a552177c8e5e796"
+ ],
+ "version": "==1.15.0"
},
"charset-normalizer": {
"hashes": [
- "sha256:0c8911edd15d19223366a194a513099a302055a962bca2cec0f54b8b63175d8b",
- "sha256:f23667ebe1084be45f6ae0538e4a5a865206544097e4e8bbcacf42cd02a348f3"
+ "sha256:1eecaa09422db5be9e29d7fc65664e6c33bd06f9ced7838578ba40d58bdf3721",
+ "sha256:b0b883e8e874edfdece9c28f314e3dd5badf067342e42fb162203335ae61aa2c"
],
"markers": "python_version >= '3'",
- "version": "==2.0.4"
+ "version": "==2.0.9"
},
"colorama": {
"hashes": [
@@ -523,105 +567,107 @@
},
"coverage": {
"hashes": [
- "sha256:004d1880bed2d97151facef49f08e255a20ceb6f9432df75f4eef018fdd5a78c",
- "sha256:01d84219b5cdbfc8122223b39a954820929497a1cb1422824bb86b07b74594b6",
- "sha256:040af6c32813fa3eae5305d53f18875bedd079960822ef8ec067a66dd8afcd45",
- "sha256:06191eb60f8d8a5bc046f3799f8a07a2d7aefb9504b0209aff0b47298333302a",
- "sha256:13034c4409db851670bc9acd836243aeee299949bd5673e11844befcb0149f03",
- "sha256:13c4ee887eca0f4c5a247b75398d4114c37882658300e153113dafb1d76de529",
- "sha256:184a47bbe0aa6400ed2d41d8e9ed868b8205046518c52464fde713ea06e3a74a",
- "sha256:18ba8bbede96a2c3dde7b868de9dcbd55670690af0988713f0603f037848418a",
- "sha256:1aa846f56c3d49205c952d8318e76ccc2ae23303351d9270ab220004c580cfe2",
- "sha256:217658ec7187497e3f3ebd901afdca1af062b42cfe3e0dafea4cced3983739f6",
- "sha256:24d4a7de75446be83244eabbff746d66b9240ae020ced65d060815fac3423759",
- "sha256:2910f4d36a6a9b4214bb7038d537f015346f413a975d57ca6b43bf23d6563b53",
- "sha256:2949cad1c5208b8298d5686d5a85b66aae46d73eec2c3e08c817dd3513e5848a",
- "sha256:2a3859cb82dcbda1cfd3e6f71c27081d18aa251d20a17d87d26d4cd216fb0af4",
- "sha256:2cafbbb3af0733db200c9b5f798d18953b1a304d3f86a938367de1567f4b5bff",
- "sha256:2e0d881ad471768bf6e6c2bf905d183543f10098e3b3640fc029509530091502",
- "sha256:30c77c1dc9f253283e34c27935fded5015f7d1abe83bc7821680ac444eaf7793",
- "sha256:3487286bc29a5aa4b93a072e9592f22254291ce96a9fbc5251f566b6b7343cdb",
- "sha256:372da284cfd642d8e08ef606917846fa2ee350f64994bebfbd3afb0040436905",
- "sha256:41179b8a845742d1eb60449bdb2992196e211341818565abded11cfa90efb821",
- "sha256:44d654437b8ddd9eee7d1eaee28b7219bec228520ff809af170488fd2fed3e2b",
- "sha256:4a7697d8cb0f27399b0e393c0b90f0f1e40c82023ea4d45d22bce7032a5d7b81",
- "sha256:51cb9476a3987c8967ebab3f0fe144819781fca264f57f89760037a2ea191cb0",
- "sha256:52596d3d0e8bdf3af43db3e9ba8dcdaac724ba7b5ca3f6358529d56f7a166f8b",
- "sha256:53194af30d5bad77fcba80e23a1441c71abfb3e01192034f8246e0d8f99528f3",
- "sha256:5fec2d43a2cc6965edc0bb9e83e1e4b557f76f843a77a2496cbe719583ce8184",
- "sha256:6c90e11318f0d3c436a42409f2749ee1a115cd8b067d7f14c148f1ce5574d701",
- "sha256:74d881fc777ebb11c63736622b60cb9e4aee5cace591ce274fb69e582a12a61a",
- "sha256:7501140f755b725495941b43347ba8a2777407fc7f250d4f5a7d2a1050ba8e82",
- "sha256:796c9c3c79747146ebd278dbe1e5c5c05dd6b10cc3bcb8389dfdf844f3ead638",
- "sha256:869a64f53488f40fa5b5b9dcb9e9b2962a66a87dab37790f3fcfb5144b996ef5",
- "sha256:8963a499849a1fc54b35b1c9f162f4108017b2e6db2c46c1bed93a72262ed083",
- "sha256:8d0a0725ad7c1a0bcd8d1b437e191107d457e2ec1084b9f190630a4fb1af78e6",
- "sha256:900fbf7759501bc7807fd6638c947d7a831fc9fdf742dc10f02956ff7220fa90",
- "sha256:92b017ce34b68a7d67bd6d117e6d443a9bf63a2ecf8567bb3d8c6c7bc5014465",
- "sha256:970284a88b99673ccb2e4e334cfb38a10aab7cd44f7457564d11898a74b62d0a",
- "sha256:972c85d205b51e30e59525694670de6a8a89691186012535f9d7dbaa230e42c3",
- "sha256:9a1ef3b66e38ef8618ce5fdc7bea3d9f45f3624e2a66295eea5e57966c85909e",
- "sha256:af0e781009aaf59e25c5a678122391cb0f345ac0ec272c7961dc5455e1c40066",
- "sha256:b6d534e4b2ab35c9f93f46229363e17f63c53ad01330df9f2d6bd1187e5eaacf",
- "sha256:b7895207b4c843c76a25ab8c1e866261bcfe27bfaa20c192de5190121770672b",
- "sha256:c0891a6a97b09c1f3e073a890514d5012eb256845c451bd48f7968ef939bf4ae",
- "sha256:c2723d347ab06e7ddad1a58b2a821218239249a9e4365eaff6649d31180c1669",
- "sha256:d1f8bf7b90ba55699b3a5e44930e93ff0189aa27186e96071fac7dd0d06a1873",
- "sha256:d1f9ce122f83b2305592c11d64f181b87153fc2c2bbd3bb4a3dde8303cfb1a6b",
- "sha256:d314ed732c25d29775e84a960c3c60808b682c08d86602ec2c3008e1202e3bb6",
- "sha256:d636598c8305e1f90b439dbf4f66437de4a5e3c31fdf47ad29542478c8508bbb",
- "sha256:deee1077aae10d8fa88cb02c845cfba9b62c55e1183f52f6ae6a2df6a2187160",
- "sha256:ebe78fe9a0e874362175b02371bdfbee64d8edc42a044253ddf4ee7d3c15212c",
- "sha256:f030f8873312a16414c0d8e1a1ddff2d3235655a2174e3648b4fa66b3f2f1079",
- "sha256:f0b278ce10936db1a37e6954e15a3730bea96a0997c26d7fee88e6c396c2086d",
- "sha256:f11642dddbb0253cc8853254301b51390ba0081750a8ac03f20ea8103f0c56b6"
+ "sha256:01774a2c2c729619760320270e42cd9e797427ecfddd32c2a7b639cdc481f3c0",
+ "sha256:03b20e52b7d31be571c9c06b74746746d4eb82fc260e594dc662ed48145e9efd",
+ "sha256:0a7726f74ff63f41e95ed3a89fef002916c828bb5fcae83b505b49d81a066884",
+ "sha256:1219d760ccfafc03c0822ae2e06e3b1248a8e6d1a70928966bafc6838d3c9e48",
+ "sha256:13362889b2d46e8d9f97c421539c97c963e34031ab0cb89e8ca83a10cc71ac76",
+ "sha256:174cf9b4bef0db2e8244f82059a5a72bd47e1d40e71c68ab055425172b16b7d0",
+ "sha256:17e6c11038d4ed6e8af1407d9e89a2904d573be29d51515f14262d7f10ef0a64",
+ "sha256:215f8afcc02a24c2d9a10d3790b21054b58d71f4b3c6f055d4bb1b15cecce685",
+ "sha256:22e60a3ca5acba37d1d4a2ee66e051f5b0e1b9ac950b5b0cf4aa5366eda41d47",
+ "sha256:2641f803ee9f95b1f387f3e8f3bf28d83d9b69a39e9911e5bfee832bea75240d",
+ "sha256:276651978c94a8c5672ea60a2656e95a3cce2a3f31e9fb2d5ebd4c215d095840",
+ "sha256:3f7c17209eef285c86f819ff04a6d4cbee9b33ef05cbcaae4c0b4e8e06b3ec8f",
+ "sha256:3feac4084291642165c3a0d9eaebedf19ffa505016c4d3db15bfe235718d4971",
+ "sha256:49dbff64961bc9bdd2289a2bda6a3a5a331964ba5497f694e2cbd540d656dc1c",
+ "sha256:4e547122ca2d244f7c090fe3f4b5a5861255ff66b7ab6d98f44a0222aaf8671a",
+ "sha256:5829192582c0ec8ca4a2532407bc14c2f338d9878a10442f5d03804a95fac9de",
+ "sha256:5d6b09c972ce9200264c35a1d53d43ca55ef61836d9ec60f0d44273a31aa9f17",
+ "sha256:600617008aa82032ddeace2535626d1bc212dfff32b43989539deda63b3f36e4",
+ "sha256:619346d57c7126ae49ac95b11b0dc8e36c1dd49d148477461bb66c8cf13bb521",
+ "sha256:63c424e6f5b4ab1cf1e23a43b12f542b0ec2e54f99ec9f11b75382152981df57",
+ "sha256:6dbc1536e105adda7a6312c778f15aaabe583b0e9a0b0a324990334fd458c94b",
+ "sha256:6e1394d24d5938e561fbeaa0cd3d356207579c28bd1792f25a068743f2d5b282",
+ "sha256:86f2e78b1eff847609b1ca8050c9e1fa3bd44ce755b2ec30e70f2d3ba3844644",
+ "sha256:8bdfe9ff3a4ea37d17f172ac0dff1e1c383aec17a636b9b35906babc9f0f5475",
+ "sha256:8e2c35a4c1f269704e90888e56f794e2d9c0262fb0c1b1c8c4ee44d9b9e77b5d",
+ "sha256:92b8c845527eae547a2a6617d336adc56394050c3ed8a6918683646328fbb6da",
+ "sha256:9365ed5cce5d0cf2c10afc6add145c5037d3148585b8ae0e77cc1efdd6aa2953",
+ "sha256:9a29311bd6429be317c1f3fe4bc06c4c5ee45e2fa61b2a19d4d1d6111cb94af2",
+ "sha256:9a2b5b52be0a8626fcbffd7e689781bf8c2ac01613e77feda93d96184949a98e",
+ "sha256:a4bdeb0a52d1d04123b41d90a4390b096f3ef38eee35e11f0b22c2d031222c6c",
+ "sha256:a9c8c4283e17690ff1a7427123ffb428ad6a52ed720d550e299e8291e33184dc",
+ "sha256:b637c57fdb8be84e91fac60d9325a66a5981f8086c954ea2772efe28425eaf64",
+ "sha256:bf154ba7ee2fd613eb541c2bc03d3d9ac667080a737449d1a3fb342740eb1a74",
+ "sha256:c254b03032d5a06de049ce8bca8338a5185f07fb76600afff3c161e053d88617",
+ "sha256:c332d8f8d448ded473b97fefe4a0983265af21917d8b0cdcb8bb06b2afe632c3",
+ "sha256:c7912d1526299cb04c88288e148c6c87c0df600eca76efd99d84396cfe00ef1d",
+ "sha256:cfd9386c1d6f13b37e05a91a8583e802f8059bebfccde61a418c5808dea6bbfa",
+ "sha256:d5d2033d5db1d58ae2d62f095e1aefb6988af65b4b12cb8987af409587cc0739",
+ "sha256:dca38a21e4423f3edb821292e97cec7ad38086f84313462098568baedf4331f8",
+ "sha256:e2cad8093172b7d1595b4ad66f24270808658e11acf43a8f95b41276162eb5b8",
+ "sha256:e3db840a4dee542e37e09f30859f1612da90e1c5239a6a2498c473183a50e781",
+ "sha256:edcada2e24ed68f019175c2b2af2a8b481d3d084798b8c20d15d34f5c733fa58",
+ "sha256:f467bbb837691ab5a8ca359199d3429a11a01e6dfb3d9dcc676dc035ca93c0a9",
+ "sha256:f506af4f27def639ba45789fa6fde45f9a217da0be05f8910458e4557eed020c",
+ "sha256:f614fc9956d76d8a88a88bb41ddc12709caa755666f580af3a688899721efecd",
+ "sha256:f9afb5b746781fc2abce26193d1c817b7eb0e11459510fba65d2bd77fe161d9e",
+ "sha256:fb8b8ee99b3fffe4fd86f4c81b35a6bf7e4462cba019997af2fe679365db0c49"
],
"index": "pypi",
- "version": "==5.5"
+ "version": "==6.2"
},
"cryptography": {
"hashes": [
- "sha256:0f1212a66329c80d68aeeb39b8a16d54ef57071bf22ff4e521657b27372e327d",
- "sha256:1e056c28420c072c5e3cb36e2b23ee55e260cb04eee08f702e0edfec3fb51959",
- "sha256:240f5c21aef0b73f40bb9f78d2caff73186700bf1bc6b94285699aff98cc16c6",
- "sha256:26965837447f9c82f1855e0bc8bc4fb910240b6e0d16a664bb722df3b5b06873",
- "sha256:37340614f8a5d2fb9aeea67fd159bfe4f5f4ed535b1090ce8ec428b2f15a11f2",
- "sha256:3d10de8116d25649631977cb37da6cbdd2d6fa0e0281d014a5b7d337255ca713",
- "sha256:3d8427734c781ea5f1b41d6589c293089704d4759e34597dce91014ac125aad1",
- "sha256:7ec5d3b029f5fa2b179325908b9cd93db28ab7b85bb6c1db56b10e0b54235177",
- "sha256:8e56e16617872b0957d1c9742a3f94b43533447fd78321514abbe7db216aa250",
- "sha256:b01fd6f2737816cb1e08ed4807ae194404790eac7ad030b34f2ce72b332f5586",
- "sha256:bf40af59ca2465b24e54f671b2de2c59257ddc4f7e5706dbd6930e26823668d3",
- "sha256:de4e5f7f68220d92b7637fc99847475b59154b7a1b3868fb7385337af54ac9ca",
- "sha256:eb8cc2afe8b05acbd84a43905832ec78e7b3873fb124ca190f574dca7389a87d",
- "sha256:ee77aa129f481be46f8d92a1a7db57269a2f23052d5f2433b4621bb457081cc9"
+ "sha256:2049f8b87f449fc6190350de443ee0c1dd631f2ce4fa99efad2984de81031681",
+ "sha256:231c4a69b11f6af79c1495a0e5a85909686ea8db946935224b7825cfb53827ed",
+ "sha256:24469d9d33217ffd0ce4582dfcf2a76671af115663a95328f63c99ec7ece61a4",
+ "sha256:2deab5ec05d83ddcf9b0916319674d3dae88b0e7ee18f8962642d3cde0496568",
+ "sha256:494106e9cd945c2cadfce5374fa44c94cfadf01d4566a3b13bb487d2e6c7959e",
+ "sha256:4c702855cd3174666ef0d2d13dcc879090aa9c6c38f5578896407a7028f75b9f",
+ "sha256:52f769ecb4ef39865719aedc67b4b7eae167bafa48dbc2a26dd36fa56460507f",
+ "sha256:5c49c9e8fb26a567a2b3fa0343c89f5d325447956cc2fc7231c943b29a973712",
+ "sha256:684993ff6f67000a56454b41bdc7e015429732d65a52d06385b6e9de6181c71e",
+ "sha256:6fbbbb8aab4053fa018984bb0e95a16faeb051dd8cca15add2a27e267ba02b58",
+ "sha256:8982c19bb90a4fa2aad3d635c6d71814e38b643649b4000a8419f8691f20ac44",
+ "sha256:9511416e85e449fe1de73f7f99b21b3aa04fba4c4d335d30c486ba3756e3a2a6",
+ "sha256:97199a13b772e74cdcdb03760c32109c808aff7cd49c29e9cf4b7754bb725d1d",
+ "sha256:a776bae1629c8d7198396fd93ec0265f8dd2341c553dc32b976168aaf0e6a636",
+ "sha256:aa94d617a4cd4cdf4af9b5af65100c036bce22280ebb15d8b5262e8273ebc6ba",
+ "sha256:b17d83b3d1610e571fedac21b2eb36b816654d6f7496004d6a0d32f99d1d8120",
+ "sha256:d73e3a96c38173e0aa5646c31bf8473bc3564837977dd480f5cbeacf1d7ef3a3",
+ "sha256:d91bc9f535599bed58f6d2e21a2724cb0c3895bf41c6403fe881391d29096f1d",
+ "sha256:ef216d13ac8d24d9cd851776662f75f8d29c9f2d05cdcc2d34a18d32463a9b0b",
+ "sha256:f6a5a85beb33e57998dc605b9dbe7deaa806385fdf5c4810fb849fcd04640c81",
+ "sha256:f92556f94e476c1b616e6daec5f7ddded2c082efa7cee7f31c7aeda615906ed8"
],
"markers": "python_version >= '3.6'",
- "version": "==3.4.7"
+ "version": "==36.0.0"
},
"docutils": {
"hashes": [
- "sha256:686577d2e4c32380bb50cbb22f575ed742d58168cee37e99117a854bcd88f125",
- "sha256:cf316c8370a737a022b72b56874f6602acf974a37a9fba42ec2876387549fc61"
+ "sha256:23010f129180089fbcd3bc08cfefccb3b890b0050e1ca00c867036e9d161b98c",
+ "sha256:679987caf361a7539d76e584cbeddc311e3aee937877c87346f31debc63e9d06"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
- "version": "==0.17.1"
+ "version": "==0.18.1"
},
"idna": {
"hashes": [
- "sha256:14475042e284991034cb48e06f6851428fb14c4dc953acd9be9a5e95c7b6dd7a",
- "sha256:467fbad99067910785144ce333826c71fb0e63a425657295239737f7ecd125f3"
+ "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff",
+ "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"
],
"markers": "python_version >= '3'",
- "version": "==3.2"
+ "version": "==3.3"
},
"importlib-metadata": {
"hashes": [
- "sha256:7b30a78db2922d78a6f47fb30683156a14f3c6aa5cc23f77cc8967e9ab2d002f",
- "sha256:ed5157fef23a4bc4594615a0dd8eba94b2bb36bf2a343fa3d8bb2fa0a62a99d5"
+ "sha256:53ccfd5c134223e497627b9815d5030edf77d2ed573922f7a0b8f8bb81a1c100",
+ "sha256:75bdec14c397f528724c1bfd9709d660b33a4d2e77387a3358f20b848bb5e5fb"
],
"markers": "python_version >= '3.6'",
- "version": "==4.6.4"
+ "version": "==4.8.2"
},
"jeepney": {
"hashes": [
@@ -633,41 +679,41 @@
},
"keyring": {
"hashes": [
- "sha256:b32397fd7e7063f8dd74a26db910c9862fc2109285fa16e3b5208bcb42a3e579",
- "sha256:b7e0156667f5dcc73c1f63a518005cd18a4eb23fe77321194fefcc03748b21a4"
+ "sha256:3dc0f66062a4f8f6f2ce30d6a516e6e623e6c3c2e76864204ceaf64695408f07",
+ "sha256:88f206024295e3c6fb16bb0a60fb4bb7ec1185629dc5a729f12aa7c236d01387"
],
"markers": "python_version >= '3.6'",
- "version": "==23.1.0"
+ "version": "==23.4.0"
},
"packaging": {
"hashes": [
- "sha256:7dc96269f53a4ccec5c0670940a4281106dd0bb343f47b7471f779df49c2fbe7",
- "sha256:c86254f9220d55e31cc94d69bade760f0847da8000def4dfe1c6b872fd14ff14"
+ "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb",
+ "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"
],
"markers": "python_version >= '3.6'",
- "version": "==21.0"
+ "version": "==21.3"
},
"pep517": {
"hashes": [
- "sha256:3fa6b85b9def7ba4de99fb7f96fe3f02e2d630df8aa2720a5cf3b183f087a738",
- "sha256:e1ba5dffa3a131387979a68ff3e391ac7d645be409216b961bc2efe6468ab0b2"
+ "sha256:931378d93d11b298cf511dd634cf5ea4cb249a28ef84160b3247ee9afb4e8ab0",
+ "sha256:dd884c326898e2c6e11f9e0b64940606a93eb10ea022a2e067959f3a110cf161"
],
- "version": "==0.11.0"
+ "version": "==0.12.0"
},
"pkginfo": {
"hashes": [
- "sha256:37ecd857b47e5f55949c41ed061eb51a0bee97a87c969219d144c0e023982779",
- "sha256:e7432f81d08adec7297633191bbf0bd47faf13cd8724c3a13250e51d542635bd"
+ "sha256:542e0d0b6750e2e21c20179803e40ab50598d8066d51097a0e382cba9eb02bff",
+ "sha256:c24c487c6a7f72c66e816ab1796b96ac6c3d14d49338293d2141664330b55ffc"
],
- "version": "==1.7.1"
+ "version": "==1.8.2"
},
"pycparser": {
"hashes": [
- "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0",
- "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705"
+ "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9",
+ "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
- "version": "==2.20"
+ "version": "==2.21"
},
"pygments": {
"hashes": [
@@ -679,18 +725,18 @@
},
"pyparsing": {
"hashes": [
- "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1",
- "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"
+ "sha256:04ff808a5b90911829c55c4e26f75fa5ca8a2f5f36aa3a51f68e27033341d3e4",
+ "sha256:d9bdec0013ef1eb5a84ab39a3b3868911598afa494f5faa038647101504e2b81"
],
- "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
- "version": "==2.4.7"
+ "markers": "python_version >= '3.1'",
+ "version": "==3.0.6"
},
"readme-renderer": {
"hashes": [
- "sha256:63b4075c6698fcfa78e584930f07f39e05d46f3ec97f65006e430b595ca6348c",
- "sha256:92fd5ac2bf8677f310f3303aa4bce5b9d5f9f2094ab98c29f13791d7b805a3db"
+ "sha256:3286806450d9961d6e3b5f8a59f77e61503799aca5155c8d8d40359b4e1e1adc",
+ "sha256:8299700d7a910c304072a7601eafada6712a5b011a20139417e1b1e9f04645d8"
],
- "version": "==29.0"
+ "version": "==30.0"
},
"requests": {
"hashes": [
@@ -732,35 +778,35 @@
},
"tomli": {
"hashes": [
- "sha256:8dd0e9524d6f386271a36b41dbf6c57d8e32fd96fd22b6584679dc569d20899f",
- "sha256:a5b75cb6f3968abb47af1b40c1819dc519ea82bcc065776a866e8d74c5ca9442"
+ "sha256:c6ce0015eb38820eaf32b5db832dbc26deb3dd427bd5f6556cf0acac2c214fee",
+ "sha256:f04066f68f5554911363063a30b108d2b5a5b1a010aa8b6132af78489fe3aade"
],
"markers": "python_version >= '3.6'",
- "version": "==1.2.1"
+ "version": "==1.2.2"
},
"tqdm": {
"hashes": [
- "sha256:07856e19a1fe4d2d9621b539d3f072fa88c9c1ef1f3b7dd4d4953383134c3164",
- "sha256:35540feeaca9ac40c304e916729e6b78045cbbeccd3e941b2868f09306798ac9"
+ "sha256:8dd278a422499cd6b727e6ae4061c40b48fce8b76d1ccbf5d34fca9b7f925b0c",
+ "sha256:d359de7217506c9851b7869f3708d8ee53ed70a1b8edbba4dbcb47442592920d"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
- "version": "==4.62.1"
+ "version": "==4.62.3"
},
"twine": {
"hashes": [
- "sha256:087328e9bb405e7ce18527a2dca4042a84c7918658f951110b38bc135acab218",
- "sha256:4caec0f1ed78dc4c9b83ad537e453d03ce485725f2aea57f1bb3fdde78dae936"
+ "sha256:5a3e3fb52b926827c99e050f0c1e5d8ae599848f3eb27764f19b886c09134590",
+ "sha256:8d6a0ad895576c97e9ad4a5da2d6adea37fd5434ecabace0054013d537ddbc6c"
],
"index": "pypi",
- "version": "==3.4.2"
+ "version": "==3.7.0"
},
"urllib3": {
"hashes": [
- "sha256:39fb8672126159acb139a7718dd10806104dec1e2f0f6c88aab05d17df10c8d4",
- "sha256:f57b4c16c62fa2760b7e3d97c35b255512fb6b59a259730f36ba32ce9f8e342f"
+ "sha256:4987c65554f7a2dbf30c18fd48778ef124af6fab771a377103da0585e2336ece",
+ "sha256:c4fdf4019605b6e5423637e01bc9fe4daef873709a7973e195ceba0a62bbc844"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4.0'",
- "version": "==1.26.6"
+ "version": "==1.26.7"
},
"webencodings": {
"hashes": [
@@ -771,11 +817,11 @@
},
"zipp": {
"hashes": [
- "sha256:957cfda87797e389580cb8b9e3870841ca991e2125350677b2ca83a0e99390a3",
- "sha256:f5812b1e007e48cff63449a5e9f4e7ebea716b4111f9c4f9a645f91d579bf0c4"
+ "sha256:71c644c5369f4a6e07636f0aa966270449561fcea2e3d6747b8d23efaa9d7832",
+ "sha256:9fe5ea21568a0a70e50f273397638d39b03353731e6cbbb3fd8502a33fec40bc"
],
"markers": "python_version >= '3.6'",
- "version": "==3.5.0"
+ "version": "==3.6.0"
}
}
}
diff --git a/deploy b/deploy
index 375eb5f..73be93f 160000
--- a/deploy
+++ b/deploy
@@ -1 +1 @@
-Subproject commit 375eb5fa5236742cf72b965e3d72c17340e44aac
+Subproject commit 73be93f2251ea6735a96c795da8542dd45a24691
diff --git a/requirements.txt b/requirements.txt
index 248b10b..cd60c1a 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -5,4 +5,6 @@ google-auth-oauthlib==0.4.4
psutil==5.8.0
python-magic==0.4.24
python-telegram-bot==13.5
+qbittorrent-api==2021.8.23
+torrentool==1.1.1
youtube_dl==2021.6.6
diff --git a/tgmb/__init__.py b/tgmb/__init__.py
index 48c5fc9..3caad46 100644
--- a/tgmb/__init__.py
+++ b/tgmb/__init__.py
@@ -1,11 +1,13 @@
# TODO: add sufficient documentation to the functions and classes in this module
-# TODO: Code for Upload to Mega
-# TODO: Code for user filters
-# TODO: Add and Handle Exceptions
-# TODO: Code for direct link generation
+# TODO: code for Upload to Mega
+# TODO: code for user filters
+# TODO: add and Handle Exceptions
+# TODO: code for direct link generation
+# TODO: add hard-restart (restart all subprocesses)
+# TODO: decide between confDefaults and optVals for AriaHelper, QbitTorrentHelper
+# TODO: remove redundant 'apt-get clean' in Dockerfile
import aria2p
import asyncio
-import copy
import googleapiclient.discovery
import googleapiclient.errors
import googleapiclient.http
@@ -20,6 +22,7 @@
import mega
import os
import psutil
+import qbittorrentapi
import random
import re
import requests
@@ -36,6 +39,7 @@
import tornado.httpserver
import tornado.ioloop
import tornado.web
+import torrentool.api
import typing
import warnings
import youtube_dl
@@ -75,12 +79,14 @@ def __init__(self):
self.loggingHelper = LoggingHelper(self)
self.mirrorHelper = MirrorHelper(self)
self.statusHelper = StatusHelper(self)
+ self.subprocessHelper = SubprocessHelper(self)
self.threadingHelper = ThreadingHelper(self)
self.commandHelper = CommandHelper(self)
self.conversationHelper = ConversationHelper(self)
self.ariaHelper = AriaHelper(self)
self.googleDriveHelper = GoogleDriveHelper(self)
self.megaHelper = MegaHelper(self)
+ self.qbitTorrentHelper = QbitTorrentHelper(self)
self.telegramHelper = TelegramHelper(self)
self.youTubeHelper = YouTubeHelper(self)
self.compressionHelper = CompressionHelper(self)
@@ -88,7 +94,10 @@ def __init__(self):
super().__init__(self)
def initHelper(self) -> None:
- self.envVars: typing.Dict[str, typing.Union[bool, str]] = {'currWorkDir': os.getcwd()}
+ self.envVars: typing.Dict[str, typing.Union[bool, str]] = \
+ {
+ 'currWorkDir': os.getcwd()
+ }
self.restartJsonFile = 'restart.json'
self.restartMsgInfo: typing.Dict[str, int] = {}
self.restartVars = (self.configHelper.jsonFileLoad(self.restartJsonFile) if os.path.exists(self.restartJsonFile) else {})
@@ -102,10 +111,12 @@ def initHelper(self) -> None:
self.dispatcher = self.updater.dispatcher
self.bot = self.updater.bot
self.envVars['dlRootDirPath'] = os.path.join(self.envVars['currWorkDir'], self.configHelper.configVars[self.configHelper.optVars[2]])
+ self.torrentFileMimeType = 'application/x-bittorrent'
def initSubHelpers(self):
self.loggingHelper.initHelper()
self.getHelper.initHelper()
+ self.subprocessHelper.initHelper()
self.threadingHelper.initHelper()
self.configHelper.initHelper()
self.mirrorHelper.initHelper()
@@ -114,6 +125,7 @@ def initSubHelpers(self):
self.ariaHelper.initHelper()
self.googleDriveHelper.initHelper()
self.megaHelper.initHelper()
+ self.qbitTorrentHelper.initHelper()
self.telegramHelper.initHelper()
self.youTubeHelper.initHelper()
self.compressionHelper.initHelper()
@@ -131,11 +143,23 @@ def addAllHandlers(self) -> None:
self.dispatcher.add_handler(unknownHandler)
def botRestart(self) -> None:
- self.ariaHelper.api.remove_all(force=True)
+ self.ariaHelper.removeAllDownloads()
+ self.qbitTorrentHelper.removeAllDownloads()
+ self.ariaHelper.stopListener()
+ self.qbitTorrentHelper.unauthorizeApi()
+ self.megaHelper.unauthorizeApi()
self.cleanDlRootDir()
+ self.listenerHelper.webhookServerStop()
+ self.statusHelper.updaterStop()
self.logger.info('Restarting the Bot...')
- restartJsonDict = {'restartMsgInfo': self.restartMsgInfo, 'ariaRpcSecret': self.ariaHelper.rpcSecret,
- 'ariaDaemonPid': self.ariaHelper.daemonPid, 'botApiServerPid': self.telegramHelper.apiServerPid}
+ restartJsonDict = \
+ {
+ 'restartMsgInfo': self.restartMsgInfo,
+ 'ariaRpcSecret': self.ariaHelper.rpcSecret,
+ 'ariaDaemonPid': self.ariaHelper.daemonPid,
+ 'botApiServerPid': self.telegramHelper.apiServerPid,
+ 'qbitDaemonPid': self.qbitTorrentHelper.daemonPid
+ }
self.configHelper.jsonFileWrite(self.restartJsonFile, restartJsonDict)
os.execl(sys.executable, sys.executable, '-m', 'tgmb')
@@ -143,20 +167,25 @@ def botStart(self) -> None:
self.cleanDlRootDir()
self.loggingHelper.checkLogLevel()
self.loggingHelper.delLogFiles()
+ self.ariaHelper.makeConf()
+ self.qbitTorrentHelper.makeConf()
self.ariaHelper.daemonStart()
+ self.qbitTorrentHelper.daemonStart()
self.telegramHelper.apiServerStart()
self.ariaHelper.daemonCheck()
+ self.qbitTorrentHelper.daemonCheck()
self.telegramHelper.apiServerCheck()
- self.ariaHelper.dlTrackersList()
- self.ariaHelper.globalOptsGet()
- self.ariaHelper.globalOptsSet()
+ self.ariaHelper.getTrackersList()
self.ariaHelper.startListener()
+ self.qbitTorrentHelper.authorizeApi()
+ self.qbitTorrentHelper.setTrackersList()
self.megaHelper.addListener()
self.googleDriveHelper.authorizeApi()
self.megaHelper.authorizeApi()
self.addAllHandlers()
self.updaterStart()
- self.listenerHelper.startWebhookServer()
+ self.listenerHelper.webhookServerStart()
+ self.statusHelper.updaterStart()
self.logger.info("Bot Started !")
def botIdle(self) -> None:
@@ -166,11 +195,18 @@ def botIdle(self) -> None:
self.updaterIdle()
def botStop(self) -> None:
+ self.ariaHelper.removeAllDownloads()
+ self.qbitTorrentHelper.removeAllDownloads()
+ self.ariaHelper.stopListener()
+ self.qbitTorrentHelper.unauthorizeApi()
self.megaHelper.unauthorizeApi()
self.telegramHelper.apiServerStop()
+ self.qbitTorrentHelper.daemonStop()
self.ariaHelper.daemonStop()
+ self.cleanDlRootDir()
self.loggingHelper.delLogFiles()
- self.listenerHelper.stopWebhookServer()
+ self.listenerHelper.webhookServerStop()
+ self.statusHelper.updaterStop()
self.logger.info("Bot Stopped !")
def ifUpdateRestartMsg(self) -> None:
@@ -203,18 +239,116 @@ def initHelper(self) -> None:
self.configJsonBakFile = self.configJsonFile + '.bak'
self.dynamicJsonFile = 'dynamic.json'
self.fileidJsonFile = 'fileid.json'
- self.configFiles: [str] = [self.configJsonFile, self.configJsonBakFile]
+ self.configFiles: typing.List[str] = \
+ [
+ self.configJsonFile,
+ self.configJsonBakFile
+ ]
self.configVars: typing.Dict = {}
- self.reqVars: [str] = ['botToken', 'botOwnerId', 'telegramApiId', 'telegramApiHash',
- 'googleDriveAuth', 'googleDriveUploadFolderIds']
- self.optVars: typing.List[str] = ['ariaGlobalOpts', 'authorizedChats', 'dlRootDir', 'logLevel',
- 'megaAuth', 'statusUpdateInterval', 'trackersListUrl', 'ytdlFormat']
+ self.reqVars: typing.List[str] = \
+ [
+ 'botToken',
+ 'botOwnerId',
+ 'telegramApiId',
+ 'telegramApiHash',
+ 'googleDriveAuth',
+ 'googleDriveUploadFolderIds'
+ ]
+ self.optVars: typing.List[str] = \
+ [
+ 'ariaConf',
+ 'authorizedChats',
+ 'dlRootDir',
+ 'logLevel',
+ 'megaAuth',
+ 'qbitTorrentConf',
+ 'statusUpdateInterval',
+ 'trackersListUrl',
+ 'ytdlFormat'
+ ]
self.optVals: typing.List[typing.Union[str, typing.Dict]] = \
- [{'allow-overwrite': 'true', 'bt-max-peers': '0', 'follow-torrent': 'mem',
- 'max-connection-per-server': '8', 'max-overall-upload-limit': '1K',
- 'min-split-size': '10M', 'seed-time': '0.01', 'split': '10'},
- {}, 'dl', 'INFO', {}, '5', 'https://trackerslist.com/all_aria2.txt', 'best/bestvideo+bestaudio']
- self.emptyVals: typing.List[typing.Union[str, typing.Dict]] = ['', ' ', {}]
+ [
+ {
+ 'allow-overwrite': 'true',
+ 'follow-torrent': 'false',
+ 'max-connection-per-server': '8',
+ 'min-split-size': '8M',
+ 'split': '8'
+ },
+ {},
+ 'dl',
+ 'INFO',
+ {
+ 'apiKey': '',
+ 'emailId': '',
+ 'passPhrase': ''
+ },
+ {
+ 'BitTorrent': {
+ 'Session': {
+ 'AsyncIOThreadsCount': '8',
+ 'MultiConnectionsPerIp': 'true',
+ 'SlowTorrentsDownloadRate': '100',
+ 'SlowTorrentsInactivityTimer': '600'
+ }
+ },
+ 'LegalNotice': {
+ '': {
+ 'Accepted': 'true'
+ }
+ },
+ 'Preferences': {
+ 'Advanced': {
+ 'AnnounceToAllTrackers': 'true',
+ 'AnonymousMode': 'false',
+ 'IgnoreLimitsLAN': 'true',
+ 'RecheckOnCompletion': 'true',
+ 'LtTrackerExchange': 'true'
+ },
+ 'Bittorrent': {
+ 'AddTrackers': 'false',
+ 'MaxConnecs': '-1',
+ 'MaxConnecsPerTorrent': '-1',
+ 'MaxUploads': '-1',
+ 'MaxUploadsPerTorrent': '-1',
+ 'DHT': 'true',
+ 'DHTPort': '6881',
+ 'PeX': 'true',
+ 'LSD': 'true',
+ 'sameDHTPortAsBT': 'true'
+ },
+ 'Downloads': {
+ 'DiskWriteCacheSize': '32',
+ 'PreAllocation': 'true',
+ 'UseIncompleteExtension': 'true'
+ },
+ 'General': {
+ 'PreventFromSuspendWhenDownloading': 'true'
+ },
+ 'Queueing': {
+ 'IgnoreSlowTorrents': 'true',
+ 'MaxActiveDownloads': '100',
+ 'MaxActiveTorrents': '50',
+ 'MaxActiveUploads': '50',
+ 'QueueingEnabled': 'false'
+ },
+ 'WebUI': {
+ 'Enabled': 'true',
+ 'Port': '8400',
+ 'LocalHostAuth': 'false'
+ }
+ }
+ },
+ '5',
+ 'https://trackerslist.com/all.txt',
+ 'best/bestvideo+bestaudio'
+ ]
+ self.emptyVals: typing.List[typing.Union[str, typing.Dict]] = \
+ [
+ '',
+ ' ',
+ {}
+ ]
self.isFixConfigJson: bool = False
self.configVarsLoad()
self.configVarsCheck()
@@ -256,10 +390,18 @@ def configVarsLoad(self) -> None:
if os.path.exists(self.dynamicJsonFile):
self.botHelper.envVars['dynamicConfig'] = True
self.logger.info('Using Dynamic Config...')
- self.botHelper.envVars = {**self.botHelper.envVars, **self.jsonFileLoad(self.dynamicJsonFile)}
+ self.botHelper.envVars = \
+ {
+ **self.botHelper.envVars,
+ **self.jsonFileLoad(self.dynamicJsonFile)
+ }
self.configFileDl(self.fileidJsonFile)
self.configFileCheck(self.fileidJsonFile)
- self.botHelper.envVars = {**self.botHelper.envVars, **self.jsonFileLoad(self.fileidJsonFile)}
+ self.botHelper.envVars = \
+ {
+ **self.botHelper.envVars,
+ **self.jsonFileLoad(self.fileidJsonFile)
+ }
for configFile in self.configFiles:
fileHashInDict = self.botHelper.envVars[self.botHelper.getHelper.fileHashKey(configFile)]
if not (os.path.exists(configFile) and fileHashInDict == self.botHelper.getHelper.fileHash(configFile)):
@@ -314,7 +456,11 @@ def jsonFileWrite(jsonFileName: str, jsonDict: dict) -> None:
def updateAuthorizedChats(self, chatId: int, chatName: str, chatType: str, auth: bool = None, unauth: bool = None) -> None:
if auth:
- self.configVars[self.optVars[1]][str(chatId)] = {"chatType": chatType, "chatName": chatName}
+ self.configVars[self.optVars[1]][str(chatId)] = \
+ {
+ "chatType": chatType,
+ "chatName": chatName
+ }
if unauth:
self.configVars[self.optVars[1]].pop(str(chatId))
self.updateConfigJson()
@@ -351,8 +497,26 @@ def initHelper(self) -> None:
super().initHelper()
self.keySuffixId: str = 'Id'
self.keySuffixHash: str = 'Hash'
- self.sizeUnits: [str] = ['B', 'KB', 'MB', 'GB', 'TB', 'PB']
- self.progressUnits: typing.List[str] = ['▏', '▎', '▍', '▌', '▋', '▊', '▉', '█']
+ self.sizeUnits: [str] = \
+ [
+ 'B',
+ 'KB',
+ 'MB',
+ 'GB',
+ 'TB',
+ 'PB'
+ ]
+ self.progressUnits: typing.List[str] = \
+ [
+ '▏',
+ '▎',
+ '▍',
+ '▌',
+ '▋',
+ '▊',
+ '▉',
+ '█'
+ ]
@staticmethod
def chatDetails(update: telegram.Update) -> (int, str, str):
@@ -395,6 +559,14 @@ def folderSize(folderPath: str) -> int:
size += os.path.getsize(os.path.join(path, file))
return size
+ @staticmethod
+ def hashFromMagnet(magnetUrl: str) -> str:
+ return re.search(r'(?<=xt=urn:btih:)[a-zA-Z0-9]+', magnetUrl).group(0).lower()
+
+ @staticmethod
+ def magnetFromTorrentFile(torrentFile: str) -> str:
+ return torrentool.api.Torrent.from_file(torrentFile).magnet_link
+
def progressBar(self, progress: float) -> str:
progressRounded = round(progress)
numFull = progressRounded // 8
@@ -470,51 +642,59 @@ def initHelper(self) -> None:
self.uploadQueueSize: int = 3
self.uploadQueueActive: int = 0
self.uploadQueue: typing.List[str] = []
- self.statusCallBacks: typing.Dict[str, typing.Callable] \
- = {MirrorStatus.addMirror: self.onAddMirror,
- MirrorStatus.cancelMirror: self.onCancelMirror,
- MirrorStatus.completeMirror: self.onCompleteMirror,
- MirrorStatus.downloadQueue: self.onDownloadQueue,
- MirrorStatus.downloadStart: self.onDownloadStart,
- MirrorStatus.downloadProgress: self.onDownloadProgress,
- MirrorStatus.downloadComplete: self.onDownloadComplete,
- MirrorStatus.downloadError: self.onDownloadError,
- MirrorStatus.compressionQueue: self.onCompressionQueue,
- MirrorStatus.compressionStart: self.onCompressionStart,
- MirrorStatus.compressionProgress: self.onCompressionProgress,
- MirrorStatus.compressionComplete: self.onCompressionComplete,
- MirrorStatus.compressionError: self.onCompressionError,
- MirrorStatus.decompressionQueue: self.onDecompressionQueue,
- MirrorStatus.decompressionStart: self.onDecompressionStart,
- MirrorStatus.decompressionProgress: self.onDecompressionProgress,
- MirrorStatus.decompressionComplete: self.onDecompressionComplete,
- MirrorStatus.decompressionError: self.onDecompressionError,
- MirrorStatus.uploadQueue: self.onUploadQueue,
- MirrorStatus.uploadStart: self.onUploadStart,
- MirrorStatus.uploadProgress: self.onUploadProgress,
- MirrorStatus.uploadComplete: self.onUploadComplete,
- MirrorStatus.uploadError: self.onUploadError}
-
- def startWebhookServer(self, ready=None, forceEventLoop=False) -> None:
+ self.statusCallBacks: typing.Dict[str, typing.Callable] = \
+ {
+ MirrorStatus.addMirror: self.onAddMirror,
+ MirrorStatus.cancelMirror: self.onCancelMirror,
+ MirrorStatus.completeMirror: self.onCompleteMirror,
+ MirrorStatus.downloadQueue: self.onDownloadQueue,
+ MirrorStatus.downloadStart: self.onDownloadStart,
+ MirrorStatus.downloadProgress: self.onDownloadProgress,
+ MirrorStatus.downloadComplete: self.onDownloadComplete,
+ MirrorStatus.downloadError: self.onDownloadError,
+ MirrorStatus.compressionQueue: self.onCompressionQueue,
+ MirrorStatus.compressionStart: self.onCompressionStart,
+ MirrorStatus.compressionProgress: self.onCompressionProgress,
+ MirrorStatus.compressionComplete: self.onCompressionComplete,
+ MirrorStatus.compressionError: self.onCompressionError,
+ MirrorStatus.decompressionQueue: self.onDecompressionQueue,
+ MirrorStatus.decompressionStart: self.onDecompressionStart,
+ MirrorStatus.decompressionProgress: self.onDecompressionProgress,
+ MirrorStatus.decompressionComplete: self.onDecompressionComplete,
+ MirrorStatus.decompressionError: self.onDecompressionError,
+ MirrorStatus.uploadQueue: self.onUploadQueue,
+ MirrorStatus.uploadStart: self.onUploadStart,
+ MirrorStatus.uploadProgress: self.onUploadProgress,
+ MirrorStatus.uploadComplete: self.onUploadComplete,
+ MirrorStatus.uploadError: self.onUploadError
+ }
+
+ def webhookServerStart(self, ready=None, forceEventLoop=False) -> None:
self.webhookServer = WebhookServer(self.botHelper)
self.botHelper.threadingHelper.initThread(target=self.webhookServer.serveForever, name='ListenerHelper.webhookServer',
forceEventLoop=forceEventLoop, ready=ready)
- def stopWebhookServer(self) -> None:
+ def webhookServerStop(self) -> None:
if self.webhookServer:
self.webhookServer.shutdown()
self.webhookServer = None
def updateStatus(self, uid: str, mirrorStatus: str) -> None:
- self.botHelper.mirrorHelper.mirrorInfos[uid].status = mirrorStatus
- data = {'mirrorUid': uid, 'mirrorStatus': mirrorStatus}
- headers = {'Content-Type': 'application/json'}
- requests.post(url=self.webhookServer.webhookUrl, data=json.dumps(data), headers=headers)
+ self.botHelper.mirrorHelper.mirrorInfos[uid].updateStatus(mirrorStatus)
+ payloadData: typing.Dict[str, str] = \
+ {
+ 'mirrorUid': uid,
+ 'mirrorStatus': mirrorStatus}
+ headers: typing.Dict[str, str] = \
+ {
+ 'Content-Type': 'application/json'
+ }
+ requests.post(url=self.webhookServer.webhookUrl, data=json.dumps(payloadData), headers=headers)
def updateStatusCallback(self, uid: str) -> None:
mirrorInfo: MirrorInfo = self.botHelper.mirrorHelper.mirrorInfos[uid]
- self.logger.info(f'{mirrorInfo.uid} : {mirrorInfo.status}')
- self.statusCallBacks[mirrorInfo.status](mirrorInfo)
+ self.logger.info(f'{mirrorInfo.uid} : {mirrorInfo.currentStatus}')
+ self.statusCallBacks[mirrorInfo.currentStatus](mirrorInfo)
def onAddMirror(self, mirrorInfo: 'MirrorInfo') -> None:
self.downloadQueue.append(mirrorInfo.uid)
@@ -522,9 +702,103 @@ def onAddMirror(self, mirrorInfo: 'MirrorInfo') -> None:
# TODO: improve method and maybe not use onCancelMirror callback in operationErrors and improve onOperationErrors
def onCancelMirror(self, mirrorInfo: 'MirrorInfo') -> None:
- # TODO: implement cancel callbacks for various download and upload types
- shutil.rmtree(mirrorInfo.path)
+ if mirrorInfo.previousStatus in [
+ MirrorStatus.downloadQueue,
+ MirrorStatus.uploadQueue,
+ MirrorStatus.compressionQueue,
+ MirrorStatus.decompressionQueue
+ ]:
+ if mirrorInfo.previousStatus == MirrorStatus.downloadQueue:
+ self.removeDownloadQueue(mirrorInfo.uid)
+ if mirrorInfo.previousStatus == MirrorStatus.uploadQueue:
+ self.removeUploadQueue(mirrorInfo.uid)
+ if mirrorInfo.previousStatus == MirrorStatus.compressionQueue:
+ self.removeCompressionQueue(mirrorInfo.uid)
+ if mirrorInfo.previousStatus == MirrorStatus.decompressionQueue:
+ self.removeDecompressionQueue(mirrorInfo.uid)
+ if mirrorInfo.previousStatus in [
+ MirrorStatus.downloadStart,
+ MirrorStatus.uploadStart,
+ MirrorStatus.compressionStart,
+ MirrorStatus.decompressionStart
+ ]:
+ while self.botHelper.mirrorHelper.mirrorInfos[mirrorInfo.uid].previousStatus in [
+ MirrorStatus.downloadStart,
+ MirrorStatus.uploadStart,
+ MirrorStatus.compressionStart,
+ MirrorStatus.decompressionStart
+ ]:
+ time.sleep(1.0)
+ self.updateStatus(mirrorInfo.uid, MirrorStatus.cancelMirror)
+ return
+ if mirrorInfo.previousStatus in [
+ MirrorStatus.downloadProgress
+ ]:
+ if mirrorInfo.isAriaDownload:
+ self.botHelper.ariaHelper.cancelDownload(mirrorInfo)
+ if mirrorInfo.isGoogleDriveDownload:
+ self.botHelper.googleDriveHelper.cancelDownload(mirrorInfo)
+ if mirrorInfo.isMegaDownload:
+ self.botHelper.megaHelper.cancelDownload(mirrorInfo)
+ if mirrorInfo.isQbitTorrentDownload:
+ self.botHelper.qbitTorrentHelper.cancelDownload(mirrorInfo)
+ if mirrorInfo.isTelegramDownload:
+ self.botHelper.telegramHelper.cancelDownload(mirrorInfo)
+ if mirrorInfo.isYouTubeDownload:
+ self.botHelper.youTubeHelper.cancelDownload(mirrorInfo)
+ self.downloadQueue.remove(mirrorInfo.uid)
+ self.downloadQueueActive -= 1
+ self.checkDownloadQueue()
+ if mirrorInfo.previousStatus in [
+ MirrorStatus.uploadProgress
+ ]:
+ if mirrorInfo.isGoogleDriveUpload:
+ self.botHelper.googleDriveHelper.cancelUpload(mirrorInfo)
+ if mirrorInfo.isMegaUpload:
+ self.botHelper.megaHelper.cancelUpload(mirrorInfo)
+ if mirrorInfo.isTelegramUpload:
+ self.botHelper.telegramHelper.cancelUpload(mirrorInfo)
+ self.uploadQueue.remove(mirrorInfo.uid)
+ self.uploadQueueActive -= 1
+ self.checkUploadQueue()
+ if mirrorInfo.previousStatus in [
+ MirrorStatus.compressionProgress,
+ MirrorStatus.decompressionProgress
+ ]:
+ while self.botHelper.mirrorHelper.mirrorInfos[mirrorInfo.uid].previousStatus in [
+ MirrorStatus.compressionProgress,
+ MirrorStatus.decompressionProgress
+ ]:
+ time.sleep(1.0)
+ self.updateStatus(mirrorInfo.uid, MirrorStatus.cancelMirror)
+ return
+ if mirrorInfo.previousStatus in [
+ MirrorStatus.downloadComplete,
+ MirrorStatus.uploadComplete,
+ MirrorStatus.compressionComplete,
+ MirrorStatus.decompressionComplete
+ ]:
+ while self.botHelper.mirrorHelper.mirrorInfos[mirrorInfo.uid].previousStatus in [
+ MirrorStatus.downloadComplete,
+ MirrorStatus.uploadComplete,
+ MirrorStatus.compressionComplete,
+ MirrorStatus.decompressionComplete
+ ]:
+ time.sleep(1.0)
+ self.updateStatus(mirrorInfo.uid, MirrorStatus.cancelMirror)
+ return
+ if mirrorInfo.previousStatus in [
+ MirrorStatus.downloadError,
+ MirrorStatus.uploadError,
+ MirrorStatus.compressionError,
+ MirrorStatus.decompressionError
+ ]:
+ pass
+ if os.path.exists(mirrorInfo.path):
+ shutil.rmtree(mirrorInfo.path)
self.botHelper.mirrorHelper.mirrorInfos.pop(mirrorInfo.uid)
+ self.botHelper.bot.sendMessage(text=f'Cancelled: [{mirrorInfo.uid}]', parse_mode='HTML',
+ chat_id=mirrorInfo.chatId, reply_to_message_id=mirrorInfo.msgId)
def onCompleteMirror(self, mirrorInfo: 'MirrorInfo') -> None:
shutil.rmtree(mirrorInfo.path)
@@ -543,6 +817,10 @@ def checkDownloadQueue(self) -> None:
self.downloadQueueActive += 1
self.checkDownloadQueue()
+ def removeDownloadQueue(self, uid: str) -> None:
+ self.downloadQueue.remove(uid)
+ self.checkDownloadQueue()
+
def onDownloadStart(self, mirrorInfo: 'MirrorInfo') -> None:
os.mkdir(mirrorInfo.path)
if mirrorInfo.isAriaDownload:
@@ -554,6 +832,9 @@ def onDownloadStart(self, mirrorInfo: 'MirrorInfo') -> None:
if mirrorInfo.isMegaDownload:
self.botHelper.threadingHelper.initThread(target=self.botHelper.megaHelper.addDownload,
name=f'{mirrorInfo.uid}-MegaDownload', mirrorInfo=mirrorInfo)
+ if mirrorInfo.isQbitTorrentDownload:
+ self.botHelper.threadingHelper.initThread(target=self.botHelper.qbitTorrentHelper.addDownload,
+ name=f'{mirrorInfo.uid}-QbitTorrentDownload', mirrorInfo=mirrorInfo)
if mirrorInfo.isTelegramDownload:
self.botHelper.threadingHelper.initThread(target=self.botHelper.telegramHelper.addDownload,
name=f'{mirrorInfo.uid}-TelegramDownload', mirrorInfo=mirrorInfo)
@@ -593,6 +874,10 @@ def checkCompressionQueue(self) -> None:
self.compressionQueueActive += 1
self.checkCompressionQueue()
+ def removeCompressionQueue(self, uid: str) -> None:
+ self.compressionQueue.remove(uid)
+ self.checkCompressionQueue()
+
def onCompressionStart(self, mirrorInfo: 'MirrorInfo') -> None:
self.botHelper.threadingHelper.initThread(target=self.botHelper.compressionHelper.addCompression,
name=f'{mirrorInfo.uid}-Compression', mirrorInfo=mirrorInfo)
@@ -629,6 +914,10 @@ def checkDecompressionQueue(self) -> None:
self.decompressionQueueActive += 1
self.checkDecompressionQueue()
+ def removeDecompressionQueue(self, uid: str) -> None:
+ self.decompressionQueue.remove(uid)
+ self.checkDecompressionQueue()
+
def onDecompressionStart(self, mirrorInfo: 'MirrorInfo') -> None:
self.botHelper.threadingHelper.initThread(target=self.botHelper.decompressionHelper.addDecompression,
name=f'{mirrorInfo.uid}-Decompression', mirrorInfo=mirrorInfo)
@@ -660,6 +949,10 @@ def checkUploadQueue(self) -> None:
self.uploadQueueActive += 1
self.checkUploadQueue()
+ def removeUploadQueue(self, uid: str) -> None:
+ self.uploadQueue.remove(uid)
+ self.checkUploadQueue()
+
def onUploadStart(self, mirrorInfo: 'MirrorInfo') -> None:
if mirrorInfo.isGoogleDriveUpload:
self.botHelper.threadingHelper.initThread(target=self.botHelper.googleDriveHelper.addUpload,
@@ -693,12 +986,14 @@ def resetMirrorProgress(self, uid: str) -> None:
class LoggingHelper(BaseHelper):
LogFormats: typing.Dict[str, str] = \
- {'DEFAULT': '{time:YYYY-MM-DD HH:mm:ss.SSS} | {level: <8} | '
- '{name}:{function}:{line} - {message}',
- 'INFO': '{time:YYYY-MM-DD HH:mm:ss.SSS} | {level: <6} | {message}',
- 'DEBUG': '{time:YYYY-MM-DD HH:mm:ss.SSS} | {level: <8} | '
- '{name}:{extra[classname]}:{function}():{line} | '
- '{message}'}
+ {
+ 'DEFAULT': '{time:YYYY-MM-DD HH:mm:ss.SSS} | {level: <8} | '
+ '{name}:{function}:{line} - {message}',
+ 'INFO': '{time:YYYY-MM-DD HH:mm:ss.SSS} | {level: <6} | {message}',
+ 'DEBUG': '{time:YYYY-MM-DD HH:mm:ss.SSS} | {level: <8} | '
+ '{name}:{extra[classname]}:{function}():{line} | '
+ '{message}'
+ }
def __init__(self, botHelper: BotHelper):
super().__init__(botHelper)
@@ -706,12 +1001,22 @@ def __init__(self, botHelper: BotHelper):
def initHelper(self) -> None:
self.logDebugFile = 'log.debug'
self.isChangeLogLevel: bool = False
- self.logFiles: typing.List[str] = ['bot.log', 'botApiServer.log', 'ariaDaemon.log',
- 'tqueue.binlog', 'webhooks_db.binlog']
+ self.logFiles: typing.List[str] = \
+ [
+ 'bot.log',
+ 'botApiServer.log',
+ 'ariaDaemon.log',
+ 'tqueue.binlog',
+ 'webhooks_db.binlog'
+ ]
if os.path.exists(self.logFiles[0]):
os.remove(self.logFiles[0])
self.logLevel = (list(self.LogFormats.keys())[2] if os.path.exists(self.logDebugFile) else list(self.LogFormats.keys())[1])
- self.logDisableModules: typing.List[str] = ['apscheduler', 'telegram.vendor.ptb_urllib3.urllib3.connectionpool']
+ self.logDisableModules: typing.List[str] = \
+ [
+ 'apscheduler',
+ 'telegram.vendor.ptb_urllib3.urllib3.connectionpool'
+ ]
self.logger = loguru.logger
self.logger.remove()
self.logger.add(sys.stderr, level=self.logLevel, format=self.LogFormats[self.logLevel])
@@ -747,6 +1052,94 @@ def delLogFiles(self) -> None:
self.logger.debug(f"Deleted: '{logFile}'")
+class MirrorHelper(BaseHelper):
+ def __init__(self, botHelper: BotHelper):
+ super().__init__(botHelper)
+
+ def initHelper(self) -> None:
+ super().initHelper()
+ self.mirrorInfos: typing.Dict[str, MirrorInfo] = {}
+ self.supportedArchiveFormats: typing.Dict[str, str] = \
+ {
+ 'zip': '.zip',
+ 'tar': '.tar',
+ 'bztar': '.tar.bz2',
+ 'gztar': '.tar.gz',
+ 'xztar': '.tar.xz'
+ }
+
+ def addMirror(self, mirrorInfo: 'MirrorInfo') -> None:
+ self.logger.debug(vars(mirrorInfo))
+ self.mirrorInfos[mirrorInfo.uid] = mirrorInfo
+ self.mirrorInfos[mirrorInfo.uid].timeStart = time.time()
+ self.botHelper.listenerHelper.updateStatus(mirrorInfo.uid, MirrorStatus.addMirror)
+ self.botHelper.threadingHelper.initThread(target=self.botHelper.statusHelper.addStatus, name=f'{mirrorInfo.uid}-addStatus',
+ chatId=mirrorInfo.chatId, msgId=mirrorInfo.msgId)
+
+ def cancelMirror(self, msg: telegram.Message) -> None:
+ if self.mirrorInfos == {}:
+ self.logger.info('No Active Downloads !')
+ return
+ uids: typing.List[str] = []
+ try:
+ msgTxt = msg.text.split(' ')[1].strip()
+ if msgTxt == 'all':
+ uids = list(self.mirrorInfos.keys())
+ if msgTxt in self.mirrorInfos.keys():
+ uids.append(msgTxt)
+ except IndexError:
+ replyTo = msg.reply_to_message
+ if replyTo:
+ msgId = replyTo.message_id
+ for mirrorInfo in self.mirrorInfos.values():
+ if msgId == mirrorInfo.msgId:
+ uids.append(mirrorInfo.uid)
+ break
+ if len(uids) == 0:
+ self.logger.info('No Valid Mirror Found !')
+ return
+ for uid in uids:
+ self.botHelper.listenerHelper.updateStatus(uid, MirrorStatus.cancelMirror)
+
+ def genMirrorInfo(self, msg: telegram.Message) -> (bool, 'MirrorInfo'):
+ mirrorInfo = MirrorInfo(msg, self.botHelper)
+ isValidDl: bool = True
+ try:
+ mirrorInfo.downloadUrl = msg.text.split(' ')[1].strip()
+ mirrorInfo.tag = msg.from_user.username
+ if re.findall(UrlRegex.googleDrive, mirrorInfo.downloadUrl):
+ mirrorInfo.isGoogleDriveDownload = True
+ elif re.findall(UrlRegex.mega, mirrorInfo.downloadUrl):
+ mirrorInfo.isMegaDownload = True
+ elif re.findall(UrlRegex.youTube, mirrorInfo.downloadUrl):
+ mirrorInfo.isYouTubeDownload = True
+ elif re.findall(UrlRegex.bittorrentMagnet, mirrorInfo.downloadUrl):
+ mirrorInfo.isQbitTorrentDownload = True
+ elif re.findall(UrlRegex.generalUrl, mirrorInfo.downloadUrl):
+ mirrorInfo.isAriaDownload = True
+ else:
+ isValidDl = False
+ except IndexError:
+ replyTo = msg.reply_to_message
+ if replyTo:
+ mirrorInfo.tag = replyTo.from_user.username
+ for media in [replyTo.document, replyTo.audio, replyTo.video]:
+ if media:
+ if media.mime_type == self.botHelper.torrentFileMimeType:
+ mirrorInfo.isQbitTorrentDownload = True
+ torrentFile = media.get_file().file_path
+ mirrorInfo.downloadUrl = self.botHelper.getHelper.magnetFromTorrentFile(torrentFile)
+ os.remove(torrentFile)
+ else:
+ mirrorInfo.isTelegramDownload = True
+ break
+ else:
+ isValidDl = False
+ if not isValidDl:
+ self.logger.info('No Valid Link Provided !')
+ return isValidDl, mirrorInfo
+
+
class StatusHelper(BaseHelper):
def __init__(self, botHelper: BotHelper):
super().__init__(botHelper)
@@ -754,9 +1147,9 @@ def __init__(self, botHelper: BotHelper):
def initHelper(self) -> None:
super().initHelper()
self.updaterLock = threading.Lock()
- self.isInitThread: bool = False
- self.isUpdateStatus: bool = False
- self.statusUpdateInterval: int = int(self.botHelper.configHelper.configVars[self.botHelper.configHelper.optVars[5]])
+ self.isContinue: bool = False
+ self.isUpdate: bool = False
+ self.statusUpdateInterval: float = float(self.botHelper.configHelper.configVars[self.botHelper.configHelper.optVars[6]])
self.msgId: int = 0
self.chatId: int = 0
self.lastStatusMsgId: int = 0
@@ -764,35 +1157,29 @@ def initHelper(self) -> None:
def addStatus(self, chatId: int, msgId: int) -> None:
with self.updaterLock:
- if self.botHelper.mirrorHelper.mirrorInfos != {}:
- self.isUpdateStatus = True
- else:
- self.isUpdateStatus = False
- if self.lastStatusMsgId == 0:
- self.isInitThread = True
if self.lastStatusMsgId != 0:
self.botHelper.bot.deleteMessage(chat_id=self.chatId, message_id=self.lastStatusMsgId)
self.chatId = chatId
self.msgId = msgId
self.lastStatusMsgId = self.botHelper.bot.sendMessage(text='...', parse_mode='HTML', chat_id=self.chatId,
reply_to_message_id=self.msgId).message_id
- if self.isInitThread:
- self.isInitThread = False
- self.botHelper.threadingHelper.initThread(target=self.updateStatusMsg, name='statusUpdaterStart')
+ self.isUpdate = True
def getStatusMsgTxt(self) -> str:
statusMsgTxt = ''
for uid in self.botHelper.mirrorHelper.mirrorInfos.keys():
mirrorInfo: MirrorInfo = self.botHelper.mirrorHelper.mirrorInfos[uid]
- statusMsgTxt += f'{mirrorInfo.uid}
| {mirrorInfo.status}\n'
- if mirrorInfo.status in [MirrorStatus.downloadProgress, MirrorStatus.uploadProgress]:
- if mirrorInfo.status == MirrorStatus.downloadProgress and mirrorInfo.isAriaDownload:
- self.botHelper.ariaHelper.updateProgress(mirrorInfo.uid)
+ statusMsgTxt += f'{mirrorInfo.uid}
| {mirrorInfo.currentStatus}\n'
+ if mirrorInfo.currentStatus in [MirrorStatus.downloadProgress, MirrorStatus.uploadProgress]:
+ if mirrorInfo.currentStatus == MirrorStatus.downloadProgress:
+ if mirrorInfo.isAriaDownload:
+ self.botHelper.ariaHelper.updateProgress(mirrorInfo.uid)
+ if mirrorInfo.isQbitTorrentDownload:
+ self.botHelper.qbitTorrentHelper.updateProgress(mirrorInfo.uid)
statusMsgTxt += f'S: {self.botHelper.getHelper.readableSize(mirrorInfo.sizeCurrent)} | ' \
f'{self.botHelper.getHelper.readableSize(mirrorInfo.sizeTotal)} | ' \
f'{self.botHelper.getHelper.readableSize(mirrorInfo.sizeTotal - mirrorInfo.sizeCurrent)}\n' \
- f'P: {self.botHelper.getHelper.progressBar(mirrorInfo.progressPercent)}
| ' \
- f'{mirrorInfo.progressPercent}% | ' \
+ f'P: {self.botHelper.getHelper.progressBar(mirrorInfo.progressPercent)}
| {mirrorInfo.progressPercent}% | ' \
f'{self.botHelper.getHelper.readableSize(mirrorInfo.speedCurrent)}/s\n' \
f'T: {self.botHelper.getHelper.readableTime(int(mirrorInfo.timeCurrent - mirrorInfo.timeStart))} | ' \
f'{self.botHelper.getHelper.readableTime(int(mirrorInfo.timeEnd - mirrorInfo.timeCurrent))}\n'
@@ -800,35 +1187,54 @@ def getStatusMsgTxt(self) -> str:
return statusMsgTxt
def updateStatusMsg(self) -> None:
- with self.updaterLock:
- if self.isUpdateStatus:
- if self.botHelper.mirrorHelper.mirrorInfos:
- statusMsgTxt = self.getStatusMsgTxt()
- if statusMsgTxt != self.lastStatusMsgTxt:
- self.botHelper.bot.editMessageText(text=statusMsgTxt, parse_mode='HTML', chat_id=self.chatId,
- message_id=self.lastStatusMsgId)
- self.lastStatusMsgTxt = statusMsgTxt
- time.sleep(self.statusUpdateInterval - 1)
- time.sleep(1)
- self.botHelper.threadingHelper.initThread(target=self.updateStatusMsg, name='statusUpdaterContinue')
- return
- if not self.botHelper.mirrorHelper.mirrorInfos:
- self.isUpdateStatus = False
- self.botHelper.threadingHelper.initThread(target=self.updateStatusMsg, name='statusUpdaterEnd')
- return
- if not self.isUpdateStatus:
- self.botHelper.bot.editMessageText(text='No Active Downloads !', parse_mode='HTML',
- chat_id=self.chatId, message_id=self.lastStatusMsgId)
- self.resetAllDat()
+ while self.isContinue:
+ with self.updaterLock:
+ if self.isUpdate:
+ if self.botHelper.mirrorHelper.mirrorInfos:
+ statusMsgTxt = self.getStatusMsgTxt()
+ if statusMsgTxt != self.lastStatusMsgTxt:
+ self.botHelper.bot.editMessageText(text=statusMsgTxt, parse_mode='HTML',
+ chat_id=self.chatId, message_id=self.lastStatusMsgId)
+ self.lastStatusMsgTxt = statusMsgTxt
+ time.sleep(self.statusUpdateInterval - 1.0)
+ if not self.botHelper.mirrorHelper.mirrorInfos:
+ self.botHelper.bot.editMessageText(text='No Active Downloads !', parse_mode='HTML',
+ chat_id=self.chatId, message_id=self.lastStatusMsgId)
+ self.resetAllDat()
+ time.sleep(1.0)
def resetAllDat(self) -> None:
- self.isInitThread = False
- self.isUpdateStatus = False
+ self.isUpdate = False
self.msgId = 0
self.chatId = 0
self.lastStatusMsgId = 0
self.lastStatusMsgTxt = ''
+ def updaterStart(self) -> None:
+ self.isContinue = True
+ self.botHelper.threadingHelper.initThread(target=self.updateStatusMsg, name='statusUpdater')
+
+ def updaterStop(self) -> None:
+ self.isContinue = False
+ self.botHelper.threadingHelper.runningThreads[0].join()
+
+
+class SubprocessHelper(BaseHelper):
+ def __init__(self, botHelper: BotHelper):
+ super().__init__(botHelper)
+
+ def initHelper(self) -> None:
+ super().initHelper()
+
+ @staticmethod
+ def procInit(procStartCmd: typing.List[str]) -> subprocess.Popen:
+ return subprocess.Popen(procStartCmd, start_new_session=True,
+ stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
+
+ @staticmethod
+ def procTerm(procPid: int) -> None:
+ return os.kill(procPid, signal.SIGTERM)
+
class ThreadingHelper(BaseHelper):
def __init__(self, botHelper: BotHelper):
@@ -902,10 +1308,20 @@ def initHelper(self) -> None:
self.syncCmdHandler = telegram.ext.CommandHandler(command=self.SyncCmd.command,
callback=self.syncCallBack, run_async=True)
self.cmdHandlers: typing.List[telegram.ext.CommandHandler] = \
- [self.startCmdHandler, self.helpCmdHandler, self.statsCmdHandler, self.pingCmdHandler,
- self.restartCmdHandler, self.statusCmdHandler, self.cancelCmdHandler, self.listCmdHandler,
- self.deleteCmdHandler, self.authorizeCmdHandler, self.unauthorizeCmdHandler,
- self.syncCmdHandler]
+ [
+ self.startCmdHandler,
+ self.helpCmdHandler,
+ self.statsCmdHandler,
+ self.pingCmdHandler,
+ self.restartCmdHandler,
+ self.statusCmdHandler,
+ self.cancelCmdHandler,
+ self.listCmdHandler,
+ self.deleteCmdHandler,
+ self.authorizeCmdHandler,
+ self.unauthorizeCmdHandler,
+ self.syncCmdHandler
+ ]
def startCallBack(self, update: telegram.Update, _: telegram.ext.CallbackContext):
self.botHelper.bot.sendMessage(text=f'A Telegram Bot Written in Python to Mirror Files on the Internet to Google Drive.\n'
@@ -1017,7 +1433,11 @@ def initHelper(self) -> None:
super().initHelper()
self.initSubHelpers()
self.convHandlers: typing.List[telegram.ext.ConversationHandler] = \
- [self.configConvHelper.handler, self.logConvHelper.handler, self.mirrorConvHelper.handler]
+ [
+ self.configConvHelper.handler,
+ self.logConvHelper.handler,
+ self.mirrorConvHelper.handler
+ ]
def initSubHelpers(self):
self.configConvHelper.initHelper()
@@ -1044,20 +1464,30 @@ def initHelper(self) -> None:
states={
# ZEROTH
# Choose Environment Variable
- self.FIRST: [telegram.ext.CallbackQueryHandler(self.stageOne)],
+ self.FIRST: [
+ telegram.ext.CallbackQueryHandler(self.stageOne)
+ ],
# Show Existing Value
- self.SECOND: [telegram.ext.CallbackQueryHandler(self.stageTwo)],
+ self.SECOND: [
+ telegram.ext.CallbackQueryHandler(self.stageTwo)
+ ],
# Capture New Value for Environment Variable
self.THIRD: [
telegram.ext.CallbackQueryHandler(self.stageThree),
telegram.ext.MessageHandler(telegram.ext.Filters.text, self.newVal)
],
# Verify New Value
- self.FOURTH: [telegram.ext.CallbackQueryHandler(self.stageFour)],
+ self.FOURTH: [
+ telegram.ext.CallbackQueryHandler(self.stageFour)
+ ],
# Show All Changes and Proceed
- self.FIFTH: [telegram.ext.CallbackQueryHandler(self.stageFive)],
+ self.FIFTH: [
+ telegram.ext.CallbackQueryHandler(self.stageFive)
+ ],
# Save or Discard Changes
- self.SIXTH: [telegram.ext.CallbackQueryHandler(self.stageSix)]
+ self.SIXTH: [
+ telegram.ext.CallbackQueryHandler(self.stageSix)
+ ]
# Exit or Start Over
},
conversation_timeout=120, run_async=True)
@@ -1118,9 +1548,14 @@ def stageSix(self, update: telegram.Update, _: telegram.ext.CallbackContext) ->
def loadConfigDict(self):
self.resetAllDat()
self.configVarsEditable = self.botHelper.configHelper.jsonFileLoad(self.botHelper.configHelper.configJsonFile)
- for key in [self.botHelper.configHelper.reqVars[4], self.botHelper.configHelper.reqVars[5],
- self.botHelper.configHelper.optVars[0], self.botHelper.configHelper.optVars[1],
- self.botHelper.configHelper.optVars[4]]:
+ for key in [
+ self.botHelper.configHelper.reqVars[4],
+ self.botHelper.configHelper.reqVars[5],
+ self.botHelper.configHelper.optVars[0],
+ self.botHelper.configHelper.optVars[1],
+ self.botHelper.configHelper.optVars[4],
+ self.botHelper.configHelper.optVars[5]
+ ]:
if key in list(self.configVarsEditable.keys()):
self.configVarsEditable.pop(key)
@@ -1210,7 +1645,11 @@ def initHelper(self) -> None:
# TODO: filter - restrict to user who sent LogCommand
self.cmdHandler = telegram.ext.CommandHandler(self.botHelper.commandHelper.LogCmd.command, self.stageZero)
self.handler = telegram.ext.ConversationHandler(entry_points=[self.cmdHandler], fallbacks=[self.cmdHandler],
- states={self.FIRST: [telegram.ext.CallbackQueryHandler(self.stageOne)]},
+ states={
+ self.FIRST: [
+ telegram.ext.CallbackQueryHandler(self.stageOne)
+ ]
+ },
conversation_timeout=120, run_async=True)
def stageZero(self, update: telegram.Update, _: telegram.ext.CallbackContext) -> int:
@@ -1258,15 +1697,25 @@ def initHelper(self) -> None:
states={
# ZEROTH
# Choose to Modify or Use Default Values
- self.FIRST: [telegram.ext.CallbackQueryHandler(self.stageOne)],
+ self.FIRST: [
+ telegram.ext.CallbackQueryHandler(self.stageOne)
+ ],
# Choose Upload Location
- self.SECOND: [telegram.ext.CallbackQueryHandler(self.stageTwo)],
+ self.SECOND: [
+ telegram.ext.CallbackQueryHandler(self.stageTwo)
+ ],
# Choose googleDriveUploadFolder
- self.THIRD: [telegram.ext.CallbackQueryHandler(self.stageThree)],
+ self.THIRD: [
+ telegram.ext.CallbackQueryHandler(self.stageThree)
+ ],
# Choose Compress / Decompress
- self.FOURTH: [telegram.ext.CallbackQueryHandler(self.stageFour)],
+ self.FOURTH: [
+ telegram.ext.CallbackQueryHandler(self.stageFour)
+ ],
# Confirm and Proceed / Cancel
- self.FIFTH: [telegram.ext.CallbackQueryHandler(self.stageFive)]
+ self.FIFTH: [
+ telegram.ext.CallbackQueryHandler(self.stageFive)
+ ]
},
conversation_timeout=120, run_async=True)
@@ -1356,6 +1805,8 @@ def getMirrorInfoStr(self):
mirrorInfoStr += f'[isGoogleDriveDownload | True]\n'
elif self.mirrorInfo.isMegaDownload:
mirrorInfoStr += f'[isMegaDownload | True]\n'
+ elif self.mirrorInfo.isQbitTorrentDownload:
+ mirrorInfoStr += f'[isQbitTorrentDownload | True]\n'
elif self.mirrorInfo.isTelegramDownload:
mirrorInfoStr += f'[isTelegramDownload | True]\n'
elif self.mirrorInfo.isYouTubeDownload:
@@ -1375,110 +1826,42 @@ def getMirrorInfoStr(self):
return mirrorInfoStr
-class MirrorHelper(BaseHelper):
- def __init__(self, botHelper: BotHelper):
- super().__init__(botHelper)
-
- def initHelper(self) -> None:
- super().initHelper()
- self.mirrorInfos: typing.Dict[str, MirrorInfo] = {}
- self.supportedArchiveFormats: typing.Dict[str, str] = {'zip': '.zip', 'tar': '.tar', 'bztar': '.tar.bz2',
- 'gztar': '.tar.gz', 'xztar': '.tar.xz'}
-
- def addMirror(self, mirrorInfo: 'MirrorInfo') -> None:
- self.logger.debug(vars(mirrorInfo))
- self.mirrorInfos[mirrorInfo.uid] = mirrorInfo
- self.mirrorInfos[mirrorInfo.uid].timeStart = time.time()
- self.botHelper.listenerHelper.updateStatus(mirrorInfo.uid, MirrorStatus.addMirror)
- self.botHelper.threadingHelper.initThread(target=self.botHelper.statusHelper.addStatus, name=f'{mirrorInfo.uid}-addStatus',
- chatId=mirrorInfo.chatId, msgId=mirrorInfo.msgId)
-
- def cancelMirror(self, msg: telegram.Message) -> None:
- if self.mirrorInfos == {}:
- self.logger.info('No Active Downloads !')
- return
- uids: typing.List[str] = []
- try:
- msgTxt = msg.text.split(' ')[1].strip()
- if msgTxt == 'all':
- uids = list(self.mirrorInfos.keys())
- if msgTxt in self.mirrorInfos.keys():
- uids.append(msgTxt)
- except IndexError:
- replyTo = msg.reply_to_message
- if replyTo:
- msgId = replyTo.message_id
- for mirrorInfo in self.mirrorInfos.values():
- if msgId == mirrorInfo.msgId:
- uids.append(mirrorInfo.uid)
- break
- if len(uids) == 0:
- self.logger.info('No Valid Mirror Found !')
- return
- for uid in uids:
- self.botHelper.listenerHelper.updateStatus(uid, MirrorStatus.cancelMirror)
-
- def genMirrorInfo(self, msg: telegram.Message) -> (bool, 'MirrorInfo'):
- mirrorInfo = MirrorInfo(msg, self.botHelper)
- isValidDl: bool = True
- try:
- mirrorInfo.downloadUrl = msg.text.split(' ')[1].strip()
- mirrorInfo.tag = msg.from_user.username
- if re.findall(UrlRegex.googleDrive, mirrorInfo.downloadUrl):
- mirrorInfo.isGoogleDriveDownload = True
- elif re.findall(UrlRegex.mega, mirrorInfo.downloadUrl):
- mirrorInfo.isMegaDownload = True
- elif re.findall(UrlRegex.youTube, mirrorInfo.downloadUrl):
- mirrorInfo.isYouTubeDownload = True
- elif re.findall(UrlRegex.bittorrentMagnet, mirrorInfo.downloadUrl):
- mirrorInfo.isAriaDownload = True
- elif re.findall(UrlRegex.generalUrl, mirrorInfo.downloadUrl):
- mirrorInfo.isAriaDownload = True
- else:
- isValidDl = False
- except IndexError:
- replyTo = msg.reply_to_message
- if replyTo:
- mirrorInfo.tag = replyTo.from_user.username
- for media in [replyTo.document, replyTo.audio, replyTo.video]:
- if media:
- if media.mime_type == 'application/x-bittorrent':
- mirrorInfo.isAriaDownload = True
- mirrorInfo.downloadUrl = media.get_file().file_path
- else:
- mirrorInfo.isTelegramDownload = True
- break
- else:
- isValidDl = False
- if not isValidDl:
- self.logger.info('No Valid Link Provided !')
- return isValidDl, mirrorInfo
-
-
class AriaHelper(BaseHelper):
def __init__(self, botHelper: BotHelper):
super().__init__(botHelper)
def initHelper(self) -> None:
super().initHelper()
+ self.rpcListenPort = 7200
self.rpcSecret = (self.botHelper.restartVars['ariaRpcSecret'] if self.botHelper.restartVars else self.botHelper.getHelper.randomString(8))
- self.api = aria2p.API(aria2p.Client(host="http://localhost", port=6800, secret=self.rpcSecret))
+ self.api = aria2p.API(aria2p.Client(host="http://localhost", port=self.rpcListenPort, secret=self.rpcSecret))
+ self.confFile = 'aria.conf'
+ self.confFileDir = 'aria/config'
+ self.confDefaults: typing.Dict[str, str] = \
+ {
+ 'enable-rpc': 'true',
+ 'rpc-listen-port': self.rpcListenPort,
+ 'rpc-secret': self.rpcSecret,
+ 'rpc-max-request-size': '32M',
+ 'disable-ipv6': 'true',
+ 'log': self.botHelper.loggingHelper.logFiles[2]
+ }
self.daemonPid: int = 0
- self.daemonStartCmd: typing.List[str] = ['aria2c', '--quiet', '--enable-rpc', f'--rpc-secret={self.rpcSecret}',
- '--rpc-max-request-size=32M', f'--log={self.botHelper.loggingHelper.logFiles[2]}']
+ self.daemonStartCmd: typing.List[str] = \
+ [
+ 'aria2c',
+ f'--conf-path={os.getcwd()}/{self.confFileDir}/{self.confFile}'
+ ]
self.globalOpts: aria2p.Options
self.trackersListFile = 'trackers.list'
self.gids: typing.Dict[str, str] = {}
def addDownload(self, mirrorInfo: 'MirrorInfo') -> None:
- if re.findall(UrlRegex.bittorrentMagnet, mirrorInfo.downloadUrl):
- self.gids[mirrorInfo.uid] = self.api.add_magnet(mirrorInfo.downloadUrl, options={'dir': mirrorInfo.path}).gid
- if re.findall(UrlRegex.generalUrl, mirrorInfo.downloadUrl):
- self.gids[mirrorInfo.uid] = self.api.add_uris([mirrorInfo.downloadUrl], options={'dir': mirrorInfo.path}).gid
+ self.gids[mirrorInfo.uid] = self.api.add_uris([mirrorInfo.downloadUrl], options={'dir': mirrorInfo.path}).gid
- def cancelDownload(self, uid: str) -> None:
- self.getDlObj(self.gids[uid]).remove(force=True, files=True)
- self.gids.pop(uid)
+ def cancelDownload(self, mirrorInfo: 'MirrorInfo') -> None:
+ self.getDlObj(self.gids[mirrorInfo.uid]).remove(force=True)
+ self.gids.pop(mirrorInfo.uid)
def getUid(self, gid: str) -> str:
for uid in self.gids.keys():
@@ -1493,32 +1876,28 @@ def daemonStart(self) -> None:
self.daemonPid = self.botHelper.restartVars['ariaDaemonPid']
self.logger.info(f'ariaDaemon Already Running (pid {self.daemonPid}) !')
if not self.daemonPid:
- self.daemonPid = subprocess.Popen(self.daemonStartCmd).pid
- self.logger.info(f"ariaDaemon started (pid {self.daemonPid}) !")
+ self.daemonPid = self.botHelper.subprocessHelper.procInit(self.daemonStartCmd).pid
+ self.logger.info(f'ariaDaemon Started (pid {self.daemonPid}) !')
# TODO: implement this method
- def daemonCheck(self):
+ def daemonCheck(self) -> None:
pass
def daemonStop(self) -> None:
- os.kill(self.daemonPid, signal.SIGTERM)
- self.logger.info(f"ariaDaemon terminated (pid {self.daemonPid})")
+ self.botHelper.subprocessHelper.procTerm(self.daemonPid)
+ self.logger.info(f'ariaDaemon Terminated (pid {self.daemonPid}) !')
- def globalOptsGet(self):
+ def globalOptsGet(self) -> None:
self.globalOpts = self.api.get_global_options()
- def globalOptsSet(self):
- userOpts = copy.deepcopy(self.botHelper.configHelper.configVars[self.botHelper.configHelper.optVars[0]])
- userOpts['bt-tracker'] = open(self.trackersListFile, 'rt').read()
- for optKey in list(userOpts.keys()):
- optSetResponse = self.globalOpts.set(optKey, userOpts[optKey])
- self.logger.debug(f"(ariaGlobalOpts) ({optSetResponse}) ['{optKey}' : '{userOpts[optKey]}']")
+ def globalOptsSet(self, optKey: str, optVal: str) -> None:
+ self.logger.debug(f"(ariaGlobalOpts) ({self.globalOpts.set(optKey, optVal)}) ['{optKey}' : '{optVal}']")
- def dlTrackersList(self):
+ def getTrackersList(self) -> None:
if os.path.exists(self.trackersListFile):
os.remove(self.trackersListFile)
self.logger.debug(f"Downloading '{self.trackersListFile}' ...")
- dlObj = self.api.add_uris(uris=[self.botHelper.configHelper.configVars[self.botHelper.configHelper.optVars[6]]],
+ dlObj = self.api.add_uris(uris=[self.botHelper.configHelper.configVars[self.botHelper.configHelper.optVars[7]]],
options={'out': self.trackersListFile})
while dlObj.status == 'active':
time.sleep(0.1)
@@ -1527,28 +1906,47 @@ def dlTrackersList(self):
self.logger.debug(f"Downloaded '{self.trackersListFile}' !")
else:
self.logger.debug(f"Download Failed - '{self.trackersListFile}' ! Retrying...")
- self.dlTrackersList()
+ self.getTrackersList()
+
+ def makeConf(self) -> None:
+ if os.path.exists(self.confFileDir.split('/')[0]):
+ shutil.rmtree(self.confFileDir.split('/')[0])
+ os.mkdir(self.confFileDir.split('/')[0])
+ os.mkdir(self.confFileDir)
+ confStr = ''
+ confData: typing.Dict = \
+ {
+ **self.confDefaults,
+ **self.botHelper.configHelper.configVars['ariaConf']
+ }
+ for confKey in confData.keys():
+ confStr += f'{confKey}={confData[confKey]}\n'
+ open(f'{self.confFileDir}/{self.confFile}', 'wt').write(confStr)
+
+ def removeAllDownloads(self) -> None:
+ self.api.remove_all(force=True)
def startListener(self) -> None:
- self.api.listen_to_notifications(threaded=True,
+ self.api.listen_to_notifications(threaded=True, handle_signals=False,
on_download_start=self.onDownloadStart,
on_download_pause=self.onDownloadPause,
on_download_complete=self.onDownloadComplete,
on_download_stop=self.onDownloadStop,
on_download_error=self.onDownloadError)
+ def stopListener(self) -> None:
+ self.api.stop_listening()
+
def updateProgress(self, uid: str) -> None:
if uid in self.gids.keys():
dlObj = self.getDlObj(self.gids[uid])
currVars: typing.Dict[str, typing.Union[int, float, str]] = \
- {MirrorInfo.updatableVars[0]: dlObj.total_length,
- MirrorInfo.updatableVars[1]: dlObj.completed_length,
- MirrorInfo.updatableVars[2]: dlObj.download_speed,
- MirrorInfo.updatableVars[3]: time.time()}
- if dlObj.is_torrent:
- currVars[MirrorInfo.updatableVars[4]] = True
- currVars[MirrorInfo.updatableVars[5]] = dlObj.num_seeders
- currVars[MirrorInfo.updatableVars[6]] = dlObj.connections
+ {
+ MirrorInfo.UpdatableVars[0]: dlObj.total_length,
+ MirrorInfo.UpdatableVars[1]: dlObj.completed_length,
+ MirrorInfo.UpdatableVars[2]: dlObj.download_speed,
+ MirrorInfo.UpdatableVars[3]: time.time()
+ }
self.botHelper.mirrorHelper.mirrorInfos[uid].updateVars(currVars)
def onDownloadStart(self, _: aria2p.API, gid: str) -> None:
@@ -1559,10 +1957,19 @@ def onDownloadPause(self, _: aria2p.API, gid: str) -> None:
def onDownloadComplete(self, _: aria2p.API, gid: str) -> None:
self.logger.debug(vars(self.getDlObj(gid)))
- if self.getDlObj(gid).followed_by_ids:
- self.gids[self.getUid(gid)] = self.getDlObj(gid).followed_by_ids[0]
- return
- self.botHelper.listenerHelper.updateStatus(self.getUid(gid), MirrorStatus.downloadComplete)
+ uid = self.getUid(gid)
+ mirrorStatus = MirrorStatus.downloadComplete
+ dlPath = self.botHelper.mirrorHelper.mirrorInfos[uid].path
+ dlContent = os.path.join(dlPath, os.listdir(dlPath)[0])
+ if os.path.isfile(dlContent) and (magic.Magic(mime=True).from_file(dlContent) == self.botHelper.torrentFileMimeType):
+ self.botHelper.mirrorHelper.mirrorInfos[uid].isAriaDownload = False
+ self.botHelper.mirrorHelper.mirrorInfos[uid].isQbitTorrentDownload = True
+ self.botHelper.mirrorHelper.mirrorInfos[uid].downloadUrl = self.botHelper.getHelper.magnetFromTorrentFile(dlContent)
+ os.remove(dlContent)
+ os.rmdir(self.botHelper.mirrorHelper.mirrorInfos[uid].path)
+ mirrorStatus = MirrorStatus.downloadStart
+ self.botHelper.listenerHelper.updateStatus(uid, mirrorStatus)
+ self.gids.pop(uid)
def onDownloadStop(self, _: aria2p.API, gid: str) -> None:
self.logger.debug(vars(self.getDlObj(gid)))
@@ -1577,14 +1984,21 @@ def __init__(self, botHelper: BotHelper):
def initHelper(self) -> None:
super().initHelper()
- self.authInfos: typing.List[str] = ['saJson', 'tokenJson']
- self.authTypes: typing.List[str] = ['saAuth', 'userAuth']
+ self.authInfos: typing.List[str] = \
+ [
+ 'saJson',
+ 'tokenJson'
+ ]
+ self.authTypes: typing.List[str] = \
+ [
+ 'saAuth',
+ 'userAuth'
+ ]
self.oauthScopes: typing.List[str] = ['https://www.googleapis.com/auth/drive']
self.baseFileDownloadUrl: str = 'https://drive.google.com/uc?id={}&export=download'
self.baseFolderDownloadUrl: str = 'https://drive.google.com/drive/folders/{}'
self.googleDriveFolderMimeType: str = 'application/vnd.google-apps.folder'
self.chunkSize: int = 32 * 1024 * 1024
- self.service: typing.Any = None
if self.botHelper.configHelper.configVars[self.botHelper.configHelper.reqVars[4]]['authType'] == self.authTypes[0] and \
self.botHelper.configHelper.configVars[self.botHelper.configHelper.reqVars[4]]['authInfos'][self.authInfos[0]]:
self.oauthCreds: google.oauth2.service_account.Credentials \
@@ -1601,7 +2015,7 @@ def initHelper(self) -> None:
def addDownload(self, mirrorInfo: 'MirrorInfo') -> None:
sourceId = self.getIdFromUrl(mirrorInfo.downloadUrl)
- self.botHelper.mirrorHelper.mirrorInfos[mirrorInfo.uid].updateVars({mirrorInfo.updatableVars[0]: self.getSizeById(sourceId)})
+ self.botHelper.mirrorHelper.mirrorInfos[mirrorInfo.uid].updateVars({mirrorInfo.UpdatableVars[0]: self.getSizeById(sourceId)})
isFolder = False
if self.getMetadataById(sourceId, 'mimeType') == self.googleDriveFolderMimeType:
isFolder = True
@@ -1619,12 +2033,12 @@ def addDownload(self, mirrorInfo: 'MirrorInfo') -> None:
self.downloadFile(sourceFileId=sourceId, dlPath=mirrorInfo.path, uid=mirrorInfo.uid)
self.botHelper.listenerHelper.updateStatus(mirrorInfo.uid, MirrorStatus.downloadComplete)
- def cancelDownload(self, uid: str) -> None:
+ def cancelDownload(self, mirrorInfo: 'MirrorInfo') -> None:
raise NotImplementedError
def addUpload(self, mirrorInfo: 'MirrorInfo') -> None:
if not (mirrorInfo.isGoogleDriveDownload and not (mirrorInfo.isCompress or mirrorInfo.isDecompress)):
- currVars = {MirrorInfo.updatableVars[0]: self.botHelper.getHelper.folderSize(mirrorInfo.path)}
+ currVars = {MirrorInfo.UpdatableVars[0]: self.botHelper.getHelper.folderSize(mirrorInfo.path)}
self.botHelper.mirrorHelper.mirrorInfos[mirrorInfo.uid].updateVars(currVars)
uploadPath = os.path.join(mirrorInfo.path, os.listdir(mirrorInfo.path)[0])
if os.path.isdir(uploadPath):
@@ -1637,35 +2051,31 @@ def addUpload(self, mirrorInfo: 'MirrorInfo') -> None:
time.sleep(self.botHelper.statusHelper.statusUpdateInterval)
self.botHelper.listenerHelper.updateStatus(mirrorInfo.uid, MirrorStatus.uploadComplete)
- def cancelUpload(self, uid: str) -> None:
+ def cancelUpload(self, mirrorInfo: 'MirrorInfo') -> None:
raise NotImplementedError
def authorizeApi(self) -> None:
if self.botHelper.configHelper.configVars[self.botHelper.configHelper.reqVars[4]]['authType'] == self.authTypes[0]:
- self.buildService()
+ pass
if self.botHelper.configHelper.configVars[self.botHelper.configHelper.reqVars[4]]['authType'] == self.authTypes[1]:
if not self.oauthCreds.valid:
if self.oauthCreds.expired and self.oauthCreds.refresh_token:
self.oauthCreds.refresh(google.auth.transport.requests.Request())
self.logger.info('Google Drive API Token Refreshed !')
self.botHelper.configHelper.configVars[self.botHelper.configHelper.reqVars[4]]['authInfos'][self.authInfos[1]] = json.loads(self.oauthCreds.to_json())
- self.buildService()
self.botHelper.configHelper.updateConfigJson()
else:
self.logger.info('Google Drive API User Token Needs to Refreshed Manually ! Exiting...')
exit(1)
- else:
- self.buildService()
- def buildService(self) -> None:
- self.service = googleapiclient.discovery.build(serviceName='drive', version='v3', credentials=self.oauthCreds,
- cache_discovery=False)
+ def buildService(self) -> typing.Any:
+ return googleapiclient.discovery.build(serviceName='drive', version='v3', credentials=self.oauthCreds, cache_discovery=False)
def uploadFile(self, filePath: str, parentFolderId: str, uid: str) -> str:
upStatus: googleapiclient.http.MediaUploadProgress
fileName, fileMimeType, fileMetadata, mediaBody = self.getUpData(filePath, isResumable=True)
fileMetadata['parents'] = [parentFolderId]
- fileOp = self.service.files().create(supportsAllDrives=True, body=fileMetadata, media_body=mediaBody)
+ fileOp = self.buildService().files().create(supportsAllDrives=True, body=fileMetadata, media_body=mediaBody)
upResponse = None
while not upResponse:
upStatus, upResponse = fileOp.next_chunk()
@@ -1688,7 +2098,7 @@ def uploadFolder(self, folderPath: str, parentFolderId: str, uid: str) -> str:
def cloneFile(self, sourceFileId: str, parentFolderId: str, uid: str) -> str:
fileMetadata = {'parents': [parentFolderId]}
- fileOp = self.service.files().copy(supportsAllDrives=True, fileId=sourceFileId, body=fileMetadata).execute()
+ fileOp = self.buildService().files().copy(supportsAllDrives=True, fileId=sourceFileId, body=fileMetadata).execute()
self.updateProgress(self.getSizeById(sourceFileId), uid)
return fileOp['id']
@@ -1709,8 +2119,8 @@ def downloadFile(self, sourceFileId: str, dlPath: str, uid: str) -> None:
filePath = os.path.join(dlPath, fileName)
downStatus: googleapiclient.http.MediaDownloadProgress
fileOp = googleapiclient.http.MediaIoBaseDownload(fd=open(filePath, 'wb'), chunksize=self.chunkSize,
- request=self.service.files().get_media(fileId=sourceFileId,
- supportsAllDrives=True))
+ request=self.buildService().files().get_media(fileId=sourceFileId,
+ supportsAllDrives=True))
downResponse = None
while not downResponse:
downStatus, downResponse = fileOp.next_chunk()
@@ -1733,13 +2143,13 @@ def downloadFolder(self, sourceFolderId: str, dlPath: str, uid: str) -> None:
def createFolder(self, folderName: str, parentFolderId: str) -> str:
folderMetadata = {'name': folderName, 'parents': [parentFolderId], 'mimeType': self.googleDriveFolderMimeType}
- folderOp = self.service.files().create(supportsAllDrives=True, body=folderMetadata).execute()
+ folderOp = self.buildService().files().create(supportsAllDrives=True, body=folderMetadata).execute()
return folderOp['id']
def deleteByUrl(self, url: str) -> str:
contentId = self.getIdFromUrl(url)
if contentId != '':
- self.service.files().delete(fileId=contentId, supportsAllDrives=True).execute()
+ self.buildService().files().delete(fileId=contentId, supportsAllDrives=True).execute()
return f'Deleted: [{url}]'
return 'Not a Valid Google Drive Link !'
@@ -1752,7 +2162,11 @@ def getIdFromUrl(url: str) -> str:
def getUpData(self, filePath: str, isResumable: bool) -> (str, str, typing.Dict, googleapiclient.http.MediaIoBaseUpload):
fileName = filePath.split('/')[-1]
fileMimeType = magic.Magic(mime=True).from_file(filePath)
- fileMetadata = {'name': fileName, 'mimeType': fileMimeType}
+ fileMetadata = \
+ {
+ 'name': fileName,
+ 'mimeType': fileMimeType
+ }
if isResumable:
mediaBody = googleapiclient.http.MediaIoBaseUpload(fd=open(filePath, 'rb'), mimetype=fileMimeType,
resumable=True, chunksize=self.chunkSize)
@@ -1762,16 +2176,16 @@ def getUpData(self, filePath: str, isResumable: bool) -> (str, str, typing.Dict,
return fileName, fileMimeType, fileMetadata, mediaBody
def getMetadataById(self, sourceId: str, field: str) -> str:
- return self.service.files().get(supportsAllDrives=True, fileId=sourceId, fields=field).execute().get(field)
+ return self.buildService().files().get(supportsAllDrives=True, fileId=sourceId, fields=field).execute().get(field)
def getFolderContentsById(self, folderId: str) -> typing.List:
query = f"'{folderId}' in parents"
pageToken = None
folderContents: typing.List = []
while True:
- result = self.service.files().list(supportsAllDrives=True, includeTeamDriveItems=True, spaces='drive',
- fields='nextPageToken, files(name, id, mimeType, size)',
- q=query, pageSize=200, pageToken=pageToken).execute()
+ result = self.buildService().files().list(supportsAllDrives=True, includeTeamDriveItems=True, spaces='drive',
+ fields='nextPageToken, files(name, id, mimeType, size)',
+ q=query, pageSize=200, pageToken=pageToken).execute()
for content in result.get('files', []):
folderContents.append(content)
pageToken = result.get('nextPageToken', None)
@@ -1795,7 +2209,7 @@ def getSizeById(self, sourceId: str) -> int:
def patchFile(self, filePath: str, fileId: str) -> str:
fileName, fileMimeType, fileMetadata, mediaBody = self.getUpData(filePath, isResumable=False)
- fileOp = self.service.files().update(fileId=fileId, body=fileMetadata, media_body=mediaBody).execute()
+ fileOp = self.buildService().files().update(fileId=fileId, body=fileMetadata, media_body=mediaBody).execute()
return f"Patched: [{fileOp['id']}] [{fileName}] [{os.path.getsize(fileName)} bytes]"
def updateProgress(self, sizeUpdate: int, uid: str):
@@ -1806,9 +2220,9 @@ def updateProgress(self, sizeUpdate: int, uid: str):
timeCurrent = time.time()
timeDiff = timeCurrent - timeLast
speedCurrent = (int(sizeUpdate / timeDiff) if timeDiff else speedLast)
- self.botHelper.mirrorHelper.mirrorInfos[uid].updateVars({MirrorInfo.updatableVars[1]: sizeCurrent,
- MirrorInfo.updatableVars[2]: speedCurrent,
- MirrorInfo.updatableVars[3]: timeCurrent})
+ self.botHelper.mirrorHelper.mirrorInfos[uid].updateVars({MirrorInfo.UpdatableVars[1]: sizeCurrent,
+ MirrorInfo.UpdatableVars[2]: speedCurrent,
+ MirrorInfo.UpdatableVars[3]: timeCurrent})
class MegaHelper(BaseHelper):
@@ -1837,16 +2251,16 @@ def addDownload(self, mirrorInfo: 'MirrorInfo') -> None:
self.dlNodes[mirrorInfo.uid] = self.apiWrapper.getFolderNode(mirrorInfo.downloadUrl)
if 'file' in mirrorInfo.downloadUrl:
self.dlNodes[mirrorInfo.uid] = self.apiWrapper.getFileNode(mirrorInfo.downloadUrl)
- self.botHelper.mirrorHelper.mirrorInfos[mirrorInfo.uid].updateVars({MirrorInfo.updatableVars[0]: self.dlNodes[mirrorInfo.uid].getSize()})
+ self.botHelper.mirrorHelper.mirrorInfos[mirrorInfo.uid].updateVars({MirrorInfo.UpdatableVars[0]: int(self.dlNodes[mirrorInfo.uid].getSize())})
self.apiWrapper.downloadNode(self.dlNodes[mirrorInfo.uid], mirrorInfo.path)
- def cancelDownload(self, uid: str) -> None:
+ def cancelDownload(self, mirrorInfo: 'MirrorInfo') -> None:
raise NotImplementedError
def addUpload(self, mirrorInfo: 'MirrorInfo') -> None:
raise NotImplementedError
- def cancelUpload(self, uid: str) -> None:
+ def cancelUpload(self, mirrorInfo: 'MirrorInfo') -> None:
raise NotImplementedError
def getUid(self, nodeName: str) -> str:
@@ -1855,6 +2269,129 @@ def getUid(self, nodeName: str) -> str:
return uid
+class QbitTorrentHelper(BaseHelper):
+ def __init__(self, botHelper: BotHelper):
+ super().__init__(botHelper)
+
+ def initHelper(self) -> None:
+ super().initHelper()
+ self.webApiPort = 8400
+ self.apiClient = qbittorrentapi.Client(host='http://localhost', port=self.webApiPort, username='admin', password='adminadmin')
+ self.confFile = 'qBittorrent.conf'
+ self.confFileDir = 'qBittorrent/config'
+ self.confDefaults: typing.Dict[str, str] = \
+ {}
+ self.daemonPid: int = 0
+ self.daemonStartCmd: typing.List[str] = \
+ [
+ 'qbittorrent-nox',
+ f'--profile={os.getcwd()}'
+ ]
+ self.torrentHashes: typing.Dict[str, str] = {}
+
+ def addDownload(self, mirrorInfo: 'MirrorInfo') -> None:
+ self.apiClient.torrents_add(urls=[mirrorInfo.downloadUrl], save_path=mirrorInfo.path)
+ self.torrentHashes[mirrorInfo.uid] = self.botHelper.getHelper.hashFromMagnet(mirrorInfo.downloadUrl)
+
+ def cancelDownload(self, mirrorInfo: 'MirrorInfo') -> None:
+ self.pauseAndRemoveTorrent(mirrorInfo.uid)
+
+ def daemonStart(self) -> None:
+ if self.botHelper.restartVars and self.botHelper.restartVars['qbitDaemonPid']:
+ self.daemonPid = self.botHelper.restartVars['qbitDaemonPid']
+ self.logger.info(f'qbitDaemon Already Running (pid {self.daemonPid}) !')
+ if not self.daemonPid:
+ self.daemonPid = self.botHelper.subprocessHelper.procInit(self.daemonStartCmd).pid
+ self.logger.info(f'qbitDaemon Started (pid {self.daemonPid}) !')
+
+ # TODO: implement this method
+ def daemonCheck(self) -> None:
+ pass
+
+ def daemonStop(self) -> None:
+ self.botHelper.subprocessHelper.procTerm(self.daemonPid)
+ self.logger.info(f'qbitDaemon Terminated (pid {self.daemonPid}) !')
+
+ def makeConf(self) -> None:
+ if os.path.exists(self.confFileDir.split('/')[0]):
+ shutil.rmtree(self.confFileDir.split('/')[0])
+ os.mkdir(self.confFileDir.split('/')[0])
+ os.mkdir(self.confFileDir)
+ confStr = ''
+ lvlZeroData: typing.Dict = \
+ {
+ **self.confDefaults,
+ **self.botHelper.configHelper.configVars['qbitTorrentConf']
+ }
+ for lvlOneKey in lvlZeroData.keys():
+ lvlOneData: typing.Dict = lvlZeroData[lvlOneKey]
+ confStr += f'[{lvlOneKey}]\n'
+ for lvlTwoKey in lvlOneData.keys():
+ lvlTwoData: typing.Dict = lvlOneData[lvlTwoKey]
+ for lvlThreeKey in lvlTwoData.keys():
+ lvlThreeData: str = lvlTwoData[lvlThreeKey]
+ confStr += f'{lvlTwoKey}\\{lvlThreeKey}={lvlThreeData}\n'
+ open(f'{self.confFileDir}/{self.confFile}', 'wt').write(confStr.replace('\n\\', '\n'))
+
+ def removeAllDownloads(self) -> None:
+ self.apiClient.torrents_delete(torrent_hashes='all', delete_files=True)
+
+ def setTrackersList(self) -> None:
+ self.apiClient.app.setPreferences({'add_trackers': open(self.botHelper.ariaHelper.trackersListFile, 'rt').read()})
+
+ def authorizeApi(self) -> None:
+ self.apiClient.auth_log_in()
+
+ def unauthorizeApi(self) -> None:
+ self.apiClient.auth_log_out()
+
+ def updateProgress(self, uid: str) -> None:
+ torrentInfo = self.getTorrentInfos([self.torrentHashes[uid]])[0]
+ self.logger.debug(torrentInfo)
+ currVars: typing.Dict[str, typing.Union[int, float, str]] = \
+ {
+ MirrorInfo.UpdatableVars[0]: int(torrentInfo.size),
+ MirrorInfo.UpdatableVars[1]: int(torrentInfo.downloaded),
+ MirrorInfo.UpdatableVars[2]: int(torrentInfo.dlspeed),
+ MirrorInfo.UpdatableVars[3]: time.time(),
+ MirrorInfo.UpdatableVars[4]: True,
+ MirrorInfo.UpdatableVars[5]: int(torrentInfo.num_seeds),
+ MirrorInfo.UpdatableVars[6]: int(torrentInfo.num_leechs)
+ }
+ self.botHelper.mirrorHelper.mirrorInfos[uid].updateVars(currVars)
+ self.checkState(torrentInfo)
+
+ def checkState(self, torrentInfo: typing.Any) -> None:
+ torrentState = qbittorrentapi.TorrentStates(torrentInfo.state)
+ if torrentState in [
+ qbittorrentapi.TorrentStates.DOWNLOADING
+ ]:
+ return
+ mirrorInfo = self.botHelper.mirrorHelper.mirrorInfos[self.getUid(torrentInfo.hash)]
+ if torrentState in [
+ qbittorrentapi.TorrentStates.QUEUED_UPLOAD,
+ qbittorrentapi.TorrentStates.STALLED_UPLOAD,
+ qbittorrentapi.TorrentStates.FORCED_UPLOAD,
+ qbittorrentapi.TorrentStates.PAUSED_UPLOAD,
+ qbittorrentapi.TorrentStates.UPLOADING
+ ]:
+ self.pauseAndRemoveTorrent(mirrorInfo.uid)
+ self.botHelper.listenerHelper.updateStatus(mirrorInfo.uid, MirrorStatus.downloadComplete)
+
+ def getTorrentInfos(self, torrentHashes: typing.List[str]) -> typing.List[typing.Any]:
+ return self.apiClient.torrents_info(torrent_hashes=torrentHashes)
+
+ def getUid(self, torrentHash: str) -> str:
+ for uid in self.torrentHashes.keys():
+ if torrentHash == self.torrentHashes[uid]:
+ return uid
+
+ def pauseAndRemoveTorrent(self, uid: str) -> None:
+ self.apiClient.torrents_pause(torrent_hashes=self.torrentHashes[uid])
+ self.apiClient.torrents_delete(torrent_hashes=self.torrentHashes[uid])
+ self.torrentHashes.pop(uid)
+
+
class TelegramHelper(BaseHelper):
def __init__(self, botHelper: BotHelper):
super().__init__(botHelper)
@@ -1863,10 +2400,14 @@ def initHelper(self) -> None:
super().initHelper()
self.apiServerPid: int = 0
self.apiServerStartCmd: typing.List[str] = \
- ['telegram-bot-api', '--local', '--verbosity=9',
- f'--api-id={self.botHelper.configHelper.configVars[self.botHelper.configHelper.reqVars[2]]}',
- f'--api-hash={self.botHelper.configHelper.configVars[self.botHelper.configHelper.reqVars[3]]}',
- f'--log={os.path.join(self.botHelper.envVars["currWorkDir"], self.botHelper.loggingHelper.logFiles[1])}']
+ [
+ 'telegram-bot-api',
+ '--local',
+ '--verbosity=9',
+ f'--api-id={self.botHelper.configHelper.configVars[self.botHelper.configHelper.reqVars[2]]}',
+ f'--api-hash={self.botHelper.configHelper.configVars[self.botHelper.configHelper.reqVars[3]]}',
+ f'--log={os.path.join(self.botHelper.envVars["currWorkDir"], self.botHelper.loggingHelper.logFiles[1])}'
+ ]
self.uploadMaxSize: int = 2 * 1024 * 1024 * 1024
self.maxTimeout: int = 24 * 60 * 60
@@ -1875,7 +2416,7 @@ def apiServerStart(self) -> None:
self.apiServerPid = self.botHelper.restartVars['botApiServerPid']
self.logger.info(f'botApiServer Already Running (pid {self.apiServerPid}) !')
if not self.apiServerPid:
- self.apiServerPid = subprocess.Popen(self.apiServerStartCmd).pid
+ self.apiServerPid = self.botHelper.subprocessHelper.procInit(self.apiServerStartCmd).pid
self.logger.info(f'botApiServer Started (pid {self.apiServerPid}) !')
def apiServerCheck(self) -> None:
@@ -1889,23 +2430,23 @@ def apiServerCheck(self) -> None:
continue
def apiServerStop(self) -> None:
- os.kill(self.apiServerPid, signal.SIGTERM)
- self.logger.info(f"botApiServer terminated (pid {self.apiServerPid})")
+ self.botHelper.subprocessHelper.procTerm(self.apiServerPid)
+ self.logger.info(f'botApiServer Terminated (pid {self.apiServerPid}) !')
def addDownload(self, mirrorInfo: 'MirrorInfo') -> None:
replyTo = mirrorInfo.msg.reply_to_message
for media in [replyTo.document, replyTo.audio, replyTo.video]:
if media:
- self.botHelper.mirrorHelper.mirrorInfos[mirrorInfo.uid].updateVars({mirrorInfo.updatableVars[0]: media.file_size})
+ self.botHelper.mirrorHelper.mirrorInfos[mirrorInfo.uid].updateVars({mirrorInfo.UpdatableVars[0]: media.file_size})
self.downloadMedia(media, mirrorInfo.path)
break
self.botHelper.listenerHelper.updateStatus(mirrorInfo.uid, MirrorStatus.downloadComplete)
- def cancelDownload(self, uid: str) -> None:
+ def cancelDownload(self, mirrorInfo: 'MirrorInfo') -> None:
raise NotImplementedError
def addUpload(self, mirrorInfo: 'MirrorInfo') -> None:
- currVars = {MirrorInfo.updatableVars[0]: self.botHelper.getHelper.folderSize(mirrorInfo.path)}
+ currVars = {MirrorInfo.UpdatableVars[0]: self.botHelper.getHelper.folderSize(mirrorInfo.path)}
self.botHelper.mirrorHelper.mirrorInfos[mirrorInfo.uid].updateVars(currVars)
uploadPath = os.path.join(mirrorInfo.path, os.listdir(mirrorInfo.path)[0])
upResponse: bool = True
@@ -1922,7 +2463,7 @@ def addUpload(self, mirrorInfo: 'MirrorInfo') -> None:
if not upResponse:
self.botHelper.listenerHelper.updateStatus(mirrorInfo.uid, MirrorStatus.cancelMirror)
- def cancelUpload(self, uid: str) -> None:
+ def cancelUpload(self, mirrorInfo: 'MirrorInfo') -> None:
raise NotImplementedError
def downloadMedia(self, media: typing.Union[telegram.Document, telegram.Audio, telegram.Video], mirrorInfoPath: str) -> None:
@@ -1964,11 +2505,18 @@ def initHelper(self) -> None:
super().initHelper()
def addDownload(self, mirrorInfo: 'MirrorInfo') -> None:
- ytdlOpts: dict = {'quiet': True, 'format': mirrorInfo.ytdlFormat, 'progress_hooks': [self.progressHook],
- 'outtmpl': f'{mirrorInfo.path}/%(title)s-%(id)s.f%(format_id)s.%(ext)s'}
+ ytdlOpts: typing.Dict = \
+ {
+ 'quiet': True,
+ 'format': mirrorInfo.ytdlFormat,
+ 'progress_hooks': [
+ self.progressHook
+ ],
+ 'outtmpl': f'{mirrorInfo.path}/%(title)s-%(id)s.f%(format_id)s.%(ext)s'
+ }
self.downloadVideo(mirrorInfo.downloadUrl, ytdlOpts)
- def cancelDownload(self, uid: str) -> None:
+ def cancelDownload(self, mirrorInfo: 'MirrorInfo') -> None:
raise NotImplementedError
@staticmethod
@@ -1980,10 +2528,12 @@ def progressHook(self, progressUpdate: dict):
uid = progressUpdate['filename'].replace(self.botHelper.envVars['dlRootDirPath'], '').split('/')[1]
if progressUpdate['status'] == 'downloading':
currVars: typing.Dict[str, typing.Union[int, float, str]] = \
- {MirrorInfo.updatableVars[0]: int((sizeTotal if (sizeTotal := progressUpdate['total_bytes']) else 0)),
- MirrorInfo.updatableVars[1]: int((sizeCurrent if (sizeCurrent := progressUpdate['downloaded_bytes']) else 0)),
- MirrorInfo.updatableVars[2]: int((speedCurrent if (speedCurrent := progressUpdate['speed']) else 0)),
- MirrorInfo.updatableVars[3]: time.time()}
+ {
+ MirrorInfo.UpdatableVars[0]: int((sizeTotal if (sizeTotal := progressUpdate['total_bytes']) else 0)),
+ MirrorInfo.UpdatableVars[1]: int((sizeCurrent if (sizeCurrent := progressUpdate['downloaded_bytes']) else 0)),
+ MirrorInfo.UpdatableVars[2]: int((speedCurrent if (speedCurrent := progressUpdate['speed']) else 0)),
+ MirrorInfo.UpdatableVars[3]: time.time()
+ }
self.botHelper.mirrorHelper.mirrorInfos[uid].updateVars(currVars)
if progressUpdate['status'] == 'finished':
self.botHelper.listenerHelper.updateStatus(uid, MirrorStatus.downloadComplete)
@@ -2147,11 +2697,13 @@ def onTransferStart(self, api: mega.MegaApi, transfer: mega.MegaTransfer):
def onTransferUpdate(self, api: mega.MegaApi, transfer: mega.MegaTransfer):
if transfer.getFileName() in [dlNode.getName() for dlNode in list(self.megaHelper.dlNodes.values())]:
uid = self.megaHelper.getUid(transfer.getFileName())
- currVars: typing.Dict[str, typing.Union[int, float, str]] = \
- {MirrorInfo.updatableVars[0]: transfer.getTotalBytes(),
- MirrorInfo.updatableVars[1]: transfer.getTransferredBytes(),
- MirrorInfo.updatableVars[2]: transfer.getSpeed(),
- MirrorInfo.updatableVars[3]: time.time()}
+ currVars: typing.Dict[str, typing.Union[int, float]] = \
+ {
+ MirrorInfo.UpdatableVars[0]: int(transfer.getTotalBytes()),
+ MirrorInfo.UpdatableVars[1]: int(transfer.getTransferredBytes()),
+ MirrorInfo.UpdatableVars[2]: int(transfer.getSpeed()),
+ MirrorInfo.UpdatableVars[3]: time.time()
+ }
self.megaHelper.botHelper.mirrorHelper.mirrorInfos[uid].updateVars(currVars)
self.logger.debug(f'Transfer Update ({transfer} {transfer.getFileName()}); '
f'Progress: {transfer.getTransferredBytes() / 1024} KB of {transfer.getTotalBytes() / 1024} KB, '
@@ -2171,8 +2723,16 @@ def onNodesUpdate(self, api: mega.MegaApi, nodes: mega.MegaNodeList):
class MirrorInfo:
- updatableVars: typing.List[str] = ['sizeTotal', 'sizeCurrent', 'speedCurrent', 'timeCurrent',
- 'isTorrent', 'numSeeders', 'numLeechers']
+ UpdatableVars: typing.List[str] = \
+ [
+ 'sizeTotal',
+ 'sizeCurrent',
+ 'speedCurrent',
+ 'timeCurrent',
+ 'isTorrent',
+ 'numSeeders',
+ 'numLeechers'
+ ]
def __init__(self, msg: telegram.Message, botHelper: BotHelper):
self.msg = msg
@@ -2180,10 +2740,11 @@ def __init__(self, msg: telegram.Message, botHelper: BotHelper):
self.chatId = msg.chat.id
self.uid: str = botHelper.getHelper.randomString(8)
self.path: str = os.path.join(botHelper.envVars['dlRootDirPath'], self.uid)
- self.status: str = ''
+ self.currentStatus: str = ''
+ self.previousStatus: str = ''
self.tag: str = ''
self.downloadUrl: str = ''
- self.ytdlFormat: str = botHelper.configHelper.configVars[botHelper.configHelper.optVars[7]]
+ self.ytdlFormat: str = botHelper.configHelper.configVars[botHelper.configHelper.optVars[8]]
self.sizeTotal: int = 0
self.sizeCurrent: int = 0
self.timeStart: float = 0.0
@@ -2199,6 +2760,7 @@ def __init__(self, msg: telegram.Message, botHelper: BotHelper):
self.isAriaDownload: bool = False
self.isGoogleDriveDownload: bool = False
self.isMegaDownload: bool = False
+ self.isQbitTorrentDownload: bool = False
self.isTelegramDownload: bool = False
self.isYouTubeDownload: bool = False
self.isGoogleDriveUpload: bool = False
@@ -2213,22 +2775,28 @@ def resetVars(self):
self.speedCurrent = 0
self.progressPercent = 0.0
+ def updateStatus(self, mirrorStatus: str) -> None:
+ if self.currentStatus == mirrorStatus:
+ return
+ self.previousStatus = self.currentStatus
+ self.currentStatus = mirrorStatus
+
def updateVars(self, currVars: typing.Dict[str, typing.Union[int, float, str]]) -> None:
currVarsKeys = list(currVars.keys())
- if self.updatableVars[0] in currVarsKeys:
- self.sizeTotal = currVars[self.updatableVars[0]]
- if self.updatableVars[1] in currVarsKeys and self.updatableVars[2] in currVarsKeys:
- self.sizeCurrent = currVars[self.updatableVars[1]]
- self.speedCurrent = currVars[self.updatableVars[2]]
- self.timeCurrent = currVars[self.updatableVars[3]]
+ if self.UpdatableVars[0] in currVarsKeys:
+ self.sizeTotal = currVars[self.UpdatableVars[0]]
+ if self.UpdatableVars[1] in currVarsKeys and self.UpdatableVars[2] in currVarsKeys:
+ self.sizeCurrent = currVars[self.UpdatableVars[1]]
+ self.speedCurrent = currVars[self.UpdatableVars[2]]
+ self.timeCurrent = currVars[self.UpdatableVars[3]]
if self.sizeTotal != 0:
self.progressPercent = round(((self.sizeCurrent / self.sizeTotal) * 100), ndigits=2)
if self.speedCurrent != 0:
self.timeEnd = self.timeCurrent + ((self.sizeTotal - self.sizeCurrent) / self.speedCurrent)
- if self.updatableVars[4] in currVarsKeys:
+ if self.UpdatableVars[4] in currVarsKeys:
self.isTorrent = True
- self.numSeeders = currVars[self.updatableVars[5]]
- self.numLeechers = currVars[self.updatableVars[6]]
+ self.numSeeders = currVars[self.UpdatableVars[5]]
+ self.numLeechers = currVars[self.UpdatableVars[6]]
class MirrorStatus:
@@ -2284,14 +2852,14 @@ def __init__(self, botHelper: BotHelper):
def serveForever(self, forceEventLoop: bool = False, ready: threading.Event = None) -> None:
with self.serverLock:
self.isRunning = True
- self.logger.debug('Webhook Server started.')
+ self.logger.debug('Webhook Server Started.')
self.ensureEventLoop(forceEventLoop=forceEventLoop)
self.loop = tornado.ioloop.IOLoop.current()
self.httpServer.listen(self.listenPort, address=self.listenAddress)
if ready is not None:
ready.set()
self.loop.start()
- self.logger.debug('Webhook Server stopped.')
+ self.logger.debug('Webhook Server Stopped.')
self.isRunning = False
def shutdown(self) -> None: