quasarr 2.4.8__tar.gz → 2.4.9__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.
Files changed (98) hide show
  1. {quasarr-2.4.8 → quasarr-2.4.9}/.github/workflows/HostnameRedaction.yml +1 -1
  2. quasarr-2.4.9/.github/workflows/PullRequests.yml +434 -0
  3. {quasarr-2.4.8 → quasarr-2.4.9}/.github/workflows/Release.yml +46 -7
  4. {quasarr-2.4.8 → quasarr-2.4.9}/.gitignore +1 -2
  5. {quasarr-2.4.8 → quasarr-2.4.9}/PKG-INFO +2 -2
  6. {quasarr-2.4.8 → quasarr-2.4.9}/Quasarr.py +1 -1
  7. {quasarr-2.4.8 → quasarr-2.4.9}/docker/Dockerfile +5 -4
  8. quasarr-2.4.9/docker/dev-setup.md +55 -0
  9. {quasarr-2.4.8 → quasarr-2.4.9}/pyproject.toml +13 -3
  10. {quasarr-2.4.8 → quasarr-2.4.9}/quasarr/__init__.py +134 -70
  11. {quasarr-2.4.8 → quasarr-2.4.9}/quasarr/api/__init__.py +40 -31
  12. {quasarr-2.4.8 → quasarr-2.4.9}/quasarr/api/arr/__init__.py +116 -108
  13. {quasarr-2.4.8 → quasarr-2.4.9}/quasarr/api/captcha/__init__.py +262 -137
  14. {quasarr-2.4.8 → quasarr-2.4.9}/quasarr/api/config/__init__.py +76 -46
  15. {quasarr-2.4.8 → quasarr-2.4.9}/quasarr/api/packages/__init__.py +138 -102
  16. {quasarr-2.4.8 → quasarr-2.4.9}/quasarr/api/sponsors_helper/__init__.py +29 -16
  17. {quasarr-2.4.8 → quasarr-2.4.9}/quasarr/api/statistics/__init__.py +19 -19
  18. {quasarr-2.4.8 → quasarr-2.4.9}/quasarr/downloads/__init__.py +165 -72
  19. {quasarr-2.4.8 → quasarr-2.4.9}/quasarr/downloads/linkcrypters/al.py +35 -18
  20. {quasarr-2.4.8 → quasarr-2.4.9}/quasarr/downloads/linkcrypters/filecrypt.py +107 -52
  21. {quasarr-2.4.8 → quasarr-2.4.9}/quasarr/downloads/linkcrypters/hide.py +5 -6
  22. {quasarr-2.4.8 → quasarr-2.4.9}/quasarr/downloads/packages/__init__.py +342 -177
  23. {quasarr-2.4.8 → quasarr-2.4.9}/quasarr/downloads/sources/al.py +191 -100
  24. {quasarr-2.4.8 → quasarr-2.4.9}/quasarr/downloads/sources/by.py +31 -13
  25. {quasarr-2.4.8 → quasarr-2.4.9}/quasarr/downloads/sources/dd.py +27 -14
  26. {quasarr-2.4.8 → quasarr-2.4.9}/quasarr/downloads/sources/dj.py +1 -3
  27. {quasarr-2.4.8 → quasarr-2.4.9}/quasarr/downloads/sources/dl.py +126 -71
  28. {quasarr-2.4.8 → quasarr-2.4.9}/quasarr/downloads/sources/dt.py +11 -5
  29. {quasarr-2.4.8 → quasarr-2.4.9}/quasarr/downloads/sources/dw.py +28 -14
  30. {quasarr-2.4.8 → quasarr-2.4.9}/quasarr/downloads/sources/he.py +32 -24
  31. {quasarr-2.4.8 → quasarr-2.4.9}/quasarr/downloads/sources/mb.py +19 -9
  32. {quasarr-2.4.8 → quasarr-2.4.9}/quasarr/downloads/sources/nk.py +14 -10
  33. {quasarr-2.4.8 → quasarr-2.4.9}/quasarr/downloads/sources/nx.py +8 -18
  34. {quasarr-2.4.8 → quasarr-2.4.9}/quasarr/downloads/sources/sf.py +45 -20
  35. {quasarr-2.4.8 → quasarr-2.4.9}/quasarr/downloads/sources/sj.py +1 -3
  36. {quasarr-2.4.8 → quasarr-2.4.9}/quasarr/downloads/sources/sl.py +9 -5
  37. {quasarr-2.4.8 → quasarr-2.4.9}/quasarr/downloads/sources/wd.py +32 -12
  38. {quasarr-2.4.8 → quasarr-2.4.9}/quasarr/downloads/sources/wx.py +35 -21
  39. {quasarr-2.4.8 → quasarr-2.4.9}/quasarr/providers/auth.py +42 -37
  40. {quasarr-2.4.8 → quasarr-2.4.9}/quasarr/providers/cloudflare.py +28 -30
  41. {quasarr-2.4.8 → quasarr-2.4.9}/quasarr/providers/hostname_issues.py +2 -1
  42. {quasarr-2.4.8 → quasarr-2.4.9}/quasarr/providers/html_images.py +2 -2
  43. {quasarr-2.4.8 → quasarr-2.4.9}/quasarr/providers/html_templates.py +22 -14
  44. {quasarr-2.4.8 → quasarr-2.4.9}/quasarr/providers/imdb_metadata.py +149 -80
  45. {quasarr-2.4.8 → quasarr-2.4.9}/quasarr/providers/jd_cache.py +131 -39
  46. {quasarr-2.4.8 → quasarr-2.4.9}/quasarr/providers/log.py +1 -1
  47. {quasarr-2.4.8 → quasarr-2.4.9}/quasarr/providers/myjd_api.py +260 -196
  48. {quasarr-2.4.8 → quasarr-2.4.9}/quasarr/providers/notifications.py +53 -41
  49. {quasarr-2.4.8 → quasarr-2.4.9}/quasarr/providers/obfuscated.py +9 -4
  50. {quasarr-2.4.8 → quasarr-2.4.9}/quasarr/providers/sessions/al.py +71 -55
  51. {quasarr-2.4.8 → quasarr-2.4.9}/quasarr/providers/sessions/dd.py +21 -14
  52. {quasarr-2.4.8 → quasarr-2.4.9}/quasarr/providers/sessions/dl.py +30 -19
  53. {quasarr-2.4.8 → quasarr-2.4.9}/quasarr/providers/sessions/nx.py +23 -14
  54. {quasarr-2.4.8 → quasarr-2.4.9}/quasarr/providers/shared_state.py +292 -141
  55. {quasarr-2.4.8 → quasarr-2.4.9}/quasarr/providers/statistics.py +75 -43
  56. {quasarr-2.4.8 → quasarr-2.4.9}/quasarr/providers/utils.py +33 -27
  57. {quasarr-2.4.8 → quasarr-2.4.9}/quasarr/providers/version.py +45 -14
  58. {quasarr-2.4.8 → quasarr-2.4.9}/quasarr/providers/web_server.py +10 -5
  59. {quasarr-2.4.8 → quasarr-2.4.9}/quasarr/search/__init__.py +30 -18
  60. {quasarr-2.4.8 → quasarr-2.4.9}/quasarr/search/sources/al.py +124 -73
  61. {quasarr-2.4.8 → quasarr-2.4.9}/quasarr/search/sources/by.py +110 -59
  62. {quasarr-2.4.8 → quasarr-2.4.9}/quasarr/search/sources/dd.py +57 -35
  63. {quasarr-2.4.8 → quasarr-2.4.9}/quasarr/search/sources/dj.py +69 -48
  64. {quasarr-2.4.8 → quasarr-2.4.9}/quasarr/search/sources/dl.py +159 -100
  65. {quasarr-2.4.8 → quasarr-2.4.9}/quasarr/search/sources/dt.py +110 -74
  66. {quasarr-2.4.8 → quasarr-2.4.9}/quasarr/search/sources/dw.py +121 -61
  67. {quasarr-2.4.8 → quasarr-2.4.9}/quasarr/search/sources/fx.py +108 -62
  68. {quasarr-2.4.8 → quasarr-2.4.9}/quasarr/search/sources/he.py +78 -49
  69. {quasarr-2.4.8 → quasarr-2.4.9}/quasarr/search/sources/mb.py +96 -48
  70. {quasarr-2.4.8 → quasarr-2.4.9}/quasarr/search/sources/nk.py +80 -50
  71. {quasarr-2.4.8 → quasarr-2.4.9}/quasarr/search/sources/nx.py +91 -62
  72. {quasarr-2.4.8 → quasarr-2.4.9}/quasarr/search/sources/sf.py +171 -106
  73. {quasarr-2.4.8 → quasarr-2.4.9}/quasarr/search/sources/sj.py +69 -48
  74. {quasarr-2.4.8 → quasarr-2.4.9}/quasarr/search/sources/sl.py +115 -71
  75. {quasarr-2.4.8 → quasarr-2.4.9}/quasarr/search/sources/wd.py +67 -44
  76. quasarr-2.4.9/quasarr/search/sources/wx.py +417 -0
  77. {quasarr-2.4.8 → quasarr-2.4.9}/quasarr/storage/config.py +65 -52
  78. {quasarr-2.4.8 → quasarr-2.4.9}/quasarr/storage/setup.py +238 -140
  79. {quasarr-2.4.8 → quasarr-2.4.9}/quasarr/storage/sqlite_database.py +10 -4
  80. quasarr-2.4.9/uv.lock +431 -0
  81. quasarr-2.4.8/.github/workflows/Beta.yml +0 -240
  82. quasarr-2.4.8/.github/workflows/PrVersionBumpCheck.yml +0 -138
  83. quasarr-2.4.8/docker/dev-setup.md +0 -21
  84. quasarr-2.4.8/quasarr/search/sources/wx.py +0 -352
  85. {quasarr-2.4.8 → quasarr-2.4.9}/.github/FUNDING.yml +0 -0
  86. {quasarr-2.4.8 → quasarr-2.4.9}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  87. {quasarr-2.4.8 → quasarr-2.4.9}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  88. {quasarr-2.4.8 → quasarr-2.4.9}/LICENSE +0 -0
  89. {quasarr-2.4.8 → quasarr-2.4.9}/Quasarr.png +0 -0
  90. {quasarr-2.4.8 → quasarr-2.4.9}/README.md +0 -0
  91. {quasarr-2.4.8 → quasarr-2.4.9}/docker/dev-services-compose.yml +0 -0
  92. {quasarr-2.4.8 → quasarr-2.4.9}/docker/docker-compose.yml +0 -0
  93. {quasarr-2.4.8 → quasarr-2.4.9}/quasarr/downloads/linkcrypters/__init__.py +0 -0
  94. {quasarr-2.4.8 → quasarr-2.4.9}/quasarr/downloads/sources/__init__.py +0 -0
  95. {quasarr-2.4.8 → quasarr-2.4.9}/quasarr/providers/__init__.py +0 -0
  96. {quasarr-2.4.8 → quasarr-2.4.9}/quasarr/providers/sessions/__init__.py +0 -0
  97. {quasarr-2.4.8 → quasarr-2.4.9}/quasarr/search/sources/__init__.py +0 -0
  98. {quasarr-2.4.8 → quasarr-2.4.9}/quasarr/storage/__init__.py +0 -0
