Skip to content

add RHD tracker#1250

Open
wasserrutschentester wants to merge 15 commits intoAudionut:masterfrom
wasserrutschentester:rocket-hd
Open

add RHD tracker#1250
wasserrutschentester wants to merge 15 commits intoAudionut:masterfrom
wasserrutschentester:rocket-hd

Conversation

@wasserrutschentester
Copy link

@wasserrutschentester wasserrutschentester commented Feb 12, 2026

since #685 was stale for about half a year i've decided to create a new working config (based on the one from SHRI) that matches the current naming and upload rules for RocketHD

this PR fixes #632

Summary by CodeRabbit

  • New Features

    • Added support for the RHD (RocketHD) tracker: resolution mapping, release-name generation, release-group parsing, language tagging, and interactive override prompts.
    • Enhanced German-language handling (titles, audio, subtitles) and tracker-specific upload validations (quality/source rules, SD restrictions, disallowed samples/proofs/images).
  • Chores

    • Added example tracker configuration and registered RHD for API workflows.

this doesn't check for anything exept blocked groups so use with care
- safe dict access using .get()
- added docstrings
- removed unused boolean assignment
- fix logic bug potentially assigning NOGRP when a viable group tag exists
- don't skip the first two tracks when trying to find a german audio track
@coderabbitai
Copy link

coderabbitai bot commented Feb 12, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds RocketHD (RHD) support: new TRACKERS["RHD"] config, a new RHD tracker plugin (extends UNIT3D) implementing resolution/name/language/release-group handling and upload checks, and registration so RHD is constructible and included in API workflows.

Changes

Cohort / File(s) Summary
Tracker Configuration
data/example-config.py
Added TRACKERS["RHD"] block (link_dir_name, api_key, announce_url, anon, use_german_title) and updated available-trackers comment.
RHD Tracker Implementation
src/trackers/RHD.py
New RHD class (extends UNIT3D) with resolution↔ID mapping, basename/name construction, language/German-title extraction, audio/subtitle checks, release-group extraction/sanitization, interactive prompts, and tracker-specific upload constraints via get_additional_checks.
Tracker Registration
src/trackersetup.py
Imported RHD, added to tracker_class_map, and included in api_trackers so RHD is instantiable and included in API-related workflows.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    actor Uploader
    participant Assistant
    participant RHD as "RHD Tracker (plugin)"
    participant Site as "Tracker API / Site"

    Uploader->>Assistant: submit upload metadata + files
    Assistant->>RHD: instantiate RHD, call get_additional_checks(meta)
    RHD-->>Assistant: checks OK / error (German audio, SD/HD rules, banned flags)
    Assistant->>RHD: call get_name(meta) to build release name
    RHD-->>Assistant: constructed release name and release-group tag
    Assistant->>Site: push upload + announce URL / metadata
    Site-->>Assistant: accept/reject response
    Assistant-->>Uploader: report result
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Poem

🐰 I hopped through files, found a German tune,
RHD sprung up beneath the coding moon.
I sniffed for titles, audio, group and name,
Tagged the release and played the upload game,
A carrot-sized commit — hop, hop — what a boon!

🚥 Pre-merge checks | ✅ 5 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Merge Conflict Detection ⚠️ Warning ❌ Merge conflicts detected (4 files):

⚔️ .github/workflows/pr-welcome-comment.yml (content)
⚔️ README.md (content)
⚔️ data/example-config.py (content)
⚔️ src/trackersetup.py (content)

These conflicts must be resolved before merging into master.
Resolve conflicts locally and push changes to this branch.
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'add RHD tracker' is concise and directly describes the main change: adding support for the RocketHD tracker.
Linked Issues check ✅ Passed The PR successfully implements all coding requirements from issue #632: adds RocketHD tracker support with UNIT3D-based configuration, handles RHD-specific naming conventions, and enables RHD-compliant uploads.
Out of Scope Changes check ✅ Passed All changes are in-scope: tracker configuration in data/example-config.py, new RHD tracker implementation in src/trackers/RHD.py, and tracker setup integration in src/trackersetup.py directly support RHD addition.
Docstring Coverage ✅ Passed Docstring coverage is 90.91% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🤖 Fix all issues with AI agents
In `@src/trackers/RHD.py`:
- Around line 111-122: The loop over akas in RHD.py can prematurely break on a
Germany AKA with a non-string title, preventing later German-language fallbacks;
update the logic in the for-loop that sets country_match and language_match so
the break is only executed when a valid string country_match is assigned (i.e.,
move the break inside the isinstance(title, str) branch or conditionally break
only if country_match was set), ensuring language_match can still be discovered
later if country_match remains unset.
- Around line 336-341: In _extract_clean_release_group, INVALID_TAG_PATTERN is
written to expect a leading '-' but tag is pre-stripped with .lstrip("-"), so
invalid names like "-NOGRP" bypass the check; update INVALID_TAG_PATTERN to
match the bare names (e.g., use anchors like ^(nogrp|nogroup|unknown|unk)$,
case-insensitive) or adjust the check to test raw_tag before stripping so that
INVALID_TAG_PATTERN.search(tag) correctly detects invalid tags; ensure the
change references INVALID_TAG_PATTERN and the _extract_clean_release_group
method.
- Around line 310-314: The WEB release naming places the streaming service after
audio instead of before the type; in the elif branch that checks effective_type
in ("WEBDL", "WEBRIP") (variables: service, type_str) update the name formatting
so {service} appears before {type_str} (and ensure audio {audio} remains after
the type as RocketHD expects) — modify the f-string used to build name to
reorder the placeholders accordingly in the name assignment.
- Around line 301-304: In the DVDRIP/BRRIP branch handling effective_type in
("DVDRIP", "BRRIP") the constructed release name (variable name) omits the
episode identifiers; update the f-string in that branch to include {episode},
{episode_title}, and {part} (position them where other branches include
season+episode, e.g., after {season}) so TV releases retain episode information
when building the name in src/trackers/RHD.py.
- Around line 42-67: get_resolution_id currently ignores the mapping_only flag
and always returns {'resolution_id': ...}, breaking request/claim matching;
modify get_resolution_id (in class/method named get_resolution_id) to return the
full mapping dict of resolution name -> id when mapping_only is True (e.g.,
{'1080p':'3', '2160p':'2', ...}), otherwise keep existing behavior of returning
{'resolution_id': <id>} based on resolution_key; ensure you use the same
resolution mapping dictionary (the one assigned to resolution_id variable) and
reference resolution_key, mapping_only, and reverse parameters appropriately so
callers like trackersetup.py receive the expected mapping.
🧹 Nitpick comments (1)
src/trackers/RHD.py (1)

378-414: cli_ui.ask_yes_no calls are not wrapped in try/except for EOFError/KeyboardInterrupt.

In unattended mode or when stdin is closed, these calls will raise EOFError and crash. Other parts of the codebase (e.g., trackersetup.py line 373) wrap similar prompts in try/except (EOFError, KeyboardInterrupt) blocks.

Example pattern from the codebase
try:
    if not cli_ui.ask_yes_no("Do you want to upload anyway?", default=False):
        return False
