quasarr 2.4.9__tar.gz → 2.4.10__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of quasarr might be problematic. Click here for more details.

Files changed (95) hide show
  1. {quasarr-2.4.9 → quasarr-2.4.10}/.github/workflows/PullRequests.yml +36 -136
  2. {quasarr-2.4.9 → quasarr-2.4.10}/.github/workflows/Release.yml +22 -5
  3. quasarr-2.4.9/docker/dev-setup.md → quasarr-2.4.10/CONTRIBUTING.md +3 -16
  4. {quasarr-2.4.9 → quasarr-2.4.10}/PKG-INFO +3 -2
  5. {quasarr-2.4.9 → quasarr-2.4.10}/README.md +2 -1
  6. quasarr-2.4.10/maintenance.py +284 -0
  7. {quasarr-2.4.9 → quasarr-2.4.10}/quasarr/providers/version.py +1 -1
  8. {quasarr-2.4.9 → quasarr-2.4.10}/.github/FUNDING.yml +0 -0
  9. {quasarr-2.4.9 → quasarr-2.4.10}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  10. {quasarr-2.4.9 → quasarr-2.4.10}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  11. {quasarr-2.4.9 → quasarr-2.4.10}/.github/workflows/HostnameRedaction.yml +0 -0
  12. {quasarr-2.4.9 → quasarr-2.4.10}/.gitignore +0 -0
  13. {quasarr-2.4.9 → quasarr-2.4.10}/LICENSE +0 -0
  14. {quasarr-2.4.9 → quasarr-2.4.10}/Quasarr.png +0 -0
  15. {quasarr-2.4.9 → quasarr-2.4.10}/Quasarr.py +0 -0
  16. {quasarr-2.4.9 → quasarr-2.4.10}/docker/Dockerfile +0 -0
  17. {quasarr-2.4.9 → quasarr-2.4.10}/docker/dev-services-compose.yml +0 -0
  18. {quasarr-2.4.9 → quasarr-2.4.10}/docker/docker-compose.yml +0 -0
  19. {quasarr-2.4.9 → quasarr-2.4.10}/pyproject.toml +0 -0
  20. {quasarr-2.4.9 → quasarr-2.4.10}/quasarr/__init__.py +0 -0
  21. {quasarr-2.4.9 → quasarr-2.4.10}/quasarr/api/__init__.py +0 -0
  22. {quasarr-2.4.9 → quasarr-2.4.10}/quasarr/api/arr/__init__.py +0 -0
  23. {quasarr-2.4.9 → quasarr-2.4.10}/quasarr/api/captcha/__init__.py +0 -0
  24. {quasarr-2.4.9 → quasarr-2.4.10}/quasarr/api/config/__init__.py +0 -0
  25. {quasarr-2.4.9 → quasarr-2.4.10}/quasarr/api/packages/__init__.py +0 -0
  26. {quasarr-2.4.9 → quasarr-2.4.10}/quasarr/api/sponsors_helper/__init__.py +0 -0
  27. {quasarr-2.4.9 → quasarr-2.4.10}/quasarr/api/statistics/__init__.py +0 -0
  28. {quasarr-2.4.9 → quasarr-2.4.10}/quasarr/downloads/__init__.py +0 -0
  29. {quasarr-2.4.9 → quasarr-2.4.10}/quasarr/downloads/linkcrypters/__init__.py +0 -0
  30. {quasarr-2.4.9 → quasarr-2.4.10}/quasarr/downloads/linkcrypters/al.py +0 -0
  31. {quasarr-2.4.9 → quasarr-2.4.10}/quasarr/downloads/linkcrypters/filecrypt.py +0 -0
  32. {quasarr-2.4.9 → quasarr-2.4.10}/quasarr/downloads/linkcrypters/hide.py +0 -0
  33. {quasarr-2.4.9 → quasarr-2.4.10}/quasarr/downloads/packages/__init__.py +0 -0
  34. {quasarr-2.4.9 → quasarr-2.4.10}/quasarr/downloads/sources/__init__.py +0 -0
  35. {quasarr-2.4.9 → quasarr-2.4.10}/quasarr/downloads/sources/al.py +0 -0
  36. {quasarr-2.4.9 → quasarr-2.4.10}/quasarr/downloads/sources/by.py +0 -0
  37. {quasarr-2.4.9 → quasarr-2.4.10}/quasarr/downloads/sources/dd.py +0 -0
  38. {quasarr-2.4.9 → quasarr-2.4.10}/quasarr/downloads/sources/dj.py +0 -0
  39. {quasarr-2.4.9 → quasarr-2.4.10}/quasarr/downloads/sources/dl.py +0 -0
  40. {quasarr-2.4.9 → quasarr-2.4.10}/quasarr/downloads/sources/dt.py +0 -0
  41. {quasarr-2.4.9 → quasarr-2.4.10}/quasarr/downloads/sources/dw.py +0 -0
  42. {quasarr-2.4.9 → quasarr-2.4.10}/quasarr/downloads/sources/he.py +0 -0
  43. {quasarr-2.4.9 → quasarr-2.4.10}/quasarr/downloads/sources/mb.py +0 -0
  44. {quasarr-2.4.9 → quasarr-2.4.10}/quasarr/downloads/sources/nk.py +0 -0
  45. {quasarr-2.4.9 → quasarr-2.4.10}/quasarr/downloads/sources/nx.py +0 -0
  46. {quasarr-2.4.9 → quasarr-2.4.10}/quasarr/downloads/sources/sf.py +0 -0
  47. {quasarr-2.4.9 → quasarr-2.4.10}/quasarr/downloads/sources/sj.py +0 -0
  48. {quasarr-2.4.9 → quasarr-2.4.10}/quasarr/downloads/sources/sl.py +0 -0
  49. {quasarr-2.4.9 → quasarr-2.4.10}/quasarr/downloads/sources/wd.py +0 -0
  50. {quasarr-2.4.9 → quasarr-2.4.10}/quasarr/downloads/sources/wx.py +0 -0
  51. {quasarr-2.4.9 → quasarr-2.4.10}/quasarr/providers/__init__.py +0 -0
  52. {quasarr-2.4.9 → quasarr-2.4.10}/quasarr/providers/auth.py +0 -0
  53. {quasarr-2.4.9 → quasarr-2.4.10}/quasarr/providers/cloudflare.py +0 -0
  54. {quasarr-2.4.9 → quasarr-2.4.10}/quasarr/providers/hostname_issues.py +0 -0
  55. {quasarr-2.4.9 → quasarr-2.4.10}/quasarr/providers/html_images.py +0 -0
  56. {quasarr-2.4.9 → quasarr-2.4.10}/quasarr/providers/html_templates.py +0 -0
  57. {quasarr-2.4.9 → quasarr-2.4.10}/quasarr/providers/imdb_metadata.py +0 -0
  58. {quasarr-2.4.9 → quasarr-2.4.10}/quasarr/providers/jd_cache.py +0 -0
  59. {quasarr-2.4.9 → quasarr-2.4.10}/quasarr/providers/log.py +0 -0
  60. {quasarr-2.4.9 → quasarr-2.4.10}/quasarr/providers/myjd_api.py +0 -0
  61. {quasarr-2.4.9 → quasarr-2.4.10}/quasarr/providers/notifications.py +0 -0
  62. {quasarr-2.4.9 → quasarr-2.4.10}/quasarr/providers/obfuscated.py +0 -0
  63. {quasarr-2.4.9 → quasarr-2.4.10}/quasarr/providers/sessions/__init__.py +0 -0
  64. {quasarr-2.4.9 → quasarr-2.4.10}/quasarr/providers/sessions/al.py +0 -0
  65. {quasarr-2.4.9 → quasarr-2.4.10}/quasarr/providers/sessions/dd.py +0 -0
  66. {quasarr-2.4.9 → quasarr-2.4.10}/quasarr/providers/sessions/dl.py +0 -0
  67. {quasarr-2.4.9 → quasarr-2.4.10}/quasarr/providers/sessions/nx.py +0 -0
  68. {quasarr-2.4.9 → quasarr-2.4.10}/quasarr/providers/shared_state.py +0 -0
  69. {quasarr-2.4.9 → quasarr-2.4.10}/quasarr/providers/statistics.py +0 -0
  70. {quasarr-2.4.9 → quasarr-2.4.10}/quasarr/providers/utils.py +0 -0
  71. {quasarr-2.4.9 → quasarr-2.4.10}/quasarr/providers/web_server.py +0 -0
  72. {quasarr-2.4.9 → quasarr-2.4.10}/quasarr/search/__init__.py +0 -0
  73. {quasarr-2.4.9 → quasarr-2.4.10}/quasarr/search/sources/__init__.py +0 -0
  74. {quasarr-2.4.9 → quasarr-2.4.10}/quasarr/search/sources/al.py +0 -0
  75. {quasarr-2.4.9 → quasarr-2.4.10}/quasarr/search/sources/by.py +0 -0
  76. {quasarr-2.4.9 → quasarr-2.4.10}/quasarr/search/sources/dd.py +0 -0
  77. {quasarr-2.4.9 → quasarr-2.4.10}/quasarr/search/sources/dj.py +0 -0
  78. {quasarr-2.4.9 → quasarr-2.4.10}/quasarr/search/sources/dl.py +0 -0
  79. {quasarr-2.4.9 → quasarr-2.4.10}/quasarr/search/sources/dt.py +0 -0
  80. {quasarr-2.4.9 → quasarr-2.4.10}/quasarr/search/sources/dw.py +0 -0
  81. {quasarr-2.4.9 → quasarr-2.4.10}/quasarr/search/sources/fx.py +0 -0
  82. {quasarr-2.4.9 → quasarr-2.4.10}/quasarr/search/sources/he.py +0 -0
  83. {quasarr-2.4.9 → quasarr-2.4.10}/quasarr/search/sources/mb.py +0 -0
  84. {quasarr-2.4.9 → quasarr-2.4.10}/quasarr/search/sources/nk.py +0 -0
  85. {quasarr-2.4.9 → quasarr-2.4.10}/quasarr/search/sources/nx.py +0 -0
  86. {quasarr-2.4.9 → quasarr-2.4.10}/quasarr/search/sources/sf.py +0 -0
  87. {quasarr-2.4.9 → quasarr-2.4.10}/quasarr/search/sources/sj.py +0 -0
  88. {quasarr-2.4.9 → quasarr-2.4.10}/quasarr/search/sources/sl.py +0 -0
  89. {quasarr-2.4.9 → quasarr-2.4.10}/quasarr/search/sources/wd.py +0 -0
  90. {quasarr-2.4.9 → quasarr-2.4.10}/quasarr/search/sources/wx.py +0 -0
  91. {quasarr-2.4.9 → quasarr-2.4.10}/quasarr/storage/__init__.py +0 -0
  92. {quasarr-2.4.9 → quasarr-2.4.10}/quasarr/storage/config.py +0 -0
  93. {quasarr-2.4.9 → quasarr-2.4.10}/quasarr/storage/setup.py +0 -0
  94. {quasarr-2.4.9 → quasarr-2.4.10}/quasarr/storage/sqlite_database.py +0 -0
  95. {quasarr-2.4.9 → quasarr-2.4.10}/uv.lock +0 -0
