From de1f3ba3b9e9d48a6b98f0f5c6f6b69db799baf2 Mon Sep 17 00:00:00 2001 From: ch3p4ll3 Date: Sat, 21 Oct 2023 18:01:14 +0200 Subject: [PATCH 1/3] update libraries, use torrent hash for search, async functions --- .gitignore | 166 ++++++++++++++++++++++++++++ Dockerfile | 4 +- main.py | 1 + requirements.txt | 35 +++--- src/bot.py | 217 +++++++++++++++++++------------------ src/config.py | 15 +-- src/db_management.py | 5 +- src/qbittorrent_control.py | 23 ++-- 8 files changed, 321 insertions(+), 145 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..72c2690 --- /dev/null +++ b/.gitignore @@ -0,0 +1,166 @@ +### Python template +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ + +config.json + +*.session +*.session-journal \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index f9b7cf0..f6f50ba 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM python:3.10 +FROM python:3.11 WORKDIR /app @@ -6,6 +6,8 @@ COPY requirements.txt requirements.txt RUN pip3 install -r requirements.txt +ENV IS_DOCKER True + COPY . . VOLUME [ "/app/config" ] diff --git a/main.py b/main.py index f9eba90..9edf306 100644 --- a/main.py +++ b/main.py @@ -2,4 +2,5 @@ if __name__ == '__main__': scheduler.start() + print("Bot started") app.run() diff --git a/requirements.txt b/requirements.txt index efe8d53..878ca43 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,21 +1,24 @@ -APScheduler==3.9.1 -async-lru==1.0.3 -certifi==2022.9.24 -charset-normalizer==2.1.1 +annotated-types==0.6.0 +APScheduler==3.10.4 +async-lru==2.0.4 +certifi==2023.7.22 +charset-normalizer==3.3.0 idna==3.4 -pony==0.7.16 -psutil==5.9.2 +packaging==23.2 +pony==0.7.17 +psutil==5.9.6 pyaes==1.6.1 -pydantic==1.10.2 -Pyrogram==2.0.57 +pydantic==2.4.2 +pydantic_core==2.10.1 +Pyrogram==2.0.106 PySocks==1.7.1 -pytz==2022.2.1 +pytz==2023.3.post1 pytz-deprecation-shim==0.1.0.post0 -qbittorrent-api==2022.8.38 -requests==2.28.1 +qbittorrent-api==2023.9.53 +requests==2.31.0 six==1.16.0 -TgCrypto==1.2.3 -typing_extensions==4.3.0 -tzdata==2022.2 -tzlocal==4.2 -urllib3==1.26.12 +TgCrypto==1.2.5 +typing_extensions==4.8.0 +tzdata==2023.3 +tzlocal==5.1 +urllib3==2.0.7 diff --git a/src/bot.py b/src/bot.py index 0f30788..7be8683 100755 --- a/src/bot.py +++ b/src/bot.py @@ -14,7 +14,6 @@ from src.config import BOT_CONFIGS from src import db_management - app = Client( "qbittorrent_bot", api_id=BOT_CONFIGS.telegram.api_id, @@ -27,7 +26,7 @@ scheduler.add_job(torrent_finished, "interval", args=[app], seconds=10) -def send_menu(message, chat) -> None: +async def send_menu(message, chat) -> None: db_management.write_support("None", chat) buttons = [[InlineKeyboardButton("📝 List", "list")], [InlineKeyboardButton("➕ Add Magnet", "category#add_magnet"), @@ -43,13 +42,14 @@ def send_menu(message, chat) -> None: [InlineKeyboardButton("📝 Modify Category", "select_category#modify_category")]] try: - app.edit_message_text(chat, message, text="Qbittorrent Control", reply_markup=InlineKeyboardMarkup(buttons)) + await app.edit_message_text(chat, message, text="Qbittorrent Control", + reply_markup=InlineKeyboardMarkup(buttons)) except MessageIdInvalid: - app.send_message(chat, text="Qbittorrent Control", reply_markup=InlineKeyboardMarkup(buttons)) + await app.send_message(chat, text="Qbittorrent Control", reply_markup=InlineKeyboardMarkup(buttons)) -def list_active_torrents(n, chat, message, callback, status_filter: str = None) -> None: +async def list_active_torrents(n, chat, message, callback, status_filter: str = None) -> None: torrents = qbittorrent_control.get_torrent_info(status_filter=status_filter) def render_categories_buttons(): @@ -65,50 +65,51 @@ def render_categories_buttons(): if not torrents: buttons = [categories_buttons, [InlineKeyboardButton("🔙 Menu", "menu")]] try: - app.edit_message_text(chat, message, "There are no torrents", reply_markup=InlineKeyboardMarkup(buttons)) + await app.edit_message_text(chat, message, "There are no torrents", + reply_markup=InlineKeyboardMarkup(buttons)) except MessageIdInvalid: - app.send_message(chat, "There are no torrents", reply_markup=InlineKeyboardMarkup(buttons)) + await app.send_message(chat, "There are no torrents", reply_markup=InlineKeyboardMarkup(buttons)) return buttons = [categories_buttons] if n == 1: for key, i in enumerate(torrents): - buttons.append([InlineKeyboardButton(i.name, f"{callback}#{key+1}")]) + buttons.append([InlineKeyboardButton(i.name, f"{callback}#{i.info.hash}")]) buttons.append([InlineKeyboardButton("🔙 Menu", "menu")]) try: - app.edit_message_reply_markup(chat, message, reply_markup=InlineKeyboardMarkup(buttons)) + await app.edit_message_reply_markup(chat, message, reply_markup=InlineKeyboardMarkup(buttons)) except MessageIdInvalid: - app.send_message(chat, "Qbittorrent Control", reply_markup=InlineKeyboardMarkup(buttons)) + await app.send_message(chat, "Qbittorrent Control", reply_markup=InlineKeyboardMarkup(buttons)) else: for key, i in enumerate(torrents): - buttons.append([InlineKeyboardButton(i.name, f"torrentInfo#{key+1}")]) + buttons.append([InlineKeyboardButton(i.name, f"torrentInfo#{i.info.hash}")]) buttons.append([InlineKeyboardButton("🔙 Menu", "menu")]) try: - app.edit_message_reply_markup(chat, message, reply_markup=InlineKeyboardMarkup(buttons)) + await app.edit_message_reply_markup(chat, message, reply_markup=InlineKeyboardMarkup(buttons)) except MessageIdInvalid: - app.send_message(chat, "Qbittorrent Control", reply_markup=InlineKeyboardMarkup(buttons)) + await app.send_message(chat, "Qbittorrent Control", reply_markup=InlineKeyboardMarkup(buttons)) @app.on_message(filters=filters.command("start")) -def start_command(client: Client, message: Message) -> None: +async def start_command(client: Client, message: Message) -> None: """Start the bot.""" if message.from_user.id in [i.user_id for i in BOT_CONFIGS.users]: - send_menu(message.id, message.chat.id) + await send_menu(message.id, message.chat.id) else: button = InlineKeyboardMarkup([[InlineKeyboardButton("Github", url="https://github.com/ch3p4ll3/QBittorrentBot/")]]) - app.send_message(message.chat.id, "You are not authorized to use this bot", reply_markup=button) + await app.send_message(message.chat.id, "You are not authorized to use this bot", reply_markup=button) @app.on_message(filters=filters.command("stats")) -def stats_command(client: Client, message: Message) -> None: +async def stats_command(client: Client, message: Message) -> None: if message.from_user.id in [i.user_id for i in BOT_CONFIGS.users]: txt = f"**============SYSTEM============**\n" \ @@ -119,34 +120,34 @@ def stats_command(client: Client, message: Message) -> None: f"**Disks usage:** {convert_size(psutil.disk_usage('/mnt').used)} of " \ f"{convert_size(psutil.disk_usage('/mnt').total)} ({psutil.disk_usage('/mnt').percent}%)" - message.reply_text(txt) + await message.reply_text(txt) else: button = InlineKeyboardMarkup([[InlineKeyboardButton("Github", url="https://github.com/ch3p4ll3/QBittorrentBot/")]]) - app.send_message(message.chat.id, "You are not authorized to use this bot", reply_markup=button) + await app.send_message(message.chat.id, "You are not authorized to use this bot", reply_markup=button) @app.on_callback_query(filters=custom_filters.add_category_filter) -def add_category_callback(client: Client, callback_query: CallbackQuery) -> None: +async def add_category_callback(client: Client, callback_query: CallbackQuery) -> None: db_management.write_support("category_name", callback_query.from_user.id) button = InlineKeyboardMarkup([[InlineKeyboardButton("🔙 Menu", "menu")]]) try: - app.edit_message_text(callback_query.from_user.id, callback_query.message.id, - "Send the category name", reply_markup=button) + await app.edit_message_text(callback_query.from_user.id, callback_query.message.id, + "Send the category name", reply_markup=button) except MessageIdInvalid: - app.send_message(callback_query.from_user.id, "Send the category name", reply_markup=button) + await app.send_message(callback_query.from_user.id, "Send the category name", reply_markup=button) @app.on_callback_query(filters=custom_filters.select_category_filter) -def list_categories(client: Client, callback_query: CallbackQuery): +async def list_categories(client: Client, callback_query: CallbackQuery): buttons = [] categories = qbittorrent_control.get_categories() if categories is None: buttons.append([InlineKeyboardButton("🔙 Menu", "menu")]) - app.edit_message_text(callback_query.from_user.id, callback_query.message.id, - "There are no categories", reply_markup=InlineKeyboardMarkup(buttons)) + await app.edit_message_text(callback_query.from_user.id, callback_query.message.id, + "There are no categories", reply_markup=InlineKeyboardMarkup(buttons)) return for key, i in enumerate(categories): @@ -155,44 +156,45 @@ def list_categories(client: Client, callback_query: CallbackQuery): buttons.append([InlineKeyboardButton("🔙 Menu", "menu")]) try: - app.edit_message_text(callback_query.from_user.id, callback_query.message.id, - "Choose a category:", reply_markup=InlineKeyboardMarkup(buttons)) + await app.edit_message_text(callback_query.from_user.id, callback_query.message.id, + "Choose a category:", reply_markup=InlineKeyboardMarkup(buttons)) except MessageIdInvalid: - app.send_message(callback_query.from_user.id, "Choose a category:", reply_markup=InlineKeyboardMarkup(buttons)) + await app.send_message(callback_query.from_user.id, "Choose a category:", + reply_markup=InlineKeyboardMarkup(buttons)) @app.on_callback_query(filters=custom_filters.remove_category_filter) -def remove_category_callback(client: Client, callback_query: CallbackQuery) -> None: +async def remove_category_callback(client: Client, callback_query: CallbackQuery) -> None: buttons = [[InlineKeyboardButton("🔙 Menu", "menu")]] qbittorrent_control.remove_category(data=callback_query.data.split("#")[1]) - app.edit_message_text(callback_query.from_user.id, callback_query.message.id, - f"The category {callback_query.data.split('#')[1]} has been removed", - reply_markup=InlineKeyboardMarkup(buttons)) + await app.edit_message_text(callback_query.from_user.id, callback_query.message.id, + f"The category {callback_query.data.split('#')[1]} has been removed", + reply_markup=InlineKeyboardMarkup(buttons)) @app.on_callback_query(filters=custom_filters.modify_category_filter) -def modify_category_callback(client: Client, callback_query: CallbackQuery) -> None: +async def modify_category_callback(client: Client, callback_query: CallbackQuery) -> None: buttons = [[InlineKeyboardButton("🔙 Menu", "menu")]] db_management.write_support(f"category_dir_modify#{callback_query.data.split('#')[1]}", callback_query.from_user.id) - app.edit_message_text(callback_query.from_user.id, callback_query.message.id, - f"Send new path for category {callback_query.data.split('#')[1]}", - reply_markup=InlineKeyboardMarkup(buttons)) + await app.edit_message_text(callback_query.from_user.id, callback_query.message.id, + f"Send new path for category {callback_query.data.split('#')[1]}", + reply_markup=InlineKeyboardMarkup(buttons)) @app.on_callback_query(filters=custom_filters.category_filter) -def category(client: Client, callback_query: CallbackQuery) -> None: +async def category(client: Client, callback_query: CallbackQuery) -> None: buttons = [] categories = qbittorrent_control.get_categories() if categories is None: if "magnet" in callback_query.data: - addmagnet_callback(client, callback_query) + await addmagnet_callback(client, callback_query) else: - addtorrent_callback(client, callback_query) + await addtorrent_callback(client, callback_query) return @@ -203,135 +205,137 @@ def category(client: Client, callback_query: CallbackQuery) -> None: buttons.append([InlineKeyboardButton("🔙 Menu", "menu")]) try: - app.edit_message_text(callback_query.from_user.id, callback_query.message.id, - "Choose a category:", reply_markup=InlineKeyboardMarkup(buttons)) + await app.edit_message_text(callback_query.from_user.id, callback_query.message.id, + "Choose a category:", reply_markup=InlineKeyboardMarkup(buttons)) except MessageIdInvalid: - app.send_message(callback_query.from_user.id, "Choose a category:", reply_markup=InlineKeyboardMarkup(buttons)) + await app.send_message(callback_query.from_user.id, "Choose a category:", + reply_markup=InlineKeyboardMarkup(buttons)) @app.on_callback_query(filters=custom_filters.menu_filter) -def menu_callback(client: Client, callback_query: CallbackQuery) -> None: - send_menu(callback_query.message.id, callback_query.from_user.id) +async def menu_callback(client: Client, callback_query: CallbackQuery) -> None: + await send_menu(callback_query.message.id, callback_query.from_user.id) @app.on_callback_query(filters=custom_filters.list_filter) -def list_callback(client: Client, callback_query: CallbackQuery) -> None: - list_active_torrents(0, callback_query.from_user.id, callback_query.message.id, - db_management.read_support(callback_query.from_user.id)) +async def list_callback(client: Client, callback_query: CallbackQuery) -> None: + await list_active_torrents(0, callback_query.from_user.id, callback_query.message.id, + db_management.read_support(callback_query.from_user.id)) @app.on_callback_query(filters=custom_filters.list_by_status_filter) -def list_by_status_callback(client: Client, callback_query: CallbackQuery) -> None: +async def list_by_status_callback(client: Client, callback_query: CallbackQuery) -> None: status_filter = callback_query.data.split("#")[1] - list_active_torrents(0, callback_query.from_user.id, callback_query.message.id, - db_management.read_support(callback_query.from_user.id), status_filter=status_filter) + await list_active_torrents(0, callback_query.from_user.id, callback_query.message.id, + db_management.read_support(callback_query.from_user.id), status_filter=status_filter) @app.on_callback_query(filters=custom_filters.add_magnet_filter) -def addmagnet_callback(client: Client, callback_query: CallbackQuery) -> None: +async def addmagnet_callback(client: Client, callback_query: CallbackQuery) -> None: db_management.write_support(f"magnet#{callback_query.data.split('#')[1]}", callback_query.from_user.id) - app.answer_callback_query(callback_query.id, "Send a magnet link") + await app.answer_callback_query(callback_query.id, "Send a magnet link") @app.on_callback_query(filters=custom_filters.add_torrent_filter) -def addtorrent_callback(client: Client, callback_query: CallbackQuery) -> None: +async def addtorrent_callback(client: Client, callback_query: CallbackQuery) -> None: db_management.write_support(f"torrent#{callback_query.data.split('#')[1]}", callback_query.from_user.id) - app.answer_callback_query(callback_query.id, "Send a torrent file") + await app.answer_callback_query(callback_query.id, "Send a torrent file") @app.on_callback_query(filters=custom_filters.pause_all_filter) -def pauseall_callback(client: Client, callback_query: CallbackQuery) -> None: +async def pauseall_callback(client: Client, callback_query: CallbackQuery) -> None: qbittorrent_control.pause_all() - app.answer_callback_query(callback_query.id, "Paused all torrents") + await app.answer_callback_query(callback_query.id, "Paused all torrents") @app.on_callback_query(filters=custom_filters.resume_all_filter) -def resumeall_callback(client: Client, callback_query: CallbackQuery) -> None: +async def resumeall_callback(client: Client, callback_query: CallbackQuery) -> None: qbittorrent_control.resume_all() - app.answer_callback_query(callback_query.id, "Resumed all torrents") + await app.answer_callback_query(callback_query.id, "Resumed all torrents") @app.on_callback_query(filters=custom_filters.pause_filter) -def pause_callback(client: Client, callback_query: CallbackQuery) -> None: +async def pause_callback(client: Client, callback_query: CallbackQuery) -> None: if callback_query.data.find("#") == -1: - list_active_torrents(1, callback_query.from_user.id, callback_query.message.id, "pause") + await list_active_torrents(1, callback_query.from_user.id, callback_query.message.id, "pause") else: - qbittorrent_control.pause(id_torrent=int(callback_query.data.split("#")[1])) - send_menu(callback_query.message.id, callback_query.from_user.id) + qbittorrent_control.pause(torrent_hash=callback_query.data.split("#")[1]) + await send_menu(callback_query.message.id, callback_query.from_user.id) @app.on_callback_query(filters=custom_filters.resume_filter) -def resume_callback(client: Client, callback_query: CallbackQuery) -> None: +async def resume_callback(client: Client, callback_query: CallbackQuery) -> None: if callback_query.data.find("#") == -1: - list_active_torrents(1, callback_query.from_user.id, callback_query.message.id, "resume") + await list_active_torrents(1, callback_query.from_user.id, callback_query.message.id, "resume") else: - qbittorrent_control.resume(id_torrent=int(callback_query.data.split("#")[1])) - send_menu(callback_query.message.id, callback_query.from_user.id) + qbittorrent_control.resume(torrent_hash=callback_query.data.split("#")[1]) + await send_menu(callback_query.message.id, callback_query.from_user.id) @app.on_callback_query(filters=custom_filters.delete_one_filter) -def delete_callback(client: Client, callback_query: CallbackQuery) -> None: +async def delete_callback(client: Client, callback_query: CallbackQuery) -> None: if callback_query.data.find("#") == -1: - list_active_torrents(1, callback_query.from_user.id, callback_query.message.id, "delete_one") + await list_active_torrents(1, callback_query.from_user.id, callback_query.message.id, "delete_one") else: - buttons = [[InlineKeyboardButton("🗑 Delete torrent", f"delete_one_no_data#{callback_query.data.split('#')[1]}")], - [InlineKeyboardButton("🗑 Delete torrent and data", f"delete_one_data#{callback_query.data.split('#')[1]}")], - [InlineKeyboardButton("🔙 Menu", "menu")]] + buttons = [ + [InlineKeyboardButton("🗑 Delete torrent", f"delete_one_no_data#{callback_query.data.split('#')[1]}")], + [InlineKeyboardButton("🗑 Delete torrent and data", f"delete_one_data#{callback_query.data.split('#')[1]}")], + [InlineKeyboardButton("🔙 Menu", "menu")]] - app.edit_message_reply_markup(callback_query.from_user.id, callback_query.message.id, - reply_markup=InlineKeyboardMarkup(buttons)) + await app.edit_message_reply_markup(callback_query.from_user.id, callback_query.message.id, + reply_markup=InlineKeyboardMarkup(buttons)) @app.on_callback_query(filters=custom_filters.delete_one_no_data_filter) -def delete_no_data_callback(client: Client, callback_query: CallbackQuery) -> None: +async def delete_no_data_callback(client: Client, callback_query: CallbackQuery) -> None: if callback_query.data.find("#") == -1: - list_active_torrents(1, callback_query.from_user.id, callback_query.message.id, "delete_one_no_data") + await list_active_torrents(1, callback_query.from_user.id, callback_query.message.id, "delete_one_no_data") else: - qbittorrent_control.delete_one_no_data(id_torrent=int(callback_query.data.split("#")[1])) - send_menu(callback_query.message.id, callback_query.from_user.id) + qbittorrent_control.delete_one_no_data(torrent_hash=callback_query.data.split("#")[1]) + await send_menu(callback_query.message.id, callback_query.from_user.id) @app.on_callback_query(filters=custom_filters.delete_one_data_filter) -def delete_with_data_callback(client: Client, callback_query: CallbackQuery) -> None: +async def delete_with_data_callback(client: Client, callback_query: CallbackQuery) -> None: if callback_query.data.find("#") == -1: - list_active_torrents(1, callback_query.from_user.id, callback_query.message.id, "delete_one_data") + await list_active_torrents(1, callback_query.from_user.id, callback_query.message.id, "delete_one_data") else: - qbittorrent_control.delete_one_data(id_torrent=int(callback_query.data.split("#")[1])) - send_menu(callback_query.message.id, callback_query.from_user.id) + qbittorrent_control.delete_one_data(torrent_hash=callback_query.data.split("#")[1]) + await send_menu(callback_query.message.id, callback_query.from_user.id) @app.on_callback_query(filters=custom_filters.delete_all_filter) -def delete_all_callback(client: Client, callback_query: CallbackQuery) -> None: +async def delete_all_callback(client: Client, callback_query: CallbackQuery) -> None: buttons = [[InlineKeyboardButton("🗑 Delete all torrents", "delete_all_no_data")], [InlineKeyboardButton("🗑 Delete all torrents and data", "delete_all_data")], [InlineKeyboardButton("🔙 Menu", "menu")]] - app.edit_message_reply_markup(callback_query.from_user.id, callback_query.message.id, - reply_markup=InlineKeyboardMarkup(buttons)) + await app.edit_message_reply_markup(callback_query.from_user.id, callback_query.message.id, + reply_markup=InlineKeyboardMarkup(buttons)) @app.on_callback_query(filters=custom_filters.delete_all_no_data_filter) -def delete_all_with_no_data_callback(client: Client, callback_query: CallbackQuery) -> None: +async def delete_all_with_no_data_callback(client: Client, callback_query: CallbackQuery) -> None: qbittorrent_control.delall_no_data() - app.answer_callback_query(callback_query.id, "Deleted only torrents") - send_menu(callback_query.message.id, callback_query.from_user.id) + await app.answer_callback_query(callback_query.id, "Deleted only torrents") + await send_menu(callback_query.message.id, callback_query.from_user.id) @app.on_callback_query(filters=custom_filters.delete_all_data_filter) -def delete_all_with_data_callback(client: Client, callback_query: CallbackQuery) -> None: +async def delete_all_with_data_callback(client: Client, callback_query: CallbackQuery) -> None: qbittorrent_control.delall_data() - app.answer_callback_query(callback_query.id, "Deleted All+Torrents") - send_menu(callback_query.message.id, callback_query.from_user.id) + await app.answer_callback_query(callback_query.id, "Deleted All+Torrents") + await send_menu(callback_query.message.id, callback_query.from_user.id) @app.on_callback_query(filters=custom_filters.torrentInfo_filter) -def torrent_info_callback(client: Client, callback_query: CallbackQuery) -> None: - torrent = qbittorrent_control.get_torrent_info(data=int(callback_query.data.split("#")[1])) +async def torrent_info_callback(client: Client, callback_query: CallbackQuery) -> None: + torrent = qbittorrent_control.get_torrent_info(data=callback_query.data.split("#")[1]) progress = torrent.progress * 100 text = "" @@ -366,12 +370,12 @@ def torrent_info_callback(client: Client, callback_query: CallbackQuery) -> None [InlineKeyboardButton("🗑 Delete", f"delete_one#{callback_query.data.split('#')[1]}")], [InlineKeyboardButton("🔙 Menu", "menu")]] - app.edit_message_text(callback_query.from_user.id, callback_query.message.id, text=text, - reply_markup=InlineKeyboardMarkup(buttons)) + await app.edit_message_text(callback_query.from_user.id, callback_query.message.id, text=text, + reply_markup=InlineKeyboardMarkup(buttons)) @app.on_message() -def on_text(client: Client, message: Message) -> None: +async def on_text(client: Client, message: Message) -> None: action = db_management.read_support(message.from_user.id) if "magnet" in action: @@ -380,29 +384,29 @@ def on_text(client: Client, message: Message) -> None: category = db_management.read_support(message.from_user.id).split("#")[1] qbittorrent_control.add_magnet(magnet_link=magnet_link, category=category) - send_menu(message.id, message.from_user.id) + await send_menu(message.id, message.from_user.id) db_management.write_support("None", message.from_user.id) else: - message.reply_text("This magnet link is invalid! Retry") + await message.reply_text("This magnet link is invalid! Retry") elif "torrent" in action and message.document: if ".torrent" in message.document.file_name: with tempfile.TemporaryDirectory() as tempdir: name = f"{tempdir}/{message.document.file_name}" category = db_management.read_support(message.from_user.id).split("#")[1] - message.download(name) + await message.download(name) qbittorrent_control.add_torrent(file_name=name, category=category) - send_menu(message.id, message.from_user.id) + await send_menu(message.id, message.from_user.id) db_management.write_support("None", message.from_user.id) else: - message.reply_text("This is not a torrent file! Retry") + await message.reply_text("This is not a torrent file! Retry") elif action == "category_name": db_management.write_support(f"category_dir#{message.text}", message.from_user.id) - message.reply_text(f"now send me the path for the category {message.text}") + await message.reply_text(f"now send me the path for the category {message.text}") elif "category_dir" in action: if os.path.exists(message.text): @@ -411,12 +415,15 @@ def on_text(client: Client, message: Message) -> None: if "modify" in action: qbittorrent_control.edit_category(name=name, save_path=message.text) - send_menu(message.id, message.from_user.id) + await send_menu(message.id, message.from_user.id) return qbittorrent_control.create_category(name=name, save_path=message.text) - send_menu(message.id, message.from_user.id) + await send_menu(message.id, message.from_user.id) else: - message.reply_text("The path entered does not exist! Retry") + await message.reply_text("The path entered does not exist! Retry") + + else: + await message.reply_text("The command does not exist") diff --git a/src/config.py b/src/config.py index 9b4fe08..cfa4b06 100644 --- a/src/config.py +++ b/src/config.py @@ -1,7 +1,8 @@ import ipaddress -from pydantic import BaseModel, validator +from pydantic import BaseModel, field_validator from typing import Optional import json +from os import getenv class Qbittorrent(BaseModel): @@ -10,19 +11,19 @@ class Qbittorrent(BaseModel): user: str password: str - @validator('port') + @field_validator('port') def port_validator(cls, v): if v <= 0: raise ValueError('Port must be >= 0') return v - @validator('user') + @field_validator('user') def user_validator(cls, v): if not v or not v.strip(): raise ValueError('User cannot be empty') return v - @validator('password') + @field_validator('password') def password_validator(cls, v): if not v or not v.strip(): raise ValueError('Password cannot be empty') @@ -34,13 +35,13 @@ class Telegram(BaseModel): api_id: int api_hash: str - @validator('bot_token') + @field_validator('bot_token') def bot_token_validator(cls, v): if not v or not v.strip(): raise ValueError('Bot token cannot be empty') return v - @validator('api_hash') + @field_validator('api_hash') def api_hash_validator(cls, v): if not v or not v.strip(): raise ValueError('API HASH cannot be empty') @@ -58,5 +59,5 @@ class Main(BaseModel): users: list[Users] -with open('/app/config/config.json', 'r') as config_json: +with open(f'{ "/app/config/" if getenv("IS_DOCKER", False) else "./"}config.json', 'r') as config_json: BOT_CONFIGS = Main(**(json.load(config_json))) diff --git a/src/db_management.py b/src/db_management.py index 91dab22..fae80c3 100644 --- a/src/db_management.py +++ b/src/db_management.py @@ -1,9 +1,10 @@ from pony.orm import Database, PrimaryKey, Required, \ db_session, ObjectNotFound - +from os import getenv db = Database() -db.bind(provider='sqlite', filename='/app/config/database.sqlite', create_db=True) +db.bind(provider='sqlite', filename=f'{"/app/config/" if getenv("IS_DOCKER", False) else "./"}/database.sqlite', + create_db=True) class Support(db.Entity): diff --git a/src/qbittorrent_control.py b/src/qbittorrent_control.py index 3c58507..0e2356d 100644 --- a/src/qbittorrent_control.py +++ b/src/qbittorrent_control.py @@ -1,5 +1,4 @@ import qbittorrentapi - from src.config import BOT_CONFIGS @@ -65,29 +64,25 @@ def pause_all(qbt_client) -> None: @qbittorrent_login -def resume(qbt_client, id_torrent: int) -> None: - qbt_client.torrents_resume(hashes=qbt_client.torrents_info()[id_torrent - - 1].hash) +def resume(qbt_client, torrent_hash: str) -> None: + qbt_client.torrents_resume(torrent_hashes=torrent_hash) @qbittorrent_login -def pause(qbt_client, id_torrent: int) -> None: - qbt_client.torrents_pause(hashes=qbt_client.torrents_info()[id_torrent - - 1].hash) +def pause(qbt_client, torrent_hash: str) -> None: + qbt_client.torrents_pause(torrent_hashes=torrent_hash) @qbittorrent_login -def delete_one_no_data(qbt_client, id_torrent: int) -> None: +def delete_one_no_data(qbt_client, torrent_hash: str) -> None: qbt_client.torrents_delete(delete_files=False, - hashes=qbt_client.torrents_info()[id_torrent - - 1].hash) + torrent_hashes=torrent_hash) @qbittorrent_login -def delete_one_data(qbt_client, id_torrent: int) -> None: +def delete_one_data(qbt_client, torrent_hash: str) -> None: qbt_client.torrents_delete(delete_files=True, - hashes=qbt_client.torrents_info()[id_torrent - - 1].hash) + torrent_hashes=torrent_hash) @qbittorrent_login @@ -116,7 +111,7 @@ def get_categories(qbt_client): def get_torrent_info(qbt_client, data: str = None, status_filter: str = None, ): if data is None: return qbt_client.torrents_info(status_filter=status_filter) - return qbt_client.torrents_info(status_filter=status_filter)[int(data) - 1] + return next(iter(qbt_client.torrents_info(status_filter=status_filter, torrent_hashes=data)), None) @qbittorrent_login From 6675c1f79af50c76b3d77026bcb93e77449d7609 Mon Sep 17 00:00:00 2001 From: ch3p4ll3 Date: Sun, 22 Oct 2023 13:04:31 +0200 Subject: [PATCH 2/3] new qbittorrent_manager --- .gitignore | 4 +- src/bot.py | 236 ++++++++++++++++++++----------------- src/qbittorrent_control.py | 131 -------------------- src/qbittorrent_manager.py | 98 +++++++++++++++ src/utils.py | 24 ++-- 5 files changed, 242 insertions(+), 251 deletions(-) delete mode 100644 src/qbittorrent_control.py create mode 100644 src/qbittorrent_manager.py diff --git a/.gitignore b/.gitignore index 72c2690..78fc413 100644 --- a/.gitignore +++ b/.gitignore @@ -163,4 +163,6 @@ cython_debug/ config.json *.session -*.session-journal \ No newline at end of file +*.session-journal + +*.sqlite \ No newline at end of file diff --git a/src/bot.py b/src/bot.py index 7be8683..6e319d0 100755 --- a/src/bot.py +++ b/src/bot.py @@ -8,7 +8,7 @@ import psutil from src import custom_filters -from src import qbittorrent_control +from src.qbittorrent_manager import QbittorrentManagement from apscheduler.schedulers.asyncio import AsyncIOScheduler from src.utils import torrent_finished, convert_size, convert_eta from src.config import BOT_CONFIGS @@ -23,10 +23,10 @@ ) scheduler = AsyncIOScheduler() -scheduler.add_job(torrent_finished, "interval", args=[app], seconds=10) +scheduler.add_job(torrent_finished, "interval", args=[app], seconds=60) -async def send_menu(message, chat) -> None: +async def send_menu(client: Client, message, chat) -> None: db_management.write_support("None", chat) buttons = [[InlineKeyboardButton("📝 List", "list")], [InlineKeyboardButton("➕ Add Magnet", "category#add_magnet"), @@ -42,15 +42,16 @@ async def send_menu(message, chat) -> None: [InlineKeyboardButton("📝 Modify Category", "select_category#modify_category")]] try: - await app.edit_message_text(chat, message, text="Qbittorrent Control", - reply_markup=InlineKeyboardMarkup(buttons)) + await client.edit_message_text(chat, message, text="Qbittorrent Control", + reply_markup=InlineKeyboardMarkup(buttons)) except MessageIdInvalid: - await app.send_message(chat, text="Qbittorrent Control", reply_markup=InlineKeyboardMarkup(buttons)) + await client.send_message(chat, text="Qbittorrent Control", reply_markup=InlineKeyboardMarkup(buttons)) -async def list_active_torrents(n, chat, message, callback, status_filter: str = None) -> None: - torrents = qbittorrent_control.get_torrent_info(status_filter=status_filter) +async def list_active_torrents(client: Client, n, chat, message, callback, status_filter: str = None) -> None: + with QbittorrentManagement() as qb: + torrents = qb.get_torrent_info(status_filter=status_filter) def render_categories_buttons(): return [ @@ -65,10 +66,10 @@ def render_categories_buttons(): if not torrents: buttons = [categories_buttons, [InlineKeyboardButton("🔙 Menu", "menu")]] try: - await app.edit_message_text(chat, message, "There are no torrents", - reply_markup=InlineKeyboardMarkup(buttons)) + await client.edit_message_text(chat, message, "There are no torrents", + reply_markup=InlineKeyboardMarkup(buttons)) except MessageIdInvalid: - await app.send_message(chat, "There are no torrents", reply_markup=InlineKeyboardMarkup(buttons)) + await client.send_message(chat, "There are no torrents", reply_markup=InlineKeyboardMarkup(buttons)) return buttons = [categories_buttons] @@ -80,9 +81,9 @@ def render_categories_buttons(): buttons.append([InlineKeyboardButton("🔙 Menu", "menu")]) try: - await app.edit_message_reply_markup(chat, message, reply_markup=InlineKeyboardMarkup(buttons)) + await client.edit_message_reply_markup(chat, message, reply_markup=InlineKeyboardMarkup(buttons)) except MessageIdInvalid: - await app.send_message(chat, "Qbittorrent Control", reply_markup=InlineKeyboardMarkup(buttons)) + await client.send_message(chat, "Qbittorrent Control", reply_markup=InlineKeyboardMarkup(buttons)) else: for key, i in enumerate(torrents): @@ -91,41 +92,41 @@ def render_categories_buttons(): buttons.append([InlineKeyboardButton("🔙 Menu", "menu")]) try: - await app.edit_message_reply_markup(chat, message, reply_markup=InlineKeyboardMarkup(buttons)) + await client.edit_message_reply_markup(chat, message, reply_markup=InlineKeyboardMarkup(buttons)) except MessageIdInvalid: - await app.send_message(chat, "Qbittorrent Control", reply_markup=InlineKeyboardMarkup(buttons)) + await client.send_message(chat, "Qbittorrent Control", reply_markup=InlineKeyboardMarkup(buttons)) @app.on_message(filters=filters.command("start")) async def start_command(client: Client, message: Message) -> None: """Start the bot.""" if message.from_user.id in [i.user_id for i in BOT_CONFIGS.users]: - await send_menu(message.id, message.chat.id) + await send_menu(client, message.id, message.chat.id) else: button = InlineKeyboardMarkup([[InlineKeyboardButton("Github", url="https://github.com/ch3p4ll3/QBittorrentBot/")]]) - await app.send_message(message.chat.id, "You are not authorized to use this bot", reply_markup=button) + await client.send_message(message.chat.id, "You are not authorized to use this bot", reply_markup=button) @app.on_message(filters=filters.command("stats")) async def stats_command(client: Client, message: Message) -> None: if message.from_user.id in [i.user_id for i in BOT_CONFIGS.users]: - txt = f"**============SYSTEM============**\n" \ - f"**CPU Usage:** {psutil.cpu_percent(interval=None)}%\n" \ - f"**CPU Temp:** {psutil.sensors_temperatures()['coretemp'][0].current}°C\n" \ - f"**Free Memory:** {convert_size(psutil.virtual_memory().available)} of " \ - f"{convert_size(psutil.virtual_memory().total)} ({psutil.virtual_memory().percent}%)\n" \ - f"**Disks usage:** {convert_size(psutil.disk_usage('/mnt').used)} of " \ - f"{convert_size(psutil.disk_usage('/mnt').total)} ({psutil.disk_usage('/mnt').percent}%)" + stats_text = f"**============SYSTEM============**\n" \ + f"**CPU Usage:** {psutil.cpu_percent(interval=None)}%\n" \ + f"**CPU Temp:** {psutil.sensors_temperatures()['coretemp'][0].current}°C\n" \ + f"**Free Memory:** {convert_size(psutil.virtual_memory().available)} of " \ + f"{convert_size(psutil.virtual_memory().total)} ({psutil.virtual_memory().percent}%)\n" \ + f"**Disks usage:** {convert_size(psutil.disk_usage('/mnt').used)} of " \ + f"{convert_size(psutil.disk_usage('/mnt').total)} ({psutil.disk_usage('/mnt').percent}%)" - await message.reply_text(txt) + await client.send_message(message.chat.id, stats_text) else: button = InlineKeyboardMarkup([[InlineKeyboardButton("Github", url="https://github.com/ch3p4ll3/QBittorrentBot/")]]) - await app.send_message(message.chat.id, "You are not authorized to use this bot", reply_markup=button) + await client.send_message(message.chat.id, "You are not authorized to use this bot", reply_markup=button) @app.on_callback_query(filters=custom_filters.add_category_filter) @@ -133,21 +134,23 @@ async def add_category_callback(client: Client, callback_query: CallbackQuery) - db_management.write_support("category_name", callback_query.from_user.id) button = InlineKeyboardMarkup([[InlineKeyboardButton("🔙 Menu", "menu")]]) try: - await app.edit_message_text(callback_query.from_user.id, callback_query.message.id, - "Send the category name", reply_markup=button) + await client.edit_message_text(callback_query.from_user.id, callback_query.message.id, + "Send the category name", reply_markup=button) except MessageIdInvalid: - await app.send_message(callback_query.from_user.id, "Send the category name", reply_markup=button) + await client.send_message(callback_query.from_user.id, "Send the category name", reply_markup=button) @app.on_callback_query(filters=custom_filters.select_category_filter) async def list_categories(client: Client, callback_query: CallbackQuery): buttons = [] - categories = qbittorrent_control.get_categories() + + with QbittorrentManagement() as qb: + categories = qb.get_categories() if categories is None: buttons.append([InlineKeyboardButton("🔙 Menu", "menu")]) - await app.edit_message_text(callback_query.from_user.id, callback_query.message.id, - "There are no categories", reply_markup=InlineKeyboardMarkup(buttons)) + await client.edit_message_text(callback_query.from_user.id, callback_query.message.id, + "There are no categories", reply_markup=InlineKeyboardMarkup(buttons)) return for key, i in enumerate(categories): @@ -156,21 +159,23 @@ async def list_categories(client: Client, callback_query: CallbackQuery): buttons.append([InlineKeyboardButton("🔙 Menu", "menu")]) try: - await app.edit_message_text(callback_query.from_user.id, callback_query.message.id, - "Choose a category:", reply_markup=InlineKeyboardMarkup(buttons)) + await client.edit_message_text(callback_query.from_user.id, callback_query.message.id, + "Choose a category:", reply_markup=InlineKeyboardMarkup(buttons)) except MessageIdInvalid: - await app.send_message(callback_query.from_user.id, "Choose a category:", - reply_markup=InlineKeyboardMarkup(buttons)) + await client.send_message(callback_query.from_user.id, "Choose a category:", + reply_markup=InlineKeyboardMarkup(buttons)) @app.on_callback_query(filters=custom_filters.remove_category_filter) async def remove_category_callback(client: Client, callback_query: CallbackQuery) -> None: buttons = [[InlineKeyboardButton("🔙 Menu", "menu")]] - qbittorrent_control.remove_category(data=callback_query.data.split("#")[1]) - await app.edit_message_text(callback_query.from_user.id, callback_query.message.id, - f"The category {callback_query.data.split('#')[1]} has been removed", - reply_markup=InlineKeyboardMarkup(buttons)) + with QbittorrentManagement() as qb: + qb.remove_category(data=callback_query.data.split("#")[1]) + + await client.edit_message_text(callback_query.from_user.id, callback_query.message.id, + f"The category {callback_query.data.split('#')[1]} has been removed", + reply_markup=InlineKeyboardMarkup(buttons)) @app.on_callback_query(filters=custom_filters.modify_category_filter) @@ -178,23 +183,24 @@ async def modify_category_callback(client: Client, callback_query: CallbackQuery buttons = [[InlineKeyboardButton("🔙 Menu", "menu")]] db_management.write_support(f"category_dir_modify#{callback_query.data.split('#')[1]}", callback_query.from_user.id) - await app.edit_message_text(callback_query.from_user.id, callback_query.message.id, - f"Send new path for category {callback_query.data.split('#')[1]}", - reply_markup=InlineKeyboardMarkup(buttons)) + await client.edit_message_text(callback_query.from_user.id, callback_query.message.id, + f"Send new path for category {callback_query.data.split('#')[1]}", + reply_markup=InlineKeyboardMarkup(buttons)) @app.on_callback_query(filters=custom_filters.category_filter) async def category(client: Client, callback_query: CallbackQuery) -> None: buttons = [] - categories = qbittorrent_control.get_categories() + with QbittorrentManagement() as qb: + categories = qb.get_categories() if categories is None: if "magnet" in callback_query.data: - await addmagnet_callback(client, callback_query) + await add_magnet_callback(client, callback_query) else: - await addtorrent_callback(client, callback_query) + await add_torrent_callback(client, callback_query) return @@ -205,79 +211,83 @@ async def category(client: Client, callback_query: CallbackQuery) -> None: buttons.append([InlineKeyboardButton("🔙 Menu", "menu")]) try: - await app.edit_message_text(callback_query.from_user.id, callback_query.message.id, - "Choose a category:", reply_markup=InlineKeyboardMarkup(buttons)) + await client.edit_message_text(callback_query.from_user.id, callback_query.message.id, + "Choose a category:", reply_markup=InlineKeyboardMarkup(buttons)) except MessageIdInvalid: - await app.send_message(callback_query.from_user.id, "Choose a category:", - reply_markup=InlineKeyboardMarkup(buttons)) + await client.send_message(callback_query.from_user.id, "Choose a category:", + reply_markup=InlineKeyboardMarkup(buttons)) @app.on_callback_query(filters=custom_filters.menu_filter) async def menu_callback(client: Client, callback_query: CallbackQuery) -> None: - await send_menu(callback_query.message.id, callback_query.from_user.id) + await send_menu(client, callback_query.message.id, callback_query.from_user.id) @app.on_callback_query(filters=custom_filters.list_filter) async def list_callback(client: Client, callback_query: CallbackQuery) -> None: - await list_active_torrents(0, callback_query.from_user.id, callback_query.message.id, + await list_active_torrents(client, 0, callback_query.from_user.id, callback_query.message.id, db_management.read_support(callback_query.from_user.id)) @app.on_callback_query(filters=custom_filters.list_by_status_filter) async def list_by_status_callback(client: Client, callback_query: CallbackQuery) -> None: status_filter = callback_query.data.split("#")[1] - await list_active_torrents(0, callback_query.from_user.id, callback_query.message.id, + await list_active_torrents(client,0, callback_query.from_user.id, callback_query.message.id, db_management.read_support(callback_query.from_user.id), status_filter=status_filter) @app.on_callback_query(filters=custom_filters.add_magnet_filter) -async def addmagnet_callback(client: Client, callback_query: CallbackQuery) -> None: +async def add_magnet_callback(client: Client, callback_query: CallbackQuery) -> None: db_management.write_support(f"magnet#{callback_query.data.split('#')[1]}", callback_query.from_user.id) - await app.answer_callback_query(callback_query.id, "Send a magnet link") + await client.answer_callback_query(callback_query.id, "Send a magnet link") @app.on_callback_query(filters=custom_filters.add_torrent_filter) -async def addtorrent_callback(client: Client, callback_query: CallbackQuery) -> None: +async def add_torrent_callback(client: Client, callback_query: CallbackQuery) -> None: db_management.write_support(f"torrent#{callback_query.data.split('#')[1]}", callback_query.from_user.id) - await app.answer_callback_query(callback_query.id, "Send a torrent file") + await client.answer_callback_query(callback_query.id, "Send a torrent file") @app.on_callback_query(filters=custom_filters.pause_all_filter) -async def pauseall_callback(client: Client, callback_query: CallbackQuery) -> None: - qbittorrent_control.pause_all() - await app.answer_callback_query(callback_query.id, "Paused all torrents") +async def pause_all_callback(client: Client, callback_query: CallbackQuery) -> None: + with QbittorrentManagement() as qb: + qb.pause_all() + await client.answer_callback_query(callback_query.id, "Paused all torrents") @app.on_callback_query(filters=custom_filters.resume_all_filter) -async def resumeall_callback(client: Client, callback_query: CallbackQuery) -> None: - qbittorrent_control.resume_all() - await app.answer_callback_query(callback_query.id, "Resumed all torrents") +async def resume_all_callback(client: Client, callback_query: CallbackQuery) -> None: + with QbittorrentManagement() as qb: + qb.resume_all() + await client.answer_callback_query(callback_query.id, "Resumed all torrents") @app.on_callback_query(filters=custom_filters.pause_filter) async def pause_callback(client: Client, callback_query: CallbackQuery) -> None: if callback_query.data.find("#") == -1: - await list_active_torrents(1, callback_query.from_user.id, callback_query.message.id, "pause") + await list_active_torrents(client, 1, callback_query.from_user.id, callback_query.message.id, "pause") else: - qbittorrent_control.pause(torrent_hash=callback_query.data.split("#")[1]) - await send_menu(callback_query.message.id, callback_query.from_user.id) + with QbittorrentManagement() as qb: + qb.pause(torrent_hash=callback_query.data.split("#")[1]) + await send_menu(client, callback_query.message.id, callback_query.from_user.id) @app.on_callback_query(filters=custom_filters.resume_filter) async def resume_callback(client: Client, callback_query: CallbackQuery) -> None: if callback_query.data.find("#") == -1: - await list_active_torrents(1, callback_query.from_user.id, callback_query.message.id, "resume") + await list_active_torrents(client, 1, callback_query.from_user.id, callback_query.message.id, "resume") else: - qbittorrent_control.resume(torrent_hash=callback_query.data.split("#")[1]) - await send_menu(callback_query.message.id, callback_query.from_user.id) + with QbittorrentManagement() as qb: + qb.resume(torrent_hash=callback_query.data.split("#")[1]) + await send_menu(client, callback_query.message.id, callback_query.from_user.id) @app.on_callback_query(filters=custom_filters.delete_one_filter) async def delete_callback(client: Client, callback_query: CallbackQuery) -> None: if callback_query.data.find("#") == -1: - await list_active_torrents(1, callback_query.from_user.id, callback_query.message.id, "delete_one") + await list_active_torrents(client, 1, callback_query.from_user.id, callback_query.message.id, "delete_one") else: @@ -286,28 +296,30 @@ async def delete_callback(client: Client, callback_query: CallbackQuery) -> None [InlineKeyboardButton("🗑 Delete torrent and data", f"delete_one_data#{callback_query.data.split('#')[1]}")], [InlineKeyboardButton("🔙 Menu", "menu")]] - await app.edit_message_reply_markup(callback_query.from_user.id, callback_query.message.id, - reply_markup=InlineKeyboardMarkup(buttons)) + await client.edit_message_reply_markup(callback_query.from_user.id, callback_query.message.id, + reply_markup=InlineKeyboardMarkup(buttons)) @app.on_callback_query(filters=custom_filters.delete_one_no_data_filter) async def delete_no_data_callback(client: Client, callback_query: CallbackQuery) -> None: if callback_query.data.find("#") == -1: - await list_active_torrents(1, callback_query.from_user.id, callback_query.message.id, "delete_one_no_data") + await list_active_torrents(client, 1, callback_query.from_user.id, callback_query.message.id, "delete_one_no_data") else: - qbittorrent_control.delete_one_no_data(torrent_hash=callback_query.data.split("#")[1]) - await send_menu(callback_query.message.id, callback_query.from_user.id) + with QbittorrentManagement() as qb: + qb.delete_one_no_data(torrent_hash=callback_query.data.split("#")[1]) + await send_menu(client, callback_query.message.id, callback_query.from_user.id) @app.on_callback_query(filters=custom_filters.delete_one_data_filter) async def delete_with_data_callback(client: Client, callback_query: CallbackQuery) -> None: if callback_query.data.find("#") == -1: - await list_active_torrents(1, callback_query.from_user.id, callback_query.message.id, "delete_one_data") + await list_active_torrents(client, 1, callback_query.from_user.id, callback_query.message.id, "delete_one_data") else: - qbittorrent_control.delete_one_data(torrent_hash=callback_query.data.split("#")[1]) - await send_menu(callback_query.message.id, callback_query.from_user.id) + with QbittorrentManagement() as qb: + qb.delete_one_data(torrent_hash=callback_query.data.split("#")[1]) + await send_menu(client, callback_query.message.id, callback_query.from_user.id) @app.on_callback_query(filters=custom_filters.delete_all_filter) @@ -315,27 +327,30 @@ async def delete_all_callback(client: Client, callback_query: CallbackQuery) -> buttons = [[InlineKeyboardButton("🗑 Delete all torrents", "delete_all_no_data")], [InlineKeyboardButton("🗑 Delete all torrents and data", "delete_all_data")], [InlineKeyboardButton("🔙 Menu", "menu")]] - await app.edit_message_reply_markup(callback_query.from_user.id, callback_query.message.id, - reply_markup=InlineKeyboardMarkup(buttons)) + await client.edit_message_reply_markup(callback_query.from_user.id, callback_query.message.id, + reply_markup=InlineKeyboardMarkup(buttons)) @app.on_callback_query(filters=custom_filters.delete_all_no_data_filter) async def delete_all_with_no_data_callback(client: Client, callback_query: CallbackQuery) -> None: - qbittorrent_control.delall_no_data() - await app.answer_callback_query(callback_query.id, "Deleted only torrents") - await send_menu(callback_query.message.id, callback_query.from_user.id) + with QbittorrentManagement() as qb: + qb.delete_all_no_data() + await client.answer_callback_query(callback_query.id, "Deleted only torrents") + await send_menu(client, callback_query.message.id, callback_query.from_user.id) @app.on_callback_query(filters=custom_filters.delete_all_data_filter) async def delete_all_with_data_callback(client: Client, callback_query: CallbackQuery) -> None: - qbittorrent_control.delall_data() - await app.answer_callback_query(callback_query.id, "Deleted All+Torrents") - await send_menu(callback_query.message.id, callback_query.from_user.id) + with QbittorrentManagement() as qb: + qb.delete_all_data() + await client.answer_callback_query(callback_query.id, "Deleted All+Torrents") + await send_menu(client, callback_query.message.id, callback_query.from_user.id) @app.on_callback_query(filters=custom_filters.torrentInfo_filter) async def torrent_info_callback(client: Client, callback_query: CallbackQuery) -> None: - torrent = qbittorrent_control.get_torrent_info(data=callback_query.data.split("#")[1]) + with QbittorrentManagement() as qb: + torrent = qb.get_torrent_info(data=callback_query.data.split("#")[1]) progress = torrent.progress * 100 text = "" @@ -370,11 +385,11 @@ async def torrent_info_callback(client: Client, callback_query: CallbackQuery) - [InlineKeyboardButton("🗑 Delete", f"delete_one#{callback_query.data.split('#')[1]}")], [InlineKeyboardButton("🔙 Menu", "menu")]] - await app.edit_message_text(callback_query.from_user.id, callback_query.message.id, text=text, - reply_markup=InlineKeyboardMarkup(buttons)) + await client.edit_message_text(callback_query.from_user.id, callback_query.message.id, text=text, + reply_markup=InlineKeyboardMarkup(buttons)) -@app.on_message() +@app.on_message(filters=~filters.me) async def on_text(client: Client, message: Message) -> None: action = db_management.read_support(message.from_user.id) @@ -382,13 +397,16 @@ async def on_text(client: Client, message: Message) -> None: if message.text.startswith("magnet:?xt"): magnet_link = message.text.split("\n") category = db_management.read_support(message.from_user.id).split("#")[1] - qbittorrent_control.add_magnet(magnet_link=magnet_link, - category=category) - await send_menu(message.id, message.from_user.id) + + with QbittorrentManagement() as qb: + qb.add_magnet(magnet_link=magnet_link, + category=category) + + await send_menu(client, message.id, message.from_user.id) db_management.write_support("None", message.from_user.id) else: - await message.reply_text("This magnet link is invalid! Retry") + await client.send_message(message.from_user.id, "This magnet link is invalid! Retry") elif "torrent" in action and message.document: if ".torrent" in message.document.file_name: @@ -396,34 +414,38 @@ async def on_text(client: Client, message: Message) -> None: name = f"{tempdir}/{message.document.file_name}" category = db_management.read_support(message.from_user.id).split("#")[1] await message.download(name) - qbittorrent_control.add_torrent(file_name=name, - category=category) - await send_menu(message.id, message.from_user.id) + + with QbittorrentManagement() as qb: + qb.add_torrent(file_name=name, + category=category) + await send_menu(client, message.id, message.from_user.id) db_management.write_support("None", message.from_user.id) else: - await message.reply_text("This is not a torrent file! Retry") + await client.send_message(message.from_user.id, "This is not a torrent file! Retry") elif action == "category_name": db_management.write_support(f"category_dir#{message.text}", message.from_user.id) - await message.reply_text(f"now send me the path for the category {message.text}") + await client.send_message(message.from_user.id, f"now send me the path for the category {message.text}") elif "category_dir" in action: if os.path.exists(message.text): name = db_management.read_support(message.from_user.id).split("#")[1] if "modify" in action: - qbittorrent_control.edit_category(name=name, - save_path=message.text) - await send_menu(message.id, message.from_user.id) + with QbittorrentManagement() as qb: + qb.edit_category(name=name, + save_path=message.text) + await send_menu(client, message.id, message.from_user.id) return - qbittorrent_control.create_category(name=name, - save_path=message.text) - await send_menu(message.id, message.from_user.id) + with QbittorrentManagement() as qb: + qb.create_category(name=name, + save_path=message.text) + await send_menu(client, message.id, message.from_user.id) else: - await message.reply_text("The path entered does not exist! Retry") + await client.send_message(message.from_user.id, "The path entered does not exist! Retry") else: - await message.reply_text("The command does not exist") + await client.send_message(message.from_user.id, "The command does not exist") diff --git a/src/qbittorrent_control.py b/src/qbittorrent_control.py deleted file mode 100644 index 0e2356d..0000000 --- a/src/qbittorrent_control.py +++ /dev/null @@ -1,131 +0,0 @@ -import qbittorrentapi -from src.config import BOT_CONFIGS - - -def qbittorrent_login(func): - def wrapper(*args, **kwargs): - - qbt_client = qbittorrentapi.Client( - host=f'http://{BOT_CONFIGS.qbittorrent.ip.network_address}:' - f'{BOT_CONFIGS.qbittorrent.port}', - username=BOT_CONFIGS.qbittorrent.user, - password=BOT_CONFIGS.qbittorrent.password) - - try: - qbt_client.auth_log_in() - except qbittorrentapi.LoginFailed as e: - print(e) - - resp = func(qbt_client, *args, **kwargs) - - qbt_client.auth_log_out() - - return resp - - return wrapper - - -@qbittorrent_login -def add_magnet(qbt_client, magnet_link: str, category: str = None) -> None: - cat = category - if cat == "None": - cat = None - - if category is not None: - qbt_client.torrents_add(urls=magnet_link, category=cat) - else: - qbt_client.torrents_add(urls=magnet_link) - - -@qbittorrent_login -def add_torrent(qbt_client, file_name: str, category: str = None) -> None: - cat = category - if cat == "None": - cat = None - - try: - if category is not None: - qbt_client.torrents_add(torrent_files=file_name, category=cat) - else: - qbt_client.torrents_add(torrent_files=file_name) - - except qbittorrentapi.exceptions.UnsupportedMediaType415Error: - pass - - -@qbittorrent_login -def resume_all(qbt_client) -> None: - qbt_client.torrents.resume.all() - - -@qbittorrent_login -def pause_all(qbt_client) -> None: - qbt_client.torrents.pause.all() - - -@qbittorrent_login -def resume(qbt_client, torrent_hash: str) -> None: - qbt_client.torrents_resume(torrent_hashes=torrent_hash) - - -@qbittorrent_login -def pause(qbt_client, torrent_hash: str) -> None: - qbt_client.torrents_pause(torrent_hashes=torrent_hash) - - -@qbittorrent_login -def delete_one_no_data(qbt_client, torrent_hash: str) -> None: - qbt_client.torrents_delete(delete_files=False, - torrent_hashes=torrent_hash) - - -@qbittorrent_login -def delete_one_data(qbt_client, torrent_hash: str) -> None: - qbt_client.torrents_delete(delete_files=True, - torrent_hashes=torrent_hash) - - -@qbittorrent_login -def delall_no_data(qbt_client) -> None: - for i in qbt_client.torrents_info(): - qbt_client.torrents_delete(delete_files=False, hashes=i.hash) - - -@qbittorrent_login -def delall_data(qbt_client) -> None: - for i in qbt_client.torrents_info(): - qbt_client.torrents_delete(delete_files=True, hashes=i.hash) - - -@qbittorrent_login -def get_categories(qbt_client): - categories = qbt_client.torrent_categories.categories - if len(categories) > 0: - return categories - - else: - return - - -@qbittorrent_login -def get_torrent_info(qbt_client, data: str = None, status_filter: str = None, ): - if data is None: - return qbt_client.torrents_info(status_filter=status_filter) - return next(iter(qbt_client.torrents_info(status_filter=status_filter, torrent_hashes=data)), None) - - -@qbittorrent_login -def edit_category(qbt_client, name: str, save_path: str) -> None: - qbt_client.torrents_edit_category(name=name, - save_path=save_path) - - -@qbittorrent_login -def create_category(qbt_client, name: str, save_path: str) -> None: - qbt_client.torrents_create_category(name=name, - save_path=save_path) - - -@qbittorrent_login -def remove_category(qbt_client, data: str) -> None: - qbt_client.torrents_remove_categories(categories=data) diff --git a/src/qbittorrent_manager.py b/src/qbittorrent_manager.py new file mode 100644 index 0000000..b066e22 --- /dev/null +++ b/src/qbittorrent_manager.py @@ -0,0 +1,98 @@ +import qbittorrentapi +from src.config import BOT_CONFIGS + +from typing import Union, List + + +class QbittorrentManagement: + def __init__(self): + self.qbt_client = qbittorrentapi.Client( + host=f'http://{BOT_CONFIGS.qbittorrent.ip.network_address}:' + f'{BOT_CONFIGS.qbittorrent.port}', + username=BOT_CONFIGS.qbittorrent.user, + password=BOT_CONFIGS.qbittorrent.password) + + def __enter__(self): + try: + self.qbt_client.auth_log_in() + except qbittorrentapi.LoginFailed as e: + print(e) + + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + self.qbt_client.auth_log_out() + + def add_magnet(self, magnet_link: Union[str, List[str]], category: str = None) -> None: + if category == "None": + category = None + + if category is not None: + self.qbt_client.torrents_add(urls=magnet_link, category=category) + else: + self.qbt_client.torrents_add(urls=magnet_link) + + def add_torrent(self, file_name: str, category: str = None) -> None: + if category == "None": + category = None + + try: + if category is not None: + self.qbt_client.torrents_add(torrent_files=file_name, category=category) + else: + self.qbt_client.torrents_add(torrent_files=file_name) + + except qbittorrentapi.exceptions.UnsupportedMediaType415Error: + pass + + def resume_all(self) -> None: + self.qbt_client.torrents.resume.all() + + def pause_all(self) -> None: + self.qbt_client.torrents.pause.all() + + def resume(self, torrent_hash: str) -> None: + self.qbt_client.torrents_resume(torrent_hashes=torrent_hash) + + def pause(self, torrent_hash: str) -> None: + self.qbt_client.torrents_pause(torrent_hashes=torrent_hash) + + def delete_one_no_data(self, torrent_hash: str) -> None: + self.qbt_client.torrents_delete(delete_files=False, + torrent_hashes=torrent_hash) + + def delete_one_data(self, torrent_hash: str) -> None: + self.qbt_client.torrents_delete(delete_files=True, + torrent_hashes=torrent_hash) + + def delete_all_no_data(self) -> None: + for i in self.qbt_client.torrents_info(): + self.qbt_client.torrents_delete(delete_files=False, hashes=i.hash) + + def delete_all_data(self) -> None: + for i in self.qbt_client.torrents_info(): + self.qbt_client.torrents_delete(delete_files=True, hashes=i.hash) + + def get_categories(self): + categories = self.qbt_client.torrent_categories.categories + if len(categories) > 0: + return categories + + else: + return + + def get_torrent_info(self, data: str = None, status_filter: str = None, ): + if data is None: + return self.qbt_client.torrents_info(status_filter=status_filter) + return next(iter(self.qbt_client.torrents_info(status_filter=status_filter, torrent_hashes=data)), None) + + def edit_category(self, name: str, save_path: str) -> None: + self.qbt_client.torrents_edit_category(name=name, + save_path=save_path) + + def create_category(self, name: str, save_path: str) -> None: + self.qbt_client.torrents_create_category(name=name, + save_path=save_path) + + def remove_category(self, data: str) -> None: + self.qbt_client.torrents_remove_categories(categories=data) diff --git a/src/utils.py b/src/utils.py index 02c1cb0..2e83eef 100644 --- a/src/utils.py +++ b/src/utils.py @@ -3,22 +3,22 @@ from pyrogram.errors.exceptions import UserIsBlocked from src import db_management -from src import qbittorrent_control +from src.qbittorrent_manager import QbittorrentManagement from src.config import BOT_CONFIGS async def torrent_finished(app): - for i in qbittorrent_control.get_torrent_info(): - if i.progress == 1 and \ - db_management.read_completed_torrents(i.hash) is None: - - for user in BOT_CONFIGS.users: - if user.notify: - try: - await app.send_message(user.user_id, f"torrent {i.name} has finished downloading!") - except UserIsBlocked: - pass - db_management.write_completed_torrents(i.hash) + with QbittorrentManagement() as qb: + for i in qb.get_torrent_info(status_filter="completed"): + if db_management.read_completed_torrents(i.hash) is None: + + for user in BOT_CONFIGS.users: + if user.notify: + try: + await app.send_message(user.user_id, f"torrent {i.name} has finished downloading!") + except UserIsBlocked: + pass + db_management.write_completed_torrents(i.hash) def convert_size(size_bytes) -> str: From 3a543af40f00eb63b7f6f84990c0075e3e9217d7 Mon Sep 17 00:00:00 2001 From: ch3p4ll3 Date: Sun, 29 Oct 2023 11:37:46 +0100 Subject: [PATCH 3/3] update requirements --- main.py | 2 +- requirements.txt | 7 ++++--- src/{bot.py => qbittorrent_bot.py} | 0 3 files changed, 5 insertions(+), 4 deletions(-) rename src/{bot.py => qbittorrent_bot.py} (100%) mode change 100755 => 100644 diff --git a/main.py b/main.py index 9edf306..f1b163b 100644 --- a/main.py +++ b/main.py @@ -1,4 +1,4 @@ -from src.bot import app, scheduler +from src.qbittorrent_bot import app, scheduler if __name__ == '__main__': scheduler.start() diff --git a/requirements.txt b/requirements.txt index 878ca43..6b75ca4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,7 @@ annotated-types==0.6.0 APScheduler==3.10.4 async-lru==2.0.4 certifi==2023.7.22 -charset-normalizer==3.3.0 +charset-normalizer==3.3.1 idna==3.4 packaging==23.2 pony==0.7.17 @@ -14,11 +14,12 @@ Pyrogram==2.0.106 PySocks==1.7.1 pytz==2023.3.post1 pytz-deprecation-shim==0.1.0.post0 -qbittorrent-api==2023.9.53 +qbittorrent-api==2023.10.54 requests==2.31.0 six==1.16.0 TgCrypto==1.2.5 typing_extensions==4.8.0 tzdata==2023.3 -tzlocal==5.1 +tzlocal==5.2 urllib3==2.0.7 +uvloop==0.19.0 diff --git a/src/bot.py b/src/qbittorrent_bot.py old mode 100755 new mode 100644 similarity index 100% rename from src/bot.py rename to src/qbittorrent_bot.py