1use serde::de::DeserializeOwned;
2
3use super::is_private_conversation;
4
5use crate::{
6 auth::AppCredentials,
7 client::async_client::Client,
8 error::{Error, Result},
9 transport::{
10 DEFAULT_MSG_KEY, parse_approval_create_response, parse_approval_get_response,
11 parse_get_token_response, parse_standard_api_text_response, parse_topapi_result_response,
12 parse_topapi_unit_response,
13 },
14 types::{
15 ApprovalCreateProcessInstanceRequest, ApprovalListProcessInstanceIdsRequest,
16 ApprovalListProcessInstanceIdsResult, ApprovalProcessInstance,
17 ApprovalTerminateProcessInstanceRequest, ContactCreateDepartmentRequest,
18 ContactCreateDepartmentResult, ContactCreateUserRequest, ContactCreateUserResult,
19 ContactDeleteDepartmentRequest, ContactDeleteUserRequest, ContactDepartment,
20 ContactGetDepartmentRequest, ContactGetUserByMobileRequest, ContactGetUserByUnionIdRequest,
21 ContactGetUserRequest, ContactListSubDepartmentIdsRequest,
22 ContactListSubDepartmentIdsResult, ContactListSubDepartmentsRequest,
23 ContactListSubDepartmentsResult, ContactListUsersRequest, ContactListUsersResult,
24 ContactUpdateDepartmentRequest, ContactUpdateUserRequest, ContactUser,
25 internal::{GroupMessageRequest, MsgParam, OtoMessageRequest},
26 },
27};
28
29#[derive(Clone)]
31pub struct EnterpriseService {
32 client: Client,
33 credentials: AppCredentials,
34 robot_code: String,
35}
36
37impl EnterpriseService {
38 pub(crate) fn new(
39 client: Client,
40 appkey: impl Into<String>,
41 appsecret: impl Into<String>,
42 robot_code: impl Into<String>,
43 ) -> Self {
44 Self {
45 client,
46 credentials: AppCredentials::new(appkey, appsecret),
47 robot_code: robot_code.into(),
48 }
49 }
50
51 pub async fn get_access_token(&self) -> Result<String> {
53 if let Some(token) = self.client.cached_access_token(&self.credentials) {
54 return Ok(token);
55 }
56
57 let endpoint = self.client.webhook_endpoint(&["gettoken"])?;
58 let payload = parse_get_token_response(
59 self.client
60 .webhook_http()
61 .get(endpoint.as_str())
62 .query_pair("appkey", self.credentials.appkey().to_string())
63 .query_pair("appsecret", self.credentials.appsecret().to_string())
64 .send_response()
65 .await?,
66 self.client.body_snippet(),
67 )?;
68 let access_token = payload.token;
69
70 self.client
71 .store_access_token(&self.credentials, access_token.clone(), payload.expires_in);
72
73 Ok(access_token)
74 }
75
76 async fn post_topapi_result<T, B>(&self, segments: &[&str], body: &B) -> Result<T>
77 where
78 T: DeserializeOwned,
79 B: serde::Serialize + ?Sized,
80 {
81 let access_token = self.get_access_token().await?;
82 let endpoint = self.client.webhook_endpoint(segments)?;
83 parse_topapi_result_response(
84 self.client
85 .webhook_http()
86 .post(endpoint.as_str())
87 .query_pair("access_token", access_token)
88 .json(body)?
89 .send_response()
90 .await?,
91 self.client.body_snippet(),
92 )
93 }
94
95 async fn post_topapi_unit<B>(&self, segments: &[&str], body: &B) -> Result<()>
96 where
97 B: serde::Serialize + ?Sized,
98 {
99 let access_token = self.get_access_token().await?;
100 let endpoint = self.client.webhook_endpoint(segments)?;
101 parse_topapi_unit_response(
102 self.client
103 .webhook_http()
104 .post(endpoint.as_str())
105 .query_pair("access_token", access_token)
106 .json(body)?
107 .send_response()
108 .await?,
109 self.client.body_snippet(),
110 )
111 }
112
113 async fn send_enterprise_message<T: serde::Serialize + ?Sized>(
114 &self,
115 segments: &[&str],
116 payload: &T,
117 ) -> Result<String> {
118 let access_token = self.get_access_token().await?;
119 let endpoint = self.client.enterprise_endpoint(segments)?;
120
121 parse_standard_api_text_response(
122 self.client
123 .enterprise_http()
124 .post(endpoint.as_str())
125 .try_header("x-acs-dingtalk-access-token", &access_token)?
126 .json(payload)?
127 .send_response()
128 .await?,
129 self.client.body_snippet(),
130 )
131 }
132
133 pub async fn send_group_message(
135 &self,
136 open_conversation_id: &str,
137 title: &str,
138 text: &str,
139 ) -> Result<String> {
140 let request = GroupMessageRequest {
141 msg_param: MsgParam {
142 title: title.to_string(),
143 text: text.to_string(),
144 },
145 msg_key: DEFAULT_MSG_KEY,
146 robot_code: &self.robot_code,
147 open_conversation_id,
148 };
149
150 self.send_enterprise_message(&["v1.0", "robot", "groupMessages", "send"], &request)
151 .await
152 }
153
154 pub async fn send_oto_message(&self, user_id: &str, title: &str, text: &str) -> Result<String> {
156 let request = OtoMessageRequest {
157 msg_param: MsgParam {
158 title: title.to_string(),
159 text: text.to_string(),
160 },
161 msg_key: DEFAULT_MSG_KEY,
162 robot_code: &self.robot_code,
163 user_ids: vec![user_id],
164 };
165
166 self.send_enterprise_message(&["v1.0", "robot", "oToMessages", "batchSend"], &request)
167 .await
168 }
169
170 pub async fn contact_get_user(&self, request: ContactGetUserRequest) -> Result<ContactUser> {
172 self.post_topapi_result(&["topapi", "v2", "user", "get"], &request)
173 .await
174 }
175
176 pub async fn contact_get_user_by_mobile(
178 &self,
179 request: ContactGetUserByMobileRequest,
180 ) -> Result<ContactUser> {
181 self.post_topapi_result(&["topapi", "v2", "user", "getbymobile"], &request)
182 .await
183 }
184
185 pub async fn contact_get_user_by_unionid(
187 &self,
188 request: ContactGetUserByUnionIdRequest,
189 ) -> Result<ContactUser> {
190 self.post_topapi_result(&["topapi", "user", "getbyunionid"], &request)
191 .await
192 }
193
194 pub async fn contact_list_users(
196 &self,
197 request: ContactListUsersRequest,
198 ) -> Result<ContactListUsersResult> {
199 self.post_topapi_result(&["topapi", "v2", "user", "list"], &request)
200 .await
201 }
202
203 pub async fn contact_create_user(
205 &self,
206 request: ContactCreateUserRequest,
207 ) -> Result<ContactCreateUserResult> {
208 self.post_topapi_result(&["topapi", "v2", "user", "create"], &request)
209 .await
210 }
211
212 pub async fn contact_update_user(&self, request: ContactUpdateUserRequest) -> Result<()> {
214 self.post_topapi_unit(&["topapi", "v2", "user", "update"], &request)
215 .await
216 }
217
218 pub async fn contact_delete_user(&self, request: ContactDeleteUserRequest) -> Result<()> {
220 self.post_topapi_unit(&["topapi", "v2", "user", "delete"], &request)
221 .await
222 }
223
224 pub async fn contact_get_department(
226 &self,
227 request: ContactGetDepartmentRequest,
228 ) -> Result<ContactDepartment> {
229 self.post_topapi_result(&["topapi", "v2", "department", "get"], &request)
230 .await
231 }
232
233 pub async fn contact_list_sub_departments(
235 &self,
236 request: ContactListSubDepartmentsRequest,
237 ) -> Result<ContactListSubDepartmentsResult> {
238 self.post_topapi_result(&["topapi", "v2", "department", "listsub"], &request)
239 .await
240 }
241
242 pub async fn contact_list_sub_department_ids(
244 &self,
245 request: ContactListSubDepartmentIdsRequest,
246 ) -> Result<ContactListSubDepartmentIdsResult> {
247 self.post_topapi_result(&["topapi", "v2", "department", "listsubid"], &request)
248 .await
249 }
250
251 pub async fn contact_create_department(
253 &self,
254 request: ContactCreateDepartmentRequest,
255 ) -> Result<ContactCreateDepartmentResult> {
256 self.post_topapi_result(&["topapi", "v2", "department", "create"], &request)
257 .await
258 }
259
260 pub async fn contact_update_department(
262 &self,
263 request: ContactUpdateDepartmentRequest,
264 ) -> Result<()> {
265 self.post_topapi_unit(&["topapi", "v2", "department", "update"], &request)
266 .await
267 }
268
269 pub async fn contact_delete_department(
271 &self,
272 request: ContactDeleteDepartmentRequest,
273 ) -> Result<()> {
274 self.post_topapi_unit(&["topapi", "v2", "department", "delete"], &request)
275 .await
276 }
277
278 pub async fn approval_create_process_instance(
280 &self,
281 request: ApprovalCreateProcessInstanceRequest,
282 ) -> Result<String> {
283 let access_token = self.get_access_token().await?;
284 let endpoint = self
285 .client
286 .webhook_endpoint(&["topapi", "processinstance", "create"])?;
287 parse_approval_create_response(
288 self.client
289 .webhook_http()
290 .post(endpoint.as_str())
291 .query_pair("access_token", access_token)
292 .json(&request)?
293 .send_response()
294 .await?,
295 self.client.body_snippet(),
296 )
297 }
298
299 pub async fn approval_get_process_instance(
301 &self,
302 process_instance_id: &str,
303 ) -> Result<ApprovalProcessInstance> {
304 let access_token = self.get_access_token().await?;
305 let endpoint = self
306 .client
307 .webhook_endpoint(&["topapi", "processinstance", "get"])?;
308 let request = serde_json::json!({
309 "process_instance_id": process_instance_id
310 });
311 parse_approval_get_response(
312 self.client
313 .webhook_http()
314 .post(endpoint.as_str())
315 .query_pair("access_token", access_token)
316 .json(&request)?
317 .send_response()
318 .await?,
319 self.client.body_snippet(),
320 )
321 }
322
323 pub async fn approval_list_process_instance_ids(
325 &self,
326 request: ApprovalListProcessInstanceIdsRequest,
327 ) -> Result<ApprovalListProcessInstanceIdsResult> {
328 self.post_topapi_result(&["topapi", "processinstance", "listids"], &request)
329 .await
330 }
331
332 pub async fn approval_terminate_process_instance(
334 &self,
335 request: ApprovalTerminateProcessInstanceRequest,
336 ) -> Result<()> {
337 let body = serde_json::json!({ "request": request });
338 self.post_topapi_unit(&["topapi", "process", "instance", "terminate"], &body)
339 .await
340 }
341
342 pub async fn reply_message(
347 &self,
348 data: &serde_json::Value,
349 title: &str,
350 text: &str,
351 ) -> Result<String> {
352 let msg_param = MsgParam {
353 title: title.to_string(),
354 text: text.to_string(),
355 };
356
357 if is_private_conversation(data) {
358 let sender_staff_id = data
359 .get("senderStaffId")
360 .and_then(|v| v.as_str())
361 .ok_or_else(|| Error::InvalidConfig {
362 message: "Missing senderStaffId".to_string(),
363 source: None,
364 })?;
365
366 let request = OtoMessageRequest {
367 msg_param,
368 msg_key: DEFAULT_MSG_KEY,
369 robot_code: &self.robot_code,
370 user_ids: vec![sender_staff_id],
371 };
372
373 self.send_enterprise_message(&["v1.0", "robot", "oToMessages", "batchSend"], &request)
374 .await
375 } else {
376 let conversation_id = data
377 .get("conversationId")
378 .and_then(|v| v.as_str())
379 .ok_or_else(|| Error::InvalidConfig {
380 message: "Missing conversationId".to_string(),
381 source: None,
382 })?;
383
384 let request = GroupMessageRequest {
385 msg_param,
386 msg_key: DEFAULT_MSG_KEY,
387 robot_code: &self.robot_code,
388 open_conversation_id: conversation_id,
389 };
390
391 self.send_enterprise_message(&["v1.0", "robot", "groupMessages", "send"], &request)
392 .await
393 }
394 }
395}