From 391ee25941b5f3b6ffb47078bfa60bfa37c74582 Mon Sep 17 00:00:00 2001 From: Admin9705 <9705@duck.com> Date: Tue, 10 Jun 2025 18:22:38 -0400 Subject: [PATCH] Swaparr Docs Update --- docs/apps/swaparr.html | 159 ++++++++++++++++++++++- frontend/static/js/settings_forms.js | 131 +++++++++++++++++++ src/primary/apps/swaparr/handler.py | 107 +++++++++++++++ src/primary/default_configs/swaparr.json | 6 +- 4 files changed, 401 insertions(+), 2 deletions(-) diff --git a/docs/apps/swaparr.html b/docs/apps/swaparr.html index b1c1a736..64874df5 100644 --- a/docs/apps/swaparr.html +++ b/docs/apps/swaparr.html @@ -160,6 +160,139 @@

Consider your setup: If you have many active downloads, shorter intervals help. If you have a slower system or fewer downloads, longer intervals are fine.

+

Security Features

+ +

Swaparr includes advanced security features to protect your system from malicious downloads and suspicious content by analyzing download names and titles. Detection is based on filename patterns, not file contents.

+ +

Malicious File Detection

+

What it does: Automatically detect and immediately remove downloads with malicious file types.

+

How it works: Scans download names for dangerous file extensions and suspicious patterns. Unlike the strike system, malicious content is removed immediately without warnings.

+

When to enable: Highly recommended for all users, especially those using public trackers where malicious content is more common.

+ +
+ Important: + This feature analyzes filename patterns only, not actual file contents. It's designed to catch obviously malicious downloads based on their names. +
+ +

Malicious File Extensions

+

What it does: File extensions to consider malicious and block immediately.

+

Default blocked extensions:

+ +

Customization: Use the tag input system to add or remove extensions. Type the extension (with or without the dot) and press Enter or click the + button.

+ +

Suspicious Patterns

+

What it does: Filename patterns that indicate potentially malicious or unwanted content.

+

Default blocked patterns:

+ +

Examples of what gets blocked:

+ +

Customization: Add patterns that are common in your region or specific to content you want to avoid.

+ +

Age-Based Cleanup

+ +

Age-based cleanup automatically removes downloads that have been stuck for too long, regardless of strike count. This provides a safety net for downloads that might slip through the normal strike system.

+ +

Enable Age-Based Removal

+

What it does: Remove downloads that have been stuck longer than the specified age limit.

+

How it works: Tracks when Swaparr first noticed each download and removes it after the specified number of days, even if it hasn't reached the maximum strike count.

+

When to use: Enable this to prevent downloads from sitting in queues indefinitely. Particularly useful for handling edge cases where the normal strike system might not catch problematic downloads.

+ +

Maximum Age (Days)

+

What it does: Remove downloads older than this many days (default: 7 days).

+

Recommended values:

+ +

Consider your setup: Private trackers with limited seeders might need longer age limits. Public trackers with many alternatives can use shorter limits.

+ +
+ Age Tracking: + The age timer starts when Swaparr first sees a download in the queue, not when the download actually started. This ensures accurate tracking across Swaparr restarts. +
+ +

Quality-Based Filtering

+ +

Quality-based filtering automatically removes downloads with poor or undesirable quality indicators in their names. This helps maintain high standards for your media library.

+ +

Enable Quality-Based Filtering

+

What it does: Automatically remove downloads with blocked quality patterns in their names.

+

How it works: Scans download names for quality indicators that suggest poor viewing/listening experience. Like malicious file detection, quality-blocked content is removed immediately without strikes.

+

When to use: Enable if you want to maintain high quality standards and automatically reject low-quality releases.

+ +

Blocked Quality Patterns

+

What it does: Quality patterns to block based on their indicators in filenames.

+

Default blocked qualities:

+ + +

Examples of what gets blocked:

+ + +

Quality standards comparison:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Quality TypeVideo QualityAudio QualityRecommendation
CAM/TSVery PoorVery PoorBlock (Default)
DVDScr/R6PoorFairBlock (Default)
HDTV/WEB-DLGoodGoodAllow
BluRay/RemuxExcellentExcellentAllow
+ +

