1use std::path::Path;
4use std::sync::Arc;
5
6use tokio_util::sync::CancellationToken;
7
8use crate::api::client::HttpApiClient;
9use crate::api::config_cache::ConfigCache;
10use crate::api::session_guard::SessionGuard;
11use crate::config::WeixinConfig;
12use crate::error::{Error, Result};
13use crate::messaging::inbound::{ContextTokenStore, SendResult};
14use crate::monitor::poll_loop::MessageHandler;
15use crate::qr_login::login::QrLoginApi;
16
17pub struct WeixinClient {
19 config: Arc<WeixinConfig>,
20 handler: Arc<dyn MessageHandler>,
21 api: Arc<HttpApiClient>,
22 session_guard: Arc<SessionGuard>,
23 config_cache: Arc<ConfigCache>,
24 context_tokens: Arc<ContextTokenStore>,
25 cancel: CancellationToken,
26}
27
28#[must_use]
30pub struct WeixinClientBuilder {
31 config: WeixinConfig,
32 handler: Option<Arc<dyn MessageHandler>>,
33}
34
35impl WeixinClient {
36 pub fn builder(config: WeixinConfig) -> WeixinClientBuilder {
38 WeixinClientBuilder {
39 config,
40 handler: None,
41 }
42 }
43
44 pub async fn start(&self, initial_sync_buf: Option<String>) -> Result<()> {
48 crate::monitor::poll_loop::run_monitor(
49 Arc::clone(&self.api),
50 self.config.cdn_base_url.clone(),
51 Arc::clone(&self.handler),
52 Arc::clone(&self.session_guard),
53 Arc::clone(&self.config_cache),
54 Arc::clone(&self.context_tokens),
55 initial_sync_buf,
56 self.config.long_poll_timeout,
57 self.cancel.clone(),
58 )
59 .await
60 }
61
62 pub fn shutdown(&self) {
64 self.cancel.cancel();
65 }
66
67 pub async fn send_text(
69 &self,
70 to: &str,
71 text: &str,
72 context_token: Option<&str>,
73 ) -> Result<SendResult> {
74 crate::messaging::send::send_text(&self.api, to, text, context_token).await
75 }
76
77 pub async fn send_media(
79 &self,
80 to: &str,
81 file_path: &Path,
82 context_token: Option<&str>,
83 ) -> Result<SendResult> {
84 crate::messaging::send_media::send_media_file(
85 &self.api,
86 &self.config.cdn_base_url,
87 to,
88 file_path,
89 "",
90 context_token,
91 )
92 .await
93 }
94
95 pub fn qr_login(&self) -> QrLoginApi<'_> {
97 QrLoginApi::new(&self.api)
98 }
99
100 pub fn context_tokens(&self) -> &ContextTokenStore {
102 &self.context_tokens
103 }
104}
105
106impl WeixinClientBuilder {
107 pub fn on_message(mut self, handler: impl MessageHandler + 'static) -> Self {
109 self.handler = Some(Arc::new(handler));
110 self
111 }
112
113 pub fn build(self) -> Result<WeixinClient> {
115 let handler = self
116 .handler
117 .ok_or_else(|| Error::Config("message handler is required".into()))?;
118 let api = Arc::new(HttpApiClient::new(&self.config));
119 let config_cache = Arc::new(ConfigCache::new(Arc::clone(&api)));
120 Ok(WeixinClient {
121 config: Arc::new(self.config),
122 handler,
123 api,
124 session_guard: Arc::new(SessionGuard::new()),
125 config_cache,
126 context_tokens: Arc::new(ContextTokenStore::new()),
127 cancel: CancellationToken::new(),
128 })
129 }
130}