1use 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}
14
15#[derive(Builder, Serialize)]
16pub struct SendMsg<'a> {
17 #[builder(start_fn)]
18 #[serde(skip_serializing)]
19 client: &'a Client,
20 #[builder(field)]
23 #[serde(
24 serialize_with = "serialize_tags",
25 skip_serializing_if = "Vec::is_empty"
26 )]
27 tags: Vec<&'a str>,
28 title: &'a str,
30 #[serde(rename = "desp", skip_serializing_if = "Option::is_none")]
31 description: Option<&'a str>,
33 #[serde(skip_serializing_if = "Option::is_none")]
34 short: Option<&'a str>,
36}
37
38fn serialize_tags<S>(tags: &[&str], serializer: S) -> Result<S::Ok, S::Error>
39where
40 S: serde::Serializer,
41{
42 let tags_str = tags.join("|");
43 serializer.serialize_str(&tags_str)
44}
45
46impl<'a, S: send_msg_builder::State> SendMsgBuilder<'a, S> {
47 pub fn tag(mut self, tag: &'a str) -> Self {
48 self.tags.push(tag);
49 self
50 }
51
52 pub fn tags(mut self, tags: impl IntoIterator<Item = &'a str>) -> Self {
53 self.tags.extend(tags);
54 self
55 }
56}
57
58pub struct Client {
60 url: String,
61 http_client: reqwest::Client,
62}
63
64#[bon]
65impl Client {
66 #[builder]
67 pub fn new(uid: i32, key: &str) -> Self {
68 Self {
69 url: format!("https://{}.push.ft07.com/send/{}.send", uid, key),
70 http_client: reqwest::Client::new(),
71 }
72 }
73
74 pub fn send_msg(&self) -> SendMsgBuilder<'_> {
75 SendMsg::builder(self)
76 }
77}
78
79impl SendMsg<'_> {
80 pub async fn send(&self) -> Result<(), Error> {
81 let client = self.client;
82 let resp = client
83 .http_client
84 .post(&client.url)
85 .json(self)
86 .send()
87 .await?;
88 if !resp.status().is_success() {
89 return Err(Error::API {
90 code: resp.status(),
91 body: resp.text().await.unwrap_or_default(),
92 });
93 }
94
95 Ok(())
96 }
97}