use bon::{Builder, bon};
use reqwest::StatusCode;
use serde::Serialize;
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error("request failed: code: {code}\nbody: {body}")]
API { code: StatusCode, body: String },
#[error("use reqwest error:\n {0}")]
Reqwest(#[from] reqwest::Error),
}
#[derive(Builder, Serialize)]
pub struct SendMsg<'a> {
#[builder(start_fn)]
#[serde(skip_serializing)]
client: &'a Client,
#[builder(field)]
#[serde(
serialize_with = "serialize_tags",
skip_serializing_if = "Vec::is_empty"
)]
tags: Vec<&'a str>,
title: &'a str,
#[serde(rename = "desp", skip_serializing_if = "Option::is_none")]
description: Option<&'a str>,
#[serde(skip_serializing_if = "Option::is_none")]
short: Option<&'a str>,
}
fn serialize_tags<S>(tags: &[&str], serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let tags_str = tags.join("|");
serializer.serialize_str(&tags_str)
}
impl<'a, S: send_msg_builder::State> SendMsgBuilder<'a, S> {
pub fn tag(mut self, tag: &'a str) -> Self {
self.tags.push(tag);
self
}
pub fn tags(mut self, tags: impl IntoIterator<Item = &'a str>) -> Self {
self.tags.extend(tags);
self
}
}
pub struct Client {
url: String,
http_client: reqwest::Client,
}
#[bon]
impl Client {
#[builder]
pub fn new(uid: i32, key: &str) -> Self {
Self {
url: format!("https://{}.push.ft07.com/send/{}.send", uid, key),
http_client: reqwest::Client::new(),
}
}
pub fn send_msg(&self) -> SendMsgBuilder<'_> {
SendMsg::builder(self)
}
}
impl SendMsg<'_> {
pub async fn send(&self) -> Result<(), Error> {
let client = self.client;
let resp = client
.http_client
.post(&client.url)
.json(self)
.send()
.await?;
if !resp.status().is_success() {
return Err(Error::API {
code: resp.status(),
body: resp.text().await.unwrap_or_default(),
});
}
Ok(())
}
}