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