@@ -17,9 +17,9 @@ jobs:
17
17
  name: Check & Auto-Fix
18
18
  runs-on: ubuntu-latest
19
19
  permissions:
20
- contents: write # To push commits
21
- pull-requests: write # To comment on PRs
22
- actions: write # To trigger the next run
20
+ contents: write
21
+ pull-requests: write
22
+ actions: write
23
23
  outputs:
24
24
  changes_pushed: ${{ steps.manager.outputs.changes_pushed }}
25
25
  steps:
@@ -33,146 +33,24 @@ jobs:
33
33
  uses: astral-sh/setup-uv@v5
34
34
  with:
35
35
  enable-cache: true
36
+
37
+ # We just install python here so uv run has something to target
36
38
  - run: uv python install 3.12
37
39
 
38
- - name: Fix, Bump, Report & Re-Trigger
40
+ - name: Run Maintenance
39
41
  id: manager
40
42
  env:
41
43
  GH_TOKEN: ${{ github.token }}
42
- PR_NUMBER_ENV: ${{ github.event.pull_request.number }}
44
+ PR_NUMBER: ${{ github.event.pull_request.number }}
43
45
  TARGET_REF: ${{ github.head_ref || github.ref_name }}
44
46
  GITHUB_REPO: ${{ github.repository }}
