diff --git a/VERSION b/VERSION index 9c136b9..6b9f0bb 100755 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -4.6.5-develop10 +4.6.5-develop11 diff --git a/config/config.yml.sample b/config/config.yml.sample index 159cc3f..2ff974c 100755 --- a/config/config.yml.sample +++ b/config/config.yml.sample @@ -33,6 +33,7 @@ settings: tracker_error_tag: issue # Will set the tag of any torrents that do not have a working tracker. nohardlinks_tag: noHL # Will set the tag of any torrents with no hardlinks. stalled_tag: stalledDL # Will set the tag of any torrents stalled downloading. + private_tag: null # Will set the tag of any torrents that are private. (Set to null to disable) share_limits_tag: ~share_limit # Will add this tag when applying share limits to provide an easy way to filter torrents by share limit group/priority for each torrent 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 diff --git a/docs/Config-Setup.md b/docs/Config-Setup.md index 14fe149..5e60018 100644 --- a/docs/Config-Setup.md +++ b/docs/Config-Setup.md @@ -65,6 +65,7 @@ This section defines any settings defined in the configuration. | `force_auto_tmm_ignore_tags` | Torrents with these tags will be ignored when force_auto_tmm is enabled. | |
| | `tracker_error_tag` | Define the tag of any torrents that do not have a working tracker. (Used in `--tag-tracker-error`) | issue |
| | `nohardlinks_tag` | Define the tag of any torrents that don't have hardlinks (Used in `--tag-nohardlinks`) | noHL |
| +| `private_tag` | Define the tag of any torrents that are private. | None |
| | `share_limits_tag` | Will add this tag when applying share limits to provide an easy way to filter torrents by share limit group/priority for each torrent. For example, if you have a share-limit group `cross-seed` with a priority of 2 and the default share_limits_tag `~share_limits` would add the tag `~share_limit_2.cross-seed` (Used in `--share-limits`) | ~share_limit |
| | `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 |
| @@ -306,13 +307,15 @@ Provide webhook notifications based on event triggers Payload will be sent on any errors ```yaml -{ - "function": "run_error", // Webhook Trigger keyword - "title": str, // Title of the Payload - "body": str, // Error Message of the Payload - "critical": bool, // Critical Error - "type": str // severity of error -} +{ "function": "run_error", ? // Webhook Trigger keyword + "title" + : str, ? // Title of the Payload + "body" + : str, ? // Error Message of the Payload + "critical" + : bool, ? // Critical Error + "type" + : str // severity of error } ``` ### **Run Start Notifications** @@ -320,16 +323,21 @@ Payload will be sent on any errors Payload will be sent at the start of the run ```yaml -{ - "function": "run_start", // Webhook Trigger keyword - "title": str, // Title of the Payload - "body": str, // Message of the Payload - "start_time": str, // Time Run is started Format "YYYY-mm-dd HH:MM:SS" - "dry_run": bool, // Dry-Run - "web_api_used": bool, // Indicates whether the run was initiated via the Web API (true) or not (false). - "commands": list, // List of commands that that will be ran - "execution_options": list // List of eecution options selected -} +{ "function": "run_start", ? // Webhook Trigger keyword + "title" + : str, ? // Title of the Payload + "body" + : str, ? // Message of the Payload + "start_time" + : str, ? // Time Run is started Format "YYYY-mm-dd HH:MM:SS" + "dry_run" + : bool, ? // Dry-Run + "web_api_used" + : bool, ? // Indicates whether the run was initiated via the Web API (true) or not (false). + "commands" + : list, ? // List of commands that that will be ran + "execution_options" + : list // List of eecution options selected } ``` ### **Run End Notifications** @@ -370,16 +378,21 @@ Payload will be sent at the end of the run Payload will be sent when rechecking/resuming a torrent that is paused ```yaml -{ - "function": "recheck", // Webhook Trigger keyword - "title": str, // Title of the Payload - "body": str, // Message of the Payload - "torrents": [str], // List of Torrent Names - "torrent_tag": str, // Torrent Tags - "torrent_category": str, // Torrent Category - "torrent_tracker": str, // Torrent Tracker URL - "notifiarr_indexer": str, // Notifiarr React name/id for indexer -} +{ "function": "recheck", ? // Webhook Trigger keyword + "title" + : str, ? // Title of the Payload + "body" + : str, ? // Message of the Payload + "torrents" + : [str], ? // List of Torrent Names + "torrent_tag" + : str, ? // Torrent Tags + "torrent_category" + : str, ? // Torrent Category + "torrent_tracker" + : str, ? // Torrent Tracker URL + "notifiarr_indexer" + : str, // Notifiarr React name/id for indexer } ``` ### **Category Update Notifications** @@ -387,16 +400,21 @@ Payload will be sent when rechecking/resuming a torrent that is paused Payload will be sent when updating torrents with missing category ```yaml -{ - "function": "cat_update", // Webhook Trigger keyword - "title": str, // Title of the Payload - "body": str, // Message of the Payload - "torrents": [str], // List of Torrent Names - "torrent_category": str, // New Torrent Category - "torrent_tag": str, // Torrent Tags - "torrent_tracker": str, // Torrent Tracker URL - "notifiarr_indexer": str, // Notifiarr React name/id for indexer -} +{ "function": "cat_update", ? // Webhook Trigger keyword + "title" + : str, ? // Title of the Payload + "body" + : str, ? // Message of the Payload + "torrents" + : [str], ? // List of Torrent Names + "torrent_category" + : str, ? // New Torrent Category + "torrent_tag" + : str, ? // Torrent Tags + "torrent_tracker" + : str, ? // Torrent Tracker URL + "notifiarr_indexer" + : str, // Notifiarr React name/id for indexer } ``` ### **Tag Update Notifications** @@ -404,16 +422,21 @@ Payload will be sent when updating torrents with missing category Payload will be sent when updating torrents with missing tag ```yaml -{ - "function": "tag_update", // 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": str, // New Torrent Tag - "torrent_tracker": str, // Torrent Tracker URL - "notifiarr_indexer": str, // Notifiarr React name/id for indexer -} +{ "function": "tag_update", ? // 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" + : str, ? // New Torrent Tag + "torrent_tracker" + : str, ? // Torrent Tracker URL + "notifiarr_indexer" + : str, // Notifiarr React name/id for indexer } ``` ### **Remove Unregistered Torrents Notifications** @@ -421,18 +444,25 @@ Payload will be sent when updating torrents with missing tag Payload will be sent when Unregistered Torrents are found ```yaml -{ - "function": "rem_unregistered", // 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_status": str, // Torrent Tracker Status message - "torrent_tag": str, // Torrent Tags - "torrent_tracker": str, // Torrent Tracker URL - "notifiarr_indexer": str, // Notifiarr React name/id for indexer - "torrents_deleted_and_contents": bool, // Deleted Torrents and contents or Deleted just the torrent -} +{ "function": "rem_unregistered", ? // 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_status" + : str, ? // Torrent Tracker Status message + "torrent_tag" + : str, ? // Torrent Tags + "torrent_tracker" + : str, ? // Torrent Tracker URL + "notifiarr_indexer" + : str, ? // Notifiarr React name/id for indexer + "torrents_deleted_and_contents" + : bool, // Deleted Torrents and contents or Deleted just the torrent } ``` ### **Tag Tracker Error Notifications** @@ -440,30 +470,41 @@ Payload will be sent when Unregistered Torrents are found Payload will be sent when trackers with errors are tagged/untagged ```yaml -{ - "function": "tag_tracker_error", // 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": "issue", // Tag Added - "torrent_status": str, // Torrent Tracker Status message - "torrent_tracker": str, // Torrent Tracker URL - "notifiarr_indexer": str, // Notifiarr React name/id for indexer -} +{ "function": "tag_tracker_error", ? // 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" + : "issue", ? // Tag Added + "torrent_status" + : str, ? // Torrent Tracker Status message + "torrent_tracker" + : str, ? // Torrent Tracker URL + "notifiarr_indexer" + : str, // Notifiarr React name/id for indexer } ``` ```yaml -{ - "function": "untag_tracker_error", // 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": str, // Tag Added - "torrent_tracker": str, // Torrent Tracker URL - "notifiarr_indexer": str, // Notifiarr React name/id for indexer -} +{ "function": "untag_tracker_error", ? // 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" + : str, ? // Tag Added + "torrent_tracker" + : str, ? // Torrent Tracker URL + "notifiarr_indexer" + : str, // Notifiarr React name/id for indexer } ``` ### **Remove Orphaned Files Notifications** @@ -471,14 +512,17 @@ Payload will be sent when trackers with errors are tagged/untagged Payload will be sent when Orphaned Files are found and moved into the orphaned folder ```yaml -{ - "function": "rem_orphaned", // Webhook Trigger keyword - "title": str, // Title of the Payload - "body": str, // Message of the Payload - "orphaned_files": list, // List of orphaned files - "orphaned_directory": str, // Folder path where orphaned files will be moved to - "total_orphaned_files": int, // Total number of orphaned files found -} +{ "function": "rem_orphaned", ? // Webhook Trigger keyword + "title" + : str, ? // Title of the Payload + "body" + : str, ? // Message of the Payload + "orphaned_files" + : list, ? // List of orphaned files + "orphaned_directory" + : str, ? // Folder path where orphaned files will be moved to + "total_orphaned_files" + : int, // Total number of orphaned files found } ``` ### **Tag No Hardlinks Notifications** @@ -486,31 +530,41 @@ Payload will be sent when Orphaned Files are found and moved into the orphaned f Payload will be sent when no hard links are found for any files in a particular torrent ```yaml -{ - "function": "tag_nohardlinks", // 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": 'noHL', // Add `noHL` to Torrent Tags - "torrent_tracker": str, // Torrent Tracker URL - "notifiarr_indexer": str, // Notifiarr React name/id for indexer -} +{ "function": "tag_nohardlinks", ? // 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" + : "noHL", ? // Add `noHL` to Torrent Tags + "torrent_tracker" + : str, ? // Torrent Tracker URL + "notifiarr_indexer" + : str, // Notifiarr React name/id for indexer } ``` Payload will be sent when hard links are found for any torrents that were previously tagged with `noHL` ```yaml -{ - "function": "untag_nohardlinks", // 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": 'noHL', // Remove `noHL` from Torrent Tags - "torrent_tracker": str, // Torrent Tracker URL - "notifiarr_indexer": str, // Notifiarr React name/id for indexer -} +{ "function": "untag_nohardlinks", ? // 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" + : "noHL", ? // Remove `noHL` from Torrent Tags + "torrent_tracker" + : str, ? // Torrent Tracker URL + "notifiarr_indexer" + : str, // Notifiarr React name/id for indexer } ``` ### **Share Limits Notifications** @@ -518,35 +572,49 @@ Payload will be sent when hard links are found for any torrents that were previo Payload will be sent when Share Limits are updated for a specific group ```yaml -{ - "function": "share_limits", // Webhook Trigger keyword - "title": str, // Title of the Payload - "body": str, // Message of the Payload - "grouping": str, // Share Limit group name - "torrents": [str], // List of Torrent Names - "torrent_tag": str, // Torrent Tags - "torrent_max_ratio": float, // Set the Max Ratio Share Limit - "torrent_max_seeding_time": int, // Set the Max Seeding Time (minutes) Share Limit - "torrent_min_seeding_time": int, // Set the Min Seeding Time (minutes) Share Limit - "torrent_limit_upload_speed": int // Set the the torrent upload speed limit (kB/s) -} +{ "function": "share_limits", ? // Webhook Trigger keyword + "title" + : str, ? // Title of the Payload + "body" + : str, ? // Message of the Payload + "grouping" + : str, ? // Share Limit group name + "torrents" + : [str], ? // List of Torrent Names + "torrent_tag" + : str, ? // Torrent Tags + "torrent_max_ratio" + : float, ? // Set the Max Ratio Share Limit + "torrent_max_seeding_time" + : int, ? // Set the Max Seeding Time (minutes) Share Limit + "torrent_min_seeding_time" + : int, ? // Set the Min Seeding Time (minutes) Share Limit + "torrent_limit_upload_speed" + : int // Set the the torrent upload speed limit (kB/s) } ``` Payload will be sent when `cleanup` flag is set to true and torrent meets share limit criteria. ```yaml -{ - "function": "cleanup_share_limits", // Webhook Trigger keyword - "title": str, // Title of the Payload - "body": str, // Message of the Payload - "grouping": str, // Share Limit group name - "torrents": [str], // List of Torrent Names - "torrent_category": str, // Torrent Category - "cleanup": True, // Cleanup flag - "torrent_tracker": str, // Torrent Tracker URL - "notifiarr_indexer": str, // Notifiarr React name/id for indexer - "torrents_deleted_and_contents": bool, // Deleted Torrents and contents or Deleted just the torrent -} +{ "function": "cleanup_share_limits", ? // Webhook Trigger keyword + "title" + : str, ? // Title of the Payload + "body" + : str, ? // Message of the Payload + "grouping" + : str, ? // Share Limit group name + "torrents" + : [str], ? // List of Torrent Names + "torrent_category" + : str, ? // Torrent Category + "cleanup" + : True, ? // Cleanup flag + "torrent_tracker" + : str, ? // Torrent Tracker URL + "notifiarr_indexer" + : str, ? // Notifiarr React name/id for indexer + "torrents_deleted_and_contents" + : bool, // Deleted Torrents and contents or Deleted just the torrent } ``` ### **Cleanup directories Notifications** @@ -554,13 +622,17 @@ Payload will be sent when `cleanup` flag is set to true and torrent meets share Payload will be sent when files are deleted/cleaned up from the various folders ```yaml -{ - "function": "cleanup_dirs", // Webhook Trigger keyword - "location": str, // Location of the folder that is being cleaned - "title": str, // Title of the Payload - "body": str, // Message of the Payload - "files": list, // List of files that were deleted from the location - "empty_after_x_days": int, // Number of days that the files will be kept in the location - "size_in_bytes": int, // Total number of bytes deleted from the location -} +{ "function": "cleanup_dirs", ? // Webhook Trigger keyword + "location" + : str, ? // Location of the folder that is being cleaned + "title" + : str, ? // Title of the Payload + "body" + : str, ? // Message of the Payload + "files" + : list, ? // List of files that were deleted from the location + "empty_after_x_days" + : int, ? // Number of days that the files will be kept in the location + "size_in_bytes" + : int, // Total number of bytes deleted from the location } ``` diff --git a/modules/config.py b/modules/config.py index 64dc9fe..9d17c8f 100755 --- a/modules/config.py +++ b/modules/config.py @@ -302,6 +302,7 @@ class Config: ), "nohardlinks_tag": self.util.check_for_attribute(self.data, "nohardlinks_tag", parent="settings", default="noHL"), "stalled_tag": self.util.check_for_attribute(self.data, "stalled_tag", parent="settings", default="stalledDL"), + "private_tag": self.util.check_for_attribute(self.data, "private_tag", parent="settings", default_is_none=True), "share_limits_tag": self.util.check_for_attribute( self.data, "share_limits_tag", parent="settings", default=share_limits_tag ), @@ -352,6 +353,7 @@ class Config: self.tracker_error_tag = self.settings["tracker_error_tag"] self.nohardlinks_tag = self.settings["nohardlinks_tag"] self.stalled_tag = self.settings["stalled_tag"] + self.private_tag = self.settings["private_tag"] self.share_limits_tag = self.settings["share_limits_tag"] self.share_limits_custom_tags = [] self.share_limits_min_seeding_time_tag = self.settings["share_limits_min_seeding_time_tag"] @@ -365,6 +367,7 @@ class Config: self.share_limits_min_num_seeds_tag, self.share_limits_last_active_tag, self.share_limits_tag, + self.private_tag, ] # "Migrate settings from v4.0.0 to v4.0.1 and beyond. Convert 'share_limits_suffix_tag' to 'share_limits_tag'" if "share_limits_suffix_tag" in self.data["settings"]: diff --git a/modules/core/tags.py b/modules/core/tags.py index 720aee1..8cc2042 100644 --- a/modules/core/tags.py +++ b/modules/core/tags.py @@ -17,6 +17,7 @@ class Tags: self.torrents_updated = [] # List of torrents updated self.notify_attr = [] # List of single torrent attributes to send to notifiarr self.stalled_tag = qbit_manager.config.stalled_tag + self.private_tag = qbit_manager.config.private_tag self.tag_stalled_torrents = self.config.settings["tag_stalled_torrents"] self.tags() @@ -53,23 +54,29 @@ class Tags: and torrent.state == "stalledDL" and not util.is_tag_in_torrent(self.stalled_tag, torrent.tags) ) + or ( + self.private_tag + and not util.is_tag_in_torrent(self.private_tag, torrent.tags) + and self.qbt.is_torrent_private(torrent) + ) ): - stalled = False + tags_to_add = tracker["tag"].copy() if self.tag_stalled_torrents and torrent.state == "stalledDL": - stalled = True - tracker["tag"].append(self.stalled_tag) - if tracker["tag"] or stalled: + tags_to_add.append(self.stalled_tag) + if self.private_tag and self.qbt.is_torrent_private(torrent): + tags_to_add.append(self.private_tag) + if tags_to_add: t_name = torrent.name - self.stats += len(tracker["tag"]) + self.stats += len(tags_to_add) 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"New Tag{'s' if len(tracker['tag']) > 1 else ''}: {', '.join(tracker['tag'])}", 8), + logger.insert_space(f"New Tag{'s' if len(tags_to_add) > 1 else ''}: {', '.join(tags_to_add)}", 8), 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.add_tags(tracker["tag"]) + torrent.add_tags(tags_to_add) category = self.qbt.get_category(torrent.save_path)[0] if torrent.category == "" else torrent.category attr = { "function": "tag_update", @@ -77,7 +84,7 @@ class Tags: "body": "\n".join(body), "torrents": [t_name], "torrent_category": category, - "torrent_tag": ", ".join(tracker["tag"]), + "torrent_tag": ", ".join(tags_to_add), "torrent_tracker": tracker["url"], "notifiarr_indexer": tracker["notifiarr"], } diff --git a/modules/qbittorrent.py b/modules/qbittorrent.py index fed9f7d..af1c15b 100755 --- a/modules/qbittorrent.py +++ b/modules/qbittorrent.py @@ -299,6 +299,23 @@ class Qbt: """Get tracker urls from torrent""" return tuple(x.url for x in trackers if x.url.startswith(("http", "udp", "ws"))) + def is_torrent_private(self, torrent): + """Checks if torrent is private""" + if hasattr(torrent, "private") and torrent.private: + return True + if hasattr(torrent, "private") and not torrent.private: + return False + + if isinstance(torrent, str): + torrent_hash = torrent + else: + torrent_hash = torrent.hash + torrent_trackers = self.client.torrents_trackers(torrent_hash) + for tracker in torrent_trackers: + if "private" in tracker["msg"].lower() or "private" in tracker["url"].lower(): + return True + return False + def get_tags(self, urls): """Get tags from config file based on keyword""" urls = list(urls) diff --git a/web-ui/js/config-schemas/settings.js b/web-ui/js/config-schemas/settings.js index 6ec9704..d0e74cd 100644 --- a/web-ui/js/config-schemas/settings.js +++ b/web-ui/js/config-schemas/settings.js @@ -44,6 +44,13 @@ export const settingsSchema = { description: 'The tag to apply to torrents that are stalled during download.', default: 'stalledDL' }, + { + name: 'private_tag', + type: 'text', + label: 'Private Tag', + description: 'The tag to apply to private torrents.', + default: null + }, { name: 'share_limits_tag', type: 'text',