@@ -4,7 +4,7 @@ on:
4
4
  issues:
5
5
  types: [ opened, edited ]
6
6
  pull_request:
7
- types: [ opened, edited ]
7
+ types: [ opened, closed, reopened, synchronize, edited ]
8
8
  issue_comment:
9
9
  types: [ created, edited ]
10
10
  pull_request_review_comment:
@@ -0,0 +1,434 @@
1
+ name: CI/CD & Beta Build
2
+
3
+ on:
4
+ workflow_dispatch:
5
+ pull_request:
6
+
7
+ concurrency:
8
+ group: ${{ github.workflow }}-${{ github.ref }}
9
+ cancel-in-progress: true
10
+
11
+ env:
12
+ GHCR_ENDPOINT: "ghcr.io/rix1337/quasarr"
13
+ DESCRIPTION: "Quasarr connects JDownloader with Radarr, Sonarr and LazyLibrarian."
14
+
15
+ jobs:
16
+ quality-check:
17
+ name: Check & Auto-Fix
18
+ runs-on: ubuntu-latest
19
+ permissions:
20
+ contents: write # To push commits
21
+ pull-requests: write # To comment on PRs
22
+ actions: write # To trigger the next run
23
+ outputs:
24
+ changes_pushed: ${{ steps.manager.outputs.changes_pushed }}
25
+ steps:
26
+ - uses: actions/checkout@v6
27
+ with:
28
+ repository: ${{ github.event.pull_request.head.repo.full_name || github.repository }}
29
+ ref: ${{ github.head_ref || github.ref_name }}
30
+ fetch-depth: 0
31
+
32
+ - name: Install uv
33
+ uses: astral-sh/setup-uv@v5
34
+ with:
35
+ enable-cache: true
36
+ - run: uv python install 3.12
37
+
38
+ - name: Fix, Bump, Report & Re-Trigger
39
+ id: manager
40
+ env:
41
+ GH_TOKEN: ${{ github.token }}
42
+ PR_NUMBER_ENV: ${{ github.event.pull_request.number }}
43
+ TARGET_REF: ${{ github.head_ref || github.ref_name }}
44
+ GITHUB_REPO: ${{ github.repository }}
45
+ WORKFLOW_NAME: ${{ github.workflow }}
46
+ 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
172
+
173
+ version:
174
+ needs: [ quality-check ]
175
+ if: needs.quality-check.outputs.changes_pushed != 'true' && github.ref == 'refs/heads/dev'
176
+ runs-on: ubuntu-latest
177
+ outputs:
178
+ version: ${{ steps.version.outputs.version }}
179
+ steps:
180
+ - uses: actions/checkout@v6
181
+ - uses: actions/setup-python@v5
182
+ with:
183
+ python-version: '3.12'
184
+ - uses: astral-sh/setup-uv@v5
185
+ with:
186
+ enable-cache: true
187
+ - id: version
188
+ run: echo "version=$(uv run python quasarr/providers/version.py)" >> $GITHUB_OUTPUT
189
+
190
+ build-wheel:
191
+ needs: [ quality-check, version ]
192
+ if: needs.quality-check.outputs.changes_pushed != 'true' && github.ref == 'refs/heads/dev'
193
+ runs-on: ubuntu-latest
194
+ outputs:
195
+ attestation-id: ${{ steps.attest.outputs.attestation-id }}
196
+ permissions:
197
+ contents: read
198
+ id-token: write
199
+ attestations: write
200
+ steps:
201
+ - uses: actions/checkout@v6
202
+ - uses: actions/setup-python@v5
203
+ with:
204
+ python-version: '3.12'
205
+ - uses: astral-sh/setup-uv@v5
206
+ with:
207
+ enable-cache: true
208
+ - run: uv build
209
+ - id: attest
210
+ uses: actions/attest-build-provenance@v2
211
+ with:
212
+ subject-path: "dist/*.whl"
213
+ - uses: actions/upload-artifact@v4
214
+ with:
215
+ name: wheel
216
+ path: ./dist/*
217
+
218
+ build-exe:
219
+ needs: [ quality-check, version ]
220
+ if: needs.quality-check.outputs.changes_pushed != 'true' && github.ref == 'refs/heads/dev'
221
+ runs-on: windows-latest
222
+ env:
223
+ TMP: "D:\\a\\temp"
224
+ TEMP: "D:\\a\\temp"
225
+ steps:
226
+ - run: mkdir D:\a\temp -Force
227
+ - uses: actions/checkout@v6
228
+ - uses: actions/setup-python@v5
229
+ with:
230
+ python-version: '3.12'
231
+ - uses: astral-sh/setup-uv@v5
232
+ with:
233
+ enable-cache: true
234
+ - shell: powershell
235
+ run: Set-MpPreference -DisableRealtimeMonitoring $true
236
+ - run: uv sync --group build
237
+ - run: |
238
+ uv run python -c "from PIL import Image; Image.open('Quasarr.png').save('Quasarr.ico')"
239
+ 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"
241
+ - uses: actions/upload-artifact@v4
242
+ with:
243
+ name: exe-amd64
244
+ path: ./dist/*.exe
245
+
246
+ beta-release:
247
+ needs: [ version, build-wheel, build-exe ]
248
+ runs-on: ubuntu-latest
249
+ permissions: { contents: write }
250
+ steps:
251
+ - uses: actions/checkout@v6
252
+ with:
253
+ fetch-depth: 0
254
+ - uses: actions/download-artifact@v4
255
+ with:
256
+ name: wheel
257
+ path: ./wheel
258
+ - uses: actions/download-artifact@v4
259
+ with:
260
+ name: exe-amd64
261
+ path: ./exe-amd64
262
+ - id: changelog
263
+ uses: metcalfc/changelog-generator@v4.6.2
264
+ with:
265
+ myToken: ${{ secrets.GITHUB_TOKEN }}
266
+ - name: Create Release Body
267
+ run: |
268
+ echo "### Docker:" > release_body.md
269
+ echo "\`docker pull ${{ env.GHCR_ENDPOINT }}:beta\`" >> release_body.md
270
+ echo "### Python:" >> release_body.md
271
+ echo "\`uv tool upgrade quasarr\`" >> release_body.md
272
+ echo "### Changelog:" >> release_body.md
273
+ echo "${{ steps.changelog.outputs.changelog }}" >> release_body.md
274
+ echo "[Attestation](https://github.com/${{ github.repository }}/attestations/${{ needs.build-wheel.outputs.attestation-id }})" >> release_body.md
275
+ - uses: ncipollo/release-action@v1
276
+ with:
277
+ artifacts: "./wheel/*.whl,./exe-amd64/*.exe"
278
+ allowUpdates: true
279
+ removeArtifacts: true
280
+ replacesArtifacts: true
281
+ tag: beta
282
+ name: Beta Build
283
+ bodyFile: "release_body.md"
284
+ prerelease: true
285
+ token: ${{ secrets.GITHUB_TOKEN }}
286
+
287
+ build-docker-amd64:
288
+ needs: [ quality-check, version, build-wheel ]
289
+ if: needs.quality-check.outputs.changes_pushed != 'true' && github.ref == 'refs/heads/dev'
290
+ runs-on: ubuntu-latest
291
+ steps:
292
+ - uses: actions/checkout@v6
293
+ - uses: actions/download-artifact@v4
294
+ with:
295
+ name: wheel
296
+ path: ./docker/dist
297
+ - uses: docker/setup-buildx-action@v3
298
+ - uses: docker/login-action@v3
299
+ with:
300
+ registry: ghcr.io
301
+ username: ${{ github.actor }}
302
+ password: ${{ secrets.GITHUB_TOKEN }}
303
+ - uses: docker/build-push-action@v6
304
+ with:
305
+ context: ./docker
306
+ platforms: linux/amd64
307
+ push: true
308
+ provenance: false
309
+ sbom: false
310
+ annotations: org.opencontainers.image.description=${{ env.DESCRIPTION }}
311
+ tags: |
312
+ ${{ env.GHCR_ENDPOINT }}:beta-amd64
313
+ ${{ env.GHCR_ENDPOINT }}:${{ needs.version.outputs.version }}-beta-amd64
314
+ build-args: VS=${{ needs.version.outputs.version }}
315
+ cache-from: type=gha,scope=beta-amd64
316
+ cache-to: type=gha,mode=max,scope=beta-amd64
317
+
318
+ build-docker-arm64:
319
+ needs: [ quality-check, version, build-wheel ]
320
+ if: needs.quality-check.outputs.changes_pushed != 'true' && github.ref == 'refs/heads/dev'
321
+ runs-on: ubuntu-24.04-arm
322
+ steps:
323
+ - uses: actions/checkout@v6
324
+ - uses: actions/download-artifact@v4
325
+ with:
326
+ name: wheel
327
+ path: ./docker/dist
328
+ - uses: docker/setup-buildx-action@v3
329
+ - uses: docker/login-action@v3
330
+ with:
331
+ registry: ghcr.io
332
+ username: ${{ github.actor }}
333
+ password: ${{ secrets.GITHUB_TOKEN }}
334
+ - uses: docker/build-push-action@v6
335
+ with:
336
+ context: ./docker
337
+ platforms: linux/arm64
338
+ push: true
339
+ provenance: false
340
+ sbom: false
341
+ annotations: org.opencontainers.image.description=${{ env.DESCRIPTION }}
342
+ tags: |
343
+ ${{ env.GHCR_ENDPOINT }}:beta-arm64
344
+ ${{ env.GHCR_ENDPOINT }}:${{ needs.version.outputs.version }}-beta-arm64
345
+ build-args: VS=${{ needs.version.outputs.version }}
346
+ cache-from: type=gha,scope=beta-arm64
347
+ cache-to: type=gha,mode=max,scope=beta-arm64
348
+
349
+ merge-docker-manifest:
350
+ needs: [ version, build-docker-amd64, build-docker-arm64 ]
351
+ runs-on: ubuntu-latest
352
+ steps:
353
+ - uses: docker/setup-buildx-action@v3
354
+ - uses: docker/login-action@v3
355
+ with:
356
+ registry: ghcr.io
357
+ username: ${{ github.actor }}
358
+ password: ${{ secrets.GITHUB_TOKEN }}
359
+ - run: |
360
+ TAG_AMD64="${{ needs.version.outputs.version }}-beta-amd64"
361
+ TAG_ARM64="${{ needs.version.outputs.version }}-beta-arm64"
362
+ ANNOTATION="index:org.opencontainers.image.description=$DESCRIPTION"
363
+
364
+ docker buildx imagetools create -t ${{ env.GHCR_ENDPOINT }}:beta \
365
+ --annotation "$ANNOTATION" \
366
+ ${{ env.GHCR_ENDPOINT }}:beta-amd64 \
367
+ ${{ env.GHCR_ENDPOINT }}:beta-arm64
368
+
369
+ docker buildx imagetools create -t ${{ env.GHCR_ENDPOINT }}:${{ needs.version.outputs.version }}-beta \
370
+ --annotation "$ANNOTATION" \
371
+ ${{ env.GHCR_ENDPOINT }}:${TAG_AMD64} \
372
+ ${{ env.GHCR_ENDPOINT }}:${TAG_ARM64}
373
+
374
+ notify:
375
+ name: Notify Discord & PR
376
+ needs: [ quality-check, version, beta-release, merge-docker-manifest ]
377
+ # Only run if the build wasn't skipped by the auto-fixer and the release succeeded
378
+ if: needs.quality-check.outputs.changes_pushed != 'true' && needs.beta-release.result == 'success'
379
+ runs-on: ubuntu-latest
380
+ permissions:
381
+ pull-requests: write # Required to comment on the PR
382
+ contents: read
383
+ steps:
384
+ - uses: actions/checkout@v6
385
+
386
+ - name: Send Notifications
387
+ env:
388
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
389
+ DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
390
+ VERSION: ${{ needs.version.outputs.version }}
391
+ REPO: ${{ github.repository }}
392
+ run: |
393
+ echo "Fetching release details..."
394
+ # Get the body of the release we just created (beta tag)
395
+ RELEASE_BODY=$(gh release view beta --json body --jq .body)
396
+
397
+ # --- 1. DISCORD NOTIFICATION ---
398
+ if [ -n "$DISCORD_WEBHOOK" ]; then
399
+ echo "Sending Discord Webhook..."
400
+
401
+ # Construct a JSON payload safely using jq (handles escaping quotes/newlines)
402
+ # We create a simple embed with the version and the release body
403
+ jq -n \
404
+ --arg title "🚀 New Beta Build: $VERSION" \
405
+ --arg desc "$RELEASE_BODY" \
406
+ --arg url "https://github.com/$REPO/releases/tag/beta" \
407
+ '{content: null, embeds: [{title: $title, description: $desc, url: $url, color: 5814783}]}' \
408
+ > discord_payload.json
409
+
410
+ curl -H "Content-Type: application/json" \
411
+ -d @discord_payload.json \
412
+ "$DISCORD_WEBHOOK"
413
+ else
414
+ echo "::warning::Skipping Discord notification (DISCORD_WEBHOOK secret not set)"
415
+ fi
416
+
417
+ # --- 2. PR COMMENT FALLBACK ---
418
+ # Find the PR associated with the commit that triggered this push
419
+ echo "Looking for associated PR..."
420
+ PR_NUMBER=$(gh pr list --search "${{ github.sha }}" --state merged --json number --jq '.[0].number')
421
+
422
+ if [ -n "$PR_NUMBER" ]; then
423
+ echo "Found PR #$PR_NUMBER. Posting comment..."
424
+
425
+ echo "## 🚀 Beta Release $VERSION is Live!" > pr_comment.md
426
+ echo "" >> pr_comment.md
427
+ echo "$RELEASE_BODY" >> pr_comment.md
428
+ echo "" >> pr_comment.md
429
+ echo "[View Release on GitHub](https://github.com/$REPO/releases/tag/beta)" >> pr_comment.md
430
+
431
+ gh pr comment "$PR_NUMBER" --body-file pr_comment.md
432
+ else
433
+ echo "No merged PR found for commit ${{ github.sha }}. Skipping PR comment."
434
+ fi
@@ -34,7 +34,6 @@ jobs:
34
34
  uses: astral-sh/setup-uv@v5
35
35
  with:
36
36
  enable-cache: true
37
- cache-dependency-glob: "pyproject.toml"
38
37
  - name: Get Version
39
38
  id: version
40
39
  run: |
@@ -58,7 +57,6 @@ jobs:
58
57
  uses: astral-sh/setup-uv@v5
59
58
  with:
60
59
  enable-cache: true
61
- cache-dependency-glob: "pyproject.toml"
62
60
  - name: Build wheel
63
61
  run: uv build
64
62
  - name: Generate artifact attestation
@@ -90,7 +88,6 @@ jobs:
90
88
  uses: astral-sh/setup-uv@v5
91
89
  with:
92
90
  enable-cache: true
93
- cache-dependency-glob: "pyproject.toml"
94
91
 
95
92
  - name: Disable Windows Defender
96
93
  shell: powershell
@@ -252,7 +249,6 @@ jobs:
252
249
  uses: astral-sh/setup-uv@v5
253
250
  with:
254
251
  enable-cache: true
255
- cache-dependency-glob: "pyproject.toml"
256
252
 
257
253
  - name: Publish to PyPI
258
254
  if: ${{ !inputs.skip_pypi }}
@@ -273,7 +269,11 @@ jobs:
273
269
  fi
274
270
  - name: Create Release Body
275
271
  run: |
276
- echo "### Install / Update:" > release_body.md
272
+ echo "### Docker:" > release_body.md
273
+ echo "" >> release_body.md
274
+ echo "\`docker pull ${{ env.GHCR_ENDPOINT }}:latest\`" >> release_body.md
275
+ echo "" >> release_body.md
276
+ echo "### Python:" >> release_body.md
277
277
  echo "" >> release_body.md
278
278
  echo "\`uv tool upgrade quasarr\`" >> release_body.md
279
279
  echo "" >> release_body.md
@@ -282,8 +282,6 @@ jobs:
282
282
  echo "${{ steps.changelog.outputs.changelog }}" >> release_body.md
283
283
  echo "" >> release_body.md
284
284
  echo "[Attestation](https://github.com/${{ github.repository }}/attestations/${{ needs.build-wheel.outputs.attestation-id }})" >> release_body.md
285
- echo "" >> release_body.md
286
- echo "\`docker pull ${{ env.GHCR_ENDPOINT }}:latest\`" >> release_body.md
287
285
 
288
286
  - name: Create Release
289
287
  uses: ncipollo/release-action@v1
@@ -292,3 +290,44 @@ jobs:
292
290
  artifactErrorsFailBuild: true
293
291
  bodyFile: "release_body.md"
294
292
  tag: v.${{ needs.version.outputs.version }}
293
+
294
+ notify:
295
+ name: Notify Discord
296
+ needs: [ version, release, merge-docker-manifest ]
297
+ if: always() && needs.release.result == 'success'
298
+ runs-on: ubuntu-latest
299
+ steps:
300
+ - uses: actions/checkout@v6
301
+
302
+ - name: Send Discord Webhook
303
+ env:
304
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
305
+ DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
306
+ VERSION: ${{ needs.version.outputs.version }}
307
+ REPO: ${{ github.repository }}
308
+ run: |
309
+ # Use the exact tag format defined in the release job (v.X.X.X)
310
+ TAG="v.$VERSION"
311
+ echo "Fetching release details for $TAG..."
312
+
313
+ # Fetch the body from the release we just created
314
+ RELEASE_BODY=$(gh release view "$TAG" --json body --jq .body)
315
+
316
+ if [ -n "$DISCORD_WEBHOOK" ]; then
317
+ echo "Sending notification..."
318
+
319
+ # Construct JSON payload using jq
320
+ # Color 5763719 is 'Green'
321
+ jq -n \
322
+ --arg title "🚀 New Release: $TAG" \
323
+ --arg desc "$RELEASE_BODY" \
324
+ --arg url "https://github.com/$REPO/releases/tag/$TAG" \
325
+ '{content: null, embeds: [{title: $title, description: $desc, url: $url, color: 5763719}]}' \
326
+ > discord_payload.json
327
+
328
+ curl -H "Content-Type: application/json" \
329
+ -d @discord_payload.json \
330
+ "$DISCORD_WEBHOOK"
331
+ else
332
+ echo "::warning::Skipping Discord notification (DISCORD_WEBHOOK secret not set)"
333
+ fi
@@ -4,8 +4,7 @@
4
4
  *.pyc
5
5
  *.bak
6
6
 
7
- # Virtual Environment by uv
8
- uv.lock
7
+ # Virtual Environment
9
8
  .venv
10
9
 
11
10
  # PyCharm
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: quasarr
3
- Version: 2.4.8
3
+ Version: 2.4.9
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
@@ -11,7 +11,7 @@ Requires-Python: >=3.12
11
11
  Requires-Dist: beautifulsoup4>=4.14.3
12
12
  Requires-Dist: bottle>=0.13.4
13
13
  Requires-Dist: dukpy>=0.5.0
14
- Requires-Dist: pillow>=12.0.0
14
+ Requires-Dist: pillow>=12.1.0
15
15
  Requires-Dist: pycryptodomex>=3.23.0
16
16
  Requires-Dist: requests>=2.32.5
17
17
  Description-Content-Type: text/markdown
@@ -6,6 +6,6 @@ import multiprocessing
6
6
 
7
7
  import quasarr
8
8
 
9
- if __name__ == '__main__':
9
+ if __name__ == "__main__":
10
10
  multiprocessing.freeze_support()
11
11
  quasarr.run()
@@ -13,13 +13,14 @@ COPY --from=uv /uv /usr/local/bin/uv
13
13
  # install local package
14
14
  COPY dist/*.whl /tmp/
15
15
 
16
- # Updated: Added cache mount for uv to speed up installation
16
+ # Configure uv to install tools in globally accessible paths
17
+ ENV UV_TOOL_DIR=/opt/uv-tools
18
+ ENV UV_TOOL_BIN_DIR=/usr/local/bin
19
+
20
+ # The binary will now automatically appear in /usr/local/bin
17
21
  RUN --mount=type=cache,target=/root/.cache/uv \
18
22
  uv tool install /tmp/*.whl --force && rm /tmp/*.whl
19
23
 
20
- # Ensure the binary is in the PATH
21
- ENV PATH="/root/.local/bin:$PATH"
22
-
23
24
  # volumes and ports
24
25
  VOLUME /config
25
26
  EXPOSE 8080
@@ -0,0 +1,55 @@
1
+ # Development Setup for Pull Requests
2
+
3
+ To test your changes before submitting a pull request:
4
+
5
+ **1. Prepare your Environment with `uv`**
6
+
7
+ Ensure you have the development tools (like `ruff`) installed and your environment synced:
8
+
9
+ ```bash
10
+ uv sync --group dev
11
+ ```
12
+
13
+ **2. Run Quasarr with the `--internal_address` parameter**
14
+
15
+ ```bash
16
+ uv run Quasarr.py --internal_address=http://<host-ip>:<port>
17
+ ```
18
+
19
+ Replace `<host-ip>` and `<port>` with the scheme, IP, and port of your host machine.
20
+ The `--internal_address` parameter is **mandatory**.
21
+
22
+ **3. Start the required services using the `dev-services-compose.yml` file**
23
+
24
+ ```bash
25
+ CONFIG_VOLUMES=/path/to/config docker-compose -f docker/dev-services-compose.yml up
26
+ ```
27
+
28
+ Replace `/path/to/config` with your desired configuration location.
29
+ The `CONFIG_VOLUMES` environment variable is **mandatory**.
30
+
31
+ ---
32
+
33
+ ### Code Quality & Maintenance
34
+
35
+ The CI pipeline enforces strict code styling and import optimization. Please run these commands before pushing your
36
+ changes:
37
+
38
+ **Optimize Imports and Fix Linting:**
39
+
40
+ ```bash
41
+ uv run ruff check --fix .
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
+ ```
@@ -8,7 +8,7 @@ dependencies = [
8
8
  "beautifulsoup4>=4.14.3",
9
9
  "bottle>=0.13.4",
10
10
  "dukpy>=0.5.0",
11
- "pillow>=12.0.0",
11
+ "pillow>=12.1.0",
12
12
  "pycryptodomex>=3.23.0",
13
13
  "requests>=2.32.5",
14
14
  ]
@@ -35,7 +35,17 @@ path = "quasarr/providers/version.py"
35
35
  packages = ["quasarr"]
36
36
 
37
37
  [dependency-groups]
38
+ dev = [
39
+ "ruff>=0.14.14",
40
+ ]
38
41
  build = [
39
- "pyinstaller>=6.0.0",
40
- "pillow>=12.0.0",
42
+ "pyinstaller>=6.18.0",
43
+ "pillow>=12.1.0",
41
44
  ]
45
+
46
+ [tool.ruff]
47
+ lint.select = ["I"]
48
+ lint.fixable = ["I"]
49
+
50
+ [tool.ruff.lint.isort]
51
+ known-first-party = ["quasarr"]