45
47
  WORKFLOW_NAME: ${{ github.workflow }}
46
48
  run: |
47
- git config --global user.name "github-actions[bot]"
48
- git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com"
49
-
50
- uv run python - <<'EOF'
51
- import os, re, subprocess, sys, json
52
- from pathlib import Path
53
-
54
- # --- CONFIG ---
55
- VERSION_FILE = Path("quasarr/providers/version.py")
56
- TARGET_REF = os.environ["TARGET_REF"]
57
- REPO = os.environ["GITHUB_REPO"]
58
- WORKFLOW_NAME = os.environ["WORKFLOW_NAME"]
59
- fixed_format = False
60
- fixed_version = False
61
-
62
- # --- 1. FORMATTING ---
63
- print("Running Ruff...")
64
- subprocess.run(["uv", "run", "ruff", "check", "--select", "I", "--fix", "."], check=False)
65
- subprocess.run(["uv", "run", "ruff", "format", "."], check=False)
66
-
67
- if subprocess.run(["git", "status", "--porcelain"], capture_output=True, text=True).stdout.strip():
68
- fixed_format = True
69
-
70
- # --- 2. VERSION BUMP ---
71
- print("Checking Version...")
72
- def get_ver(c):
73
- m = re.search(r'__version__\s*=\s*["\']([^"\']+)["\']', c)
74
- return m.group(1) if m else None
75
-
76
- def bump(v):
77
- p = v.split('.')
78
- while len(p)<3: p.append('0')
79
- try: p[-1] = str(int(p[-1])+1)
80
- except: p.append('1')
81
- return ".".join(p)
82
-
83
- try:
84
- subprocess.run(["git", "fetch", "origin", "main"], check=True, capture_output=True)
85
- try: base = subprocess.check_output(["git", "merge-base", "HEAD", "origin/main"], text=True).strip()
86
- except: base = "origin/main"
87
-
88
- subprocess.run(["git", "checkout", base, "--", str(VERSION_FILE)], check=True, capture_output=True)
89
- main_v = get_ver(VERSION_FILE.read_text())
90
- subprocess.run(["git", "checkout", "HEAD", "--", str(VERSION_FILE)], check=True, capture_output=True)
91
- curr_v = get_ver(VERSION_FILE.read_text())
92
-
93
- print(f"Main: {main_v} | Current: {curr_v}")
94
- if curr_v == main_v:
95
- new_v = bump(curr_v)
96
- print(f">> Bumping to {new_v}")
97
- VERSION_FILE.write_text(VERSION_FILE.read_text().replace(f'"{curr_v}"', f'"{new_v}"'))
98
- fixed_version = True
99
- except Exception as e:
100
- print(f"Version check warning (non-fatal): {e}")
101
-
102
- # --- 3. PUSH & REPORT ---
103
- if fixed_format or fixed_version:
104
- try:
105
- print(">> Committing fixes...")
106
- subprocess.run(["git", "add", "."], check=True)
107
- msg = "chore: auto-fix " + ("fmt & ver" if fixed_format and fixed_version else "fmt" if fixed_format else "ver")
108
- subprocess.run(["git", "commit", "-m", msg], check=True)
109
-
110
- # Pull --rebase to avoid race conditions (crashes) if remote changed
111
- subprocess.run(["git", "pull", "--rebase", "origin", TARGET_REF], check=False)
112
- subprocess.run(["git", "push", "origin", f"HEAD:{TARGET_REF}"], check=True)
113
-
114
- with open(os.environ['GITHUB_OUTPUT'], 'a') as f: f.write("changes_pushed=true\n")
115
- except subprocess.CalledProcessError as e:
116
- print(f"::error::Failed to push fixes. Please pull the latest changes and try again. ({e})")
117
- # Exit 1 so the job shows red, but cleanly.
118
- sys.exit(1)
119
-
120
- # --- FIND PR ---
121
- pr_num = os.environ.get("PR_NUMBER_ENV")
122
- if not pr_num:
123
- try:
124
- pr_json = subprocess.check_output(["gh", "pr", "list", "--head", TARGET_REF, "--json", "number"], text=True)
125
- prs = json.loads(pr_json)
126
- if prs: pr_num = str(prs[0]['number'])
127
- except: pass
128
-
129
- # --- POST "AUTO-FIX" COMMENT (Original Formatting) ---
130
- if pr_num:
131
- fixes_list = ""
132
- if fixed_format: fixes_list += "- ✅ **Formatted Code** (Imports & Layout)\n"
133
- if fixed_version: fixes_list += "- ✅ **Bumped Version**\n"
134
-
135
- body = "### 🤖 Auto-Fix Applied\n"
136
- body += "I fixed the following issues so we can merge:\n"
137
- body += fixes_list + "\n"
138
- body += "**Note:** The build is now **GREEN** 🟢, but your local branch is out of sync.\n"
139
- body += f"Please run this locally:\n```bash\ngit pull origin {TARGET_REF}\n```\n"
140
- body += "\n---\n#### 💡 For future reference:\n\n"
141
- if fixed_format:
142
- body += "**To check formatting before pushing:**\n```bash\nuv run ruff check --select I --fix .\nuv run ruff format .\n```\n\n"
143
- if fixed_version:
144
- body += "**To update the version manually:**\nEdit the `__version__` string in:\n`quasarr/providers/version.py`\n"
145
-
146
- Path("comment.md").write_text(body, encoding="utf-8")
147
- subprocess.run(["gh", "pr", "comment", pr_num, "--body-file", "comment.md"], check=False)
148
-
149
- # --- POST "RE-TRIGGER" COMMENT ---
150
- print(f">> Triggering new workflow run for: {WORKFLOW_NAME}...")
151
-
152
- if pr_num:
153
- actions_url = f"https://github.com/{REPO}/actions?query=branch%3A{TARGET_REF}"
154
- retrigger_body = "🚀 **Beta Build Triggered!**\n\n"
155
- retrigger_body += "I have automatically started a new workflow run on the updated branch.\n"
156
- retrigger_body += f"\n[**👉 Click here to view the new run**]({actions_url})"
157
-
158
- Path("retrigger.md").write_text(retrigger_body, encoding="utf-8")
159
- subprocess.run(["gh", "pr", "comment", pr_num, "--body-file", "retrigger.md"], check=False)
160
-
161
- # --- FIRE THE TRIGGER ---
162
- ret = subprocess.run(["gh", "workflow", "run", WORKFLOW_NAME, "--ref", TARGET_REF], check=False)
163
-
164
- if ret.returncode != 0:
165
- print("::warning::Could not auto-trigger next run. Please retry the job manually.")
166
-
167
- sys.exit(0)
168
-
169
- with open(os.environ['GITHUB_OUTPUT'], 'a') as f: f.write("changes_pushed=false\n")
170
- print("Clean run.")
171
- EOF
49
+ uv run maintenance.py --ci
172
50
 