Customization: Use the tag input system to add quality patterns specific to your preferences. For example, you might want to block "720p" if you only accept 1080p or higher.

+ +
+ Be Careful: + Adding too many quality patterns might block legitimate releases. Start with the defaults and add patterns gradually based on your specific needs. +
+

*arr Instance Setup

Swaparr uses your existing *arr configurations from Huntarr. In the Swaparr configuration, you'll see checkboxes for each of your configured instances:

@@ -240,9 +373,24 @@ Removals - Downloads actually removed + Downloads removed via strike system When a download reaches max strikes and gets removed + + Malicious Removals + Downloads removed for malicious content + When malicious file detection blocks a download + + + Quality Removals + Downloads removed for poor quality + When quality-based filtering blocks a download + + + Age Removals + Downloads removed for being too old + When age-based cleanup removes stale downloads + Ignored Downloads skipped due to rules @@ -285,6 +433,9 @@
  • Ignore Above Size: 50GB
  • Sleep Duration: 600 seconds (10 minutes)
  • Remove from Client: Enabled
  • +
  • Malicious File Detection: Enabled
  • +
  • Age-Based Removal: Enabled (5 days)
  • +
  • Quality-Based Filtering: Enabled (default patterns)
  • Balanced Setup (Most Users)

    @@ -294,6 +445,9 @@
  • Ignore Above Size: 25GB
  • Sleep Duration: 900 seconds (15 minutes)
  • Remove from Client: Enabled
  • +
  • Malicious File Detection: Enabled
  • +
  • Age-Based Removal: Enabled (7 days)
  • +
  • Quality-Based Filtering: Optional (based on preferences)
  • Conservative Setup (Slower Connection, Private Trackers)

    @@ -303,6 +457,9 @@
  • Ignore Above Size: 15GB
  • Sleep Duration: 1800 seconds (30 minutes)
  • Remove from Client: Enabled
  • +
  • Malicious File Detection: Enabled
  • +
  • Age-Based Removal: Enabled (14 days)
  • +
  • Quality-Based Filtering: Disabled (manual quality control)
  • Troubleshooting

    diff --git a/frontend/static/js/settings_forms.js b/frontend/static/js/settings_forms.js index 6143ad8f..feb6e06e 100644 --- a/frontend/static/js/settings_forms.js +++ b/frontend/static/js/settings_forms.js @@ -1225,6 +1225,91 @@ const SettingsForms = { +
    +

    Age-Based Cleanup

    +

    + Automatically remove downloads that have been stuck for too long, regardless of strike count. +

    + +
    + + +

    Remove downloads that have been stuck longer than the specified age limit

    +
    + +
    + + +

    Remove downloads older than this many days (default: 7 days)

    +
    +
    + +
    +

    Quality-Based Filtering

    +

    + Automatically remove downloads with poor or undesirable quality indicators in their names. +

    + +
    + + +

    Automatically remove downloads with blocked quality patterns in their names

    +
    + +
    + +
    +
    +
    + + +
    +
    +

    Quality patterns to block. Type pattern and press Enter or click +. Examples: cam, ts, hdcam, workprint

    +
    +
    `; @@ -1276,9 +1361,15 @@ const SettingsForms = { const patterns = settings.suspicious_patterns || defaultPatterns; this.loadTags('swaparr_suspicious_patterns_tags', patterns); + // Initialize quality patterns + const defaultQualityPatterns = ['cam', 'camrip', 'hdcam', 'ts', 'telesync', 'tc', 'telecine', 'r6', 'dvdscr', 'dvdscreener', 'workprint', 'wp']; + const qualityPatterns = settings.blocked_quality_patterns || defaultQualityPatterns; + this.loadTags('swaparr_quality_patterns_tags', qualityPatterns); + // Add enter key listeners const extensionInput = document.getElementById('swaparr_malicious_extensions_input'); const patternInput = document.getElementById('swaparr_suspicious_patterns_input'); + const qualityInput = document.getElementById('swaparr_quality_patterns_input'); if (extensionInput) { extensionInput.addEventListener('keypress', (e) => { @@ -1298,9 +1389,19 @@ const SettingsForms = { }); } + if (qualityInput) { + qualityInput.addEventListener('keypress', (e) => { + if (e.key === 'Enter') { + e.preventDefault(); + this.addQualityTag(); + } + }); + } + // Make functions globally accessible window.addExtensionTag = () => this.addExtensionTag(); window.addPatternTag = () => this.addPatternTag(); + window.addQualityTag = () => this.addQualityTag(); }, // Load tags into a tag list @@ -1374,6 +1475,27 @@ const SettingsForms = { input.value = ''; }, + // Add quality pattern tag + addQualityTag: function() { + const input = document.getElementById('swaparr_quality_patterns_input'); + const container = document.getElementById('swaparr_quality_patterns_tags'); + + if (!input || !container) return; + + const value = input.value.trim().toLowerCase(); + if (!value) return; + + // Check for duplicates + const existing = Array.from(container.querySelectorAll('.tag-text')).map(el => el.textContent.toLowerCase()); + if (existing.includes(value)) { + input.value = ''; + return; + } + + this.createTagElement(container, value); + input.value = ''; + }, + // Get tags from a container getTagsFromContainer: function(containerId) { const container = document.getElementById(containerId); @@ -1427,6 +1549,7 @@ const SettingsForms = { `; + container.innerHTML = html; }, @@ -1766,6 +1889,14 @@ const SettingsForms = { // Get tags from tag containers settings.malicious_extensions = this.getTagsFromContainer('swaparr_malicious_extensions_tags'); settings.suspicious_patterns = this.getTagsFromContainer('swaparr_suspicious_patterns_tags'); + + // Age-based removal settings + settings.age_based_removal = getInputValue('#swaparr_age_based_removal', false); + settings.max_age_days = getInputValue('#swaparr_max_age_days', 7); + + // Quality-based removal settings + settings.quality_based_removal = getInputValue('#swaparr_quality_based_removal', false); + settings.blocked_quality_patterns = this.getTagsFromContainer('swaparr_quality_patterns_tags'); } } diff --git a/src/primary/apps/swaparr/handler.py b/src/primary/apps/swaparr/handler.py index dc3c625d..08594b92 100644 --- a/src/primary/apps/swaparr/handler.py +++ b/src/primary/apps/swaparr/handler.py @@ -168,6 +168,51 @@ def check_for_malicious_files(item, settings): return False, None +def check_age_based_removal(item, strike_data, settings): + """Check if download should be removed based on age""" + if not settings.get('age_based_removal', False): + return False, None + + max_age_days = settings.get('max_age_days', 7) + item_id = str(item.get('id', '')) + + # Check if we have strike data for this item + if item_id not in strike_data or not strike_data[item_id].get('first_strike_time'): + return False, None + + try: + first_strike = datetime.fromisoformat(strike_data[item_id]['first_strike_time'].replace('Z', '+00:00')) + age_days = (datetime.utcnow() - first_strike).days + + if age_days >= max_age_days: + swaparr_logger.warning(f"Age-based removal triggered for '{item['name']}': {age_days} days old (max: {max_age_days})") + return True, f"Too old: {age_days} days (max: {max_age_days})" + except (ValueError, KeyError) as e: + swaparr_logger.error(f"Error parsing first strike time for item {item_id}: {e}") + + return False, None + +def check_quality_based_removal(item, settings): + """Check if download should be removed based on quality""" + if not settings.get('quality_based_removal', False): + return False, None + + # Use user-defined blocked quality patterns from settings + blocked_qualities = settings.get('blocked_quality_patterns', [ + 'cam', 'camrip', 'hdcam', 'ts', 'telesync', 'tc', 'telecine', + 'r6', 'dvdscr', 'dvdscreener', 'workprint', 'wp' + ]) + + item_name = item.get('name', '').lower() + + # Check for blocked quality patterns + for quality in blocked_qualities: + if quality.lower() in item_name: + swaparr_logger.warning(f"Quality-based removal triggered for '{item['name']}': contains blocked quality '{quality}'") + return True, f"Blocked quality: {quality}" + + return False, None + def parse_time_string_to_seconds(time_string): """Parse a time string like '2h', '30m', '1d' to seconds""" if not time_string: @@ -508,6 +553,35 @@ def process_stalled_downloads(app_name, instance_name, instance_data, settings): continue # Skip to next item - don't process further + # Check for quality-based removal SECOND - immediate removal without strikes + is_quality_blocked, quality_reason = check_quality_based_removal(item, settings) + if is_quality_blocked: + swaparr_logger.warning(f"QUALITY-BASED REMOVAL: {item['name']} - {quality_reason}") + + if not settings.get("dry_run", False): + if delete_download(app_name, instance_data["api_url"], instance_data["api_key"], item["id"], True): + swaparr_logger.info(f"Successfully removed quality-blocked download: {item['name']}") + + # Mark as removed to prevent reappearance + removed_items[item_hash] = { + "name": item["name"], + "removed_time": datetime.utcnow().isoformat(), + "reason": f"Quality: {quality_reason}", + "size": item["size"] + } + save_removed_items(app_name, removed_items) + + item_state = f"REMOVED (Quality: {quality_reason})" + + # Track quality removal statistics + SWAPARR_STATS['quality_removed'] = SWAPARR_STATS.get('quality_removed', 0) + 1 + increment_swaparr_stat("quality_removals", 1) + else: + swaparr_logger.info(f"DRY RUN: Would remove quality-blocked download: {item['name']} - {quality_reason}") + item_state = f"Would Remove (Quality: {quality_reason})" + + continue # Skip to next item - don't process further + # Initialize strike count if not already in strike data if item_id not in strike_data: strike_data[item_id] = { @@ -517,6 +591,39 @@ def process_stalled_downloads(app_name, instance_name, instance_data, settings): "last_strike_time": None } + # Check for age-based removal THIRD - immediate removal without strikes + is_age_expired, age_reason = check_age_based_removal(item, strike_data, settings) + if is_age_expired: + swaparr_logger.warning(f"AGE-BASED REMOVAL: {item['name']} - {age_reason}") + + if not settings.get("dry_run", False): + if delete_download(app_name, instance_data["api_url"], instance_data["api_key"], item["id"], True): + swaparr_logger.info(f"Successfully removed age-expired download: {item['name']}") + + # Mark as removed to prevent reappearance + removed_items[item_hash] = { + "name": item["name"], + "removed_time": datetime.utcnow().isoformat(), + "reason": f"Age: {age_reason}", + "size": item["size"] + } + save_removed_items(app_name, removed_items) + + # Keep the item in strike data for reference but mark as removed + strike_data[item_id]["removed"] = True + strike_data[item_id]["removed_time"] = datetime.utcnow().isoformat() + + item_state = f"REMOVED (Age: {age_reason})" + + # Track age removal statistics + SWAPARR_STATS['age_removed'] = SWAPARR_STATS.get('age_removed', 0) + 1 + increment_swaparr_stat("age_removals", 1) + else: + swaparr_logger.info(f"DRY RUN: Would remove age-expired download: {item['name']} - {age_reason}") + item_state = f"Would Remove (Age: {age_reason})" + + continue # Skip to next item - don't process further + # Check if download should be striked should_strike = False strike_reason = "" diff --git a/src/primary/default_configs/swaparr.json b/src/primary/default_configs/swaparr.json index cb4ea266..fae64672 100644 --- a/src/primary/default_configs/swaparr.json +++ b/src/primary/default_configs/swaparr.json @@ -8,5 +8,9 @@ "sleep_duration": 900, "malicious_file_detection": false, "malicious_extensions": [".lnk", ".exe", ".bat", ".cmd", ".scr", ".pif", ".com", ".zipx", ".jar", ".vbs", ".js", ".jse", ".wsf", ".wsh"], - "suspicious_patterns": ["password.txt", "readme.txt", "install.exe", "setup.exe", "keygen", "crack", "patch.exe", "activator"] + "suspicious_patterns": ["password.txt", "readme.txt", "install.exe", "setup.exe", "keygen", "crack", "patch.exe", "activator"], + "age_based_removal": false, + "max_age_days": 7, + "quality_based_removal": false, + "blocked_quality_patterns": ["cam", "camrip", "hdcam", "ts", "telesync", "tc", "telecine", "r6", "dvdscr", "dvdscreener", "workprint", "wp"] } \ No newline at end of file