Skip to main content

u_sdk/server_chan/
mod.rs

1//! Server chan 3 sdk
2
3use bon::{Builder, bon};
4use reqwest::StatusCode;
5use serde::Serialize;
6
7#[derive(thiserror::Error, Debug)]
8pub enum Error {
9    #[error("request failed: code: {code}\nbody: {body}")]
10    API { code: StatusCode, body: String },
11    #[error("use reqwest error:\n {0}")]
12    Reqwest(#[from] reqwest::Error),
13    #[error("error: {0}")]
14    Other(String),
15}
16
17#[derive(Builder, Serialize)]
18pub struct SendMsg<'a> {
19    #[builder(start_fn)]
20    #[serde(skip_serializing)]
21    client: &'a Client,
22    /// 标签列表,多个标签使用竖线`|`分隔
23    // 注意这个#[builder(filed)字段有顺序要求,需要放在start_fn之后,finish_fn之前
24    #[builder(field)]
25    #[serde(
26        serialize_with = "serialize_tags",
27        skip_serializing_if = "Vec::is_empty"
28    )]
29    tags: Vec<&'a str>,
30    /// 推送的标题
31    title: &'a str,
32    #[serde(rename = "desp", skip_serializing_if = "Option::is_none")]
33    /// 推送的正文内容,则为必填,支持markdown
34    description: Option<&'a str>,
35    #[serde(skip_serializing_if = "Option::is_none")]
36    /// 推送消息的简短描述,用于指定消息卡片的内容部分,尤其是在推送markdown的时候
37    short: Option<&'a str>,
38}
39
40fn serialize_tags<S>(tags: &[&str], serializer: S) -> Result<S::Ok, S::Error>
41where
42    S: serde::Serializer,
43{
44    let tags_str = tags.join("|");
45    serializer.serialize_str(&tags_str)
46}
47
48impl<'a, S: send_msg_builder::State> SendMsgBuilder<'a, S> {
49    pub fn tag(mut self, tag: &'a str) -> Self {
50        self.tags.push(tag);
51        self
52    }
53
54    pub fn tags(mut self, tags: impl IntoIterator<Item = &'a str>) -> Self {
55        self.tags.extend(tags);
56        self
57    }
58}
59
60/// 使用Server酱3
61pub struct Client {
62    url: String,
63    http_client: reqwest::Client,
64}
65
66#[bon]
67impl Client {
68    #[builder]
69    pub fn new(send_key: &str) -> Result<Self, Error> {
70        let uid = send_key
71            .strip_prefix("sctp")
72            .and_then(|send_key| send_key.split_once('t'))
73            .filter(|(uid, key)| {
74                !uid.is_empty() && uid.bytes().all(|byte| byte.is_ascii_digit()) && !key.is_empty()
75            })
76            .map(|(uid, _)| uid)
77            .ok_or_else(|| Error::Other("invalid send key".to_string()))?;
78
79        Ok(Self {
80            url: format!("https://{}.push.ft07.com/send/{}.send", uid, send_key),
81            http_client: reqwest::Client::new(),
82        })
83    }
84
85    pub fn send_msg(&self) -> SendMsgBuilder<'_> {
86        SendMsg::builder(self)
87    }
88}
89
90impl SendMsg<'_> {
91    pub async fn send(&self) -> Result<(), Error> {
92        let client = self.client;
93        let resp = client
94            .http_client
95            .post(&client.url)
96            .json(self)
97            .send()
98            .await?;
99        if !resp.status().is_success() {
100            return Err(Error::API {
101                code: resp.status(),
102                body: resp.text().await.unwrap_or_default(),
103            });
104        }
105
106        Ok(())
107    }
108}