173
51
  version:
174
52
  needs: [ quality-check ]
175
- if: needs.quality-check.outputs.changes_pushed != 'true' && github.ref == 'refs/heads/dev'
53
+ if: needs.quality-check.outputs.changes_pushed != 'true' && (github.head_ref || github.ref_name) == 'dev'
176
54
  runs-on: ubuntu-latest
177
55
  outputs:
178
56
  version: ${{ steps.version.outputs.version }}
@@ -189,7 +67,7 @@ jobs:
189
67
 
190
68
  build-wheel:
191
69
  needs: [ quality-check, version ]
192
- if: needs.quality-check.outputs.changes_pushed != 'true' && github.ref == 'refs/heads/dev'
70
+ if: needs.quality-check.outputs.changes_pushed != 'true' && (github.head_ref || github.ref_name) == 'dev'
193
71
  runs-on: ubuntu-latest
194
72
  outputs:
195
73
  attestation-id: ${{ steps.attest.outputs.attestation-id }}
@@ -217,7 +95,7 @@ jobs:
217
95
 
218
96
  build-exe:
219
97
  needs: [ quality-check, version ]
220
- if: needs.quality-check.outputs.changes_pushed != 'true' && github.ref == 'refs/heads/dev'
98
+ if: needs.quality-check.outputs.changes_pushed != 'true' && (github.head_ref || github.ref_name) == 'dev'
221
99
  runs-on: windows-latest
