1use std::{fs, path::Path, time::Duration};
2
3use derive_builder::Builder;
4use reqwest::{Body, Method, StatusCode};
5use thiserror::Error;
6
7impl<'a> super::Client<'a> {
8 pub async fn upload_web_file<P: AsRef<Path>>(
9 &self,
10 path: P,
11 upload: Upload,
12 ) -> Result<(String, String), UploadError> {
13 let bytes = fs::read(path)?;
14 self.upload_web(bytes, upload).await
15 }
16
17 pub async fn upload_web<'b, B: Into<Body>>(
18 &self,
19 body: B,
20 upload: Upload,
21 ) -> Result<(String, String), UploadError> {
22 let mut request = self
23 .http
24 .request(
25 Method::PUT,
26 format!("{}{}/", upload.protocol.to_string(), self.base_uri),
27 )
28 .body(body);
29
30 if let Some(filename) = upload.filename {
31 request = request.query(&[("filename", filename)]);
32 }
33
34 if let Some(randomizefn) = upload.randomizefn {
35 request = request.query(&[("randomizefn", randomizefn as i32)]);
36 }
37
38 if let Some(expire) = upload.expire {
39 request = request.query(&[("expire", expire.as_secs() / 60)]);
40 }
41
42 if let Some(autodestroy) = upload.autodestroy {
43 request = request.query(&[("autodestroy", autodestroy as i32)]);
44 }
45
46 if let Some(shorturl) = upload.shorturl {
47 request = request.query(&[("shorturl", shorturl as i32)]);
48 }
49
50 let response = request.send().await?;
51
52 let content = match response.status() {
53 StatusCode::OK => response.text().await?,
54 StatusCode::PAYLOAD_TOO_LARGE => return Err(UploadError::PayloadTooLarge),
55 status => return Err(UploadError::UnknownStatusCode(status)),
56 };
57
58 if !content.contains("[Admin]") || !content.contains("[Download]") {
59 return Err(UploadError::MalformedResponse(content));
60 }
61
62 let mut lines = content.lines().skip(1);
63
64 let admin_url = lines
65 .next()
66 .ok_or(UploadError::MalformedResponse(content.to_owned()))?
67 .strip_suffix(" [Admin]")
68 .ok_or(UploadError::MalformedResponse(content.to_owned()))?
69 .to_string();
70 let download_url = lines
71 .next()
72 .ok_or(UploadError::MalformedResponse(content.to_owned()))?
73 .strip_suffix(" [Download]")
74 .ok_or(UploadError::MalformedResponse(content.to_owned()))?
75 .to_string();
76
77 Ok((admin_url, download_url))
78 }
79}
80
81#[derive(Debug, Error)]
82pub enum UploadError {
83 #[error("The payload you are trying to upload is too large")]
84 PayloadTooLarge,
85 #[error("The server returned an unknown status code {0}")]
86 UnknownStatusCode(StatusCode),
87 #[error("The server returned a malformed response {0}")]
88 MalformedResponse(String),
89 #[error(transparent)]
90 Io(#[from] std::io::Error),
91 #[error(transparent)]
92 Http(#[from] reqwest::Error),
93}
94
95#[derive(Default, Debug, Clone, Builder)]
96#[builder(setter(into), default)]
97pub struct Upload {
98 protocol: UploadProtocol,
99 filename: Option<String>,
100 randomizefn: Option<bool>,
101 expire: Option<Duration>,
102 autodestroy: Option<bool>,
103 shorturl: Option<bool>,
104}
105
106impl Upload {
107 pub fn builder() -> UploadBuilder {
108 UploadBuilder::default()
109 }
110}
111
112#[derive(Default, Debug, Clone)]
113pub enum UploadMethod {
114 #[default]
115 Put,
116 Post,
117}
118
119#[derive(Default, Debug, Clone)]
120pub enum UploadProtocol {
121 #[default]
122 Https,
123 Http,
124}
125
126impl ToString for UploadProtocol {
127 fn to_string(&self) -> String {
128 match self {
129 UploadProtocol::Https => "https://".to_string(),
130 UploadProtocol::Http => "http://".to_string(),
131 }
132 }
133}