discord_webhook_rs/
webhook.rs1use super::Embed;
2
3use reqwest::blocking::{
4 Client, Response,
5 multipart::{Form, Part},
6};
7use serde_json::{Map, Value};
8use std::fs;
9use std::path::Path;
10
11#[derive(Debug)]
12pub enum Error {
13 Request(reqwest::Error),
14 File(std::io::Error),
15 MaxFile,
16 MaxContent,
17 MaxEmbed,
18 MaxField,
19 InvalidColor,
20 InvalidEmbed,
21 MissingNameField,
22 MissingValueField,
23}
24
25pub struct Webhook {
26 url: String,
27 username: Option<String>,
28 avatar_url: Option<String>,
29 content: Option<String>,
30 embeds: Vec<Embed>,
31 files: Vec<String>,
32 client: Client,
33}
34
35impl Webhook {
36 pub fn new(url: impl Into<String>) -> Self {
37 Webhook {
38 url: url.into(),
39 username: None,
40 avatar_url: None,
41 content: None,
42 embeds: Vec::new(),
43 files: Vec::new(),
44 client: Client::new(),
45 }
46 }
47
48 pub fn set_client(mut self, client: Client) -> Self {
49 self.client = client;
50 self
51 }
52
53 pub fn username(mut self, username: impl Into<String>) -> Self {
54 self.username = Some(username.into());
55 self
56 }
57
58 pub fn avatar_url(mut self, avatar_url: impl Into<String>) -> Self {
59 self.avatar_url = Some(avatar_url.into());
60 self
61 }
62
63 pub fn content(mut self, content: impl Into<String>) -> Self {
64 self.content = Some(content.into());
65 self
66 }
67
68 pub fn add_embed(mut self, embed: Embed) -> Self {
69 self.embeds.push(embed);
70 self
71 }
72
73 pub fn add_file(mut self, file: impl Into<String>) -> Self {
74 self.files.push(file.into());
75 self
76 }
77
78 fn verify(&self) -> Result<(), Error> {
79 if let Some(content) = &self.content {
80 if content.len() > 2000 {
81 return Err(Error::MaxContent);
82 }
83 }
84
85 if self.embeds.len() > 10 {
86 return Err(Error::MaxEmbed);
87 }
88
89 Ok(())
90 }
91
92 fn build_form(&self) -> Result<Form, Error> {
93 if self.files.len() > 10 {
94 return Err(Error::MaxFile);
95 }
96
97 let mut form = Form::new();
98
99 for (i, file_path) in self.files.iter().enumerate() {
100 let file_bytes = match fs::read(file_path) {
101 Err(why) => return Err(Error::File(why)),
102 Ok(data) => data,
103 };
104
105 let filename = Path::new(file_path)
106 .file_name()
107 .and_then(|name| name.to_str())
108 .unwrap_or("file");
109
110 let part = Part::bytes(file_bytes.to_vec()).file_name(filename.to_string());
111
112 let part = match part.mime_str("application/octet-stream") {
113 Err(why) => return Err(Error::Request(why)),
114 Ok(data) => data,
115 };
116
117 form = form.part(format!("files[{}]", i), part);
118 }
119
120 Ok(form)
121 }
122
123 fn build_body(&self) -> Result<Value, Error> {
124 self.verify()?;
125
126 let mut obj = Map::new(); if let Some(content) = &self.content {
129 obj.insert("content".into(), Value::String(content.clone()));
130 } else {
131 obj.insert("content".into(), Value::String("Hello from Rust!".into()));
132 }
133
134 if let Some(username) = &self.username {
135 obj.insert("username".into(), Value::String(username.clone()));
136 }
137
138 if let Some(avatar_url) = &self.avatar_url {
139 obj.insert("avatar_url".into(), Value::String(avatar_url.clone()));
140 }
141
142 obj.insert("embeds".into(), Value::Array(vec![]));
143 for embed in &self.embeds {
144 if let Value::Array(ref mut embeds) = obj["embeds"] {
145 embeds.push(embed.build()?);
146 }
147 }
148
149 Ok(Value::Object(obj))
150 }
151
152 pub fn send(self) -> Result<Response, Error> {
153 let mut form = self.build_form()?;
154 form = form.text("payload_json", self.build_body()?.to_string());
155
156 let req = self.client.post(&self.url).multipart(form);
157
158 match req.send() {
159 Ok(response) => Ok(response),
160 Err(error) => Err(Error::Request(error)),
161 }
162 }
163}