222
100
  env:
223
101
  TMP: "D:\\a\\temp"
@@ -228,16 +106,38 @@ jobs:
228
106
  - uses: actions/setup-python@v5
229
107
  with:
230
108
  python-version: '3.12'
109
+
231
110
  - uses: astral-sh/setup-uv@v5
232
111
  with:
233
112
  enable-cache: true
113
+ cache-suffix: "win-build" # Isolate this cache from Linux builds if you share keys
114
+
115
+ # --- CACHE ---
116
+ - name: Cache PyInstaller Analysis
117
+ uses: actions/cache@v4
118
+ with:
119
+ path: |
120
+ build
121
+ ~\AppData\Local\pyinstaller
122
+ # Invalidates cache only if dependencies (uv.lock) change
123
+ key: pyinstaller-analysis-${{ runner.os }}-${{ hashFiles('uv.lock') }}
124
+ restore-keys: |
125
+ pyinstaller-analysis-${{ runner.os }}-
126
+
234
127
  - shell: powershell
235
128
  run: Set-MpPreference -DisableRealtimeMonitoring $true
129
+
236
130
  - run: uv sync --group build
131
+
132
+ # --- BUILD COMMAND ---
237
133
  - run: |
238
134
  uv run python -c "from PIL import Image; Image.open('Quasarr.png').save('Quasarr.ico')"
239
135
  uv run python quasarr/providers/version.py --create-version-file
240
- uv run pyinstaller --clean --onefile -y --version-file "file_version_info.txt" --icon "Quasarr.ico" "Quasarr.py" -n "quasarr-${{ needs.version.outputs.version }}-standalone-win64"
136
+ # 1. Removed '--clean'
137
+ # 2. Added '--workpath "build"' (Matches the cached path above)
138
+ # 3. Added '--distpath "dist"' (Explicit output folder)
139
+ uv run pyinstaller --onefile -y --version-file "file_version_info.txt" --workpath "build" --distpath "dist" --icon "Quasarr.ico" "Quasarr.py" -n "quasarr-${{ needs.version.outputs.version }}-standalone-win64"
140
+
241
141
  - uses: actions/upload-artifact@v4
242
142
  with:
243
143
  name: exe-amd64
@@ -286,7 +186,7 @@ jobs:
286
186
 
287
187
  build-docker-amd64:
288
188
  needs: [ quality-check, version, build-wheel ]
289
- if: needs.quality-check.outputs.changes_pushed != 'true' && github.ref == 'refs/heads/dev'
189
+ if: needs.quality-check.outputs.changes_pushed != 'true' && (github.head_ref || github.ref_name) == 'dev'
290
190
  runs-on: ubuntu-latest
291
191
  steps:
292
192
  - uses: actions/checkout@v6
@@ -317,7 +217,7 @@ jobs:
317
217
 
318
218
  build-docker-arm64:
319
219
  needs: [ quality-check, version, build-wheel ]
320
- if: needs.quality-check.outputs.changes_pushed != 'true' && github.ref == 'refs/heads/dev'
220
+ if: needs.quality-check.outputs.changes_pushed != 'true' && (github.head_ref || github.ref_name) == 'dev'
321
221
  runs-on: ubuntu-24.04-arm
322
222
  steps:
323
223
  - uses: actions/checkout@v6
@@ -74,20 +74,33 @@ jobs:
74
74
  runs-on: windows-latest
75
75
  needs: version
76
76
  env:
77
- TMP: D:\a\temp
78
- TEMP: D:\a\temp
77
+ # We define a consistent build path to make caching reliable
78
+ BUILD_PATH: "build"
79
79
  steps:
80
- - name: Create Temp Dir
81
- run: mkdir D:\a\temp -Force
82
80
  - uses: actions/checkout@v6
83
81
  - uses: actions/setup-python@v5
84
82
  with:
85
83
  python-version: '3.12'
86
84
 
85
+ # 1. Install uv with its own persistent cache
87
86
  - name: Install uv
88
87
  uses: astral-sh/setup-uv@v5
89
88
  with:
90
89
  enable-cache: true
90
+ cache-suffix: "win-build"
91
+
92
+ # 2. PyInstaller Caching
93
+ # We cache the local 'build' folder AND the global PyInstaller cache.
94
+ # The key invalidates if 'uv.lock' changes, forcing a re-analysis only when deps update.
95
+ - name: Cache PyInstaller Analysis
96
+ uses: actions/cache@v4
97
+ with:
98
+ path: |
99
+ build
100
+ ~\AppData\Local\pyinstaller
101
+ key: pyinstaller-analysis-${{ runner.os }}-${{ hashFiles('uv.lock') }}
102
+ restore-keys: |
103
+ pyinstaller-analysis-${{ runner.os }}-
91
104
 
92
105
  - name: Disable Windows Defender
93
106
  shell: powershell
@@ -98,10 +111,14 @@ jobs:
98
111
  uv sync --group build
99
112
 
100
113
  - name: Build exe
114
+ # CHANGES:
115
+ # 1. Removed '--clean' (Critical for speed)
116
+ # 2. Added '--workpath' to point to our cached folder
117
+ # 3. Added '--distpath' explicitly
101
118
  run: |
102
119
  uv run python -c "from PIL import Image; Image.open('Quasarr.png').save('Quasarr.ico')"
103
120
  uv run python quasarr/providers/version.py --create-version-file
104
- uv run pyinstaller --clean --onefile -y --version-file "file_version_info.txt" --icon "Quasarr.ico" "Quasarr.py" -n "quasarr-${{ needs.version.outputs.version }}-standalone-win64"
121
+ uv run pyinstaller --onefile -y --version-file "file_version_info.txt" --workpath "build" --distpath "dist" --icon "Quasarr.ico" "Quasarr.py" -n "quasarr-${{ needs.version.outputs.version }}-standalone-win64"
105
122
 
106
123
  - uses: actions/upload-artifact@v4
107
124
  with:
@@ -32,24 +32,11 @@ The `CONFIG_VOLUMES` environment variable is **mandatory**.
32
32
 
33
33
  ### Code Quality & Maintenance
34
34
 
35
- The CI pipeline enforces strict code styling and import optimization. Please run these commands before pushing your
35
+ The CI pipeline enforces strict code styling and import optimization. Please run this commands before pushing your
36
36
  changes:
37
37
 
38
- **Optimize Imports and Fix Linting:**
38
+ **Format code AND upgrade dependencies:**
39
39
 
40
40
  ```bash
41
- uv run ruff check --fix .
41
+ uv run maintenance.py --upgrade
42
42
  ```
43
-
44
- **Format Code Layout:**
45
-
46
- ```bash
47
- uv run ruff format .
48
- ```
49
-
50
- **Update Dependencies:**
51
- To update the project lockfile to the latest versions of all packages without manually editing `pyproject.toml`:
52
-
53
- ```bash
54
- uv lock --upgrade
55
- ```
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: quasarr
3
- Version: 2.4.9
3
+ Version: 2.4.10
4
4
  Summary: Quasarr connects JDownloader with Radarr, Sonarr and LazyLibrarian. It also decrypts links protected by CAPTCHAs.
5
5
  Author-email: rix1337 <rix1337@users.noreply.github.com>
6
6
  License-File: LICENSE
