├── out └── .gitkeep ├── .vscode └── settings.json ├── prettier.config.mjs ├── compose.yaml ├── src ├── event.rs ├── utils.rs ├── fs.rs ├── parsers.rs ├── checker.rs ├── proxy.rs ├── raw_config.rs ├── http.rs ├── ipdb.rs ├── scraper.rs ├── config.rs ├── output.rs ├── main.rs └── tui.rs ├── rustfmt.toml ├── .pre-commit-config.yaml ├── .github ├── renovate.json └── workflows │ └── ci.yml ├── Dockerfile ├── LICENSE ├── termux.sh ├── Cargo.toml ├── README.md ├── .gitignore ├── config.toml └── Cargo.lock /out/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { "rust-analyzer.cargo.features": ["tui"] } 2 | -------------------------------------------------------------------------------- /prettier.config.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * @type {import('prettier').Config} 3 | */ 4 | export default { objectWrap: "collapse" }; 5 | -------------------------------------------------------------------------------- /compose.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | app: 3 | build: 4 | args: 5 | - "UID=${UID:-1000}" 6 | - "GID=${GID:-1000}" 7 | pull: true 8 | init: true 9 | volumes: 10 | - proxy_scraper_checker_cache:/home/app/.cache/proxy_scraper_checker 11 | - ./out:/home/app/.local/share/proxy_scraper_checker 12 | - ./config.toml:/app/config.toml 13 | volumes: 14 | proxy_scraper_checker_cache: 15 | -------------------------------------------------------------------------------- /src/event.rs: -------------------------------------------------------------------------------- 1 | use crate::{ipdb, proxy::ProxyType}; 2 | 3 | pub enum AppEvent { 4 | IpDbTotal(ipdb::DbType, Option), 5 | IpDbDownloaded(ipdb::DbType, usize), 6 | 7 | SourcesTotal(ProxyType, usize), 8 | SourceScraped(ProxyType), 9 | 10 | TotalProxies(ProxyType, usize), 11 | ProxyChecked(ProxyType), 12 | ProxyWorking(ProxyType), 13 | 14 | Done, 15 | Quit, 16 | } 17 | 18 | pub enum Event { 19 | Tick, 20 | Crossterm(crossterm::event::Event), 21 | App(AppEvent), 22 | } 23 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | condense_wildcard_suffixes = true 2 | error_on_unformatted = true 3 | format_code_in_doc_comments = true 4 | format_macro_matchers = true 5 | format_strings = true 6 | group_imports = "StdExternalCrate" 7 | hex_literal_case = "Upper" 8 | imports_granularity = "Crate" 9 | max_width = 80 10 | newline_style = "Unix" 11 | normalize_comments = true 12 | normalize_doc_attributes = true 13 | reorder_impl_items = true 14 | style_edition = "2024" 15 | unstable_features = true 16 | use_small_heuristics = "Max" 17 | use_try_shorthand = true 18 | wrap_comments = true 19 | -------------------------------------------------------------------------------- /src/utils.rs: -------------------------------------------------------------------------------- 1 | use itertools::Itertools as _; 2 | 3 | pub async fn is_docker() -> bool { 4 | #[cfg(target_os = "linux")] 5 | { 6 | static CACHE: tokio::sync::OnceCell = 7 | tokio::sync::OnceCell::const_new(); 8 | 9 | *CACHE 10 | .get_or_init(async || { 11 | tokio::fs::try_exists("/.dockerenv").await.unwrap_or(false) 12 | }) 13 | .await 14 | } 15 | #[cfg(not(target_os = "linux"))] 16 | { 17 | false 18 | } 19 | } 20 | 21 | pub fn pretty_error(e: &crate::Error) -> String { 22 | e.chain().join(" \u{2192} ") 23 | } 24 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/pre-commit/pre-commit-hooks 3 | rev: v6.0.0 4 | hooks: 5 | - id: check-case-conflict 6 | - id: check-executables-have-shebangs 7 | - id: check-illegal-windows-names 8 | - id: check-merge-conflict 9 | - id: check-shebang-scripts-are-executable 10 | exclude_types: 11 | - rust 12 | - id: check-symlinks 13 | - id: check-toml 14 | - id: check-xml 15 | - id: destroyed-symlinks 16 | - id: end-of-file-fixer 17 | - id: fix-byte-order-marker 18 | - id: mixed-line-ending 19 | args: 20 | - --fix=lf 21 | - id: trailing-whitespace 22 | - repo: https://github.com/rbubley/mirrors-prettier 23 | rev: v3.7.4 24 | hooks: 25 | - id: prettier 26 | -------------------------------------------------------------------------------- /src/fs.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | 3 | use color_eyre::eyre::{OptionExt as _, WrapErr as _}; 4 | 5 | use crate::config::APP_DIRECTORY_NAME; 6 | 7 | pub async fn get_cache_path() -> crate::Result { 8 | static CACHE: tokio::sync::OnceCell = 9 | tokio::sync::OnceCell::const_new(); 10 | 11 | Ok(CACHE 12 | .get_or_try_init(async || -> crate::Result { 13 | let mut path = tokio::task::spawn_blocking(dirs::cache_dir) 14 | .await? 15 | .ok_or_eyre("failed to get user's cache directory")?; 16 | path.push(APP_DIRECTORY_NAME); 17 | tokio::fs::create_dir_all(&path).await.wrap_err_with(|| { 18 | format!("failed to create directory: {}", path.display()) 19 | })?; 20 | Ok(path) 21 | }) 22 | .await? 23 | .clone()) 24 | } 25 | -------------------------------------------------------------------------------- /.github/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "configMigration": true, 4 | "dependencyDashboard": false, 5 | "docker-compose": { "enabled": true }, 6 | "extends": ["config:recommended"], 7 | "lockFileMaintenance": { "enabled": true, "schedule": ["at any time"] }, 8 | "packageRules": [ 9 | { 10 | "matchCurrentVersion": "!/^0/", 11 | "matchUpdateTypes": ["minor"], 12 | "automerge": true 13 | }, 14 | { 15 | "matchUpdateTypes": ["lockFileMaintenance", "patch", "replacement"], 16 | "automerge": true 17 | }, 18 | { "matchManagers": ["github-actions", "pre-commit"], "automerge": true } 19 | ], 20 | "prConcurrentLimit": 0, 21 | "prHourlyLimit": 0, 22 | "pre-commit": { "enabled": true }, 23 | "rollbackPrs": true, 24 | "semanticCommits": "enabled", 25 | "schedule": ["at any time"] 26 | } 27 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # syntax=docker.io/docker/dockerfile:1 2 | 3 | FROM docker.io/rust:1-slim-trixie AS builder 4 | 5 | WORKDIR /app 6 | 7 | RUN --mount=source=src,target=src \ 8 | --mount=source=Cargo.toml,target=Cargo.toml \ 9 | --mount=source=Cargo.lock,target=Cargo.lock \ 10 | --mount=type=cache,target=/app/target,sharing=locked \ 11 | --mount=type=cache,target=/usr/local/cargo/registry,sharing=locked \ 12 | cargo build --release --locked \ 13 | && cp target/release/proxy-scraper-checker . 14 | 15 | 16 | FROM docker.io/debian:trixie-slim AS final 17 | 18 | WORKDIR /app 19 | 20 | ARG \ 21 | UID=1000 \ 22 | GID=1000 23 | 24 | RUN (getent group "${GID}" || groupadd --gid "${GID}" app) \ 25 | && useradd --gid "${GID}" --no-log-init --create-home --uid "${UID}" app \ 26 | && mkdir -p /home/app/.cache/proxy_scraper_checker \ 27 | && chown "${UID}:${GID}" /home/app/.cache/proxy_scraper_checker 28 | 29 | COPY --from=builder --chown=${UID}:${GID} --link /app/proxy-scraper-checker . 30 | 31 | USER app 32 | 33 | CMD ["/app/proxy-scraper-checker"] 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 monosans 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /termux.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -euo pipefail 4 | 5 | project_name="proxy-scraper-checker" 6 | install_path="${HOME}/${project_name}" 7 | download_path="${TMPDIR}/${project_name}.zip" 8 | 9 | case $(getprop ro.product.cpu.abi) in 10 | "arm64-v8a") 11 | target="aarch64-linux-android" 12 | ;; 13 | "armeabi-v7a") 14 | if grep -qi 'neon' /proc/cpuinfo; then 15 | target="thumbv7neon-linux-androideabi" 16 | else 17 | target="armv7-linux-androideabi" 18 | fi 19 | ;; 20 | "armeabi") 21 | target="arm-linux-androideabi" 22 | ;; 23 | "x86") 24 | target="i686-linux-android" 25 | ;; 26 | "x86_64") 27 | target="x86_64-linux-android" 28 | ;; 29 | *) 30 | echo "Unsupported CPU ABI: ${abi}" >&2 31 | exit 1 32 | ;; 33 | esac 34 | 35 | curl -fLo "${download_path}" "https://nightly.link/monosans/${project_name}/workflows/ci/main/${project_name}-binary-${target}.zip" 36 | mkdir -p "${install_path}" 37 | unzip -qod "${install_path}" "${download_path}" 38 | rm -f "${download_path}" 39 | printf "%s installed successfully.\nRun 'cd %s && ./%s'.\n" "${project_name}" "${install_path}" "${project_name}" 40 | -------------------------------------------------------------------------------- /src/parsers.rs: -------------------------------------------------------------------------------- 1 | use std::sync::LazyLock; 2 | 3 | pub static PROXY_REGEX: LazyLock = LazyLock::new(|| { 4 | let pattern = r"(?:^|[^0-9A-Za-z])(?:(?Phttps?|socks[45]):\/\/)?(?:(?P[0-9A-Za-z]{1,64}):(?P[0-9A-Za-z]{1,64})@)?(?P[A-Za-z][\-\.A-Za-z]{0,251}[A-Za-z]|[A-Za-z]|(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(?:\.(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])){3}):(?P[0-9]|[1-9][0-9]{1,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(?=[^0-9A-Za-z]|$)"; 5 | fancy_regex::RegexBuilder::new(pattern) 6 | .backtrack_limit(usize::MAX) 7 | .build() 8 | .unwrap() 9 | }); 10 | 11 | static IPV4_REGEX: LazyLock = LazyLock::new(|| { 12 | let pattern = r"^\s*(?P(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(?:\.(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])){3})(?::(?:[0-9]|[1-9][0-9]{1,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5]))?\s*$"; 13 | fancy_regex::Regex::new(pattern).unwrap() 14 | }); 15 | 16 | pub fn parse_ipv4(s: &str) -> Option { 17 | if let Ok(Some(captures)) = IPV4_REGEX.captures(s) { 18 | captures.name("host").map(|capture| capture.as_str().to_owned()) 19 | } else { 20 | None 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "proxy-scraper-checker" 3 | version = "0.1.0" 4 | edition = "2024" 5 | license = "MIT" 6 | publish = false 7 | 8 | [dependencies] 9 | async-trait = "=0.1.89" 10 | color-eyre = "=0.6.5" 11 | crossterm = { version = "=0.28.1", default-features = false, features = [ 12 | "event-stream", 13 | "windows", 14 | ], optional = true } 15 | dhat = { version = "=0.3.3", optional = true } 16 | dirs = "=6.0.0" 17 | fancy-regex = "=0.17.0" 18 | foldhash = "=0.2.0" 19 | futures = { version = "=0.3.31", default-features = false, features = [ 20 | "std", 21 | ], optional = true } 22 | hickory-resolver = "=0.25.2" 23 | http = "=1.4.0" 24 | httpdate = "=1.0.3" 25 | itertools = "=0.14" 26 | log = { version = "=0.4.29", features = [ 27 | "max_level_debug", 28 | "release_max_level_debug", 29 | "std", 30 | ] } 31 | maxminddb = { version = "=0.27.0", features = ["mmap"] } 32 | parking_lot = "=0.12.5" 33 | rand = { version = "=0.9.2", default-features = false, features = [ 34 | "std", 35 | "thread_rng", 36 | ] } 37 | ratatui = { version = "=0.29.0", default-features = false, features = [ 38 | "crossterm", 39 | ], optional = true } 40 | reqwest = { version = "=0.12.25", default-features = false, features = [ 41 | "brotli", 42 | "charset", 43 | "gzip", 44 | "http2", 45 | "rustls-tls", 46 | "socks", 47 | "system-proxy", 48 | ] } 49 | reqwest-middleware = "=0.4.2" 50 | rlimit = "=0.10.2" 51 | serde = "=1.0.228" 52 | serde_json = "=1.0.145" 53 | smallvec = { version = "=1.15.1", features = [ 54 | "const_generics", 55 | "union", 56 | ], optional = true } 57 | strum = { version = "=0.27.2", features = ["derive"], optional = true } 58 | tokio = { version = "=1.48.0", features = ["fs", "parking_lot", "signal"] } 59 | tokio-util = "=0.7.17" 60 | toml = { version = "=0.9.8", default-features = false, features = [ 61 | "parse", 62 | "serde", 63 | "std", 64 | ] } 65 | tracing = { version = "=0.1.43", default-features = false, features = [ 66 | "max_level_debug", 67 | "release_max_level_debug", 68 | "std", 69 | ] } 70 | tracing-subscriber = { version = "=0.3.22", features = ["parking_lot"] } 71 | tui-logger = { version = "=0.17.4", features = [ 72 | "tracing-support", 73 | ], optional = true } 74 | url = "=2.5.7" 75 | 76 | [target.'cfg(all(any(target_arch = "aarch64", target_arch = "x86_64"), any(target_os = "linux", target_os = "macos", target_os = "windows")))'.dependencies] 77 | mimalloc = { version = "=0.1.48", optional = true } 78 | 79 | [target.'cfg(all(any(target_arch = "aarch64", target_arch = "x86_64"), any(target_os = "linux", target_os = "macos")))'.dependencies] 80 | tikv-jemallocator = { version = "=0.6.1", optional = true } 81 | 82 | [features] 83 | dhat = ["dep:dhat"] 84 | jemalloc = ["dep:tikv-jemallocator"] 85 | mimalloc_v2 = ["dep:mimalloc"] 86 | mimalloc_v3 = ["dep:mimalloc", "mimalloc/v3"] 87 | tokio-multi-thread = [] 88 | tui = [ 89 | "dep:crossterm", 90 | "dep:futures", 91 | "dep:smallvec", 92 | "dep:strum", 93 | "dep:ratatui", 94 | "dep:tui-logger", 95 | ] 96 | 97 | [profile.release] 98 | strip = true 99 | lto = "fat" 100 | codegen-units = 1 101 | 102 | [profile.dhat] 103 | inherits = "release" 104 | debug = true 105 | strip = "none" 106 | 107 | [profile.profiling] 108 | inherits = "release" 109 | debug = true 110 | strip = "none" 111 | lto = "off" 112 | -------------------------------------------------------------------------------- /src/checker.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use color_eyre::eyre::OptionExt as _; 4 | 5 | #[cfg(feature = "tui")] 6 | use crate::event::{AppEvent, Event}; 7 | use crate::{config::Config, proxy::Proxy, utils::pretty_error}; 8 | 9 | pub async fn check_all( 10 | config: Arc, 11 | dns_resolver: Arc, 12 | proxies: Vec, 13 | token: tokio_util::sync::CancellationToken, 14 | #[cfg(feature = "tui")] tx: tokio::sync::mpsc::UnboundedSender, 15 | ) -> crate::Result> { 16 | if config.checking.check_url.is_none() { 17 | return Ok(proxies); 18 | } 19 | 20 | let workers_count = 21 | config.checking.max_concurrent_checks.min(proxies.len()); 22 | if workers_count == 0 { 23 | return Ok(Vec::new()); 24 | } 25 | 26 | #[cfg(not(feature = "tui"))] 27 | tracing::info!("Started checking {} proxies", proxies.len()); 28 | 29 | let queue = Arc::new(parking_lot::Mutex::new(proxies)); 30 | let checked_proxies = Arc::new(parking_lot::Mutex::new(Vec::new())); 31 | 32 | let mut join_set = tokio::task::JoinSet::<()>::new(); 33 | for _ in 0..workers_count { 34 | let queue = Arc::clone(&queue); 35 | let config = Arc::clone(&config); 36 | let dns_resolver = Arc::clone(&dns_resolver); 37 | let checked_proxies = Arc::clone(&checked_proxies); 38 | let token = token.clone(); 39 | #[cfg(feature = "tui")] 40 | let tx = tx.clone(); 41 | join_set.spawn(async move { 42 | tokio::select! { 43 | biased; 44 | res = async move { 45 | loop { 46 | let Some(mut proxy) = queue.lock().pop() else { 47 | break; 48 | }; 49 | let check_result = proxy.check(&config, Arc::clone(&dns_resolver)).await; 50 | #[cfg(feature = "tui")] 51 | drop(tx.send(Event::App(AppEvent::ProxyChecked(proxy.protocol)))); 52 | match check_result { 53 | Ok(()) => { 54 | #[cfg(feature = "tui")] 55 | drop(tx.send(Event::App(AppEvent::ProxyWorking(proxy.protocol)))); 56 | checked_proxies.lock().push(proxy); 57 | } 58 | Err(e) if tracing::event_enabled!(tracing::Level::DEBUG) => { 59 | tracing::debug!( 60 | "{}: {}", 61 | proxy.to_string(true), 62 | pretty_error(&e) 63 | ); 64 | } 65 | Err(_) => {} 66 | } 67 | } 68 | } => res, 69 | () = token.cancelled() => (), 70 | } 71 | }); 72 | } 73 | 74 | drop(config); 75 | drop(dns_resolver); 76 | drop(queue); 77 | drop(token); 78 | #[cfg(feature = "tui")] 79 | drop(tx); 80 | 81 | while let Some(res) = join_set.join_next().await { 82 | match res { 83 | Ok(()) => {} 84 | Err(e) if e.is_panic() => { 85 | tracing::error!( 86 | "Proxy checking task panicked: {}", 87 | pretty_error(&e.into()) 88 | ); 89 | } 90 | Err(e) => { 91 | return Err(e.into()); 92 | } 93 | } 94 | } 95 | 96 | drop(join_set); 97 | 98 | Ok(Arc::into_inner(checked_proxies) 99 | .ok_or_eyre("failed to unwrap Arc")? 100 | .into_inner()) 101 | } 102 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🚀 proxy-scraper-checker 2 | 3 | [![CI](https://github.com/monosans/proxy-scraper-checker/actions/workflows/ci.yml/badge.svg)](https://github.com/monosans/proxy-scraper-checker/actions/workflows/ci.yml) 4 | 5 | ![TUI Demo](https://github.com/user-attachments/assets/0ac37021-d11c-4f68-b80d-bafdbaeb00bb) 6 | 7 | **A lightning-fast, feature-rich proxy scraper and checker built in Rust.** 8 | 9 | Collect, test, and organize HTTP/SOCKS4/SOCKS5 proxies from multiple sources with detailed metadata and intelligent filtering. 10 | 11 | ## ✨ Key Features 12 | 13 | - **🔥 Blazing Performance** - Rust-powered async engine with configurable concurrency 14 | - **🌍 Rich Metadata** - ASN, country/city geolocation, and response time data via offline MaxMind databases 15 | - **🎯 Smart Parsing** - Advanced regex engine extracts proxies from any format (`protocol://user:pass@host:port`) 16 | - **🔐 Auth Support** - Handles username/password authentication seamlessly 17 | - **📊 Interactive TUI** - Real-time progress monitoring with beautiful terminal interface 18 | - **⚡ Flexible Output** - JSON (with metadata) and plain text formats 19 | - **🎛️ Configurable** - Extensive options for sources, timeouts, and checking 20 | - **📁 Local & Remote** - Supports both web URLs and local files as proxy sources 21 | - **🐳 Docker Ready** - Containerized deployment with volume mounting 22 | 23 | ## 🔗 Related 24 | 25 | Get pre-checked proxies from [monosans/proxy-list](https://github.com/monosans/proxy-list) - updated regularly using this tool. 26 | 27 | ## ⚠️ SAFETY WARNING ⚠️ 28 | 29 | This tool makes thousands of concurrent network requests to test proxies, which may trigger rate limiting or security flags from your ISP or network provider. Consider using a VPN to protect your IP reputation. 30 | 31 | ## 🚀 Quick Start 32 | 33 | > All configuration options are documented in `config.toml` with detailed comments explaining each setting. 34 | 35 |
36 | 💻 Binary Installation 37 | 38 | > **Note:** For Termux users, see the dedicated section below. 39 | 40 | 1. **Download** the archive for your platform from [nightly builds](https://nightly.link/monosans/proxy-scraper-checker/workflows/ci/main?preview) 41 | - Look for artifacts starting with `proxy-scraper-checker-binary-` followed by your platform 42 | - Not sure which platform? Check the [platform support table](https://doc.rust-lang.org/beta/rustc/platform-support.html) 43 | 44 | 2. **Extract** the archive to a dedicated folder 45 | 46 | 3. **Configure** by editing `config.toml` to your needs 47 | 48 | 4. **Run** the executable 49 | 50 |
51 | 52 |
53 | 🐳 Docker Installation 54 | 55 | > **Note:** Docker version outputs logs to stdout instead of the interactive TUI (terminal user interface). 56 | 57 | 1. **Install** [Docker Compose](https://docs.docker.com/compose/install/) 58 | 59 | 2. **Download** the archive from [nightly builds](https://nightly.link/monosans/proxy-scraper-checker/workflows/ci/main?preview) 60 | - Look for artifacts starting with `proxy-scraper-checker-docker-` followed by your CPU architecture 61 | 62 | 3. **Extract** to a folder and configure `config.toml` 63 | 64 | 4. **Build and run:** 65 | 66 | ```bash 67 | # Windows 68 | docker compose build 69 | docker compose up --no-log-prefix --remove-orphans 70 | 71 | # Linux/macOS 72 | docker compose build --build-arg UID=$(id -u) --build-arg GID=$(id -g) 73 | docker compose up --no-log-prefix --remove-orphans 74 | ``` 75 | 76 |
77 | 78 |
79 | 📱 Termux Installation 80 | 81 | > **Important:** Download Termux from [F-Droid](https://f-droid.org/en/packages/com.termux/), not Google Play ([why?](https://github.com/termux/termux-app#google-play-store-experimental-branch)). 82 | 83 | 1. **Install** with one command: 84 | 85 | ```bash 86 | bash <(curl -fsSL 'https://raw.githubusercontent.com/monosans/proxy-scraper-checker/main/termux.sh') 87 | ``` 88 | 89 | 2. **Configure** using a text editor: 90 | 91 | ```bash 92 | nano ~/proxy-scraper-checker/config.toml 93 | ``` 94 | 95 | 3. **Run the tool:** 96 | ```bash 97 | cd ~/proxy-scraper-checker && ./proxy-scraper-checker 98 | ``` 99 | 100 |
101 | 102 | ## 💎 Sponsors 103 | 104 | **[🐦 BirdProxies.com](https://www.birdproxies.com/@MONOSANS)** — Premium proxy service with exclusive benefits: Get **10% off** + **15% bonus data** using this link! 105 | 106 | ### 💖 Support This Project 107 | 108 | Help this project grow and reach more developers: 109 | 110 | - ⭐ **Star the repository** — Help others discover this tool 111 | - 💰 **Sponsor via crypto** — [DM me on Telegram](https://t.me/monosans) 112 | 113 | ## 📄 License 114 | 115 | [MIT](LICENSE) 116 | 117 | _This product includes GeoLite2 Data created by MaxMind, available from https://www.maxmind.com_ 118 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.toptal.com/developers/gitignore/api/jetbrains+all,linux,macos,rust,vim,visualstudiocode,windows 2 | # Edit at https://www.toptal.com/developers/gitignore?templates=jetbrains+all,linux,macos,rust,vim,visualstudiocode,windows 3 | 4 | ### JetBrains+all ### 5 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 6 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 7 | 8 | # User-specific stuff 9 | .idea/**/workspace.xml 10 | .idea/**/tasks.xml 11 | .idea/**/usage.statistics.xml 12 | .idea/**/dictionaries 13 | .idea/**/shelf 14 | 15 | # AWS User-specific 16 | .idea/**/aws.xml 17 | 18 | # Generated files 19 | .idea/**/contentModel.xml 20 | 21 | # Sensitive or high-churn files 22 | .idea/**/dataSources/ 23 | .idea/**/dataSources.ids 24 | .idea/**/dataSources.local.xml 25 | .idea/**/sqlDataSources.xml 26 | .idea/**/dynamic.xml 27 | .idea/**/uiDesigner.xml 28 | .idea/**/dbnavigator.xml 29 | 30 | # Gradle 31 | .idea/**/gradle.xml 32 | .idea/**/libraries 33 | 34 | # Gradle and Maven with auto-import 35 | # When using Gradle or Maven with auto-import, you should exclude module files, 36 | # since they will be recreated, and may cause churn. Uncomment if using 37 | # auto-import. 38 | # .idea/artifacts 39 | # .idea/compiler.xml 40 | # .idea/jarRepositories.xml 41 | # .idea/modules.xml 42 | # .idea/*.iml 43 | # .idea/modules 44 | # *.iml 45 | # *.ipr 46 | 47 | # CMake 48 | cmake-build-*/ 49 | 50 | # Mongo Explorer plugin 51 | .idea/**/mongoSettings.xml 52 | 53 | # File-based project format 54 | *.iws 55 | 56 | # IntelliJ 57 | out/ 58 | 59 | # mpeltonen/sbt-idea plugin 60 | .idea_modules/ 61 | 62 | # JIRA plugin 63 | atlassian-ide-plugin.xml 64 | 65 | # Cursive Clojure plugin 66 | .idea/replstate.xml 67 | 68 | # SonarLint plugin 69 | .idea/sonarlint/ 70 | 71 | # Crashlytics plugin (for Android Studio and IntelliJ) 72 | com_crashlytics_export_strings.xml 73 | crashlytics.properties 74 | crashlytics-build.properties 75 | fabric.properties 76 | 77 | # Editor-based Rest Client 78 | .idea/httpRequests 79 | 80 | # Android studio 3.1+ serialized cache file 81 | .idea/caches/build_file_checksums.ser 82 | 83 | ### JetBrains+all Patch ### 84 | # Ignore everything but code style settings and run configurations 85 | # that are supposed to be shared within teams. 86 | 87 | .idea/* 88 | 89 | !.idea/codeStyles 90 | !.idea/runConfigurations 91 | 92 | ### Linux ### 93 | *~ 94 | 95 | # temporary files which can be created if a process still has a handle open of a deleted file 96 | .fuse_hidden* 97 | 98 | # KDE directory preferences 99 | .directory 100 | 101 | # Linux trash folder which might appear on any partition or disk 102 | .Trash-* 103 | 104 | # .nfs files are created when an open file is removed but is still being accessed 105 | .nfs* 106 | 107 | ### macOS ### 108 | # General 109 | .DS_Store 110 | .AppleDouble 111 | .LSOverride 112 | 113 | # Icon must end with two \r 114 | Icon 115 | 116 | 117 | # Thumbnails 118 | ._* 119 | 120 | # Files that might appear in the root of a volume 121 | .DocumentRevisions-V100 122 | .fseventsd 123 | .Spotlight-V100 124 | .TemporaryItems 125 | .Trashes 126 | .VolumeIcon.icns 127 | .com.apple.timemachine.donotpresent 128 | 129 | # Directories potentially created on remote AFP share 130 | .AppleDB 131 | .AppleDesktop 132 | Network Trash Folder 133 | Temporary Items 134 | .apdisk 135 | 136 | ### macOS Patch ### 137 | # iCloud generated files 138 | *.icloud 139 | 140 | ### Rust ### 141 | # Generated by Cargo 142 | # will have compiled files and executables 143 | debug/ 144 | target/ 145 | 146 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 147 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 148 | Cargo.lock 149 | 150 | # These are backup files generated by rustfmt 151 | **/*.rs.bk 152 | 153 | # MSVC Windows builds of rustc generate these, which store debugging information 154 | *.pdb 155 | 156 | ### Vim ### 157 | # Swap 158 | [._]*.s[a-v][a-z] 159 | !*.svg # comment out if you don't need vector files 160 | [._]*.sw[a-p] 161 | [._]s[a-rt-v][a-z] 162 | [._]ss[a-gi-z] 163 | [._]sw[a-p] 164 | 165 | # Session 166 | Session.vim 167 | Sessionx.vim 168 | 169 | # Temporary 170 | .netrwhist 171 | # Auto-generated tag files 172 | tags 173 | # Persistent undo 174 | [._]*.un~ 175 | 176 | ### VisualStudioCode ### 177 | .vscode/* 178 | !.vscode/settings.json 179 | !.vscode/tasks.json 180 | !.vscode/launch.json 181 | !.vscode/extensions.json 182 | !.vscode/*.code-snippets 183 | 184 | # Local History for Visual Studio Code 185 | .history/ 186 | 187 | # Built Visual Studio Code Extensions 188 | *.vsix 189 | 190 | ### VisualStudioCode Patch ### 191 | # Ignore all local history of files 192 | .history 193 | .ionide 194 | 195 | ### Windows ### 196 | # Windows thumbnail cache files 197 | Thumbs.db 198 | Thumbs.db:encryptable 199 | ehthumbs.db 200 | ehthumbs_vista.db 201 | 202 | # Dump file 203 | *.stackdump 204 | 205 | # Folder config file 206 | [Dd]esktop.ini 207 | 208 | # Recycle Bin used on file shares 209 | $RECYCLE.BIN/ 210 | 211 | # Windows Installer files 212 | *.cab 213 | *.msi 214 | *.msix 215 | *.msm 216 | *.msp 217 | 218 | # Windows shortcuts 219 | *.lnk 220 | 221 | # End of https://www.toptal.com/developers/gitignore/api/jetbrains+all,linux,macos,rust,vim,visualstudiocode,windows 222 | 223 | !Cargo.lock 224 | -------------------------------------------------------------------------------- /src/proxy.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | fmt::Write as _, 3 | hash::{Hash, Hasher}, 4 | str::FromStr, 5 | sync::Arc, 6 | time::{Duration, Instant}, 7 | }; 8 | 9 | use color_eyre::eyre::eyre; 10 | 11 | use crate::{ 12 | config::{Config, HttpbinResponse}, 13 | parsers::parse_ipv4, 14 | }; 15 | 16 | #[derive( 17 | Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, serde::Serialize, 18 | )] 19 | #[cfg_attr(feature = "tui", derive(strum::EnumCount))] 20 | #[serde(rename_all = "lowercase")] 21 | pub enum ProxyType { 22 | Http, 23 | Socks4, 24 | Socks5, 25 | } 26 | 27 | impl FromStr for ProxyType { 28 | type Err = crate::Error; 29 | 30 | fn from_str(s: &str) -> Result { 31 | if s.eq_ignore_ascii_case("http") || s.eq_ignore_ascii_case("https") { 32 | Ok(Self::Http) 33 | } else if s.eq_ignore_ascii_case("socks4") { 34 | Ok(Self::Socks4) 35 | } else if s.eq_ignore_ascii_case("socks5") { 36 | Ok(Self::Socks5) 37 | } else { 38 | Err(eyre!("failed to convert {s} to ProxyType")) 39 | } 40 | } 41 | } 42 | 43 | impl ProxyType { 44 | pub const fn as_str(self) -> &'static str { 45 | match self { 46 | Self::Http => "http", 47 | Self::Socks4 => "socks4", 48 | Self::Socks5 => "socks5", 49 | } 50 | } 51 | } 52 | 53 | #[derive(Eq)] 54 | pub struct Proxy { 55 | pub protocol: ProxyType, 56 | pub host: String, 57 | pub port: u16, 58 | pub username: Option, 59 | pub password: Option, 60 | pub timeout: Option, 61 | pub exit_ip: Option, 62 | } 63 | 64 | impl TryFrom<&mut Proxy> for reqwest::Proxy { 65 | type Error = crate::Error; 66 | 67 | #[inline] 68 | fn try_from(value: &mut Proxy) -> Result { 69 | let proxy = Self::all(format!( 70 | "{}://{}:{}", 71 | value.protocol.as_str(), 72 | value.host, 73 | value.port 74 | ))?; 75 | 76 | if let (Some(username), Some(password)) = 77 | (value.username.as_ref(), value.password.as_ref()) 78 | { 79 | Ok(proxy.basic_auth(username, password)) 80 | } else { 81 | Ok(proxy) 82 | } 83 | } 84 | } 85 | 86 | impl Proxy { 87 | pub const fn is_checked(&self) -> bool { 88 | self.timeout.is_some() 89 | } 90 | 91 | pub async fn check( 92 | &mut self, 93 | config: &Config, 94 | dns_resolver: Arc, 95 | ) -> crate::Result<()> { 96 | if let Some(check_url) = config.checking.check_url.clone() { 97 | let builder = reqwest::ClientBuilder::new() 98 | .user_agent(&config.checking.user_agent) 99 | .proxy(self.try_into()?) 100 | .timeout(config.checking.timeout) 101 | .connect_timeout(config.checking.connect_timeout) 102 | .pool_idle_timeout(Duration::ZERO) 103 | .pool_max_idle_per_host(0) 104 | .http1_only() 105 | .tcp_keepalive(None) 106 | .tcp_keepalive_interval(Duration::ZERO) 107 | .tcp_keepalive_retries(0) 108 | .dns_resolver(dns_resolver); 109 | #[cfg(any( 110 | target_os = "android", 111 | target_os = "fuchsia", 112 | target_os = "linux" 113 | ))] 114 | let builder = builder.tcp_user_timeout(None); 115 | let request = { 116 | let client = builder.build()?; 117 | client.get(check_url) 118 | }; 119 | let start = Instant::now(); 120 | let response = request.send().await?.error_for_status()?; 121 | self.timeout = Some(start.elapsed()); 122 | self.exit_ip = response.text().await.map_or(None, |text| { 123 | if let Ok(httpbin) = 124 | serde_json::from_str::(&text) 125 | { 126 | parse_ipv4(&httpbin.origin) 127 | } else { 128 | parse_ipv4(&text) 129 | } 130 | }); 131 | } 132 | Ok(()) 133 | } 134 | 135 | pub fn to_string(&self, include_protocol: bool) -> String { 136 | let mut s = String::new(); 137 | 138 | if include_protocol { 139 | s.push_str(self.protocol.as_str()); 140 | s.push_str("://"); 141 | } 142 | 143 | if let (Some(username), Some(password)) = 144 | (&self.username, &self.password) 145 | { 146 | s.push_str(username); 147 | s.push(':'); 148 | s.push_str(password); 149 | s.push('@'); 150 | } 151 | 152 | s.push_str(&self.host); 153 | s.push(':'); 154 | write!(s, "{}", self.port).unwrap(); 155 | 156 | s 157 | } 158 | } 159 | 160 | #[expect(clippy::missing_trait_methods)] 161 | impl PartialEq for Proxy { 162 | fn eq(&self, other: &Self) -> bool { 163 | self.protocol == other.protocol 164 | && self.host == other.host 165 | && self.port == other.port 166 | && self.username == other.username 167 | && self.password == other.password 168 | } 169 | } 170 | 171 | #[expect(clippy::missing_trait_methods)] 172 | impl Hash for Proxy { 173 | fn hash(&self, state: &mut H) { 174 | self.protocol.hash(state); 175 | self.host.hash(state); 176 | self.port.hash(state); 177 | self.username.hash(state); 178 | self.password.hash(state); 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /src/raw_config.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | env, 3 | num::NonZero, 4 | path::{Path, PathBuf}, 5 | }; 6 | 7 | use color_eyre::eyre::WrapErr as _; 8 | use itertools::Itertools as _; 9 | use serde::Deserialize as _; 10 | 11 | use crate::{HashMap, http::BasicAuth}; 12 | 13 | fn validate_positive_f64<'de, D: serde::Deserializer<'de>>( 14 | deserializer: D, 15 | ) -> Result { 16 | let val = f64::deserialize(deserializer)?; 17 | if val > 0.0 { 18 | Ok(val) 19 | } else { 20 | Err(serde::de::Error::custom("value must be positive")) 21 | } 22 | } 23 | 24 | fn validate_url_generic<'de, D>( 25 | deserializer: D, 26 | allowed_schemes: &[&str], 27 | ) -> Result, D::Error> 28 | where 29 | D: serde::Deserializer<'de>, 30 | { 31 | let s = String::deserialize(deserializer)?; 32 | if s.trim().is_empty() { 33 | return Ok(None); 34 | } 35 | if let Ok(u) = url::Url::parse(&s) 36 | && allowed_schemes.contains(&u.scheme()) 37 | && u.host_str().is_some() 38 | { 39 | Ok(Some(u)) 40 | } else { 41 | let type_label = match allowed_schemes { 42 | [] => String::new(), 43 | [single] => format!("'{single}'"), 44 | [rest @ .., last] => { 45 | format!( 46 | "{} or '{last}'", 47 | rest.iter().map(|s| format!("'{s}'")).join(", ") 48 | ) 49 | } 50 | }; 51 | Err(serde::de::Error::custom(format!( 52 | "'{s}' is not a valid {type_label} url" 53 | ))) 54 | } 55 | } 56 | 57 | fn validate_proxy_url<'de, D: serde::Deserializer<'de>>( 58 | deserializer: D, 59 | ) -> Result, D::Error> { 60 | validate_url_generic(deserializer, &["http", "https", "socks4", "socks5"]) 61 | } 62 | 63 | fn validate_http_url<'de, D: serde::Deserializer<'de>>( 64 | deserializer: D, 65 | ) -> Result, D::Error> { 66 | validate_url_generic(deserializer, &["http", "https"]) 67 | } 68 | 69 | #[derive(serde::Deserialize)] 70 | pub struct DetailedSourceConfig { 71 | pub url: String, 72 | #[serde(default)] 73 | pub basic_auth: Option, 74 | #[serde(default)] 75 | pub headers: Option>, 76 | } 77 | 78 | #[derive(serde::Deserialize)] 79 | #[serde(untagged)] 80 | pub enum SourceConfig { 81 | Simple(String), 82 | Detailed(Box), 83 | } 84 | 85 | #[derive(serde::Deserialize)] 86 | pub struct ScrapingProtocolConfig { 87 | pub enabled: bool, 88 | pub urls: Vec, 89 | } 90 | 91 | #[derive(serde::Deserialize)] 92 | pub struct ScrapingConfig { 93 | pub max_proxies_per_source: usize, 94 | #[serde(deserialize_with = "validate_positive_f64")] 95 | pub timeout: f64, 96 | #[serde(deserialize_with = "validate_positive_f64")] 97 | pub connect_timeout: f64, 98 | #[serde(deserialize_with = "validate_proxy_url")] 99 | pub proxy: Option, 100 | pub user_agent: String, 101 | 102 | pub http: ScrapingProtocolConfig, 103 | pub socks4: ScrapingProtocolConfig, 104 | pub socks5: ScrapingProtocolConfig, 105 | } 106 | 107 | #[derive(serde::Deserialize)] 108 | pub struct CheckingConfig { 109 | #[serde(deserialize_with = "validate_http_url")] 110 | pub check_url: Option, 111 | pub max_concurrent_checks: NonZero, 112 | #[serde(deserialize_with = "validate_positive_f64")] 113 | pub timeout: f64, 114 | #[serde(deserialize_with = "validate_positive_f64")] 115 | pub connect_timeout: f64, 116 | pub user_agent: String, 117 | } 118 | 119 | #[derive(serde::Deserialize)] 120 | pub struct TxtOutputConfig { 121 | pub enabled: bool, 122 | } 123 | 124 | #[derive(serde::Deserialize)] 125 | pub struct JsonOutputConfig { 126 | pub enabled: bool, 127 | pub include_asn: bool, 128 | pub include_geolocation: bool, 129 | } 130 | 131 | pub struct OutputConfig { 132 | pub path: PathBuf, 133 | pub sort_by_speed: bool, 134 | pub txt: TxtOutputConfig, 135 | pub json: JsonOutputConfig, 136 | } 137 | 138 | #[derive(serde::Deserialize)] 139 | pub struct RawConfig { 140 | pub debug: bool, 141 | pub scraping: ScrapingConfig, 142 | pub checking: CheckingConfig, 143 | pub output: OutputConfig, 144 | } 145 | 146 | #[expect(clippy::missing_trait_methods)] 147 | impl<'de> serde::Deserialize<'de> for OutputConfig { 148 | fn deserialize>( 149 | deserializer: D, 150 | ) -> Result { 151 | #[derive(serde::Deserialize)] 152 | struct InnerOutputConfig { 153 | pub path: PathBuf, 154 | pub sort_by_speed: bool, 155 | pub txt: TxtOutputConfig, 156 | pub json: JsonOutputConfig, 157 | } 158 | 159 | let inner = InnerOutputConfig::deserialize(deserializer)?; 160 | if !inner.json.enabled && !inner.txt.enabled { 161 | return Err(serde::de::Error::custom( 162 | "at least one of 'output.json' or 'output.txt' must be \ 163 | enabled in config", 164 | )); 165 | } 166 | 167 | Ok(Self { 168 | path: inner.path, 169 | sort_by_speed: inner.sort_by_speed, 170 | txt: inner.txt, 171 | json: inner.json, 172 | }) 173 | } 174 | } 175 | 176 | const CONFIG_ENV: &str = "PROXY_SCRAPER_CHECKER_CONFIG"; 177 | 178 | pub fn get_config_path() -> String { 179 | env::var(CONFIG_ENV).unwrap_or_else(|_| "config.toml".to_owned()) 180 | } 181 | 182 | pub async fn read_config(path: &Path) -> crate::Result { 183 | let raw_config = 184 | tokio::fs::read_to_string(path).await.wrap_err_with(move || { 185 | format!("failed to read file to string: {}", path.display()) 186 | })?; 187 | toml::from_str(&raw_config).wrap_err_with(move || { 188 | format!("failed to parse TOML config file: {}", path.display()) 189 | }) 190 | } 191 | -------------------------------------------------------------------------------- /src/http.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | io, 3 | net::SocketAddr, 4 | sync::Arc, 5 | time::{Duration, SystemTime}, 6 | }; 7 | 8 | use crate::config::Config; 9 | 10 | const DEFAULT_MAX_RETRIES: u32 = 2; 11 | const INITIAL_RETRY_DELAY: Duration = Duration::from_millis(500); 12 | const MAX_RETRY_DELAY: Duration = Duration::from_secs(8); 13 | 14 | static RETRY_STATUSES: &[reqwest::StatusCode] = &[ 15 | reqwest::StatusCode::REQUEST_TIMEOUT, 16 | reqwest::StatusCode::TOO_MANY_REQUESTS, 17 | reqwest::StatusCode::INTERNAL_SERVER_ERROR, 18 | reqwest::StatusCode::BAD_GATEWAY, 19 | reqwest::StatusCode::SERVICE_UNAVAILABLE, 20 | reqwest::StatusCode::GATEWAY_TIMEOUT, 21 | ]; 22 | 23 | #[derive(Clone, serde::Deserialize)] 24 | pub struct BasicAuth { 25 | pub username: String, 26 | pub password: Option, 27 | } 28 | 29 | pub struct HickoryDnsResolver(Arc); 30 | 31 | impl HickoryDnsResolver { 32 | pub async fn new() -> Result { 33 | let mut builder = tokio::task::spawn_blocking( 34 | hickory_resolver::TokioResolver::builder_tokio, 35 | ) 36 | .await? 37 | .unwrap_or_else(|_| { 38 | hickory_resolver::TokioResolver::builder_with_config( 39 | hickory_resolver::config::ResolverConfig::cloudflare(), 40 | hickory_resolver::name_server::TokioConnectionProvider::default( 41 | ), 42 | ) 43 | }); 44 | 45 | builder.options_mut().ip_strategy = 46 | hickory_resolver::config::LookupIpStrategy::Ipv4AndIpv6; 47 | Ok(Self(Arc::new(builder.build()))) 48 | } 49 | } 50 | 51 | impl reqwest::dns::Resolve for HickoryDnsResolver { 52 | fn resolve(&self, name: reqwest::dns::Name) -> reqwest::dns::Resolving { 53 | let resolver = Arc::clone(&self.0); 54 | Box::pin(async move { 55 | let lookup = resolver.lookup_ip(name.as_str()).await; 56 | drop(name); 57 | drop(resolver); 58 | let addrs: reqwest::dns::Addrs = Box::new( 59 | lookup?.into_iter().map(|ip_addr| SocketAddr::new(ip_addr, 0)), 60 | ); 61 | Ok(addrs) 62 | }) 63 | } 64 | } 65 | 66 | fn parse_retry_after(headers: &reqwest::header::HeaderMap) -> Option { 67 | if let Some(val) = headers.get("retry-after-ms") 68 | && let Ok(s) = val.to_str() 69 | && let Ok(ms) = s.parse() 70 | { 71 | return Some(Duration::from_millis(ms)); 72 | } 73 | 74 | if let Some(val) = headers.get(reqwest::header::RETRY_AFTER) 75 | && let Ok(s) = val.to_str() 76 | { 77 | if let Ok(sec) = s.parse() { 78 | return Some(Duration::from_secs(sec)); 79 | } 80 | 81 | if let Ok(parsed) = httpdate::parse_http_date(s) 82 | && let Ok(dur) = parsed.duration_since(SystemTime::now()) 83 | { 84 | return Some(dur); 85 | } 86 | } 87 | None 88 | } 89 | 90 | fn calculate_retry_timeout( 91 | headers: Option<&reqwest::header::HeaderMap>, 92 | attempt: u32, 93 | ) -> Option { 94 | if let Some(h) = headers 95 | && let Some(after) = parse_retry_after(h) 96 | { 97 | if after > Duration::from_secs(60) { 98 | return None; 99 | } 100 | return Some(after); 101 | } 102 | 103 | let base = INITIAL_RETRY_DELAY 104 | .saturating_mul(2_u32.pow(attempt)) 105 | .min(MAX_RETRY_DELAY); 106 | let jitter = 0.25_f64.mul_add(-rand::random::(), 1.0); 107 | Some(base.mul_f64(jitter)) 108 | } 109 | 110 | pub struct RetryMiddleware; 111 | 112 | #[async_trait::async_trait] 113 | impl reqwest_middleware::Middleware for RetryMiddleware { 114 | async fn handle( 115 | &self, 116 | req: reqwest::Request, 117 | extensions: &mut http::Extensions, 118 | next: reqwest_middleware::Next<'_>, 119 | ) -> reqwest_middleware::Result { 120 | let mut attempt: u32 = 0; 121 | loop { 122 | let req = req.try_clone().ok_or_else(|| { 123 | reqwest_middleware::Error::middleware(io::Error::other( 124 | "Request object is not cloneable", 125 | )) 126 | })?; 127 | 128 | match next.clone().run(req, extensions).await { 129 | Ok(resp) => { 130 | let status = resp.status(); 131 | if status.is_client_error() || status.is_server_error() { 132 | if attempt < DEFAULT_MAX_RETRIES 133 | && RETRY_STATUSES.contains(&status) 134 | && let Some(delay) = calculate_retry_timeout( 135 | Some(resp.headers()), 136 | attempt, 137 | ) 138 | { 139 | tokio::time::sleep(delay).await; 140 | attempt = attempt.saturating_add(1); 141 | continue; 142 | } 143 | resp.error_for_status_ref()?; 144 | } 145 | return Ok(resp); 146 | } 147 | Err(err) => { 148 | if attempt < DEFAULT_MAX_RETRIES 149 | && err.is_connect() 150 | && let Some(delay) = 151 | calculate_retry_timeout(None, attempt) 152 | { 153 | tokio::time::sleep(delay).await; 154 | attempt = attempt.saturating_add(1); 155 | continue; 156 | } 157 | return Err(err); 158 | } 159 | } 160 | } 161 | } 162 | } 163 | 164 | pub fn create_reqwest_client( 165 | config: &Config, 166 | dns_resolver: Arc, 167 | ) -> reqwest::Result { 168 | let mut builder = reqwest::ClientBuilder::new() 169 | .user_agent(&config.scraping.user_agent) 170 | .timeout(config.scraping.timeout) 171 | .connect_timeout(config.scraping.connect_timeout) 172 | .dns_resolver(dns_resolver); 173 | 174 | if let Some(proxy) = config.scraping.proxy.clone() { 175 | builder = builder.proxy(reqwest::Proxy::all(proxy)?); 176 | } 177 | 178 | let client = builder.build()?; 179 | let client_with_middleware = reqwest_middleware::ClientBuilder::new(client) 180 | .with(RetryMiddleware) 181 | .build(); 182 | 183 | Ok(client_with_middleware) 184 | } 185 | -------------------------------------------------------------------------------- /config.toml: -------------------------------------------------------------------------------- 1 | # Enable debug logging to see detailed checking process for each proxy 2 | # Warning: Extremely verbose - logs every connection attempt and failure 3 | debug = false 4 | 5 | 6 | [scraping] 7 | # Maximum proxies to collect per source (0 = unlimited) 8 | # When this limit is reached, the source is skipped with a warning to prevent memory issues 9 | max_proxies_per_source = 100000 10 | 11 | # Request timeout for fetching proxy sources (seconds) 12 | # Higher values allow slower sources to complete but increase total scraping time 13 | timeout = 60.0 14 | connect_timeout = 5.0 15 | 16 | # HTTP(S), SOCKS4, or SOCKS5 proxy used for fetching sources (e.g., "socks5://user:pass@host:port"). Leave empty to disable. 17 | proxy = "" 18 | 19 | # User-Agent header for scraping requests 20 | user_agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36" 21 | 22 | 23 | [checking] 24 | # URL for checking proxy functionality 25 | # httpbin-compatible: Returns JSON with IP info for ASN/geo data 26 | # plain-text: Returns just IP address for basic connectivity 27 | # Examples: 28 | # "https://httpbin.org/ip" - JSON with "origin" key. Full-featured checking. 29 | # "https://api.ipify.org" - Simple IP return. Full-featured checking. 30 | # "https://google.com" - Basic connect/read check only 31 | # "" - Skip checking entirely (scrape only) 32 | check_url = "https://api.ipify.org" 33 | 34 | # Number of proxies to check simultaneously 35 | # Higher values = faster checking but more RAM/network usage. Lower if you experience crashes or timeouts. 36 | max_concurrent_checks = 1024 37 | 38 | # Proxy response timeout (seconds) 39 | # Higher = finds more slow proxies but takes longer 40 | # Lower = faster checking but discards slow proxies 41 | timeout = 60.0 42 | connect_timeout = 5.0 43 | 44 | # User-Agent header for proxy check requests 45 | user_agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36" 46 | 47 | 48 | [output] 49 | # Output directory (Docker overrides this to /home/app/.local/share/proxy_scraper_checker via volume mount) 50 | path = "./out" 51 | 52 | # Sort proxies by response time (true) or by IP address (false) 53 | # Speed sorting: fastest proxies first. IP sorting: natural order by protocol, then IP, then port. 54 | sort_by_speed = true 55 | 56 | 57 | # Plain text output (.txt files) 58 | [output.txt] 59 | enabled = true 60 | 61 | 62 | # JSON output with metadata (.json files) 63 | [output.json] 64 | enabled = true 65 | 66 | # Add ASN (network provider) info to JSON output 67 | # Uses offline MaxMind database 68 | include_asn = true 69 | 70 | # Add geolocation (country/city) data to JSON output 71 | # Uses offline MaxMind GeoLite2 City database 72 | include_geolocation = true 73 | 74 | 75 | # Proxy sources configuration 76 | # Add local files: ["./my_proxies.txt"] or URLs 77 | # Sources are fetched in parallel for speed 78 | 79 | [scraping.http] 80 | enabled = true 81 | urls = [ 82 | # Local file examples: 83 | # "./my_http_proxies.txt", 84 | # "/home/user/my_http_proxies.txt", 85 | # "C:/Users/user/Desktop/my_http_proxies.txt", 86 | # "file:///home/user/my_http_proxies.txt", 87 | 88 | # Advanced URL configuration examples (with basic auth or custom headers): 89 | # HTTP Basic Auth example: 90 | # { url = "https://some.api/endpoint", basic_auth = { username = "user", password = "password123" } }, 91 | # Custom headers example: 92 | # { url = "https://some.api/endpoint", headers = { Authorization = "Bearer YOUR_API_KEY" } }, 93 | 94 | "https://api.proxyscrape.com/v3/free-proxy-list/get?request=getproxies&protocol=http", 95 | "https://api.proxyscrape.com/v3/free-proxy-list/get?request=getproxies&protocol=https", 96 | "https://raw.githubusercontent.com/proxifly/free-proxy-list/refs/heads/main/proxies/protocols/http/data.txt", 97 | "https://raw.githubusercontent.com/proxifly/free-proxy-list/refs/heads/main/proxies/protocols/https/data.txt", 98 | "https://raw.githubusercontent.com/roosterkid/openproxylist/refs/heads/main/HTTPS_RAW.txt", 99 | "https://raw.githubusercontent.com/sunny9577/proxy-scraper/refs/heads/master/generated/http_proxies.txt", 100 | "https://raw.githubusercontent.com/TheSpeedX/PROXY-List/refs/heads/master/http.txt", 101 | ] 102 | 103 | [scraping.socks4] 104 | enabled = true 105 | urls = [ 106 | # Local file examples: 107 | # "./my_socks4_proxies.txt", 108 | # "/home/user/my_socks4_proxies.txt", 109 | # "C:/Users/user/Desktop/my_socks4_proxies.txt", 110 | # "file:///home/user/my_socks4_proxies.txt", 111 | 112 | # Advanced URL configuration examples (with basic auth or custom headers): 113 | # HTTP Basic Auth example: 114 | # { url = "https://some.api/endpoint", basic_auth = { username = "user", password = "password123" } }, 115 | # Custom headers example: 116 | # { url = "https://some.api/endpoint", headers = { Authorization = "Bearer YOUR_API_KEY" } }, 117 | 118 | "https://api.proxyscrape.com/v3/free-proxy-list/get?request=getproxies&protocol=socks4", 119 | "https://raw.githubusercontent.com/proxifly/free-proxy-list/refs/heads/main/proxies/protocols/socks4/data.txt", 120 | "https://raw.githubusercontent.com/roosterkid/openproxylist/refs/heads/main/SOCKS4_RAW.txt", 121 | "https://raw.githubusercontent.com/sunny9577/proxy-scraper/refs/heads/master/generated/socks4_proxies.txt", 122 | "https://raw.githubusercontent.com/TheSpeedX/PROXY-List/refs/heads/master/socks4.txt", 123 | ] 124 | 125 | [scraping.socks5] 126 | enabled = true 127 | urls = [ 128 | # Local file examples: 129 | # "./my_socks5_proxies.txt", 130 | # "/home/user/my_socks5_proxies.txt", 131 | # "C:/Users/user/Desktop/my_socks5_proxies.txt", 132 | # "file:///home/user/my_socks5_proxies.txt", 133 | 134 | # Advanced URL configuration examples (with basic auth or custom headers): 135 | # HTTP Basic Auth example: 136 | # { url = "https://some.api/endpoint", basic_auth = { username = "user", password = "password123" } }, 137 | # Custom headers example: 138 | # { url = "https://some.api/endpoint", headers = { Authorization = "Bearer YOUR_API_KEY" } }, 139 | 140 | "https://api.proxyscrape.com/v3/free-proxy-list/get?request=getproxies&protocol=socks5", 141 | "https://raw.githubusercontent.com/hookzof/socks5_list/refs/heads/master/proxy.txt", 142 | "https://raw.githubusercontent.com/proxifly/free-proxy-list/refs/heads/main/proxies/protocols/socks5/data.txt", 143 | "https://raw.githubusercontent.com/roosterkid/openproxylist/refs/heads/main/SOCKS5_RAW.txt", 144 | "https://raw.githubusercontent.com/sunny9577/proxy-scraper/refs/heads/master/generated/socks5_proxies.txt", 145 | "https://raw.githubusercontent.com/TheSpeedX/PROXY-List/refs/heads/master/socks5.txt", 146 | ] 147 | -------------------------------------------------------------------------------- /src/ipdb.rs: -------------------------------------------------------------------------------- 1 | use std::{io, path::PathBuf, time::Duration}; 2 | 3 | use color_eyre::eyre::{WrapErr as _, eyre}; 4 | use tokio::io::AsyncWriteExt as _; 5 | 6 | #[cfg(feature = "tui")] 7 | use crate::event::{AppEvent, Event}; 8 | use crate::{fs::get_cache_path, utils::is_docker}; 9 | 10 | #[derive(Clone, Copy)] 11 | pub enum DbType { 12 | Asn, 13 | Geo, 14 | } 15 | 16 | impl DbType { 17 | const fn name(self) -> &'static str { 18 | match self { 19 | Self::Asn => "ASN", 20 | Self::Geo => "geolocation", 21 | } 22 | } 23 | 24 | const fn url(self) -> &'static str { 25 | match self { 26 | Self::Asn => { 27 | "https://raw.githubusercontent.com/P3TERX/GeoLite.mmdb/refs/heads/download/GeoLite2-ASN.mmdb" 28 | } 29 | Self::Geo => { 30 | "https://raw.githubusercontent.com/P3TERX/GeoLite.mmdb/refs/heads/download/GeoLite2-City.mmdb" 31 | } 32 | } 33 | } 34 | 35 | async fn db_path(self) -> crate::Result { 36 | let mut cache_path = get_cache_path().await?; 37 | match self { 38 | Self::Asn => cache_path.push("asn_database.mmdb"), 39 | Self::Geo => cache_path.push("geolocation_database.mmdb"), 40 | } 41 | Ok(cache_path) 42 | } 43 | 44 | async fn etag_path(self) -> crate::Result { 45 | let mut db_path = self.db_path().await?; 46 | db_path.set_extension("mmdb.etag"); 47 | Ok(db_path) 48 | } 49 | 50 | async fn save_db( 51 | self, 52 | mut response: reqwest::Response, 53 | #[cfg(feature = "tui")] tx: tokio::sync::mpsc::UnboundedSender, 54 | ) -> crate::Result<()> { 55 | #[cfg(feature = "tui")] 56 | drop(tx.send(Event::App(AppEvent::IpDbTotal( 57 | self, 58 | response.content_length(), 59 | )))); 60 | 61 | let db_path = self.db_path().await?; 62 | let mut file = 63 | tokio::fs::File::create(&db_path).await.wrap_err_with(|| { 64 | format!("failed to create file: {}", db_path.display()) 65 | })?; 66 | while let Some(chunk) = response.chunk().await? { 67 | file.write_all(&chunk).await.wrap_err_with(|| { 68 | format!("failed to write to file: {}", db_path.display()) 69 | })?; 70 | #[cfg(feature = "tui")] 71 | drop( 72 | tx.send(Event::App(AppEvent::IpDbDownloaded( 73 | self, 74 | chunk.len(), 75 | ))), 76 | ); 77 | } 78 | Ok(()) 79 | } 80 | 81 | async fn save_etag(self, etag: impl AsRef<[u8]>) -> crate::Result<()> { 82 | let path = self.etag_path().await?; 83 | tokio::fs::write(&path, etag).await.wrap_err_with(move || { 84 | format!("failed to write to file: {}", path.display()) 85 | }) 86 | } 87 | 88 | async fn read_etag( 89 | self, 90 | ) -> crate::Result> { 91 | let path = self.etag_path().await?; 92 | match tokio::fs::read_to_string(&path).await { 93 | Ok(text) => Ok(text.parse().ok()), 94 | Err(e) if e.kind() == io::ErrorKind::NotFound => Ok(None), 95 | Err(e) => Err(e).wrap_err_with(move || { 96 | format!("failed to read file to string: {}", path.display()) 97 | }), 98 | } 99 | } 100 | 101 | async fn remove_etag(self) -> crate::Result<()> { 102 | let path = self.etag_path().await?; 103 | match tokio::fs::remove_file(&path).await { 104 | Ok(()) => Ok(()), 105 | Err(e) if e.kind() == io::ErrorKind::NotFound => Ok(()), 106 | Err(e) => Err(e).wrap_err_with(move || { 107 | format!("failed to remove file: {}", path.display()) 108 | }), 109 | } 110 | } 111 | 112 | pub async fn download( 113 | self, 114 | http_client: reqwest_middleware::ClientWithMiddleware, 115 | #[cfg(feature = "tui")] tx: tokio::sync::mpsc::UnboundedSender, 116 | ) -> crate::Result<()> { 117 | let db_path = self.db_path().await?; 118 | let mut headers = reqwest::header::HeaderMap::new(); 119 | #[expect(clippy::collapsible_if)] 120 | if tokio::fs::metadata(&db_path).await.is_ok_and(|m| m.is_file()) { 121 | if let Some(etag) = self.read_etag().await? { 122 | headers.insert(reqwest::header::IF_NONE_MATCH, etag); 123 | } 124 | } 125 | 126 | let response = http_client 127 | .get(self.url()) 128 | .headers(headers) 129 | .timeout(Duration::MAX) 130 | .send() 131 | .await? 132 | .error_for_status()?; 133 | 134 | if response.status() == reqwest::StatusCode::NOT_MODIFIED { 135 | tracing::info!( 136 | "Latest {} database is already cached at {}", 137 | self.name(), 138 | db_path.display() 139 | ); 140 | return Ok(()); 141 | } 142 | 143 | if response.status() != reqwest::StatusCode::OK { 144 | return Err(eyre!( 145 | "HTTP status error ({}) for url ({})", 146 | response.status(), 147 | response.url() 148 | )); 149 | } 150 | 151 | let etag = response.headers().get(reqwest::header::ETAG).cloned(); 152 | 153 | self.save_db( 154 | response, 155 | #[cfg(feature = "tui")] 156 | tx, 157 | ) 158 | .await?; 159 | 160 | if is_docker().await { 161 | tracing::info!( 162 | "Downloaded {} database to Docker volume ({} in container)", 163 | self.name(), 164 | db_path.display() 165 | ); 166 | } else { 167 | tracing::info!( 168 | "Downloaded {} database to {}", 169 | self.name(), 170 | db_path.display() 171 | ); 172 | } 173 | drop(db_path); 174 | 175 | if let Some(etag_value) = etag { 176 | self.save_etag(etag_value).await 177 | } else { 178 | self.remove_etag().await 179 | } 180 | } 181 | 182 | pub async fn open_mmap( 183 | self, 184 | ) -> crate::Result> { 185 | let path = self.db_path().await?; 186 | tokio::task::spawn_blocking(move || { 187 | #[expect(clippy::undocumented_unsafe_blocks)] 188 | unsafe { maxminddb::Reader::open_mmap(&path) }.wrap_err_with( 189 | move || { 190 | format!("failed to open IP database: {}", path.display()) 191 | }, 192 | ) 193 | }) 194 | .await? 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /src/scraper.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use color_eyre::eyre::{OptionExt as _, WrapErr as _}; 4 | use foldhash::HashSetExt as _; 5 | 6 | #[cfg(feature = "tui")] 7 | use crate::event::{AppEvent, Event}; 8 | use crate::{ 9 | HashSet, 10 | config::{Config, Source}, 11 | parsers::PROXY_REGEX, 12 | proxy::{Proxy, ProxyType}, 13 | utils::pretty_error, 14 | }; 15 | 16 | async fn scrape_one( 17 | config: Arc, 18 | http_client: reqwest_middleware::ClientWithMiddleware, 19 | proto: ProxyType, 20 | proxies: Arc>>, 21 | source: Arc, 22 | #[cfg(feature = "tui")] tx: tokio::sync::mpsc::UnboundedSender, 23 | ) -> crate::Result<()> { 24 | let text_result = if let Ok(u) = url::Url::parse(&source.url) { 25 | match u.scheme() { 26 | "http" | "https" => { 27 | let mut request = http_client.get(u); 28 | drop(http_client); 29 | 30 | if let Some(auth) = &source.basic_auth { 31 | request = request 32 | .basic_auth(&auth.username, auth.password.as_ref()); 33 | } 34 | 35 | if let Some(headers) = &source.headers { 36 | for (k, v) in headers { 37 | request = request.header(k, v); 38 | } 39 | } 40 | 41 | match request.send().await { 42 | Ok(resp) => resp.text().await.map_err(Into::into), 43 | Err(e) => Err(e.into()), 44 | } 45 | } 46 | _ => { 47 | drop(http_client); 48 | match u.to_file_path() { 49 | Ok(path) => tokio::fs::read_to_string(path) 50 | .await 51 | .wrap_err_with(move || { 52 | format!("failed to read file to string: {u}") 53 | }), 54 | Err(()) => tokio::fs::read_to_string(&source.url) 55 | .await 56 | .wrap_err_with(move || { 57 | format!("failed to read file to string: {u}") 58 | }), 59 | } 60 | } 61 | } 62 | } else { 63 | drop(http_client); 64 | tokio::fs::read_to_string(&source.url).await.wrap_err_with(|| { 65 | format!("failed to read file to string: {}", source.url) 66 | }) 67 | }; 68 | 69 | #[cfg(feature = "tui")] 70 | drop(tx.send(Event::App(AppEvent::SourceScraped(proto)))); 71 | 72 | let text = match text_result { 73 | Ok(text) => text, 74 | Err(e) => { 75 | tracing::warn!("{}: {}", source.url, pretty_error(&e)); 76 | return Ok(()); 77 | } 78 | }; 79 | 80 | #[cfg(feature = "tui")] 81 | let mut seen_protocols = HashSet::new(); 82 | 83 | let mut new_proxies = HashSet::new(); 84 | 85 | for maybe_capture in PROXY_REGEX.captures_iter(&text) { 86 | if config.scraping.max_proxies_per_source != 0 87 | && new_proxies.len() >= config.scraping.max_proxies_per_source 88 | { 89 | tracing::warn!( 90 | "{}: too many proxies (> {}) - skipped", 91 | source.url, 92 | config.scraping.max_proxies_per_source 93 | ); 94 | return Ok(()); 95 | } 96 | 97 | let capture = maybe_capture?; 98 | 99 | let protocol = match capture.name("protocol") { 100 | Some(m) => m.as_str().parse()?, 101 | None => proto, 102 | }; 103 | 104 | if config.protocol_is_enabled(protocol) { 105 | #[cfg(feature = "tui")] 106 | seen_protocols.insert(protocol); 107 | 108 | new_proxies.insert(Proxy { 109 | protocol, 110 | host: capture 111 | .name("host") 112 | .ok_or_eyre("failed to match \"host\" regex capture group")? 113 | .as_str() 114 | .to_owned(), 115 | port: capture 116 | .name("port") 117 | .ok_or_eyre("failed to match \"port\" regex capture group")? 118 | .as_str() 119 | .parse()?, 120 | username: capture 121 | .name("username") 122 | .map(|m| m.as_str().to_owned()), 123 | password: capture 124 | .name("password") 125 | .map(|m| m.as_str().to_owned()), 126 | timeout: None, 127 | exit_ip: None, 128 | }); 129 | } 130 | } 131 | 132 | drop(config); 133 | drop(text); 134 | 135 | if new_proxies.is_empty() { 136 | tracing::warn!("{}: no proxies found", source.url); 137 | return Ok(()); 138 | } 139 | 140 | drop(source); 141 | 142 | let mut proxies = proxies.lock(); 143 | proxies.extend(new_proxies); 144 | 145 | #[cfg(feature = "tui")] 146 | for proto in seen_protocols { 147 | let count = proxies.iter().filter(move |p| p.protocol == proto).count(); 148 | drop(tx.send(Event::App(AppEvent::TotalProxies(proto, count)))); 149 | } 150 | 151 | drop(proxies); 152 | 153 | Ok(()) 154 | } 155 | 156 | pub async fn scrape_all( 157 | config: Arc, 158 | http_client: reqwest_middleware::ClientWithMiddleware, 159 | token: tokio_util::sync::CancellationToken, 160 | #[cfg(feature = "tui")] tx: tokio::sync::mpsc::UnboundedSender, 161 | ) -> crate::Result> { 162 | let proxies = Arc::new(parking_lot::Mutex::new(HashSet::new())); 163 | 164 | let mut join_set = tokio::task::JoinSet::new(); 165 | for (&proto, sources) in &config.scraping.sources { 166 | #[cfg(feature = "tui")] 167 | drop(tx.send(Event::App(AppEvent::SourcesTotal(proto, sources.len())))); 168 | 169 | for source in sources { 170 | let config = Arc::clone(&config); 171 | let http_client = http_client.clone(); 172 | let proxies = Arc::clone(&proxies); 173 | let token = token.clone(); 174 | let source = Arc::clone(source); 175 | #[cfg(feature = "tui")] 176 | let tx = tx.clone(); 177 | join_set.spawn(async move { 178 | tokio::select! { 179 | biased; 180 | res = scrape_one( 181 | config, 182 | http_client, 183 | proto, 184 | proxies, 185 | source, 186 | #[cfg(feature = "tui")] 187 | tx, 188 | ) => res, 189 | () = token.cancelled() => Ok(()), 190 | } 191 | }); 192 | } 193 | } 194 | 195 | drop(config); 196 | drop(http_client); 197 | drop(token); 198 | #[cfg(feature = "tui")] 199 | drop(tx); 200 | 201 | while let Some(res) = join_set.join_next().await { 202 | res??; 203 | } 204 | 205 | drop(join_set); 206 | 207 | Ok(Arc::into_inner(proxies) 208 | .ok_or_eyre("failed to unwrap Arc")? 209 | .into_inner() 210 | .into_iter() 211 | .collect()) 212 | } 213 | -------------------------------------------------------------------------------- /src/config.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | collections::hash_map, 3 | path::{Path, PathBuf}, 4 | sync::Arc, 5 | time::Duration, 6 | }; 7 | 8 | use color_eyre::eyre::{OptionExt as _, WrapErr as _}; 9 | 10 | use crate::{ 11 | HashMap, http::BasicAuth, proxy::ProxyType, raw_config, utils::is_docker, 12 | }; 13 | 14 | pub const APP_DIRECTORY_NAME: &str = "proxy_scraper_checker"; 15 | 16 | #[derive(serde::Deserialize)] 17 | pub struct HttpbinResponse { 18 | pub origin: String, 19 | } 20 | 21 | pub struct Source { 22 | pub url: String, 23 | pub basic_auth: Option, 24 | pub headers: Option>, 25 | } 26 | 27 | pub struct ScrapingConfig { 28 | pub max_proxies_per_source: usize, 29 | pub timeout: Duration, 30 | pub connect_timeout: Duration, 31 | pub proxy: Option, 32 | pub user_agent: String, 33 | pub sources: HashMap>>, 34 | } 35 | 36 | pub struct CheckingConfig { 37 | pub check_url: Option, 38 | pub max_concurrent_checks: usize, 39 | pub timeout: Duration, 40 | pub connect_timeout: Duration, 41 | pub user_agent: String, 42 | } 43 | 44 | pub struct TxtOutputConfig { 45 | pub enabled: bool, 46 | } 47 | 48 | pub struct JsonOutputConfig { 49 | pub enabled: bool, 50 | pub include_asn: bool, 51 | pub include_geolocation: bool, 52 | } 53 | 54 | pub struct OutputConfig { 55 | pub path: PathBuf, 56 | pub sort_by_speed: bool, 57 | pub txt: TxtOutputConfig, 58 | pub json: JsonOutputConfig, 59 | } 60 | 61 | pub struct Config { 62 | pub debug: bool, 63 | pub scraping: ScrapingConfig, 64 | pub checking: CheckingConfig, 65 | pub output: OutputConfig, 66 | } 67 | 68 | async fn get_output_path( 69 | raw_config: &raw_config::RawConfig, 70 | ) -> crate::Result { 71 | let output_path = if is_docker().await { 72 | let mut path = tokio::task::spawn_blocking(dirs::data_local_dir) 73 | .await? 74 | .ok_or_eyre("failed to get user's local data directory")?; 75 | path.push(APP_DIRECTORY_NAME); 76 | path 77 | } else { 78 | raw_config.output.path.clone() 79 | }; 80 | tokio::fs::create_dir_all(&output_path).await.wrap_err_with(|| { 81 | format!("failed to create directory: {}", output_path.display()) 82 | })?; 83 | Ok(output_path) 84 | } 85 | 86 | impl Config { 87 | pub const fn asn_enabled(&self) -> bool { 88 | self.output.json.enabled && self.output.json.include_asn 89 | } 90 | 91 | pub const fn geolocation_enabled(&self) -> bool { 92 | self.output.json.enabled && self.output.json.include_geolocation 93 | } 94 | 95 | pub fn enabled_protocols( 96 | &self, 97 | ) -> hash_map::Keys<'_, ProxyType, Vec>> { 98 | self.scraping.sources.keys() 99 | } 100 | 101 | pub fn protocol_is_enabled(&self, protocol: ProxyType) -> bool { 102 | self.scraping.sources.contains_key(&protocol) 103 | } 104 | 105 | pub async fn from_raw_config( 106 | raw_config: raw_config::RawConfig, 107 | ) -> crate::Result { 108 | let output_path = get_output_path(&raw_config).await?; 109 | 110 | let max_concurrent_checks = 111 | if let Ok(lim) = rlimit::increase_nofile_limit(u64::MAX) { 112 | let lim = usize::try_from(lim).unwrap_or(usize::MAX); 113 | 114 | if raw_config.checking.max_concurrent_checks.get() > lim { 115 | tracing::warn!( 116 | "max_concurrent_checks config value is too high for \ 117 | your OS. It will be ignored and {lim} will be used." 118 | ); 119 | lim 120 | } else { 121 | raw_config.checking.max_concurrent_checks.get() 122 | } 123 | } else { 124 | raw_config.checking.max_concurrent_checks.get() 125 | }; 126 | 127 | Ok(Self { 128 | debug: raw_config.debug, 129 | scraping: ScrapingConfig { 130 | max_proxies_per_source: raw_config 131 | .scraping 132 | .max_proxies_per_source, 133 | timeout: Duration::from_secs_f64(raw_config.scraping.timeout), 134 | connect_timeout: Duration::from_secs_f64( 135 | raw_config.scraping.connect_timeout, 136 | ), 137 | proxy: raw_config.scraping.proxy, 138 | user_agent: raw_config.scraping.user_agent, 139 | sources: [ 140 | (ProxyType::Http, raw_config.scraping.http), 141 | (ProxyType::Socks4, raw_config.scraping.socks4), 142 | (ProxyType::Socks5, raw_config.scraping.socks5), 143 | ] 144 | .into_iter() 145 | .filter_map(|(proxy_type, section)| { 146 | section.enabled.then(move || { 147 | ( 148 | proxy_type, 149 | section 150 | .urls 151 | .into_iter() 152 | .map(Into::into) 153 | .map(Arc::new) 154 | .collect(), 155 | ) 156 | }) 157 | }) 158 | .collect(), 159 | }, 160 | checking: CheckingConfig { 161 | check_url: raw_config.checking.check_url, 162 | max_concurrent_checks, 163 | timeout: Duration::from_secs_f64(raw_config.checking.timeout), 164 | connect_timeout: Duration::from_secs_f64( 165 | raw_config.checking.connect_timeout, 166 | ), 167 | user_agent: raw_config.checking.user_agent, 168 | }, 169 | output: OutputConfig { 170 | path: output_path, 171 | sort_by_speed: raw_config.output.sort_by_speed, 172 | txt: TxtOutputConfig { enabled: raw_config.output.txt.enabled }, 173 | json: JsonOutputConfig { 174 | enabled: raw_config.output.json.enabled, 175 | include_asn: raw_config.output.json.include_asn, 176 | include_geolocation: raw_config 177 | .output 178 | .json 179 | .include_geolocation, 180 | }, 181 | }, 182 | }) 183 | } 184 | } 185 | 186 | impl From for Source { 187 | fn from(sc: raw_config::SourceConfig) -> Self { 188 | match sc { 189 | raw_config::SourceConfig::Simple(url) => { 190 | Self { url, basic_auth: None, headers: None } 191 | } 192 | raw_config::SourceConfig::Detailed(config) => Self { 193 | url: config.url, 194 | basic_auth: config.basic_auth, 195 | headers: config.headers, 196 | }, 197 | } 198 | } 199 | } 200 | 201 | pub async fn load_config() -> crate::Result> { 202 | let raw_config = { 203 | let raw_config_path = raw_config::get_config_path(); 204 | raw_config::read_config(Path::new(&raw_config_path)).await 205 | }?; 206 | 207 | let config = Config::from_raw_config(raw_config).await?; 208 | 209 | Ok(Arc::new(config)) 210 | } 211 | -------------------------------------------------------------------------------- /src/output.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | cmp::Ordering, 3 | io, 4 | net::{IpAddr, Ipv4Addr}, 5 | sync::Arc, 6 | time::Duration, 7 | }; 8 | 9 | use color_eyre::eyre::WrapErr as _; 10 | use itertools::Itertools as _; 11 | 12 | use crate::{ 13 | HashMap, 14 | config::Config, 15 | ipdb, 16 | proxy::{Proxy, ProxyType}, 17 | utils::is_docker, 18 | }; 19 | 20 | fn compare_timeout(a: &Proxy, b: &Proxy) -> Ordering { 21 | a.timeout.unwrap_or(Duration::MAX).cmp(&b.timeout.unwrap_or(Duration::MAX)) 22 | } 23 | 24 | fn compare_natural(a: &Proxy, b: &Proxy) -> Ordering { 25 | a.protocol 26 | .cmp(&b.protocol) 27 | .then_with(move || { 28 | match (a.host.parse::(), b.host.parse::()) { 29 | (Ok(ai), Ok(bi)) => ai.octets().cmp(&bi.octets()), 30 | (Ok(_), Err(_)) => Ordering::Less, 31 | (Err(_), Ok(_)) => Ordering::Greater, 32 | (Err(_), Err(_)) => a.host.cmp(&b.host), 33 | } 34 | }) 35 | .then_with(move || a.port.cmp(&b.port)) 36 | } 37 | 38 | #[derive(serde::Serialize)] 39 | struct ProxyJson<'a> { 40 | protocol: ProxyType, 41 | username: Option<&'a str>, 42 | password: Option<&'a str>, 43 | host: &'a str, 44 | port: u16, 45 | timeout: Option, 46 | exit_ip: Option<&'a str>, 47 | asn: Option>, 48 | geolocation: Option>, 49 | } 50 | 51 | fn group_proxies<'a>( 52 | config: &Config, 53 | proxies: &'a [Proxy], 54 | ) -> HashMap> { 55 | let mut groups: HashMap<_, _> = 56 | config.enabled_protocols().copied().map(|p| (p, Vec::new())).collect(); 57 | for proxy in proxies { 58 | if let Some(proxies) = groups.get_mut(&proxy.protocol) { 59 | proxies.push(proxy); 60 | } 61 | } 62 | groups 63 | } 64 | 65 | #[expect(clippy::too_many_lines)] 66 | pub async fn save_proxies( 67 | config: Arc, 68 | mut proxies: Vec, 69 | ) -> crate::Result<()> { 70 | if config.output.sort_by_speed { 71 | proxies.sort_unstable_by(compare_timeout); 72 | } else { 73 | proxies.sort_unstable_by(compare_natural); 74 | } 75 | 76 | if config.output.json.enabled { 77 | let (maybe_asn_db, maybe_geo_db) = tokio::try_join!( 78 | async { 79 | if config.output.json.include_asn { 80 | ipdb::DbType::Asn.open_mmap().await.map(Some) 81 | } else { 82 | Ok(None) 83 | } 84 | }, 85 | async { 86 | if config.output.json.include_geolocation { 87 | ipdb::DbType::Geo.open_mmap().await.map(Some) 88 | } else { 89 | Ok(None) 90 | } 91 | } 92 | )?; 93 | 94 | let mut proxy_dicts = Vec::with_capacity(proxies.len()); 95 | for proxy in &proxies { 96 | proxy_dicts.push(ProxyJson { 97 | protocol: proxy.protocol, 98 | username: proxy.username.as_deref(), 99 | password: proxy.password.as_deref(), 100 | host: &proxy.host, 101 | port: proxy.port, 102 | timeout: proxy 103 | .timeout 104 | .map(|d| (d.as_secs_f64() * 100.0).round() / 100.0_f64), 105 | exit_ip: proxy.exit_ip.as_deref(), 106 | asn: if let Some(asn_db) = &maybe_asn_db { 107 | if let Some(exit_ip) = proxy.exit_ip.as_ref() { 108 | let exit_ip_addr: IpAddr = exit_ip.parse()?; 109 | asn_db.lookup(exit_ip_addr)?.decode()? 110 | } else { 111 | None 112 | } 113 | } else { 114 | None 115 | }, 116 | geolocation: if let Some(geo_db) = &maybe_geo_db { 117 | if let Some(exit_ip) = proxy.exit_ip.as_ref() { 118 | let exit_ip_addr: IpAddr = exit_ip.parse()?; 119 | geo_db.lookup(exit_ip_addr)?.decode()? 120 | } else { 121 | None 122 | } 123 | } else { 124 | None 125 | }, 126 | }); 127 | } 128 | 129 | for (path, pretty) in [ 130 | (config.output.path.join("proxies.json"), false), 131 | (config.output.path.join("proxies_pretty.json"), true), 132 | ] { 133 | match tokio::fs::remove_file(&path).await { 134 | Ok(()) => Ok(()), 135 | Err(e) if e.kind() == io::ErrorKind::NotFound => Ok(()), 136 | Err(e) => Err(e).wrap_err_with(|| { 137 | format!("failed to remove file: {}", path.display()) 138 | }), 139 | }?; 140 | let json_data = if pretty { 141 | serde_json::to_vec_pretty(&proxy_dicts)? 142 | } else { 143 | serde_json::to_vec(&proxy_dicts)? 144 | }; 145 | tokio::fs::write(&path, json_data).await.wrap_err_with( 146 | move || format!("failed to write to file: {}", path.display()), 147 | )?; 148 | } 149 | } 150 | 151 | if config.output.txt.enabled { 152 | let grouped_proxies = group_proxies(&config, &proxies); 153 | let directory_path = config.output.path.join("proxies"); 154 | match tokio::fs::remove_dir_all(&directory_path).await { 155 | Ok(()) => Ok(()), 156 | Err(e) if e.kind() == io::ErrorKind::NotFound => Ok(()), 157 | Err(e) => Err(e).wrap_err_with(|| { 158 | format!( 159 | "failed to remove directory: {}", 160 | directory_path.display() 161 | ) 162 | }), 163 | }?; 164 | tokio::fs::create_dir_all(&directory_path).await.wrap_err_with( 165 | || { 166 | format!( 167 | "failed to create directory: {}", 168 | directory_path.display() 169 | ) 170 | }, 171 | )?; 172 | 173 | let text = create_proxy_list_str(proxies.iter(), true); 174 | tokio::fs::write(directory_path.join("all.txt"), text) 175 | .await 176 | .wrap_err_with(|| { 177 | format!( 178 | "failed to write to file: {}", 179 | directory_path.join("all.txt").display() 180 | ) 181 | })?; 182 | 183 | for (proto, proxies) in grouped_proxies { 184 | let text = create_proxy_list_str(proxies, false); 185 | let mut file_path = directory_path.join(proto.as_str()); 186 | file_path.set_extension("txt"); 187 | tokio::fs::write(&file_path, text).await.wrap_err_with( 188 | move || { 189 | format!("failed to write to file: {}", file_path.display()) 190 | }, 191 | )?; 192 | } 193 | } 194 | 195 | drop(proxies); 196 | 197 | let path = tokio::fs::canonicalize(&config.output.path) 198 | .await 199 | .unwrap_or_else(move |_| config.output.path.clone()); 200 | if is_docker().await { 201 | tracing::info!( 202 | "Proxies have been saved to ./out ({} in container)", 203 | path.display() 204 | ); 205 | } else { 206 | tracing::info!("Proxies have been saved to {}", path.display()); 207 | } 208 | 209 | Ok(()) 210 | } 211 | 212 | fn create_proxy_list_str<'a, I>(proxies: I, include_protocol: bool) -> String 213 | where 214 | I: IntoIterator, 215 | { 216 | proxies 217 | .into_iter() 218 | .map(move |proxy| proxy.to_string(include_protocol)) 219 | .join("\n") 220 | } 221 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | #![deny( 2 | warnings, 3 | deprecated_safe, 4 | future_incompatible, 5 | keyword_idents, 6 | let_underscore, 7 | nonstandard_style, 8 | refining_impl_trait, 9 | rust_2018_compatibility, 10 | rust_2018_idioms, 11 | rust_2021_compatibility, 12 | rust_2024_compatibility, 13 | unused, 14 | clippy::all, 15 | clippy::pedantic, 16 | clippy::restriction, 17 | clippy::nursery, 18 | clippy::cargo 19 | )] 20 | #![allow( 21 | clippy::absolute_paths, 22 | clippy::allow_attributes_without_reason, 23 | clippy::arbitrary_source_item_ordering, 24 | clippy::as_conversions, 25 | clippy::blanket_clippy_restriction_lints, 26 | clippy::cast_precision_loss, 27 | clippy::cognitive_complexity, 28 | clippy::else_if_without_else, 29 | clippy::float_arithmetic, 30 | clippy::implicit_return, 31 | clippy::integer_division_remainder_used, 32 | clippy::iter_over_hash_type, 33 | clippy::min_ident_chars, 34 | clippy::missing_docs_in_private_items, 35 | clippy::mod_module_files, 36 | clippy::multiple_crate_versions, 37 | clippy::pattern_type_mismatch, 38 | clippy::question_mark_used, 39 | clippy::separated_literal_suffix, 40 | clippy::shadow_reuse, 41 | clippy::shadow_unrelated, 42 | clippy::single_call_fn, 43 | clippy::single_char_lifetime_names, 44 | clippy::std_instead_of_alloc, 45 | clippy::std_instead_of_core, 46 | clippy::too_many_lines, 47 | clippy::unwrap_used 48 | )] 49 | 50 | mod checker; 51 | mod config; 52 | #[cfg(feature = "tui")] 53 | mod event; 54 | mod fs; 55 | mod http; 56 | mod ipdb; 57 | mod output; 58 | mod parsers; 59 | mod proxy; 60 | mod raw_config; 61 | mod scraper; 62 | #[cfg(feature = "tui")] 63 | mod tui; 64 | mod utils; 65 | 66 | use std::sync::Arc; 67 | 68 | use tracing_subscriber::{ 69 | layer::SubscriberExt as _, util::SubscriberInitExt as _, 70 | }; 71 | 72 | #[cfg(feature = "dhat")] 73 | #[global_allocator] 74 | static GLOBAL: dhat::Alloc = dhat::Alloc; 75 | 76 | #[cfg(all( 77 | feature = "jemalloc", 78 | any(target_arch = "aarch64", target_arch = "x86_64"), 79 | any(target_os = "linux", target_os = "macos"), 80 | ))] 81 | #[global_allocator] 82 | static GLOBAL: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc; 83 | 84 | #[cfg(all( 85 | any(feature = "mimalloc_v2", feature = "mimalloc_v3"), 86 | any(target_arch = "aarch64", target_arch = "x86_64"), 87 | any(target_os = "linux", target_os = "macos", target_os = "windows"), 88 | ))] 89 | #[global_allocator] 90 | static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; 91 | 92 | type Error = color_eyre::Report; 93 | type Result = color_eyre::Result; 94 | 95 | type HashMap = foldhash::HashMap; 96 | type HashSet = foldhash::HashSet; 97 | 98 | fn create_logging_filter( 99 | config: &config::Config, 100 | ) -> tracing_subscriber::filter::Targets { 101 | let base = tracing_subscriber::filter::Targets::new() 102 | .with_default(tracing::level_filters::LevelFilter::INFO) 103 | .with_target( 104 | "hyper_util::client::legacy::connect::http", 105 | tracing::level_filters::LevelFilter::ERROR, 106 | ); 107 | 108 | if config.debug { 109 | base.with_target( 110 | "proxy_scraper_checker", 111 | tracing::level_filters::LevelFilter::DEBUG, 112 | ) 113 | } else { 114 | base 115 | } 116 | } 117 | 118 | async fn download_output_dependencies( 119 | config: &config::Config, 120 | http_client: reqwest_middleware::ClientWithMiddleware, 121 | token: tokio_util::sync::CancellationToken, 122 | #[cfg(feature = "tui")] tx: tokio::sync::mpsc::UnboundedSender< 123 | event::Event, 124 | >, 125 | ) -> crate::Result<()> { 126 | let mut output_dependencies_tasks = tokio::task::JoinSet::new(); 127 | 128 | if config.asn_enabled() { 129 | let http_client = http_client.clone(); 130 | let token = token.clone(); 131 | #[cfg(feature = "tui")] 132 | let tx = tx.clone(); 133 | 134 | output_dependencies_tasks.spawn(async move { 135 | tokio::select! { 136 | biased; 137 | res = ipdb::DbType::Asn.download( 138 | http_client, 139 | #[cfg(feature = "tui")] 140 | tx, 141 | ) => res, 142 | () = token.cancelled() => Ok(()), 143 | } 144 | }); 145 | } 146 | 147 | if config.geolocation_enabled() { 148 | output_dependencies_tasks.spawn(async move { 149 | tokio::select! { 150 | biased; 151 | res = ipdb::DbType::Geo.download( 152 | http_client, 153 | #[cfg(feature = "tui")] 154 | tx, 155 | ) => res, 156 | () = token.cancelled() => Ok(()), 157 | } 158 | }); 159 | } 160 | 161 | while let Some(task) = output_dependencies_tasks.join_next().await { 162 | task??; 163 | } 164 | Ok(()) 165 | } 166 | 167 | async fn main_task( 168 | config: Arc, 169 | token: tokio_util::sync::CancellationToken, 170 | #[cfg(feature = "tui")] tx: tokio::sync::mpsc::UnboundedSender< 171 | event::Event, 172 | >, 173 | ) -> crate::Result<()> { 174 | let dns_resolver = Arc::new(http::HickoryDnsResolver::new().await?); 175 | let http_client = 176 | http::create_reqwest_client(&config, Arc::clone(&dns_resolver))?; 177 | 178 | let ((), mut proxies) = tokio::try_join!( 179 | download_output_dependencies( 180 | &config, 181 | http_client.clone(), 182 | token.clone(), 183 | #[cfg(feature = "tui")] 184 | tx.clone(), 185 | ), 186 | scraper::scrape_all( 187 | Arc::clone(&config), 188 | http_client, 189 | token.clone(), 190 | #[cfg(feature = "tui")] 191 | tx.clone(), 192 | ), 193 | )?; 194 | 195 | proxies = checker::check_all( 196 | Arc::clone(&config), 197 | dns_resolver, 198 | proxies, 199 | token, 200 | #[cfg(feature = "tui")] 201 | tx.clone(), 202 | ) 203 | .await?; 204 | 205 | output::save_proxies(config, proxies).await?; 206 | 207 | tracing::info!("Thank you for using proxy-scraper-checker!"); 208 | 209 | #[cfg(feature = "tui")] 210 | drop(tx.send(event::Event::App(event::AppEvent::Done))); 211 | 212 | Ok(()) 213 | } 214 | 215 | #[cfg(any(unix, windows))] 216 | fn watch_signals( 217 | token: &tokio_util::sync::CancellationToken, 218 | #[cfg(feature = "tui")] tx: &tokio::sync::mpsc::UnboundedSender< 219 | event::Event, 220 | >, 221 | ) { 222 | #[cfg(unix)] 223 | let signals = [ 224 | ( 225 | "SIGINT", 226 | tokio::signal::unix::signal( 227 | tokio::signal::unix::SignalKind::interrupt(), 228 | ), 229 | ), 230 | ( 231 | "SIGTERM", 232 | tokio::signal::unix::signal( 233 | tokio::signal::unix::SignalKind::terminate(), 234 | ), 235 | ), 236 | ]; 237 | 238 | #[cfg(windows)] 239 | let signals = [("Ctrl-C", tokio::signal::windows::ctrl_c())]; 240 | 241 | for (signal_name, stream) in signals { 242 | let mut stream = match stream { 243 | Ok(signal) => signal, 244 | Err(e) => { 245 | tracing::warn!( 246 | "Failed to listen for {} signal: {}", 247 | signal_name, 248 | utils::pretty_error(&e.into()) 249 | ); 250 | continue; 251 | } 252 | }; 253 | let token = token.clone(); 254 | #[cfg(feature = "tui")] 255 | let tx = tx.clone(); 256 | tokio::spawn(async move { 257 | tokio::select! { 258 | biased; 259 | () = token.cancelled() => {}, 260 | _ = stream.recv() => { 261 | tracing::info!("Received {} signal, exiting...", signal_name); 262 | token.cancel(); 263 | #[cfg(feature = "tui")] 264 | drop(tx.send(event::Event::App(event::AppEvent::Quit))); 265 | }, 266 | } 267 | }); 268 | } 269 | } 270 | 271 | #[cfg(feature = "tui")] 272 | async fn run_with_tui( 273 | config: Arc, 274 | logging_filter: tracing_subscriber::filter::Targets, 275 | ) -> crate::Result<()> { 276 | tui_logger::init_logger(tui_logger::LevelFilter::Debug)?; 277 | tracing_subscriber::registry() 278 | .with(logging_filter) 279 | .with(tui_logger::TuiTracingSubscriberLayer) 280 | .init(); 281 | 282 | let terminal = ratatui::try_init()?; 283 | let terminal_guard = tui::RatatuiRestoreGuard; 284 | 285 | let token = tokio_util::sync::CancellationToken::new(); 286 | let (tx, rx) = tokio::sync::mpsc::unbounded_channel(); 287 | 288 | #[cfg(any(unix, windows))] 289 | watch_signals(&token, &tx); 290 | 291 | tokio::try_join!( 292 | main_task(config, token.clone(), tx.clone()), 293 | async move { 294 | let result = tui::run(terminal, token, tx, rx).await; 295 | drop(terminal_guard); 296 | result 297 | } 298 | )?; 299 | 300 | Ok(()) 301 | } 302 | 303 | #[cfg(not(feature = "tui"))] 304 | async fn run_without_tui( 305 | config: Arc, 306 | logging_filter: tracing_subscriber::filter::Targets, 307 | ) -> crate::Result<()> { 308 | tracing_subscriber::registry() 309 | .with(logging_filter) 310 | .with(tracing_subscriber::fmt::layer()) 311 | .init(); 312 | 313 | let token = tokio_util::sync::CancellationToken::new(); 314 | 315 | #[cfg(any(unix, windows))] 316 | watch_signals(&token); 317 | 318 | main_task(config, token).await 319 | } 320 | 321 | #[cfg_attr( 322 | feature = "tokio-multi-thread", 323 | tokio::main(flavor = "multi_thread") 324 | )] 325 | #[cfg_attr( 326 | not(feature = "tokio-multi-thread"), 327 | tokio::main(flavor = "current_thread") 328 | )] 329 | async fn main() -> crate::Result<()> { 330 | #[cfg(feature = "dhat")] 331 | let _profiler = dhat::Profiler::new_heap(); 332 | 333 | color_eyre::install()?; 334 | 335 | let config = config::load_config().await?; 336 | let logging_filter = create_logging_filter(&config); 337 | 338 | #[cfg(feature = "tui")] 339 | { 340 | run_with_tui(config, logging_filter).await 341 | } 342 | #[cfg(not(feature = "tui"))] 343 | { 344 | run_without_tui(config, logging_filter).await 345 | } 346 | } 347 | -------------------------------------------------------------------------------- /src/tui.rs: -------------------------------------------------------------------------------- 1 | #![expect( 2 | clippy::indexing_slicing, 3 | clippy::missing_asserts_for_indexing, 4 | clippy::wildcard_enum_match_arm 5 | )] 6 | 7 | use std::time::Duration; 8 | 9 | use crossterm::event::{ 10 | Event as CrosstermEvent, KeyCode, KeyModifiers, MouseEventKind, 11 | }; 12 | use futures::StreamExt as _; 13 | use ratatui::{ 14 | Frame, 15 | layout::{Alignment, Constraint, Direction, Layout}, 16 | style::{Color, Style}, 17 | text::{Line, Text}, 18 | widgets::{Block, Gauge}, 19 | }; 20 | use strum::EnumCount as _; 21 | use tui_logger::{TuiLoggerWidget, TuiWidgetEvent, TuiWidgetState}; 22 | 23 | use crate::{ 24 | HashMap, 25 | event::{AppEvent, Event}, 26 | ipdb, 27 | proxy::ProxyType, 28 | }; 29 | 30 | const FPS: f64 = 30.0; 31 | 32 | pub struct RatatuiRestoreGuard; 33 | impl Drop for RatatuiRestoreGuard { 34 | fn drop(&mut self) { 35 | ratatui::restore(); 36 | } 37 | } 38 | 39 | pub async fn run( 40 | mut terminal: ratatui::DefaultTerminal, 41 | token: tokio_util::sync::CancellationToken, 42 | tx: tokio::sync::mpsc::UnboundedSender, 43 | mut rx: tokio::sync::mpsc::UnboundedReceiver, 44 | ) -> crate::Result<()> { 45 | tokio::spawn(tick_event_listener(tx.clone())); 46 | tokio::spawn(crossterm_event_listener(tx)); 47 | 48 | let mut app_state = AppState::default(); 49 | let logger_state = TuiWidgetState::default(); 50 | 51 | while !matches!(app_state.mode, AppMode::Quit) { 52 | if let Some(event) = rx.recv().await { 53 | if handle_event(event, &mut app_state, &token, &logger_state) { 54 | terminal 55 | .draw(|frame| draw(frame, &app_state, &logger_state))?; 56 | } 57 | } else { 58 | break; 59 | } 60 | } 61 | Ok(()) 62 | } 63 | 64 | #[derive(Default)] 65 | pub enum AppMode { 66 | #[default] 67 | Running, 68 | Done, 69 | Quit, 70 | } 71 | 72 | impl AppMode { 73 | pub const fn next(&self) -> Self { 74 | match self { 75 | Self::Running => Self::Done, 76 | Self::Done | Self::Quit => Self::Quit, 77 | } 78 | } 79 | } 80 | 81 | #[derive(Default)] 82 | pub struct AppState { 83 | pub mode: AppMode, 84 | 85 | pub asn_db_total: u64, 86 | pub asn_db_downloaded: usize, 87 | 88 | pub geo_db_total: u64, 89 | pub geo_db_downloaded: usize, 90 | 91 | pub sources_total: HashMap, 92 | pub sources_scraped: HashMap, 93 | 94 | pub proxies_total: HashMap, 95 | pub proxies_checked: HashMap, 96 | pub proxies_working: HashMap, 97 | } 98 | 99 | async fn tick_event_listener(tx: tokio::sync::mpsc::UnboundedSender) { 100 | let mut tick = tokio::time::interval(Duration::from_secs_f64(1.0 / FPS)); 101 | loop { 102 | tokio::select! { 103 | biased; 104 | () = tx.closed() => { 105 | break; 106 | }, 107 | _ = tick.tick() => { 108 | if tx.send(Event::Tick).is_err() { 109 | break; 110 | } 111 | }, 112 | } 113 | } 114 | } 115 | 116 | async fn crossterm_event_listener( 117 | tx: tokio::sync::mpsc::UnboundedSender, 118 | ) { 119 | let mut reader = crossterm::event::EventStream::new(); 120 | loop { 121 | tokio::select! { 122 | biased; 123 | () = tx.closed() => { 124 | break; 125 | }, 126 | maybe = reader.next() => { 127 | match maybe { 128 | Some(Ok(event)) => { 129 | if tx.send(Event::Crossterm(event)).is_err() { 130 | break; 131 | } 132 | }, 133 | Some(Err(_)) => {}, 134 | None => { 135 | break; 136 | } 137 | } 138 | }, 139 | } 140 | } 141 | } 142 | 143 | fn draw(f: &mut Frame<'_>, state: &AppState, logger_state: &TuiWidgetState) { 144 | let outer_block = Block::default() 145 | .title("https://github.com/monosans/proxy-scraper-checker") 146 | .title_alignment(Alignment::Center); 147 | f.render_widget(outer_block.clone(), f.area()); 148 | let outer_layout = Layout::default() 149 | .direction(Direction::Vertical) 150 | .constraints([ 151 | // Logs 152 | Constraint::Fill(1), 153 | // IP database download 154 | Constraint::Length(3), 155 | // Scraping and checking 156 | Constraint::Length(1 + (3 * 3) + 1), 157 | // Hotkeys 158 | Constraint::Length(4), 159 | ]) 160 | .split(outer_block.inner(f.area())); 161 | drop(outer_block); 162 | 163 | f.render_widget( 164 | TuiLoggerWidget::default() 165 | .state(logger_state) 166 | .block(Block::bordered().title("Logs")) 167 | .output_file(false) 168 | .output_line(false) 169 | .style_trace(Style::default().fg(Color::Magenta)) 170 | .style_debug(Style::default().fg(Color::Green)) 171 | .style_info(Style::default().fg(Color::Cyan)) 172 | .style_warn(Style::default().fg(Color::Yellow)) 173 | .style_error(Style::default().fg(Color::Red)), 174 | outer_layout[0], 175 | ); 176 | 177 | let ipdb_layout = Layout::default() 178 | .direction(Direction::Horizontal) 179 | .constraints([Constraint::Fill(1); 2]) 180 | .split(outer_layout[1]); 181 | f.render_widget( 182 | Gauge::default() 183 | .block(Block::bordered().title("ASN database download")) 184 | .ratio({ 185 | if state.asn_db_total == 0 { 186 | 1.0 187 | } else { 188 | (state.asn_db_downloaded as f64) 189 | / (state.asn_db_total as f64) 190 | } 191 | }), 192 | ipdb_layout[0], 193 | ); 194 | f.render_widget( 195 | Gauge::default() 196 | .block(Block::bordered().title("Geolocation database download")) 197 | .ratio({ 198 | if state.geo_db_total == 0 { 199 | 1.0 200 | } else { 201 | (state.geo_db_downloaded as f64) 202 | / (state.geo_db_total as f64) 203 | } 204 | }), 205 | ipdb_layout[1], 206 | ); 207 | drop(ipdb_layout); 208 | 209 | let mut proxy_types: smallvec::SmallVec<[_; ProxyType::COUNT]> = 210 | state.sources_total.keys().collect(); 211 | proxy_types.sort_unstable(); 212 | 213 | let proxies_layout = Layout::default() 214 | .direction(Direction::Horizontal) 215 | .constraints(proxy_types.iter().map(|_| Constraint::Fill(1))) 216 | .split(outer_layout[2]); 217 | 218 | for (i, proxy_type) in proxy_types.into_iter().enumerate() { 219 | let block = Block::bordered().title(proxy_type.as_str().to_uppercase()); 220 | f.render_widget(block.clone(), proxies_layout[i]); 221 | 222 | let layout = Layout::default() 223 | .direction(Direction::Vertical) 224 | .constraints([Constraint::Fill(1); 3]) 225 | .split(block.inner(proxies_layout[i])); 226 | drop(block); 227 | 228 | let sources_scraped = 229 | state.sources_scraped.get(proxy_type).copied().unwrap_or_default(); 230 | let sources_total = 231 | state.sources_total.get(proxy_type).copied().unwrap_or_default(); 232 | 233 | f.render_widget( 234 | Gauge::default() 235 | .ratio({ 236 | if sources_total == 0 { 237 | 1.0 238 | } else { 239 | (sources_scraped as f64) / (sources_total as f64) 240 | } 241 | }) 242 | .block(Block::bordered().title("Scraping sources")) 243 | .label(format!("{sources_scraped}/{sources_total}")), 244 | layout[0], 245 | ); 246 | 247 | let proxies_total = 248 | state.proxies_total.get(proxy_type).copied().unwrap_or_default(); 249 | let proxies_checked = 250 | state.proxies_checked.get(proxy_type).copied().unwrap_or_default(); 251 | f.render_widget( 252 | Gauge::default() 253 | .ratio({ 254 | if proxies_total == 0 { 255 | 1.0 256 | } else { 257 | (proxies_checked as f64) / (proxies_total as f64) 258 | } 259 | }) 260 | .block(Block::bordered().title("Checking proxies")) 261 | .label(format!("{proxies_checked}/{proxies_total}")), 262 | layout[1], 263 | ); 264 | 265 | let working_proxies_block = Block::bordered().title("Working proxies"); 266 | f.render_widget(working_proxies_block.clone(), layout[2]); 267 | 268 | let proxies_working = 269 | state.proxies_working.get(proxy_type).copied().unwrap_or_default(); 270 | f.render_widget( 271 | Line::from(format!("{} ({:.1}%)", proxies_working, { 272 | if proxies_checked == 0 { 273 | 0.0_f64 274 | } else { 275 | (proxies_working as f64) / (proxies_checked as f64) 276 | * 100.0_f64 277 | } 278 | })) 279 | .alignment(Alignment::Center), 280 | working_proxies_block.inner(layout[2]), 281 | ); 282 | } 283 | 284 | drop(proxies_layout); 285 | 286 | let running = matches!(state.mode, AppMode::Running); 287 | let mut lines = Vec::with_capacity(if running { 4 } else { 3 }); 288 | lines.push(Line::from("Up / PageUp / k - scroll logs up")); 289 | lines.push(Line::from("Down / PageDown / j - scroll logs down")); 290 | if running { 291 | lines.push( 292 | Line::from("ESC / q - stop") 293 | .style(Style::default().fg(Color::Yellow)), 294 | ); 295 | } 296 | lines.push( 297 | Line::from(if running { 298 | "Ctrl-C - quit" 299 | } else { 300 | "ESC / q / Ctrl-C - quit" 301 | }) 302 | .style(Style::default().fg(Color::Red)), 303 | ); 304 | 305 | f.render_widget(Text::from(lines).centered(), outer_layout[3]); 306 | } 307 | 308 | fn handle_event( 309 | event: Event, 310 | state: &mut AppState, 311 | token: &tokio_util::sync::CancellationToken, 312 | logger_state: &TuiWidgetState, 313 | ) -> bool { 314 | match event { 315 | Event::Tick => true, 316 | Event::Crossterm(crossterm_event) => { 317 | match crossterm_event { 318 | CrosstermEvent::Key(key_event) => match key_event.code { 319 | KeyCode::Esc | KeyCode::Char('q' | 'Q') => { 320 | state.mode = state.mode.next(); 321 | token.cancel(); 322 | } 323 | KeyCode::Char('c' | 'C') 324 | if key_event.modifiers == KeyModifiers::CONTROL => 325 | { 326 | state.mode = AppMode::Quit; 327 | token.cancel(); 328 | } 329 | KeyCode::Up | KeyCode::PageUp | KeyCode::Char('k') => { 330 | logger_state.transition(TuiWidgetEvent::PrevPageKey); 331 | } 332 | KeyCode::Down | KeyCode::PageDown | KeyCode::Char('j') => { 333 | logger_state.transition(TuiWidgetEvent::NextPageKey); 334 | } 335 | _ => {} 336 | }, 337 | CrosstermEvent::Mouse(mouse_event) => match mouse_event.kind { 338 | MouseEventKind::ScrollUp => { 339 | logger_state.transition(TuiWidgetEvent::PrevPageKey); 340 | } 341 | MouseEventKind::ScrollDown => { 342 | logger_state.transition(TuiWidgetEvent::NextPageKey); 343 | } 344 | _ => {} 345 | }, 346 | _ => {} 347 | } 348 | false 349 | } 350 | Event::App(app_event) => { 351 | match app_event { 352 | AppEvent::IpDbTotal(ipdb::DbType::Asn, bytes) => { 353 | state.asn_db_total = bytes.unwrap_or_default(); 354 | } 355 | AppEvent::IpDbTotal(ipdb::DbType::Geo, bytes) => { 356 | state.geo_db_total = bytes.unwrap_or_default(); 357 | } 358 | AppEvent::IpDbDownloaded(ipdb::DbType::Asn, bytes) => { 359 | state.asn_db_downloaded = 360 | state.asn_db_downloaded.saturating_add(bytes); 361 | } 362 | AppEvent::IpDbDownloaded(ipdb::DbType::Geo, bytes) => { 363 | state.geo_db_downloaded = 364 | state.geo_db_downloaded.saturating_add(bytes); 365 | } 366 | AppEvent::SourcesTotal(proxy_type, amount) => { 367 | state.sources_total.insert(proxy_type, amount); 368 | } 369 | AppEvent::SourceScraped(proxy_type) => { 370 | state 371 | .sources_scraped 372 | .entry(proxy_type) 373 | .and_modify(|c| *c = c.saturating_add(1)) 374 | .or_insert(1); 375 | } 376 | AppEvent::TotalProxies(proxy_type, amount) => { 377 | state.proxies_total.insert(proxy_type, amount); 378 | } 379 | AppEvent::ProxyChecked(proxy_type) => { 380 | state 381 | .proxies_checked 382 | .entry(proxy_type) 383 | .and_modify(|c| *c = c.saturating_add(1)) 384 | .or_insert(1); 385 | } 386 | AppEvent::ProxyWorking(proxy_type) => { 387 | state 388 | .proxies_working 389 | .entry(proxy_type) 390 | .and_modify(|c| *c = c.saturating_add(1)) 391 | .or_insert(1); 392 | } 393 | AppEvent::Done => { 394 | if matches!(state.mode, AppMode::Running) { 395 | state.mode = AppMode::Done; 396 | } 397 | } 398 | AppEvent::Quit => { 399 | state.mode = AppMode::Quit; 400 | } 401 | } 402 | false 403 | } 404 | } 405 | } 406 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | workflow_dispatch: 8 | concurrency: 9 | group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} 10 | cancel-in-progress: true 11 | jobs: 12 | check: 13 | if: ${{ always() && github.event_name == 'pull_request' }} 14 | needs: 15 | - build 16 | - clippy 17 | - pre-commit 18 | - rustfmt 19 | - udeps 20 | runs-on: ubuntu-24.04 21 | steps: 22 | - uses: re-actors/alls-green@release/v1 23 | with: 24 | jobs: ${{ toJSON(needs) }} 25 | build: 26 | strategy: 27 | matrix: 28 | include: 29 | - target: aarch64-apple-darwin 30 | runner: macos-15 31 | cross: false 32 | - target: aarch64-pc-windows-msvc 33 | runner: windows-11-arm 34 | cross: false 35 | executable: proxy-scraper-checker.exe 36 | - target: aarch64-unknown-linux-gnu 37 | runner: ubuntu-24.04-arm 38 | cross: false 39 | - target: i686-pc-windows-msvc 40 | runner: windows-2025 41 | cross: false 42 | executable: proxy-scraper-checker.exe 43 | - target: x86_64-apple-darwin 44 | runner: macos-15 45 | cross: false 46 | - target: x86_64-pc-windows-msvc 47 | runner: windows-2025 48 | cross: false 49 | executable: proxy-scraper-checker.exe 50 | - target: x86_64-unknown-linux-gnu 51 | runner: ubuntu-24.04 52 | cross: false 53 | - target: aarch64-linux-android 54 | runner: ubuntu-24.04 55 | cross: true 56 | # - target: aarch64-unknown-linux-gnu 57 | # runner: ubuntu-24.04 58 | # cross: true 59 | # - target: aarch64-unknown-linux-gnu:centos 60 | # runner: ubuntu-24.04 61 | # cross: true 62 | - target: aarch64-unknown-linux-musl 63 | runner: ubuntu-24.04 64 | cross: true 65 | - target: arm-linux-androideabi 66 | runner: ubuntu-24.04 67 | cross: true 68 | - target: arm-unknown-linux-gnueabi 69 | runner: ubuntu-24.04 70 | cross: true 71 | - target: arm-unknown-linux-gnueabihf 72 | runner: ubuntu-24.04 73 | cross: true 74 | - target: arm-unknown-linux-musleabi 75 | runner: ubuntu-24.04 76 | cross: true 77 | - target: arm-unknown-linux-musleabihf 78 | runner: ubuntu-24.04 79 | cross: true 80 | - target: armv5te-unknown-linux-gnueabi 81 | runner: ubuntu-24.04 82 | cross: true 83 | - target: armv5te-unknown-linux-musleabi 84 | runner: ubuntu-24.04 85 | cross: true 86 | - target: armv7-linux-androideabi 87 | runner: ubuntu-24.04 88 | cross: true 89 | - target: armv7-unknown-linux-gnueabi 90 | runner: ubuntu-24.04 91 | cross: true 92 | - target: armv7-unknown-linux-gnueabihf 93 | runner: ubuntu-24.04 94 | cross: true 95 | - target: armv7-unknown-linux-musleabi 96 | runner: ubuntu-24.04 97 | cross: true 98 | - target: armv7-unknown-linux-musleabihf 99 | runner: ubuntu-24.04 100 | cross: true 101 | # - target: i586-unknown-linux-gnu 102 | # runner: ubuntu-24.04 103 | # cross: true 104 | # - target: i586-unknown-linux-musl 105 | # runner: ubuntu-24.04 106 | # cross: true 107 | - target: i686-unknown-freebsd 108 | runner: ubuntu-24.04 109 | cross: true 110 | - target: i686-linux-android 111 | runner: ubuntu-24.04 112 | cross: true 113 | # - target: i686-pc-windows-gnu 114 | # runner: ubuntu-24.04 115 | # cross: true 116 | - target: i686-unknown-linux-gnu 117 | runner: ubuntu-24.04 118 | cross: true 119 | - target: loongarch64-unknown-linux-gnu 120 | runner: ubuntu-24.04 121 | cross: true 122 | - target: loongarch64-unknown-linux-musl 123 | runner: ubuntu-24.04 124 | cross: true 125 | # - target: mips-unknown-linux-gnu 126 | # runner: ubuntu-24.04 127 | # cross: true 128 | # - target: mips-unknown-linux-musl 129 | # runner: ubuntu-24.04 130 | # cross: true 131 | # - target: mips64-unknown-linux-gnuabi64 132 | # runner: ubuntu-24.04 133 | # cross: true 134 | # - target: mips64-unknown-linux-muslabi64 135 | # runner: ubuntu-24.04 136 | # cross: true 137 | # - target: mips64el-unknown-linux-gnuabi64 138 | # runner: ubuntu-24.04 139 | # cross: true 140 | # - target: mips64el-unknown-linux-muslabi64 141 | # runner: ubuntu-24.04 142 | # cross: true 143 | # - target: mipsel-unknown-linux-gnu 144 | # runner: ubuntu-24.04 145 | # cross: true 146 | # - target: mipsel-unknown-linux-musl 147 | # runner: ubuntu-24.04 148 | # cross: true 149 | - target: powerpc-unknown-linux-gnu 150 | runner: ubuntu-24.04 151 | cross: true 152 | - target: powerpc64-unknown-linux-gnu 153 | runner: ubuntu-24.04 154 | cross: true 155 | - target: powerpc64le-unknown-linux-gnu 156 | runner: ubuntu-24.04 157 | cross: true 158 | - target: riscv64gc-unknown-linux-gnu 159 | runner: ubuntu-24.04 160 | cross: true 161 | - target: riscv64gc-unknown-linux-musl 162 | runner: ubuntu-24.04 163 | cross: true 164 | - target: s390x-unknown-linux-gnu 165 | runner: ubuntu-24.04 166 | cross: true 167 | - target: sparc64-unknown-linux-gnu 168 | runner: ubuntu-24.04 169 | cross: true 170 | # - target: sparcv9-sun-solaris 171 | # runner: ubuntu-24.04 172 | # cross: true 173 | # - target: thumbv6m-none-eabi 174 | # runner: ubuntu-24.04 175 | # cross: true 176 | # - target: thumbv7em-none-eabi 177 | # runner: ubuntu-24.04 178 | # cross: true 179 | # - target: thumbv7em-none-eabihf 180 | # runner: ubuntu-24.04 181 | # cross: true 182 | # - target: thumbv7m-none-eabi 183 | # runner: ubuntu-24.04 184 | # cross: true 185 | - target: thumbv7neon-linux-androideabi 186 | runner: ubuntu-24.04 187 | cross: true 188 | - target: thumbv7neon-unknown-linux-gnueabihf 189 | runner: ubuntu-24.04 190 | cross: true 191 | # - target: thumbv8m.base-none-eabi 192 | # runner: ubuntu-24.04 193 | # cross: true 194 | # - target: thumbv8m.main-none-eabi 195 | # runner: ubuntu-24.04 196 | # cross: true 197 | # - target: thumbv8m.main-none-eabihf 198 | # runner: ubuntu-24.04 199 | # cross: true 200 | # - target: wasm32-unknown-emscripten 201 | # runner: ubuntu-24.04 202 | # cross: true 203 | - target: x86_64-linux-android 204 | runner: ubuntu-24.04 205 | cross: true 206 | # - target: x86_64-pc-windows-gnu 207 | # runner: ubuntu-24.04 208 | # cross: true 209 | # - target: x86_64-pc-solaris 210 | # runner: ubuntu-24.04 211 | # cross: true 212 | - target: x86_64-unknown-freebsd 213 | runner: ubuntu-24.04 214 | cross: true 215 | # - target: x86_64-unknown-dragonfly 216 | # runner: ubuntu-24.04 217 | # cross: true 218 | - target: x86_64-unknown-illumos 219 | runner: ubuntu-24.04 220 | cross: true 221 | # - target: x86_64-unknown-linux-gnu 222 | # runner: ubuntu-24.04 223 | # cross: true 224 | # - target: x86_64-unknown-linux-gnu:centos 225 | # runner: ubuntu-24.04 226 | # cross: true 227 | - target: x86_64-unknown-linux-musl 228 | runner: ubuntu-24.04 229 | cross: true 230 | # - target: x86_64-unknown-netbsd 231 | # runner: ubuntu-24.04 232 | # cross: true 233 | fail-fast: false 234 | runs-on: ${{ matrix.runner }} 235 | steps: 236 | - uses: actions/checkout@v6 237 | with: 238 | persist-credentials: false 239 | - uses: actions-rust-lang/setup-rust-toolchain@v1 240 | with: 241 | toolchain: beta 242 | target: ${{ matrix.target }} 243 | cache-key: ${{ matrix.target }} 244 | - if: ${{ matrix.cross }} 245 | run: cargo +beta install cross --git https://github.com/cross-rs/cross 246 | env: 247 | RUSTFLAGS: "" 248 | - if: ${{ matrix.cross }} 249 | run: cross +beta build --features tui --release --target '${{ matrix.target }}' --locked 250 | - if: ${{ !matrix.cross }} 251 | run: cargo +beta build --features tui --release --target '${{ matrix.target }}' --locked 252 | - run: mkdir dist 253 | - run: echo -n '${{ github.sha }}' > dist/commit-sha.txt 254 | shell: bash 255 | - run: mv config.toml dist/ 256 | - run: mv LICENSE dist/ 257 | - run: mv target/${{ matrix.target}}/release/${{ matrix.executable || 'proxy-scraper-checker' }} dist/ 258 | - uses: actions/upload-artifact@v6 259 | with: 260 | name: proxy-scraper-checker-binary-${{ matrix.target }} 261 | path: dist/ 262 | if-no-files-found: error 263 | build-docker: 264 | if: ${{ github.event_name != 'pull_request' }} 265 | permissions: 266 | packages: write 267 | runs-on: ${{ matrix.runner }} 268 | strategy: 269 | matrix: 270 | include: 271 | - platform: "linux/386" 272 | tag: "386" 273 | runner: ubuntu-24.04 274 | - platform: "linux/amd64" 275 | tag: "amd64" 276 | runner: ubuntu-24.04 277 | - platform: "linux/arm/v7" 278 | tag: "arm-v7" 279 | runner: ubuntu-24.04-arm 280 | - platform: "linux/arm64/v8" 281 | tag: "arm64-v8" 282 | runner: ubuntu-24.04-arm 283 | steps: 284 | - uses: actions/checkout@v6 285 | with: 286 | persist-credentials: false 287 | - id: meta 288 | uses: docker/metadata-action@v5 289 | with: 290 | images: ghcr.io/${{ github.repository }} 291 | tags: type=raw,value=${{ github.sha }}-${{ matrix.tag }} 292 | - uses: docker/login-action@v3 293 | with: 294 | registry: ghcr.io 295 | username: ${{ github.actor }} 296 | password: ${{ secrets.GITHUB_TOKEN }} 297 | - uses: docker/setup-buildx-action@v3 298 | - id: push 299 | env: 300 | DOCKER_BUILD_RECORD_UPLOAD: false 301 | uses: docker/build-push-action@v6 302 | with: 303 | cache-from: type=gha 304 | cache-to: type=gha,mode=max 305 | labels: ${{ steps.meta.outputs.labels }} 306 | platforms: ${{ matrix.platform }} 307 | provenance: false 308 | push: true 309 | tags: ${{ steps.meta.outputs.tags }} 310 | upload-docker-manifest: 311 | needs: 312 | - build-docker 313 | runs-on: ubuntu-24.04 314 | permissions: 315 | packages: write 316 | steps: 317 | - uses: docker/login-action@v3 318 | with: 319 | registry: ghcr.io 320 | username: ${{ github.actor }} 321 | password: ${{ secrets.GITHUB_TOKEN }} 322 | - run: > 323 | docker manifest create ghcr.io/${{ github.repository }}:${{ github.sha }} 324 | --amend ghcr.io/${{ github.repository }}:${{ github.sha }}-386 325 | --amend ghcr.io/${{ github.repository }}:${{ github.sha }}-amd64 326 | --amend ghcr.io/${{ github.repository }}:${{ github.sha }}-arm-v7 327 | --amend ghcr.io/${{ github.repository }}:${{ github.sha }}-arm64-v8 328 | - run: docker manifest push ghcr.io/${{ github.repository }}:${{ github.sha }} 329 | upload-docker-prebuilt: 330 | needs: 331 | - upload-docker-manifest 332 | runs-on: ubuntu-24.04 333 | strategy: 334 | matrix: 335 | platform: 336 | - "386" 337 | - "amd64" 338 | - "arm-v7" 339 | - "arm64-v8" 340 | steps: 341 | - uses: actions/checkout@v6 342 | with: 343 | persist-credentials: false 344 | - run: mkdir -p dist 345 | - run: echo -n '${{ github.sha }}' > dist/commit-sha.txt 346 | - run: mv {out,compose.yaml,config.toml,LICENSE} dist/ 347 | - name: Create Dockerfile 348 | run: | 349 | cat << 'EOF' > dist/Dockerfile 350 | # syntax=docker.io/docker/dockerfile:1 351 | 352 | FROM ghcr.io/${{ github.repository }}:${{ github.sha }} 353 | 354 | ARG \ 355 | UID=1000 \ 356 | GID=1000 357 | 358 | USER root 359 | 360 | RUN (getent group "${GID}" || groupmod --gid "${GID}" app) \ 361 | && usermod --uid "${UID}" --gid "${GID}" app 362 | 363 | USER app 364 | EOF 365 | - uses: actions/upload-artifact@v6 366 | with: 367 | name: proxy-scraper-checker-docker-${{ matrix.platform }} 368 | path: dist/ 369 | if-no-files-found: error 370 | include-hidden-files: true 371 | upload-docker-src: 372 | needs: 373 | - build-docker 374 | strategy: 375 | matrix: 376 | platform: 377 | - "ppc64le" 378 | - "riscv64" 379 | - "s390x" 380 | runs-on: ubuntu-24.04 381 | steps: 382 | - uses: actions/checkout@v6 383 | with: 384 | persist-credentials: false 385 | - run: mkdir -p dist 386 | - run: echo -n '${{ github.sha }}' > dist/commit-sha.txt 387 | - run: mv {out,src,Cargo.lock,Cargo.toml,compose.yaml,config.toml,Dockerfile,LICENSE} dist/ 388 | - uses: actions/upload-artifact@v6 389 | with: 390 | name: proxy-scraper-checker-docker-${{ matrix.platform }} 391 | path: dist/ 392 | if-no-files-found: error 393 | include-hidden-files: true 394 | clippy: 395 | runs-on: ubuntu-24.04 396 | strategy: 397 | matrix: 398 | include: 399 | - features: "--features tui" 400 | target: aarch64-apple-darwin,aarch64-pc-windows-msvc,aarch64-unknown-linux-gnu,i686-pc-windows-msvc,x86_64-apple-darwin,x86_64-pc-windows-msvc,x86_64-unknown-linux-gnu,aarch64-linux-android,aarch64-unknown-linux-musl,arm-linux-androideabi,arm-unknown-linux-gnueabi,arm-unknown-linux-gnueabihf,arm-unknown-linux-musleabi,arm-unknown-linux-musleabihf,armv5te-unknown-linux-gnueabi,armv5te-unknown-linux-musleabi,armv7-linux-androideabi,armv7-unknown-linux-gnueabi,armv7-unknown-linux-gnueabihf,armv7-unknown-linux-musleabi,armv7-unknown-linux-musleabihf,i686-unknown-freebsd,i686-linux-android,i686-unknown-linux-gnu,loongarch64-unknown-linux-gnu,loongarch64-unknown-linux-musl,powerpc-unknown-linux-gnu,powerpc64-unknown-linux-gnu,powerpc64le-unknown-linux-gnu,riscv64gc-unknown-linux-gnu,riscv64gc-unknown-linux-musl,s390x-unknown-linux-gnu,sparc64-unknown-linux-gnu,thumbv7neon-linux-androideabi,thumbv7neon-unknown-linux-gnueabihf,x86_64-linux-android,x86_64-unknown-freebsd,x86_64-unknown-illumos,x86_64-unknown-linux-musl,x86_64-unknown-netbsd 401 | - features: "" 402 | target: aarch64-unknown-linux-gnu,x86_64-unknown-linux-gnu,armv7-unknown-linux-gnueabihf,i686-unknown-linux-gnu,powerpc64le-unknown-linux-gnu,s390x-unknown-linux-gnu 403 | fail-fast: false 404 | steps: 405 | - uses: actions/checkout@v6 406 | with: 407 | persist-credentials: false 408 | - uses: actions-rust-lang/setup-rust-toolchain@v1 409 | with: 410 | toolchain: beta 411 | target: ${{ matrix.target }} 412 | components: clippy 413 | cache-key: x86_64-unknown-linux-gnu 414 | - run: cargo +beta clippy ${{ matrix.features }} 415 | pre-commit: 416 | runs-on: ubuntu-24.04 417 | steps: 418 | - uses: actions/checkout@v6 419 | with: 420 | persist-credentials: false 421 | - uses: astral-sh/setup-uv@v7 422 | with: 423 | enable-cache: false 424 | - run: uv tool run --no-cache --python 3.14 prek run --all-files --show-diff-on-failure 425 | env: 426 | RUFF_OUTPUT_FORMAT: github 427 | rustfmt: 428 | runs-on: ubuntu-24.04 429 | steps: 430 | - uses: actions/checkout@v6 431 | with: 432 | persist-credentials: false 433 | - uses: actions-rust-lang/setup-rust-toolchain@v1 434 | with: 435 | toolchain: nightly 436 | components: rustfmt 437 | cache-key: x86_64-unknown-linux-gnu 438 | - run: cargo +nightly fmt --check 439 | udeps: 440 | runs-on: ubuntu-24.04 441 | strategy: 442 | matrix: 443 | features: 444 | - "" 445 | - "--features tui" 446 | steps: 447 | - uses: actions/checkout@v6 448 | with: 449 | persist-credentials: false 450 | - uses: actions-rust-lang/setup-rust-toolchain@v1 451 | with: 452 | toolchain: nightly 453 | cache-key: x86_64-unknown-linux-gnu 454 | - run: cargo +nightly install --locked cargo-udeps 455 | - run: cargo +nightly udeps ${{ matrix.features }} 456 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "addr2line" 7 | version = "0.25.1" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" 10 | dependencies = [ 11 | "gimli", 12 | ] 13 | 14 | [[package]] 15 | name = "adler2" 16 | version = "2.0.1" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" 19 | 20 | [[package]] 21 | name = "aho-corasick" 22 | version = "1.1.4" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" 25 | dependencies = [ 26 | "memchr", 27 | ] 28 | 29 | [[package]] 30 | name = "alloc-no-stdlib" 31 | version = "2.0.4" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" 34 | 35 | [[package]] 36 | name = "alloc-stdlib" 37 | version = "0.2.2" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" 40 | dependencies = [ 41 | "alloc-no-stdlib", 42 | ] 43 | 44 | [[package]] 45 | name = "allocator-api2" 46 | version = "0.2.21" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" 49 | 50 | [[package]] 51 | name = "android_system_properties" 52 | version = "0.1.5" 53 | source = "registry+https://github.com/rust-lang/crates.io-index" 54 | checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" 55 | dependencies = [ 56 | "libc", 57 | ] 58 | 59 | [[package]] 60 | name = "anyhow" 61 | version = "1.0.100" 62 | source = "registry+https://github.com/rust-lang/crates.io-index" 63 | checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" 64 | 65 | [[package]] 66 | name = "async-compression" 67 | version = "0.4.36" 68 | source = "registry+https://github.com/rust-lang/crates.io-index" 69 | checksum = "98ec5f6c2f8bc326c994cb9e241cc257ddaba9afa8555a43cffbb5dd86efaa37" 70 | dependencies = [ 71 | "compression-codecs", 72 | "compression-core", 73 | "futures-core", 74 | "pin-project-lite", 75 | "tokio", 76 | ] 77 | 78 | [[package]] 79 | name = "async-trait" 80 | version = "0.1.89" 81 | source = "registry+https://github.com/rust-lang/crates.io-index" 82 | checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" 83 | dependencies = [ 84 | "proc-macro2", 85 | "quote", 86 | "syn", 87 | ] 88 | 89 | [[package]] 90 | name = "atomic-waker" 91 | version = "1.1.2" 92 | source = "registry+https://github.com/rust-lang/crates.io-index" 93 | checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" 94 | 95 | [[package]] 96 | name = "autocfg" 97 | version = "1.5.0" 98 | source = "registry+https://github.com/rust-lang/crates.io-index" 99 | checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" 100 | 101 | [[package]] 102 | name = "backtrace" 103 | version = "0.3.76" 104 | source = "registry+https://github.com/rust-lang/crates.io-index" 105 | checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" 106 | dependencies = [ 107 | "addr2line", 108 | "cfg-if", 109 | "libc", 110 | "miniz_oxide", 111 | "object", 112 | "rustc-demangle", 113 | "windows-link", 114 | ] 115 | 116 | [[package]] 117 | name = "base64" 118 | version = "0.22.1" 119 | source = "registry+https://github.com/rust-lang/crates.io-index" 120 | checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" 121 | 122 | [[package]] 123 | name = "bit-set" 124 | version = "0.8.0" 125 | source = "registry+https://github.com/rust-lang/crates.io-index" 126 | checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" 127 | dependencies = [ 128 | "bit-vec", 129 | ] 130 | 131 | [[package]] 132 | name = "bit-vec" 133 | version = "0.8.0" 134 | source = "registry+https://github.com/rust-lang/crates.io-index" 135 | checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" 136 | 137 | [[package]] 138 | name = "bitflags" 139 | version = "2.10.0" 140 | source = "registry+https://github.com/rust-lang/crates.io-index" 141 | checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" 142 | 143 | [[package]] 144 | name = "brotli" 145 | version = "8.0.2" 146 | source = "registry+https://github.com/rust-lang/crates.io-index" 147 | checksum = "4bd8b9603c7aa97359dbd97ecf258968c95f3adddd6db2f7e7a5bef101c84560" 148 | dependencies = [ 149 | "alloc-no-stdlib", 150 | "alloc-stdlib", 151 | "brotli-decompressor", 152 | ] 153 | 154 | [[package]] 155 | name = "brotli-decompressor" 156 | version = "5.0.0" 157 | source = "registry+https://github.com/rust-lang/crates.io-index" 158 | checksum = "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03" 159 | dependencies = [ 160 | "alloc-no-stdlib", 161 | "alloc-stdlib", 162 | ] 163 | 164 | [[package]] 165 | name = "bumpalo" 166 | version = "3.19.0" 167 | source = "registry+https://github.com/rust-lang/crates.io-index" 168 | checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" 169 | 170 | [[package]] 171 | name = "bytes" 172 | version = "1.11.0" 173 | source = "registry+https://github.com/rust-lang/crates.io-index" 174 | checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" 175 | 176 | [[package]] 177 | name = "cassowary" 178 | version = "0.3.0" 179 | source = "registry+https://github.com/rust-lang/crates.io-index" 180 | checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" 181 | 182 | [[package]] 183 | name = "castaway" 184 | version = "0.2.4" 185 | source = "registry+https://github.com/rust-lang/crates.io-index" 186 | checksum = "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a" 187 | dependencies = [ 188 | "rustversion", 189 | ] 190 | 191 | [[package]] 192 | name = "cc" 193 | version = "1.2.49" 194 | source = "registry+https://github.com/rust-lang/crates.io-index" 195 | checksum = "90583009037521a116abf44494efecd645ba48b6622457080f080b85544e2215" 196 | dependencies = [ 197 | "find-msvc-tools", 198 | "shlex", 199 | ] 200 | 201 | [[package]] 202 | name = "cfg-if" 203 | version = "1.0.4" 204 | source = "registry+https://github.com/rust-lang/crates.io-index" 205 | checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" 206 | 207 | [[package]] 208 | name = "cfg_aliases" 209 | version = "0.2.1" 210 | source = "registry+https://github.com/rust-lang/crates.io-index" 211 | checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" 212 | 213 | [[package]] 214 | name = "chrono" 215 | version = "0.4.42" 216 | source = "registry+https://github.com/rust-lang/crates.io-index" 217 | checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" 218 | dependencies = [ 219 | "iana-time-zone", 220 | "num-traits", 221 | "windows-link", 222 | ] 223 | 224 | [[package]] 225 | name = "color-eyre" 226 | version = "0.6.5" 227 | source = "registry+https://github.com/rust-lang/crates.io-index" 228 | checksum = "e5920befb47832a6d61ee3a3a846565cfa39b331331e68a3b1d1116630f2f26d" 229 | dependencies = [ 230 | "backtrace", 231 | "color-spantrace", 232 | "eyre", 233 | "indenter", 234 | "once_cell", 235 | "owo-colors", 236 | "tracing-error", 237 | ] 238 | 239 | [[package]] 240 | name = "color-spantrace" 241 | version = "0.3.0" 242 | source = "registry+https://github.com/rust-lang/crates.io-index" 243 | checksum = "b8b88ea9df13354b55bc7234ebcce36e6ef896aca2e42a15de9e10edce01b427" 244 | dependencies = [ 245 | "once_cell", 246 | "owo-colors", 247 | "tracing-core", 248 | "tracing-error", 249 | ] 250 | 251 | [[package]] 252 | name = "compact_str" 253 | version = "0.8.1" 254 | source = "registry+https://github.com/rust-lang/crates.io-index" 255 | checksum = "3b79c4069c6cad78e2e0cdfcbd26275770669fb39fd308a752dc110e83b9af32" 256 | dependencies = [ 257 | "castaway", 258 | "cfg-if", 259 | "itoa", 260 | "rustversion", 261 | "ryu", 262 | "static_assertions", 263 | ] 264 | 265 | [[package]] 266 | name = "compression-codecs" 267 | version = "0.4.35" 268 | source = "registry+https://github.com/rust-lang/crates.io-index" 269 | checksum = "b0f7ac3e5b97fdce45e8922fb05cae2c37f7bbd63d30dd94821dacfd8f3f2bf2" 270 | dependencies = [ 271 | "brotli", 272 | "compression-core", 273 | "flate2", 274 | "memchr", 275 | ] 276 | 277 | [[package]] 278 | name = "compression-core" 279 | version = "0.4.31" 280 | source = "registry+https://github.com/rust-lang/crates.io-index" 281 | checksum = "75984efb6ed102a0d42db99afb6c1948f0380d1d91808d5529916e6c08b49d8d" 282 | 283 | [[package]] 284 | name = "core-foundation" 285 | version = "0.9.4" 286 | source = "registry+https://github.com/rust-lang/crates.io-index" 287 | checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" 288 | dependencies = [ 289 | "core-foundation-sys", 290 | "libc", 291 | ] 292 | 293 | [[package]] 294 | name = "core-foundation-sys" 295 | version = "0.8.7" 296 | source = "registry+https://github.com/rust-lang/crates.io-index" 297 | checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" 298 | 299 | [[package]] 300 | name = "crc32fast" 301 | version = "1.5.0" 302 | source = "registry+https://github.com/rust-lang/crates.io-index" 303 | checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" 304 | dependencies = [ 305 | "cfg-if", 306 | ] 307 | 308 | [[package]] 309 | name = "critical-section" 310 | version = "1.2.0" 311 | source = "registry+https://github.com/rust-lang/crates.io-index" 312 | checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" 313 | 314 | [[package]] 315 | name = "crossbeam-channel" 316 | version = "0.5.15" 317 | source = "registry+https://github.com/rust-lang/crates.io-index" 318 | checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" 319 | dependencies = [ 320 | "crossbeam-utils", 321 | ] 322 | 323 | [[package]] 324 | name = "crossbeam-epoch" 325 | version = "0.9.18" 326 | source = "registry+https://github.com/rust-lang/crates.io-index" 327 | checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" 328 | dependencies = [ 329 | "crossbeam-utils", 330 | ] 331 | 332 | [[package]] 333 | name = "crossbeam-utils" 334 | version = "0.8.21" 335 | source = "registry+https://github.com/rust-lang/crates.io-index" 336 | checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" 337 | 338 | [[package]] 339 | name = "crossterm" 340 | version = "0.28.1" 341 | source = "registry+https://github.com/rust-lang/crates.io-index" 342 | checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" 343 | dependencies = [ 344 | "bitflags", 345 | "crossterm_winapi", 346 | "futures-core", 347 | "mio", 348 | "parking_lot", 349 | "rustix", 350 | "signal-hook", 351 | "signal-hook-mio", 352 | "winapi", 353 | ] 354 | 355 | [[package]] 356 | name = "crossterm_winapi" 357 | version = "0.9.1" 358 | source = "registry+https://github.com/rust-lang/crates.io-index" 359 | checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" 360 | dependencies = [ 361 | "winapi", 362 | ] 363 | 364 | [[package]] 365 | name = "darling" 366 | version = "0.20.11" 367 | source = "registry+https://github.com/rust-lang/crates.io-index" 368 | checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" 369 | dependencies = [ 370 | "darling_core", 371 | "darling_macro", 372 | ] 373 | 374 | [[package]] 375 | name = "darling_core" 376 | version = "0.20.11" 377 | source = "registry+https://github.com/rust-lang/crates.io-index" 378 | checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" 379 | dependencies = [ 380 | "fnv", 381 | "ident_case", 382 | "proc-macro2", 383 | "quote", 384 | "strsim", 385 | "syn", 386 | ] 387 | 388 | [[package]] 389 | name = "darling_macro" 390 | version = "0.20.11" 391 | source = "registry+https://github.com/rust-lang/crates.io-index" 392 | checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" 393 | dependencies = [ 394 | "darling_core", 395 | "quote", 396 | "syn", 397 | ] 398 | 399 | [[package]] 400 | name = "data-encoding" 401 | version = "2.9.0" 402 | source = "registry+https://github.com/rust-lang/crates.io-index" 403 | checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" 404 | 405 | [[package]] 406 | name = "dhat" 407 | version = "0.3.3" 408 | source = "registry+https://github.com/rust-lang/crates.io-index" 409 | checksum = "98cd11d84628e233de0ce467de10b8633f4ddaecafadefc86e13b84b8739b827" 410 | dependencies = [ 411 | "backtrace", 412 | "lazy_static", 413 | "mintex", 414 | "parking_lot", 415 | "rustc-hash 1.1.0", 416 | "serde", 417 | "serde_json", 418 | "thousands", 419 | ] 420 | 421 | [[package]] 422 | name = "dirs" 423 | version = "6.0.0" 424 | source = "registry+https://github.com/rust-lang/crates.io-index" 425 | checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" 426 | dependencies = [ 427 | "dirs-sys", 428 | ] 429 | 430 | [[package]] 431 | name = "dirs-sys" 432 | version = "0.5.0" 433 | source = "registry+https://github.com/rust-lang/crates.io-index" 434 | checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" 435 | dependencies = [ 436 | "libc", 437 | "option-ext", 438 | "redox_users", 439 | "windows-sys 0.61.2", 440 | ] 441 | 442 | [[package]] 443 | name = "displaydoc" 444 | version = "0.2.5" 445 | source = "registry+https://github.com/rust-lang/crates.io-index" 446 | checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" 447 | dependencies = [ 448 | "proc-macro2", 449 | "quote", 450 | "syn", 451 | ] 452 | 453 | [[package]] 454 | name = "either" 455 | version = "1.15.0" 456 | source = "registry+https://github.com/rust-lang/crates.io-index" 457 | checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" 458 | 459 | [[package]] 460 | name = "encoding_rs" 461 | version = "0.8.35" 462 | source = "registry+https://github.com/rust-lang/crates.io-index" 463 | checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" 464 | dependencies = [ 465 | "cfg-if", 466 | ] 467 | 468 | [[package]] 469 | name = "enum-as-inner" 470 | version = "0.6.1" 471 | source = "registry+https://github.com/rust-lang/crates.io-index" 472 | checksum = "a1e6a265c649f3f5979b601d26f1d05ada116434c87741c9493cb56218f76cbc" 473 | dependencies = [ 474 | "heck", 475 | "proc-macro2", 476 | "quote", 477 | "syn", 478 | ] 479 | 480 | [[package]] 481 | name = "env_filter" 482 | version = "0.1.4" 483 | source = "registry+https://github.com/rust-lang/crates.io-index" 484 | checksum = "1bf3c259d255ca70051b30e2e95b5446cdb8949ac4cd22c0d7fd634d89f568e2" 485 | dependencies = [ 486 | "log", 487 | "regex", 488 | ] 489 | 490 | [[package]] 491 | name = "equivalent" 492 | version = "1.0.2" 493 | source = "registry+https://github.com/rust-lang/crates.io-index" 494 | checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" 495 | 496 | [[package]] 497 | name = "errno" 498 | version = "0.3.14" 499 | source = "registry+https://github.com/rust-lang/crates.io-index" 500 | checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" 501 | dependencies = [ 502 | "libc", 503 | "windows-sys 0.61.2", 504 | ] 505 | 506 | [[package]] 507 | name = "eyre" 508 | version = "0.6.12" 509 | source = "registry+https://github.com/rust-lang/crates.io-index" 510 | checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" 511 | dependencies = [ 512 | "indenter", 513 | "once_cell", 514 | ] 515 | 516 | [[package]] 517 | name = "fancy-regex" 518 | version = "0.17.0" 519 | source = "registry+https://github.com/rust-lang/crates.io-index" 520 | checksum = "72cf461f865c862bb7dc573f643dd6a2b6842f7c30b07882b56bd148cc2761b8" 521 | dependencies = [ 522 | "bit-set", 523 | "regex-automata", 524 | "regex-syntax", 525 | ] 526 | 527 | [[package]] 528 | name = "find-msvc-tools" 529 | version = "0.1.5" 530 | source = "registry+https://github.com/rust-lang/crates.io-index" 531 | checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" 532 | 533 | [[package]] 534 | name = "flate2" 535 | version = "1.1.5" 536 | source = "registry+https://github.com/rust-lang/crates.io-index" 537 | checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb" 538 | dependencies = [ 539 | "crc32fast", 540 | "miniz_oxide", 541 | ] 542 | 543 | [[package]] 544 | name = "fnv" 545 | version = "1.0.7" 546 | source = "registry+https://github.com/rust-lang/crates.io-index" 547 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 548 | 549 | [[package]] 550 | name = "foldhash" 551 | version = "0.1.5" 552 | source = "registry+https://github.com/rust-lang/crates.io-index" 553 | checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" 554 | 555 | [[package]] 556 | name = "foldhash" 557 | version = "0.2.0" 558 | source = "registry+https://github.com/rust-lang/crates.io-index" 559 | checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" 560 | 561 | [[package]] 562 | name = "form_urlencoded" 563 | version = "1.2.2" 564 | source = "registry+https://github.com/rust-lang/crates.io-index" 565 | checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" 566 | dependencies = [ 567 | "percent-encoding", 568 | ] 569 | 570 | [[package]] 571 | name = "futures" 572 | version = "0.3.31" 573 | source = "registry+https://github.com/rust-lang/crates.io-index" 574 | checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" 575 | dependencies = [ 576 | "futures-channel", 577 | "futures-core", 578 | "futures-io", 579 | "futures-sink", 580 | "futures-task", 581 | "futures-util", 582 | ] 583 | 584 | [[package]] 585 | name = "futures-channel" 586 | version = "0.3.31" 587 | source = "registry+https://github.com/rust-lang/crates.io-index" 588 | checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" 589 | dependencies = [ 590 | "futures-core", 591 | "futures-sink", 592 | ] 593 | 594 | [[package]] 595 | name = "futures-core" 596 | version = "0.3.31" 597 | source = "registry+https://github.com/rust-lang/crates.io-index" 598 | checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" 599 | 600 | [[package]] 601 | name = "futures-io" 602 | version = "0.3.31" 603 | source = "registry+https://github.com/rust-lang/crates.io-index" 604 | checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" 605 | 606 | [[package]] 607 | name = "futures-sink" 608 | version = "0.3.31" 609 | source = "registry+https://github.com/rust-lang/crates.io-index" 610 | checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" 611 | 612 | [[package]] 613 | name = "futures-task" 614 | version = "0.3.31" 615 | source = "registry+https://github.com/rust-lang/crates.io-index" 616 | checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" 617 | 618 | [[package]] 619 | name = "futures-util" 620 | version = "0.3.31" 621 | source = "registry+https://github.com/rust-lang/crates.io-index" 622 | checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" 623 | dependencies = [ 624 | "futures-channel", 625 | "futures-core", 626 | "futures-io", 627 | "futures-sink", 628 | "futures-task", 629 | "memchr", 630 | "pin-project-lite", 631 | "pin-utils", 632 | "slab", 633 | ] 634 | 635 | [[package]] 636 | name = "getrandom" 637 | version = "0.2.16" 638 | source = "registry+https://github.com/rust-lang/crates.io-index" 639 | checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" 640 | dependencies = [ 641 | "cfg-if", 642 | "js-sys", 643 | "libc", 644 | "wasi", 645 | "wasm-bindgen", 646 | ] 647 | 648 | [[package]] 649 | name = "getrandom" 650 | version = "0.3.4" 651 | source = "registry+https://github.com/rust-lang/crates.io-index" 652 | checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" 653 | dependencies = [ 654 | "cfg-if", 655 | "js-sys", 656 | "libc", 657 | "r-efi", 658 | "wasip2", 659 | "wasm-bindgen", 660 | ] 661 | 662 | [[package]] 663 | name = "gimli" 664 | version = "0.32.3" 665 | source = "registry+https://github.com/rust-lang/crates.io-index" 666 | checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" 667 | 668 | [[package]] 669 | name = "h2" 670 | version = "0.4.12" 671 | source = "registry+https://github.com/rust-lang/crates.io-index" 672 | checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386" 673 | dependencies = [ 674 | "atomic-waker", 675 | "bytes", 676 | "fnv", 677 | "futures-core", 678 | "futures-sink", 679 | "http", 680 | "indexmap", 681 | "slab", 682 | "tokio", 683 | "tokio-util", 684 | "tracing", 685 | ] 686 | 687 | [[package]] 688 | name = "hashbrown" 689 | version = "0.15.5" 690 | source = "registry+https://github.com/rust-lang/crates.io-index" 691 | checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" 692 | dependencies = [ 693 | "allocator-api2", 694 | "equivalent", 695 | "foldhash 0.1.5", 696 | ] 697 | 698 | [[package]] 699 | name = "hashbrown" 700 | version = "0.16.1" 701 | source = "registry+https://github.com/rust-lang/crates.io-index" 702 | checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" 703 | 704 | [[package]] 705 | name = "heck" 706 | version = "0.5.0" 707 | source = "registry+https://github.com/rust-lang/crates.io-index" 708 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 709 | 710 | [[package]] 711 | name = "hickory-proto" 712 | version = "0.25.2" 713 | source = "registry+https://github.com/rust-lang/crates.io-index" 714 | checksum = "f8a6fe56c0038198998a6f217ca4e7ef3a5e51f46163bd6dd60b5c71ca6c6502" 715 | dependencies = [ 716 | "async-trait", 717 | "cfg-if", 718 | "data-encoding", 719 | "enum-as-inner", 720 | "futures-channel", 721 | "futures-io", 722 | "futures-util", 723 | "idna", 724 | "ipnet", 725 | "once_cell", 726 | "rand", 727 | "ring", 728 | "thiserror 2.0.17", 729 | "tinyvec", 730 | "tokio", 731 | "tracing", 732 | "url", 733 | ] 734 | 735 | [[package]] 736 | name = "hickory-resolver" 737 | version = "0.25.2" 738 | source = "registry+https://github.com/rust-lang/crates.io-index" 739 | checksum = "dc62a9a99b0bfb44d2ab95a7208ac952d31060efc16241c87eaf36406fecf87a" 740 | dependencies = [ 741 | "cfg-if", 742 | "futures-util", 743 | "hickory-proto", 744 | "ipconfig", 745 | "moka", 746 | "once_cell", 747 | "parking_lot", 748 | "rand", 749 | "resolv-conf", 750 | "smallvec", 751 | "thiserror 2.0.17", 752 | "tokio", 753 | "tracing", 754 | ] 755 | 756 | [[package]] 757 | name = "http" 758 | version = "1.4.0" 759 | source = "registry+https://github.com/rust-lang/crates.io-index" 760 | checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" 761 | dependencies = [ 762 | "bytes", 763 | "itoa", 764 | ] 765 | 766 | [[package]] 767 | name = "http-body" 768 | version = "1.0.1" 769 | source = "registry+https://github.com/rust-lang/crates.io-index" 770 | checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" 771 | dependencies = [ 772 | "bytes", 773 | "http", 774 | ] 775 | 776 | [[package]] 777 | name = "http-body-util" 778 | version = "0.1.3" 779 | source = "registry+https://github.com/rust-lang/crates.io-index" 780 | checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" 781 | dependencies = [ 782 | "bytes", 783 | "futures-core", 784 | "http", 785 | "http-body", 786 | "pin-project-lite", 787 | ] 788 | 789 | [[package]] 790 | name = "httparse" 791 | version = "1.10.1" 792 | source = "registry+https://github.com/rust-lang/crates.io-index" 793 | checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" 794 | 795 | [[package]] 796 | name = "httpdate" 797 | version = "1.0.3" 798 | source = "registry+https://github.com/rust-lang/crates.io-index" 799 | checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" 800 | 801 | [[package]] 802 | name = "hyper" 803 | version = "1.8.1" 804 | source = "registry+https://github.com/rust-lang/crates.io-index" 805 | checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" 806 | dependencies = [ 807 | "atomic-waker", 808 | "bytes", 809 | "futures-channel", 810 | "futures-core", 811 | "h2", 812 | "http", 813 | "http-body", 814 | "httparse", 815 | "itoa", 816 | "pin-project-lite", 817 | "pin-utils", 818 | "smallvec", 819 | "tokio", 820 | "want", 821 | ] 822 | 823 | [[package]] 824 | name = "hyper-rustls" 825 | version = "0.27.7" 826 | source = "registry+https://github.com/rust-lang/crates.io-index" 827 | checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" 828 | dependencies = [ 829 | "http", 830 | "hyper", 831 | "hyper-util", 832 | "rustls", 833 | "rustls-pki-types", 834 | "tokio", 835 | "tokio-rustls", 836 | "tower-service", 837 | "webpki-roots", 838 | ] 839 | 840 | [[package]] 841 | name = "hyper-util" 842 | version = "0.1.19" 843 | source = "registry+https://github.com/rust-lang/crates.io-index" 844 | checksum = "727805d60e7938b76b826a6ef209eb70eaa1812794f9424d4a4e2d740662df5f" 845 | dependencies = [ 846 | "base64", 847 | "bytes", 848 | "futures-channel", 849 | "futures-core", 850 | "futures-util", 851 | "http", 852 | "http-body", 853 | "hyper", 854 | "ipnet", 855 | "libc", 856 | "percent-encoding", 857 | "pin-project-lite", 858 | "socket2 0.6.1", 859 | "system-configuration", 860 | "tokio", 861 | "tower-service", 862 | "tracing", 863 | "windows-registry", 864 | ] 865 | 866 | [[package]] 867 | name = "iana-time-zone" 868 | version = "0.1.64" 869 | source = "registry+https://github.com/rust-lang/crates.io-index" 870 | checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" 871 | dependencies = [ 872 | "android_system_properties", 873 | "core-foundation-sys", 874 | "iana-time-zone-haiku", 875 | "js-sys", 876 | "log", 877 | "wasm-bindgen", 878 | "windows-core", 879 | ] 880 | 881 | [[package]] 882 | name = "iana-time-zone-haiku" 883 | version = "0.1.2" 884 | source = "registry+https://github.com/rust-lang/crates.io-index" 885 | checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" 886 | dependencies = [ 887 | "cc", 888 | ] 889 | 890 | [[package]] 891 | name = "icu_collections" 892 | version = "2.1.1" 893 | source = "registry+https://github.com/rust-lang/crates.io-index" 894 | checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" 895 | dependencies = [ 896 | "displaydoc", 897 | "potential_utf", 898 | "yoke", 899 | "zerofrom", 900 | "zerovec", 901 | ] 902 | 903 | [[package]] 904 | name = "icu_locale_core" 905 | version = "2.1.1" 906 | source = "registry+https://github.com/rust-lang/crates.io-index" 907 | checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" 908 | dependencies = [ 909 | "displaydoc", 910 | "litemap", 911 | "tinystr", 912 | "writeable", 913 | "zerovec", 914 | ] 915 | 916 | [[package]] 917 | name = "icu_normalizer" 918 | version = "2.1.1" 919 | source = "registry+https://github.com/rust-lang/crates.io-index" 920 | checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" 921 | dependencies = [ 922 | "icu_collections", 923 | "icu_normalizer_data", 924 | "icu_properties", 925 | "icu_provider", 926 | "smallvec", 927 | "zerovec", 928 | ] 929 | 930 | [[package]] 931 | name = "icu_normalizer_data" 932 | version = "2.1.1" 933 | source = "registry+https://github.com/rust-lang/crates.io-index" 934 | checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" 935 | 936 | [[package]] 937 | name = "icu_properties" 938 | version = "2.1.2" 939 | source = "registry+https://github.com/rust-lang/crates.io-index" 940 | checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" 941 | dependencies = [ 942 | "icu_collections", 943 | "icu_locale_core", 944 | "icu_properties_data", 945 | "icu_provider", 946 | "zerotrie", 947 | "zerovec", 948 | ] 949 | 950 | [[package]] 951 | name = "icu_properties_data" 952 | version = "2.1.2" 953 | source = "registry+https://github.com/rust-lang/crates.io-index" 954 | checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" 955 | 956 | [[package]] 957 | name = "icu_provider" 958 | version = "2.1.1" 959 | source = "registry+https://github.com/rust-lang/crates.io-index" 960 | checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" 961 | dependencies = [ 962 | "displaydoc", 963 | "icu_locale_core", 964 | "writeable", 965 | "yoke", 966 | "zerofrom", 967 | "zerotrie", 968 | "zerovec", 969 | ] 970 | 971 | [[package]] 972 | name = "ident_case" 973 | version = "1.0.1" 974 | source = "registry+https://github.com/rust-lang/crates.io-index" 975 | checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" 976 | 977 | [[package]] 978 | name = "idna" 979 | version = "1.1.0" 980 | source = "registry+https://github.com/rust-lang/crates.io-index" 981 | checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" 982 | dependencies = [ 983 | "idna_adapter", 984 | "smallvec", 985 | "utf8_iter", 986 | ] 987 | 988 | [[package]] 989 | name = "idna_adapter" 990 | version = "1.2.1" 991 | source = "registry+https://github.com/rust-lang/crates.io-index" 992 | checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" 993 | dependencies = [ 994 | "icu_normalizer", 995 | "icu_properties", 996 | ] 997 | 998 | [[package]] 999 | name = "indenter" 1000 | version = "0.3.4" 1001 | source = "registry+https://github.com/rust-lang/crates.io-index" 1002 | checksum = "964de6e86d545b246d84badc0fef527924ace5134f30641c203ef52ba83f58d5" 1003 | 1004 | [[package]] 1005 | name = "indexmap" 1006 | version = "2.12.1" 1007 | source = "registry+https://github.com/rust-lang/crates.io-index" 1008 | checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" 1009 | dependencies = [ 1010 | "equivalent", 1011 | "hashbrown 0.16.1", 1012 | ] 1013 | 1014 | [[package]] 1015 | name = "indoc" 1016 | version = "2.0.7" 1017 | source = "registry+https://github.com/rust-lang/crates.io-index" 1018 | checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706" 1019 | dependencies = [ 1020 | "rustversion", 1021 | ] 1022 | 1023 | [[package]] 1024 | name = "instability" 1025 | version = "0.3.10" 1026 | source = "registry+https://github.com/rust-lang/crates.io-index" 1027 | checksum = "6778b0196eefee7df739db78758e5cf9b37412268bfa5650bfeed028aed20d9c" 1028 | dependencies = [ 1029 | "darling", 1030 | "indoc", 1031 | "proc-macro2", 1032 | "quote", 1033 | "syn", 1034 | ] 1035 | 1036 | [[package]] 1037 | name = "ipconfig" 1038 | version = "0.3.2" 1039 | source = "registry+https://github.com/rust-lang/crates.io-index" 1040 | checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f" 1041 | dependencies = [ 1042 | "socket2 0.5.10", 1043 | "widestring", 1044 | "windows-sys 0.48.0", 1045 | "winreg", 1046 | ] 1047 | 1048 | [[package]] 1049 | name = "ipnet" 1050 | version = "2.11.0" 1051 | source = "registry+https://github.com/rust-lang/crates.io-index" 1052 | checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" 1053 | 1054 | [[package]] 1055 | name = "ipnetwork" 1056 | version = "0.21.1" 1057 | source = "registry+https://github.com/rust-lang/crates.io-index" 1058 | checksum = "cf370abdafd54d13e54a620e8c3e1145f28e46cc9d704bc6d94414559df41763" 1059 | 1060 | [[package]] 1061 | name = "iri-string" 1062 | version = "0.7.9" 1063 | source = "registry+https://github.com/rust-lang/crates.io-index" 1064 | checksum = "4f867b9d1d896b67beb18518eda36fdb77a32ea590de864f1325b294a6d14397" 1065 | dependencies = [ 1066 | "memchr", 1067 | "serde", 1068 | ] 1069 | 1070 | [[package]] 1071 | name = "itertools" 1072 | version = "0.13.0" 1073 | source = "registry+https://github.com/rust-lang/crates.io-index" 1074 | checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" 1075 | dependencies = [ 1076 | "either", 1077 | ] 1078 | 1079 | [[package]] 1080 | name = "itertools" 1081 | version = "0.14.0" 1082 | source = "registry+https://github.com/rust-lang/crates.io-index" 1083 | checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" 1084 | dependencies = [ 1085 | "either", 1086 | ] 1087 | 1088 | [[package]] 1089 | name = "itoa" 1090 | version = "1.0.15" 1091 | source = "registry+https://github.com/rust-lang/crates.io-index" 1092 | checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" 1093 | 1094 | [[package]] 1095 | name = "js-sys" 1096 | version = "0.3.83" 1097 | source = "registry+https://github.com/rust-lang/crates.io-index" 1098 | checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8" 1099 | dependencies = [ 1100 | "once_cell", 1101 | "wasm-bindgen", 1102 | ] 1103 | 1104 | [[package]] 1105 | name = "lazy_static" 1106 | version = "1.5.0" 1107 | source = "registry+https://github.com/rust-lang/crates.io-index" 1108 | checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 1109 | 1110 | [[package]] 1111 | name = "libc" 1112 | version = "0.2.178" 1113 | source = "registry+https://github.com/rust-lang/crates.io-index" 1114 | checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" 1115 | 1116 | [[package]] 1117 | name = "libmimalloc-sys" 1118 | version = "0.1.44" 1119 | source = "registry+https://github.com/rust-lang/crates.io-index" 1120 | checksum = "667f4fec20f29dfc6bc7357c582d91796c169ad7e2fce709468aefeb2c099870" 1121 | dependencies = [ 1122 | "cc", 1123 | "libc", 1124 | ] 1125 | 1126 | [[package]] 1127 | name = "libredox" 1128 | version = "0.1.10" 1129 | source = "registry+https://github.com/rust-lang/crates.io-index" 1130 | checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" 1131 | dependencies = [ 1132 | "bitflags", 1133 | "libc", 1134 | ] 1135 | 1136 | [[package]] 1137 | name = "linux-raw-sys" 1138 | version = "0.4.15" 1139 | source = "registry+https://github.com/rust-lang/crates.io-index" 1140 | checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" 1141 | 1142 | [[package]] 1143 | name = "litemap" 1144 | version = "0.8.1" 1145 | source = "registry+https://github.com/rust-lang/crates.io-index" 1146 | checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" 1147 | 1148 | [[package]] 1149 | name = "lock_api" 1150 | version = "0.4.14" 1151 | source = "registry+https://github.com/rust-lang/crates.io-index" 1152 | checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" 1153 | dependencies = [ 1154 | "scopeguard", 1155 | ] 1156 | 1157 | [[package]] 1158 | name = "log" 1159 | version = "0.4.29" 1160 | source = "registry+https://github.com/rust-lang/crates.io-index" 1161 | checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" 1162 | 1163 | [[package]] 1164 | name = "lru" 1165 | version = "0.12.5" 1166 | source = "registry+https://github.com/rust-lang/crates.io-index" 1167 | checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" 1168 | dependencies = [ 1169 | "hashbrown 0.15.5", 1170 | ] 1171 | 1172 | [[package]] 1173 | name = "lru-slab" 1174 | version = "0.1.2" 1175 | source = "registry+https://github.com/rust-lang/crates.io-index" 1176 | checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" 1177 | 1178 | [[package]] 1179 | name = "maxminddb" 1180 | version = "0.27.0" 1181 | source = "registry+https://github.com/rust-lang/crates.io-index" 1182 | checksum = "7ef0551fc3e7345a6c854c1026b0ddada1e443e51f4fb4cdcf86cc1a71d4b337" 1183 | dependencies = [ 1184 | "ipnetwork", 1185 | "log", 1186 | "memchr", 1187 | "memmap2", 1188 | "serde", 1189 | "thiserror 2.0.17", 1190 | ] 1191 | 1192 | [[package]] 1193 | name = "memchr" 1194 | version = "2.7.6" 1195 | source = "registry+https://github.com/rust-lang/crates.io-index" 1196 | checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" 1197 | 1198 | [[package]] 1199 | name = "memmap2" 1200 | version = "0.9.9" 1201 | source = "registry+https://github.com/rust-lang/crates.io-index" 1202 | checksum = "744133e4a0e0a658e1374cf3bf8e415c4052a15a111acd372764c55b4177d490" 1203 | dependencies = [ 1204 | "libc", 1205 | ] 1206 | 1207 | [[package]] 1208 | name = "mimalloc" 1209 | version = "0.1.48" 1210 | source = "registry+https://github.com/rust-lang/crates.io-index" 1211 | checksum = "e1ee66a4b64c74f4ef288bcbb9192ad9c3feaad75193129ac8509af543894fd8" 1212 | dependencies = [ 1213 | "libmimalloc-sys", 1214 | ] 1215 | 1216 | [[package]] 1217 | name = "mime" 1218 | version = "0.3.17" 1219 | source = "registry+https://github.com/rust-lang/crates.io-index" 1220 | checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" 1221 | 1222 | [[package]] 1223 | name = "miniz_oxide" 1224 | version = "0.8.9" 1225 | source = "registry+https://github.com/rust-lang/crates.io-index" 1226 | checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" 1227 | dependencies = [ 1228 | "adler2", 1229 | "simd-adler32", 1230 | ] 1231 | 1232 | [[package]] 1233 | name = "mintex" 1234 | version = "0.1.4" 1235 | source = "registry+https://github.com/rust-lang/crates.io-index" 1236 | checksum = "c505b3e17ed6b70a7ed2e67fbb2c560ee327353556120d6e72f5232b6880d536" 1237 | 1238 | [[package]] 1239 | name = "mio" 1240 | version = "1.1.1" 1241 | source = "registry+https://github.com/rust-lang/crates.io-index" 1242 | checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" 1243 | dependencies = [ 1244 | "libc", 1245 | "log", 1246 | "wasi", 1247 | "windows-sys 0.61.2", 1248 | ] 1249 | 1250 | [[package]] 1251 | name = "moka" 1252 | version = "0.12.11" 1253 | source = "registry+https://github.com/rust-lang/crates.io-index" 1254 | checksum = "8261cd88c312e0004c1d51baad2980c66528dfdb2bee62003e643a4d8f86b077" 1255 | dependencies = [ 1256 | "crossbeam-channel", 1257 | "crossbeam-epoch", 1258 | "crossbeam-utils", 1259 | "equivalent", 1260 | "parking_lot", 1261 | "portable-atomic", 1262 | "rustc_version", 1263 | "smallvec", 1264 | "tagptr", 1265 | "uuid", 1266 | ] 1267 | 1268 | [[package]] 1269 | name = "nu-ansi-term" 1270 | version = "0.50.3" 1271 | source = "registry+https://github.com/rust-lang/crates.io-index" 1272 | checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" 1273 | dependencies = [ 1274 | "windows-sys 0.61.2", 1275 | ] 1276 | 1277 | [[package]] 1278 | name = "num-traits" 1279 | version = "0.2.19" 1280 | source = "registry+https://github.com/rust-lang/crates.io-index" 1281 | checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" 1282 | dependencies = [ 1283 | "autocfg", 1284 | ] 1285 | 1286 | [[package]] 1287 | name = "object" 1288 | version = "0.37.3" 1289 | source = "registry+https://github.com/rust-lang/crates.io-index" 1290 | checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" 1291 | dependencies = [ 1292 | "memchr", 1293 | ] 1294 | 1295 | [[package]] 1296 | name = "once_cell" 1297 | version = "1.21.3" 1298 | source = "registry+https://github.com/rust-lang/crates.io-index" 1299 | checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" 1300 | dependencies = [ 1301 | "critical-section", 1302 | "portable-atomic", 1303 | ] 1304 | 1305 | [[package]] 1306 | name = "option-ext" 1307 | version = "0.2.0" 1308 | source = "registry+https://github.com/rust-lang/crates.io-index" 1309 | checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" 1310 | 1311 | [[package]] 1312 | name = "owo-colors" 1313 | version = "4.2.3" 1314 | source = "registry+https://github.com/rust-lang/crates.io-index" 1315 | checksum = "9c6901729fa79e91a0913333229e9ca5dc725089d1c363b2f4b4760709dc4a52" 1316 | 1317 | [[package]] 1318 | name = "parking_lot" 1319 | version = "0.12.5" 1320 | source = "registry+https://github.com/rust-lang/crates.io-index" 1321 | checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" 1322 | dependencies = [ 1323 | "lock_api", 1324 | "parking_lot_core", 1325 | ] 1326 | 1327 | [[package]] 1328 | name = "parking_lot_core" 1329 | version = "0.9.12" 1330 | source = "registry+https://github.com/rust-lang/crates.io-index" 1331 | checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" 1332 | dependencies = [ 1333 | "cfg-if", 1334 | "libc", 1335 | "redox_syscall", 1336 | "smallvec", 1337 | "windows-link", 1338 | ] 1339 | 1340 | [[package]] 1341 | name = "paste" 1342 | version = "1.0.15" 1343 | source = "registry+https://github.com/rust-lang/crates.io-index" 1344 | checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" 1345 | 1346 | [[package]] 1347 | name = "percent-encoding" 1348 | version = "2.3.2" 1349 | source = "registry+https://github.com/rust-lang/crates.io-index" 1350 | checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" 1351 | 1352 | [[package]] 1353 | name = "pin-project-lite" 1354 | version = "0.2.16" 1355 | source = "registry+https://github.com/rust-lang/crates.io-index" 1356 | checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" 1357 | 1358 | [[package]] 1359 | name = "pin-utils" 1360 | version = "0.1.0" 1361 | source = "registry+https://github.com/rust-lang/crates.io-index" 1362 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 1363 | 1364 | [[package]] 1365 | name = "portable-atomic" 1366 | version = "1.11.1" 1367 | source = "registry+https://github.com/rust-lang/crates.io-index" 1368 | checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" 1369 | 1370 | [[package]] 1371 | name = "potential_utf" 1372 | version = "0.1.4" 1373 | source = "registry+https://github.com/rust-lang/crates.io-index" 1374 | checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" 1375 | dependencies = [ 1376 | "zerovec", 1377 | ] 1378 | 1379 | [[package]] 1380 | name = "ppv-lite86" 1381 | version = "0.2.21" 1382 | source = "registry+https://github.com/rust-lang/crates.io-index" 1383 | checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" 1384 | dependencies = [ 1385 | "zerocopy", 1386 | ] 1387 | 1388 | [[package]] 1389 | name = "proc-macro2" 1390 | version = "1.0.103" 1391 | source = "registry+https://github.com/rust-lang/crates.io-index" 1392 | checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" 1393 | dependencies = [ 1394 | "unicode-ident", 1395 | ] 1396 | 1397 | [[package]] 1398 | name = "proxy-scraper-checker" 1399 | version = "0.1.0" 1400 | dependencies = [ 1401 | "async-trait", 1402 | "color-eyre", 1403 | "crossterm", 1404 | "dhat", 1405 | "dirs", 1406 | "fancy-regex", 1407 | "foldhash 0.2.0", 1408 | "futures", 1409 | "hickory-resolver", 1410 | "http", 1411 | "httpdate", 1412 | "itertools 0.14.0", 1413 | "log", 1414 | "maxminddb", 1415 | "mimalloc", 1416 | "parking_lot", 1417 | "rand", 1418 | "ratatui", 1419 | "reqwest", 1420 | "reqwest-middleware", 1421 | "rlimit", 1422 | "serde", 1423 | "serde_json", 1424 | "smallvec", 1425 | "strum 0.27.2", 1426 | "tikv-jemallocator", 1427 | "tokio", 1428 | "tokio-util", 1429 | "toml", 1430 | "tracing", 1431 | "tracing-subscriber", 1432 | "tui-logger", 1433 | "url", 1434 | ] 1435 | 1436 | [[package]] 1437 | name = "quinn" 1438 | version = "0.11.9" 1439 | source = "registry+https://github.com/rust-lang/crates.io-index" 1440 | checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" 1441 | dependencies = [ 1442 | "bytes", 1443 | "cfg_aliases", 1444 | "pin-project-lite", 1445 | "quinn-proto", 1446 | "quinn-udp", 1447 | "rustc-hash 2.1.1", 1448 | "rustls", 1449 | "socket2 0.6.1", 1450 | "thiserror 2.0.17", 1451 | "tokio", 1452 | "tracing", 1453 | "web-time", 1454 | ] 1455 | 1456 | [[package]] 1457 | name = "quinn-proto" 1458 | version = "0.11.13" 1459 | source = "registry+https://github.com/rust-lang/crates.io-index" 1460 | checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" 1461 | dependencies = [ 1462 | "bytes", 1463 | "getrandom 0.3.4", 1464 | "lru-slab", 1465 | "rand", 1466 | "ring", 1467 | "rustc-hash 2.1.1", 1468 | "rustls", 1469 | "rustls-pki-types", 1470 | "slab", 1471 | "thiserror 2.0.17", 1472 | "tinyvec", 1473 | "tracing", 1474 | "web-time", 1475 | ] 1476 | 1477 | [[package]] 1478 | name = "quinn-udp" 1479 | version = "0.5.14" 1480 | source = "registry+https://github.com/rust-lang/crates.io-index" 1481 | checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" 1482 | dependencies = [ 1483 | "cfg_aliases", 1484 | "libc", 1485 | "once_cell", 1486 | "socket2 0.6.1", 1487 | "tracing", 1488 | "windows-sys 0.60.2", 1489 | ] 1490 | 1491 | [[package]] 1492 | name = "quote" 1493 | version = "1.0.42" 1494 | source = "registry+https://github.com/rust-lang/crates.io-index" 1495 | checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" 1496 | dependencies = [ 1497 | "proc-macro2", 1498 | ] 1499 | 1500 | [[package]] 1501 | name = "r-efi" 1502 | version = "5.3.0" 1503 | source = "registry+https://github.com/rust-lang/crates.io-index" 1504 | checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" 1505 | 1506 | [[package]] 1507 | name = "rand" 1508 | version = "0.9.2" 1509 | source = "registry+https://github.com/rust-lang/crates.io-index" 1510 | checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" 1511 | dependencies = [ 1512 | "rand_chacha", 1513 | "rand_core", 1514 | ] 1515 | 1516 | [[package]] 1517 | name = "rand_chacha" 1518 | version = "0.9.0" 1519 | source = "registry+https://github.com/rust-lang/crates.io-index" 1520 | checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" 1521 | dependencies = [ 1522 | "ppv-lite86", 1523 | "rand_core", 1524 | ] 1525 | 1526 | [[package]] 1527 | name = "rand_core" 1528 | version = "0.9.3" 1529 | source = "registry+https://github.com/rust-lang/crates.io-index" 1530 | checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" 1531 | dependencies = [ 1532 | "getrandom 0.3.4", 1533 | ] 1534 | 1535 | [[package]] 1536 | name = "ratatui" 1537 | version = "0.29.0" 1538 | source = "registry+https://github.com/rust-lang/crates.io-index" 1539 | checksum = "eabd94c2f37801c20583fc49dd5cd6b0ba68c716787c2dd6ed18571e1e63117b" 1540 | dependencies = [ 1541 | "bitflags", 1542 | "cassowary", 1543 | "compact_str", 1544 | "crossterm", 1545 | "indoc", 1546 | "instability", 1547 | "itertools 0.13.0", 1548 | "lru", 1549 | "paste", 1550 | "strum 0.26.3", 1551 | "unicode-segmentation", 1552 | "unicode-truncate", 1553 | "unicode-width 0.2.0", 1554 | ] 1555 | 1556 | [[package]] 1557 | name = "redox_syscall" 1558 | version = "0.5.18" 1559 | source = "registry+https://github.com/rust-lang/crates.io-index" 1560 | checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" 1561 | dependencies = [ 1562 | "bitflags", 1563 | ] 1564 | 1565 | [[package]] 1566 | name = "redox_users" 1567 | version = "0.5.2" 1568 | source = "registry+https://github.com/rust-lang/crates.io-index" 1569 | checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" 1570 | dependencies = [ 1571 | "getrandom 0.2.16", 1572 | "libredox", 1573 | "thiserror 2.0.17", 1574 | ] 1575 | 1576 | [[package]] 1577 | name = "regex" 1578 | version = "1.12.2" 1579 | source = "registry+https://github.com/rust-lang/crates.io-index" 1580 | checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" 1581 | dependencies = [ 1582 | "aho-corasick", 1583 | "memchr", 1584 | "regex-automata", 1585 | "regex-syntax", 1586 | ] 1587 | 1588 | [[package]] 1589 | name = "regex-automata" 1590 | version = "0.4.13" 1591 | source = "registry+https://github.com/rust-lang/crates.io-index" 1592 | checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" 1593 | dependencies = [ 1594 | "aho-corasick", 1595 | "memchr", 1596 | "regex-syntax", 1597 | ] 1598 | 1599 | [[package]] 1600 | name = "regex-syntax" 1601 | version = "0.8.8" 1602 | source = "registry+https://github.com/rust-lang/crates.io-index" 1603 | checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" 1604 | 1605 | [[package]] 1606 | name = "reqwest" 1607 | version = "0.12.25" 1608 | source = "registry+https://github.com/rust-lang/crates.io-index" 1609 | checksum = "b6eff9328d40131d43bd911d42d79eb6a47312002a4daefc9e37f17e74a7701a" 1610 | dependencies = [ 1611 | "base64", 1612 | "bytes", 1613 | "encoding_rs", 1614 | "futures-core", 1615 | "h2", 1616 | "http", 1617 | "http-body", 1618 | "http-body-util", 1619 | "hyper", 1620 | "hyper-rustls", 1621 | "hyper-util", 1622 | "js-sys", 1623 | "log", 1624 | "mime", 1625 | "percent-encoding", 1626 | "pin-project-lite", 1627 | "quinn", 1628 | "rustls", 1629 | "rustls-pki-types", 1630 | "serde", 1631 | "serde_json", 1632 | "serde_urlencoded", 1633 | "sync_wrapper", 1634 | "tokio", 1635 | "tokio-rustls", 1636 | "tower", 1637 | "tower-http", 1638 | "tower-service", 1639 | "url", 1640 | "wasm-bindgen", 1641 | "wasm-bindgen-futures", 1642 | "web-sys", 1643 | "webpki-roots", 1644 | ] 1645 | 1646 | [[package]] 1647 | name = "reqwest-middleware" 1648 | version = "0.4.2" 1649 | source = "registry+https://github.com/rust-lang/crates.io-index" 1650 | checksum = "57f17d28a6e6acfe1733fe24bcd30774d13bffa4b8a22535b4c8c98423088d4e" 1651 | dependencies = [ 1652 | "anyhow", 1653 | "async-trait", 1654 | "http", 1655 | "reqwest", 1656 | "serde", 1657 | "thiserror 1.0.69", 1658 | "tower-service", 1659 | ] 1660 | 1661 | [[package]] 1662 | name = "resolv-conf" 1663 | version = "0.7.6" 1664 | source = "registry+https://github.com/rust-lang/crates.io-index" 1665 | checksum = "1e061d1b48cb8d38042de4ae0a7a6401009d6143dc80d2e2d6f31f0bdd6470c7" 1666 | 1667 | [[package]] 1668 | name = "ring" 1669 | version = "0.17.14" 1670 | source = "registry+https://github.com/rust-lang/crates.io-index" 1671 | checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" 1672 | dependencies = [ 1673 | "cc", 1674 | "cfg-if", 1675 | "getrandom 0.2.16", 1676 | "libc", 1677 | "untrusted", 1678 | "windows-sys 0.52.0", 1679 | ] 1680 | 1681 | [[package]] 1682 | name = "rlimit" 1683 | version = "0.10.2" 1684 | source = "registry+https://github.com/rust-lang/crates.io-index" 1685 | checksum = "7043b63bd0cd1aaa628e476b80e6d4023a3b50eb32789f2728908107bd0c793a" 1686 | dependencies = [ 1687 | "libc", 1688 | ] 1689 | 1690 | [[package]] 1691 | name = "rustc-demangle" 1692 | version = "0.1.26" 1693 | source = "registry+https://github.com/rust-lang/crates.io-index" 1694 | checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" 1695 | 1696 | [[package]] 1697 | name = "rustc-hash" 1698 | version = "1.1.0" 1699 | source = "registry+https://github.com/rust-lang/crates.io-index" 1700 | checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" 1701 | 1702 | [[package]] 1703 | name = "rustc-hash" 1704 | version = "2.1.1" 1705 | source = "registry+https://github.com/rust-lang/crates.io-index" 1706 | checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" 1707 | 1708 | [[package]] 1709 | name = "rustc_version" 1710 | version = "0.4.1" 1711 | source = "registry+https://github.com/rust-lang/crates.io-index" 1712 | checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" 1713 | dependencies = [ 1714 | "semver", 1715 | ] 1716 | 1717 | [[package]] 1718 | name = "rustix" 1719 | version = "0.38.44" 1720 | source = "registry+https://github.com/rust-lang/crates.io-index" 1721 | checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" 1722 | dependencies = [ 1723 | "bitflags", 1724 | "errno", 1725 | "libc", 1726 | "linux-raw-sys", 1727 | "windows-sys 0.59.0", 1728 | ] 1729 | 1730 | [[package]] 1731 | name = "rustls" 1732 | version = "0.23.35" 1733 | source = "registry+https://github.com/rust-lang/crates.io-index" 1734 | checksum = "533f54bc6a7d4f647e46ad909549eda97bf5afc1585190ef692b4286b198bd8f" 1735 | dependencies = [ 1736 | "once_cell", 1737 | "ring", 1738 | "rustls-pki-types", 1739 | "rustls-webpki", 1740 | "subtle", 1741 | "zeroize", 1742 | ] 1743 | 1744 | [[package]] 1745 | name = "rustls-pki-types" 1746 | version = "1.13.1" 1747 | source = "registry+https://github.com/rust-lang/crates.io-index" 1748 | checksum = "708c0f9d5f54ba0272468c1d306a52c495b31fa155e91bc25371e6df7996908c" 1749 | dependencies = [ 1750 | "web-time", 1751 | "zeroize", 1752 | ] 1753 | 1754 | [[package]] 1755 | name = "rustls-webpki" 1756 | version = "0.103.8" 1757 | source = "registry+https://github.com/rust-lang/crates.io-index" 1758 | checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52" 1759 | dependencies = [ 1760 | "ring", 1761 | "rustls-pki-types", 1762 | "untrusted", 1763 | ] 1764 | 1765 | [[package]] 1766 | name = "rustversion" 1767 | version = "1.0.22" 1768 | source = "registry+https://github.com/rust-lang/crates.io-index" 1769 | checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" 1770 | 1771 | [[package]] 1772 | name = "ryu" 1773 | version = "1.0.20" 1774 | source = "registry+https://github.com/rust-lang/crates.io-index" 1775 | checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" 1776 | 1777 | [[package]] 1778 | name = "scopeguard" 1779 | version = "1.2.0" 1780 | source = "registry+https://github.com/rust-lang/crates.io-index" 1781 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 1782 | 1783 | [[package]] 1784 | name = "semver" 1785 | version = "1.0.27" 1786 | source = "registry+https://github.com/rust-lang/crates.io-index" 1787 | checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" 1788 | 1789 | [[package]] 1790 | name = "serde" 1791 | version = "1.0.228" 1792 | source = "registry+https://github.com/rust-lang/crates.io-index" 1793 | checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" 1794 | dependencies = [ 1795 | "serde_core", 1796 | "serde_derive", 1797 | ] 1798 | 1799 | [[package]] 1800 | name = "serde_core" 1801 | version = "1.0.228" 1802 | source = "registry+https://github.com/rust-lang/crates.io-index" 1803 | checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" 1804 | dependencies = [ 1805 | "serde_derive", 1806 | ] 1807 | 1808 | [[package]] 1809 | name = "serde_derive" 1810 | version = "1.0.228" 1811 | source = "registry+https://github.com/rust-lang/crates.io-index" 1812 | checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" 1813 | dependencies = [ 1814 | "proc-macro2", 1815 | "quote", 1816 | "syn", 1817 | ] 1818 | 1819 | [[package]] 1820 | name = "serde_json" 1821 | version = "1.0.145" 1822 | source = "registry+https://github.com/rust-lang/crates.io-index" 1823 | checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" 1824 | dependencies = [ 1825 | "itoa", 1826 | "memchr", 1827 | "ryu", 1828 | "serde", 1829 | "serde_core", 1830 | ] 1831 | 1832 | [[package]] 1833 | name = "serde_spanned" 1834 | version = "1.0.3" 1835 | source = "registry+https://github.com/rust-lang/crates.io-index" 1836 | checksum = "e24345aa0fe688594e73770a5f6d1b216508b4f93484c0026d521acd30134392" 1837 | dependencies = [ 1838 | "serde_core", 1839 | ] 1840 | 1841 | [[package]] 1842 | name = "serde_urlencoded" 1843 | version = "0.7.1" 1844 | source = "registry+https://github.com/rust-lang/crates.io-index" 1845 | checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" 1846 | dependencies = [ 1847 | "form_urlencoded", 1848 | "itoa", 1849 | "ryu", 1850 | "serde", 1851 | ] 1852 | 1853 | [[package]] 1854 | name = "sharded-slab" 1855 | version = "0.1.7" 1856 | source = "registry+https://github.com/rust-lang/crates.io-index" 1857 | checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" 1858 | dependencies = [ 1859 | "lazy_static", 1860 | ] 1861 | 1862 | [[package]] 1863 | name = "shlex" 1864 | version = "1.3.0" 1865 | source = "registry+https://github.com/rust-lang/crates.io-index" 1866 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 1867 | 1868 | [[package]] 1869 | name = "signal-hook" 1870 | version = "0.3.18" 1871 | source = "registry+https://github.com/rust-lang/crates.io-index" 1872 | checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" 1873 | dependencies = [ 1874 | "libc", 1875 | "signal-hook-registry", 1876 | ] 1877 | 1878 | [[package]] 1879 | name = "signal-hook-mio" 1880 | version = "0.2.5" 1881 | source = "registry+https://github.com/rust-lang/crates.io-index" 1882 | checksum = "b75a19a7a740b25bc7944bdee6172368f988763b744e3d4dfe753f6b4ece40cc" 1883 | dependencies = [ 1884 | "libc", 1885 | "mio", 1886 | "signal-hook", 1887 | ] 1888 | 1889 | [[package]] 1890 | name = "signal-hook-registry" 1891 | version = "1.4.7" 1892 | source = "registry+https://github.com/rust-lang/crates.io-index" 1893 | checksum = "7664a098b8e616bdfcc2dc0e9ac44eb231eedf41db4e9fe95d8d32ec728dedad" 1894 | dependencies = [ 1895 | "libc", 1896 | ] 1897 | 1898 | [[package]] 1899 | name = "simd-adler32" 1900 | version = "0.3.8" 1901 | source = "registry+https://github.com/rust-lang/crates.io-index" 1902 | checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" 1903 | 1904 | [[package]] 1905 | name = "slab" 1906 | version = "0.4.11" 1907 | source = "registry+https://github.com/rust-lang/crates.io-index" 1908 | checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" 1909 | 1910 | [[package]] 1911 | name = "smallvec" 1912 | version = "1.15.1" 1913 | source = "registry+https://github.com/rust-lang/crates.io-index" 1914 | checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" 1915 | 1916 | [[package]] 1917 | name = "socket2" 1918 | version = "0.5.10" 1919 | source = "registry+https://github.com/rust-lang/crates.io-index" 1920 | checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" 1921 | dependencies = [ 1922 | "libc", 1923 | "windows-sys 0.52.0", 1924 | ] 1925 | 1926 | [[package]] 1927 | name = "socket2" 1928 | version = "0.6.1" 1929 | source = "registry+https://github.com/rust-lang/crates.io-index" 1930 | checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" 1931 | dependencies = [ 1932 | "libc", 1933 | "windows-sys 0.60.2", 1934 | ] 1935 | 1936 | [[package]] 1937 | name = "stable_deref_trait" 1938 | version = "1.2.1" 1939 | source = "registry+https://github.com/rust-lang/crates.io-index" 1940 | checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" 1941 | 1942 | [[package]] 1943 | name = "static_assertions" 1944 | version = "1.1.0" 1945 | source = "registry+https://github.com/rust-lang/crates.io-index" 1946 | checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" 1947 | 1948 | [[package]] 1949 | name = "strsim" 1950 | version = "0.11.1" 1951 | source = "registry+https://github.com/rust-lang/crates.io-index" 1952 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 1953 | 1954 | [[package]] 1955 | name = "strum" 1956 | version = "0.26.3" 1957 | source = "registry+https://github.com/rust-lang/crates.io-index" 1958 | checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" 1959 | dependencies = [ 1960 | "strum_macros 0.26.4", 1961 | ] 1962 | 1963 | [[package]] 1964 | name = "strum" 1965 | version = "0.27.2" 1966 | source = "registry+https://github.com/rust-lang/crates.io-index" 1967 | checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" 1968 | dependencies = [ 1969 | "strum_macros 0.27.2", 1970 | ] 1971 | 1972 | [[package]] 1973 | name = "strum_macros" 1974 | version = "0.26.4" 1975 | source = "registry+https://github.com/rust-lang/crates.io-index" 1976 | checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" 1977 | dependencies = [ 1978 | "heck", 1979 | "proc-macro2", 1980 | "quote", 1981 | "rustversion", 1982 | "syn", 1983 | ] 1984 | 1985 | [[package]] 1986 | name = "strum_macros" 1987 | version = "0.27.2" 1988 | source = "registry+https://github.com/rust-lang/crates.io-index" 1989 | checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" 1990 | dependencies = [ 1991 | "heck", 1992 | "proc-macro2", 1993 | "quote", 1994 | "syn", 1995 | ] 1996 | 1997 | [[package]] 1998 | name = "subtle" 1999 | version = "2.6.1" 2000 | source = "registry+https://github.com/rust-lang/crates.io-index" 2001 | checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" 2002 | 2003 | [[package]] 2004 | name = "syn" 2005 | version = "2.0.111" 2006 | source = "registry+https://github.com/rust-lang/crates.io-index" 2007 | checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" 2008 | dependencies = [ 2009 | "proc-macro2", 2010 | "quote", 2011 | "unicode-ident", 2012 | ] 2013 | 2014 | [[package]] 2015 | name = "sync_wrapper" 2016 | version = "1.0.2" 2017 | source = "registry+https://github.com/rust-lang/crates.io-index" 2018 | checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" 2019 | dependencies = [ 2020 | "futures-core", 2021 | ] 2022 | 2023 | [[package]] 2024 | name = "synstructure" 2025 | version = "0.13.2" 2026 | source = "registry+https://github.com/rust-lang/crates.io-index" 2027 | checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" 2028 | dependencies = [ 2029 | "proc-macro2", 2030 | "quote", 2031 | "syn", 2032 | ] 2033 | 2034 | [[package]] 2035 | name = "system-configuration" 2036 | version = "0.6.1" 2037 | source = "registry+https://github.com/rust-lang/crates.io-index" 2038 | checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" 2039 | dependencies = [ 2040 | "bitflags", 2041 | "core-foundation", 2042 | "system-configuration-sys", 2043 | ] 2044 | 2045 | [[package]] 2046 | name = "system-configuration-sys" 2047 | version = "0.6.0" 2048 | source = "registry+https://github.com/rust-lang/crates.io-index" 2049 | checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" 2050 | dependencies = [ 2051 | "core-foundation-sys", 2052 | "libc", 2053 | ] 2054 | 2055 | [[package]] 2056 | name = "tagptr" 2057 | version = "0.2.0" 2058 | source = "registry+https://github.com/rust-lang/crates.io-index" 2059 | checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" 2060 | 2061 | [[package]] 2062 | name = "thiserror" 2063 | version = "1.0.69" 2064 | source = "registry+https://github.com/rust-lang/crates.io-index" 2065 | checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" 2066 | dependencies = [ 2067 | "thiserror-impl 1.0.69", 2068 | ] 2069 | 2070 | [[package]] 2071 | name = "thiserror" 2072 | version = "2.0.17" 2073 | source = "registry+https://github.com/rust-lang/crates.io-index" 2074 | checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" 2075 | dependencies = [ 2076 | "thiserror-impl 2.0.17", 2077 | ] 2078 | 2079 | [[package]] 2080 | name = "thiserror-impl" 2081 | version = "1.0.69" 2082 | source = "registry+https://github.com/rust-lang/crates.io-index" 2083 | checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" 2084 | dependencies = [ 2085 | "proc-macro2", 2086 | "quote", 2087 | "syn", 2088 | ] 2089 | 2090 | [[package]] 2091 | name = "thiserror-impl" 2092 | version = "2.0.17" 2093 | source = "registry+https://github.com/rust-lang/crates.io-index" 2094 | checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" 2095 | dependencies = [ 2096 | "proc-macro2", 2097 | "quote", 2098 | "syn", 2099 | ] 2100 | 2101 | [[package]] 2102 | name = "thousands" 2103 | version = "0.2.0" 2104 | source = "registry+https://github.com/rust-lang/crates.io-index" 2105 | checksum = "3bf63baf9f5039dadc247375c29eb13706706cfde997d0330d05aa63a77d8820" 2106 | 2107 | [[package]] 2108 | name = "thread_local" 2109 | version = "1.1.9" 2110 | source = "registry+https://github.com/rust-lang/crates.io-index" 2111 | checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" 2112 | dependencies = [ 2113 | "cfg-if", 2114 | ] 2115 | 2116 | [[package]] 2117 | name = "tikv-jemalloc-sys" 2118 | version = "0.6.1+5.3.0-1-ge13ca993e8ccb9ba9847cc330696e02839f328f7" 2119 | source = "registry+https://github.com/rust-lang/crates.io-index" 2120 | checksum = "cd8aa5b2ab86a2cefa406d889139c162cbb230092f7d1d7cbc1716405d852a3b" 2121 | dependencies = [ 2122 | "cc", 2123 | "libc", 2124 | ] 2125 | 2126 | [[package]] 2127 | name = "tikv-jemallocator" 2128 | version = "0.6.1" 2129 | source = "registry+https://github.com/rust-lang/crates.io-index" 2130 | checksum = "0359b4327f954e0567e69fb191cf1436617748813819c94b8cd4a431422d053a" 2131 | dependencies = [ 2132 | "libc", 2133 | "tikv-jemalloc-sys", 2134 | ] 2135 | 2136 | [[package]] 2137 | name = "tinystr" 2138 | version = "0.8.2" 2139 | source = "registry+https://github.com/rust-lang/crates.io-index" 2140 | checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" 2141 | dependencies = [ 2142 | "displaydoc", 2143 | "zerovec", 2144 | ] 2145 | 2146 | [[package]] 2147 | name = "tinyvec" 2148 | version = "1.10.0" 2149 | source = "registry+https://github.com/rust-lang/crates.io-index" 2150 | checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" 2151 | dependencies = [ 2152 | "tinyvec_macros", 2153 | ] 2154 | 2155 | [[package]] 2156 | name = "tinyvec_macros" 2157 | version = "0.1.1" 2158 | source = "registry+https://github.com/rust-lang/crates.io-index" 2159 | checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" 2160 | 2161 | [[package]] 2162 | name = "tokio" 2163 | version = "1.48.0" 2164 | source = "registry+https://github.com/rust-lang/crates.io-index" 2165 | checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" 2166 | dependencies = [ 2167 | "bytes", 2168 | "libc", 2169 | "mio", 2170 | "parking_lot", 2171 | "pin-project-lite", 2172 | "signal-hook-registry", 2173 | "socket2 0.6.1", 2174 | "tokio-macros", 2175 | "windows-sys 0.61.2", 2176 | ] 2177 | 2178 | [[package]] 2179 | name = "tokio-macros" 2180 | version = "2.6.0" 2181 | source = "registry+https://github.com/rust-lang/crates.io-index" 2182 | checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" 2183 | dependencies = [ 2184 | "proc-macro2", 2185 | "quote", 2186 | "syn", 2187 | ] 2188 | 2189 | [[package]] 2190 | name = "tokio-rustls" 2191 | version = "0.26.4" 2192 | source = "registry+https://github.com/rust-lang/crates.io-index" 2193 | checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" 2194 | dependencies = [ 2195 | "rustls", 2196 | "tokio", 2197 | ] 2198 | 2199 | [[package]] 2200 | name = "tokio-util" 2201 | version = "0.7.17" 2202 | source = "registry+https://github.com/rust-lang/crates.io-index" 2203 | checksum = "2efa149fe76073d6e8fd97ef4f4eca7b67f599660115591483572e406e165594" 2204 | dependencies = [ 2205 | "bytes", 2206 | "futures-core", 2207 | "futures-sink", 2208 | "pin-project-lite", 2209 | "tokio", 2210 | ] 2211 | 2212 | [[package]] 2213 | name = "toml" 2214 | version = "0.9.8" 2215 | source = "registry+https://github.com/rust-lang/crates.io-index" 2216 | checksum = "f0dc8b1fb61449e27716ec0e1bdf0f6b8f3e8f6b05391e8497b8b6d7804ea6d8" 2217 | dependencies = [ 2218 | "indexmap", 2219 | "serde_core", 2220 | "serde_spanned", 2221 | "toml_datetime", 2222 | "toml_parser", 2223 | "toml_writer", 2224 | "winnow", 2225 | ] 2226 | 2227 | [[package]] 2228 | name = "toml_datetime" 2229 | version = "0.7.3" 2230 | source = "registry+https://github.com/rust-lang/crates.io-index" 2231 | checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533" 2232 | dependencies = [ 2233 | "serde_core", 2234 | ] 2235 | 2236 | [[package]] 2237 | name = "toml_parser" 2238 | version = "1.0.4" 2239 | source = "registry+https://github.com/rust-lang/crates.io-index" 2240 | checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e" 2241 | dependencies = [ 2242 | "winnow", 2243 | ] 2244 | 2245 | [[package]] 2246 | name = "toml_writer" 2247 | version = "1.0.4" 2248 | source = "registry+https://github.com/rust-lang/crates.io-index" 2249 | checksum = "df8b2b54733674ad286d16267dcfc7a71ed5c776e4ac7aa3c3e2561f7c637bf2" 2250 | 2251 | [[package]] 2252 | name = "tower" 2253 | version = "0.5.2" 2254 | source = "registry+https://github.com/rust-lang/crates.io-index" 2255 | checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" 2256 | dependencies = [ 2257 | "futures-core", 2258 | "futures-util", 2259 | "pin-project-lite", 2260 | "sync_wrapper", 2261 | "tokio", 2262 | "tower-layer", 2263 | "tower-service", 2264 | ] 2265 | 2266 | [[package]] 2267 | name = "tower-http" 2268 | version = "0.6.8" 2269 | source = "registry+https://github.com/rust-lang/crates.io-index" 2270 | checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" 2271 | dependencies = [ 2272 | "async-compression", 2273 | "bitflags", 2274 | "bytes", 2275 | "futures-core", 2276 | "futures-util", 2277 | "http", 2278 | "http-body", 2279 | "http-body-util", 2280 | "iri-string", 2281 | "pin-project-lite", 2282 | "tokio", 2283 | "tokio-util", 2284 | "tower", 2285 | "tower-layer", 2286 | "tower-service", 2287 | ] 2288 | 2289 | [[package]] 2290 | name = "tower-layer" 2291 | version = "0.3.3" 2292 | source = "registry+https://github.com/rust-lang/crates.io-index" 2293 | checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" 2294 | 2295 | [[package]] 2296 | name = "tower-service" 2297 | version = "0.3.3" 2298 | source = "registry+https://github.com/rust-lang/crates.io-index" 2299 | checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" 2300 | 2301 | [[package]] 2302 | name = "tracing" 2303 | version = "0.1.43" 2304 | source = "registry+https://github.com/rust-lang/crates.io-index" 2305 | checksum = "2d15d90a0b5c19378952d479dc858407149d7bb45a14de0142f6c534b16fc647" 2306 | dependencies = [ 2307 | "pin-project-lite", 2308 | "tracing-attributes", 2309 | "tracing-core", 2310 | ] 2311 | 2312 | [[package]] 2313 | name = "tracing-attributes" 2314 | version = "0.1.31" 2315 | source = "registry+https://github.com/rust-lang/crates.io-index" 2316 | checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" 2317 | dependencies = [ 2318 | "proc-macro2", 2319 | "quote", 2320 | "syn", 2321 | ] 2322 | 2323 | [[package]] 2324 | name = "tracing-core" 2325 | version = "0.1.35" 2326 | source = "registry+https://github.com/rust-lang/crates.io-index" 2327 | checksum = "7a04e24fab5c89c6a36eb8558c9656f30d81de51dfa4d3b45f26b21d61fa0a6c" 2328 | dependencies = [ 2329 | "once_cell", 2330 | "valuable", 2331 | ] 2332 | 2333 | [[package]] 2334 | name = "tracing-error" 2335 | version = "0.2.1" 2336 | source = "registry+https://github.com/rust-lang/crates.io-index" 2337 | checksum = "8b1581020d7a273442f5b45074a6a57d5757ad0a47dac0e9f0bd57b81936f3db" 2338 | dependencies = [ 2339 | "tracing", 2340 | "tracing-subscriber", 2341 | ] 2342 | 2343 | [[package]] 2344 | name = "tracing-log" 2345 | version = "0.2.0" 2346 | source = "registry+https://github.com/rust-lang/crates.io-index" 2347 | checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" 2348 | dependencies = [ 2349 | "log", 2350 | "once_cell", 2351 | "tracing-core", 2352 | ] 2353 | 2354 | [[package]] 2355 | name = "tracing-subscriber" 2356 | version = "0.3.22" 2357 | source = "registry+https://github.com/rust-lang/crates.io-index" 2358 | checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e" 2359 | dependencies = [ 2360 | "nu-ansi-term", 2361 | "parking_lot", 2362 | "sharded-slab", 2363 | "smallvec", 2364 | "thread_local", 2365 | "tracing-core", 2366 | "tracing-log", 2367 | ] 2368 | 2369 | [[package]] 2370 | name = "try-lock" 2371 | version = "0.2.5" 2372 | source = "registry+https://github.com/rust-lang/crates.io-index" 2373 | checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" 2374 | 2375 | [[package]] 2376 | name = "tui-logger" 2377 | version = "0.17.4" 2378 | source = "registry+https://github.com/rust-lang/crates.io-index" 2379 | checksum = "382b7ea88082dbe2236ed1e942552b1bfc59e98fdc5d0599f11a627aae9ee2be" 2380 | dependencies = [ 2381 | "chrono", 2382 | "env_filter", 2383 | "lazy_static", 2384 | "log", 2385 | "parking_lot", 2386 | "ratatui", 2387 | "tracing", 2388 | "tracing-subscriber", 2389 | "unicode-segmentation", 2390 | ] 2391 | 2392 | [[package]] 2393 | name = "unicode-ident" 2394 | version = "1.0.22" 2395 | source = "registry+https://github.com/rust-lang/crates.io-index" 2396 | checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" 2397 | 2398 | [[package]] 2399 | name = "unicode-segmentation" 2400 | version = "1.12.0" 2401 | source = "registry+https://github.com/rust-lang/crates.io-index" 2402 | checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" 2403 | 2404 | [[package]] 2405 | name = "unicode-truncate" 2406 | version = "1.1.0" 2407 | source = "registry+https://github.com/rust-lang/crates.io-index" 2408 | checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf" 2409 | dependencies = [ 2410 | "itertools 0.13.0", 2411 | "unicode-segmentation", 2412 | "unicode-width 0.1.14", 2413 | ] 2414 | 2415 | [[package]] 2416 | name = "unicode-width" 2417 | version = "0.1.14" 2418 | source = "registry+https://github.com/rust-lang/crates.io-index" 2419 | checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" 2420 | 2421 | [[package]] 2422 | name = "unicode-width" 2423 | version = "0.2.0" 2424 | source = "registry+https://github.com/rust-lang/crates.io-index" 2425 | checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" 2426 | 2427 | [[package]] 2428 | name = "untrusted" 2429 | version = "0.9.0" 2430 | source = "registry+https://github.com/rust-lang/crates.io-index" 2431 | checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" 2432 | 2433 | [[package]] 2434 | name = "url" 2435 | version = "2.5.7" 2436 | source = "registry+https://github.com/rust-lang/crates.io-index" 2437 | checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" 2438 | dependencies = [ 2439 | "form_urlencoded", 2440 | "idna", 2441 | "percent-encoding", 2442 | "serde", 2443 | ] 2444 | 2445 | [[package]] 2446 | name = "utf8_iter" 2447 | version = "1.0.4" 2448 | source = "registry+https://github.com/rust-lang/crates.io-index" 2449 | checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" 2450 | 2451 | [[package]] 2452 | name = "uuid" 2453 | version = "1.19.0" 2454 | source = "registry+https://github.com/rust-lang/crates.io-index" 2455 | checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a" 2456 | dependencies = [ 2457 | "getrandom 0.3.4", 2458 | "js-sys", 2459 | "wasm-bindgen", 2460 | ] 2461 | 2462 | [[package]] 2463 | name = "valuable" 2464 | version = "0.1.1" 2465 | source = "registry+https://github.com/rust-lang/crates.io-index" 2466 | checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" 2467 | 2468 | [[package]] 2469 | name = "want" 2470 | version = "0.3.1" 2471 | source = "registry+https://github.com/rust-lang/crates.io-index" 2472 | checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" 2473 | dependencies = [ 2474 | "try-lock", 2475 | ] 2476 | 2477 | [[package]] 2478 | name = "wasi" 2479 | version = "0.11.1+wasi-snapshot-preview1" 2480 | source = "registry+https://github.com/rust-lang/crates.io-index" 2481 | checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" 2482 | 2483 | [[package]] 2484 | name = "wasip2" 2485 | version = "1.0.1+wasi-0.2.4" 2486 | source = "registry+https://github.com/rust-lang/crates.io-index" 2487 | checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" 2488 | dependencies = [ 2489 | "wit-bindgen", 2490 | ] 2491 | 2492 | [[package]] 2493 | name = "wasm-bindgen" 2494 | version = "0.2.106" 2495 | source = "registry+https://github.com/rust-lang/crates.io-index" 2496 | checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd" 2497 | dependencies = [ 2498 | "cfg-if", 2499 | "once_cell", 2500 | "rustversion", 2501 | "wasm-bindgen-macro", 2502 | "wasm-bindgen-shared", 2503 | ] 2504 | 2505 | [[package]] 2506 | name = "wasm-bindgen-futures" 2507 | version = "0.4.56" 2508 | source = "registry+https://github.com/rust-lang/crates.io-index" 2509 | checksum = "836d9622d604feee9e5de25ac10e3ea5f2d65b41eac0d9ce72eb5deae707ce7c" 2510 | dependencies = [ 2511 | "cfg-if", 2512 | "js-sys", 2513 | "once_cell", 2514 | "wasm-bindgen", 2515 | "web-sys", 2516 | ] 2517 | 2518 | [[package]] 2519 | name = "wasm-bindgen-macro" 2520 | version = "0.2.106" 2521 | source = "registry+https://github.com/rust-lang/crates.io-index" 2522 | checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3" 2523 | dependencies = [ 2524 | "quote", 2525 | "wasm-bindgen-macro-support", 2526 | ] 2527 | 2528 | [[package]] 2529 | name = "wasm-bindgen-macro-support" 2530 | version = "0.2.106" 2531 | source = "registry+https://github.com/rust-lang/crates.io-index" 2532 | checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40" 2533 | dependencies = [ 2534 | "bumpalo", 2535 | "proc-macro2", 2536 | "quote", 2537 | "syn", 2538 | "wasm-bindgen-shared", 2539 | ] 2540 | 2541 | [[package]] 2542 | name = "wasm-bindgen-shared" 2543 | version = "0.2.106" 2544 | source = "registry+https://github.com/rust-lang/crates.io-index" 2545 | checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4" 2546 | dependencies = [ 2547 | "unicode-ident", 2548 | ] 2549 | 2550 | [[package]] 2551 | name = "web-sys" 2552 | version = "0.3.83" 2553 | source = "registry+https://github.com/rust-lang/crates.io-index" 2554 | checksum = "9b32828d774c412041098d182a8b38b16ea816958e07cf40eec2bc080ae137ac" 2555 | dependencies = [ 2556 | "js-sys", 2557 | "wasm-bindgen", 2558 | ] 2559 | 2560 | [[package]] 2561 | name = "web-time" 2562 | version = "1.1.0" 2563 | source = "registry+https://github.com/rust-lang/crates.io-index" 2564 | checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" 2565 | dependencies = [ 2566 | "js-sys", 2567 | "wasm-bindgen", 2568 | ] 2569 | 2570 | [[package]] 2571 | name = "webpki-roots" 2572 | version = "1.0.4" 2573 | source = "registry+https://github.com/rust-lang/crates.io-index" 2574 | checksum = "b2878ef029c47c6e8cf779119f20fcf52bde7ad42a731b2a304bc221df17571e" 2575 | dependencies = [ 2576 | "rustls-pki-types", 2577 | ] 2578 | 2579 | [[package]] 2580 | name = "widestring" 2581 | version = "1.2.1" 2582 | source = "registry+https://github.com/rust-lang/crates.io-index" 2583 | checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471" 2584 | 2585 | [[package]] 2586 | name = "winapi" 2587 | version = "0.3.9" 2588 | source = "registry+https://github.com/rust-lang/crates.io-index" 2589 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 2590 | dependencies = [ 2591 | "winapi-i686-pc-windows-gnu", 2592 | "winapi-x86_64-pc-windows-gnu", 2593 | ] 2594 | 2595 | [[package]] 2596 | name = "winapi-i686-pc-windows-gnu" 2597 | version = "0.4.0" 2598 | source = "registry+https://github.com/rust-lang/crates.io-index" 2599 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 2600 | 2601 | [[package]] 2602 | name = "winapi-x86_64-pc-windows-gnu" 2603 | version = "0.4.0" 2604 | source = "registry+https://github.com/rust-lang/crates.io-index" 2605 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 2606 | 2607 | [[package]] 2608 | name = "windows-core" 2609 | version = "0.62.2" 2610 | source = "registry+https://github.com/rust-lang/crates.io-index" 2611 | checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" 2612 | dependencies = [ 2613 | "windows-implement", 2614 | "windows-interface", 2615 | "windows-link", 2616 | "windows-result", 2617 | "windows-strings", 2618 | ] 2619 | 2620 | [[package]] 2621 | name = "windows-implement" 2622 | version = "0.60.2" 2623 | source = "registry+https://github.com/rust-lang/crates.io-index" 2624 | checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" 2625 | dependencies = [ 2626 | "proc-macro2", 2627 | "quote", 2628 | "syn", 2629 | ] 2630 | 2631 | [[package]] 2632 | name = "windows-interface" 2633 | version = "0.59.3" 2634 | source = "registry+https://github.com/rust-lang/crates.io-index" 2635 | checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" 2636 | dependencies = [ 2637 | "proc-macro2", 2638 | "quote", 2639 | "syn", 2640 | ] 2641 | 2642 | [[package]] 2643 | name = "windows-link" 2644 | version = "0.2.1" 2645 | source = "registry+https://github.com/rust-lang/crates.io-index" 2646 | checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" 2647 | 2648 | [[package]] 2649 | name = "windows-registry" 2650 | version = "0.6.1" 2651 | source = "registry+https://github.com/rust-lang/crates.io-index" 2652 | checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" 2653 | dependencies = [ 2654 | "windows-link", 2655 | "windows-result", 2656 | "windows-strings", 2657 | ] 2658 | 2659 | [[package]] 2660 | name = "windows-result" 2661 | version = "0.4.1" 2662 | source = "registry+https://github.com/rust-lang/crates.io-index" 2663 | checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" 2664 | dependencies = [ 2665 | "windows-link", 2666 | ] 2667 | 2668 | [[package]] 2669 | name = "windows-strings" 2670 | version = "0.5.1" 2671 | source = "registry+https://github.com/rust-lang/crates.io-index" 2672 | checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" 2673 | dependencies = [ 2674 | "windows-link", 2675 | ] 2676 | 2677 | [[package]] 2678 | name = "windows-sys" 2679 | version = "0.48.0" 2680 | source = "registry+https://github.com/rust-lang/crates.io-index" 2681 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 2682 | dependencies = [ 2683 | "windows-targets 0.48.5", 2684 | ] 2685 | 2686 | [[package]] 2687 | name = "windows-sys" 2688 | version = "0.52.0" 2689 | source = "registry+https://github.com/rust-lang/crates.io-index" 2690 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 2691 | dependencies = [ 2692 | "windows-targets 0.52.6", 2693 | ] 2694 | 2695 | [[package]] 2696 | name = "windows-sys" 2697 | version = "0.59.0" 2698 | source = "registry+https://github.com/rust-lang/crates.io-index" 2699 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 2700 | dependencies = [ 2701 | "windows-targets 0.52.6", 2702 | ] 2703 | 2704 | [[package]] 2705 | name = "windows-sys" 2706 | version = "0.60.2" 2707 | source = "registry+https://github.com/rust-lang/crates.io-index" 2708 | checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" 2709 | dependencies = [ 2710 | "windows-targets 0.53.5", 2711 | ] 2712 | 2713 | [[package]] 2714 | name = "windows-sys" 2715 | version = "0.61.2" 2716 | source = "registry+https://github.com/rust-lang/crates.io-index" 2717 | checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" 2718 | dependencies = [ 2719 | "windows-link", 2720 | ] 2721 | 2722 | [[package]] 2723 | name = "windows-targets" 2724 | version = "0.48.5" 2725 | source = "registry+https://github.com/rust-lang/crates.io-index" 2726 | checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 2727 | dependencies = [ 2728 | "windows_aarch64_gnullvm 0.48.5", 2729 | "windows_aarch64_msvc 0.48.5", 2730 | "windows_i686_gnu 0.48.5", 2731 | "windows_i686_msvc 0.48.5", 2732 | "windows_x86_64_gnu 0.48.5", 2733 | "windows_x86_64_gnullvm 0.48.5", 2734 | "windows_x86_64_msvc 0.48.5", 2735 | ] 2736 | 2737 | [[package]] 2738 | name = "windows-targets" 2739 | version = "0.52.6" 2740 | source = "registry+https://github.com/rust-lang/crates.io-index" 2741 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 2742 | dependencies = [ 2743 | "windows_aarch64_gnullvm 0.52.6", 2744 | "windows_aarch64_msvc 0.52.6", 2745 | "windows_i686_gnu 0.52.6", 2746 | "windows_i686_gnullvm 0.52.6", 2747 | "windows_i686_msvc 0.52.6", 2748 | "windows_x86_64_gnu 0.52.6", 2749 | "windows_x86_64_gnullvm 0.52.6", 2750 | "windows_x86_64_msvc 0.52.6", 2751 | ] 2752 | 2753 | [[package]] 2754 | name = "windows-targets" 2755 | version = "0.53.5" 2756 | source = "registry+https://github.com/rust-lang/crates.io-index" 2757 | checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" 2758 | dependencies = [ 2759 | "windows-link", 2760 | "windows_aarch64_gnullvm 0.53.1", 2761 | "windows_aarch64_msvc 0.53.1", 2762 | "windows_i686_gnu 0.53.1", 2763 | "windows_i686_gnullvm 0.53.1", 2764 | "windows_i686_msvc 0.53.1", 2765 | "windows_x86_64_gnu 0.53.1", 2766 | "windows_x86_64_gnullvm 0.53.1", 2767 | "windows_x86_64_msvc 0.53.1", 2768 | ] 2769 | 2770 | [[package]] 2771 | name = "windows_aarch64_gnullvm" 2772 | version = "0.48.5" 2773 | source = "registry+https://github.com/rust-lang/crates.io-index" 2774 | checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" 2775 | 2776 | [[package]] 2777 | name = "windows_aarch64_gnullvm" 2778 | version = "0.52.6" 2779 | source = "registry+https://github.com/rust-lang/crates.io-index" 2780 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 2781 | 2782 | [[package]] 2783 | name = "windows_aarch64_gnullvm" 2784 | version = "0.53.1" 2785 | source = "registry+https://github.com/rust-lang/crates.io-index" 2786 | checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" 2787 | 2788 | [[package]] 2789 | name = "windows_aarch64_msvc" 2790 | version = "0.48.5" 2791 | source = "registry+https://github.com/rust-lang/crates.io-index" 2792 | checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" 2793 | 2794 | [[package]] 2795 | name = "windows_aarch64_msvc" 2796 | version = "0.52.6" 2797 | source = "registry+https://github.com/rust-lang/crates.io-index" 2798 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 2799 | 2800 | [[package]] 2801 | name = "windows_aarch64_msvc" 2802 | version = "0.53.1" 2803 | source = "registry+https://github.com/rust-lang/crates.io-index" 2804 | checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" 2805 | 2806 | [[package]] 2807 | name = "windows_i686_gnu" 2808 | version = "0.48.5" 2809 | source = "registry+https://github.com/rust-lang/crates.io-index" 2810 | checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" 2811 | 2812 | [[package]] 2813 | name = "windows_i686_gnu" 2814 | version = "0.52.6" 2815 | source = "registry+https://github.com/rust-lang/crates.io-index" 2816 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 2817 | 2818 | [[package]] 2819 | name = "windows_i686_gnu" 2820 | version = "0.53.1" 2821 | source = "registry+https://github.com/rust-lang/crates.io-index" 2822 | checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" 2823 | 2824 | [[package]] 2825 | name = "windows_i686_gnullvm" 2826 | version = "0.52.6" 2827 | source = "registry+https://github.com/rust-lang/crates.io-index" 2828 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 2829 | 2830 | [[package]] 2831 | name = "windows_i686_gnullvm" 2832 | version = "0.53.1" 2833 | source = "registry+https://github.com/rust-lang/crates.io-index" 2834 | checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" 2835 | 2836 | [[package]] 2837 | name = "windows_i686_msvc" 2838 | version = "0.48.5" 2839 | source = "registry+https://github.com/rust-lang/crates.io-index" 2840 | checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" 2841 | 2842 | [[package]] 2843 | name = "windows_i686_msvc" 2844 | version = "0.52.6" 2845 | source = "registry+https://github.com/rust-lang/crates.io-index" 2846 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 2847 | 2848 | [[package]] 2849 | name = "windows_i686_msvc" 2850 | version = "0.53.1" 2851 | source = "registry+https://github.com/rust-lang/crates.io-index" 2852 | checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" 2853 | 2854 | [[package]] 2855 | name = "windows_x86_64_gnu" 2856 | version = "0.48.5" 2857 | source = "registry+https://github.com/rust-lang/crates.io-index" 2858 | checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" 2859 | 2860 | [[package]] 2861 | name = "windows_x86_64_gnu" 2862 | version = "0.52.6" 2863 | source = "registry+https://github.com/rust-lang/crates.io-index" 2864 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 2865 | 2866 | [[package]] 2867 | name = "windows_x86_64_gnu" 2868 | version = "0.53.1" 2869 | source = "registry+https://github.com/rust-lang/crates.io-index" 2870 | checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" 2871 | 2872 | [[package]] 2873 | name = "windows_x86_64_gnullvm" 2874 | version = "0.48.5" 2875 | source = "registry+https://github.com/rust-lang/crates.io-index" 2876 | checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" 2877 | 2878 | [[package]] 2879 | name = "windows_x86_64_gnullvm" 2880 | version = "0.52.6" 2881 | source = "registry+https://github.com/rust-lang/crates.io-index" 2882 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 2883 | 2884 | [[package]] 2885 | name = "windows_x86_64_gnullvm" 2886 | version = "0.53.1" 2887 | source = "registry+https://github.com/rust-lang/crates.io-index" 2888 | checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" 2889 | 2890 | [[package]] 2891 | name = "windows_x86_64_msvc" 2892 | version = "0.48.5" 2893 | source = "registry+https://github.com/rust-lang/crates.io-index" 2894 | checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 2895 | 2896 | [[package]] 2897 | name = "windows_x86_64_msvc" 2898 | version = "0.52.6" 2899 | source = "registry+https://github.com/rust-lang/crates.io-index" 2900 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 2901 | 2902 | [[package]] 2903 | name = "windows_x86_64_msvc" 2904 | version = "0.53.1" 2905 | source = "registry+https://github.com/rust-lang/crates.io-index" 2906 | checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" 2907 | 2908 | [[package]] 2909 | name = "winnow" 2910 | version = "0.7.14" 2911 | source = "registry+https://github.com/rust-lang/crates.io-index" 2912 | checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" 2913 | 2914 | [[package]] 2915 | name = "winreg" 2916 | version = "0.50.0" 2917 | source = "registry+https://github.com/rust-lang/crates.io-index" 2918 | checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" 2919 | dependencies = [ 2920 | "cfg-if", 2921 | "windows-sys 0.48.0", 2922 | ] 2923 | 2924 | [[package]] 2925 | name = "wit-bindgen" 2926 | version = "0.46.0" 2927 | source = "registry+https://github.com/rust-lang/crates.io-index" 2928 | checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" 2929 | 2930 | [[package]] 2931 | name = "writeable" 2932 | version = "0.6.2" 2933 | source = "registry+https://github.com/rust-lang/crates.io-index" 2934 | checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" 2935 | 2936 | [[package]] 2937 | name = "yoke" 2938 | version = "0.8.1" 2939 | source = "registry+https://github.com/rust-lang/crates.io-index" 2940 | checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" 2941 | dependencies = [ 2942 | "stable_deref_trait", 2943 | "yoke-derive", 2944 | "zerofrom", 2945 | ] 2946 | 2947 | [[package]] 2948 | name = "yoke-derive" 2949 | version = "0.8.1" 2950 | source = "registry+https://github.com/rust-lang/crates.io-index" 2951 | checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" 2952 | dependencies = [ 2953 | "proc-macro2", 2954 | "quote", 2955 | "syn", 2956 | "synstructure", 2957 | ] 2958 | 2959 | [[package]] 2960 | name = "zerocopy" 2961 | version = "0.8.31" 2962 | source = "registry+https://github.com/rust-lang/crates.io-index" 2963 | checksum = "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3" 2964 | dependencies = [ 2965 | "zerocopy-derive", 2966 | ] 2967 | 2968 | [[package]] 2969 | name = "zerocopy-derive" 2970 | version = "0.8.31" 2971 | source = "registry+https://github.com/rust-lang/crates.io-index" 2972 | checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a" 2973 | dependencies = [ 2974 | "proc-macro2", 2975 | "quote", 2976 | "syn", 2977 | ] 2978 | 2979 | [[package]] 2980 | name = "zerofrom" 2981 | version = "0.1.6" 2982 | source = "registry+https://github.com/rust-lang/crates.io-index" 2983 | checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" 2984 | dependencies = [ 2985 | "zerofrom-derive", 2986 | ] 2987 | 2988 | [[package]] 2989 | name = "zerofrom-derive" 2990 | version = "0.1.6" 2991 | source = "registry+https://github.com/rust-lang/crates.io-index" 2992 | checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" 2993 | dependencies = [ 2994 | "proc-macro2", 2995 | "quote", 2996 | "syn", 2997 | "synstructure", 2998 | ] 2999 | 3000 | [[package]] 3001 | name = "zeroize" 3002 | version = "1.8.2" 3003 | source = "registry+https://github.com/rust-lang/crates.io-index" 3004 | checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" 3005 | 3006 | [[package]] 3007 | name = "zerotrie" 3008 | version = "0.2.3" 3009 | source = "registry+https://github.com/rust-lang/crates.io-index" 3010 | checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" 3011 | dependencies = [ 3012 | "displaydoc", 3013 | "yoke", 3014 | "zerofrom", 3015 | ] 3016 | 3017 | [[package]] 3018 | name = "zerovec" 3019 | version = "0.11.5" 3020 | source = "registry+https://github.com/rust-lang/crates.io-index" 3021 | checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" 3022 | dependencies = [ 3023 | "yoke", 3024 | "zerofrom", 3025 | "zerovec-derive", 3026 | ] 3027 | 3028 | [[package]] 3029 | name = "zerovec-derive" 3030 | version = "0.11.2" 3031 | source = "registry+https://github.com/rust-lang/crates.io-index" 3032 | checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" 3033 | dependencies = [ 3034 | "proc-macro2", 3035 | "quote", 3036 | "syn", 3037 | ] 3038 | --------------------------------------------------------------------------------