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.1.1";
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_GET_TIMEOUT_MS: u64 = 5_000;
45pub const DEFAULT_QR_POLL_TIMEOUT_MS: u64 = 35_000;
47
48#[derive(Debug, Clone, Copy, Serialize_repr, Deserialize_repr, PartialEq, Eq)]
52#[repr(u8)]
53pub enum UploadMediaType {
54 Image = 1,
56 Video = 2,
58 File = 3,
60 Voice = 4,
62}
63
64#[derive(Debug, Clone, Copy, Default, Serialize_repr, Deserialize_repr, PartialEq, Eq)]
66#[repr(u8)]
67pub enum MessageType {
68 #[default]
70 None = 0,
71 User = 1,
73 Bot = 2,
75}
76
77#[derive(Debug, Clone, Copy, Default, Serialize_repr, Deserialize_repr, PartialEq, Eq)]
79#[repr(u8)]
80pub enum MessageItemType {
81 #[default]
83 None = 0,
84 Text = 1,
86 Image = 2,
88 Voice = 3,
90 File = 4,
92 Video = 5,
94}
95
96#[derive(Debug, Clone, Copy, Default, Serialize_repr, Deserialize_repr, PartialEq, Eq)]
98#[repr(u8)]
99pub enum MessageState {
100 #[default]
102 New = 0,
103 Generating = 1,
105 Finish = 2,
107}
108
109#[derive(Debug, Clone, Copy, Serialize_repr, Deserialize_repr, PartialEq, Eq)]
111#[repr(u8)]
112pub enum TypingStatus {
113 Typing = 1,
115 Cancel = 2,
117}
118
119#[derive(Debug, Clone, Copy, PartialEq, Eq)]
121pub enum MediaType {
122 Image,
124 Video,
126 Voice,
128 File,
130}
131
132#[derive(Debug, Clone, Default, Serialize, Deserialize)]
136pub struct BaseInfo {
137 #[serde(skip_serializing_if = "Option::is_none")]
139 pub channel_version: Option<String>,
140}
141
142pub fn build_base_info() -> BaseInfo {
144 BaseInfo {
145 channel_version: Some(CHANNEL_VERSION.to_owned()),
146 }
147}
148
149#[derive(Debug, Clone, Default, Serialize, Deserialize)]
153pub struct CdnMedia {
154 #[serde(skip_serializing_if = "Option::is_none")]
156 pub encrypt_query_param: Option<String>,
157 #[serde(skip_serializing_if = "Option::is_none")]
159 pub aes_key: Option<String>,
160 #[serde(skip_serializing_if = "Option::is_none")]
162 pub encrypt_type: Option<i32>,
163 #[serde(skip_serializing_if = "Option::is_none")]
165 pub full_url: Option<String>,
166}
167
168#[derive(Debug, Clone, Default, Serialize, Deserialize)]
170pub struct TextItem {
171 #[serde(skip_serializing_if = "Option::is_none")]
173 pub text: Option<String>,
174}
175
176#[derive(Debug, Clone, Default, Serialize, Deserialize)]
178pub struct ImageItem {
179 #[serde(skip_serializing_if = "Option::is_none")]
181 pub media: Option<CdnMedia>,
182 #[serde(skip_serializing_if = "Option::is_none")]
184 pub thumb_media: Option<CdnMedia>,
185 #[serde(skip_serializing_if = "Option::is_none")]
187 pub aeskey: Option<String>,
188 #[serde(skip_serializing_if = "Option::is_none")]
190 pub url: Option<String>,
191 #[serde(skip_serializing_if = "Option::is_none")]
193 pub mid_size: Option<i64>,
194 #[serde(skip_serializing_if = "Option::is_none")]
196 pub thumb_size: Option<i64>,
197 #[serde(skip_serializing_if = "Option::is_none")]
199 pub thumb_height: Option<i64>,
200 #[serde(skip_serializing_if = "Option::is_none")]
202 pub thumb_width: Option<i64>,
203 #[serde(skip_serializing_if = "Option::is_none")]
205 pub hd_size: Option<i64>,
206}
207
208#[derive(Debug, Clone, Default, Serialize, Deserialize)]
210pub struct VoiceItem {
211 #[serde(skip_serializing_if = "Option::is_none")]
213 pub media: Option<CdnMedia>,
214 #[serde(skip_serializing_if = "Option::is_none")]
216 pub encode_type: Option<i32>,
217 #[serde(skip_serializing_if = "Option::is_none")]
219 pub bits_per_sample: Option<i32>,
220 #[serde(skip_serializing_if = "Option::is_none")]
222 pub sample_rate: Option<i32>,
223 #[serde(skip_serializing_if = "Option::is_none")]
225 pub playtime: Option<i64>,
226 #[serde(skip_serializing_if = "Option::is_none")]
228 pub text: Option<String>,
229}
230
231#[derive(Debug, Clone, Default, Serialize, Deserialize)]
233pub struct FileItem {
234 #[serde(skip_serializing_if = "Option::is_none")]
236 pub media: Option<CdnMedia>,
237 #[serde(skip_serializing_if = "Option::is_none")]
239 pub file_name: Option<String>,
240 #[serde(skip_serializing_if = "Option::is_none")]
242 pub md5: Option<String>,
243 #[serde(skip_serializing_if = "Option::is_none")]
245 pub len: Option<String>,
246}
247
248#[derive(Debug, Clone, Default, Serialize, Deserialize)]
250pub struct VideoItem {
251 #[serde(skip_serializing_if = "Option::is_none")]
253 pub media: Option<CdnMedia>,
254 #[serde(skip_serializing_if = "Option::is_none")]
256 pub video_size: Option<i64>,
257 #[serde(skip_serializing_if = "Option::is_none")]
259 pub play_length: Option<i64>,
260 #[serde(skip_serializing_if = "Option::is_none")]
262 pub video_md5: Option<String>,
263 #[serde(skip_serializing_if = "Option::is_none")]
265 pub thumb_media: Option<CdnMedia>,
266 #[serde(skip_serializing_if = "Option::is_none")]
268 pub thumb_size: Option<i64>,
269 #[serde(skip_serializing_if = "Option::is_none")]
271 pub thumb_height: Option<i64>,
272 #[serde(skip_serializing_if = "Option::is_none")]
274 pub thumb_width: Option<i64>,
275}
276
277#[derive(Debug, Clone, Default, Serialize, Deserialize)]
279pub struct RefMessage {
280 #[serde(skip_serializing_if = "Option::is_none")]
282 pub message_item: Option<Box<MessageItem>>,
283 #[serde(skip_serializing_if = "Option::is_none")]
285 pub title: Option<String>,
286}
287
288#[derive(Debug, Clone, Default, Serialize, Deserialize)]
290pub struct MessageItem {
291 #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
293 pub item_type: Option<MessageItemType>,
294 #[serde(skip_serializing_if = "Option::is_none")]
296 pub create_time_ms: Option<i64>,
297 #[serde(skip_serializing_if = "Option::is_none")]
299 pub update_time_ms: Option<i64>,
300 #[serde(skip_serializing_if = "Option::is_none")]
302 pub is_completed: Option<bool>,
303 #[serde(skip_serializing_if = "Option::is_none")]
305 pub msg_id: Option<String>,
306 #[serde(skip_serializing_if = "Option::is_none")]
308 pub ref_msg: Option<RefMessage>,
309 #[serde(skip_serializing_if = "Option::is_none")]
311 pub text_item: Option<TextItem>,
312 #[serde(skip_serializing_if = "Option::is_none")]
314 pub image_item: Option<ImageItem>,
315 #[serde(skip_serializing_if = "Option::is_none")]
317 pub voice_item: Option<VoiceItem>,
318 #[serde(skip_serializing_if = "Option::is_none")]
320 pub file_item: Option<FileItem>,
321 #[serde(skip_serializing_if = "Option::is_none")]
323 pub video_item: Option<VideoItem>,
324}
325
326#[derive(Debug, Clone, Default, Serialize, Deserialize)]
330pub struct WeixinMessage {
331 #[serde(skip_serializing_if = "Option::is_none")]
333 pub seq: Option<i64>,
334 #[serde(skip_serializing_if = "Option::is_none")]
336 pub message_id: Option<i64>,
337 #[serde(skip_serializing_if = "Option::is_none")]
339 pub from_user_id: Option<String>,
340 #[serde(skip_serializing_if = "Option::is_none")]
342 pub to_user_id: Option<String>,
343 #[serde(skip_serializing_if = "Option::is_none")]
345 pub client_id: Option<String>,
346 #[serde(skip_serializing_if = "Option::is_none")]
348 pub create_time_ms: Option<i64>,
349 #[serde(skip_serializing_if = "Option::is_none")]
351 pub update_time_ms: Option<i64>,
352 #[serde(skip_serializing_if = "Option::is_none")]
354 pub delete_time_ms: Option<i64>,
355 #[serde(skip_serializing_if = "Option::is_none")]
357 pub session_id: Option<String>,
358 #[serde(skip_serializing_if = "Option::is_none")]
360 pub group_id: Option<String>,
361 #[serde(skip_serializing_if = "Option::is_none")]
363 pub message_type: Option<MessageType>,
364 #[serde(skip_serializing_if = "Option::is_none")]
366 pub message_state: Option<MessageState>,
367 #[serde(skip_serializing_if = "Option::is_none")]
369 pub item_list: Option<Vec<MessageItem>>,
370 #[serde(skip_serializing_if = "Option::is_none")]
372 pub context_token: Option<String>,
373}
374
375#[derive(Debug, Clone, Serialize, Deserialize)]
379pub struct GetUpdatesRequest {
380 pub get_updates_buf: String,
382 pub base_info: BaseInfo,
384}
385
386#[derive(Debug, Clone, Default, Serialize, Deserialize)]
388pub struct GetUpdatesResponse {
389 #[serde(skip_serializing_if = "Option::is_none")]
391 pub ret: Option<i32>,
392 #[serde(skip_serializing_if = "Option::is_none")]
394 pub errcode: Option<i32>,
395 #[serde(skip_serializing_if = "Option::is_none")]
397 pub errmsg: Option<String>,
398 #[serde(skip_serializing_if = "Option::is_none")]
400 pub msgs: Option<Vec<WeixinMessage>>,
401 #[serde(skip_serializing_if = "Option::is_none")]
403 pub sync_buf: Option<String>,
404 #[serde(skip_serializing_if = "Option::is_none")]
406 pub get_updates_buf: Option<String>,
407 #[serde(skip_serializing_if = "Option::is_none")]
409 pub longpolling_timeout_ms: Option<u64>,
410}
411
412#[derive(Debug, Clone, Serialize, Deserialize)]
414pub struct SendMessageRequest {
415 pub msg: WeixinMessage,
417 pub base_info: BaseInfo,
419}
420
421#[derive(Debug, Clone, Serialize, Deserialize)]
423pub struct GetUploadUrlRequest {
424 pub filekey: String,
426 pub media_type: UploadMediaType,
428 pub to_user_id: String,
430 pub rawsize: u64,
432 pub rawfilemd5: String,
434 pub filesize: u64,
436 #[serde(skip_serializing_if = "Option::is_none")]
438 pub no_need_thumb: Option<bool>,
439 #[serde(skip_serializing_if = "Option::is_none")]
441 pub thumb_rawsize: Option<u64>,
442 #[serde(skip_serializing_if = "Option::is_none")]
444 pub thumb_rawfilemd5: Option<String>,
445 #[serde(skip_serializing_if = "Option::is_none")]
447 pub thumb_filesize: Option<u64>,
448 pub aeskey: String,
450 pub base_info: BaseInfo,
452}
453
454#[derive(Debug, Clone, Default, Serialize, Deserialize)]
456pub struct GetUploadUrlResponse {
457 #[serde(skip_serializing_if = "Option::is_none")]
459 pub upload_param: Option<String>,
460 #[serde(skip_serializing_if = "Option::is_none")]
462 pub thumb_upload_param: Option<String>,
463 #[serde(skip_serializing_if = "Option::is_none")]
465 pub upload_full_url: Option<String>,
466}
467
468#[derive(Debug, Clone, Serialize)]
470pub(crate) struct GetConfigRequest {
471 pub ilink_user_id: String,
473 #[serde(skip_serializing_if = "Option::is_none")]
475 pub context_token: Option<String>,
476 pub base_info: BaseInfo,
478}
479
480#[derive(Debug, Clone, Default, Serialize, Deserialize)]
482pub struct GetConfigResponse {
483 #[serde(skip_serializing_if = "Option::is_none")]
485 pub ret: Option<i32>,
486 #[serde(skip_serializing_if = "Option::is_none")]
488 pub errmsg: Option<String>,
489 #[serde(skip_serializing_if = "Option::is_none")]
491 pub typing_ticket: Option<String>,
492}
493
494#[derive(Debug, Clone, Serialize, Deserialize)]
496pub struct SendTypingRequest {
497 pub ilink_user_id: String,
499 #[serde(skip_serializing_if = "Option::is_none")]
501 pub typing_ticket: Option<String>,
502 pub status: TypingStatus,
504 pub base_info: BaseInfo,
506}
507
508#[derive(Debug, Clone, Serialize, Deserialize)]
512pub struct QrCodeResponse {
513 pub qrcode: String,
515 pub qrcode_img_content: String,
517}
518
519#[derive(Debug, Clone, Serialize, Deserialize)]
521pub struct QrStatusResponse {
522 pub status: String,
524 #[serde(skip_serializing_if = "Option::is_none")]
526 pub bot_token: Option<String>,
527 #[serde(skip_serializing_if = "Option::is_none")]
529 pub ilink_bot_id: Option<String>,
530 #[serde(skip_serializing_if = "Option::is_none")]
532 pub baseurl: Option<String>,
533 #[serde(skip_serializing_if = "Option::is_none")]
535 pub ilink_user_id: Option<String>,
536 #[serde(skip_serializing_if = "Option::is_none")]
538 pub redirect_host: Option<String>,
539}