ChannelMessage.sender was used both for display (username) and as the reply target in Channel::send(). For Telegram, sender is the username (e.g. "unknown") while send() requires the numeric chat_id, causing "Bad Request: chat not found" errors. Add a dedicated reply_to field to ChannelMessage that stores the channel-specific reply address (Telegram chat_id, Discord channel_id, Slack channel, etc.). Update all channel implementations and dispatch code to use reply_to for send/start_typing/stop_typing calls. This also fixes the same latent bug in Discord and Slack channels where sender (user ID) was incorrectly passed as the reply target.
121 lines
3.1 KiB
Rust
121 lines
3.1 KiB
Rust
use super::traits::{Channel, ChannelMessage};
|
|
use async_trait::async_trait;
|
|
use tokio::io::{self, AsyncBufReadExt, BufReader};
|
|
use uuid::Uuid;
|
|
|
|
/// CLI channel — stdin/stdout, always available, zero deps
|
|
pub struct CliChannel;
|
|
|
|
impl CliChannel {
|
|
pub fn new() -> Self {
|
|
Self
|
|
}
|
|
}
|
|
|
|
#[async_trait]
|
|
impl Channel for CliChannel {
|
|
fn name(&self) -> &str {
|
|
"cli"
|
|
}
|
|
|
|
async fn send(&self, message: &str, _recipient: &str) -> anyhow::Result<()> {
|
|
println!("{message}");
|
|
Ok(())
|
|
}
|
|
|
|
async fn listen(&self, tx: tokio::sync::mpsc::Sender<ChannelMessage>) -> anyhow::Result<()> {
|
|
let stdin = io::stdin();
|
|
let reader = BufReader::new(stdin);
|
|
let mut lines = reader.lines();
|
|
|
|
while let Ok(Some(line)) = lines.next_line().await {
|
|
let line = line.trim().to_string();
|
|
if line.is_empty() {
|
|
continue;
|
|
}
|
|
if line == "/quit" || line == "/exit" {
|
|
break;
|
|
}
|
|
|
|
let msg = ChannelMessage {
|
|
id: Uuid::new_v4().to_string(),
|
|
sender: "user".to_string(),
|
|
reply_to: "user".to_string(),
|
|
content: line,
|
|
channel: "cli".to_string(),
|
|
timestamp: std::time::SystemTime::now()
|
|
.duration_since(std::time::UNIX_EPOCH)
|
|
.unwrap_or_default()
|
|
.as_secs(),
|
|
};
|
|
|
|
if tx.send(msg).await.is_err() {
|
|
break;
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn cli_channel_name() {
|
|
assert_eq!(CliChannel::new().name(), "cli");
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn cli_channel_send_does_not_panic() {
|
|
let ch = CliChannel::new();
|
|
let result = ch.send("hello", "user").await;
|
|
assert!(result.is_ok());
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn cli_channel_send_empty_message() {
|
|
let ch = CliChannel::new();
|
|
let result = ch.send("", "").await;
|
|
assert!(result.is_ok());
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn cli_channel_health_check() {
|
|
let ch = CliChannel::new();
|
|
assert!(ch.health_check().await);
|
|
}
|
|
|
|
#[test]
|
|
fn channel_message_struct() {
|
|
let msg = ChannelMessage {
|
|
id: "test-id".into(),
|
|
sender: "user".into(),
|
|
reply_to: "user".into(),
|
|
content: "hello".into(),
|
|
channel: "cli".into(),
|
|
timestamp: 1_234_567_890,
|
|
};
|
|
assert_eq!(msg.id, "test-id");
|
|
assert_eq!(msg.sender, "user");
|
|
assert_eq!(msg.content, "hello");
|
|
assert_eq!(msg.channel, "cli");
|
|
assert_eq!(msg.timestamp, 1_234_567_890);
|
|
}
|
|
|
|
#[test]
|
|
fn channel_message_clone() {
|
|
let msg = ChannelMessage {
|
|
id: "id".into(),
|
|
sender: "s".into(),
|
|
reply_to: "s".into(),
|
|
content: "c".into(),
|
|
channel: "ch".into(),
|
|
timestamp: 0,
|
|
};
|
|
let cloned = msg.clone();
|
|
assert_eq!(cloned.id, msg.id);
|
|
assert_eq!(cloned.content, msg.content);
|
|
}
|
|
}
|