Harden bot: line cap, send throttle, panic isolation, verbose flag, weather cache, tests
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
d9e279d8ed
commit
0094465ca9
7 changed files with 165 additions and 10 deletions
|
|
@ -1,20 +1,27 @@
|
|||
use std::io::{self, BufRead, BufReader, Read, Write};
|
||||
use std::net::TcpStream;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use native_tls::TlsConnector;
|
||||
|
||||
use super::Message;
|
||||
|
||||
const MAX_LINE: usize = 500;
|
||||
const SEND_THROTTLE: Duration = Duration::from_millis(300);
|
||||
|
||||
trait Stream: Read + Write {}
|
||||
impl<T: Read + Write> Stream for T {}
|
||||
|
||||
pub struct Connection {
|
||||
inner: BufReader<Box<dyn Stream>>,
|
||||
log_prefix: String,
|
||||
verbose: bool,
|
||||
throttle: Duration,
|
||||
last_send: Option<Instant>,
|
||||
}
|
||||
|
||||
impl Connection {
|
||||
pub fn connect(host: &str, port: u16, use_tls: bool) -> io::Result<Connection> {
|
||||
pub fn connect(host: &str, port: u16, use_tls: bool, verbose: bool) -> io::Result<Connection> {
|
||||
let tcp = TcpStream::connect((host, port))?;
|
||||
let stream: Box<dyn Stream> = if use_tls {
|
||||
let connector =
|
||||
|
|
@ -26,7 +33,24 @@ impl Connection {
|
|||
} else {
|
||||
Box::new(tcp)
|
||||
};
|
||||
Ok(Connection { inner: BufReader::new(stream), log_prefix: String::new() })
|
||||
Ok(Connection {
|
||||
inner: BufReader::new(stream),
|
||||
log_prefix: String::new(),
|
||||
verbose,
|
||||
throttle: SEND_THROTTLE,
|
||||
last_send: None,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn from_stream<S: Read + Write + 'static>(stream: S) -> Connection {
|
||||
let boxed: Box<dyn Stream> = Box::new(stream);
|
||||
Connection {
|
||||
inner: BufReader::new(boxed),
|
||||
log_prefix: String::new(),
|
||||
verbose: false,
|
||||
throttle: Duration::ZERO,
|
||||
last_send: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_label(&mut self, label: &str) {
|
||||
|
|
@ -47,7 +71,9 @@ impl Connection {
|
|||
if trimmed.is_empty() {
|
||||
continue;
|
||||
}
|
||||
eprintln!("{}<< {trimmed}", self.log_prefix);
|
||||
if self.verbose {
|
||||
eprintln!("{}<< {trimmed}", self.log_prefix);
|
||||
}
|
||||
if let Some(msg) = Message::parse(trimmed) {
|
||||
return Ok(Some(msg));
|
||||
}
|
||||
|
|
@ -55,8 +81,20 @@ impl Connection {
|
|||
}
|
||||
|
||||
pub fn send_raw(&mut self, line: &str) -> io::Result<()> {
|
||||
if !self.throttle.is_zero() {
|
||||
if let Some(last) = self.last_send {
|
||||
let elapsed = last.elapsed();
|
||||
if elapsed < self.throttle {
|
||||
std::thread::sleep(self.throttle - elapsed);
|
||||
}
|
||||
}
|
||||
self.last_send = Some(Instant::now());
|
||||
}
|
||||
let clean = line.replace(['\r', '\n'], " ");
|
||||
eprintln!("{}>> {clean}", self.log_prefix);
|
||||
let clean = truncate_to(&clean, MAX_LINE);
|
||||
if self.verbose {
|
||||
eprintln!("{}>> {clean}", self.log_prefix);
|
||||
}
|
||||
let writer = self.inner.get_mut();
|
||||
write!(writer, "{clean}\r\n")?;
|
||||
writer.flush()
|
||||
|
|
@ -110,3 +148,14 @@ impl Connection {
|
|||
self.send_raw(&format!("AUTHENTICATE {payload}"))
|
||||
}
|
||||
}
|
||||
|
||||
fn truncate_to(s: &str, max: usize) -> &str {
|
||||
if s.len() <= max {
|
||||
return s;
|
||||
}
|
||||
let mut end = max;
|
||||
while end > 0 && !s.is_char_boundary(end) {
|
||||
end -= 1;
|
||||
}
|
||||
&s[..end]
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue