feat: update dependencies and refactor KeyCheckerConfig for improved argument handling

main
Yoo1tic 2025-07-19 00:23:03 +08:00
parent e6d2309d9e
commit 43e535ec15
6 changed files with 74 additions and 109 deletions

View File

@ -4,21 +4,21 @@ version = "0.1.0"
edition = "2024" edition = "2024"
[dependencies] [dependencies]
anyhow = "1.0.98" anyhow = "1.0"
backon = "1" backon = "1"
clap = { version = "4", features = ["derive"] } clap = { version = "4.5", features = ["derive"] }
futures = "0.3" futures = "0.3"
regex = "1.11.1" regex = "1.11"
reqwest = { version = "0.12.22", features = ["json"] } reqwest = { version = "0.12", features = ["json"] }
serde_json = "1.0.140" serde_json = "1.0"
tokio = { version = "1.46", features = [ tokio = { version = "1.46", features = [
"macros", "macros",
"rt-multi-thread", "rt-multi-thread",
"time", "time",
"fs", "fs",
] } ] }
url = { version = "2.5.4", features = ["serde"] } url = { version = "2.5", features = ["serde"] }
async-stream = "0.3" async-stream = "0.3"
figment = { version = "0.10.19", features = ["env", "toml"] } figment = { version = "0.10", features = ["env", "toml"] }
serde = { version = "1.0.219", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
toml = "0.9" toml = "0.9"

View File

@ -5,9 +5,9 @@ use reqwest::Client;
use crate::config::KeyCheckerConfig; use crate::config::KeyCheckerConfig;
pub fn client_builder(config: &KeyCheckerConfig) -> Result<Client, reqwest::Error> { pub fn client_builder(config: &KeyCheckerConfig) -> Result<Client, reqwest::Error> {
let mut builder = Client::builder().timeout(Duration::from_secs(config.timeout_sec())); let mut builder = Client::builder().timeout(Duration::from_secs(config.timeout_sec));
if let Some(ref proxy_url) = config.proxy() { if let Some(ref proxy_url) = config.proxy {
builder = builder.proxy(reqwest::Proxy::all(proxy_url.clone())?); builder = builder.proxy(reqwest::Proxy::all(proxy_url.clone())?);
} }

View File

@ -1,4 +1,4 @@
use anyhow::{Ok, Result}; use anyhow::Result;
use clap::Parser; use clap::Parser;
use figment::{ use figment::{
Figment, Figment,
@ -10,55 +10,72 @@ use std::path::PathBuf;
use std::sync::LazyLock; use std::sync::LazyLock;
use url::Url; use url::Url;
#[derive(Debug, Serialize, Deserialize, Parser)] /// Cli arguments
#[derive(Parser, Debug, Serialize, Deserialize)]
struct Cli {
#[arg(short = 'i', long)]
#[serde(skip_serializing_if = "Option::is_none")]
input_path: Option<PathBuf>,
#[arg(short = 'o', long)]
#[serde(skip_serializing_if = "Option::is_none")]
output_path: Option<PathBuf>,
#[arg(short = 'b', long)]
#[serde(skip_serializing_if = "Option::is_none")]
backup_path: Option<PathBuf>,
#[arg(short = 'u', long)]
#[serde(skip_serializing_if = "Option::is_none")]
api_host: Option<Url>,
#[arg(short = 't', long)]
#[serde(skip_serializing_if = "Option::is_none")]
timeout_sec: Option<u64>,
#[arg(short = 'c', long)]
#[serde(skip_serializing_if = "Option::is_none")]
concurrency: Option<usize>,
#[arg(short = 'x', long)]
#[serde(skip_serializing_if = "Option::is_none")]
proxy: Option<Url>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct KeyCheckerConfig { pub struct KeyCheckerConfig {
// Input file path containing API keys to check. // Input file path containing API keys to check.
#[serde(default)] #[serde(default)]
#[arg(short, long)] pub input_path: PathBuf,
input_path: Option<PathBuf>,
// Output file path for valid API keys. // Output file path for valid API keys.
#[serde(default)] #[serde(default)]
#[arg(short, long)] pub output_path: PathBuf,
output_path: Option<PathBuf>,
// Backup file path for all API keys. // Backup file path for all API keys.
#[serde(default)] #[serde(default)]
#[arg(short, long)] pub backup_path: PathBuf,
backup_path: Option<PathBuf>,
// API host URL for key validation. // API host URL for key validation.
#[serde(default)] #[serde(default = "default_api_host")]
#[arg(short, long)] pub api_host: Url,
api_host: Option<Url>,
// Request timeout in seconds. // Request timeout in seconds.
#[serde(default)] #[serde(default)]
#[arg(short, long)] pub timeout_sec: u64,
timeout_sec: Option<u64>,
// Maximum number of concurrent requests. // Maximum number of concurrent requests.
#[serde(default)] #[serde(default)]
#[arg(short, long)] pub concurrency: usize,
concurrency: Option<usize>,
// Optional proxy URL for HTTP requests (e.g., --proxy http://user:pass@host:port). // Optional proxy URL for HTTP requests (e.g., --proxy http://user:pass@host:port).
#[serde(default)] #[serde(default)]
#[arg(short, long)] pub proxy: Option<Url>,
proxy: Option<Url>,
} }
impl Default for KeyCheckerConfig { impl Default for KeyCheckerConfig {
fn default() -> Self { fn default() -> Self {
Self { (*DEFAULT_CONFIG).clone()
input_path: Some(default_input_path()),
output_path: Some(default_output_path()),
backup_path: Some(default_backup_path()),
api_host: Some(default_api_host()),
timeout_sec: Some(default_timeout()),
concurrency: Some(default_concurrency()),
proxy: None,
}
} }
} }
impl KeyCheckerConfig { impl KeyCheckerConfig {
@ -73,81 +90,30 @@ impl KeyCheckerConfig {
fs::write(CONFIG_PATH.as_path(), toml_content)?; fs::write(CONFIG_PATH.as_path(), toml_content)?;
} }
// Load configuration from config.toml, environment variables, and defaults // Load configuration from config.toml, environment variables, and CLI arguments
let mut figment = Figment::new() let config: Self = Figment::new()
.merge(Serialized::defaults(Self::default())) .merge(Serialized::defaults(Self::default()))
.merge(Toml::file(CONFIG_PATH.as_path())) .merge(Toml::file(CONFIG_PATH.as_path()))
.merge(Env::prefixed("KEYCHECKER_")); .merge(Env::prefixed("KEYCHECKER_"))
.merge(Serialized::defaults(Cli::parse()))
.extract()?;
// Only merge non-None command line arguments dbg!(&config);
let cli_args = Self::parse();
if let Some(input_path) = cli_args.input_path {
figment = figment.merge(("input_path", input_path));
}
if let Some(output_path) = cli_args.output_path {
figment = figment.merge(("output_path", output_path));
}
if let Some(backup_path) = cli_args.backup_path {
figment = figment.merge(("backup_path", backup_path));
}
if let Some(api_host) = cli_args.api_host {
figment = figment.merge(("api_host", api_host));
}
if let Some(timeout_sec) = cli_args.timeout_sec {
figment = figment.merge(("timeout_sec", timeout_sec));
}
if let Some(concurrency) = cli_args.concurrency {
figment = figment.merge(("concurrency", concurrency));
}
if let Some(proxy) = cli_args.proxy {
figment = figment.merge(("proxy", proxy));
}
let config = figment.extract()?;
println!("Final loaded config: {:?}", config);
Ok(config) Ok(config)
} }
pub fn input_path(&self) -> PathBuf {
self.input_path.clone().unwrap_or_else(default_input_path)
}
pub fn output_path(&self) -> PathBuf {
self.output_path.clone().unwrap_or_else(default_output_path)
}
pub fn backup_path(&self) -> PathBuf {
self.backup_path.clone().unwrap_or_else(default_backup_path)
}
pub fn api_host(&self) -> Url {
self.api_host.clone().unwrap_or_else(default_api_host)
}
pub fn timeout_sec(&self) -> u64 {
self.timeout_sec.unwrap_or_else(default_timeout)
}
pub fn concurrency(&self) -> usize {
self.concurrency.unwrap_or_else(default_concurrency)
}
pub fn proxy(&self) -> Option<Url> {
self.proxy.clone()
}
} }
fn default_input_path() -> PathBuf { // Single LazyLock for entire default configuration
"keys.txt".into() static DEFAULT_CONFIG: LazyLock<KeyCheckerConfig> = LazyLock::new(|| KeyCheckerConfig {
} input_path: "keys.txt".into(),
output_path: "output_keys.txt".into(),
fn default_output_path() -> PathBuf { backup_path: "backup_keys.txt".into(),
"output_keys.txt".into() api_host: Url::parse("https://generativelanguage.googleapis.com/").unwrap(),
} timeout_sec: 15,
fn default_backup_path() -> PathBuf { concurrency: 50,
"backup_keys.txt".into() proxy: None,
} });
fn default_api_host() -> Url { fn default_api_host() -> Url {
Url::parse("https://generativelanguage.googleapis.com/").unwrap() DEFAULT_CONFIG.api_host.clone()
}
fn default_timeout() -> u64 {
20
}
fn default_concurrency() -> usize {
30
} }

View File

@ -61,7 +61,6 @@ pub async fn validate_key_with_retry(
async fn keytest(client: Client, api_host: &Url, key: &GeminiKey) -> Result<KeyStatus> { async fn keytest(client: Client, api_host: &Url, key: &GeminiKey) -> Result<KeyStatus> {
const API_PATH: &str = "v1beta/models/gemini-2.0-flash-exp:generateContent"; const API_PATH: &str = "v1beta/models/gemini-2.0-flash-exp:generateContent";
let full_url = api_host.join(API_PATH)?; let full_url = api_host.join(API_PATH)?;
let request_body = serde_json::json!({ let request_body = serde_json::json!({
"contents": [ "contents": [
{ {

View File

@ -7,7 +7,7 @@ use gemini_keychecker::validation::ValidationService;
#[tokio::main] #[tokio::main]
async fn main() -> Result<()> { async fn main() -> Result<()> {
let config = KeyCheckerConfig::load_config().unwrap(); let config = KeyCheckerConfig::load_config().unwrap();
let keys = load_keys(config.input_path().as_path())?; let keys = load_keys(config.input_path.as_path())?;
let client = client_builder(&config)?; let client = client_builder(&config)?;
let validation_service = ValidationService::new(config, client); let validation_service = ValidationService::new(config, client);

View File

@ -42,13 +42,13 @@ impl ValidationService {
// Create stream to validate keys concurrently // Create stream to validate keys concurrently
let valid_keys_stream = stream let valid_keys_stream = stream
.map(|key| validate_key_with_retry(self.client.to_owned(), self.config.api_host(), key)) .map(|key| validate_key_with_retry(self.client.to_owned(), self.config.api_host.clone(), key))
.buffer_unordered(self.config.concurrency()) .buffer_unordered(self.config.concurrency)
.filter_map(|r| async { r }); .filter_map(|r| async { r });
pin_mut!(valid_keys_stream); pin_mut!(valid_keys_stream);
// Open output file for writing valid keys // Open output file for writing valid keys
let output_file = fs::File::create(&self.config.output_path()).await?; let output_file = fs::File::create(&self.config.output_path).await?;
let mut buffer_writer = tokio::io::BufWriter::new(output_file); let mut buffer_writer = tokio::io::BufWriter::new(output_file);
// Process validated keys and write to output file // Process validated keys and write to output file