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',