Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 105 additions & 0 deletions scripts/update-thirdparty.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
#!/usr/bin/env python3
from __future__ import annotations

import fnmatch
import hashlib
import io
import json
import urllib.request
from urllib.parse import urlparse
from pathlib import Path
from zipfile import ZipFile

REPO_ROOT = Path(__file__).resolve().parents[1]
THIRDPARTY_DIR = REPO_ROOT / "thirdparty"
Expand Down Expand Up @@ -63,6 +66,11 @@
"zapret-ip-user.txt",
]

MODULE_FILES = [
"dnscrypt-proxy/dnscrypt-proxy.exe",
"tg-ws-proxy-rs/tg-ws-proxy.exe",
]

UPSTREAMS = [
(
"https://raw.githubusercontent.com/StressOzz/Zapret-Manager/refs/heads/main/zapret-hosts-user-exclude.txt",
Expand Down Expand Up @@ -90,6 +98,21 @@
),
]

GITHUB_RELEASE_ZIP_UPSTREAMS = [
{
"repo": "DNSCrypt/dnscrypt-proxy",
"asset_pattern": "dnscrypt-proxy-win64-*.zip",
"member_pattern": "*/dnscrypt-proxy.exe",
"destination": THIRDPARTY_DIR / "modules" / "dnscrypt-proxy" / "dnscrypt-proxy.exe",
},
{
"repo": "valnesfjord/tg-ws-proxy-rs",
"asset_pattern": "tg-ws-proxy-x86_64-pc-windows-gnu.zip",
"member_pattern": "tg-ws-proxy.exe",
"destination": THIRDPARTY_DIR / "modules" / "tg-ws-proxy-rs" / "tg-ws-proxy.exe",
},
]


def sha256_bytes(data: bytes) -> str:
return hashlib.sha256(data).hexdigest()
Expand All @@ -116,6 +139,73 @@ def download(url: str) -> bytes:
return response.read()


def download_json(url: str) -> dict:
return json.loads(download(url).decode("utf-8"))


def fetch_latest_release_asset(repo: str, asset_pattern: str) -> bytes:
release = download_json(f"https://api.github.com/repos/{repo}/releases/latest")
assets = release.get("assets", [])
matches = [
asset for asset in assets
if fnmatch.fnmatch(asset.get("name", ""), asset_pattern)
]

if not matches:
available_assets = ", ".join(
sorted(asset.get("name", "<unnamed>") for asset in assets)
)
raise FileNotFoundError(
f"Latest release asset for {repo} matching {asset_pattern!r} not found. "
f"Available assets: {available_assets}"
)

if len(matches) > 1:
ambiguous_assets = ", ".join(
sorted(
f"{asset.get('name', '<unnamed>')} ({asset.get('browser_download_url', '<no-url>')})"
for asset in matches
)
)
raise ValueError(
f"Latest release asset lookup for {repo} with pattern {asset_pattern!r} "
f"is ambiguous: {ambiguous_assets}"
)

asset = matches[0]
asset_name = asset.get("name", "")
asset_url = asset.get("browser_download_url")
if not asset_url:
raise ValueError(
f"Latest release asset {asset_name} for {repo} has no browser_download_url"
)
return download(asset_url)


def extract_zip_member(data: bytes, member_pattern: str) -> bytes:
with ZipFile(io.BytesIO(data)) as archive:
matches = [
member_name
for member_name in archive.namelist()
if fnmatch.fnmatch(member_name.replace("\\", "/"), member_pattern)
]

if not matches:
raise FileNotFoundError(f"Zip member matching {member_pattern!r} not found")

if len(matches) > 1:
normalized_matches = ", ".join(
sorted(member_name.replace("\\", "/") for member_name in matches)
)
raise ValueError(
f"Zip member lookup with pattern {member_pattern!r} is ambiguous: "
f"{normalized_matches}"
)

with archive.open(matches[0]) as handle:
return handle.read()


def normalize_download(destination: Path, data: bytes) -> bytes:
if destination.name != "zapret-hosts-user-exclude.txt":
return data
Expand Down Expand Up @@ -169,6 +259,12 @@ def build_hash_manifest() -> dict[str, str]:
raise FileNotFoundError(f"Missing managed list: {path}")
manifest[f"lists:{name}"] = sha256_file(path)

for name in MODULE_FILES:
path = THIRDPARTY_DIR / "modules" / name
if not path.is_file():
raise FileNotFoundError(f"Missing managed module binary: {path}")
manifest[f"modules:{name}"] = sha256_file(path)