@@ -243,11 +243,12 @@ Most feature requests can be satisfied by:
243
243
  [here](https://github.com/rix1337/Quasarr/pulls).
244
244
  - **Pull requests are welcome!** Especially for popular hostnames.
245
245
  - A short guide to set up required dev services is found
246
- in [/docker/dev-setup.md](https://github.com/rix1337/Quasarr/blob/main/docker/dev-setup.md)
246
+ [here](https://github.com/rix1337/Quasarr/blob/main/CONTRIBUTING.md).
247
247
  - Always reach out on Discord before starting work on a new feature to prevent waste of time.
248
248
  - Please follow the existing code style and project structure.
249
249
  - Anti-bot measures must be circumvented fully by Quasarr. Thus, you will need to provide a working solution for new
250
250
  CAPTCHA types by integrating it in the Quasarr Web UI.
251
+ The simplest CAPTCHA bypass involves creating a Tampermonkey user script.
251
252
  - Please provide proof of functionality (screenshots/examples) when submitting your pull request.
252
253
 
253
254
  # SponsorsHelper
@@ -225,11 +225,12 @@ Most feature requests can be satisfied by:
225
225
  [here](https://github.com/rix1337/Quasarr/pulls).
226
226
  - **Pull requests are welcome!** Especially for popular hostnames.
227
227
  - A short guide to set up required dev services is found
228
- in [/docker/dev-setup.md](https://github.com/rix1337/Quasarr/blob/main/docker/dev-setup.md)
228
+ [here](https://github.com/rix1337/Quasarr/blob/main/CONTRIBUTING.md).
229
229
  - Always reach out on Discord before starting work on a new feature to prevent waste of time.
230
230
  - Please follow the existing code style and project structure.
231
231
  - Anti-bot measures must be circumvented fully by Quasarr. Thus, you will need to provide a working solution for new
232
232
  CAPTCHA types by integrating it in the Quasarr Web UI.
233
+ The simplest CAPTCHA bypass involves creating a Tampermonkey user script.
233
234
  - Please provide proof of functionality (screenshots/examples) when submitting your pull request.
234
235
 
235
236
  # SponsorsHelper
@@ -0,0 +1,284 @@
1
+ import json
2
+ import os
3
+ import re
4
+ import subprocess
5
+ import sys
6
+ import tomllib
7
+ from pathlib import Path
8
+
9
+ # --- CONFIGURATION ---
10
+ VERSION_FILE = Path("quasarr/providers/version.py")
11
+ PYPROJECT_FILE = Path("pyproject.toml")
12
+
13
+
14
+ def run(cmd, check=True, capture=False, text=True):
15
+ """Helper to run shell commands comfortably."""
16
+ print(f"Exec: {' '.join(cmd)}")
17
+ return subprocess.run(cmd, check=check, capture_output=capture, text=text)
18
+
19
+
20
+ def get_env(key, default=None):
21
+ return os.environ.get(key, default)
22
+
23
+
24
+ def git_status_has_changes():
25
+ return bool(run(["git", "status", "--porcelain"], capture=True).stdout.strip())
26
+
27
+
28
+ # --- TASKS ---
29
+
30
+
31
+ def task_format():
32
+ print("--- 1. FORMATTING ---")
33
+ run(["uv", "run", "ruff", "check", "--select", "I", "--fix", "."], check=False)
34
+ run(["uv", "run", "ruff", "format", "."], check=False)
35
+
36
+ if git_status_has_changes():
37
+ run(["git", "add", "."])
38
+ return True
39
+ return False
40
+
41
+
42
+ def task_upgrade_deps():
43
+ print("--- 2. DEPENDENCIES ---")
44
+ try:
45
+ with open(PYPROJECT_FILE, "rb") as f:
46
+ pyproj = tomllib.load(f)
47
+
48
+ def get_pkg_name(dep_str):
49
+ m = re.match(r"^[a-zA-Z0-9_\-\.]+", dep_str)
50
+ return m.group(0) if m else None
51
+
52
+ # Main dependencies
53
+ deps = pyproj.get("project", {}).get("dependencies", [])
54
+ if deps:
55
+ pkgs = [get_pkg_name(d) for d in deps if get_pkg_name(d)]
56
+ if pkgs:
57
+ print(f"Upgrading main: {pkgs}")
58
+ run(["uv", "add", "--upgrade"] + pkgs, check=False)
59
+
60
+ # Groups
61
+ groups = pyproj.get("dependency-groups", {})
62
+ for group, g_deps in groups.items():
63
+ if g_deps:
64
+ pkgs = [get_pkg_name(d) for d in g_deps if get_pkg_name(d)]
65
+ if pkgs:
66
+ print(f"Upgrading group '{group}': {pkgs}")
67
+ run(
68
+ ["uv", "add", "--group", group, "--upgrade"] + pkgs, check=False
69
+ )
70
+
71
+ # Lock file
72
+ run(["uv", "lock", "--upgrade"], check=False)
73
+
74
+ except Exception as e:
75
+ print(f"Dependency upgrade failed: {e}")
76
+
77
+ if git_status_has_changes():
78
+ run(["git", "add", "."])
79
+ return True
80
+ return False
81
+
82
+
83
+ def task_version_bump():
84
+ print("--- 3. VERSION CHECK ---")
85
+ new_v = ""
86
+
87
+ def get_ver(content):
88
+ m = re.search(r'__version__\s*=\s*["\']([^"\']+)["\']', content)
89
+ return m.group(1) if m else None
90
+
91
+ def bump(v):
92
+ p = v.split(".")
93
+ while len(p) < 3:
94
+ p.append("0")
95
+ try:
96
+ p[-1] = str(int(p[-1]) + 1)
97
+ except:
98
+ p.append("1")
99
+ return ".".join(p)
100
+
101
+ def ver_tuple(v):
102
+ try:
103
+ return tuple(map(int, v.split(".")))
104
+ except:
105
+ return (0, 0, 0)
106
+
107
+ try:
108
+ # Fetch main to compare versions
109
+ run(["git", "fetch", "origin", "main"], check=False)
110
+ try:
111
+ base = subprocess.check_output(
112
+ ["git", "merge-base", "HEAD", "origin/main"], text=True
113
+ ).strip()
114
+ except:
115
+ base = "origin/main"
116
+
117
+ # Read Main Version
118
+ run(["git", "checkout", base, "--", str(VERSION_FILE)], capture=True)
119
+ main_v = get_ver(VERSION_FILE.read_text())
120
+
121
+ # Reset file and read Current Version
122
+ run(["git", "checkout", "HEAD", "--", str(VERSION_FILE)], capture=True)
123
+ curr_v = get_ver(VERSION_FILE.read_text())
124
+
125
+ print(f"Main: {main_v} | Current: {curr_v}")
126
+
127
+ if main_v and curr_v and ver_tuple(curr_v) <= ver_tuple(main_v):
128
+ new_v = bump(main_v)
129
+ print(f">> Bumping to {new_v}")
130
+ content = VERSION_FILE.read_text().replace(f'"{curr_v}"', f'"{new_v}"')
131
+ VERSION_FILE.write_text(content)
132
+
133
+ run(["git", "add", "."])
134
+ return True, new_v
135
+
136
+ except Exception as e:
137
+ print(f"Version check warning (non-fatal): {e}")
138
+
139
+ return False, new_v
140
+
141
+
142
+ def main():
143
+ # Parse minimal args
144
+ is_ci = "--ci" in sys.argv
145
+ do_upgrade = "--upgrade" in sys.argv or is_ci
146
+
147
+ # Run Tasks
148
+ fixed_format = task_format()
149
+ fixed_deps = False
150
+ if do_upgrade:
151
+ fixed_deps = task_upgrade_deps()
152
+
153
+ fixed_version = False
154
+ new_v = ""
155
+
156
+ # Version Bump only runs in CI
157
+ if is_ci:
158
+ fixed_version, new_v = task_version_bump()
159
+
160
+ # --- CI Specific Logic (Push & Notify) ---
161
+ if is_ci and (fixed_format or fixed_deps or fixed_version):
162
+ print("--- 4. PUSH & REPORT ---")
163
+
164
+ # 1. Config Git
165
+ run(["git", "config", "--global", "user.name", "github-actions[bot]"])
166
+ run(
167
+ [
168
+ "git",
169
+ "config",
170
+ "--global",
171
+ "user.email",
172
+ "41898282+github-actions[bot]@users.noreply.github.com",
173
+ ]
174
+ )
175
+
176
+ # 2. Commit Message
177
+ parts = []
178
+ if fixed_format:
179
+ parts.append("Fixed linting")
180
+ if fixed_deps:
181
+ parts.append("upgraded dependencies")
182
+ if fixed_version:
183
+ parts.append(f"increased version to {new_v}")
184
+
185
+ msg_body = (
186
+ ", ".join(parts[:-1]) + " and " + parts[-1] if len(parts) > 1 else parts[0]
187
+ )
188
+ msg = f"chore: {msg_body}"
189
+
190
+ try:
191
+ run(["git", "commit", "-m", msg])
192
+
193
+ target_ref = get_env("TARGET_REF")
194
+ run(["git", "pull", "--rebase", "origin", target_ref], check=False)
195
+ run(["git", "push", "origin", f"HEAD:{target_ref}"])
196
+
197
+ # Write to Github Output
198
+ if "GITHUB_OUTPUT" in os.environ:
199
+ with open(os.environ["GITHUB_OUTPUT"], "a") as f:
200
+ f.write("changes_pushed=true\n")
201
+ except subprocess.CalledProcessError as e:
202
+ print(f"::error::Failed to push fixes. ({e})")
203
+ sys.exit(1)
204
+
205
+ # 3. Handle PR Comments & Retrigger logic
206
+ repo = get_env("GITHUB_REPO")
207
+ workflow_name = get_env("WORKFLOW_NAME")
208
+ pr_num = get_env("PR_NUMBER") # We will pass this from YAML
209
+
210
+ # Attempt to find PR number if not provided
211
+ if not pr_num:
212
+ try:
213
+ pr_json = subprocess.check_output(
214
+ ["gh", "pr", "list", "--head", target_ref, "--json", "number"],
215
+ text=True,
216
+ )
217
+ prs = json.loads(pr_json)
218
+ if prs:
219
+ pr_num = str(prs[0]["number"])
220
+ except:
221
+ pass
222
+
223
+ # Comment on PR
224
+ if pr_num:
225
+ fixes_list = ""
226
+ if fixed_format:
227
+ fixes_list += "- ✅ **Formatted Code** (Imports & Layout)\n"
228
+ if fixed_deps:
229
+ fixes_list += "- ✅ **Upgraded Dependencies**\n"
230
+ if fixed_version:
231
+ fixes_list += f"- ✅ **Bumped Version** (to {new_v})\n"
232
+
233
+ body = "### 🤖 Auto-Fix Applied\n"
234
+ body += "I fixed the following issues so we can merge:\n"
235
+ body += fixes_list + "\n"
236
+ body += "**Note:** The build is now **GREEN** 🟢, but your local branch is out of sync.\n"
237
+ body += f"Please run this locally:\n```bash\ngit pull origin {target_ref}\n```\n"
238
+
239
+ Path("comment.md").write_text(body, encoding="utf-8")
240
+ run(
241
+ ["gh", "pr", "comment", pr_num, "--body-file", "comment.md"],
242
+ check=False,
243
+ )
244
+
245
+ # Beta Build Retrigger Notification
246
+ if target_ref == "dev":
247
+ actions_url = (
248
+ f"https://github.com/{repo}/actions?query=branch%3A{target_ref}"
249
+ )
250
+ retrigger_body = "🚀 **Beta Build Triggered!**\n\n"
251
+ retrigger_body += "I have automatically started a new workflow run on the updated branch.\n"
252
+ retrigger_body += (
253
+ f"\n[**👉 Click here to view the new run**]({actions_url})"
254
+ )
255
+
256
+ Path("retrigger.md").write_text(retrigger_body, encoding="utf-8")
257
+ run(
258
+ ["gh", "pr", "comment", pr_num, "--body-file", "retrigger.md"],
259
+ check=False,
260
+ )
261
+
262
+ # 4. Retrigger Workflow
263
+ print(f">> Triggering new workflow run for: {workflow_name}...")
264
+ ret = run(
265
+ ["gh", "workflow", "run", workflow_name, "--ref", target_ref], check=False
266
+ )
267
+
268
+ if ret.returncode != 0:
269
+ print(
270
+ "::warning::Could not auto-trigger next run. Please retry the job manually."
271
+ )
272
+
273
+ sys.exit(0)
274
+
275
+ else:
276
+ # No changes
277
+ print("Clean run.")
278
+ if "GITHUB_OUTPUT" in os.environ:
279
+ with open(os.environ["GITHUB_OUTPUT"], "a") as f:
280
+ f.write("changes_pushed=false\n")
281
+
282
+
283
+ if __name__ == "__main__":
284
+ main()
@@ -5,7 +5,7 @@
5
5
  import re
6
6
  import sys
7
7
 
8
- __version__ = "2.4.9"
8
+ __version__ = "2.4.10"
9
9
 
10
10
 
11
11
  def get_version():
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes