Fix empty proxy sources (empty pool), first-run bat, elite hint
- load_settings/sanitize_settings: treat sources=[] as invalid; restore defaults (all([]) was true) - _build_pool: defensive default sources + clearer empty-fetch log; elite-only empty fetch message - FirstRun_Build_And_Install.bat: Python check + setup_and_build.ps1 - README + test for empty sources sanitize Made-with: Cursor
This commit is contained in:
31
FirstRun_Build_And_Install.bat
Normal file
31
FirstRun_Build_And_Install.bat
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
@echo off
|
||||||
|
setlocal
|
||||||
|
title Proxy God — first-time build
|
||||||
|
cd /d "%~dp0"
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo === Proxy God: upgrade pip, install deps, build exe, Desktop shortcut ===
|
||||||
|
echo.
|
||||||
|
|
||||||
|
where py >nul 2>nul && py -3 -c "import sys; assert sys.version_info>=(3,10)" 2>nul && goto RUN
|
||||||
|
where python >nul 2>nul && python -c "import sys; assert sys.version_info>=(3,10)" 2>nul && goto RUN
|
||||||
|
|
||||||
|
echo [X] Python 3.10+ not found on PATH.
|
||||||
|
echo winget install Python.Python.3.12 --accept-package-agreements --accept-source-agreements
|
||||||
|
echo Or https://www.python.org/downloads/ ^(enable "Add python.exe to PATH"^)
|
||||||
|
echo.
|
||||||
|
pause
|
||||||
|
exit /b 1
|
||||||
|
|
||||||
|
:RUN
|
||||||
|
powershell -NoProfile -ExecutionPolicy Bypass -File "%~dp0scripts\setup_and_build.ps1"
|
||||||
|
if errorlevel 1 (
|
||||||
|
echo.
|
||||||
|
echo Build failed — see messages above.
|
||||||
|
pause
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
echo.
|
||||||
|
echo === Done. Use Desktop: "Proxy God.lnk" ===
|
||||||
|
pause
|
||||||
|
endlocal
|
||||||
@@ -62,7 +62,7 @@ Typical flow:
|
|||||||
```powershell
|
```powershell
|
||||||
powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\setup_and_build.ps1
|
powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\setup_and_build.ps1
|
||||||
```
|
```
|
||||||
Same steps from **`build_exe.bat`** in the repo root (it calls that script).
|
Same steps from **`build_exe.bat`** or double‑click **`FirstRun_Build_And_Install.bat`** (checks Python 3.10+, then runs the same script; pauses at the end so you can read errors).
|
||||||
|
|
||||||
| Where | What |
|
| Where | What |
|
||||||
|--------|------|
|
|--------|------|
|
||||||
|
|||||||
@@ -217,6 +217,10 @@ def sanitize_settings(s: Settings) -> tuple[Settings, bool]:
|
|||||||
except (TypeError, ValueError):
|
except (TypeError, ValueError):
|
||||||
s.validation_timeout_seconds = 12.0
|
s.validation_timeout_seconds = 12.0
|
||||||
changed = True
|
changed = True
|
||||||
|
# Empty list survives "all(isinstance…)" — would skip all fetches and yield an empty pool.
|
||||||
|
if not isinstance(s.sources, list) or not all(isinstance(x, str) for x in s.sources) or len(s.sources) == 0:
|
||||||
|
s.sources = list(Settings().sources)
|
||||||
|
changed = True
|
||||||
return s, changed
|
return s, changed
|
||||||
|
|
||||||
|
|
||||||
@@ -240,8 +244,12 @@ def load_settings() -> Settings:
|
|||||||
for k, v in raw.items():
|
for k, v in raw.items():
|
||||||
if hasattr(s, k):
|
if hasattr(s, k):
|
||||||
setattr(s, k, v)
|
setattr(s, k, v)
|
||||||
if not isinstance(s.sources, list) or not all(isinstance(x, str) for x in s.sources):
|
if (
|
||||||
s.sources = Settings().sources
|
not isinstance(s.sources, list)
|
||||||
|
or not all(isinstance(x, str) for x in s.sources)
|
||||||
|
or len(s.sources) == 0
|
||||||
|
):
|
||||||
|
s.sources = list(Settings().sources)
|
||||||
if s.obfuscation_mode not in OBFUSCATION_MODES:
|
if s.obfuscation_mode not in OBFUSCATION_MODES:
|
||||||
s.obfuscation_mode = "auto"
|
s.obfuscation_mode = "auto"
|
||||||
if not isinstance(s.pinned_chain, list):
|
if not isinstance(s.pinned_chain, list):
|
||||||
|
|||||||
@@ -469,6 +469,14 @@ class ChainService:
|
|||||||
timeout=55.0,
|
timeout=55.0,
|
||||||
)
|
)
|
||||||
entries = normalize_entries(rows, s.prefer_elite)
|
entries = normalize_entries(rows, s.prefer_elite)
|
||||||
|
if s.prefer_elite and rows and not entries:
|
||||||
|
self._notify({
|
||||||
|
"type": "log",
|
||||||
|
"text": (
|
||||||
|
f"Source returned {len(rows)} proxies but none marked “elite” — all skipped. "
|
||||||
|
"Turn off “Elite proxies only” in Chain Builder if every fetch is empty."
|
||||||
|
),
|
||||||
|
})
|
||||||
self._notify({"type": "log", "text": f"Fetched {len(entries)} from source."})
|
self._notify({"type": "log", "text": f"Fetched {len(entries)} from source."})
|
||||||
return entries
|
return entries
|
||||||
except asyncio.TimeoutError:
|
except asyncio.TimeoutError:
|
||||||
@@ -478,13 +486,26 @@ class ChainService:
|
|||||||
self._notify({"type": "log", "text": f"Fetch error: {e!s}"})
|
self._notify({"type": "log", "text": f"Fetch error: {e!s}"})
|
||||||
return []
|
return []
|
||||||
|
|
||||||
results = await asyncio.gather(*(_fetch_one(u) for u in s.sources))
|
src_urls = list(s.sources) if s.sources else list(Settings().sources)
|
||||||
|
if not s.sources:
|
||||||
|
self._notify({
|
||||||
|
"type": "log",
|
||||||
|
"text": "Settings had no proxy sources — using built-in defaults (save Settings to persist).",
|
||||||
|
})
|
||||||
|
if not src_urls:
|
||||||
|
self._notify({"type": "log", "text": "No proxy source URLs configured — cannot build a pool."})
|
||||||
|
return []
|
||||||
|
|
||||||
|
results = await asyncio.gather(*(_fetch_one(u) for u in src_urls))
|
||||||
raw_urls: list[str] = []
|
raw_urls: list[str] = []
|
||||||
for chunk in results:
|
for chunk in results:
|
||||||
raw_urls.extend(chunk)
|
raw_urls.extend(chunk)
|
||||||
|
|
||||||
if not raw_urls:
|
if not raw_urls:
|
||||||
self._notify({"type": "log", "text": "No proxies fetched from any source!"})
|
self._notify({
|
||||||
|
"type": "log",
|
||||||
|
"text": "No proxies fetched from any source (check network, URLs, or turn off “Elite only” if every list is filtered empty).",
|
||||||
|
})
|
||||||
return []
|
return []
|
||||||
|
|
||||||
# Deduplicate, remove blacklisted, shuffle before capping
|
# Deduplicate, remove blacklisted, shuffle before capping
|
||||||
|
|||||||
@@ -46,6 +46,13 @@ class TestConfig(unittest.TestCase):
|
|||||||
self.assertNotIn("secret", r)
|
self.assertNotIn("secret", r)
|
||||||
self.assertNotIn("alice", r)
|
self.assertNotIn("alice", r)
|
||||||
|
|
||||||
|
def test_sanitize_restores_empty_sources(self) -> None:
|
||||||
|
s = Settings()
|
||||||
|
s.sources = []
|
||||||
|
s2, changed = sanitize_settings(s)
|
||||||
|
self.assertTrue(changed)
|
||||||
|
self.assertGreater(len(s2.sources), 0)
|
||||||
|
|
||||||
def test_sanitize_clamps_extremes(self) -> None:
|
def test_sanitize_clamps_extremes(self) -> None:
|
||||||
s = Settings(
|
s = Settings(
|
||||||
chain_length=0,
|
chain_length=0,
|
||||||
|
|||||||
Reference in New Issue
Block a user