1use crate::message::{Message, SendOptions};
42use crate::messenger::{Messenger, PresenceStatus, SearchQuery};
43use crate::server::ChatServer;
44use anyhow::Result;
45use async_trait::async_trait;
46use serde::{Deserialize, Serialize};
47
48#[derive(Debug, Clone, Serialize, Deserialize)]
52pub struct IrcConfig {
53 pub name: String,
54 pub server: String,
55 #[serde(default = "default_irc_port")]
56 pub port: u16,
57 pub nick: String,
58 #[serde(default)]
59 pub channels: Vec<String>,
60 #[serde(default)]
61 pub tls: bool,
62}
63fn default_irc_port() -> u16 {
64 6667
65}
66
67#[derive(Debug, Clone, Serialize, Deserialize)]
69pub struct DiscordConfig {
70 pub name: String,
71 pub token: String,
72}
73
74#[derive(Debug, Clone, Serialize, Deserialize)]
76pub struct TelegramConfig {
77 pub name: String,
78 pub token: String,
79}
80
81#[derive(Debug, Clone, Serialize, Deserialize)]
83pub struct SlackConfig {
84 pub name: String,
85 pub token: String,
86}
87
88#[derive(Debug, Clone, Serialize, Deserialize)]
93pub struct TeamsConfig {
94 pub name: String,
95 #[serde(default)]
96 pub webhook_url: Option<String>,
97 #[serde(default)]
98 pub token: Option<String>,
99 #[serde(default)]
100 pub team_id: Option<String>,
101 #[serde(default)]
102 pub channel_id: Option<String>,
103}
104
105#[derive(Debug, Clone, Serialize, Deserialize)]
110pub struct GoogleChatConfig {
111 pub name: String,
112 #[serde(default)]
113 pub webhook_url: Option<String>,
114 #[serde(default)]
115 pub token: Option<String>,
116 #[serde(default)]
117 pub space_id: Option<String>,
118}
119
120#[derive(Debug, Clone, Serialize, Deserialize)]
122pub struct ConsoleConfig {
123 pub name: String,
124}
125
126#[derive(Debug, Clone, Serialize, Deserialize)]
128pub struct WebhookConfig {
129 pub name: String,
130 pub url: String,
131}
132
133#[derive(Debug, Clone, Serialize, Deserialize)]
135pub struct IMessageConfig {
136 pub name: String,
137}
138
139#[cfg(feature = "matrix")]
140#[derive(Debug, Clone, Serialize, Deserialize)]
142pub struct MatrixConfig {
143 pub name: String,
144 pub homeserver: String,
145 pub username: String,
146 pub password: String,
147}
148
149#[cfg(feature = "signal-cli")]
150#[derive(Debug, Clone, Serialize, Deserialize)]
152pub struct SignalCliConfig {
153 pub name: String,
154 pub phone_number: String,
155 #[serde(default = "default_signal_cli_path")]
156 pub cli_path: String,
157}
158#[cfg(feature = "signal-cli")]
159fn default_signal_cli_path() -> String {
160 "signal-cli".to_string()
161}
162
163#[cfg(feature = "whatsapp")]
164#[derive(Debug, Clone, Serialize, Deserialize)]
166pub struct WhatsAppConfig {
167 pub name: String,
168 pub db_path: String,
170}
171
172#[derive(Debug, Clone, Serialize, Deserialize)]
183#[serde(tag = "protocol", rename_all = "lowercase")]
184pub enum MessengerConfig {
185 Irc(IrcConfig),
186 Discord(DiscordConfig),
187 Telegram(TelegramConfig),
188 Slack(SlackConfig),
189 Teams(TeamsConfig),
190 #[serde(rename = "googlechat")]
191 GoogleChat(GoogleChatConfig),
192 Console(ConsoleConfig),
193 Webhook(WebhookConfig),
194 #[serde(rename = "imessage")]
195 IMessage(IMessageConfig),
196 #[cfg(feature = "matrix")]
197 Matrix(MatrixConfig),
198 #[cfg(feature = "signal-cli")]
199 #[serde(rename = "signal")]
200 SignalCli(SignalCliConfig),
201 #[cfg(feature = "whatsapp")]
202 WhatsApp(WhatsAppConfig),
203}
204
205impl MessengerConfig {
206 pub fn name(&self) -> &str {
208 match self {
209 Self::Irc(c) => &c.name,
210 Self::Discord(c) => &c.name,
211 Self::Telegram(c) => &c.name,
212 Self::Slack(c) => &c.name,
213 Self::Teams(c) => &c.name,
214 Self::GoogleChat(c) => &c.name,
215 Self::Console(c) => &c.name,
216 Self::Webhook(c) => &c.name,
217 Self::IMessage(c) => &c.name,
218 #[cfg(feature = "matrix")]
219 Self::Matrix(c) => &c.name,
220 #[cfg(feature = "signal-cli")]
221 Self::SignalCli(c) => &c.name,
222 #[cfg(feature = "whatsapp")]
223 Self::WhatsApp(c) => &c.name,
224 }
225 }
226
227 pub fn protocol_name(&self) -> &'static str {
229 match self {
230 Self::Irc(_) => "irc",
231 Self::Discord(_) => "discord",
232 Self::Telegram(_) => "telegram",
233 Self::Slack(_) => "slack",
234 Self::Teams(_) => "teams",
235 Self::GoogleChat(_) => "googlechat",
236 Self::Console(_) => "console",
237 Self::Webhook(_) => "webhook",
238 Self::IMessage(_) => "imessage",
239 #[cfg(feature = "matrix")]
240 Self::Matrix(_) => "matrix",
241 #[cfg(feature = "signal-cli")]
242 Self::SignalCli(_) => "signal",
243 #[cfg(feature = "whatsapp")]
244 Self::WhatsApp(_) => "whatsapp",
245 }
246 }
247
248 pub fn build(&self) -> Result<Box<dyn Messenger>> {
254 use crate::messengers::*;
255 let m: Box<dyn Messenger> = match self {
256 Self::Irc(c) => Box::new(
257 IrcMessenger::new(&c.name, &c.server, c.port, &c.nick)
258 .with_channels(c.channels.clone())
259 .with_tls(c.tls),
260 ),
261 Self::Discord(c) => Box::new(DiscordMessenger::new(&c.name, &c.token)),
262 Self::Telegram(c) => Box::new(TelegramMessenger::new(&c.name, &c.token)),
263 Self::Slack(c) => Box::new(SlackMessenger::new(&c.name, &c.token)),
264 Self::Teams(c) => match (&c.webhook_url, &c.token, &c.team_id, &c.channel_id) {
265 (_, Some(token), Some(team_id), Some(channel_id)) => Box::new(
266 TeamsMessenger::new_graph(&c.name, token, team_id, channel_id),
267 ),
268 (Some(webhook_url), _, _, _) => Box::new(TeamsMessenger::new(&c.name, webhook_url)),
269 _ => anyhow::bail!(
270 "Teams config requires either webhook_url or token + team_id + channel_id"
271 ),
272 },
273 Self::GoogleChat(c) => match (&c.webhook_url, &c.token, &c.space_id) {
274 (_, Some(token), Some(space_id)) => {
275 Box::new(GoogleChatMessenger::new_api(&c.name, token, space_id))
276 }
277 (Some(webhook_url), _, _) => Box::new(GoogleChatMessenger::new(&c.name, webhook_url)),
278 _ => anyhow::bail!(
279 "Google Chat config requires either webhook_url or token + space_id"
280 ),
281 },
282 Self::Console(c) => Box::new(ConsoleMessenger::new(&c.name)),
283 Self::Webhook(c) => Box::new(WebhookMessenger::new(&c.name, &c.url)),
284 Self::IMessage(c) => Box::new(IMessageMessenger::new(&c.name)),
285 #[cfg(feature = "matrix")]
286 Self::Matrix(c) => Box::new(MatrixMessenger::new(
287 &c.name,
288 &c.homeserver,
289 &c.username,
290 &c.password,
291 )),
292 #[cfg(feature = "signal-cli")]
293 Self::SignalCli(c) => Box::new(
294 SignalCliMessenger::new(&c.name, &c.phone_number).with_cli_path(&c.cli_path),
295 ),
296 #[cfg(feature = "whatsapp")]
297 Self::WhatsApp(c) => Box::new(WhatsAppMessenger::new(&c.name, &c.db_path)),
298 };
299 Ok(m)
300 }
301}
302
303#[derive(Debug, Clone, Serialize, Deserialize)]
310pub struct IrcListenerConfig {
311 pub address: String,
313}
314
315#[typetag::serde(name = "irc")]
316impl ListenerConfig for IrcListenerConfig {
317 fn protocol(&self) -> &str {
318 "irc"
319 }
320
321 fn address(&self) -> &str {
322 &self.address
323 }
324
325 fn build(&self) -> Box<dyn crate::server::ChatListener> {
326 Box::new(crate::servers::IrcListener::new(&self.address))
327 }
328
329 fn clone_box(&self) -> Box<dyn ListenerConfig> {
330 Box::new(self.clone())
331 }
332
333 fn debug_fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
334 std::fmt::Debug::fmt(self, f)
335 }
336}
337
338#[typetag::serde(tag = "protocol")]
366pub trait ListenerConfig: Send + Sync {
367 fn protocol(&self) -> &str;
369
370 fn address(&self) -> &str;
372
373 fn build(&self) -> Box<dyn crate::server::ChatListener>;
376
377 fn clone_box(&self) -> Box<dyn ListenerConfig>;
379
380 fn debug_fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result;
382}
383
384impl Clone for Box<dyn ListenerConfig> {
385 fn clone(&self) -> Self {
386 self.clone_box()
387 }
388}
389
390impl std::fmt::Debug for dyn ListenerConfig {
391 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
392 self.debug_fmt(f)
393 }
394}
395
396#[derive(Clone, Debug, Serialize, Deserialize)]
415pub struct ServerConfig {
416 pub name: String,
417 pub listeners: Vec<Box<dyn ListenerConfig>>,
418}
419
420impl ServerConfig {
421 pub fn name(&self) -> &str {
423 &self.name
424 }
425
426 pub fn listener_configs(&self) -> &[Box<dyn ListenerConfig>] {
428 &self.listeners
429 }
430}
431
432pub struct GenericMessenger {
444 config: MessengerConfig,
445 inner: Option<Box<dyn Messenger>>,
446}
447
448impl GenericMessenger {
449 pub fn new(config: MessengerConfig) -> Self {
451 Self {
452 config,
453 inner: None,
454 }
455 }
456
457 pub fn config(&self) -> &MessengerConfig {
459 &self.config
460 }
461}
462
463#[async_trait]
464impl Messenger for GenericMessenger {
465 fn name(&self) -> &str {
466 self.inner
467 .as_ref()
468 .map(|m| m.name())
469 .unwrap_or_else(|| self.config.name())
470 }
471
472 fn messenger_type(&self) -> &str {
473 self.inner
474 .as_ref()
475 .map(|m| m.messenger_type())
476 .unwrap_or_else(|| self.config.protocol_name())
477 }
478
479 async fn initialize(&mut self) -> Result<()> {
480 let mut built = self.config.build()?;
481 built.initialize().await?;
482 self.inner = Some(built);
483 Ok(())
484 }
485
486 async fn send_message(&self, recipient: &str, content: &str) -> Result<String> {
487 self.inner
488 .as_ref()
489 .ok_or_else(|| anyhow::anyhow!("GenericMessenger not initialized"))?
490 .send_message(recipient, content)
491 .await
492 }
493
494 async fn send_message_with_options(&self, opts: SendOptions<'_>) -> Result<String> {
495 self.inner
496 .as_ref()
497 .ok_or_else(|| anyhow::anyhow!("GenericMessenger not initialized"))?
498 .send_message_with_options(opts)
499 .await
500 }
501
502 async fn receive_messages(&self) -> Result<Vec<Message>> {
503 self.inner
504 .as_ref()
505 .ok_or_else(|| anyhow::anyhow!("GenericMessenger not initialized"))?
506 .receive_messages()
507 .await
508 }
509
510 fn is_connected(&self) -> bool {
511 self.inner
512 .as_ref()
513 .map(|m| m.is_connected())
514 .unwrap_or(false)
515 }
516
517 async fn disconnect(&mut self) -> Result<()> {
518 if let Some(inner) = &mut self.inner {
519 inner.disconnect().await?;
520 }
521 Ok(())
522 }
523
524 async fn set_typing(&self, channel: &str, typing: bool) -> Result<()> {
525 if let Some(inner) = &self.inner {
526 inner.set_typing(channel, typing).await
527 } else {
528 Ok(())
529 }
530 }
531
532 async fn set_status(&self, status: PresenceStatus) -> Result<()> {
533 if let Some(inner) = &self.inner {
534 inner.set_status(status).await
535 } else {
536 Ok(())
537 }
538 }
539
540 async fn add_reaction(&self, message_id: &str, channel: &str, emoji: &str) -> Result<()> {
541 if let Some(inner) = &self.inner {
542 inner.add_reaction(message_id, channel, emoji).await
543 } else {
544 Ok(())
545 }
546 }
547
548 async fn remove_reaction(&self, message_id: &str, channel: &str, emoji: &str) -> Result<()> {
549 if let Some(inner) = &self.inner {
550 inner.remove_reaction(message_id, channel, emoji).await
551 } else {
552 Ok(())
553 }
554 }
555
556 async fn get_profile_picture(&self, user_id: &str) -> Result<Option<String>> {
557 if let Some(inner) = &self.inner {
558 inner.get_profile_picture(user_id).await
559 } else {
560 Ok(None)
561 }
562 }
563
564 async fn set_profile_picture(&self, url: &str) -> Result<()> {
565 if let Some(inner) = &self.inner {
566 inner.set_profile_picture(url).await
567 } else {
568 Ok(())
569 }
570 }
571
572 async fn set_text_status(&self, text: &str) -> Result<()> {
573 if let Some(inner) = &self.inner {
574 inner.set_text_status(text).await
575 } else {
576 Ok(())
577 }
578 }
579
580 async fn search_messages(&self, query: SearchQuery) -> Result<Vec<Message>> {
581 if let Some(inner) = &self.inner {
582 inner.search_messages(query).await
583 } else {
584 Ok(Vec::new())
585 }
586 }
587}
588
589pub struct GenericServer {
597 config: ServerConfig,
598 inner: Option<crate::server::Server>,
599}
600
601impl GenericServer {
602 pub fn new(config: ServerConfig) -> Self {
604 Self {
605 config,
606 inner: None,
607 }
608 }
609
610 pub fn config(&self) -> &ServerConfig {
612 &self.config
613 }
614
615 fn build_inner(&self) -> crate::server::Server {
616 let mut server = crate::server::Server::new(&self.config.name);
617 for lc in &self.config.listeners {
618 server = server.add_boxed_listener(lc.build());
619 }
620 server
621 }
622}
623
624#[async_trait]
625impl ChatServer for GenericServer {
626 fn name(&self) -> &str {
627 match &self.inner {
628 Some(s) => s.name(),
629 None => &self.config.name,
630 }
631 }
632
633 fn listeners(&self) -> Vec<&dyn crate::server::ChatListener> {
634 match &self.inner {
635 Some(s) => s.listeners(),
636 None => Vec::new(),
637 }
638 }
639
640 async fn run<F, Fut>(&mut self, handler: F) -> Result<()>
641 where
642 F: Fn(Message) -> Fut + Send + Sync + 'static,
643 Fut: std::future::Future<Output = Result<Option<String>>> + Send + 'static,
644 {
645 if self.inner.is_none() {
646 self.inner = Some(self.build_inner());
647 }
648 self.inner.as_mut().unwrap().run(handler).await
649 }
650
651 async fn shutdown(&mut self) -> Result<()> {
652 if let Some(s) = &mut self.inner {
653 s.shutdown().await
654 } else {
655 Ok(())
656 }
657 }
658}
659
660#[cfg(test)]
661mod tests {
662 use super::*;
663
664 #[test]
665 fn messenger_config_roundtrip_json() {
666 let cfg = MessengerConfig::Irc(IrcConfig {
667 name: "bot".into(),
668 server: "irc.libera.chat".into(),
669 port: 6697,
670 nick: "bot".into(),
671 channels: vec!["#rust".into()],
672 tls: true,
673 });
674 let json = serde_json::to_string(&cfg).unwrap();
675 let decoded: MessengerConfig = serde_json::from_str(&json).unwrap();
676 assert_eq!(decoded.protocol_name(), "irc");
677 assert_eq!(decoded.name(), "bot");
678 }
679
680 #[test]
681 fn messenger_config_deserialize_protocol_tag() {
682 let json = r#"{"protocol":"discord","name":"d-bot","token":"tok123"}"#;
683 let cfg: MessengerConfig = serde_json::from_str(json).unwrap();
684 assert_eq!(cfg.protocol_name(), "discord");
685 assert_eq!(cfg.name(), "d-bot");
686 }
687
688 #[test]
689 fn server_config_roundtrip_json() {
690 let cfg = ServerConfig {
691 name: "srv".into(),
692 listeners: vec![Box::new(IrcListenerConfig {
693 address: "0.0.0.0:6667".into(),
694 })],
695 };
696 let json = serde_json::to_string(&cfg).unwrap();
697 let decoded: ServerConfig = serde_json::from_str(&json).unwrap();
698 assert_eq!(decoded.name(), "srv");
699 assert_eq!(decoded.listeners.len(), 1);
700 assert_eq!(decoded.listeners[0].address(), "0.0.0.0:6667");
701 assert_eq!(decoded.listeners[0].protocol(), "irc");
702 }
703
704 #[test]
705 fn server_config_multi_listener_roundtrip_json() {
706 let cfg = ServerConfig {
707 name: "srv".into(),
708 listeners: vec![
709 Box::new(IrcListenerConfig {
710 address: "0.0.0.0:6667".into(),
711 }),
712 Box::new(IrcListenerConfig {
713 address: "0.0.0.0:6697".into(),
714 }),
715 ],
716 };
717 let json = serde_json::to_string(&cfg).unwrap();
718 let decoded: ServerConfig = serde_json::from_str(&json).unwrap();
719 assert_eq!(decoded.listeners.len(), 2);
720 assert_eq!(decoded.listeners[0].address(), "0.0.0.0:6667");
721 assert_eq!(decoded.listeners[1].address(), "0.0.0.0:6697");
722 }
723
724 #[test]
725 fn generic_messenger_name_before_init() {
726 let cfg = MessengerConfig::Console(ConsoleConfig { name: "con".into() });
727 let gm = GenericMessenger::new(cfg);
728 assert_eq!(gm.name(), "con");
729 assert_eq!(gm.messenger_type(), "console");
730 assert!(!gm.is_connected());
731 }
732
733 #[test]
734 fn generic_server_name_before_run() {
735 let cfg = ServerConfig {
736 name: "srv".into(),
737 listeners: vec![Box::new(IrcListenerConfig {
738 address: "127.0.0.1:7777".into(),
739 })],
740 };
741 let gs = GenericServer::new(cfg);
742 assert_eq!(gs.name(), "srv");
743 }
744
745 #[tokio::test]
746 async fn generic_messenger_set_typing_before_init_is_ok() {
747 let cfg = MessengerConfig::Console(ConsoleConfig { name: "con".into() });
748 let gm = GenericMessenger::new(cfg);
749 gm.set_typing("#general", true).await.unwrap();
751 gm.set_typing("#general", false).await.unwrap();
752 }
753
754 #[tokio::test]
755 async fn generic_messenger_set_typing_after_init_is_ok() {
756 let cfg = MessengerConfig::Console(ConsoleConfig { name: "con".into() });
757 let mut gm = GenericMessenger::new(cfg);
758 gm.initialize().await.unwrap();
759 gm.set_typing("#general", true).await.unwrap();
760 gm.set_typing("#general", false).await.unwrap();
761 gm.disconnect().await.unwrap();
762 }
763
764 #[tokio::test]
765 async fn generic_messenger_set_status_before_init_is_ok() {
766 let cfg = MessengerConfig::Console(ConsoleConfig { name: "con".into() });
767 let gm = GenericMessenger::new(cfg);
768 gm.set_status(PresenceStatus::Online).await.unwrap();
769 gm.set_status(PresenceStatus::Away).await.unwrap();
770 gm.set_status(PresenceStatus::Busy).await.unwrap();
771 gm.set_status(PresenceStatus::Invisible).await.unwrap();
772 gm.set_status(PresenceStatus::Offline).await.unwrap();
773 }
774
775 #[tokio::test]
776 async fn generic_messenger_set_status_after_init_is_ok() {
777 let cfg = MessengerConfig::Console(ConsoleConfig { name: "con".into() });
778 let mut gm = GenericMessenger::new(cfg);
779 gm.initialize().await.unwrap();
780 gm.set_status(PresenceStatus::Online).await.unwrap();
781 gm.set_status(PresenceStatus::Away).await.unwrap();
782 gm.disconnect().await.unwrap();
783 }
784
785 #[test]
786 fn presence_status_serde_roundtrip() {
787 for status in [
788 PresenceStatus::Online,
789 PresenceStatus::Away,
790 PresenceStatus::Busy,
791 PresenceStatus::Invisible,
792 PresenceStatus::Offline,
793 ] {
794 let json = serde_json::to_string(&status).unwrap();
795 let decoded: PresenceStatus = serde_json::from_str(&json).unwrap();
796 assert_eq!(decoded, status);
797 }
798 }
799
800 #[test]
801 fn presence_status_json_values() {
802 assert_eq!(
803 serde_json::to_string(&PresenceStatus::Online).unwrap(),
804 r#""online""#
805 );
806 assert_eq!(
807 serde_json::to_string(&PresenceStatus::Away).unwrap(),
808 r#""away""#
809 );
810 assert_eq!(
811 serde_json::to_string(&PresenceStatus::Busy).unwrap(),
812 r#""busy""#
813 );
814 assert_eq!(
815 serde_json::to_string(&PresenceStatus::Invisible).unwrap(),
816 r#""invisible""#
817 );
818 assert_eq!(
819 serde_json::to_string(&PresenceStatus::Offline).unwrap(),
820 r#""offline""#
821 );
822 }
823
824 #[tokio::test]
825 async fn generic_messenger_add_reaction_before_init_is_ok() {
826 let cfg = MessengerConfig::Console(ConsoleConfig { name: "con".into() });
827 let gm = GenericMessenger::new(cfg);
828 gm.add_reaction("msg-1", "#general", "👍").await.unwrap();
829 }
830
831 #[tokio::test]
832 async fn generic_messenger_add_reaction_after_init_is_ok() {
833 let cfg = MessengerConfig::Console(ConsoleConfig { name: "con".into() });
834 let mut gm = GenericMessenger::new(cfg);
835 gm.initialize().await.unwrap();
836 gm.add_reaction("msg-1", "#general", "👍").await.unwrap();
837 gm.disconnect().await.unwrap();
838 }
839
840 #[tokio::test]
841 async fn generic_messenger_remove_reaction_before_init_is_ok() {
842 let cfg = MessengerConfig::Console(ConsoleConfig { name: "con".into() });
843 let gm = GenericMessenger::new(cfg);
844 gm.remove_reaction("msg-1", "#general", "👍").await.unwrap();
845 }
846
847 #[tokio::test]
848 async fn generic_messenger_remove_reaction_after_init_is_ok() {
849 let cfg = MessengerConfig::Console(ConsoleConfig { name: "con".into() });
850 let mut gm = GenericMessenger::new(cfg);
851 gm.initialize().await.unwrap();
852 gm.remove_reaction("msg-1", "#general", "❤️").await.unwrap();
853 gm.disconnect().await.unwrap();
854 }
855
856 #[tokio::test]
857 async fn generic_messenger_get_profile_picture_before_init_returns_none() {
858 let cfg = MessengerConfig::Console(ConsoleConfig { name: "con".into() });
859 let gm = GenericMessenger::new(cfg);
860 let pic = gm.get_profile_picture("alice").await.unwrap();
861 assert!(pic.is_none());
862 }
863
864 #[tokio::test]
865 async fn generic_messenger_get_profile_picture_after_init_returns_none() {
866 let cfg = MessengerConfig::Console(ConsoleConfig { name: "con".into() });
867 let mut gm = GenericMessenger::new(cfg);
868 gm.initialize().await.unwrap();
869 let pic = gm.get_profile_picture("bob").await.unwrap();
870 assert!(pic.is_none());
871 gm.disconnect().await.unwrap();
872 }
873
874 #[tokio::test]
875 async fn generic_messenger_set_profile_picture_before_init_is_ok() {
876 let cfg = MessengerConfig::Console(ConsoleConfig { name: "con".into() });
877 let gm = GenericMessenger::new(cfg);
878 gm.set_profile_picture("https://example.com/avatar.png")
879 .await
880 .unwrap();
881 }
882
883 #[tokio::test]
884 async fn generic_messenger_set_profile_picture_after_init_is_ok() {
885 let cfg = MessengerConfig::Console(ConsoleConfig { name: "con".into() });
886 let mut gm = GenericMessenger::new(cfg);
887 gm.initialize().await.unwrap();
888 gm.set_profile_picture("https://example.com/avatar.png")
889 .await
890 .unwrap();
891 gm.disconnect().await.unwrap();
892 }
893
894 #[tokio::test]
895 async fn generic_messenger_set_text_status_before_init_is_ok() {
896 let cfg = MessengerConfig::Console(ConsoleConfig { name: "con".into() });
897 let gm = GenericMessenger::new(cfg);
898 gm.set_text_status("Working from home 🏠").await.unwrap();
899 }
900
901 #[tokio::test]
902 async fn generic_messenger_set_text_status_after_init_is_ok() {
903 let cfg = MessengerConfig::Console(ConsoleConfig { name: "con".into() });
904 let mut gm = GenericMessenger::new(cfg);
905 gm.initialize().await.unwrap();
906 gm.set_text_status("In a meeting").await.unwrap();
907 gm.disconnect().await.unwrap();
908 }
909
910 #[tokio::test]
911 async fn generic_messenger_search_messages_before_init_returns_empty() {
912 let cfg = MessengerConfig::Console(ConsoleConfig { name: "con".into() });
913 let gm = GenericMessenger::new(cfg);
914 let results = gm
915 .search_messages(SearchQuery {
916 text: "hello".into(),
917 ..Default::default()
918 })
919 .await
920 .unwrap();
921 assert!(results.is_empty());
922 }
923
924 #[tokio::test]
925 async fn generic_messenger_search_messages_after_init_returns_empty() {
926 let cfg = MessengerConfig::Console(ConsoleConfig { name: "con".into() });
927 let mut gm = GenericMessenger::new(cfg);
928 gm.initialize().await.unwrap();
929 let results = gm
930 .search_messages(SearchQuery {
931 text: "rust".into(),
932 channel: Some("#general".into()),
933 limit: Some(10),
934 ..Default::default()
935 })
936 .await
937 .unwrap();
938 assert!(results.is_empty());
939 gm.disconnect().await.unwrap();
940 }
941
942 #[test]
943 fn search_query_serde_roundtrip() {
944 let q = SearchQuery {
945 text: "hello world".into(),
946 channel: Some("#rust".into()),
947 from: Some("alice".into()),
948 limit: Some(50),
949 before_timestamp: Some(9_999_999),
950 after_timestamp: Some(1_000_000),
951 };
952 let json = serde_json::to_string(&q).unwrap();
953 let de: SearchQuery = serde_json::from_str(&json).unwrap();
954 assert_eq!(de.text, q.text);
955 assert_eq!(de.channel, q.channel);
956 assert_eq!(de.from, q.from);
957 assert_eq!(de.limit, q.limit);
958 assert_eq!(de.before_timestamp, q.before_timestamp);
959 assert_eq!(de.after_timestamp, q.after_timestamp);
960 }
961
962 #[test]
963 fn search_query_defaults() {
964 let q: SearchQuery = serde_json::from_str(r#"{"text":"hi"}"#).unwrap();
965 assert_eq!(q.text, "hi");
966 assert!(q.channel.is_none());
967 assert!(q.from.is_none());
968 assert!(q.limit.is_none());
969 assert!(q.before_timestamp.is_none());
970 assert!(q.after_timestamp.is_none());
971 }
972}