1use reqwest::Client;
15#[derive(serde::Serialize, Debug, serde::Deserialize)]
16pub struct TextContent {
17 pub content: String,
18}
19#[derive(serde::Serialize, Debug, serde::Deserialize)]
20pub struct At {
21 #[serde(rename = "atUserIds", skip_serializing_if = "Option::is_none")]
22 pub at_ids: Option<Vec<String>>,
23 #[serde(rename = "atMobiles", skip_serializing_if = "Option::is_none")]
24 pub at_mobiles: Option<Vec<String>>,
25 #[serde(rename = "isAtAll", skip_serializing_if = "Option::is_none")]
26 pub is_at_all: Option<bool>,
27}
28#[derive(serde::Serialize, Debug, serde::Deserialize)]
29pub struct LinkContent {
30 pub title: String,
31 pub text: String,
32 #[serde(rename = "messageUrl")]
33 pub message_url: String,
34 #[serde(rename = "picUrl", skip_serializing_if = "Option::is_none")]
35 pub pic_url: Option<String>,
36}
37#[derive(serde::Serialize, Debug, serde::Deserialize)]
38pub struct MarkdownContent {
39 pub title: String,
40 pub text: String,
41}
42#[derive(serde::Serialize, Debug, serde::Deserialize)]
43pub struct ActionCardContent {
44 pub title: String,
45 pub text: String, #[serde(rename = "singleTitle")]
47 pub single_title: String,
48 #[serde(rename = "singleURL")]
49 pub single_url: String,
50 #[serde(rename = "btnOrientation")]
51 pub btn_orientation: Option<ButtonOrientation>, #[serde(skip_serializing_if = "Option::is_none")]
53 pub btns: Option<Vec<ActionCardButton>>, }
55#[derive(serde_repr::Serialize_repr, Debug, serde_repr::Deserialize_repr)]
56#[repr(u8)]
57pub enum ButtonOrientation {
58 Vertical = 0,
59 Horizontal = 1,
60}
61#[derive(serde::Serialize, Debug, serde::Deserialize)]
62pub struct ActionCardButton {
63 pub title: String,
64 #[serde(rename = "actionURL")]
65 pub action_url: String,
66}
67#[derive(serde::Serialize, Debug, serde::Deserialize)]
68pub struct FeedCardLink {
69 pub title: String,
70 #[serde(rename = "messageURL")]
71 pub message_url: String,
72 #[serde(rename = "picURL")]
73 pub pic_url: String,
74}
75
76#[derive(serde::Serialize, Debug, serde::Deserialize)]
77pub struct FeedCardContent {
78 pub links: Vec<FeedCardLink>,
79}
80#[derive(serde::Serialize, Debug, serde::Deserialize)]
81#[serde(tag = "msgtype", rename_all = "camelCase")]
82pub enum RobotMessage {
83 #[serde(rename = "text")]
84 Text {
85 text: TextContent,
86 #[serde(skip_serializing_if = "Option::is_none")]
87 at: Option<At>,
88 },
89 #[serde(rename = "link")]
90 Link {
91 link: LinkContent,
92 },
93 #[serde(rename = "markdown")]
94 Markdown {
95 markdown: MarkdownContent,
96 #[serde(skip_serializing_if = "Option::is_none")]
97 at: Option<At>,
98 },
99 #[serde(rename = "actionCard")]
100 ActionCard {
101 content: ActionCardContent,
102 },
103 #[serde(rename = "feedCard")]
104 FeedCard {
105 feed_card: FeedCardContent,
106 },
107}
108
109pub async fn send_message(
111 access_token: &str,
112 message: &RobotMessage,
113) -> Result<(), Box<dyn std::error::Error>> {
114
115 let webhook_url = format!(
116 "https://oapi.dingtalk.com/robot/send?access_token={}",
117 access_token,
118 );
119
120 let client = Client::new();
121 let response = client.post(&webhook_url).json(message).send().await?;
122
123 if response.status().is_success() {
124 let response_body: serde_json::Value = response.json().await?;
125 println!("发送成功: {:?}", response_body);
126 Ok(())
127 } else {
128 let error_text = response.text().await?;
129 eprintln!("发送失败: {}", error_text);
130 Err(Box::from(format!("发送失败: {}", error_text)))
131 }
132}