Added Qbittorrent

use /mirror qb to mirror with qbitorrent
and use /mirror qbs to select files before downloading

Signed-off-by: anas <e.anastayyar@gmail.com>
This commit is contained in:
anas 2021-08-06 22:45:48 +03:00
parent 82cb9b2212
commit 0bfba523f0
21 changed files with 874 additions and 45 deletions

View File

@ -1,8 +1,10 @@
FROM breakdowns/mega-sdk-python:latest
FROM anasty17/megasdk:latest
WORKDIR /usr/src/app
RUN chmod 777 /usr/src/app
RUN apt-get install -y xz-utils neofetch unzip && apt-get autoremove -y
COPY requirements.txt .
RUN pip3 install --no-cache-dir -r requirements.txt

11
alive.py Normal file
View File

@ -0,0 +1,11 @@
import time
import requests
import os
from dotenv import load_dotenv
load_dotenv('config.env')
url = os.environ.get("BASE_URL_OF_BOT")
while True:
time.sleep(1000)
status = requests.get(url).status_code

21
aria.sh
View File

@ -1,15 +1,10 @@
export MAX_DOWNLOAD_SPEED=0
tracker_list=$(curl -Ns https://raw.githubusercontent.com/XIU2/TrackersListCollection/master/all.txt https://ngosang.github.io/trackerslist/trackers_all_http.txt https://raw.githubusercontent.com/DeSireFire/animeTrackerList/master/AT_all.txt https://raw.githubusercontent.com/hezhijie0327/Trackerslist/main/trackerslist_combine.txt | awk '$0' | tr '\n' ',')
export MAX_CONCURRENT_DOWNLOADS=7
aria2c --enable-rpc --rpc-listen-all=false --check-certificate=false \
aria2c --enable-rpc --check-certificate=false \
--max-connection-per-server=10 --rpc-max-request-size=1024M \
--bt-tracker="[$tracker_list]" --bt-max-peers=0 --bt-tracker-connect-timeout=300 --bt-stop-timeout=1200 --min-split-size=10M \
--follow-torrent=mem --split=10 \
--daemon=true --allow-overwrite=true --max-overall-download-limit=$MAX_DOWNLOAD_SPEED \
--max-overall-upload-limit=1K --max-concurrent-downloads=$MAX_CONCURRENT_DOWNLOADS \
--bt-stop-timeout=1200 --min-split-size=10M --follow-torrent=mem --split=10 \
--daemon=true --allow-overwrite=true --max-overall-download-limit=0 \
--max-overall-upload-limit=1K --max-concurrent-downloads=15 --continue=true \
--peer-id-prefix=-qB4360- --user-agent=qBittorrent/4.3.6 --peer-agent=qBittorrent/4.3.6 \
--disk-cache=64M --file-allocation=prealloc --continue=true \
--max-file-not-found=0 --max-tries=20 --auto-file-renaming=true \
--bt-enable-lpd=true --seed-time=0.01 --seed-ratio=1.0 \
--content-disposition-default-utf8=true --http-accept-gzip=true --reuse-uri=true --netrc-path=/usr/src/app/.netrc
--disk-cache=64M --bt-enable-lpd=true --seed-time=0 --max-file-not-found=0 \
--max-tries=20 --auto-file-renaming=true --reuse-uri=true --http-accept-gzip=true \
--content-disposition-default-utf8=true --netrc-path=/usr/src/app/.netrc

View File

@ -6,6 +6,7 @@ import random
import string
import aria2p
import qbittorrentapi as qba
import telegram.ext as tg
from dotenv import load_dotenv
from pyrogram import Client
@ -66,6 +67,18 @@ aria2 = aria2p.API(
)
)
def get_client() -> qba.TorrentsAPIMixIn:
qb_client = qba.Client(host="localhost", port=8090, username="admin", password="adminadmin")
try:
qb_client.auth_log_in()
qb_client.application.set_preferences({"disk_cache":64, "incomplete_files_ext":True, "max_connec":3000, "max_connec_per_torrent":300, "async_io_thread":32})
return qb_client
except qba.LoginFailed as e:
LOGGER.error(str(e))
return None
DOWNLOAD_DIR = None
BOT_TOKEN = None
@ -313,6 +326,31 @@ try:
except KeyError:
pass
try:
BASE_URL = getConfig('BASE_URL_OF_BOT')
if len(BASE_URL) == 0:
BASE_URL = None
except KeyError:
logging.warning('BASE_URL_OF_BOT not provided!')
BASE_URL = None
try:
IS_VPS = getConfig('IS_VPS')
if IS_VPS.lower() == 'true':
IS_VPS = True
else:
IS_VPS = False
except KeyError:
IS_VPS = False
try:
SERVER_PORT = getConfig('SERVER_PORT')
if len(SERVER_PORT) == 0:
SERVER_PORT = None
except KeyError:
logging.warning('SERVER_PORT not provided!')
SERVER_PORT = None
updater = tg.Updater(token=BOT_TOKEN)
bot = updater.bot
dispatcher = updater.dispatcher

View File

