1use serde::{Deserialize, Serialize};
4use serde_repr::{Deserialize_repr, Serialize_repr};
5
6pub const ILINK_APP_ID: &str = "bot";
10pub const CHANNEL_VERSION: &str = "2.4.3";
12pub const QR_CODE_BASE_URL: &str = "https://ilinkai.weixin.qq.com/";
14pub const DEFAULT_ILINK_BOT_TYPE: &str = "3";
16pub const SESSION_EXPIRED_ERRCODE: i32 = -14;
18pub const TEXT_CHUNK_LIMIT: usize = 4000;
20
21pub const DEFAULT_LONG_POLL_TIMEOUT_MS: u64 = 35_000;
25pub const DEFAULT_API_TIMEOUT_MS: u64 = 15_000;
27pub const DEFAULT_CONFIG_TIMEOUT_MS: u64 = 10_000;
29pub const SESSION_PAUSE_DURATION_MS: u64 = 3_600_000;
31pub const MAX_CONSECUTIVE_FAILURES: u32 = 3;
33pub const BACKOFF_DELAY_MS: u64 = 30_000;
35pub const RETRY_DELAY_MS: u64 = 2_000;
37pub const UPLOAD_MAX_RETRIES: u32 = 3;
39pub const CONFIG_CACHE_TTL_MS: u64 = 86_400_000;
41pub const MAX_QR_REFRESH_COUNT: u32 = 3;
43pub const DEFAULT_QR_POLL_TIMEOUT_MS: u64 = 35_000;
45
46#[derive(Debug, Clone, Copy, Serialize_repr, Deserialize_repr, PartialEq, Eq)]
50#[repr(u8)]
51pub enum UploadMediaType {
52 Image = 1,
54 Video = 2,
56 File = 3,
58 Voice = 4,
60}
61
62#[derive(Debug, Clone, Copy, Default, Serialize_repr, Deserialize_repr, PartialEq, Eq)]
64#[repr(u8)]
65pub enum MessageType {
66 #[default]
68 None = 0,
69 User = 1,
71 Bot = 2,
73}
74
75#[derive(Debug, Clone, Copy, Default, Serialize_repr, Deserialize_repr, PartialEq, Eq)]
77#[repr(u8)]
78pub enum MessageItemType {
79 #[default]
81 None = 0,
82 Text = 1,
84 Image = 2,
86 Voice = 3,
88 File = 4,
90 Video = 5,
92}
93
94#[derive(Debug, Clone, Copy, Default, Serialize_repr, Deserialize_repr, PartialEq, Eq)]
96#[repr(u8)]
97pub enum MessageState {
98 #[default]
100 New = 0,
101 Generating = 1,
103 Finish = 2,
105}
106
107#[derive(Debug, Clone, Copy, Serialize_repr, Deserialize_repr, PartialEq, Eq)]
109#[repr(u8)]
110pub enum TypingStatus {
111 Typing = 1,
113 Cancel = 2,
115}
116
117#[derive(Debug, Clone, Copy, PartialEq, Eq)]
119pub enum MediaType {
120 Image,
122 Video,
124 Voice,
126 File,
128}
129
130#[derive(Debug, Clone, Default, Serialize, Deserialize)]
134pub struct BaseInfo {
135 #[serde(skip_serializing_if = "Option::is_none")]
137 pub channel_version: Option<String>,
138 #[serde(skip_serializing_if = "Option::is_none")]
140 pub bot_agent: Option<String>,
141}
142
143pub fn build_base_info_with_agent(bot_agent: &str) -> BaseInfo {
145 BaseInfo {
146 channel_version: Some(CHANNEL_VERSION.to_owned()),
147 bot_agent: Some(bot_agent.to_owned()),
148 }
149}
150
151pub fn build_base_info() -> BaseInfo {
153 build_base_info_with_agent("weixin-agent-rs")
154}
155
156#[derive(Debug, Clone, Default, Serialize, Deserialize)]
160pub struct CdnMedia {
161 #[serde(skip_serializing_if = "Option::is_none")]
163 pub encrypt_query_param: Option<String>,
164 #[serde(skip_serializing_if = "Option::is_none")]
166 pub aes_key: Option<String>,
167 #[serde(skip_serializing_if = "Option::is_none")]
169 pub encrypt_type: Option<i32>,
170 #[serde(skip_serializing_if = "Option::is_none")]
172 pub full_url: Option<String>,
173}
174
175#[derive(Debug, Clone, Default, Serialize, Deserialize)]
177pub struct TextItem {
178 #[serde(skip_serializing_if = "Option::is_none")]
180 pub text: Option<String>,
181}
182
183#[derive(Debug, Clone, Default, Serialize, Deserialize)]
185pub struct ImageItem {
186 #[serde(skip_serializing_if = "Option::is_none")]
188 pub media: Option<CdnMedia>,
189 #[serde(skip_serializing_if = "Option::is_none")]
191 pub thumb_media: Option<CdnMedia>,
192 #[serde(skip_serializing_if = "Option::is_none")]
194 pub aeskey: Option<String>,
195 #[serde(skip_serializing_if = "Option::is_none")]
197 pub url: Option<String>,
198 #[serde(skip_serializing_if = "Option::is_none")]
200 pub mid_size: Option<i64>,
201 #[serde(skip_serializing_if = "Option::is_none")]
203 pub thumb_size: Option<i64>,
204 #[serde(skip_serializing_if = "Option::is_none")]
206 pub thumb_height: Option<i64>,
207 #[serde(skip_serializing_if = "Option::is_none")]
209 pub thumb_width: Option<i64>,
210 #[serde(skip_serializing_if = "Option::is_none")]
212 pub hd_size: Option<i64>,
213}
214
215#[derive(Debug, Clone, Default, Serialize, Deserialize)]
217pub struct VoiceItem {
218 #[serde(skip_serializing_if = "Option::is_none")]
220 pub media: Option<CdnMedia>,
221 #[serde(skip_serializing_if = "Option::is_none")]
223 pub encode_type: Option<i32>,
224 #[serde(skip_serializing_if = "Option::is_none")]
226 pub bits_per_sample: Option<i32>,
227 #[serde(skip_serializing_if = "Option::is_none")]
229 pub sample_rate: Option<i32>,
230 #[serde(skip_serializing_if = "Option::is_none")]
232 pub playtime: Option<i64>,
233 #[serde(skip_serializing_if = "Option::is_none")]
235 pub text: Option<String>,
236}
237
238#[derive(Debug, Clone, Default, Serialize, Deserialize)]
240pub struct FileItem {
241 #[serde(skip_serializing_if = "Option::is_none")]
243 pub media: Option<CdnMedia>,
244 #[serde(skip_serializing_if = "Option::is_none")]
246 pub file_name: Option<String>,
247 #[serde(skip_serializing_if = "Option::is_none")]
249 pub md5: Option<String>,
250 #[serde(skip_serializing_if = "Option::is_none")]
252 pub len: Option<String>,
253}
254
255#[derive(Debug, Clone, Default, Serialize, Deserialize)]
257pub struct VideoItem {
258 #[serde(skip_serializing_if = "Option::is_none")]
260 pub media: Option<CdnMedia>,
261 #[serde(skip_serializing_if = "Option::is_none")]
263 pub video_size: Option<i64>,
264 #[serde(skip_serializing_if = "Option::is_none")]
266 pub play_length: Option<i64>,
267 #[serde(skip_serializing_if = "Option::is_none")]
269 pub video_md5: Option<String>,
270 #[serde(skip_serializing_if = "Option::is_none")]
272 pub thumb_media: Option<CdnMedia>,
273 #[serde(skip_serializing_if = "Option::is_none")]
275 pub thumb_size: Option<i64>,
276 #[serde(skip_serializing_if = "Option::is_none")]
278 pub thumb_height: Option<i64>,
279 #[serde(skip_serializing_if = "Option::is_none")]
281 pub thumb_width: Option<i64>,
282}
283
284#[derive(Debug, Clone, Default, Serialize, Deserialize)]
286pub struct RefMessage {
287 #[serde(skip_serializing_if = "Option::is_none")]
289 pub message_item: Option<Box<MessageItem>>,
290 #[serde(skip_serializing_if = "Option::is_none")]
292 pub title: Option<String>,
293}
294
295#[derive(Debug, Clone, Default, Serialize, Deserialize)]
297pub struct MessageItem {
298 #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
300 pub item_type: Option<MessageItemType>,
301 #[serde(skip_serializing_if = "Option::is_none")]
303 pub create_time_ms: Option<i64>,
304 #[serde(skip_serializing_if = "Option::is_none")]
306 pub update_time_ms: Option<i64>,
307 #[serde(skip_serializing_if = "Option::is_none")]
309 pub is_completed: Option<bool>,
310 #[serde(skip_serializing_if = "Option::is_none")]
312 pub msg_id: Option<String>,
313 #[serde(skip_serializing_if = "Option::is_none")]
315 pub ref_msg: Option<RefMessage>,
316 #[serde(skip_serializing_if = "Option::is_none")]
318 pub text_item: Option<TextItem>,
319 #[serde(skip_serializing_if = "Option::is_none")]
321 pub image_item: Option<ImageItem>,
322 #[serde(skip_serializing_if = "Option::is_none")]
324 pub voice_item: Option<VoiceItem>,
325 #[serde(skip_serializing_if = "Option::is_none")]
327 pub file_item: Option<FileItem>,
328 #[serde(skip_serializing_if = "Option::is_none")]
330 pub video_item: Option<VideoItem>,
331}
332
333#[derive(Debug, Clone, Default, Serialize, Deserialize)]
337pub struct WeixinMessage {
338 #[serde(skip_serializing_if = "Option::is_none")]
340 pub seq: Option<i64>,
341 #[serde(skip_serializing_if = "Option::is_none")]
343 pub message_id: Option<i64>,
344 #[serde(skip_serializing_if = "Option::is_none")]
346 pub from_user_id: Option<String>,
347 #[serde(skip_serializing_if = "Option::is_none")]
349 pub to_user_id: Option<String>,
350 #[serde(skip_serializing_if = "Option::is_none")]
352 pub client_id: Option<String>,
353 #[serde(skip_serializing_if = "Option::is_none")]
355 pub create_time_ms: Option<i64>,
356 #[serde(skip_serializing_if = "Option::is_none")]
358 pub update_time_ms: Option<i64>,
359 #[serde(skip_serializing_if = "Option::is_none")]
361 pub delete_time_ms: Option<i64>,
362 #[serde(skip_serializing_if = "Option::is_none")]
364 pub session_id: Option<String>,
365 #[serde(skip_serializing_if = "Option::is_none")]
367 pub group_id: Option<String>,
368 #[serde(skip_serializing_if = "Option::is_none")]
370 pub message_type: Option<MessageType>,
371 #[serde(skip_serializing_if = "Option::is_none")]
373 pub message_state: Option<MessageState>,
374 #[serde(skip_serializing_if = "Option::is_none")]
376 pub item_list: Option<Vec<MessageItem>>,
377 #[serde(skip_serializing_if = "Option::is_none")]
379 pub context_token: Option<String>,
380}
381
382#[derive(Debug, Clone, Serialize, Deserialize)]
386pub struct GetUpdatesRequest {
387 pub get_updates_buf: String,
389 pub base_info: BaseInfo,
391}
392
393#[derive(Debug, Clone, Default, Serialize, Deserialize)]
395pub struct GetUpdatesResponse {
396 #[serde(skip_serializing_if = "Option::is_none")]
398 pub ret: Option<i32>,
399 #[serde(skip_serializing_if = "Option::is_none")]
401 pub errcode: Option<i32>,
402 #[serde(skip_serializing_if = "Option::is_none")]
404 pub errmsg: Option<String>,
405 #[serde(skip_serializing_if = "Option::is_none")]
407 pub msgs: Option<Vec<WeixinMessage>>,
408 #[serde(skip_serializing_if = "Option::is_none")]
410 pub sync_buf: Option<String>,
411 #[serde(skip_serializing_if = "Option::is_none")]
413 pub get_updates_buf: Option<String>,
414 #[serde(skip_serializing_if = "Option::is_none")]
416 pub longpolling_timeout_ms: Option<u64>,
417}
418
419#[derive(Debug, Clone, Serialize, Deserialize)]
421pub struct SendMessageRequest {
422 pub msg: WeixinMessage,
424 pub base_info: BaseInfo,
426}
427
428#[derive(Debug, Clone, Serialize, Deserialize)]
430pub struct GetUploadUrlRequest {
431 pub filekey: String,
433 pub media_type: UploadMediaType,
435 pub to_user_id: String,
437 pub rawsize: u64,
439 pub rawfilemd5: String,
441 pub filesize: u64,
443 #[serde(skip_serializing_if = "Option::is_none")]
445 pub no_need_thumb: Option<bool>,
446 #[serde(skip_serializing_if = "Option::is_none")]
448 pub thumb_rawsize: Option<u64>,
449 #[serde(skip_serializing_if = "Option::is_none")]
451 pub thumb_rawfilemd5: Option<String>,
452 #[serde(skip_serializing_if = "Option::is_none")]
454 pub thumb_filesize: Option<u64>,
455 pub aeskey: String,
457 pub base_info: BaseInfo,
459}
460
461#[derive(Debug, Clone, Default, Serialize, Deserialize)]
463pub struct GetUploadUrlResponse {
464 #[serde(skip_serializing_if = "Option::is_none")]
466 pub upload_param: Option<String>,
467 #[serde(skip_serializing_if = "Option::is_none")]
469 pub thumb_upload_param: Option<String>,
470 #[serde(skip_serializing_if = "Option::is_none")]
472 pub upload_full_url: Option<String>,
473}
474
475#[derive(Debug, Clone, Serialize)]
477pub(crate) struct GetConfigRequest {
478 pub ilink_user_id: String,
480 #[serde(skip_serializing_if = "Option::is_none")]
482 pub context_token: Option<String>,
483 pub base_info: BaseInfo,
485}
486
487#[derive(Debug, Clone, Default, Serialize, Deserialize)]
489pub struct GetConfigResponse {
490 #[serde(skip_serializing_if = "Option::is_none")]
492 pub ret: Option<i32>,
493 #[serde(skip_serializing_if = "Option::is_none")]
495 pub errmsg: Option<String>,
496 #[serde(skip_serializing_if = "Option::is_none")]
498 pub typing_ticket: Option<String>,
499}
500
501#[derive(Debug, Clone, Serialize, Deserialize)]
503pub struct SendTypingRequest {
504 pub ilink_user_id: String,
506 #[serde(skip_serializing_if = "Option::is_none")]
508 pub typing_ticket: Option<String>,
509 pub status: TypingStatus,
511 pub base_info: BaseInfo,
513}
514
515#[derive(Debug, Clone, Serialize, Deserialize)]
519pub struct QrCodeResponse {
520 pub qrcode: String,
522 pub qrcode_img_content: String,
524}
525
526#[derive(Debug, Clone, Default, Serialize, Deserialize)]
528pub struct QrStatusResponse {
529 pub status: String,
531 #[serde(skip_serializing_if = "Option::is_none")]
533 pub bot_token: Option<String>,
534 #[serde(skip_serializing_if = "Option::is_none")]
536 pub ilink_bot_id: Option<String>,
537 #[serde(skip_serializing_if = "Option::is_none")]
539 pub baseurl: Option<String>,
540 #[serde(skip_serializing_if = "Option::is_none")]
542 pub ilink_user_id: Option<String>,
543 #[serde(skip_serializing_if = "Option::is_none")]
545 pub redirect_host: Option<String>,
546}