quasarr 2.4.11__tar.gz → 2.5.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.5.0}/.github/workflows/PullRequests.yml +12 -5
- {quasarr-2.4.11 → quasarr-2.5.0}/PKG-INFO +1 -1
- {quasarr-2.4.11 → quasarr-2.5.0}/pre-commit.py +40 -27
- {quasarr-2.4.11 → quasarr-2.5.0}/quasarr/api/__init__.py +6 -6
- {quasarr-2.4.11 → quasarr-2.5.0}/quasarr/api/config/__init__.py +22 -169
- {quasarr-2.4.11 → quasarr-2.5.0}/quasarr/providers/version.py +1 -1
- {quasarr-2.4.11 → quasarr-2.5.0}/quasarr/search/__init__.py +86 -15
- {quasarr-2.4.11 → quasarr-2.5.0}/quasarr/storage/setup.py +517 -236
- {quasarr-2.4.11 → quasarr-2.5.0}/.github/FUNDING.yml +0 -0
- {quasarr-2.4.11 → quasarr-2.5.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {quasarr-2.4.11 → quasarr-2.5.0}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {quasarr-2.4.11 → quasarr-2.5.0}/.github/workflows/HostnameRedaction.yml +0 -0
- {quasarr-2.4.11 → quasarr-2.5.0}/.github/workflows/Release.yml +0 -0
- {quasarr-2.4.11 → quasarr-2.5.0}/.gitignore +0 -0
- {quasarr-2.4.11 → quasarr-2.5.0}/.pre-commit-config.yaml +0 -0
- {quasarr-2.4.11 → quasarr-2.5.0}/CONTRIBUTING.md +0 -0
- {quasarr-2.4.11 → quasarr-2.5.0}/LICENSE +0 -0
- {quasarr-2.4.11 → quasarr-2.5.0}/Quasarr.png +0 -0
- {quasarr-2.4.11 → quasarr-2.5.0}/Quasarr.py +0 -0
- {quasarr-2.4.11 → quasarr-2.5.0}/README.md +0 -0
- {quasarr-2.4.11 → quasarr-2.5.0}/docker/Dockerfile +0 -0
- {quasarr-2.4.11 → quasarr-2.5.0}/docker/dev-services-compose.yml +0 -0
- {quasarr-2.4.11 → quasarr-2.5.0}/docker/docker-compose.yml +0 -0
- {quasarr-2.4.11 → quasarr-2.5.0}/pyproject.toml +0 -0
- {quasarr-2.4.11 → quasarr-2.5.0}/quasarr/__init__.py +0 -0
- {quasarr-2.4.11 → quasarr-2.5.0}/quasarr/api/arr/__init__.py +0 -0
- {quasarr-2.4.11 → quasarr-2.5.0}/quasarr/api/captcha/__init__.py +0 -0
- {quasarr-2.4.11 → quasarr-2.5.0}/quasarr/api/packages/__init__.py +0 -0
- {quasarr-2.4.11 → quasarr-2.5.0}/quasarr/api/sponsors_helper/__init__.py +0 -0
- {quasarr-2.4.11 → quasarr-2.5.0}/quasarr/api/statistics/__init__.py +0 -0
- {quasarr-2.4.11 → quasarr-2.5.0}/quasarr/downloads/__init__.py +0 -0
- {quasarr-2.4.11 → quasarr-2.5.0}/quasarr/downloads/linkcrypters/__init__.py +0 -0
- {quasarr-2.4.11 → quasarr-2.5.0}/quasarr/downloads/linkcrypters/al.py +0 -0
- {quasarr-2.4.11 → quasarr-2.5.0}/quasarr/downloads/linkcrypters/filecrypt.py +0 -0
- {quasarr-2.4.11 → quasarr-2.5.0}/quasarr/downloads/linkcrypters/hide.py +0 -0
- {quasarr-2.4.11 → quasarr-2.5.0}/quasarr/downloads/packages/__init__.py +0 -0
- {quasarr-2.4.11 → quasarr-2.5.0}/quasarr/downloads/sources/__init__.py +0 -0
- {quasarr-2.4.11 → quasarr-2.5.0}/quasarr/downloads/sources/al.py +0 -0
- {quasarr-2.4.11 → quasarr-2.5.0}/quasarr/downloads/sources/by.py +0 -0
- {quasarr-2.4.11 → quasarr-2.5.0}/quasarr/downloads/sources/dd.py +0 -0
- {quasarr-2.4.11 → quasarr-2.5.0}/quasarr/downloads/sources/dj.py +0 -0
- {quasarr-2.4.11 → quasarr-2.5.0}/quasarr/downloads/sources/dl.py +0 -0
- {quasarr-2.4.11 → quasarr-2.5.0}/quasarr/downloads/sources/dt.py +0 -0
- {quasarr-2.4.11 → quasarr-2.5.0}/quasarr/downloads/sources/dw.py +0 -0
- {quasarr-2.4.11 → quasarr-2.5.0}/quasarr/downloads/sources/he.py +0 -0
- {quasarr-2.4.11 → quasarr-2.5.0}/quasarr/downloads/sources/mb.py +0 -0
- {quasarr-2.4.11 → quasarr-2.5.0}/quasarr/downloads/sources/nk.py +0 -0
- {quasarr-2.4.11 → quasarr-2.5.0}/quasarr/downloads/sources/nx.py +0 -0
- {quasarr-2.4.11 → quasarr-2.5.0}/quasarr/downloads/sources/sf.py +0 -0
- {quasarr-2.4.11 → quasarr-2.5.0}/quasarr/downloads/sources/sj.py +0 -0
- {quasarr-2.4.11 → quasarr-2.5.0}/quasarr/downloads/sources/sl.py +0 -0
- {quasarr-2.4.11 → quasarr-2.5.0}/quasarr/downloads/sources/wd.py +0 -0
- {quasarr-2.4.11 → quasarr-2.5.0}/quasarr/downloads/sources/wx.py +0 -0
- {quasarr-2.4.11 → quasarr-2.5.0}/quasarr/providers/__init__.py +0 -0
- {quasarr-2.4.11 → quasarr-2.5.0}/quasarr/providers/auth.py +0 -0
- {quasarr-2.4.11 → quasarr-2.5.0}/quasarr/providers/cloudflare.py +0 -0
- {quasarr-2.4.11 → quasarr-2.5.0}/quasarr/providers/hostname_issues.py +0 -0
- {quasarr-2.4.11 → quasarr-2.5.0}/quasarr/providers/html_images.py +0 -0
- {quasarr-2.4.11 → quasarr-2.5.0}/quasarr/providers/html_templates.py +0 -0
- {quasarr-2.4.11 → quasarr-2.5.0}/quasarr/providers/imdb_metadata.py +0 -0
- {quasarr-2.4.11 → quasarr-2.5.0}/quasarr/providers/jd_cache.py +0 -0
- {quasarr-2.4.11 → quasarr-2.5.0}/quasarr/providers/log.py +0 -0
- {quasarr-2.4.11 → quasarr-2.5.0}/quasarr/providers/myjd_api.py +0 -0
- {quasarr-2.4.11 → quasarr-2.5.0}/quasarr/providers/notifications.py +0 -0
- {quasarr-2.4.11 → quasarr-2.5.0}/quasarr/providers/obfuscated.py +0 -0
- {quasarr-2.4.11 → quasarr-2.5.0}/quasarr/providers/sessions/__init__.py +0 -0
- {quasarr-2.4.11 → quasarr-2.5.0}/quasarr/providers/sessions/al.py +0 -0
- {quasarr-2.4.11 → quasarr-2.5.0}/quasarr/providers/sessions/dd.py +0 -0
- {quasarr-2.4.11 → quasarr-2.5.0}/quasarr/providers/sessions/dl.py +0 -0
- {quasarr-2.4.11 → quasarr-2.5.0}/quasarr/providers/sessions/nx.py +0 -0
- {quasarr-2.4.11 → quasarr-2.5.0}/quasarr/providers/shared_state.py +0 -0
- {quasarr-2.4.11 → quasarr-2.5.0}/quasarr/providers/statistics.py +0 -0
- {quasarr-2.4.11 → quasarr-2.5.0}/quasarr/providers/utils.py +0 -0
- {quasarr-2.4.11 → quasarr-2.5.0}/quasarr/providers/web_server.py +0 -0
- {quasarr-2.4.11 → quasarr-2.5.0}/quasarr/search/sources/__init__.py +0 -0
- {quasarr-2.4.11 → quasarr-2.5.0}/quasarr/search/sources/al.py +0 -0
- {quasarr-2.4.11 → quasarr-2.5.0}/quasarr/search/sources/by.py +0 -0
- {quasarr-2.4.11 → quasarr-2.5.0}/quasarr/search/sources/dd.py +0 -0
- {quasarr-2.4.11 → quasarr-2.5.0}/quasarr/search/sources/dj.py +0 -0
- {quasarr-2.4.11 → quasarr-2.5.0}/quasarr/search/sources/dl.py +0 -0
- {quasarr-2.4.11 → quasarr-2.5.0}/quasarr/search/sources/dt.py +0 -0
- {quasarr-2.4.11 → quasarr-2.5.0}/quasarr/search/sources/dw.py +0 -0
- {quasarr-2.4.11 → quasarr-2.5.0}/quasarr/search/sources/fx.py +0 -0
- {quasarr-2.4.11 → quasarr-2.5.0}/quasarr/search/sources/he.py +0 -0
- {quasarr-2.4.11 → quasarr-2.5.0}/quasarr/search/sources/mb.py +0 -0
- {quasarr-2.4.11 → quasarr-2.5.0}/quasarr/search/sources/nk.py +0 -0
- {quasarr-2.4.11 → quasarr-2.5.0}/quasarr/search/sources/nx.py +0 -0
- {quasarr-2.4.11 → quasarr-2.5.0}/quasarr/search/sources/sf.py +0 -0
- {quasarr-2.4.11 → quasarr-2.5.0}/quasarr/search/sources/sj.py +0 -0
- {quasarr-2.4.11 → quasarr-2.5.0}/quasarr/search/sources/sl.py +0 -0
- {quasarr-2.4.11 → quasarr-2.5.0}/quasarr/search/sources/wd.py +0 -0
- {quasarr-2.4.11 → quasarr-2.5.0}/quasarr/search/sources/wx.py +0 -0
- {quasarr-2.4.11 → quasarr-2.5.0}/quasarr/storage/__init__.py +0 -0
- {quasarr-2.4.11 → quasarr-2.5.0}/quasarr/storage/config.py +0 -0
- {quasarr-2.4.11 → quasarr-2.5.0}/quasarr/storage/sqlite_database.py +0 -0
- {quasarr-2.4.11 → quasarr-2.5.0}/uv.lock +0 -0
|
@@ -57,6 +57,7 @@ jobs:
|
|
|
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,8 +69,10 @@ 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 ]
|
|
@@ -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,7 +187,7 @@ 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 }}
|
|
@@ -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
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: quasarr
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.5.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")
|
|
@@ -367,15 +367,15 @@ def get_api(shared_state_dict, shared_state_lock):
|
|
|
367
367
|
/* Dark mode */
|
|
368
368
|
@media (prefers-color-scheme: dark) {{
|
|
369
369
|
:root {{
|
|
370
|
-
--status-success-bg: #
|
|
371
|
-
--status-success-color: #
|
|
372
|
-
--status-success-border: #
|
|
370
|
+
--status-success-bg: #1c4532;
|
|
371
|
+
--status-success-color: #68d391;
|
|
372
|
+
--status-success-border: #276749;
|
|
373
373
|
--status-warning-bg: #3d3520;
|
|
374
374
|
--status-warning-color: #ffb74d;
|
|
375
375
|
--status-warning-border: #d69e2e;
|
|
376
|
-
--status-error-bg: #
|
|
377
|
-
--status-error-color: #
|
|
378
|
-
--status-error-border: #
|
|
376
|
+
--status-error-bg: #3d2d2d;
|
|
377
|
+
--status-error-color: #fc8181;
|
|
378
|
+
--status-error-border: #c53030;
|
|
379
379
|
--alert-warning-bg: #3d3520;
|
|
380
380
|
--alert-warning-border: #d69e2e;
|
|
381
381
|
--card-bg: #2d3748;
|
|
@@ -3,27 +3,24 @@
|
|
|
3
3
|
# Project by https://github.com/rix1337
|
|
4
4
|
|
|
5
5
|
import os
|
|
6
|
-
import re
|
|
7
6
|
import signal
|
|
8
7
|
import threading
|
|
9
8
|
import time
|
|
10
|
-
from urllib.parse import urlparse
|
|
11
9
|
|
|
12
|
-
import
|
|
13
|
-
from bottle import request, response
|
|
10
|
+
from bottle import response
|
|
14
11
|
|
|
15
|
-
from quasarr.providers.html_templates import render_button,
|
|
12
|
+
from quasarr.providers.html_templates import render_button, render_form
|
|
16
13
|
from quasarr.providers.log import info
|
|
17
|
-
from quasarr.providers.shared_state import extract_valid_hostname
|
|
18
|
-
from quasarr.providers.utils import (
|
|
19
|
-
check_flaresolverr,
|
|
20
|
-
extract_allowed_keys,
|
|
21
|
-
extract_kv_pairs,
|
|
22
|
-
)
|
|
23
14
|
from quasarr.storage.config import Config
|
|
24
15
|
from quasarr.storage.setup import (
|
|
16
|
+
check_credentials,
|
|
17
|
+
clear_skip_login,
|
|
18
|
+
delete_skip_flaresolverr_preference,
|
|
19
|
+
get_flaresolverr_status_data,
|
|
20
|
+
get_skip_login,
|
|
25
21
|
hostname_form_html,
|
|
26
|
-
|
|
22
|
+
import_hostnames_from_url,
|
|
23
|
+
save_flaresolverr_url,
|
|
27
24
|
save_hostnames,
|
|
28
25
|
)
|
|
29
26
|
from quasarr.storage.sqlite_database import DataBase
|
|
@@ -50,7 +47,6 @@ def setup_config(app, shared_state):
|
|
|
50
47
|
hostname_form_html(
|
|
51
48
|
shared_state,
|
|
52
49
|
message,
|
|
53
|
-
show_restart_button=True,
|
|
54
50
|
show_skip_management=True,
|
|
55
51
|
)
|
|
56
52
|
+ back_button,
|
|
@@ -60,97 +56,21 @@ def setup_config(app, shared_state):
|
|
|
60
56
|
def hostnames_api():
|
|
61
57
|
return save_hostnames(shared_state, timeout=1, first_run=False)
|
|
62
58
|
|
|
63
|
-
@app.post("/api/hostnames/
|
|
64
|
-
def
|
|
65
|
-
|
|
66
|
-
response.content_type = "application/json"
|
|
67
|
-
try:
|
|
68
|
-
data = request.json
|
|
69
|
-
url = data.get("url", "").strip()
|
|
70
|
-
|
|
71
|
-
if not url:
|
|
72
|
-
return {"success": False, "error": "No URL provided"}
|
|
73
|
-
|
|
74
|
-
# Validate URL
|
|
75
|
-
parsed = urlparse(url)
|
|
76
|
-
if parsed.scheme not in ("http", "https") or not parsed.netloc:
|
|
77
|
-
return {"success": False, "error": "Invalid URL format"}
|
|
78
|
-
|
|
79
|
-
# Fetch content
|
|
80
|
-
try:
|
|
81
|
-
resp = requests.get(url, timeout=15)
|
|
82
|
-
resp.raise_for_status()
|
|
83
|
-
content = resp.text
|
|
84
|
-
except requests.RequestException as e:
|
|
85
|
-
info(f"Failed to fetch hostnames URL: {e}")
|
|
86
|
-
return {
|
|
87
|
-
"success": False,
|
|
88
|
-
"error": "Failed to fetch URL. Check the console log for details.",
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
# Parse hostnames
|
|
92
|
-
allowed_keys = extract_allowed_keys(Config._DEFAULT_CONFIG, "Hostnames")
|
|
93
|
-
results = extract_kv_pairs(content, allowed_keys)
|
|
94
|
-
|
|
95
|
-
if not results:
|
|
96
|
-
return {
|
|
97
|
-
"success": False,
|
|
98
|
-
"error": "No hostnames found in the provided URL",
|
|
99
|
-
}
|
|
59
|
+
@app.post("/api/hostnames/check-credentials/<shorthand>")
|
|
60
|
+
def check_credentials_api(shorthand):
|
|
61
|
+
return check_credentials(shared_state, shorthand)
|
|
100
62
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
for shorthand, hostname in results.items():
|
|
105
|
-
domain_check = extract_valid_hostname(hostname, shorthand)
|
|
106
|
-
domain = domain_check.get("domain")
|
|
107
|
-
if domain:
|
|
108
|
-
valid_hostnames[shorthand] = domain
|
|
109
|
-
else:
|
|
110
|
-
invalid_hostnames[shorthand] = domain_check.get(
|
|
111
|
-
"message", "Invalid"
|
|
112
|
-
)
|
|
113
|
-
|
|
114
|
-
if not valid_hostnames:
|
|
115
|
-
return {
|
|
116
|
-
"success": False,
|
|
117
|
-
"error": "No valid hostnames found in the provided URL",
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
return {
|
|
121
|
-
"success": True,
|
|
122
|
-
"hostnames": valid_hostnames,
|
|
123
|
-
"errors": invalid_hostnames,
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
except Exception as e:
|
|
127
|
-
return {"success": False, "error": f"Error: {str(e)}"}
|
|
63
|
+
@app.post("/api/hostnames/import-url")
|
|
64
|
+
def import_hostnames_route():
|
|
65
|
+
return import_hostnames_from_url()
|
|
128
66
|
|
|
129
67
|
@app.get("/api/skip-login")
|
|
130
|
-
def
|
|
131
|
-
|
|
132
|
-
response.content_type = "application/json"
|
|
133
|
-
skip_db = DataBase("skip_login")
|
|
134
|
-
login_required_sites = ["al", "dd", "dl", "nx"]
|
|
135
|
-
skipped = []
|
|
136
|
-
for site in login_required_sites:
|
|
137
|
-
if skip_db.retrieve(site):
|
|
138
|
-
skipped.append(site)
|
|
139
|
-
return {"skipped": skipped}
|
|
68
|
+
def get_skip_login_route():
|
|
69
|
+
return get_skip_login()
|
|
140
70
|
|
|
141
71
|
@app.delete("/api/skip-login/<shorthand>")
|
|
142
|
-
def
|
|
143
|
-
|
|
144
|
-
response.content_type = "application/json"
|
|
145
|
-
shorthand = shorthand.lower()
|
|
146
|
-
login_required_sites = ["al", "dd", "dl", "nx"]
|
|
147
|
-
if shorthand not in login_required_sites:
|
|
148
|
-
return {"success": False, "error": f"Invalid shorthand: {shorthand}"}
|
|
149
|
-
|
|
150
|
-
skip_db = DataBase("skip_login")
|
|
151
|
-
skip_db.delete(shorthand)
|
|
152
|
-
info(f'Skip login preference cleared for "{shorthand.upper()}"')
|
|
153
|
-
return {"success": True}
|
|
72
|
+
def clear_skip_login_route(shorthand):
|
|
73
|
+
return clear_skip_login(shorthand)
|
|
154
74
|
|
|
155
75
|
@app.get("/flaresolverr")
|
|
156
76
|
def flaresolverr_ui():
|
|
@@ -183,12 +103,6 @@ def setup_config(app, shared_state):
|
|
|
183
103
|
{form_content}
|
|
184
104
|
{render_button("Save", "primary", {"type": "submit", "id": "submitBtn"})}
|
|
185
105
|
</form>
|
|
186
|
-
<p style="font-size:0.875rem; color:var(--secondary, #6c757d); margin-top:1rem;">
|
|
187
|
-
A restart is recommended after configuring FlareSolverr.
|
|
188
|
-
</p>
|
|
189
|
-
<div class="section-divider" style="margin-top:1.5rem; padding-top:1rem; border-top:1px solid var(--divider-color, #dee2e6);">
|
|
190
|
-
{render_button("Restart Quasarr", "secondary", {"type": "button", "onclick": "confirmRestart()"})}
|
|
191
|
-
</div>
|
|
192
106
|
<p>{render_button("Back", "secondary", {"onclick": "location.href='/';"})}</p>
|
|
193
107
|
<script>
|
|
194
108
|
var formSubmitted = false;
|
|
@@ -278,78 +192,17 @@ def setup_config(app, shared_state):
|
|
|
278
192
|
@app.post("/api/flaresolverr")
|
|
279
193
|
def set_flaresolverr_url():
|
|
280
194
|
"""Save FlareSolverr URL from web UI."""
|
|
281
|
-
|
|
282
|
-
config = Config("FlareSolverr")
|
|
283
|
-
|
|
284
|
-
if not url:
|
|
285
|
-
return render_fail("Please provide a FlareSolverr URL.")
|
|
286
|
-
|
|
287
|
-
if not url.startswith("http://") and not url.startswith("https://"):
|
|
288
|
-
url = "http://" + url
|
|
289
|
-
|
|
290
|
-
# Validate URL format
|
|
291
|
-
if not re.search(r"/v\d+$", url):
|
|
292
|
-
return render_fail(
|
|
293
|
-
"FlareSolverr URL must end with /v1 (or similar version path)."
|
|
294
|
-
)
|
|
295
|
-
|
|
296
|
-
try:
|
|
297
|
-
headers = {"Content-Type": "application/json"}
|
|
298
|
-
data = {
|
|
299
|
-
"cmd": "request.get",
|
|
300
|
-
"url": "http://www.google.com/",
|
|
301
|
-
"maxTimeout": 30000,
|
|
302
|
-
}
|
|
303
|
-
resp = requests.post(url, headers=headers, json=data, timeout=30)
|
|
304
|
-
if resp.status_code == 200:
|
|
305
|
-
json_data = resp.json()
|
|
306
|
-
if json_data.get("status") == "ok":
|
|
307
|
-
config.save("url", url)
|
|
308
|
-
# Clear skip preference since we now have a working URL
|
|
309
|
-
DataBase("skip_flaresolverr").delete("skipped")
|
|
310
|
-
# Update user agent from FlareSolverr response
|
|
311
|
-
solution = json_data.get("solution", {})
|
|
312
|
-
solution_ua = solution.get("userAgent")
|
|
313
|
-
if solution_ua:
|
|
314
|
-
shared_state.update("user_agent", solution_ua)
|
|
315
|
-
info(f'FlareSolverr URL configured: "{url}"')
|
|
316
|
-
return render_reconnect_success(
|
|
317
|
-
"FlareSolverr URL saved successfully! A restart is recommended."
|
|
318
|
-
)
|
|
319
|
-
else:
|
|
320
|
-
return render_fail(
|
|
321
|
-
f"FlareSolverr returned unexpected status: {json_data.get('status')}"
|
|
322
|
-
)
|
|
323
|
-
except requests.RequestException:
|
|
324
|
-
return render_fail("Could not reach FlareSolverr!")
|
|
325
|
-
|
|
326
|
-
return render_fail(
|
|
327
|
-
"Could not reach FlareSolverr at that URL (expected HTTP 200)."
|
|
328
|
-
)
|
|
195
|
+
return save_flaresolverr_url(shared_state)
|
|
329
196
|
|
|
330
197
|
@app.get("/api/flaresolverr/status")
|
|
331
198
|
def get_flaresolverr_status():
|
|
332
199
|
"""Return FlareSolverr configuration status."""
|
|
333
|
-
|
|
334
|
-
skip_db = DataBase("skip_flaresolverr")
|
|
335
|
-
is_skipped = bool(skip_db.retrieve("skipped"))
|
|
336
|
-
current_url = Config("FlareSolverr").get("url") or ""
|
|
337
|
-
|
|
338
|
-
# Test connection if URL is set
|
|
339
|
-
is_working = False
|
|
340
|
-
if current_url and not is_skipped:
|
|
341
|
-
is_working = check_flaresolverr(shared_state, current_url)
|
|
342
|
-
|
|
343
|
-
return {"skipped": is_skipped, "url": current_url, "working": is_working}
|
|
200
|
+
return get_flaresolverr_status_data(shared_state)
|
|
344
201
|
|
|
345
202
|
@app.delete("/api/skip-flaresolverr")
|
|
346
203
|
def clear_skip_flaresolverr():
|
|
347
204
|
"""Clear skip FlareSolverr preference."""
|
|
348
|
-
|
|
349
|
-
skip_db = DataBase("skip_flaresolverr")
|
|
350
|
-
skip_db.delete("skipped")
|
|
351
|
-
info("Skip FlareSolverr preference cleared")
|
|
352
|
-
return {"success": True}
|
|
205
|
+
return delete_skip_flaresolverr_preference()
|
|
353
206
|
|
|
354
207
|
@app.post("/api/restart")
|
|
355
208
|
def restart_quasarr():
|