quasarr 2.4.11__tar.gz → 2.6.0__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.
- {quasarr-2.4.11 → quasarr-2.6.0}/.github/workflows/PullRequests.yml +17 -10
- {quasarr-2.4.11 → quasarr-2.6.0}/.github/workflows/Release.yml +8 -2
- {quasarr-2.4.11 → quasarr-2.6.0}/PKG-INFO +1 -1
- {quasarr-2.4.11 → quasarr-2.6.0}/pre-commit.py +40 -27
- {quasarr-2.4.11 → quasarr-2.6.0}/quasarr/__init__.py +30 -35
- {quasarr-2.4.11 → quasarr-2.6.0}/quasarr/api/__init__.py +23 -15
- {quasarr-2.4.11 → quasarr-2.6.0}/quasarr/api/arr/__init__.py +15 -6
- {quasarr-2.4.11 → quasarr-2.6.0}/quasarr/api/captcha/__init__.py +2 -9
- {quasarr-2.4.11 → quasarr-2.6.0}/quasarr/api/config/__init__.py +31 -168
- quasarr-2.6.0/quasarr/api/jdownloader/__init__.py +232 -0
- {quasarr-2.4.11 → quasarr-2.6.0}/quasarr/api/packages/__init__.py +2 -12
- {quasarr-2.4.11 → quasarr-2.6.0}/quasarr/downloads/__init__.py +2 -0
- quasarr-2.6.0/quasarr/downloads/sources/hs.py +131 -0
- {quasarr-2.4.11 → quasarr-2.6.0}/quasarr/providers/html_templates.py +14 -3
- {quasarr-2.4.11 → quasarr-2.6.0}/quasarr/providers/sessions/al.py +4 -0
- {quasarr-2.4.11 → quasarr-2.6.0}/quasarr/providers/shared_state.py +17 -17
- {quasarr-2.4.11 → quasarr-2.6.0}/quasarr/providers/version.py +1 -1
- {quasarr-2.4.11 → quasarr-2.6.0}/quasarr/search/__init__.py +90 -15
- {quasarr-2.4.11 → quasarr-2.6.0}/quasarr/search/sources/al.py +17 -13
- {quasarr-2.4.11 → quasarr-2.6.0}/quasarr/search/sources/by.py +4 -1
- {quasarr-2.4.11 → quasarr-2.6.0}/quasarr/search/sources/dd.py +16 -4
- {quasarr-2.4.11 → quasarr-2.6.0}/quasarr/search/sources/dl.py +13 -1
- quasarr-2.6.0/quasarr/search/sources/hs.py +515 -0
- {quasarr-2.4.11 → quasarr-2.6.0}/quasarr/search/sources/mb.py +1 -7
- {quasarr-2.4.11 → quasarr-2.6.0}/quasarr/search/sources/nx.py +4 -1
- {quasarr-2.4.11 → quasarr-2.6.0}/quasarr/search/sources/wd.py +4 -1
- {quasarr-2.4.11 → quasarr-2.6.0}/quasarr/search/sources/wx.py +10 -8
- {quasarr-2.4.11 → quasarr-2.6.0}/quasarr/storage/config.py +1 -0
- {quasarr-2.4.11 → quasarr-2.6.0}/quasarr/storage/setup.py +564 -266
- {quasarr-2.4.11 → quasarr-2.6.0}/.github/FUNDING.yml +0 -0
- {quasarr-2.4.11 → quasarr-2.6.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {quasarr-2.4.11 → quasarr-2.6.0}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {quasarr-2.4.11 → quasarr-2.6.0}/.github/workflows/HostnameRedaction.yml +0 -0
- {quasarr-2.4.11 → quasarr-2.6.0}/.gitignore +0 -0
- {quasarr-2.4.11 → quasarr-2.6.0}/.pre-commit-config.yaml +0 -0
- {quasarr-2.4.11 → quasarr-2.6.0}/CONTRIBUTING.md +0 -0
- {quasarr-2.4.11 → quasarr-2.6.0}/LICENSE +0 -0
- {quasarr-2.4.11 → quasarr-2.6.0}/Quasarr.png +0 -0
- {quasarr-2.4.11 → quasarr-2.6.0}/Quasarr.py +0 -0
- {quasarr-2.4.11 → quasarr-2.6.0}/README.md +0 -0
- {quasarr-2.4.11 → quasarr-2.6.0}/docker/Dockerfile +0 -0
- {quasarr-2.4.11 → quasarr-2.6.0}/docker/dev-services-compose.yml +0 -0
- {quasarr-2.4.11 → quasarr-2.6.0}/docker/docker-compose.yml +0 -0
- {quasarr-2.4.11 → quasarr-2.6.0}/pyproject.toml +0 -0
- {quasarr-2.4.11 → quasarr-2.6.0}/quasarr/api/sponsors_helper/__init__.py +0 -0
- {quasarr-2.4.11 → quasarr-2.6.0}/quasarr/api/statistics/__init__.py +0 -0
- {quasarr-2.4.11 → quasarr-2.6.0}/quasarr/downloads/linkcrypters/__init__.py +0 -0
- {quasarr-2.4.11 → quasarr-2.6.0}/quasarr/downloads/linkcrypters/al.py +0 -0
- {quasarr-2.4.11 → quasarr-2.6.0}/quasarr/downloads/linkcrypters/filecrypt.py +0 -0
- {quasarr-2.4.11 → quasarr-2.6.0}/quasarr/downloads/linkcrypters/hide.py +0 -0
- {quasarr-2.4.11 → quasarr-2.6.0}/quasarr/downloads/packages/__init__.py +0 -0
- {quasarr-2.4.11 → quasarr-2.6.0}/quasarr/downloads/sources/__init__.py +0 -0
- {quasarr-2.4.11 → quasarr-2.6.0}/quasarr/downloads/sources/al.py +0 -0
- {quasarr-2.4.11 → quasarr-2.6.0}/quasarr/downloads/sources/by.py +0 -0
- {quasarr-2.4.11 → quasarr-2.6.0}/quasarr/downloads/sources/dd.py +0 -0
- {quasarr-2.4.11 → quasarr-2.6.0}/quasarr/downloads/sources/dj.py +0 -0
- {quasarr-2.4.11 → quasarr-2.6.0}/quasarr/downloads/sources/dl.py +0 -0
- {quasarr-2.4.11 → quasarr-2.6.0}/quasarr/downloads/sources/dt.py +0 -0
- {quasarr-2.4.11 → quasarr-2.6.0}/quasarr/downloads/sources/dw.py +0 -0
- {quasarr-2.4.11 → quasarr-2.6.0}/quasarr/downloads/sources/he.py +0 -0
- {quasarr-2.4.11 → quasarr-2.6.0}/quasarr/downloads/sources/mb.py +0 -0
- {quasarr-2.4.11 → quasarr-2.6.0}/quasarr/downloads/sources/nk.py +0 -0
- {quasarr-2.4.11 → quasarr-2.6.0}/quasarr/downloads/sources/nx.py +0 -0
- {quasarr-2.4.11 → quasarr-2.6.0}/quasarr/downloads/sources/sf.py +0 -0
- {quasarr-2.4.11 → quasarr-2.6.0}/quasarr/downloads/sources/sj.py +0 -0
- {quasarr-2.4.11 → quasarr-2.6.0}/quasarr/downloads/sources/sl.py +0 -0
- {quasarr-2.4.11 → quasarr-2.6.0}/quasarr/downloads/sources/wd.py +0 -0
- {quasarr-2.4.11 → quasarr-2.6.0}/quasarr/downloads/sources/wx.py +0 -0
- {quasarr-2.4.11 → quasarr-2.6.0}/quasarr/providers/__init__.py +0 -0
- {quasarr-2.4.11 → quasarr-2.6.0}/quasarr/providers/auth.py +0 -0
- {quasarr-2.4.11 → quasarr-2.6.0}/quasarr/providers/cloudflare.py +0 -0
- {quasarr-2.4.11 → quasarr-2.6.0}/quasarr/providers/hostname_issues.py +0 -0
- {quasarr-2.4.11 → quasarr-2.6.0}/quasarr/providers/html_images.py +0 -0
- {quasarr-2.4.11 → quasarr-2.6.0}/quasarr/providers/imdb_metadata.py +0 -0
- {quasarr-2.4.11 → quasarr-2.6.0}/quasarr/providers/jd_cache.py +0 -0
- {quasarr-2.4.11 → quasarr-2.6.0}/quasarr/providers/log.py +0 -0
- {quasarr-2.4.11 → quasarr-2.6.0}/quasarr/providers/myjd_api.py +0 -0
- {quasarr-2.4.11 → quasarr-2.6.0}/quasarr/providers/notifications.py +0 -0
- {quasarr-2.4.11 → quasarr-2.6.0}/quasarr/providers/obfuscated.py +0 -0
- {quasarr-2.4.11 → quasarr-2.6.0}/quasarr/providers/sessions/__init__.py +0 -0
- {quasarr-2.4.11 → quasarr-2.6.0}/quasarr/providers/sessions/dd.py +0 -0
- {quasarr-2.4.11 → quasarr-2.6.0}/quasarr/providers/sessions/dl.py +0 -0
- {quasarr-2.4.11 → quasarr-2.6.0}/quasarr/providers/sessions/nx.py +0 -0
- {quasarr-2.4.11 → quasarr-2.6.0}/quasarr/providers/statistics.py +0 -0
- {quasarr-2.4.11 → quasarr-2.6.0}/quasarr/providers/utils.py +0 -0
- {quasarr-2.4.11 → quasarr-2.6.0}/quasarr/providers/web_server.py +0 -0
- {quasarr-2.4.11 → quasarr-2.6.0}/quasarr/search/sources/__init__.py +0 -0
- {quasarr-2.4.11 → quasarr-2.6.0}/quasarr/search/sources/dj.py +0 -0
- {quasarr-2.4.11 → quasarr-2.6.0}/quasarr/search/sources/dt.py +0 -0
- {quasarr-2.4.11 → quasarr-2.6.0}/quasarr/search/sources/dw.py +0 -0
- {quasarr-2.4.11 → quasarr-2.6.0}/quasarr/search/sources/fx.py +0 -0
- {quasarr-2.4.11 → quasarr-2.6.0}/quasarr/search/sources/he.py +0 -0
- {quasarr-2.4.11 → quasarr-2.6.0}/quasarr/search/sources/nk.py +0 -0
- {quasarr-2.4.11 → quasarr-2.6.0}/quasarr/search/sources/sf.py +0 -0
- {quasarr-2.4.11 → quasarr-2.6.0}/quasarr/search/sources/sj.py +0 -0
- {quasarr-2.4.11 → quasarr-2.6.0}/quasarr/search/sources/sl.py +0 -0
- {quasarr-2.4.11 → quasarr-2.6.0}/quasarr/storage/__init__.py +0 -0
- {quasarr-2.4.11 → quasarr-2.6.0}/quasarr/storage/sqlite_database.py +0 -0
- {quasarr-2.4.11 → quasarr-2.6.0}/uv.lock +0 -0
|
@@ -53,10 +53,11 @@ jobs:
|
|
|
53
53
|
|
|
54
54
|
version:
|
|
55
55
|
needs: [ quality-check ]
|
|
56
|
-
if: needs.quality-check.outputs.changes_pushed != 'true' && (github.head_ref || github.ref_name
|
|
56
|
+
if: needs.quality-check.outputs.changes_pushed != 'true' && ((github.head_ref == 'dev' && github.base_ref == 'main') || (github.event_name == 'workflow_dispatch' && github.ref_name == 'dev'))
|
|
57
57
|
runs-on: ubuntu-latest
|
|
58
58
|
outputs:
|
|
59
59
|
version: ${{ steps.version.outputs.version }}
|
|
60
|
+
epoch: ${{ steps.version.outputs.epoch }}
|
|
60
61
|
steps:
|
|
61
62
|
- uses: actions/checkout@v6
|
|
62
63
|
- uses: actions/setup-python@v5
|
|
@@ -68,12 +69,14 @@ jobs:
|
|
|
68
69
|
- id: version
|
|
69
70
|
run: |
|
|
70
71
|
VERSION=$(uv run python quasarr/providers/version.py)
|
|
72
|
+
EPOCH=$(date +%s)
|
|
71
73
|
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
|
72
|
-
echo "
|
|
74
|
+
echo "epoch=$EPOCH" >> $GITHUB_OUTPUT
|
|
75
|
+
echo "🏷️ Detected version: $VERSION ($EPOCH)"
|
|
73
76
|
|
|
74
77
|
build-wheel:
|
|
75
78
|
needs: [ quality-check, version ]
|
|
76
|
-
if: needs.quality-check.outputs.changes_pushed != 'true' && (github.head_ref || github.ref_name
|
|
79
|
+
if: needs.quality-check.outputs.changes_pushed != 'true' && ((github.head_ref == 'dev' && github.base_ref == 'main') || (github.event_name == 'workflow_dispatch' && github.ref_name == 'dev'))
|
|
77
80
|
runs-on: ubuntu-latest
|
|
78
81
|
outputs:
|
|
79
82
|
attestation-id: ${{ steps.attest.outputs.attestation-id }}
|
|
@@ -103,7 +106,7 @@ jobs:
|
|
|
103
106
|
|
|
104
107
|
build-exe:
|
|
105
108
|
needs: [ quality-check, version ]
|
|
106
|
-
if: needs.quality-check.outputs.changes_pushed != 'true' && (github.head_ref || github.ref_name
|
|
109
|
+
if: needs.quality-check.outputs.changes_pushed != 'true' && ((github.head_ref == 'dev' && github.base_ref == 'main') || (github.event_name == 'workflow_dispatch' && github.ref_name == 'dev'))
|
|
107
110
|
runs-on: windows-latest
|
|
108
111
|
env:
|
|
109
112
|
TMP: "D:\\a\\temp"
|
|
@@ -168,10 +171,12 @@ jobs:
|
|
|
168
171
|
- name: Create Release Body
|
|
169
172
|
run: |
|
|
170
173
|
echo "📝 Generating Release Body..."
|
|
171
|
-
echo "
|
|
174
|
+
echo "# ${{ needs.version.outputs.version }} (${{ needs.version.outputs.epoch }})" > release_body.md
|
|
175
|
+
echo "" >> release_body.md
|
|
176
|
+
echo "### Docker:" >> release_body.md
|
|
172
177
|
echo "\`docker pull ${{ env.GHCR_ENDPOINT }}:beta\`" >> release_body.md
|
|
173
178
|
echo "### Python:" >> release_body.md
|
|
174
|
-
echo "\`uv tool
|
|
179
|
+
echo "\`uv tool install https://github.com/rix1337/Quasarr/releases/download/beta/quasarr-${{ needs.version.outputs.version }}-py3-none-any.whl\`" >> release_body.md
|
|
175
180
|
echo "### Changelog:" >> release_body.md
|
|
176
181
|
echo "${{ steps.changelog.outputs.changelog }}" >> release_body.md
|
|
177
182
|
echo "[Attestation](https://github.com/${{ github.repository }}/attestations/${{ needs.build-wheel.outputs.attestation-id }})" >> release_body.md
|
|
@@ -182,14 +187,14 @@ jobs:
|
|
|
182
187
|
removeArtifacts: true
|
|
183
188
|
replacesArtifacts: true
|
|
184
189
|
tag: beta
|
|
185
|
-
name:
|
|
190
|
+
name: "${{ needs.version.outputs.version }} (${{ needs.version.outputs.epoch }})"
|
|
186
191
|
bodyFile: "release_body.md"
|
|
187
192
|
prerelease: true
|
|
188
193
|
token: ${{ secrets.GITHUB_TOKEN }}
|
|
189
194
|
|
|
190
195
|
build-docker-amd64:
|
|
191
196
|
needs: [ quality-check, version, build-wheel ]
|
|
192
|
-
if: needs.quality-check.outputs.changes_pushed != 'true' && (github.head_ref || github.ref_name
|
|
197
|
+
if: needs.quality-check.outputs.changes_pushed != 'true' && ((github.head_ref == 'dev' && github.base_ref == 'main') || (github.event_name == 'workflow_dispatch' && github.ref_name == 'dev'))
|
|
193
198
|
runs-on: ubuntu-latest
|
|
194
199
|
steps:
|
|
195
200
|
- uses: actions/checkout@v6
|
|
@@ -221,7 +226,7 @@ jobs:
|
|
|
221
226
|
|
|
222
227
|
build-docker-arm64:
|
|
223
228
|
needs: [ quality-check, version, build-wheel ]
|
|
224
|
-
if: needs.quality-check.outputs.changes_pushed != 'true' && (github.head_ref || github.ref_name
|
|
229
|
+
if: needs.quality-check.outputs.changes_pushed != 'true' && ((github.head_ref == 'dev' && github.base_ref == 'main') || (github.event_name == 'workflow_dispatch' && github.ref_name == 'dev'))
|
|
225
230
|
runs-on: ubuntu-24.04-arm
|
|
226
231
|
steps:
|
|
227
232
|
- uses: actions/checkout@v6
|
|
@@ -292,12 +297,13 @@ jobs:
|
|
|
292
297
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
293
298
|
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
|
|
294
299
|
VERSION: ${{ needs.version.outputs.version }}
|
|
300
|
+
EPOCH: ${{ needs.version.outputs.epoch }}
|
|
295
301
|
REPO: ${{ github.repository }}
|
|
296
302
|
run: |
|
|
297
303
|
echo "📢 Notifying stakeholders..."
|
|
298
304
|
RELEASE_BODY=$(gh release view beta --json body --jq .body)
|
|
299
305
|
if [ -n "$DISCORD_WEBHOOK" ]; then
|
|
300
|
-
jq -n --arg title "🚀 New Beta Build: $VERSION
|
|
306
|
+
jq -n --arg title "🚀 New Beta Build: $VERSION ($EPOCH)" --arg url "https://github.com/$REPO/releases/tag/beta" '{content: null, flags: 4096, embeds: [{title: $title, url: $url, color: 5814783}]}' > discord_payload.json
|
|
301
307
|
curl -H "Content-Type: application/json" -d @discord_payload.json "$DISCORD_WEBHOOK"
|
|
302
308
|
fi
|
|
303
309
|
PR_NUMBER=$(gh pr list --search "${{ github.sha }}" --state merged --json number --jq '.[0].number')
|
|
@@ -338,4 +344,5 @@ jobs:
|
|
|
338
344
|
echo "| Metric | Value |" >> $GITHUB_STEP_SUMMARY
|
|
339
345
|
echo "| --- | --- |" >> $GITHUB_STEP_SUMMARY
|
|
340
346
|
echo "| **Version** | \`${{ needs.version.outputs.version || 'N/A' }}\` |" >> $GITHUB_STEP_SUMMARY
|
|
347
|
+
echo "| **Epoch** | \`${{ needs.version.outputs.epoch || 'N/A' }}\` |" >> $GITHUB_STEP_SUMMARY
|
|
341
348
|
echo "| **Release Job** | ${{ needs.beta-release.result || 'Skipped' }} |" >> $GITHUB_STEP_SUMMARY
|
|
@@ -254,18 +254,24 @@ jobs:
|
|
|
254
254
|
uses: metcalfc/changelog-generator@v4.6.2
|
|
255
255
|
with:
|
|
256
256
|
myToken: ${{ secrets.GITHUB_TOKEN }}
|
|
257
|
-
- name:
|
|
257
|
+
- name: Get PR Body
|
|
258
258
|
env:
|
|
259
259
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
260
260
|
run: |
|
|
261
261
|
PR_BODY=$(gh pr list --search "${{ github.sha }}" --state merged --json body --jq '.[0].body // empty')
|
|
262
|
-
if [ -n "$PR_BODY" ]; then
|
|
262
|
+
if [ -n "$PR_BODY" ]; then
|
|
263
|
+
echo "$PR_BODY" > pr_body.txt
|
|
264
|
+
fi
|
|
263
265
|
- name: Create Release Body
|
|
264
266
|
run: |
|
|
265
267
|
echo "### Docker:" > release_body.md
|
|
266
268
|
echo "\`docker pull ${{ env.GHCR_ENDPOINT }}:latest\`" >> release_body.md
|
|
267
269
|
echo "### Python:" >> release_body.md
|
|
268
270
|
echo "\`uv tool upgrade quasarr\`" >> release_body.md
|
|
271
|
+
if [ -f pr_body.txt ]; then
|
|
272
|
+
cat pr_body.txt >> release_body.md
|
|
273
|
+
echo "" >> release_body.md
|
|
274
|
+
fi
|
|
269
275
|
echo "### Changelog:" >> release_body.md
|
|
270
276
|
echo "${{ steps.changelog.outputs.changelog }}" >> release_body.md
|
|
271
277
|
echo "[Attestation](https://github.com/${{ github.repository }}/attestations/${{ needs.build-wheel.outputs.attestation-id }})" >> release_body.md
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: quasarr
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.6.0
|
|
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,9 +11,16 @@ VERSION_FILE = Path("quasarr/providers/version.py")
|
|
|
11
11
|
PYPROJECT_FILE = Path("pyproject.toml")
|
|
12
12
|
|
|
13
13
|
|
|
14
|
+
def safe_print(msg):
|
|
15
|
+
try:
|
|
16
|
+
print(msg)
|
|
17
|
+
except Exception:
|
|
18
|
+
print(msg.encode("ascii", errors="replace").decode("ascii"))
|
|
19
|
+
|
|
20
|
+
|
|
14
21
|
def run(cmd, check=True, capture=False, text=True):
|
|
15
22
|
"""Helper to run shell commands comfortably."""
|
|
16
|
-
|
|
23
|
+
safe_print(f"⚙️ Exec: {' '.join(cmd)}")
|
|
17
24
|
return subprocess.run(cmd, check=check, capture_output=capture, text=text)
|
|
18
25
|
|
|
19
26
|
|
|
@@ -29,29 +36,31 @@ def git_status_has_changes():
|
|
|
29
36
|
|
|
30
37
|
|
|
31
38
|
def task_format():
|
|
32
|
-
|
|
39
|
+
safe_print("\n🔍 --- 1. FORMATTING & SYNTAX CHECK ---")
|
|
33
40
|
|
|
34
41
|
# Runs Ruff using the rules defined in pyproject.toml
|
|
35
42
|
result = run(["uv", "run", "ruff", "check", "--fix", "."], check=False)
|
|
36
43
|
|
|
37
44
|
if result.returncode != 0:
|
|
38
|
-
|
|
45
|
+
safe_print(
|
|
46
|
+
"❌ Critical errors or syntax issues found. Fix them before staging."
|
|
47
|
+
)
|
|
39
48
|
return False
|
|
40
49
|
|
|
41
50
|
# Standard formatting (indentation/spacing)
|
|
42
51
|
run(["uv", "run", "ruff", "format", "."], check=False)
|
|
43
52
|
|
|
44
53
|
if git_status_has_changes():
|
|
45
|
-
|
|
54
|
+
safe_print("✅ Linting fixes applied and staged.")
|
|
46
55
|
run(["git", "add", "."])
|
|
47
56
|
return True
|
|
48
57
|
|
|
49
|
-
|
|
58
|
+
safe_print("✨ Code style is already perfect.")
|
|
50
59
|
return False
|
|
51
60
|
|
|
52
61
|
|
|
53
62
|
def task_upgrade_deps():
|
|
54
|
-
|
|
63
|
+
safe_print("\n📦 --- 2. DEPENDENCIES ---")
|
|
55
64
|
try:
|
|
56
65
|
with open(PYPROJECT_FILE, "rb") as f:
|
|
57
66
|
pyproj = tomllib.load(f)
|
|
@@ -65,7 +74,7 @@ def task_upgrade_deps():
|
|
|
65
74
|
if deps:
|
|
66
75
|
pkgs = [get_pkg_name(d) for d in deps if get_pkg_name(d)]
|
|
67
76
|
if pkgs:
|
|
68
|
-
|
|
77
|
+
safe_print(f"⬆️ Upgrading main: {pkgs}")
|
|
69
78
|
run(["uv", "add", "--upgrade"] + pkgs, check=False)
|
|
70
79
|
|
|
71
80
|
# Groups
|
|
@@ -74,27 +83,27 @@ def task_upgrade_deps():
|
|
|
74
83
|
if g_deps:
|
|
75
84
|
pkgs = [get_pkg_name(d) for d in g_deps if get_pkg_name(d)]
|
|
76
85
|
if pkgs:
|
|
77
|
-
|
|
86
|
+
safe_print(f"🏗️ Upgrading group '{group}': {pkgs}")
|
|
78
87
|
run(
|
|
79
88
|
["uv", "add", "--group", group, "--upgrade"] + pkgs, check=False
|
|
80
89
|
)
|
|
81
90
|
|
|
82
91
|
# Lock file
|
|
83
|
-
|
|
92
|
+
safe_print("🔒 Refreshing lockfile...")
|
|
84
93
|
run(["uv", "lock", "--upgrade"], check=False)
|
|
85
94
|
|
|
86
95
|
except Exception as e:
|
|
87
|
-
|
|
96
|
+
safe_print(f"⚠️ Dependency upgrade failed: {e}")
|
|
88
97
|
|
|
89
98
|
if git_status_has_changes():
|
|
90
|
-
|
|
99
|
+
safe_print("✅ Dependencies updated.")
|
|
91
100
|
run(["git", "add", "."])
|
|
92
101
|
return True
|
|
93
102
|
return False
|
|
94
103
|
|
|
95
104
|
|
|
96
105
|
def task_version_bump():
|
|
97
|
-
|
|
106
|
+
safe_print("\n🏷️ --- 3. VERSION CHECK ---")
|
|
98
107
|
new_v = ""
|
|
99
108
|
|
|
100
109
|
def get_ver(content):
|
|
@@ -118,7 +127,7 @@ def task_version_bump():
|
|
|
118
127
|
return (0, 0, 0)
|
|
119
128
|
|
|
120
129
|
try:
|
|
121
|
-
|
|
130
|
+
safe_print("🌐 Fetching remote to compare versions...")
|
|
122
131
|
run(["git", "fetch", "origin", "main"], check=False)
|
|
123
132
|
try:
|
|
124
133
|
base = subprocess.check_output(
|
|
@@ -128,18 +137,22 @@ def task_version_bump():
|
|
|
128
137
|
base = "origin/main"
|
|
129
138
|
|
|
130
139
|
# Read Main Version
|
|
131
|
-
|
|
132
|
-
|
|
140
|
+
try:
|
|
141
|
+
main_v_content = subprocess.check_output(
|
|
142
|
+
["git", "show", f"{base}:{VERSION_FILE.as_posix()}"], text=True
|
|
143
|
+
)
|
|
144
|
+
main_v = get_ver(main_v_content)
|
|
145
|
+
except Exception:
|
|
146
|
+
main_v = None
|
|
133
147
|
|
|
134
|
-
#
|
|
135
|
-
run(["git", "checkout", "HEAD", "--", str(VERSION_FILE)], capture=True)
|
|
148
|
+
# Read Current Version
|
|
136
149
|
curr_v = get_ver(VERSION_FILE.read_text())
|
|
137
150
|
|
|
138
|
-
|
|
151
|
+
safe_print(f"📊 Main: {main_v} | Current: {curr_v}")
|
|
139
152
|
|
|
140
153
|
if main_v and curr_v and ver_tuple(curr_v) <= ver_tuple(main_v):
|
|
141
154
|
new_v = bump(main_v)
|
|
142
|
-
|
|
155
|
+
safe_print(f"🚀 Bumping version to: {new_v}")
|
|
143
156
|
content = VERSION_FILE.read_text().replace(f'"{curr_v}"', f'"{new_v}"')
|
|
144
157
|
VERSION_FILE.write_text(content)
|
|
145
158
|
|
|
@@ -147,7 +160,7 @@ def task_version_bump():
|
|
|
147
160
|
return True, new_v
|
|
148
161
|
|
|
149
162
|
except Exception as e:
|
|
150
|
-
|
|
163
|
+
safe_print(f"⚠️ Version check warning (non-fatal): {e}")
|
|
151
164
|
|
|
152
165
|
return False, new_v
|
|
153
166
|
|
|
@@ -166,7 +179,7 @@ def main():
|
|
|
166
179
|
|
|
167
180
|
# --- CI Specific Logic ---
|
|
168
181
|
if is_ci and (fixed_format or fixed_deps or fixed_version):
|
|
169
|
-
|
|
182
|
+
safe_print("\n📤 --- 4. PUSH & REPORT ---")
|
|
170
183
|
|
|
171
184
|
run(["git", "config", "--global", "user.name", "github-actions[bot]"])
|
|
172
185
|
run(
|
|
@@ -195,7 +208,7 @@ def main():
|
|
|
195
208
|
try:
|
|
196
209
|
run(["git", "commit", "-m", msg])
|
|
197
210
|
target_ref = get_env("TARGET_REF")
|
|
198
|
-
|
|
211
|
+
safe_print(f"🔄 Rebase and pushing to {target_ref}...")
|
|
199
212
|
run(["git", "pull", "--rebase", "origin", target_ref], check=False)
|
|
200
213
|
run(["git", "push", "origin", f"HEAD:{target_ref}"])
|
|
201
214
|
|
|
@@ -203,7 +216,7 @@ def main():
|
|
|
203
216
|
with open(os.environ["GITHUB_OUTPUT"], "a") as f:
|
|
204
217
|
f.write("changes_pushed=true\n")
|
|
205
218
|
except subprocess.CalledProcessError as e:
|
|
206
|
-
|
|
219
|
+
safe_print(f"❌ ::error::Failed to push fixes. ({e})")
|
|
207
220
|
sys.exit(1)
|
|
208
221
|
|
|
209
222
|
repo = get_env("GITHUB_REPO")
|
|
@@ -223,7 +236,7 @@ def main():
|
|
|
223
236
|
pass
|
|
224
237
|
|
|
225
238
|
if pr_num:
|
|
226
|
-
|
|
239
|
+
safe_print(f"💬 Posting status update to PR #{pr_num}...")
|
|
227
240
|
fixes_list = ""
|
|
228
241
|
if fixed_format:
|
|
229
242
|
fixes_list += "- ✅ **Formatted Code**\n"
|
|
@@ -252,18 +265,18 @@ def main():
|
|
|
252
265
|
check=False,
|
|
253
266
|
)
|
|
254
267
|
|
|
255
|
-
|
|
268
|
+
safe_print(f"⚡ Triggering workflow: {workflow_name}...")
|
|
256
269
|
ret = run(
|
|
257
270
|
["gh", "workflow", "run", workflow_name, "--ref", target_ref], check=False
|
|
258
271
|
)
|
|
259
272
|
|
|
260
273
|
if ret.returncode != 0:
|
|
261
|
-
|
|
274
|
+
safe_print("⚠️ ::warning::Could not auto-trigger next run.")
|
|
262
275
|
|
|
263
276
|
sys.exit(0)
|
|
264
277
|
|
|
265
278
|
else:
|
|
266
|
-
|
|
279
|
+
safe_print("\n✨ Clean run. No changes needed.")
|
|
267
280
|
if "GITHUB_OUTPUT" in os.environ:
|
|
268
281
|
with open(os.environ["GITHUB_OUTPUT"], "a") as f:
|
|
269
282
|
f.write("changes_pushed=false\n")
|
|
@@ -386,48 +386,43 @@ def jdownloader_connection(shared_state_dict, shared_state_lock):
|
|
|
386
386
|
try:
|
|
387
387
|
shared_state.set_state(shared_state_dict, shared_state_lock)
|
|
388
388
|
|
|
389
|
-
|
|
389
|
+
while True:
|
|
390
|
+
shared_state.set_device_from_config()
|
|
390
391
|
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
if not connection_established:
|
|
395
|
-
i = 0
|
|
396
|
-
while i < 10:
|
|
397
|
-
i += 1
|
|
392
|
+
device = shared_state.get_device()
|
|
393
|
+
|
|
394
|
+
try:
|
|
398
395
|
info(
|
|
399
|
-
f'Connection
|
|
396
|
+
f'Connection to JDownloader successful. Device name: "{device.name}"'
|
|
400
397
|
)
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
shared_state.get_device() and shared_state.get_device().name
|
|
405
|
-
)
|
|
406
|
-
if connection_established:
|
|
407
|
-
break
|
|
398
|
+
except Exception as e:
|
|
399
|
+
info(f"Error connecting to JDownloader: {e}! Stopping Quasarr...")
|
|
400
|
+
sys.exit(1)
|
|
408
401
|
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
except Exception as e:
|
|
414
|
-
info(f"Error connecting to JDownloader: {e}! Stopping Quasarr!")
|
|
415
|
-
sys.exit(1)
|
|
402
|
+
try:
|
|
403
|
+
shared_state.set_device_settings()
|
|
404
|
+
except Exception as e:
|
|
405
|
+
print(f"Error checking settings: {e}")
|
|
416
406
|
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
407
|
+
try:
|
|
408
|
+
shared_state.update_jdownloader()
|
|
409
|
+
except Exception as e:
|
|
410
|
+
print(f"Error updating JDownloader: {e}")
|
|
421
411
|
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
412
|
+
try:
|
|
413
|
+
shared_state.start_downloads()
|
|
414
|
+
except Exception as e:
|
|
415
|
+
print(f"Error starting downloads: {e}")
|
|
426
416
|
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
417
|
+
while True:
|
|
418
|
+
time.sleep(300)
|
|
419
|
+
device_state = shared_state.check_device(
|
|
420
|
+
shared_state.values.get("device")
|
|
421
|
+
)
|
|
422
|
+
if not device_state:
|
|
423
|
+
info("Lost connection to JDownloader. Reconnecting...")
|
|
424
|
+
shared_state.update("device", False)
|
|
425
|
+
break
|
|
431
426
|
|
|
432
427
|
except KeyboardInterrupt:
|
|
433
428
|
pass
|
|
@@ -8,6 +8,7 @@ import quasarr.providers.html_images as images
|
|
|
8
8
|
from quasarr.api.arr import setup_arr_routes
|
|
9
9
|
from quasarr.api.captcha import setup_captcha_routes
|
|
10
10
|
from quasarr.api.config import setup_config
|
|
11
|
+
from quasarr.api.jdownloader import get_jdownloader_modal_script, get_jdownloader_status
|
|
11
12
|
from quasarr.api.packages import setup_packages_routes
|
|
12
13
|
from quasarr.api.sponsors_helper import setup_sponsors_helper_routes
|
|
13
14
|
from quasarr.api.statistics import setup_statistics
|
|
@@ -49,12 +50,9 @@ def get_api(shared_state_dict, shared_state_lock):
|
|
|
49
50
|
protected = shared_state.get_db("protected").retrieve_all_titles()
|
|
50
51
|
api_key = Config("API").get("key")
|
|
51
52
|
|
|
52
|
-
# Get
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
jd_connected = device is not None and device is not False
|
|
56
|
-
except:
|
|
57
|
-
jd_connected = False
|
|
53
|
+
# Get JDownloader status and modal script
|
|
54
|
+
jd_status = get_jdownloader_status(shared_state)
|
|
55
|
+
jd_modal_script = get_jdownloader_modal_script()
|
|
58
56
|
|
|
59
57
|
# Calculate hostname status
|
|
60
58
|
hostnames_config = Config("Hostnames")
|
|
@@ -126,10 +124,14 @@ def get_api(shared_state_dict, shared_state_lock):
|
|
|
126
124
|
# Status bars
|
|
127
125
|
status_bars = f"""
|
|
128
126
|
<div class="status-bar">
|
|
129
|
-
<span class="status-pill {"
|
|
130
|
-
|
|
127
|
+
<span class="status-pill {jd_status['status_class']}"
|
|
128
|
+
onclick="openJDownloaderModal()"
|
|
129
|
+
title="Click to configure JDownloader">
|
|
130
|
+
{jd_status['status_text']}
|
|
131
131
|
</span>
|
|
132
|
-
<span class="status-pill {hostname_status_class}"
|
|
132
|
+
<span class="status-pill {hostname_status_class}"
|
|
133
|
+
onclick="location.href='/hostnames'"
|
|
134
|
+
title="Click to configure Hostnames">
|
|
133
135
|
{hostname_status_emoji} {hostname_status_text}
|
|
134
136
|
</span>
|
|
135
137
|
</div>
|
|
@@ -209,6 +211,11 @@ def get_api(shared_state_dict, shared_state_lock):
|
|
|
209
211
|
padding: 8px 16px;
|
|
210
212
|
border-radius: 0.5rem;
|
|
211
213
|
font-weight: 500;
|
|
214
|
+
transition: transform 0.1s ease;
|
|
215
|
+
cursor: pointer;
|
|
216
|
+
}}
|
|
217
|
+
.status-pill:hover {{
|
|
218
|
+
transform: scale(1.05);
|
|
212
219
|
}}
|
|
213
220
|
.status-pill.success {{
|
|
214
221
|
background: var(--status-success-bg, #e8f5e9);
|
|
@@ -367,15 +374,15 @@ def get_api(shared_state_dict, shared_state_lock):
|
|
|
367
374
|
/* Dark mode */
|
|
368
375
|
@media (prefers-color-scheme: dark) {{
|
|
369
376
|
:root {{
|
|
370
|
-
--status-success-bg: #
|
|
371
|
-
--status-success-color: #
|
|
372
|
-
--status-success-border: #
|
|
377
|
+
--status-success-bg: #1c4532;
|
|
378
|
+
--status-success-color: #68d391;
|
|
379
|
+
--status-success-border: #276749;
|
|
373
380
|
--status-warning-bg: #3d3520;
|
|
374
381
|
--status-warning-color: #ffb74d;
|
|
375
382
|
--status-warning-border: #d69e2e;
|
|
376
|
-
--status-error-bg: #
|
|
377
|
-
--status-error-color: #
|
|
378
|
-
--status-error-border: #
|
|
383
|
+
--status-error-bg: #3d2d2d;
|
|
384
|
+
--status-error-color: #fc8181;
|
|
385
|
+
--status-error-border: #c53030;
|
|
379
386
|
--alert-warning-bg: #3d3520;
|
|
380
387
|
--alert-warning-border: #d69e2e;
|
|
381
388
|
--card-bg: #2d3748;
|
|
@@ -478,6 +485,7 @@ def get_api(shared_state_dict, shared_state_lock):
|
|
|
478
485
|
);
|
|
479
486
|
}}
|
|
480
487
|
</script>
|
|
488
|
+
{jd_modal_script}
|
|
481
489
|
"""
|
|
482
490
|
# Add logout link for form auth
|
|
483
491
|
logout_html = '<a href="/logout">Logout</a>' if show_logout_link() else ""
|
|
@@ -56,6 +56,16 @@ def parse_payload(payload_str):
|
|
|
56
56
|
|
|
57
57
|
|
|
58
58
|
def setup_arr_routes(app):
|
|
59
|
+
def check_user_agent():
|
|
60
|
+
user_agent = request.headers.get("User-Agent") or ""
|
|
61
|
+
if not any(
|
|
62
|
+
tool in user_agent.lower() for tool in ["radarr", "sonarr", "lazylibrarian"]
|
|
63
|
+
):
|
|
64
|
+
msg = f"Unsupported User-Agent: {user_agent}. Quasarr as a compatibility layer must be called by Radarr, Sonarr or LazyLibrarian directly."
|
|
65
|
+
info(msg)
|
|
66
|
+
abort(406, msg)
|
|
67
|
+
return user_agent
|
|
68
|
+
|
|
59
69
|
@app.get("/download/")
|
|
60
70
|
def fake_nzb_file():
|
|
61
71
|
payload = request.query.payload
|
|
@@ -75,6 +85,7 @@ def setup_arr_routes(app):
|
|
|
75
85
|
@app.post("/api")
|
|
76
86
|
@require_api_key
|
|
77
87
|
def download_fake_nzb_file():
|
|
88
|
+
request_from = check_user_agent()
|
|
78
89
|
downloads = request.files.getall("name")
|
|
79
90
|
nzo_ids = [] # naming structure for package IDs expected in newznab
|
|
80
91
|
|
|
@@ -97,7 +108,6 @@ def setup_arr_routes(app):
|
|
|
97
108
|
source_key = root.find(".//file").attrib.get("source_key") or None
|
|
98
109
|
|
|
99
110
|
info(f'Attempting download for "{title}"')
|
|
100
|
-
request_from = request.headers.get("User-Agent")
|
|
101
111
|
downloaded = download(
|
|
102
112
|
shared_state,
|
|
103
113
|
request_from,
|
|
@@ -128,6 +138,8 @@ def setup_arr_routes(app):
|
|
|
128
138
|
@app.get("/api/<mirror>")
|
|
129
139
|
@require_api_key
|
|
130
140
|
def quasarr_api(mirror=None):
|
|
141
|
+
request_from = check_user_agent()
|
|
142
|
+
|
|
131
143
|
api_type = (
|
|
132
144
|
"arr_download_client"
|
|
133
145
|
if request.query.mode
|
|
@@ -198,7 +210,6 @@ def setup_arr_routes(app):
|
|
|
198
210
|
|
|
199
211
|
nzo_ids = []
|
|
200
212
|
info(f'Attempting download for "{parsed_payload["title"]}"')
|
|
201
|
-
request_from = "lazylibrarian"
|
|
202
213
|
|
|
203
214
|
downloaded = download(
|
|
204
215
|
shared_state,
|
|
@@ -267,8 +278,6 @@ def setup_arr_routes(app):
|
|
|
267
278
|
)
|
|
268
279
|
|
|
269
280
|
mode = request.query.t
|
|
270
|
-
request_from = request.headers.get("User-Agent")
|
|
271
|
-
|
|
272
281
|
if mode == "caps":
|
|
273
282
|
info(f"Providing indexer capability information to {request_from}")
|
|
274
283
|
return """<?xml version="1.0" encoding="UTF-8"?>
|
|
@@ -352,10 +361,10 @@ def setup_arr_routes(app):
|
|
|
352
361
|
mirror=mirror,
|
|
353
362
|
)
|
|
354
363
|
else:
|
|
355
|
-
|
|
364
|
+
# sonarr expects this but we will not support non-imdbid searches
|
|
365
|
+
debug(
|
|
356
366
|
f"Ignoring search request from {request_from} - only imdbid searches are supported"
|
|
357
367
|
)
|
|
358
|
-
releases = [] # sonarr expects this but we will not support non-imdbid searches
|
|
359
368
|
|
|
360
369
|
items = ""
|
|
361
370
|
for release in releases:
|
|
@@ -11,6 +11,7 @@ import requests
|
|
|
11
11
|
from bottle import HTTPResponse, redirect, request, response
|
|
12
12
|
|
|
13
13
|
import quasarr.providers.html_images as images
|
|
14
|
+
from quasarr.api.jdownloader import get_jdownloader_disconnected_page
|
|
14
15
|
from quasarr.downloads.linkcrypters.filecrypt import DLC, get_filecrypt_links
|
|
15
16
|
from quasarr.downloads.packages import delete_package
|
|
16
17
|
from quasarr.providers import obfuscated, shared_state
|
|
@@ -46,15 +47,7 @@ def setup_captcha_routes(app):
|
|
|
46
47
|
except KeyError:
|
|
47
48
|
device = None
|
|
48
49
|
if not device:
|
|
49
|
-
return
|
|
50
|
-
<div class="status-bar">
|
|
51
|
-
<span class="status-pill error">
|
|
52
|
-
❌ JDownloader disconnected
|
|
53
|
-
</span>
|
|
54
|
-
</div>
|
|
55
|
-
<p>
|
|
56
|
-
{render_button("Back", "secondary", {"onclick": "location.href='/'"})}
|
|
57
|
-
</p>''')
|
|
50
|
+
return get_jdownloader_disconnected_page(shared_state)
|
|
58
51
|
|
|
59
52
|
protected = shared_state.get_db("protected").retrieve_all_titles()
|
|
60
53
|
if not protected:
|