@ -1,6 +1,7 @@
import shutil, psutil
import signal
import os
import asyncio
from pyrogram import idle
from bot import app
@ -8,7 +9,8 @@ from sys import executable
from telegram import ParseMode
from telegram.ext import CommandHandler
from bot import bot, dispatcher, updater, botStartTime, IGNORE_PENDING_REQUESTS
from wserver import start_server_async
from bot import bot, dispatcher, updater, botStartTime, IGNORE_PENDING_REQUESTS, IS_VPS, SERVER_PORT
from bot.helper.ext_utils import fs_utils
from bot.helper.telegram_helper.bot_commands import BotCommands
from bot.helper.telegram_helper.message_utils import *
@ -201,6 +203,10 @@ botcmds = [
def main():
fs_utils.start_cleanup()
if IS_VPS:
asyncio.get_event_loop().run_until_complete(start_server_async(SERVER_PORT))
# Check if the bot is restarting
if os.path.isfile(".restartmsg"):
with open(".restartmsg") as f:

View File

@ -26,6 +26,7 @@ class MirrorStatus:
STATUS_CLONING = "Cloning...♻️"
STATUS_WAITING = "Queued...📝"
STATUS_FAILED = "Failed 🚫. Cleaning Download..."
STATUS_PAUSE = "Paused...⭕️"
STATUS_ARCHIVING = "Archiving...🔐"
STATUS_EXTRACTING = "Extracting...📂"
@ -76,6 +77,7 @@ def getDownloadByGid(gid):
return dl
return None
def getAllDownload():
with download_dict_lock:
for dlDetails in list(download_dict.values()):
@ -84,6 +86,7 @@ def getAllDownload():
return dlDetails
return None
def get_progress_bar_string(status):
completed = status.processed_bytes() / 8
total = status.size_raw() / 8
@ -134,6 +137,11 @@ def get_readable_message():
f" | <b>Peers:</b> {download.aria_download().connections}"
except:
pass
try:
msg += f"\n<b>Seeders:</b> {download.torrent_info().num_seeds}" \
f" | <b>Leechers:</b> {download.torrent_info().num_leechs}"
except:
pass
msg += f"\n<b>To Stop:</b> <code>/{BotCommands.CancelMirror} {download.gid()}</code>"
msg += "\n\n"
if STATUS_LIMIT is not None:
@ -151,6 +159,7 @@ def get_readable_message():
return msg, button
return msg, ""
def flip(update, context):
query = update.callback_query
query.answer()
@ -171,6 +180,7 @@ def flip(update, context):
PAGE_NO -= 1
message_utils.update_all_messages()
def get_readable_time(seconds: int) -> str:
result = ''
(days, remainder) = divmod(seconds, 86400)
@ -196,12 +206,15 @@ def is_url(url: str):
return True
return False
def is_gdrive_link(url: str):
return "drive.google.com" in url
def is_mega_link(url: str):
return "mega.nz" in url or "mega.co.nz" in url
def get_mega_link_type(url: str):
if "folder" in url:
return "folder"
@ -211,12 +224,14 @@ def get_mega_link_type(url: str):
return "folder"
return "file"
def is_magnet(url: str):
magnet = re.findall(MAGNET_REGEX, url)
if magnet:
return True
return False
def new_thread(fn):
"""To use as decorator to make a function call threaded.
Needs import
@ -229,6 +244,7 @@ def new_thread(fn):
return wrapper
next_handler = CallbackQueryHandler(flip, pattern="nex", run_async=True)
previous_handler = CallbackQueryHandler(flip, pattern="pre", run_async=True)
dispatcher.add_handler(next_handler)

View File

@ -1,5 +1,5 @@
import sys
from bot import aria2, LOGGER, DOWNLOAD_DIR
from bot import aria2, LOGGER, DOWNLOAD_DIR, get_client
import shutil
import os
import pathlib
@ -23,6 +23,7 @@ def start_cleanup():
def clean_all():
aria2.remove_all(True)
get_client().torrents_delete(torrent_hashes="all", delete_files=True)
try:
shutil.rmtree(DOWNLOAD_DIR)
except FileNotFoundError:

View File

@ -177,6 +177,7 @@ class MegaDownloadHelper:
if smsg:
msg1 = "File/Folder is already available in Drive.\nHere are the search results:"
sendMarkup(msg1, listener.bot, listener.update, button)
executor.continue_event.set()
return
if MEGA_LIMIT is not None or TAR_UNZIP_LIMIT is not None:
limit = None
@ -193,10 +194,12 @@ class MegaDownloadHelper:
if 'G' in limit[1] or 'g' in limit[1]:
if api.getSize(node) > limitint * 1024**3:
sendMessage(msg3, listener.bot, listener.update)
executor.continue_event.set()
return
elif 'T' in limit[1] or 't' in limit[1]:
if api.getSize(node) > limitint * 1024**4:
sendMessage(msg3, listener.bot, listener.update)
executor.continue_event.set()
return
with download_dict_lock:
download_dict[listener.uid] = MegaDownloadStatus(mega_listener, listener)

View File

@ -0,0 +1,198 @@
import os
import random
import string
import time
import logging
import qbittorrentapi as qba
from urllib.parse import urlparse, parse_qs
from torrentool.api import Torrent
from telegram import InlineKeyboardMarkup
from telegram.ext import CallbackQueryHandler
from bot import download_dict, download_dict_lock, BASE_URL, dispatcher, get_client
from bot.helper.mirror_utils.status_utils.qbit_download_status import QbDownloadStatus
from bot.helper.telegram_helper.message_utils import *
from bot.helper.ext_utils.bot_utils import setInterval, new_thread, MirrorStatus
from bot.helper.telegram_helper import button_build
LOGGER = logging.getLogger(__name__)
class qbittorrent:
def __init__(self):
self.update_interval = 1.5
self.meta_time = time.time()
@new_thread
def add_torrent(self, link, dire, listener, qbitsel):
self.client = get_client()
self.listener = listener
is_file = False
count = 0
pincode = ""
markup = None
try:
if os.path.exists(link):
is_file = True
self.ext_hash = get_hash_file(link)
else:
self.ext_hash = get_hash_magnet(link)
tor_info = self.client.torrents_info(torrent_hashes=self.ext_hash)
if len(tor_info) > 0:
sendMessage("This torrent is already in list.", listener.bot, listener.update)
return
if is_file:
op = self.client.torrents_add(torrent_files=[link], save_path=dire)
os.remove(link)
else:
op = self.client.torrents_add(link, save_path=dire)
if op.lower() == "ok.":
LOGGER.info(f"QbitDownload started: {self.ext_hash}")
tor_info = self.client.torrents_info(torrent_hashes=self.ext_hash)
if len(tor_info) == 0:
while True:
if time.time() - self.meta_time >= 300:
sendMessage("The torrent was not added. report when u see this error", listener.bot, listener.update)
return False
tor_info = self.client.torrents_info(torrent_hashes=self.ext_hash)
if len(tor_info) > 0:
break
else:
sendMessage("This is an unsupported/invalid link.", listener.bot, listener.update)
return
gid = ''.join(random.SystemRandom().choices(string.ascii_letters + string.digits, k=14))
self.updater = setInterval(self.update_interval, self.update)
tor_info = tor_info[0]
if BASE_URL is not None and qbitsel:
if not is_file and (tor_info.state == "checkingResumeData" or tor_info.state == "metaDL"):
meta = sendMessage("Downloading Metadata...Please wait then you can select files or mirror torrent file if it have low seeders", listener.bot, listener.update)
while True:
tor_info = self.client.torrents_info(torrent_hashes=self.ext_hash)
if len(tor_info) == 0:
deleteMessage(listener.bot, meta)
return False
tor_info = tor_info[0]
if tor_info.state == "metaDL" or tor_info.state == "checkingResumeData":
time.sleep(1)
else:
break
deleteMessage(listener.bot, meta)
for n in str(self.ext_hash):
if n.isdigit():
pincode += str(n)
count += 1
if count == 4:
break
URL = f"{BASE_URL}/slam/files/{self.ext_hash}"
pindata = f"pin {gid} {pincode}"
donedata = f"done {gid} {self.ext_hash}"
buttons = button_build.ButtonMaker()
buttons.buildbutton("Select Files", URL)
buttons.sbutton("Pincode", pindata)
buttons.sbutton("Done Selecting", donedata)
QBBUTTONS = InlineKeyboardMarkup(buttons.build_menu(2))
msg = "Your download paused. Choose files then press Done Selecting button to start downloading."
markup = sendMarkup(msg, listener.bot, listener.update, QBBUTTONS)
self.client.torrents_pause(torrent_hashes=self.ext_hash)
with download_dict_lock:
download_dict[listener.uid] = QbDownloadStatus(gid, listener, self.ext_hash, self.client, markup)
else:
with download_dict_lock:
download_dict[listener.uid] = QbDownloadStatus(gid, listener, self.ext_hash, self.client ,markup)
sendStatusMessage(listener.update, listener.bot)
except qba.UnsupportedMediaType415Error as e:
LOGGER.error(str(e))
sendMessage("This is an unsupported/invalid link. {str(e)}", listener.bot, listener.update)
except Exception as e:
LOGGER.error(str(e))
sendMessage(str(e), listener.bot, listener.update)
self.client.torrents_delete(torrent_hashes=self.ext_hash)
def update(self):
tor_info = self.client.torrents_info(torrent_hashes=self.ext_hash)
if len(tor_info) == 0:
self.updater.cancel()
return
else:
tor_info = tor_info[0]
if tor_info.state == "metaDL":
if time.time() - self.meta_time > 600:
self.client.torrents_delete(torrent_hashes=self.ext_hash)
self.listener.onDownloadError("Dead Torrent!")
self.updater.cancel()
return
elif tor_info.state == "error":
self.client.torrents_delete(torrent_hashes=self.ext_hash)
self.listener.onDownloadError("Error. IDK why, report in support group")
self.updater.cancel()
return
elif tor_info.state == "uploading" or tor_info.state.lower().endswith("up"):
self.client.torrents_pause(torrent_hashes=self.ext_hash)
self.listener.onDownloadComplete()
self.client.torrents_delete(torrent_hashes=self.ext_hash, delete_files=True)
self.updater.cancel()
def get_confirm(update, context):
query = update.callback_query
user_id = query.from_user.id
data = query.data
data = data.split(" ")
qdl = None
with download_dict_lock:
for dl in download_dict.values():
if dl.status() == MirrorStatus.STATUS_PAUSE:
if dl.gid() == data[1]:
qdl = dl
break
if qdl is not None:
if user_id != qdl.listen().message.from_user.id:
query.answer(text="Don't waste your time!", show_alert=True)
return
if data[0] == "pin":
query.answer(text=data[2], show_alert=True)
elif data[0] == "done":
query.answer()
qdl.qbclient().torrents_resume(torrent_hashes=data[2])
sendStatusMessage(qdl.listen().update, qdl.listen().bot)
deleteMessage(context.bot, qdl.mark())
else:
query.answer(text="This task has been cancelled!", show_alert=True)
query.delete_message()
def get_hash_magnet(mgt):
if mgt.startswith('magnet:'):
_, _, _, _, query, _ = urlparse(mgt)
qs = parse_qs(query)
v = qs.get('xt', None)
if v == None or v == []:
LOGGER.error('Invalid magnet URI: no "xt" query parameter.')
return False
v = v[0]
if not v.startswith('urn:btih:'):
LOGGER.error('Invalid magnet URI: "xt" value not valid for BitTorrent.')
return False
mgt = v[len('urn:btih:'):]
return mgt.lower()
def get_hash_file(path):
tr = Torrent.from_file(path)
mgt = tr.magnet_link
return get_hash_magnet(mgt)
pin_handler = CallbackQueryHandler(get_confirm, pattern="pin", run_async=True)
done_handler = CallbackQueryHandler(get_confirm, pattern="done", run_async=True)
dispatcher.add_handler(pin_handler)
dispatcher.add_handler(done_handler)

View File

@ -0,0 +1,94 @@
from bot import DOWNLOAD_DIR, LOGGER, get_client
from bot.helper.ext_utils.bot_utils import MirrorStatus, get_readable_file_size, get_readable_time
from .status import Status
class QbDownloadStatus(Status):
def __init__(self, gid, listener, qbhash, client, markup):
super().__init__()
self.__gid = gid
self.__hash = qbhash
self.__client = client
self.__markup = markup
self.__uid = listener.uid
self.__listener = listener
self.message = listener.message
self.is_extracting = False
self.is_archiving = False
def progress(self):
"""
Calculates the progress of the mirror (upload or download)
:return: returns progress in percentage
"""
return f'{round(self.torrent_info().progress*100,2)}%'
def size_raw(self):
"""
Gets total size of the mirror file/folder
:return: total size of mirror
"""
return self.torrent_info().total_size
def processed_bytes(self):
return self.torrent_info().downloaded
def speed(self):
return f"{get_readable_file_size(self.torrent_info().dlspeed)}/s"
def name(self):
return self.torrent_info().name
def path(self):
return f"{DOWNLOAD_DIR}{self.__uid}"
def size(self):
return get_readable_file_size(self.torrent_info().total_size)
def eta(self):
return get_readable_time(self.torrent_info().eta)
def status(self):
download = self.torrent_info().state
if download == "queuedDL":
status = MirrorStatus.STATUS_WAITING
elif download == "metaDL":
status = MirrorStatus.STATUS_DOWNLOADING + " (Metadata)"
elif download == "pausedDL":
status = MirrorStatus.STATUS_PAUSE
else:
status = MirrorStatus.STATUS_DOWNLOADING
return status
def torrent_info(self):
tor_info = self.__client.torrents_info(torrent_hashes=self.__hash)
if len(tor_info) == 0:
return None
else:
return tor_info[0]
def download(self):
return self
def uid(self):
return self.__uid
def gid(self):
return self.__gid
def listen(self):
return self.__listener
def qbclient(self):
return self.__client
def mark(self):
return self.__markup
def cancel_download(self):
LOGGER.info(f"Cancelling Download: {self.name()}")
self.__listener.onDownloadError('Download stopped by user!')
self.__client.torrents_delete(torrent_hashes=self.__hash)

View File

@ -4,8 +4,8 @@ from telegram.update import Update
import psutil, shutil
import time
from bot import AUTO_DELETE_MESSAGE_DURATION, LOGGER, bot, \
status_reply_dict, status_reply_dict_lock, download_dict, download_dict_lock, botStartTime
from bot.helper.ext_utils.bot_utils import get_readable_message, get_readable_file_size, get_readable_time, MirrorStatus
status_reply_dict, status_reply_dict_lock, download_dict, download_dict_lock, botStartTime, Interval, DOWNLOAD_STATUS_UPDATE_INTERVAL
from bot.helper.ext_utils.bot_utils import get_readable_message, get_readable_file_size, get_readable_time, MirrorStatus, setInterval
from telegram.error import TimedOut, BadRequest
@ -108,6 +108,8 @@ def update_all_messages():
def sendStatusMessage(msg, bot):
if len(Interval) == 0:
Interval.append(setInterval(DOWNLOAD_STATUS_UPDATE_INTERVAL, update_all_messages))
total, used, free = shutil.disk_usage('.')
free = get_readable_file_size(free)
currentTime = get_readable_time(time.time() - botStartTime)

View File

@ -42,9 +42,9 @@ def cancel_mirror(update, context):
elif not mirror_message:
sendMessage(msg, context.bot, update)
return
if dl.status() == "Archiving...🔐":
if dl.status() == MirrorStatus.STATUS_ARCHIVING:
sendMessage("Archival in Progress, You Can't Cancel It.", context.bot, update)
elif dl.status() == "Extracting...📂":
elif dl.status() == MirrorStatus.STATUS_EXTRACTING:
sendMessage("Extract in Progress, You Can't Cancel It.", context.bot, update)
else:
dl.download().cancel_download()
@ -54,16 +54,15 @@ def cancel_mirror(update, context):
def cancel_all(update, context):
count = 0
gid = 1
gid = 0
while True:
dl = getAllDownload()
if dl:
if dl.gid() == gid:
continue
else:
if dl.gid() != gid:
gid = dl.gid()
dl.download().cancel_download()
count += 1
sleep(0.3)
else:
break
sendMessage(f'{count} Download(s) has been Cancelled!', context.bot, update)

View File

@ -4,8 +4,8 @@ from bot.helper.telegram_helper.message_utils import *
from bot.helper.telegram_helper.filters import CustomFilters
from bot.helper.telegram_helper.bot_commands import BotCommands
from bot.helper.mirror_utils.status_utils.clone_status import CloneStatus
from bot import dispatcher, LOGGER, CLONE_LIMIT, STOP_DUPLICATE, download_dict, download_dict_lock, Interval, DOWNLOAD_STATUS_UPDATE_INTERVAL
from bot.helper.ext_utils.bot_utils import get_readable_file_size, setInterval
from bot import dispatcher, LOGGER, CLONE_LIMIT, STOP_DUPLICATE, download_dict, download_dict_lock, Interval
from bot.helper.ext_utils.bot_utils import get_readable_file_size
import random
import string
@ -50,8 +50,6 @@ def cloneNode(update, context):
clone_status = CloneStatus(drive, clonesize, update, gid)
with download_dict_lock:
download_dict[update.message.message_id] = clone_status
if len(Interval) == 0:
Interval.append(setInterval(DOWNLOAD_STATUS_UPDATE_INTERVAL, update_all_messages))
sendStatusMessage(update, context.bot)
result, button = drive.clone(link)
with download_dict_lock:

View File

@ -3,12 +3,13 @@ from telegram.ext import CommandHandler
from telegram import InlineKeyboardMarkup
from bot import Interval, INDEX_URL, BUTTON_FOUR_NAME, BUTTON_FOUR_URL, BUTTON_FIVE_NAME, BUTTON_FIVE_URL, BUTTON_SIX_NAME, BUTTON_SIX_URL, BLOCK_MEGA_FOLDER, BLOCK_MEGA_LINKS, VIEW_LINK, aria2
from bot import dispatcher, DOWNLOAD_DIR, DOWNLOAD_STATUS_UPDATE_INTERVAL, download_dict, download_dict_lock, SHORTENER, SHORTENER_API, TAR_UNZIP_LIMIT
from bot import dispatcher, DOWNLOAD_DIR, download_dict, download_dict_lock, SHORTENER, SHORTENER_API, TAR_UNZIP_LIMIT
from bot.helper.ext_utils import fs_utils, bot_utils
from bot.helper.ext_utils.bot_utils import setInterval, get_mega_link_type
from bot.helper.ext_utils.bot_utils import get_mega_link_type
from bot.helper.ext_utils.exceptions import DirectDownloadLinkException, NotSupportedExtractionArchive
from bot.helper.mirror_utils.download_utils.aria2_download import AriaDownloadHelper
from bot.helper.mirror_utils.download_utils.mega_downloader import MegaDownloadHelper
from bot.helper.mirror_utils.download_utils.qbit_downloader import qbittorrent
from bot.helper.mirror_utils.download_utils.direct_link_generator import direct_link_generator
from bot.helper.mirror_utils.download_utils.telegram_downloader import TelegramDownloadHelper
from bot.helper.mirror_utils.status_utils import listeners
@ -235,8 +236,15 @@ def _mirror(bot, update, isTar=False, extract=False):
mesg = update.message.text.split('\n')
message_args = mesg[0].split(' ')
name_args = mesg[0].split('|')
qbit = False
qbitsel = False
try:
link = message_args[1]
if link == "qb" or link == "qbs":
qbit = True
if link == "qbs":
qbitsel = True
link = message_args[2]
print(link)
if link.startswith("|") or link.startswith("pswd: "):
link = ''
@ -281,11 +289,13 @@ def _mirror(bot, update, isTar=False, extract=False):
tg_downloader = TelegramDownloadHelper(listener)
ms = update.message
tg_downloader.add_download(ms, f'{DOWNLOAD_DIR}{listener.uid}/', name)
if len(Interval) == 0:
Interval.append(setInterval(DOWNLOAD_STATUS_UPDATE_INTERVAL, update_all_messages))
return
else:
link = file.get_file().file_path
if qbit:
file.get_file().download(custom_path=f"/usr/src/app/{file.file_name}")
link = f"/usr/src/app/{file.file_name}"
else:
link = file.get_file().file_path
else:
tag = None
if not bot_utils.is_url(link) and not bot_utils.is_magnet(link):
@ -333,8 +343,6 @@ def _mirror(bot, update, isTar=False, extract=False):
download_status = DownloadStatus(drive, size, listener, gid)
with download_dict_lock:
download_dict[listener.uid] = download_status
if len(Interval) == 0:
Interval.append(setInterval(DOWNLOAD_STATUS_UPDATE_INTERVAL, update_all_messages))
sendStatusMessage(update, bot)
drive.download(link)
@ -347,11 +355,14 @@ def _mirror(bot, update, isTar=False, extract=False):
else:
mega_dl = MegaDownloadHelper()
mega_dl.add_download(link, f'{DOWNLOAD_DIR}/{listener.uid}/', listener)
elif qbit and (bot_utils.is_magnet(link) or os.path.exists(link)):
qbit = qbittorrent()
qbit.add_torrent(link, f'{DOWNLOAD_DIR}{listener.uid}/', listener, qbitsel)
else:
ariaDlManager.add_download(link, f'{DOWNLOAD_DIR}/{listener.uid}/', listener, name)
sendStatusMessage(update, bot)
if len(Interval) == 0:
Interval.append(setInterval(DOWNLOAD_STATUS_UPDATE_INTERVAL, update_all_messages))
def mirror(update, context):

View File

@ -1,8 +1,7 @@
from telegram.ext import CommandHandler
from telegram import Bot, Update
from bot import Interval, DOWNLOAD_DIR, DOWNLOAD_STATUS_UPDATE_INTERVAL, dispatcher, LOGGER
from bot.helper.ext_utils.bot_utils import setInterval
from bot.helper.telegram_helper.message_utils import update_all_messages, sendMessage, sendStatusMessage
from bot import DOWNLOAD_DIR, dispatcher, LOGGER
from bot.helper.telegram_helper.message_utils import sendMessage, sendStatusMessage
from .mirror import MirrorListener
from bot.helper.mirror_utils.download_utils.youtube_dl_download_helper import YoutubeDLHelper
from bot.helper.telegram_helper.bot_commands import BotCommands
@ -50,8 +49,6 @@ def _watch(bot: Bot, update, isTar=False):
ydl = YoutubeDLHelper(listener)
threading.Thread(target=ydl.add_download,args=(link, f'{DOWNLOAD_DIR}{listener.uid}', qual, name)).start()
sendStatusMessage(update, bot)
if len(Interval) == 0:
Interval.append(setInterval(DOWNLOAD_STATUS_UPDATE_INTERVAL, update_all_messages))
def watchTar(update, context):

View File

@ -30,6 +30,10 @@ BLOCK_MEGA_LINKS = ""
STOP_DUPLICATE = ""
SHORTENER = ""
SHORTENER_API = ""
#Qbittorrent
IS_VPS = ""
SERVER_PORT = "80" #For VPS
BASE_URL_OF_BOT = ""
# If you want to use Credentials externally from Index Links, fill these vars with the direct links
# These are optional, if you don't know, simply leave them, don't fill anything in them.
ACCOUNTS_ZIP_URL = ""

View File

@ -1,5 +1,5 @@
build:
docker:
worker: Dockerfile
web: Dockerfile
run:
worker: bash start.sh
web: bash start.sh

140
nodes.py Normal file
View File

@ -0,0 +1,140 @@
# -*- coding: utf-8 -*-
# (c) YashDK [yash-dk@github]
from anytree import NodeMixin, RenderTree, PreOrderIter
import qbittorrentapi as qba
SIZE_UNITS = ['B', 'KB', 'MB', 'GB', 'TB', 'PB']
class TorNode(NodeMixin):
def __init__(self, name, is_folder=False, is_file=False, parent=None, progress=None, size=None, priority=None, file_id=None):
super().__init__()
self.name = name
self.is_folder = is_folder
self.is_file = is_file
if parent is not None:
self.parent = parent
if progress is not None:
self.progress = progress
if size is not None:
self.size = size
if priority is not None:
self.priority = priority
if file_id is not None:
self.file_id = file_id
def get_folders(path):
path_seperator = "/"
folders = path.split(path_seperator)
return folders
def make_tree(res):
"""This function takes the list of all the torrent files. The files are name hierarchically.
Felt a need to document to save time.
Args:
res (list): Torrent files list.
Returns:
TorNode: Parent node of the tree constructed and can be used further.
"""
parent = TorNode("Torrent")
#nodes = dict()
l = 0
for i in res:
# Get the hierarchy of the folders by splitting based on '/'
folders = get_folders(i.name)
# Check if the file is alone for if its in folder
if len(folders) > 1:
# Enter here if in folder
# Set the parent
previous_node = parent
# Traverse till second last assuming the last is a file.
for j in range(len(folders)-1):
current_node = None
if previous_node is not None:
# As we are traversing the folder from top to bottom we are searching
# the first folder (folders list) under the parent node in first iteration.
# If the node is found then it becomes the current node else the current node
# is left None.
for k in previous_node.children:
if k.name == folders[j]:
current_node = k
break
else:
# think its useless afterall
for k in parent.children:
if k.name == folders[j]:
current_node = k
break
# if the node is not found then create the folder node
# if the node is found then use it as base for the next
if current_node is None:
previous_node = TorNode(folders[j],parent=previous_node,is_folder=True)
else:
previous_node = current_node
# at this point the previous_node will contain the deepest folder in it so add the file to it
TorNode(folders[-1],is_file=True,parent=previous_node,progress=i.progress,size=i.size,priority=i.priority,file_id=l)
l += 1
else:
# at the file to the parent if no folders are there
TorNode(folders[-1],is_file=True,parent=parent,progress=i.progress,size=i.size,priority=i.priority,file_id=l)
l += 1
return parent
def print_tree(parent):
for pre, _, node in RenderTree(parent):
treestr = u"%s%s" % (pre, node.name)
print(treestr.ljust(8), node.is_folder, node.is_file)
def create_list(par, msg):
if par.name != ".unwanted":
msg[0] += "<ul>"
for i in par.children:
if i.is_folder:
msg[0] += "<li>"
if i.name != ".unwanted":
msg[0] += f"<input type=\"checkbox\" name=\"foldernode_{msg[1]}\"> <label for=\"{i.name}\">{i.name}</label>"
create_list(i,msg)
msg[0] += "</li>"
msg[1] += 1
else:
msg[0] += "<li>"
if i.priority == 0:
msg[0] += f"<input type=\"checkbox\" name=\"filenode_{i.file_id}\"> <label for=\"filenode_{i.file_id}\">{i.name} - {get_readable_file_size(i.size)}</label>"
msg[0] += f"<input type=\"hidden\" value=\"off\" name=\"filenode_{i.file_id}\">"
else:
msg[0] += f"<input type=\"checkbox\" checked name=\"filenode_{i.file_id}\"> <label for=\"filenode_{i.file_id}\">{i.name} - {get_readable_file_size(i.size)}</label>"
msg[0] += f"<input type=\"hidden\" value=\"off\" name=\"filenode_{i.file_id}\">"
msg[0] += "</li>"
if par.name != ".unwanted":
msg[0] += "</ul>"
def get_readable_file_size(size_in_bytes) -> str:
if size_in_bytes is None:
return '0B'
index = 0
while size_in_bytes >= 1024:
size_in_bytes /= 1024
index += 1
try:
return f'{round(size_in_bytes, 2)}{SIZE_UNITS[index]}'
except IndexError:
return 'File too large'

View File

@ -1,4 +1,5 @@
aiohttp
anytree
aria2p
appdirs
beautifulsoup4
@ -8,6 +9,7 @@ gitpython
google-api-python-client
google-auth-httplib2
google-auth-oauthlib
gunicorn
heroku3
js2py
lk21
@ -19,9 +21,12 @@ pyrogram
python-dotenv
python-magic
python-telegram-bot
qbittorrent-api
requests
speedtest-cli
telegraph
tenacity
TgCrypto
torrentool
youtube_dl
urllib3

View File

@ -8,4 +8,4 @@ if [[ -n $ACCOUNTS_ZIP_URL ]]; then
rm accounts.zip
fi
./aria.sh; python3 -m bot
gunicorn wserver:start_server --bind 0.0.0.0:$PORT --worker-class aiohttp.GunicornWebWorker & qbittorrent-nox -d --webui-port=8090 & python3 alive.py & ./aria.sh; python3 -m bot

309
wserver.py Normal file
View File

@ -0,0 +1,309 @@
# -*- coding: utf-8 -*-
# (c) YashDK [yash-dk@github]
import os
import time
import logging
import qbittorrentapi as qba
import asyncio
from aiohttp import web
import nodes
LOGGER = logging.getLogger(__name__)
routes = web.RouteTableDef()
page = """
<html>
<head>
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha256-4+XzXVhsDmqanXGHaHvgh1gMQKX40OUvDEBTu8JcmNs=" crossorigin="anonymous"></script>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
body {
padding: 20px;
}
ul {
list-style: none;
margin: 5px 20px;
}
li {
margin: 10px 0;
}
p { font-size: 12px; margin: 24px;}
</style>
</head>
<body>
<h1>Slam-mirror-bot : <a href="#">Github</a></h1>
<form action="{form_url}" method="POST">
{My_content}
<input type="submit" name="Select these files ;)">
</form>
<script>
$('input[type="checkbox"]').change(function(e) {
var checked = $(this).prop("checked"),
container = $(this).parent(),
siblings = container.siblings();
/*
$(this).attr('value', function(index, attr){
return attr == 'yes' ? 'noo' : 'yes';
});
*/
container.find('input[type="checkbox"]').prop({
indeterminate: false,
checked: checked
});
function checkSiblings(el) {
var parent = el.parent().parent(),
all = true;
el.siblings().each(function() {
let returnValue = all = ($(this).children('input[type="checkbox"]').prop("checked") === checked);
return returnValue;
});
if (all && checked) {
parent.children('input[type="checkbox"]').prop({
indeterminate: false,
checked: checked
});
checkSiblings(parent);
} else if (all && !checked) {
parent.children('input[type="checkbox"]').prop("checked", checked);
parent.children('input[type="checkbox"]').prop("indeterminate", (parent.find('input[type="checkbox"]:checked').length > 0));
checkSiblings(parent);
} else {
el.parents("li").children('input[type="checkbox"]').prop({
indeterminate: true,
checked: false
});
}
}
checkSiblings(container);
});
</script>
</body>
</html>
"""
code_page = """
<html>
<head>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" integrity="sha384-JcKb8q3iqJ61gNV9KGb8thSsNjpSL0n8PARn9HuZOnIxN0hoP+VmmDGMN5t9UJ0Z" crossorigin="anonymous">
<title>
Slam Torrent Files
</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<div class="container">
<form action="{form_url}">
<div class="form-group">
<label for="pin_code">Pin Code</label>
<input type="text" class="form-control" name="pin_code" placeholder="Enter code to access the torrent">
<small class="form-text text-muted">Dont mess around. You download will get messed up.</small>
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
</div>
</body>
</html>
"""
@routes.get('/slam/files/{hash_id}')
async def list_torrent_contents(request):
torr = request.match_info["hash_id"]
gets = request.query
if not "pin_code" in gets.keys():
rend_page = code_page.replace("{form_url}",f"/slam/files/{torr}")
return web.Response(text=rend_page,content_type='text/html')
client = qba.Client(host="localhost",port="8090",username="admin",password="adminadmin")
client.auth_log_in()
try:
res = client.torrents_files(torrent_hash=torr)
except qba.NotFound404Error:
raise web.HTTPNotFound()
count = 0
passw = ""
for n in str(torr):
if n.isdigit():
passw += str(n)
count += 1
if count == 4:
break
if isinstance(passw, bool):
raise web.HTTPNotFound()
pincode = passw
if gets["pin_code"] != pincode:
return web.Response(text="Incorrect pin code")
par = nodes.make_tree(res)
cont = ["",0]
nodes.create_list(par,cont)
rend_page = page.replace("{My_content}",cont[0])
rend_page = rend_page.replace("{form_url}",f"/slam/files/{torr}?pin_code={pincode}")
client.auth_log_out()
return web.Response(text=rend_page,content_type='text/html')
async def re_verfiy(paused, resumed, client, torr):
paused = paused.strip()
resumed = resumed.strip()
if paused:
paused = paused.split("|")
if resumed:
resumed = resumed.split("|")
k = 0
while True:
res = client.torrents_files(torrent_hash=torr)
verify = True
for i in res:
if str(i.id) in paused:
if i.priority == 0:
continue
else:
verify = False
break
if str(i.id) in resumed:
if i.priority != 0:
continue
else:
verify = False
break
if not verify:
LOGGER.info("Reverification Failed :- correcting stuff")
# reconnect and issue the request again
client.auth_log_out()
client = qba.Client(host="localhost",port="8090",username="admin",password="adminadmin")
client.auth_log_in()
try:
client.torrents_file_priority(torrent_hash=torr,file_ids=paused,priority=0)
except:
LOGGER.error("Errored in reverification paused")
try:
client.torrents_file_priority(torrent_hash=torr,file_ids=resumed,priority=1)
except:
LOGGER.error("Errored in reverification resumed")
client.auth_log_out()
else:
break
k += 1
if k >= 2:
# avoid an infite loop here
return False
return True
@routes.post('/slam/files/{hash_id}')
async def set_priority(request):
torr = request.match_info["hash_id"]
client = qba.Client(host="localhost",port="8090",username="admin",password="adminadmin")
client.auth_log_in()
data = await request.post()
resume = ""
pause = ""
data = dict(data)
for i in data.keys():
if i.find("filenode") != -1:
node_no = i.split("_")[-1]
if data[i] == "on":
resume += f"{node_no}|"
else:
pause += f"{node_no}|"
pause = pause.strip("|")
resume = resume.strip("|")
LOGGER.info(f"Paused {pause} of {torr}")
LOGGER.info(f"Resumed {resume} of {torr}")
try:
client.torrents_file_priority(torrent_hash=torr,file_ids=pause,priority=0)
except qba.NotFound404Error:
raise web.HTTPNotFound()
except:
LOGGER.info("Errored in paused")
try:
client.torrents_file_priority(torrent_hash=torr,file_ids=resume,priority=1)
except qba.NotFound404Error:
raise web.HTTPNotFound()
except:
LOGGER.info("Errored in resumed")
await asyncio.sleep(2)
if not await re_verfiy(pause,resume,client,torr):
LOGGER.error("The torrent choose errored reverification failed")
client.auth_log_out()
return await list_torrent_contents(request)
@routes.get('/')
async def homepage(request):
return web.Response(text="<h1>See slam-mirror-bot <a href=\"#\">@GitHub</a>By Slam</h1>",content_type="text/html")
async def e404_middleware(app, handler):
async def middleware_handler(request):
try:
response = await handler(request)
if response.status == 404:
return web.Response(text="<h1>404: Page not found</h2><br><h3>Slam</h3>",content_type="text/html")
return response
except web.HTTPException as ex:
if ex.status == 404:
return web.Response(text="<h1>404: Page not found</h2><br><h3>Slam</h3>",content_type="text/html")
raise
return middleware_handler
async def start_server():
app = web.Application(middlewares=[e404_middleware])
app.add_routes(routes)
return app
async def start_server_async(port = 8080):
app = web.Application(middlewares=[e404_middleware])
app.add_routes(routes)
runner = web.AppRunner(app)
await runner.setup()
await web.TCPSite(runner,"0.0.0.0", port).start()