feat: update dependencies and refactor KeyCheckerConfig for improved argument handling
parent
e6d2309d9e
commit
43e535ec15
16
Cargo.toml
16
Cargo.toml
|
@ -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"
|
||||||
|
|
|
@ -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())?);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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": [
|
||||||
{
|
{
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue