1use std::env;
2use std::io::{self, Write};
3use std::time::Duration;
4use std::thread;
5use std::fs::OpenOptions;
6use crossterm::{
7 event::{self, Event, KeyCode, KeyEvent},
8 terminal,
9};
10
11
12pub trait KeyFromEnv {
14 const KEY_NAME: &'static str;
16
17 fn find_key() -> Option<String> {
19 let _ = dotenvy::dotenv();
21
22 env::var(Self::KEY_NAME).ok()
24 }
25
26 fn find_key_with_user() -> String {
28 if let Some(key) = Self::find_key() {
29 return key;
30 }
31
32 print!("Environment variable {} not found. Please enter the API key (15 second timeout): ", Self::KEY_NAME);
34 io::stdout().flush().unwrap();
35
36 let (sender, receiver) = std::sync::mpsc::channel();
38
39 thread::spawn(move || {
41 let mut input = String::new();
42 if io::stdin().read_line(&mut input).is_ok() {
43 let _ = sender.send(input.trim().to_string());
44 }
45 });
46
47 let api_key = match receiver.recv_timeout(Duration::from_secs(15)) {
49 Ok(input) if !input.is_empty() => input,
50 _ => panic!("Timeout waiting for {} input after 15 seconds", Self::KEY_NAME),
51 };
52
53 if Self::prompt_save_to_env() {
55 if let Err(e) = Self::save_to_env_file(&api_key) {
56 eprintln!("Warning: Failed to save to .env file: {}", e);
57 } else {
58 println!("API key saved to .env file");
59 }
60 }
61
62 api_key
63 }
64
65 fn prompt_save_to_env() -> bool {
68 print!("Add {} to .env file? (y/N): ", Self::KEY_NAME);
69 io::stdout().flush().unwrap();
70
71 if let Ok(response) = Self::read_single_key() {
73 println!("{}", response); return response.to_lowercase() == "y";
75 }
76
77 let mut input = String::new();
79 if io::stdin().read_line(&mut input).is_ok() {
80 input.trim().to_lowercase() == "y"
81 } else {
82 false
83 }
84 }
85
86 fn read_single_key() -> Result<String, Box<dyn std::error::Error>> {
88 terminal::enable_raw_mode()?;
90
91 let result = if event::poll(Duration::from_secs(30))? {
92 if let Event::Key(KeyEvent { code, .. }) = event::read()? {
93 match code {
94 KeyCode::Char('y') | KeyCode::Char('Y') => Ok("y".to_string()),
95 KeyCode::Char('n') | KeyCode::Char('N') => Ok("n".to_string()),
96 KeyCode::Enter => Ok("n".to_string()), _ => Ok("n".to_string()), }
99 } else {
100 Ok("n".to_string())
101 }
102 } else {
103 Ok("n".to_string()) };
105
106 terminal::disable_raw_mode()?;
107 result
108 }
109
110 fn save_to_env_file(api_key: &str) -> Result<(), Box<dyn std::error::Error>> {
112 let env_line = format!("{}={}\n", Self::KEY_NAME, api_key);
113
114 if let Ok(content) = std::fs::read_to_string(".env") {
116 if content.contains(&format!("{}=", Self::KEY_NAME)) {
117 return Ok(());
119 }
120 }
121
122 let mut file = OpenOptions::new()
124 .create(true)
125 .append(true)
126 .open(".env")?;
127
128 use std::io::Write;
129 file.write_all(env_line.as_bytes())?;
130
131 Ok(())
132 }
133}