return manifest


Expand All @@ -180,6 +276,15 @@ def main() -> None:
if write_if_changed(destination, data):
changed_paths.append(destination.relative_to(REPO_ROOT).as_posix())

for upstream in GITHUB_RELEASE_ZIP_UPSTREAMS:
archive_bytes = fetch_latest_release_asset(
upstream["repo"],
upstream["asset_pattern"],
)
extracted_bytes = extract_zip_member(archive_bytes, upstream["member_pattern"])
if write_if_changed(upstream["destination"], extracted_bytes):
changed_paths.append(upstream["destination"].relative_to(REPO_ROOT).as_posix())

manifest = build_hash_manifest()
hashes_payload = json.dumps(manifest, ensure_ascii=False, indent=2, sort_keys=True) + "\n"
if write_if_changed(HASHES_PATH, hashes_payload.encode("utf-8")):
Expand Down
9 changes: 9 additions & 0 deletions scripts/verify-thirdparty-hashes.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ def resolve_manifest_path(key: str) -> Path:
return THIRDPARTY_DIR / "fake" / name
if group == "lists":
return THIRDPARTY_DIR / "lists" / name
if group == "modules":
return THIRDPARTY_DIR / "modules" / name
raise ValueError(f"Unsupported manifest group: {group}")


Expand All @@ -54,6 +56,7 @@ def build_expected_keys() -> set[str]:
binary_files = getattr(update_module, "BINARY_FILES", None)
fake_files = getattr(update_module, "FAKE_FILES", None)
list_files = getattr(update_module, "LIST_FILES", None)
module_files = getattr(update_module, "MODULE_FILES", None)

if binary_files is None:
missing_constants.append("BINARY_FILES")
Expand All @@ -64,6 +67,9 @@ def build_expected_keys() -> set[str]:
if list_files is None:
missing_constants.append("LIST_FILES")
list_files = []
if module_files is None:
missing_constants.append("MODULE_FILES")
module_files = []

if missing_constants:
raise RuntimeError(
Expand All @@ -80,6 +86,9 @@ def build_expected_keys() -> set[str]:
for name in list_files:
expected_keys.add(f"lists:{name}")

for name in module_files:
expected_keys.add(f"modules:{name}")

return expected_keys


Expand Down
37 changes: 31 additions & 6 deletions src-tauri/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ surge-ping = "0.8.4"
toml = "1.1.2"
duct = "1.1.1"
sysinfo = "0.38.4"
discord-rich-presence = "1.1.0"
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify discord-rich-presence is available on crates.io
curl -s "https://crates.io/api/v1/crates/discord-rich-presence" | jq -r '.crate.max_version'

Repository: Noktomezo/ZapretInteractive

Length of output: 76


Use cargo add discord-rich-presence instead of manually editing Cargo.toml.

Dependencies should be added using the cargo add command rather than manual editing. Add the dependency via cargo add discord-rich-presence and then run cargo check to verify.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src-tauri/Cargo.toml` at line 52, The dependency line "discord-rich-presence
= \"1.1.0\"" was added manually to Cargo.toml; instead remove that manual edit
and add the crate using the Cargo tooling by running `cargo add
discord-rich-presence` in the project root (this will update Cargo.toml and
Cargo.lock correctly), then run `cargo check` to verify the workspace builds and
dependency resolution; confirm the manual line referencing discord-rich-presence
in Cargo.toml is gone or matches the cargo-added entry.


[target.'cfg(windows)'.dependencies]
windows = { version = "0.62.2", features = ["Win32_Foundation", "Win32_Security", "Win32_Security_Authentication_Identity", "Win32_System_Threading", "Win32_System_Services", "Win32_System_Registry", "Win32_System_Diagnostics_ToolHelp", "Win32_NetworkManagement_IpHelper", "Win32_NetworkManagement_Ndis", "Win32_Networking_WinSock"] }
Expand Down
4 changes: 3 additions & 1 deletion src-tauri/default-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
"tgWsProxyPort": 1443,
"tgWsProxySecret": "",
"tgWsProxyModuleEnabled": false,
"discordPresenceEnabled": false,
"discordPresenceActivityType": "playing",
"categories": [
{
"id": "preset-http",
Expand Down Expand Up @@ -489,5 +491,5 @@
"connectOnAutostart": false,
"coreFileUpdatePromptsEnabled": true,
"appAutoUpdatesEnabled": true,
"windowMaterial": "acrylic"
"windowMaterial": "none"
}
Loading
Loading