diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 04f0fa4..1bfe951 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -49,7 +49,7 @@ repos: language_version: python3 args: [--line-length, '130'] - repo: https://github.com/PyCQA/flake8 - rev: 7.1.1 + rev: 7.1.2 hooks: - id: flake8 args: [--config=.flake8] diff --git a/CHANGELOG b/CHANGELOG index 2047959..c01466e 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,9 @@ -# Requirements Updated -qbittorrent-api==2025.2.0 -humanize==4.12.1 +# New Updates +- Removes deprecated cross-seed command/function (#758) +- Adds ability to tag stalled downloads (#757) +- Adds ability to customize a list of status to ignore during rem_unregistered command (#756) -**Full Changelog**: https://github.com/StuffAnThings/qbit_manage/compare/v4.1.17...v4.1.18 +# Bug Fixes +- Fixes bug in Windows during category updates (#755) + +**Full Changelog**: https://github.com/StuffAnThings/qbit_manage/compare/v4.1.18...v4.2.0 diff --git a/README.md b/README.md index 127b68a..b5ad7cc 100755 --- a/README.md +++ b/README.md @@ -15,7 +15,6 @@ This is a program used to manage your qBittorrent instance such as: * Apply category based on `save_path` to uncategorized torrents in category's `save_path` * Change categories based on current category (`cat_change`) * Remove unregistered torrents (delete data & torrent if it is not being cross-seeded, otherwise it will just remove the torrent) -* Automatically add [cross-seed](https://github.com/cross-seed/cross-seed) torrents in paused state. **\*Note: cross-seed now allows for torrent injections directly to qBit, making this feature rarely needed/used.\*** * Recheck paused torrents sorted by lowest size and resume if completed * Remove orphaned files from your root directory that are not referenced by qBittorrent * Tag any torrents that have no hard links outside the root folder (for multi-file torrents the largest file is used) diff --git a/SUPPORTED_VERSIONS.json b/SUPPORTED_VERSIONS.json index b4e2f3b..90a92cc 100644 --- a/SUPPORTED_VERSIONS.json +++ b/SUPPORTED_VERSIONS.json @@ -1,7 +1,7 @@ { "master": { - "qbit": "v5.0.3", - "qbitapi": "2024.12.71" + "qbit": "v5.0.4", + "qbitapi": "2025.2.0" }, "develop": { "qbit": "v5.0.4", diff --git a/VERSION b/VERSION index 60623b5..6aba2b2 100755 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -4.1.18 +4.2.0 diff --git a/config/config.yml.sample b/config/config.yml.sample index 94f419f..18332f4 100755 --- a/config/config.yml.sample +++ b/config/config.yml.sample @@ -1,12 +1,12 @@ # This is an example configuration file that documents all the options. # It will need to be modified for your specific use case. +# These are not default values. You MUST review the config settings and properly configure this EXAMPLE file. # Please refer to the link below for more details on how to set up the configuration file # https://github.com/StuffAnThings/qbit_manage/wiki/Config-Setup commands: # The commands defined below will IGNORE any commands used in command line and docker env variables. dry_run: True - cross_seed: False recheck: False cat_update: False tag_update: False @@ -36,16 +36,18 @@ settings: share_limits_min_seeding_time_tag: MinSeedTimeNotReached # Tag to be added to torrents that have not yet reached the minimum seeding time share_limits_min_num_seeds_tag: MinSeedsNotMet # Tag to be added to torrents that have not yet reached the minimum number of seeds share_limits_last_active_tag: LastActiveLimitNotReached # Tag to be added to torrents that have not yet reached the last active limit - cross_seed_tag: cross-seed # Will set the tag of any torrents that are added by cross-seed command cat_filter_completed: True # Filters for completed torrents only when running cat_update command share_limits_filter_completed: True # Filters for completed torrents only when running share_limits command tag_nohardlinks_filter_completed: True # Filters for completed torrents only when running tag_nohardlinks command cat_update_all: True # Checks and updates all torrent categories if set to True when running cat_update command, otherwise only update torrents that are uncategorized disable_qbt_default_share_limits: True # Allows QBM to handle share limits by disabling qBittorrents default Share limits. Only active when the share_limits command is set to True + tag_stalled_torrents: True # Tags any downloading torrents that are stalled with the `stalledDL` tag when running the tag_update command + rem_unregistered_ignore_list: # Ignores a list of words found in the status of the tracker when running rem_unregistered command and will not remove the torrent if matched + - example placeholder words + - ignore if found directory: # Do not remove these - # Cross-seed var: # Output directory of cross-seed # root_dir var: # Root downloads directory used to check for orphaned files, noHL, and RecycleBin. # remote_dir var: # Path of docker host mapping of root_dir. # remote_dir must be set if you're running qbit_manage locally and qBittorrent/cross_seed is in a docker @@ -53,12 +55,11 @@ directory: # recycle_bin var: # Path of the RecycleBin folder. Default location is set to remote_dir/.RecycleBin # torrents_dir var: # Path of the your qbittorrent torrents directory. Required for `save_torrents` attribute in recyclebin # orphaned_dir var: # Path of the the Orphaned Data folder. This is similar to RecycleBin, but only for orphaned data. - cross_seed: "/your/path/here/" - root_dir: "/data/torrents/" - remote_dir: "/mnt/user/data/torrents/" - recycle_bin: "/mnt/user/data/torrents/.RecycleBin" - torrents_dir: "/qbittorrent/data/BT_backup" - orphaned_dir: "/data/torrents/orphaned_data" + root_dir: "/path/to/torrents/" + remote_dir: "/host/path/to/torrents/ifdocker/torrents/" + recycle_bin: "/path/to/.RecycleBin" + torrents_dir: "/path/to/qbitAppData/BT_backup" + orphaned_dir: "/path/to/orphaned_data" cat: # Category & Path Parameters @@ -66,17 +67,15 @@ cat: # If you want to leave a save_path as uncategorized you can use the key 'Uncategorized' as the name of the category. # You can use Unix filename pattern matching as well when specifying the save_path # : # Path of your save directory. - movies: "/data/torrents/Movies" - tv: "/data/torrents/TV" + somecategory: "/path/to/torrents/somecategory" + anothercategory: "/path/to/torrents/anothercategory" cat_change: # This moves all the torrents from one category to another category. This executes on --cat-update # WARNING: if the paths are different and Default Torrent Management Mode is set to automatic the files could be moved !!! # : - Radarr-HD.cross-seed: movies-hd - Radarr-UHD.cross-seed: movies-uhd - movies-hd.cross-seed: movies-hd - movies-uhd.cross-seed: movies-uhd + CatA.cross-seed: CatA + CatB.cross-seed: CatB tracker: # Mandatory @@ -311,7 +310,6 @@ webhooks: run_start: notifiarr run_end: apprise function: - cross_seed: https://mywebhookurl.com/qbt_manage recheck: notifiarr cat_update: apprise tag_update: notifiarr diff --git a/docs/Commands.md b/docs/Commands.md index 53a8a38..e5a7076 100644 --- a/docs/Commands.md +++ b/docs/Commands.md @@ -7,7 +7,6 @@ | `-sd` or `--startup-delay` | QBT_STARTUP_DELAY | N/A | Set delay in seconds on the first run of a schedule (Default set to 0) | 0 | | `-c CONFIG` or `--config-file CONFIG` | QBT_CONFIG | N/A | This is used if you want to use a different name for your config.yml. `Example: tv.yml` | config.yml | | `-lf LOGFILE,` or `--log-file LOGFILE,` | QBT_LOGFILE | N/A | This is used if you want to use a different name for your log file. `Example: tv.log` | activity.log | -| `-cs` or `--cross-seed` | QBT_CROSS_SEED | cross_seed | Use this after running [cross-seed script](https://github.com/mmgoodnow/cross-seed) to add torrents from the cross-seed output folder to qBittorrent | False | | `-re` or `--recheck` | QBT_RECHECK | recheck | Recheck paused torrents sorted by lowest size. Resume if Completed. | False | | `-cu` or `--cat-update` | QBT_CAT_UPDATE | cat_update | Use this if you would like to update your categories or move from one category to another. | False | | `-tu` or `--tag-update` | QBT_TAG_UPDATE | tag_update | Use this if you would like to update your tags and/or set seed goals/limit upload speed by tag. (Only adds tags to untagged torrents) | False | diff --git a/docs/Config-Setup.md b/docs/Config-Setup.md index 4f8dfb4..4d27623 100644 --- a/docs/Config-Setup.md +++ b/docs/Config-Setup.md @@ -52,12 +52,13 @@ This section defines any settings defined in the configuration. | `share_limits_min_seeding_time_tag` | Will add this tag when applying share limits to torrents that have not yet reached the minimum seeding time (Used in `--share-limits`) | MinSeedTimeNotReached |
| | `share_limits_min_num_seeds_tag` | Will add this tag when applying share limits to torrents that have not yet reached the minimum number of seeds (Used in `--share-limits`) | MinSeedsNotMet |
| | `share_limits_last_active_tag` | Will add this tag when applying share limits to torrents that have not yet reached the last active limit (Used in `--share-limits`) | LastActiveLimitNotReached |
| -| `cross_seed_tag` | When running `--cross-seed` function, it will update any added cross-seed torrents with this tag. | cross-seed |
| | `cat_filter_completed` | When running `--cat-update` function, it will filter for completed torrents only. | True |
| | `share_limits_filter_completed` | When running `--share-limits` function, it will filter for completed torrents only. | True |
| | `tag_nohardlinks_filter_completed` | When running `--tag-nohardlinks` function, , it will filter for completed torrents only. | True |
| | `cat_update_all` | When running `--cat-update` function, it will check and update all torrents categories, otherwise it will only update uncategorized torrents. | True |
| | `disable_qbt_default_share_limits` | When running `--share-limits` function, it allows QBM to handle share limits by disabling qBittorrents default Share limits. | True |
| +| `tag_stalled_torrents` | Tags any downloading torrents that are stalled with the `stalledDL` tag when running the tag_update command | True |
| +| `rem_unregistered_ignore_list` | Ignores a list of words found in the status of the tracker when running rem_unregistered command and will not remove the torrent if matched | |
| ## **directory:** @@ -66,7 +67,6 @@ This section defines the directories that qbit_manage will be looking into for v | Variable | Definition | Required | | :------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------ | -| `cross_seed` | Output directory of cross-seed, originally the application [cross-seed](https://github.com/mmgoodnow/cross-seed) was incapable of injecting cross-seed torrent into qB, this was built to inject them for the application. This is no longer required if you're using injects with that software. However, you can find other uses for this as it is more of a watch directory now. | QBT_CROSS_SEED | | `root_dir` | Root downloads directory used to check for orphaned files, noHL, and remove unregistered. This directory is where you place all your downloads. This will need to be how qB views the directory where it places the downloads. This is required if you're using qbit_managee and/or qBittorrent within a container. | QBT_REM_ORPHANED / QBT_TAG_NOHARDLINKS / QBT_REM_UNREGISTERED | | `remote_dir` | Path of docker host mapping of root_dir, this must be set if you're running qbit_manage locally (not required if running qbit_manage in a container) and qBittorrent/cross_seed is in a docker. Essentially this is where your downloads are being kept on the host. |
| | `recycle_bin` | Path of the RecycleBin folder. Default location is set to `remote_dir/.RecycleBin`. All files in this folder will be cleaned up based on your recycle bin settings. |
| @@ -237,7 +237,6 @@ Provide webhook notifications based on event triggers | [error](#error-notifications) | When errors occur during the run | N/A |
| | [run_start](#run-start-notifications) | At the beginning of every run | N/A |
| | [run_end](#run-end-notifications) | At the end of every run | N/A |
| -| [cross_seed](#cross-seed-notifications) | During the cross-seed function | N/A |
| | [recheck](#recheck-notifications) | During the recheck function | N/A |
| | [cat_update](#category-update-notifications) | During the category update function | N/A |
| | [tag_update](#tag-update-notifications) | During the tag update function | N/A |
| @@ -309,36 +308,6 @@ Payload will be sent at the end of the run } ``` -### **Cross-Seed Notifications** - -Payload will be sent when adding a cross-seed torrent to qBittorrent if the original torrent is complete - -```yaml -{ - "function": "cross_seed", // Webhook Trigger keyword - "title": str, // Title of the Payload - "body": str, // Message of the Payload - "torrents": [str], // List of Torrent Names - "torrent_category": str, // Torrent Category - "torrent_save_path": str, // Torrent Download directory - "torrent_tag": "cross-seed", // Total Torrents Added - "torrent_tracker": str // Torrent Tracker -} -``` - -Payload will be sent when there are existing torrents found that are missing the cross-seed tag - -```yaml -{ - "function": "tag_cross_seed", // Webhook Trigger keyword - "title": str, // Title of the Payload - "body": str, // Message of the Payload - "torrents": [str], // List of Torrent Names - "torrent_category": str, // Torrent Category - "torrent_tag": "cross-seed", // Tag Added - "torrent_tracker": str // Torrent Tracker -} -``` ### **Recheck Notifications** diff --git a/docs/Docker-Installation.md b/docs/Docker-Installation.md index e4dffc0..3235169 100644 --- a/docs/Docker-Installation.md +++ b/docs/Docker-Installation.md @@ -38,7 +38,6 @@ services: - QBT_SCHEDULE=1440 - QBT_CONFIG=/config/config.yml - QBT_LOGFILE=activity.log - - QBT_CROSS_SEED=false - QBT_RECHECK=false - QBT_CAT_UPDATE=false - QBT_TAG_UPDATE=false diff --git a/modules/config.py b/modules/config.py index 9652b64..aa16759 100755 --- a/modules/config.py +++ b/modules/config.py @@ -22,7 +22,6 @@ from modules.webhooks import Webhooks logger = util.logger COMMANDS = [ - "cross_seed", "recheck", "cat_update", "tag_update", @@ -91,7 +90,6 @@ class Config: logger.debug(f" --debug (QBT_DEBUG): {args['debug']}") logger.debug(f" --trace (QBT_TRACE): {args['trace']}") logger.separator("CONFIG OVERRIDE RUN COMMDANDS", space=False, border=False, loglevel="DEBUG") - logger.debug(f" --cross-seed (QBT_CROSS_SEED): {self.commands['cross_seed']}") logger.debug(f" --recheck (QBT_RECHECK): {self.commands['recheck']}") logger.debug(f" --cat-update (QBT_CAT_UPDATE): {self.commands['cat_update']}") logger.debug(f" --tag-update (QBT_TAG_UPDATE): {self.commands['tag_update']}") @@ -119,7 +117,6 @@ class Config: logger.debug(f" --debug (QBT_DEBUG): {args['debug']}") logger.debug(f" --trace (QBT_TRACE): {args['trace']}") logger.separator("DOCKER ENV RUN COMMANDS", space=False, border=False, loglevel="DEBUG") - logger.debug(f" --cross-seed (QBT_CROSS_SEED): {args['cross_seed']}") logger.debug(f" --recheck (QBT_RECHECK): {args['recheck']}") logger.debug(f" --cat-update (QBT_CAT_UPDATE): {args['cat_update']}") logger.debug(f" --tag-update (QBT_TAG_UPDATE): {args['tag_update']}") @@ -175,7 +172,6 @@ class Config: temp["function"][attr] = {} temp["function"][attr] = None - hooks("cross_seed") hooks("recheck") hooks("cat_update") hooks("tag_update") @@ -218,7 +214,6 @@ class Config: "share_limits_last_active_tag": self.util.check_for_attribute( self.data, "share_limits_last_active_tag", parent="settings", default="LastActiveLimitNotReached" ), - "cross_seed_tag": self.util.check_for_attribute(self.data, "cross_seed_tag", parent="settings", default="cross-seed"), "cat_filter_completed": self.util.check_for_attribute( self.data, "cat_filter_completed", parent="settings", var_type="bool", default=True ), @@ -237,6 +232,12 @@ class Config: "disable_qbt_default_share_limits": self.util.check_for_attribute( self.data, "disable_qbt_default_share_limits", parent="settings", var_type="bool", default=True ), + "tag_stalled_torrents": self.util.check_for_attribute( + self.data, "tag_stalled_torrents", parent="settings", var_type="bool", default=True + ), + "rem_unregistered_ignore_list": self.util.check_for_attribute( + self.data, "rem_unregistered_ignore_list", parent="settings", var_type="upper_list", default=[] + ), } self.tracker_error_tag = self.settings["tracker_error_tag"] @@ -246,12 +247,10 @@ class Config: self.share_limits_min_seeding_time_tag = self.settings["share_limits_min_seeding_time_tag"] self.share_limits_min_num_seeds_tag = self.settings["share_limits_min_num_seeds_tag"] self.share_limits_last_active_tag = self.settings["share_limits_last_active_tag"] - self.cross_seed_tag = self.settings["cross_seed_tag"] self.default_ignore_tags = [ self.nohardlinks_tag, self.tracker_error_tag, - self.cross_seed_tag, self.share_limits_min_seeding_time_tag, self.share_limits_min_num_seeds_tag, self.share_limits_last_active_tag, @@ -262,7 +261,6 @@ class Config: self.util.overwrite_attributes(self.settings, "settings") default_function = { - "cross_seed": None, "recheck": None, "cat_update": None, "tag_update": None, @@ -662,7 +660,7 @@ class Config: ), "", ) - if self.commands["cross_seed"] or self.commands["tag_nohardlinks"] or self.commands["rem_orphaned"]: + if self.commands["tag_nohardlinks"] or self.commands["rem_orphaned"]: self.remote_dir = self.util.check_for_attribute( self.data, "remote_dir", @@ -685,12 +683,6 @@ class Config: ) if not self.remote_dir: self.remote_dir = self.root_dir - if self.commands["cross_seed"]: - self.cross_seed_dir = self.util.check_for_attribute(self.data, "cross_seed", parent="directory", var_type="path") - else: - self.cross_seed_dir = self.util.check_for_attribute( - self.data, "cross_seed", parent="directory", default_is_none=True - ) if self.commands["rem_orphaned"]: if "orphaned_dir" in self.data["directory"] and self.data["directory"]["orphaned_dir"] is not None: default_orphaned = os.path.join( diff --git a/modules/core/cross_seed.py b/modules/core/cross_seed.py deleted file mode 100644 index 11e1740..0000000 --- a/modules/core/cross_seed.py +++ /dev/null @@ -1,157 +0,0 @@ -import os -from collections import Counter - -from modules import util -from modules.torrent_hash_generator import TorrentHashGenerator - -logger = util.logger - - -class CrossSeed: - def __init__(self, qbit_manager): - self.qbt = qbit_manager - self.config = qbit_manager.config - self.client = qbit_manager.client - self.stats_added = 0 - self.stats_tagged = 0 - self.cross_seed_tag = qbit_manager.config.cross_seed_tag - - self.torrents_updated = [] # List of torrents added by cross-seed - self.notify_attr = [] # List of single torrent attributes to send to notifiarr - - self.cross_seed() - - def cross_seed(self): - """Move torrents from cross seed directory to correct save directory.""" - logger.separator("Checking for Cross-Seed Torrents", space=False, border=False) - # List of categories for all torrents moved - categories = [] - - # Only get torrent files - cs_files = [f for f in os.listdir(self.config.cross_seed_dir) if f.endswith("torrent")] - dir_cs = self.config.cross_seed_dir - dir_cs_out = os.path.join(dir_cs, "qbit_manage_added") - os.makedirs(dir_cs_out, exist_ok=True) - dir_cs_err = os.path.join(dir_cs, "qbit_manage_error") - os.makedirs(dir_cs_err, exist_ok=True) - for file in cs_files: - tr_name = file.split("]", 2)[2].split(".torrent")[0] - t_tracker = file.split("]", 2)[1][1:] - # Substring Key match in dictionary (used because t_name might not match exactly with self.qbt.torrentinfo key) - # Returned the dictionary of filtered item - torrentdict_file = dict(filter(lambda item: tr_name in item[0], self.qbt.torrentinfo.items())) - src = os.path.join(dir_cs, file) - file_cs_out = os.path.join(dir_cs_out, file) - file_cs_err = os.path.join(dir_cs_err, file) - if torrentdict_file: - # Get the exact torrent match name from self.qbt.torrentinfo - t_name = next(iter(torrentdict_file)) - dest = os.path.join(self.qbt.torrentinfo[t_name]["save_path"], "") - category = self.qbt.torrentinfo[t_name].get("Category", self.qbt.get_category(dest)[0]) - # Only add cross-seed torrent if original torrent is complete - if self.qbt.torrentinfo[t_name]["is_complete"]: - categories.append(category) - body = [] - body += logger.print_line( - f"{'Not Adding' if self.config.dry_run else 'Adding'} to qBittorrent:", self.config.loglevel - ) - body += logger.print_line(logger.insert_space(f"Torrent Name: {t_name}", 3), self.config.loglevel) - body += logger.print_line(logger.insert_space(f"Category: {category}", 7), self.config.loglevel) - body += logger.print_line(logger.insert_space(f"Save_Path: {dest}", 6), self.config.loglevel) - body += logger.print_line(logger.insert_space(f"Tracker: {t_tracker}", 8), self.config.loglevel) - attr = { - "function": "cross_seed", - "title": "Adding New Cross-Seed Torrent", - "body": "\n".join(body), - "torrents": [t_name], - "torrent_category": category, - "torrent_save_path": dest, - "torrent_tag": self.cross_seed_tag, - "torrent_tracker": t_tracker, - } - self.notify_attr.append(attr) - self.torrents_updated.append(t_name) - self.stats_added += 1 - if not self.config.dry_run: - self.client.torrents.add( - torrent_files=src, save_path=dest, category=category, tags=self.cross_seed_tag, is_paused=True - ) - try: - torrent_hash_generator = TorrentHashGenerator(src) - torrent_hash = torrent_hash_generator.generate_torrent_hash() - util.move_files(src, file_cs_out) - except Exception as e: - logger.warning(f"Unable to generate torrent hash from cross-seed {t_name}: {e}") - try: - if torrent_hash: - torrent_info = self.qbt.get_torrents({"torrent_hashes": torrent_hash}) - except Exception as e: - logger.warning(f"Unable to find hash {torrent_hash} in qbt: {e}") - if torrent_info: - torrent = torrent_info[0] - self.qbt.add_torrent_files(torrent.hash, torrent.files, torrent.save_path) - self.qbt.torrentvalid.append(torrent) - self.qbt.torrentinfo[t_name]["torrents"].append(torrent) - self.qbt.torrent_list.append(torrent) - else: - logger.print_line(f"Found {t_name} in {dir_cs} but original torrent is not complete.", self.config.loglevel) - logger.print_line("Not adding to qBittorrent", self.config.loglevel) - else: - error = f"{tr_name} not found in torrents. Cross-seed Torrent not added to qBittorrent." - if self.config.dry_run: - logger.print_line(error, self.config.loglevel) - else: - logger.print_line(error, "WARNING") - util.move_files(src, file_cs_err) - self.config.notify(error, "cross-seed", False) - - self.config.webhooks_factory.notify(self.torrents_updated, self.notify_attr, group_by="category") - self.torrents_updated = [] - self.notify_attr = [] - # Tag missing cross-seed torrents tags - for torrent in self.qbt.torrent_list: - t_name = torrent.name - t_cat = torrent.category - if ( - not util.is_tag_in_torrent(self.cross_seed_tag, torrent.tags) - and self.qbt.is_cross_seed(torrent) - and torrent.downloaded == 0 - and torrent.seeding_time > 0 - ): - tracker = self.qbt.get_tags(self.qbt.get_tracker_urls(torrent.trackers)) - self.stats_tagged += 1 - body = logger.print_line( - f"{'Not Adding' if self.config.dry_run else 'Adding'} '{self.cross_seed_tag}' tag to {t_name}", - self.config.loglevel, - ) - attr = { - "function": "tag_cross_seed", - "title": "Tagging Cross-Seed Torrent", - "body": body, - "torrents": [t_name], - "torrent_category": t_cat, - "torrent_tag": self.cross_seed_tag, - "torrent_tracker": tracker["url"], - } - self.notify_attr.append(attr) - self.torrents_updated.append(t_name) - if not self.config.dry_run: - torrent.add_tags(tags=self.cross_seed_tag) - self.config.webhooks_factory.notify(self.torrents_updated, self.notify_attr, group_by="category") - numcategory = Counter(categories) - for cat in numcategory: - if numcategory[cat] > 0: - logger.print_line( - f"{numcategory[cat]} {cat} cross-seed .torrents {'not added' if self.config.dry_run else 'added'}.", - self.config.loglevel, - ) - if self.stats_added > 0: - logger.print_line( - f"Total {self.stats_added} cross-seed .torrents {'not added' if self.config.dry_run else 'added'}.", - self.config.loglevel, - ) - if self.stats_tagged > 0: - logger.print_line( - f"Total {self.stats_tagged} cross-seed .torrents {'not added' if self.config.dry_run else 'added'}.", - self.config.loglevel, - ) diff --git a/modules/core/remove_unregistered.py b/modules/core/remove_unregistered.py index 095b587..e425860 100644 --- a/modules/core/remove_unregistered.py +++ b/modules/core/remove_unregistered.py @@ -21,6 +21,7 @@ class RemoveUnregistered: self.tag_error = self.config.tracker_error_tag self.cfg_rem_unregistered = self.config.commands["rem_unregistered"] self.cfg_tag_error = self.config.commands["tag_tracker_error"] + self.rem_unregistered_ignore_list = self.config.settings["rem_unregistered_ignore_list"] tag_error_msg = "Tagging Torrents with Tracker Errors" if self.cfg_tag_error else "" rem_unregistered_msg = "Removing Unregistered Torrents" if self.cfg_rem_unregistered else "" @@ -125,7 +126,13 @@ class RemoveUnregistered: if list_in_text(msg_up, TorrentMessages.UNREGISTERED_MSGS) and not list_in_text( msg_up, TorrentMessages.IGNORE_MSGS ): - self.del_unregistered(msg, tracker, torrent) + if list_in_text(msg_up, self.rem_unregistered_ignore_list): + logger.print_line( + f"Ignoring unregistered torrent {self.t_name} due to matching phrase found in ignore list.", + self.config.loglevel, + ) + else: + self.del_unregistered(msg, tracker, torrent) else: if self.check_for_unregistered_torrents_in_bhd(tracker, msg_up, torrent.hash): self.del_unregistered(msg, tracker, torrent) diff --git a/modules/core/tags.py b/modules/core/tags.py index f0f8c3c..8ddff85 100644 --- a/modules/core/tags.py +++ b/modules/core/tags.py @@ -12,6 +12,7 @@ class Tags: self.share_limits_tag = qbit_manager.config.share_limits_tag # suffix tag for share limits self.torrents_updated = [] # List of torrents updated self.notify_attr = [] # List of single torrent attributes to send to notifiarr + self.stalled_tag = "stalledDL" self.tags() self.config.webhooks_factory.notify(self.torrents_updated, self.notify_attr, group_by="tag") @@ -21,8 +22,26 @@ class Tags: logger.separator("Updating Tags", space=False, border=False) for torrent in self.qbt.torrent_list: tracker = self.qbt.get_tags(self.qbt.get_tracker_urls(torrent.trackers)) - if torrent.tags == "" or not util.is_tag_in_torrent(tracker["tag"], torrent.tags): - if tracker["tag"]: + + # Remove stalled_tag if torrent is no longer stalled + if util.is_tag_in_torrent(self.stalled_tag, torrent.tags) and torrent.state != "stalledDL": + t_name = torrent.name + body = [] + body += logger.print_line(logger.insert_space(f"Torrent Name: {t_name}", 3), self.config.loglevel) + body += logger.print_line(logger.insert_space(f"Removing Tag: {self.stalled_tag}", 3), self.config.loglevel) + body += logger.print_line(logger.insert_space(f'Tracker: {tracker["url"]}', 8), self.config.loglevel) + if not self.config.dry_run: + torrent.remove_tags(self.stalled_tag) + if ( + torrent.tags == "" + or not util.is_tag_in_torrent(tracker["tag"], torrent.tags) + or (torrent.state == "stalledDL" and not util.is_tag_in_torrent(self.stalled_tag, torrent.tags)) + ): + stalled = False + if torrent.state == "stalledDL": + stalled = True + tracker["tag"].append(self.stalled_tag) + if tracker["tag"] or stalled: t_name = torrent.name self.stats += len(tracker["tag"]) body = [] diff --git a/modules/qbittorrent.py b/modules/qbittorrent.py index 1b0c690..3544510 100755 --- a/modules/qbittorrent.py +++ b/modules/qbittorrent.py @@ -27,7 +27,7 @@ class Qbt: SUPPORTED_VERSION = Version.latest_supported_app_version() MIN_SUPPORTED_VERSION = "v4.3.0" - TORRENT_DICT_COMMANDS = ["recheck", "cross_seed", "rem_unregistered", "tag_tracker_error", "tag_nohardlinks", "share_limits"] + TORRENT_DICT_COMMANDS = ["recheck", "rem_unregistered", "tag_tracker_error", "tag_nohardlinks", "share_limits"] def __init__(self, config, params): self.config = config @@ -48,9 +48,9 @@ class Qbt: ) self.client.auth_log_in() self.current_version = self.client.app.version - logger.debug(f"qBittorrent: {self.current_version}") - logger.debug(f"qBittorrent Web API: {self.client.app.web_api_version}") - logger.debug(f"qbit_manage supported versions: {self.MIN_SUPPORTED_VERSION} - {self.SUPPORTED_VERSION}") + logger.info(f"qBittorrent: {self.current_version}") + logger.info(f"qBittorrent Web API: {self.client.app.web_api_version}") + logger.info(f"qbit_manage supported versions: {self.MIN_SUPPORTED_VERSION} - {self.SUPPORTED_VERSION}") if self.current_version < self.MIN_SUPPORTED_VERSION: ex = ( f"Qbittorrent Error: qbit_manage is only compatible with {self.MIN_SUPPORTED_VERSION} or higher. " @@ -311,7 +311,7 @@ class Qbt: self.config.data, "tag", parent="tracker", subparent="other", default_is_none=True, var_type="list", save=False ) try: - tracker["url"] = util.trunc_val(urls[0], os.sep) + tracker["url"] = util.trunc_val(urls[0], "/") except IndexError as e: tracker["url"] = None if not urls: @@ -331,8 +331,8 @@ class Qbt: default_tag = tracker_other_tag else: try: - tracker["url"] = util.trunc_val(url, os.sep) - default_tag = tracker["url"].split(os.sep)[2].split(":")[0] + tracker["url"] = util.trunc_val(url, "/") + default_tag = tracker["url"].split("/")[2].split(":")[0] except IndexError as e: logger.debug(f"Tracker Url:{url}") logger.debug(e) @@ -380,7 +380,7 @@ class Qbt: if tracker_other_tag: default_tag = tracker_other_tag else: - default_tag = tracker["url"].split(os.sep)[2].split(":")[0] + default_tag = tracker["url"].split("/")[2].split(":")[0] tracker["tag"] = self.config.util.check_for_attribute( self.config.data, "tag", parent="tracker", subparent=default_tag, default=default_tag, var_type="list" ) diff --git a/modules/util.py b/modules/util.py index 5bf4652..6f6620a 100755 --- a/modules/util.py +++ b/modules/util.py @@ -17,11 +17,15 @@ from ruamel.yaml.constructor import ConstructorError logger = logging.getLogger("qBit Manage") -def get_list(data, lower=False, split=True, int_list=False): +def get_list(data, lower=False, split=True, int_list=False, upper=False): """Return a list from a string or list.""" if data is None: return None elif isinstance(data, list): + if lower is True: + return [d.strip().lower() for d in data] + if upper is True: + return [d.strip().upper() for d in data] return data elif isinstance(data, dict): return [data] @@ -29,6 +33,8 @@ def get_list(data, lower=False, split=True, int_list=False): return [str(data)] elif lower is True: return [d.strip().lower() for d in str(data).split(",")] + elif upper is True: + return [d.strip().upper() for d in str(data).split(",")] elif int_list is True: try: return [int(d.strip()) for d in str(data).split(",")] @@ -81,6 +87,8 @@ class TorrentMessages: "TRACKER NICHT REGISTRIERT.", "TORRENT EXISTIERT NICHT", "TORRENT NICHT GEFUNDEN", + "TORRENT DELETED", # NexusPHP + "TORRENT BANNED", # NexusPHP ] UNREGISTERED_MSGS_BHD = [ @@ -357,6 +365,8 @@ class check: message = "No Paths exist" elif var_type == "lower_list": return get_list(data[attribute], lower=True) + elif var_type == "upper_list": + return get_list(data[attribute], upper=True) elif test_list is None or data[attribute] in test_list: return data[attribute] else: diff --git a/qbit_manage.py b/qbit_manage.py index b9345b8..c79dbc3 100755 --- a/qbit_manage.py +++ b/qbit_manage.py @@ -83,14 +83,6 @@ parser.add_argument( type=str, help="This is used if you want to use a different name for your log file. Example: tv.log", ) -parser.add_argument( - "-cs", - "--cross-seed", - dest="cross_seed", - action="store_true", - default=False, - help="Use this after running cross-seed script to add torrents from the cross-seed output folder to qBittorrent", -) parser.add_argument( "-re", "--recheck", @@ -271,7 +263,6 @@ sch = get_arg("QBT_SCHEDULE", args.schedule) startupDelay = get_arg("QBT_STARTUP_DELAY", args.startupDelay) config_files = get_arg("QBT_CONFIG", args.configfiles) log_file = get_arg("QBT_LOGFILE", args.logfile) -cross_seed = get_arg("QBT_CROSS_SEED", args.cross_seed, arg_bool=True) recheck = get_arg("QBT_RECHECK", args.recheck, arg_bool=True) cat_update = get_arg("QBT_CAT_UPDATE", args.cat_update, arg_bool=True) tag_update = get_arg("QBT_TAG_UPDATE", args.tag_update, arg_bool=True) @@ -322,7 +313,6 @@ for v in [ "startupDelay", "config_files", "log_file", - "cross_seed", "recheck", "cat_update", "tag_update", @@ -370,7 +360,6 @@ from modules import util # noqa util.logger = logger from modules.config import Config # noqa from modules.core.category import Category # noqa -from modules.core.cross_seed import CrossSeed # noqa from modules.core.recheck import ReCheck # noqa from modules.core.remove_orphaned import RemoveOrphaned # noqa from modules.core.remove_unregistered import RemoveUnregistered # noqa @@ -497,12 +486,6 @@ def start(): if cfg.commands["tag_update"]: stats["tagged"] += Tags(qbit_manager).stats - # Set Cross Seed - if cfg.commands["cross_seed"]: - cross_seed = CrossSeed(qbit_manager) - stats["added"] += cross_seed.stats_added - stats["tagged"] += cross_seed.stats_tagged - # Remove Unregistered Torrents and tag errors if cfg.commands["rem_unregistered"] or cfg.commands["tag_tracker_error"]: rem_unreg = RemoveUnregistered(qbit_manager)