1use super::{
7 Channel, ChannelCapabilities, ChannelMessage, ChannelStatus, ChannelType, ChannelUser,
8 MessageId, StreamingMode,
9};
10use crate::error::{ChannelError, RustantError};
11use async_trait::async_trait;
12use serde::{Deserialize, Serialize};
13
14#[derive(Debug, Clone, Serialize, Deserialize)]
16pub struct IrcConfig {
17 pub enabled: bool,
18 pub server: String,
19 pub port: u16,
20 pub nick: String,
21 pub channels: Vec<String>,
22 pub use_tls: bool,
23}
24
25impl Default for IrcConfig {
26 fn default() -> Self {
27 Self {
28 enabled: false,
29 server: String::new(),
30 port: 6667,
31 nick: "rustant".into(),
32 channels: Vec::new(),
33 use_tls: false,
34 }
35 }
36}
37
38#[async_trait]
40pub trait IrcConnection: Send + Sync {
41 async fn connect(&self) -> Result<(), String>;
42 async fn disconnect(&self) -> Result<(), String>;
43 async fn send_privmsg(&self, target: &str, text: &str) -> Result<(), String>;
44 async fn receive(&self) -> Result<Vec<IrcMessage>, String>;
45}
46
47#[derive(Debug, Clone)]
49pub struct IrcMessage {
50 pub nick: String,
51 pub channel: String,
52 pub text: String,
53}
54
55pub struct IrcChannel {
57 config: IrcConfig,
58 status: ChannelStatus,
59 connection: Box<dyn IrcConnection>,
60 name: String,
61}
62
63impl IrcChannel {
64 pub fn new(config: IrcConfig, connection: Box<dyn IrcConnection>) -> Self {
65 Self {
66 config,
67 status: ChannelStatus::Disconnected,
68 connection,
69 name: "irc".to_string(),
70 }
71 }
72
73 pub fn with_name(mut self, name: impl Into<String>) -> Self {
74 self.name = name.into();
75 self
76 }
77}
78
79#[async_trait]
80impl Channel for IrcChannel {
81 fn name(&self) -> &str {
82 &self.name
83 }
84
85 fn channel_type(&self) -> ChannelType {
86 ChannelType::Irc
87 }
88
89 async fn connect(&mut self) -> Result<(), RustantError> {
90 if self.config.server.is_empty() {
91 return Err(RustantError::Channel(ChannelError::ConnectionFailed {
92 name: self.name.clone(),
93 message: "No server configured".into(),
94 }));
95 }
96 self.connection.connect().await.map_err(|e| {
97 RustantError::Channel(ChannelError::ConnectionFailed {
98 name: self.name.clone(),
99 message: e,
100 })
101 })?;
102 self.status = ChannelStatus::Connected;
103 Ok(())
104 }
105
106 async fn disconnect(&mut self) -> Result<(), RustantError> {
107 let _ = self.connection.disconnect().await;
108 self.status = ChannelStatus::Disconnected;
109 Ok(())
110 }
111
112 async fn send_message(&self, msg: ChannelMessage) -> Result<MessageId, RustantError> {
113 if self.status != ChannelStatus::Connected {
114 return Err(RustantError::Channel(ChannelError::NotConnected {
115 name: self.name.clone(),
116 }));
117 }
118 let text = msg.content.as_text().unwrap_or("");
119 self.connection
120 .send_privmsg(&msg.channel_id, text)
121 .await
122 .map_err(|e| {
123 RustantError::Channel(ChannelError::SendFailed {
124 name: self.name.clone(),
125 message: e,
126 })
127 })?;
128 Ok(MessageId::random())
129 }
130
131 async fn receive_messages(&self) -> Result<Vec<ChannelMessage>, RustantError> {
132 let incoming = self.connection.receive().await.map_err(|e| {
133 RustantError::Channel(ChannelError::ConnectionFailed {
134 name: self.name.clone(),
135 message: e,
136 })
137 })?;
138
139 let messages = incoming
140 .into_iter()
141 .map(|m| {
142 let sender = ChannelUser::new(&m.nick, ChannelType::Irc);
143 ChannelMessage::text(ChannelType::Irc, &m.channel, sender, &m.text)
144 })
145 .collect();
146
147 Ok(messages)
148 }
149
150 fn status(&self) -> ChannelStatus {
151 self.status
152 }
153
154 fn capabilities(&self) -> ChannelCapabilities {
155 ChannelCapabilities {
156 supports_threads: false,
157 supports_reactions: false,
158 supports_files: false,
159 supports_voice: false,
160 supports_video: false,
161 max_message_length: Some(512),
162 supports_editing: false,
163 supports_deletion: false,
164 }
165 }
166
167 fn streaming_mode(&self) -> StreamingMode {
168 StreamingMode::WebSocket
169 }
170}
171
172pub struct RealIrcConnection {
174 server: String,
175 port: u16,
176 nick: String,
177 channels: Vec<String>,
178 writer: tokio::sync::Mutex<Option<tokio::io::WriteHalf<tokio::net::TcpStream>>>,
179}
180
181impl RealIrcConnection {
182 pub fn new(server: String, port: u16, nick: String, channels: Vec<String>) -> Self {
183 Self {
184 server,
185 port,
186 nick,
187 channels,
188 writer: tokio::sync::Mutex::new(None),
189 }
190 }
191}
192
193#[async_trait]
194impl IrcConnection for RealIrcConnection {
195 async fn connect(&self) -> Result<(), String> {
196 use tokio::io::{AsyncBufReadExt, AsyncWriteExt};
197
198 let addr = format!("{}:{}", self.server, self.port);
199 let stream = tokio::net::TcpStream::connect(&addr)
200 .await
201 .map_err(|e| format!("TCP connect error: {e}"))?;
202
203 let (reader, mut writer) = tokio::io::split(stream);
204
205 let nick_cmd = format!("NICK {}\r\n", self.nick);
207 let user_cmd = format!("USER {} 0 * :{}\r\n", self.nick, self.nick);
208 writer
209 .write_all(nick_cmd.as_bytes())
210 .await
211 .map_err(|e| format!("Write error: {e}"))?;
212 writer
213 .write_all(user_cmd.as_bytes())
214 .await
215 .map_err(|e| format!("Write error: {e}"))?;
216
217 for ch in &self.channels {
219 let join_cmd = format!("JOIN {}\r\n", ch);
220 writer
221 .write_all(join_cmd.as_bytes())
222 .await
223 .map_err(|e| format!("Write error: {e}"))?;
224 }
225
226 *self.writer.lock().await = Some(writer);
228
229 let mut buf_reader = tokio::io::BufReader::new(reader);
231 tokio::spawn(async move {
232 let mut line = String::new();
233 loop {
234 line.clear();
235 match buf_reader.read_line(&mut line).await {
236 Ok(0) | Err(_) => break,
237 Ok(_) => {}
238 }
239 }
240 });
241
242 Ok(())
243 }
244
245 async fn disconnect(&self) -> Result<(), String> {
246 use tokio::io::AsyncWriteExt;
247
248 if let Some(ref mut writer) = *self.writer.lock().await {
249 let _ = writer.write_all(b"QUIT :Leaving\r\n").await;
250 }
251 *self.writer.lock().await = None;
252 Ok(())
253 }
254
255 async fn send_privmsg(&self, target: &str, text: &str) -> Result<(), String> {
256 use tokio::io::AsyncWriteExt;
257
258 let mut guard = self.writer.lock().await;
259 let writer = guard.as_mut().ok_or_else(|| "Not connected".to_string())?;
260
261 let cmd = format!("PRIVMSG {} :{}\r\n", target, text);
262 writer
263 .write_all(cmd.as_bytes())
264 .await
265 .map_err(|e| format!("Write error: {e}"))?;
266
267 Ok(())
268 }
269
270 async fn receive(&self) -> Result<Vec<IrcMessage>, String> {
271 Ok(vec![])
274 }
275}
276
277pub fn create_irc_channel(config: IrcConfig) -> IrcChannel {
279 let conn = RealIrcConnection::new(
280 config.server.clone(),
281 config.port,
282 config.nick.clone(),
283 config.channels.clone(),
284 );
285 IrcChannel::new(config, Box::new(conn))
286}
287
288#[cfg(test)]
289mod tests {
290 use super::*;
291
292 struct MockIrcConnection;
293
294 #[async_trait]
295 impl IrcConnection for MockIrcConnection {
296 async fn connect(&self) -> Result<(), String> {
297 Ok(())
298 }
299 async fn disconnect(&self) -> Result<(), String> {
300 Ok(())
301 }
302 async fn send_privmsg(&self, _target: &str, _text: &str) -> Result<(), String> {
303 Ok(())
304 }
305 async fn receive(&self) -> Result<Vec<IrcMessage>, String> {
306 Ok(vec![])
307 }
308 }
309
310 #[test]
311 fn test_irc_channel_creation() {
312 let ch = IrcChannel::new(IrcConfig::default(), Box::new(MockIrcConnection));
313 assert_eq!(ch.name(), "irc");
314 assert_eq!(ch.channel_type(), ChannelType::Irc);
315 }
316
317 #[test]
318 fn test_irc_capabilities() {
319 let ch = IrcChannel::new(IrcConfig::default(), Box::new(MockIrcConnection));
320 let caps = ch.capabilities();
321 assert!(!caps.supports_threads);
322 assert!(!caps.supports_files);
323 assert_eq!(caps.max_message_length, Some(512));
324 }
325
326 #[test]
327 fn test_irc_streaming_mode() {
328 let ch = IrcChannel::new(IrcConfig::default(), Box::new(MockIrcConnection));
329 assert_eq!(ch.streaming_mode(), StreamingMode::WebSocket);
330 }
331
332 #[test]
333 fn test_irc_status_disconnected() {
334 let ch = IrcChannel::new(IrcConfig::default(), Box::new(MockIrcConnection));
335 assert_eq!(ch.status(), ChannelStatus::Disconnected);
336 }
337
338 #[tokio::test]
339 async fn test_irc_send_without_connect() {
340 let ch = IrcChannel::new(IrcConfig::default(), Box::new(MockIrcConnection));
341 let sender = ChannelUser::new("nick", ChannelType::Irc);
342 let msg = ChannelMessage::text(ChannelType::Irc, "#test", sender, "hi");
343 assert!(ch.send_message(msg).await.is_err());
344 }
345}