except EOFError:
    console.print("\n[red]Prompt cancelled[/red]")
    return False

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@src/trackers/RHD.py`:
- Line 400: The message string in the upscale check references the wrong
attribute name and will raise AttributeError; replace the incorrect self.baseurl
with the correct instance attribute self.base_url and also fix the duplicated
word "is is" to "is" in the same message (look for the upload/upscale check code
in class RHD or the method that prints "Uploading upscaled releases..."). Ensure
the formatted string uses self.base_url so the log prints correctly.
- Around line 269-271: The current logic in RHD.py replaces audio_lang_str when
German subtitles exist without German audio; instead update the code in the
method that builds audio_lang_str (use the variables/audio helpers
_has_german_audio and _has_german_subtitles) to append a German-subbed marker
rather than overwrite—e.g., if audio_lang_str is non-empty, set it to
"<existing> (GERMAN SUBBED)" or otherwise "GERMAN SUBBED"; keep use of the
_has_german_audio and _has_german_subtitles checks and only add the marker when
German audio is absent.
🧹 Nitpick comments (3)
src/trackers/RHD.py (3)

394-394: Remove extraneous f prefix on string without placeholders.

Per static analysis (Ruff F541), this f-string has no interpolated expressions.

Proposed fix
-            console.print(f"[bold red]Uploading MIC, CAM, TS or LD releases, is prohibited")
+            console.print("[bold red]Uploading MIC, CAM, TS or LD releases is prohibited")

34-36: Minor cleanup: remove pass and leftover template comment.

The pass on line 36 is unnecessary after the assignments, and line 31's comment (# If the site supports requests…) reads like a copy-paste template artifact. Line 38-40 comments are similarly boilerplate.

Proposed cleanup
         self.banned_groups = ["1XBET", "MEGA", "MTZ", "Whistler", "WOTT", "Taylor.D", "HELD", "FSX", "FuN", "MagicX", "w00t", "PaTroL", "BB",
                               "266ers", "GTF", "JellyfinPlex", "2BA", "FritzBox"]
-        pass
-
-    # The section below can be deleted if no changes are needed, as everything else is handled in UNIT3D.py
-    # If advanced changes are required, copy the necessary functions from UNIT3D.py here
-    # For example, if you need to modify the description, copy and paste the 'get_description' function and adjust it accordingly

417-427: Sample/proof check may false-positive on legitimate filenames containing "sample" or "proof".

The substring check "sample" in str(file).lower() will match files like Sampleson.mkv or paths containing sample anywhere (e.g., a directory named resampled). Consider matching only common patterns like a Sample/ directory or files named exactly sample.*.

Tighter matching example
         if any(
             str(file).lower().endswith((".jpg", ".jpeg", ".png", ".bmp", ".gif", ".webp", ".tiff", ".pdf"))
-            or "sample" in str(file).lower()
-            or "proof" in str(file).lower()
+            or re.search(r'(?:^|[\\/])sample(?:[\\/.])', str(file), re.IGNORECASE)
+            or re.search(r'(?:^|[\\/])proof(?:[\\/.])', str(file), re.IGNORECASE)
             for file in filelist
         ):

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In `@src/trackers/RHD.py`:
- Line 19: INVALID_TAG_PATTERN currently matches substrings (e.g., "unk" matches
"TRUNK"), causing false positives; update the regex in INVALID_TAG_PATTERN in
src/trackers/RHD.py (and the similar pattern used around lines 344-349) to
anchor to the whole tag (e.g., use ^(nogrp|nogroup|unknown|unk)$) or, if you
intend to allow word-boundary matches, wrap alternatives with \b (e.g.,
\b(nogrp|nogroup|unknown|unk)\b); modify the compiled pattern(s) accordingly so
tag validation uses full-string/word-boundary matching instead of substring
matching.
- Around line 318-322: The f-string for WEB releases (in the branch checking
effective_type in ("WEBDL", "WEBRIP")) places service before type_str and audio;
update the construction of name (variable name) so that type_str appears
immediately after resolution and audio appears before service, i.e., ensure the
order uses ...{resolution} {type_str} {audio} {service} {hdr} {video_encode}...,
referencing the local variables effective_type, service, type_str, audio and
name to locate and adjust the f-string.
- Around line 386-428: The method get_additional_checks uses interactive
cli_ui.ask_yes_no calls (cli_ui.ask_yes_no) which will block in unattended runs;
update each prompt site in get_additional_checks to first check
meta.get("unattended", False) and, if True, avoid prompting and return the
non-confirmed result (i.e., treat the implicit answer as "no" by logging the
same warning and returning False). Apply this pattern at every place where
cli_ui.ask_yes_no is called (the prohib_markers check, UPSCALE check, SD
resolution check, German audio check, and samples/images check) so the function
never blocks when meta["unattended"] is truthy.
🧹 Nitpick comments (1)
src/trackers/RHD.py (1)

31-40: Remove leftover template/scaffolding comments before merging.

Lines 31, 36, and 38–40 contain placeholder text and a dangling pass carried over from a template. They add noise without value.

Suggested cleanup
-        self.requests_url = f'{self.base_url}/api/requests/filter'  # If the site supports requests via API, otherwise remove this line
+        self.requests_url = f'{self.base_url}/api/requests/filter'
         self.search_url = f'{self.base_url}/api/torrents/filter'
         self.torrent_url = f'{self.base_url}/torrents/'
         self.banned_groups = ["1XBET", "MEGA", "MTZ", "Whistler", "WOTT", "Taylor.D", "HELD", "FSX", "FuN", "MagicX", "w00t", "PaTroL", "BB",
                               "266ers", "GTF", "JellyfinPlex", "2BA", "FritzBox"]
-        pass
-
-    # The section below can be deleted if no changes are needed, as everything else is handled in UNIT3D.py
-    # If advanced changes are required, copy the necessary functions from UNIT3D.py here
-    # For example, if you need to modify the description, copy and paste the 'get_description' function and adjust it accordingly



class RHD(UNIT3D):
INVALID_TAG_PATTERN = re.compile(r"(nogrp|nogroup|unknown|unk)", re.IGNORECASE)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

INVALID_TAG_PATTERN lacks word boundaries/anchors — "unk" matches as a substring, causing false positives.

re.search(r"(nogrp|nogroup|unknown|unk)", tag, re.IGNORECASE) will match tags that merely contain these strings. For example, legitimate group names like "PUNK", "CHUNK", "JUNK", "DUNK", or "TRUNK" would all match the unk alternative and be incorrectly rejected.

Suggested fix — anchor to full string match
-    INVALID_TAG_PATTERN = re.compile(r"(nogrp|nogroup|unknown|unk)", re.IGNORECASE)
+    INVALID_TAG_PATTERN = re.compile(r"^(nogrp|nogroup|unknown|unk)$", re.IGNORECASE)

With ^$ anchors, search on the full tag string ensures only exact matches are rejected. Alternatively, use \b word boundaries if partial-tag filtering is intentional.

Also applies to: 344-349

🤖 Prompt for AI Agents
In `@src/trackers/RHD.py` at line 19, INVALID_TAG_PATTERN currently matches
substrings (e.g., "unk" matches "TRUNK"), causing false positives; update the
regex in INVALID_TAG_PATTERN in src/trackers/RHD.py (and the similar pattern
used around lines 344-349) to anchor to the whole tag (e.g., use
^(nogrp|nogroup|unknown|unk)$) or, if you intend to allow word-boundary matches,
wrap alternatives with \b (e.g., \b(nogrp|nogroup|unknown|unk)\b); modify the
compiled pattern(s) accordingly so tag validation uses full-string/word-boundary
matching instead of substring matching.

Comment on lines +386 to +428
async def get_additional_checks(self, meta: Meta) -> bool:
"""make sure the upload complies with the RHD rules"""
# Uploading MIC, CAM, TS, or LD releases, is prohibited.
prohib_markers = ["MIC", "CAM", "TS", "TELESYNC", "LD", "LINE"]
basename = self.get_basename(meta)
# Split on delimiters (dot, hyphen, underscore) or whitespace so tags like "LD" only match as separate tokens
basename_up = [tok for tok in re.split(r'[\.\s_-]+', str(basename).upper()) if tok]
if any(x in basename_up for x in prohib_markers):
console.print("[bold red]Uploading MIC, CAM, TS or LD releases, is prohibited")
if not cli_ui.ask_yes_no("Do you want to upload anyway?", default=False):
return False

# Uploading upscaled releases is prohibited. Exception: The release is from a group on the upscale whitelist
if "UPSCALE" in basename_up:
console.print(f"[bold red]Uploading upscaled releases is prohibited, unless the group is is whitelisted {self.base_url}/wikis/17")
if not cli_ui.ask_yes_no("Do you want to upload anyway?", default=False):
return False

# Uploading SD content is not allowed. Exception: No HD version exists. Check release databases beforehand to ensure an HD version doesn't exist
if meta.get("resolution") in ["384p", "480p", "480i", "540p","576p", "576i"]:
console.print(f"[bold red]Uploading SD releases is not allowed on {self.tracker}, unless no HD version exists.")
console.print("[bold red]Please check release databases beforehand to be sure.")
if not cli_ui.ask_yes_no("Do you want to upload anyway?", default=False):
return False

# Uploads must contain a German audio track. Exception: The release was requested in its original language.
if not self._has_german_audio(meta) and not meta.get("requested_release", False):
console.print("[bold red]Uploads must contain a German audio track, unless the release was requested in its original language.")
if not cli_ui.ask_yes_no("Do you want to upload anyway?", default=False):
return False

# check for samples, proofs, and images in the upload directory
filelist = meta.get("filelist", [])
if any(
str(file).lower().endswith((".jpg", ".jpeg", ".png", ".bmp", ".gif", ".webp", ".tiff", ".pdf"))
or "sample" in str(file).lower()
or "proof" in str(file).lower()
for file in filelist
):
console.print("[bold red]Uploads containing samples, proofs, and images are prohibited.[/bold red]")
if not cli_ui.ask_yes_no("Do you want to upload anyway?", default=False):
return False
return True
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for unattended mode handling patterns with cli_ui.ask_yes_no
rg -n "unattended" src/trackers/ -A 5 -B 2 | head -120

Repository: Audionut/Upload-Assistant

Length of output: 8967


🏁 Script executed:

#!/bin/bash
# Check how cli_ui.ask_yes_no is used across other trackers
rg -n "ask_yes_no" src/trackers/ -B 3 | head -150

Repository: Audionut/Upload-Assistant

Length of output: 13497


🏁 Script executed:

#!/bin/bash
# Look at the cli_ui module implementation
find . -name "*cli*" -o -name "*ui*" | grep -E "\.(py|js)$" | head -20

Repository: Audionut/Upload-Assistant

Length of output: 85


🏁 Script executed:

#!/bin/bash
# Check RHD.py for any unattended handling already
cat -n src/trackers/RHD.py | grep -A 5 -B 5 "unattended"

Repository: Audionut/Upload-Assistant

Length of output: 51


Interactive prompts will block indefinitely in unattended/automated mode.

All cli_ui.ask_yes_no calls at lines 395, 401, 408, 414, and 426 will hang if the tool runs unattended. Other trackers consistently check meta.get('unattended') before prompting—apply the same pattern here:

Example pattern
         if any(x in basename_up for x in prohib_markers):
             console.print("[bold red]Uploading MIC, CAM, TS or LD releases, is prohibited")
-            if not cli_ui.ask_yes_no("Do you want to upload anyway?", default=False):
+            if meta.get('unattended') or not cli_ui.ask_yes_no("Do you want to upload anyway?", default=False):
                 return False

Apply to all five interactive prompts in this method.

🤖 Prompt for AI Agents
In `@src/trackers/RHD.py` around lines 386 - 428, The method get_additional_checks
uses interactive cli_ui.ask_yes_no calls (cli_ui.ask_yes_no) which will block in
unattended runs; update each prompt site in get_additional_checks to first check
meta.get("unattended", False) and, if True, avoid prompting and return the
non-confirmed result (i.e., treat the implicit answer as "no" by logging the
same warning and returning False). Apply this pattern at every place where
cli_ui.ask_yes_no is called (the prohib_markers check, UPSCALE check, SD
resolution check, German audio check, and samples/images check) so the function
never blocks when meta["unattended"] is truthy.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add Support for RocketHD (RHD)

1 participant