Skip to content
This repository was archived by the owner on Jun 14, 2026. It is now read-only.
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion data/example-config.py
Original file line number Diff line number Diff line change
Expand Up @@ -536,8 +536,14 @@
# Instead of using the tracker acronym for folder name when sym/hard linking, you can use a custom name
"link_dir_name": "",
"username": "",
"password": "",
"passkey": "",
"uploader_name": "https://filelist.io/Custom_Announce_URL",
# Credentials for https://up.img4k.net/ and https://bd.img4k.net/. Should be the same credentials
"fltools": {
"user": "",
"pass": ""
},
"uploader_name": "",
"anon": False,
},
"FNP": {
Expand Down
22 changes: 14 additions & 8 deletions src/trackers/FL.py
Original file line number Diff line number Diff line change
Expand Up @@ -387,15 +387,15 @@ async def edit_desc(self, meta: dict[str, Any]) -> None:
desc = desc.replace('[img]', '[img]').replace('[/img]', '[/img]')
desc = re.sub(r"(\[img=\d+)]", "[img]", desc, flags=re.IGNORECASE)
if meta['is_disc'] != 'BDMV':
url = "https://up.img4k.net/api/description"
url = "https://up.img4k.net"
mediainfo_path = f"{meta['base_dir']}/tmp/{meta['uuid']}/MEDIAINFO_CLEANPATH.txt"
async with aiofiles.open(mediainfo_path, encoding='utf-8') as mi_file:
data = {
'mediainfo': await mi_file.read(),
}
if int(meta['imdb_id']) != 0:
data['imdbURL'] = f"tt{meta['imdb_id']}"
screen_glob = [os.path.basename(f) for f in glob.glob(os.path.join(f"{meta['base_dir']}/tmp/{meta['uuid']}", f"{meta['filename']}-*.png"))]
if meta.get('imdb_id') != 0:
data['imdb_url'] = str(meta.get('imdb_info', {}).get('imdb_url', '')) + '/'
Comment on lines +396 to +397

@coderabbitai coderabbitai Bot Mar 23, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Fix IMDb URL gating to avoid sending malformed imdb_url.

At Line 396, meta.get('imdb_id') != 0 is unsafe for string IDs (e.g., "0"), and Line 397 can send "/" when imdb_info.imdb_url is missing.

Proposed fix
-                if meta.get('imdb_id') != 0:
-                    data['imdb_url'] = str(meta.get('imdb_info', {}).get('imdb_url', '')) + '/'
+                imdb_id_value = str(meta.get('imdb_id', '0')).strip()
+                imdb_info = meta.get('imdb_info')
+                imdb_info_dict = cast(dict[str, Any], imdb_info) if isinstance(imdb_info, dict) else {}
+                imdb_url = str(imdb_info_dict.get('imdb_url', '')).strip()
+                if imdb_id_value.isdigit() and int(imdb_id_value) != 0 and imdb_url:
+                    data['imdb_url'] = imdb_url.rstrip('/') + '/'
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/trackers/FL.py` around lines 396 - 397, The imdb gating currently uses
meta.get('imdb_id') != 0 which fails for string IDs like "0" and unconditionally
appends '/' even when imdb_info.imdb_url is missing; update the logic (in the
code handling meta, imdb_id, imdb_info and data['imdb_url']) to: 1) treat
imdb_id as absent if it's 0, "0", empty or None (e.g., convert to str and check
truthiness or explicitly check both 0 and "0"), and 2) only set data['imdb_url']
when imdb_info.get('imdb_url') is a non-empty string, adding a trailing '/' only
if the URL exists and doesn't already end with '/'.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

imdb_id is an int, but the forward slash should be addressed.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@Audionut, understood — since imdb_id is always an int, the condition is fine as-is. The only issue is the unconditional / append when imdb_url is empty. Here's a minimal fix:

if meta.get('imdb_id') != 0:
    imdb_url = str(meta.get('imdb_info', {}).get('imdb_url', '')).strip()
    if imdb_url:
        data['imdb_url'] = imdb_url.rstrip('/') + '/'

This ensures data['imdb_url'] is only set when the URL is actually present, and avoids double slashes if the URL already ends with one.

(ᐢ•ᴗ•ᐢ)∫


✏️ Learnings added
Learnt from: Audionut
Repo: Audionut/Upload-Assistant PR: 1317
File: src/trackers/FL.py:396-397
Timestamp: 2026-03-23T10:00:38.629Z
Learning: In src/trackers/FL.py, meta['imdb_id'] is always an integer. Direct integer comparison (e.g., meta.get('imdb_id') != 0) is safe without needing to convert to string first.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: Audionut
Repo: Audionut/Upload-Assistant PR: 1007
File: src/trackers/RTF.py:38-38
Timestamp: 2025-12-01T01:43:01.865Z
Learning: In src/trackers/RTF.py, the forum_link (https://retroflix.club/forums.php?action=viewtopic&topicid=3619) is intentionally passed as the signature parameter to DescriptionBuilder.unit3d_edit_desc() for historical purposes, which is RTF-specific behavior.

Learnt from: Audionut
Repo: Audionut/Upload-Assistant PR: 1025
File: src/trackers/TVC.py:860-873
Timestamp: 2025-12-09T10:08:42.221Z
Learning: In the Upload-Assistant codebase, metadata dictionary keys with `_id` suffix (e.g., `meta['imdb_id']`, `meta['tmdb_id']`, `meta['tvdb_id']`, `meta['mal_id']`) are integer stores of ID values, while unsuffixed versions like `meta['imdb']` and `meta['tmdb']` may be string versions or used for different purposes such as API data. Code should use the `_id` suffixed versions when checking for presence of integer IDs.

Learnt from: Audionut
Repo: Audionut/Upload-Assistant PR: 1201
File: src/get_desc.py:579-587
Timestamp: 2026-02-02T07:44:14.622Z
Learning: In the Upload-Assistant codebase, meta['tvmaze_episode_data'] is only set when meta['tvmaze_id'] is present; therefore, accessing meta['tvmaze_id'] inside branches guarded by 'tvmaze_episode_data' is safe.

Learnt from: Audionut
Repo: Audionut/Upload-Assistant PR: 1201
File: src/get_desc.py:579-587
Timestamp: 2026-02-02T07:41:52.301Z
Learning: In the Upload-Assistant codebase, meta['tvdb_episode_data'] is only set when meta['tvdb_id'] is present; therefore, accessing meta['tvdb_id'] inside branches guarded by 'tvdb_episode_data' is safe.

Learnt from: Audionut
Repo: Audionut/Upload-Assistant PR: 1025
File: src/trackers/TVC.py:32-86
Timestamp: 2025-12-14T00:20:21.219Z
Learning: In src/trackers/TVC.py, the genres argument passed to get_cat_id() is a string (comma-separated) rather than a list or other type. Remove or avoid code paths that handle list inputs for this parameter; assume and validate string input, adjust type hints if present, and, if possible, add a unit test to ensure a string is passed.

Learnt from: Audionut
Repo: Audionut/Upload-Assistant PR: 1081
File: src/trackers/CBR.py:84-91
Timestamp: 2025-12-27T13:04:08.659Z
Learning: In tracker modules under src/trackers, meta['aka'] is a guaranteed key and can be accessed directly (meta['aka']) without using get(). This improves clarity and small performance overhead. Only rely on this when you are certain the key is always present; if a tracker could ever omit it, add a guard or a clear default to avoid KeyError.

Learnt from: Audionut
Repo: Audionut/Upload-Assistant PR: 1081
File: src/trackers/CBR.py:94-94
Timestamp: 2025-12-27T13:04:22.071Z
Learning: In Python codebases where a dict (e.g., meta) is guaranteed to always include a specific key (e.g., 'tag'), direct dictionary access meta['tag'] is safe and preferred over meta.get('tag') for performance and readability. Ensure the key is indeed always initialized in all code paths; if there's any doubt, consider defensive checks or documentation to avoid KeyError.

Learnt from: Audionut
Repo: Audionut/Upload-Assistant PR: 1114
File: src/manualpackage.py:16-23
Timestamp: 2026-01-11T05:07:09.469Z
Learning: In Python type hints for read-only mappings (e.g., DEFAULT_CONFIG, TRACKER_CONFIG), prefer Mapping[str, Any] over Dict[str, Any] to signal immutability intent. Use from typing import Mapping; annotate with Mapping[str, Any] while allowing concrete dicts at runtime. Do not rely on isinstance(value, dict) for immutability checks; this is a type-hint convention, not a runtime guarantee.

Learnt from: Audionut
Repo: Audionut/Upload-Assistant PR: 1114
File: src/manualpackage.py:16-23
Timestamp: 2026-01-11T05:07:09.469Z
Learning: In the Upload-Assistant repository, avoid flagging minor or stylistic issues (e.g., ValueError vs TypeError for type validation) unless they represent actual bugs or significant problems. Follow Audionut's preference to focus on substantive issues rather than minor concerns across Python files.

Learnt from: wasserrutschentester
Repo: Audionut/Upload-Assistant PR: 1250
File: src/trackers/RHD.py:269-271
Timestamp: 2026-02-13T20:15:30.786Z
Learning: In src/trackers/RHD.py, the 'GERMAN SUBBED' tag is a standalone tag that overwrites audio_lang_str when German subtitles exist without German audio. It indicates 'OV (original version) audio + German subtitles' according to RocketHD naming conventions, so the audio language information should be overwritten, not appended. Apply this rule consistently to similar trackers and ensure the tag replaces existing language metadata rather than augmenting it.

screen_glob = [os.path.basename(f) for f in glob.glob(os.path.join(f"{meta['base_dir']}/tmp/{meta['uuid']}", "*.png"))]

@coderabbitai coderabbitai Bot Mar 23, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

*.png glob is too broad and may upload non-screenshot assets.

Using *.png in the temp folder can include files like FILE*, PLAYLIST*, or POSTER*, which are explicitly filtered elsewhere in the codebase.

Proposed fix
-                screen_glob = [os.path.basename(f) for f in glob.glob(os.path.join(f"{meta['base_dir']}/tmp/{meta['uuid']}", "*.png"))]
+                all_png = [os.path.basename(f) for f in glob.glob(os.path.join(f"{meta['base_dir']}/tmp/{meta['uuid']}", "*.png"))]
+                unwanted_prefixes = ("FILE", "PLAYLIST", "POSTER")
+                screen_glob = [name for name in all_png if not name.upper().startswith(unwanted_prefixes)]
@@
-                    screen_glob = [os.path.basename(f) for f in glob.glob(os.path.join(f"{meta['base_dir']}/tmp/{meta['uuid']}", "*.png"))]
+                    all_png = [os.path.basename(f) for f in glob.glob(os.path.join(f"{meta['base_dir']}/tmp/{meta['uuid']}", "*.png"))]
+                    unwanted_prefixes = ("FILE", "PLAYLIST", "POSTER")
+                    screen_glob = [name for name in all_png if not name.upper().startswith(unwanted_prefixes)]

Also applies to: 422-422

🧰 Tools
🪛 Ruff (0.15.6)

[error] 398-398: Probable insecure usage of temporary file or directory: "/tmp/"

(S108)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/trackers/FL.py` at line 398, The glob pattern in the screen_glob
assignment is too broad and picks up non-screenshot PNGs; update the glob.glob
call (used where screen_glob is computed for meta['base_dir'] and meta['uuid'])
to either narrow the pattern to the actual screenshot filename convention (e.g.,
use the screenshot prefix used elsewhere) or post-filter the results to exclude
known non-screenshot prefixes like "FILE", "PLAYLIST", and "POSTER"; ensure the
same change is applied to the other occurrence that also builds screen_glob.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

yes, see

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Look at the screenshot naming convention around line 692 in uploadscreens.py
sed -n '680,710p' src/uploadscreens.py

Length of output: 1342


@Audionut, thanks for the reference. The uploadscreens.py approach uses *.png followed by explicit exclusion of FILE*, PLAYLIST*, and POSTER*. The same filtering should be applied to both screen_glob assignments in FL.py. Here's a fix consistent with that pattern:

# Non-BDMV (line ~398)
all_png = glob.glob(os.path.join(f"{meta['base_dir']}/tmp/{meta['uuid']}", "*.png"))
unwanted_patterns = ["FILE*", "PLAYLIST*", "POSTER*"]
unwanted_files: set[str] = set()
for pattern in unwanted_patterns:
    unwanted_files.update(
        os.path.basename(f) for f in glob.glob(os.path.join(f"{meta['base_dir']}/tmp/{meta['uuid']}", pattern))
    )
screen_glob = [os.path.basename(f) for f in all_png if os.path.basename(f) not in unwanted_files]
# BDMV (line ~422)
all_png = glob.glob(os.path.join(f"{meta['base_dir']}/tmp/{meta['uuid']}", "*.png"))
unwanted_patterns = ["FILE*", "PLAYLIST*", "POSTER*"]
unwanted_files: set[str] = set()
for pattern in unwanted_patterns:
    unwanted_files.update(
        os.path.basename(f) for f in glob.glob(os.path.join(f"{meta['base_dir']}/tmp/{meta['uuid']}", pattern))
    )
screen_glob = [os.path.basename(f) for f in all_png if os.path.basename(f) not in unwanted_files]

This mirrors the uploadscreens.py logic while adapting it to work with the full path prefix used in FL.py.

(ᵔᴥᵔ)


🧠 Learnings used
Learnt from: Audionut
Repo: Audionut/Upload-Assistant PR: 1317
File: src/trackers/FL.py:396-397
Timestamp: 2026-03-23T10:00:38.629Z
Learning: In src/trackers/FL.py, meta['imdb_id'] is always an integer. Direct integer comparison (e.g., meta.get('imdb_id') != 0) is safe without needing to convert to string first.

Learnt from: Audionut
Repo: Audionut/Upload-Assistant PR: 1007
File: src/trackers/RTF.py:38-38
Timestamp: 2025-12-01T01:43:01.865Z
Learning: In src/trackers/RTF.py, the forum_link (https://retroflix.club/forums.php?action=viewtopic&topicid=3619) is intentionally passed as the signature parameter to DescriptionBuilder.unit3d_edit_desc() for historical purposes, which is RTF-specific behavior.

Learnt from: Lusephur
Repo: Audionut/Upload-Assistant PR: 1023
File: src/trackers/TVC.py:484-487
Timestamp: 2025-11-23T15:26:45.408Z
Learning: In the Upload-Assistant codebase, particularly for src/trackers/TVC.py, do not mark error handling suggestions as critical severity when the code has been tested and is working successfully across multiple scenarios. The user (Lusephur) has thoroughly tested TVC uploads including TV episodes, TV packs, foreign content, and movies.

Learnt from: Audionut
Repo: Audionut/Upload-Assistant PR: 1114
File: src/manualpackage.py:16-23
Timestamp: 2026-01-11T05:07:09.469Z
Learning: In the Upload-Assistant repository, avoid flagging minor or stylistic issues (e.g., ValueError vs TypeError for type validation) unless they represent actual bugs or significant problems. Follow Audionut's preference to focus on substantive issues rather than minor concerns across Python files.

Learnt from: Audionut
Repo: Audionut/Upload-Assistant PR: 991
File: src/trackers/THR.py:263-263
Timestamp: 2025-11-11T08:09:03.403Z
Learning: For src/trackers/THR.py in Audionut's pull requests, do not flag issues about mixing synchronous requests calls (like requests.post, requests.get) with async functions. The file needs a refactor, and these issues should be ignored until then.

Learnt from: Audionut
Repo: Audionut/Upload-Assistant PR: 1118
File: upload.py:1181-1181
Timestamp: 2026-01-11T06:59:20.992Z
Learning: In the Upload-Assistant codebase, particularly in upload.py around line 1181, the hardcoded `successful_trackers = 10` before the optional secondary dupe check (dupe_again) is intentional design. The real threshold check happens earlier (~line 377), and this value ensures uploads proceed when no secondary check is performed. It gets overwritten when dupe_again is True.

Learnt from: Audionut
Repo: Audionut/Upload-Assistant PR: 1025
File: src/trackers/TVC.py:32-86
Timestamp: 2025-12-14T00:20:21.219Z
Learning: In src/trackers/TVC.py, the genres argument passed to get_cat_id() is a string (comma-separated) rather than a list or other type. Remove or avoid code paths that handle list inputs for this parameter; assume and validate string input, adjust type hints if present, and, if possible, add a unit test to ensure a string is passed.

Learnt from: Audionut
Repo: Audionut/Upload-Assistant PR: 1081
File: src/trackers/CBR.py:84-91
Timestamp: 2025-12-27T13:04:08.659Z
Learning: In tracker modules under src/trackers, meta['aka'] is a guaranteed key and can be accessed directly (meta['aka']) without using get(). This improves clarity and small performance overhead. Only rely on this when you are certain the key is always present; if a tracker could ever omit it, add a guard or a clear default to avoid KeyError.

Learnt from: Audionut
Repo: Audionut/Upload-Assistant PR: 1081
File: src/trackers/CBR.py:94-94
Timestamp: 2025-12-27T13:04:22.071Z
Learning: In Python codebases where a dict (e.g., meta) is guaranteed to always include a specific key (e.g., 'tag'), direct dictionary access meta['tag'] is safe and preferred over meta.get('tag') for performance and readability. Ensure the key is indeed always initialized in all code paths; if there's any doubt, consider defensive checks or documentation to avoid KeyError.

Learnt from: Audionut
Repo: Audionut/Upload-Assistant PR: 1114
File: src/manualpackage.py:16-23
Timestamp: 2026-01-11T05:07:09.469Z
Learning: In Python type hints for read-only mappings (e.g., DEFAULT_CONFIG, TRACKER_CONFIG), prefer Mapping[str, Any] over Dict[str, Any] to signal immutability intent. Use from typing import Mapping; annotate with Mapping[str, Any] while allowing concrete dicts at runtime. Do not rely on isinstance(value, dict) for immutability checks; this is a type-hint convention, not a runtime guarantee.

Learnt from: wasserrutschentester
Repo: Audionut/Upload-Assistant PR: 1250
File: src/trackers/RHD.py:269-271
Timestamp: 2026-02-13T20:15:30.786Z
Learning: In src/trackers/RHD.py, the 'GERMAN SUBBED' tag is a standalone tag that overwrites audio_lang_str when German subtitles exist without German audio. It indicates 'OV (original version) audio + German subtitles' according to RocketHD naming conventions, so the audio language information should be overwritten, not appended. Apply this rule consistently to similar trackers and ensure the tag replaces existing language metadata rather than augmenting it.

files: list[tuple[str, tuple[str, bytes, str]]] = []
for screen in screen_glob:
screen_path = f"{meta['base_dir']}/tmp/{meta['uuid']}/{screen}"
Expand All @@ -404,7 +404,10 @@ async def edit_desc(self, meta: dict[str, Any]) -> None:
files.append(('images', (os.path.basename(screen), image_bytes, 'image/png')))
async with httpx.AsyncClient(timeout=30.0) as client:
response = await client.post(url, data=data, files=files, auth=(self.fltools['user'], self.fltools['pass']))
final_desc = response.text.replace('\r\n', '\n')
soup = BeautifulSoup(response.text, 'html.parser')
pre_tag = soup.find('pre', {'id': 'description-text'})
final_desc = pre_tag.get_text() if pre_tag else response.text
final_desc = final_desc.replace('\r\n', '\n')
else:
# BD Description Generator
bd_summary_path = f"{meta['base_dir']}/tmp/{meta['uuid']}/BD_SUMMARY_EXT.txt"
Expand All @@ -415,8 +418,8 @@ async def edit_desc(self, meta: dict[str, Any]) -> None:
final_desc = final_desc.replace('DISC INFO:', '[pre][quote=BD_Info][b][color=#FF0000]DISC INFO:[/color][/b]').replace('PLAYLIST REPORT:', '[b][color=#FF0000]PLAYLIST REPORT:[/color][/b]').replace('VIDEO:', '[b][color=#FF0000]VIDEO:[/color][/b]').replace('AUDIO:', '[b][color=#FF0000]AUDIO:[/color][/b]').replace('SUBTITLES:', '[b][color=#FF0000]SUBTITLES:[/color][/b]')
final_desc += "[/pre][/quote]\n" # Closed bbcode tags
# Upload screens and append to the end of the description
url = "https://up.img4k.net/api/description"
screen_glob = [os.path.basename(f) for f in glob.glob(os.path.join(f"{meta['base_dir']}/tmp/{meta['uuid']}", f"{meta['filename']}-*.png"))]
url = "https://bd.img4k.net/"
screen_glob = [os.path.basename(f) for f in glob.glob(os.path.join(f"{meta['base_dir']}/tmp/{meta['uuid']}", "*.png"))]
files: list[tuple[str, tuple[str, bytes, str]]] = []
for screen in screen_glob:
screen_path = f"{meta['base_dir']}/tmp/{meta['uuid']}/{screen}"
Expand All @@ -425,7 +428,10 @@ async def edit_desc(self, meta: dict[str, Any]) -> None:
files.append(('images', (os.path.basename(screen), image_bytes, 'image/png')))
async with httpx.AsyncClient(timeout=30.0) as client:
response = await client.post(url, files=files, auth=(self.fltools['user'], self.fltools['pass']))
final_desc += response.text.replace('\r\n', '\n')
soup = BeautifulSoup(response.text, 'html.parser')
pre_tag = soup.find('pre', {'id': 'description-text'})
final_desc = pre_tag.get_text() if pre_tag else response.text
final_desc = final_desc.replace('\r\n', '\n')
await descfile.write(final_desc)

if self.signature is not None:
Expand Down
Loading