diff --git a/Cargo.toml b/Cargo.toml index 8e9652b..7f30de7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,4 +15,5 @@ regex = "1.7.3" custom_error = "1.9.2" native-tls = "0.2.11" tokio-tungstenite = {version = "0.18.0", features = ["native-tls"]} -futures-util = "0.3.28" \ No newline at end of file +futures-util = "0.3.28" +http = "0.2.9" \ No newline at end of file diff --git a/src/api/auth/register.rs b/src/api/auth/register.rs index 186be78..28c9c96 100644 --- a/src/api/auth/register.rs +++ b/src/api/auth/register.rs @@ -80,7 +80,7 @@ pub mod register { #[cfg(test)] mod test { - use crate::api::schemas::{AuthEmail, AuthPassword, AuthUsername, RegisterSchema}; + use crate::api::schemas::{AuthUsername, RegisterSchema}; use crate::instance::Instance; use crate::limit::LimitedRequester; use crate::URLBundle; @@ -98,9 +98,9 @@ mod test { .unwrap(); let reg = RegisterSchema::new( AuthUsername::new("Hiiii".to_string()).unwrap(), - Some(AuthPassword::new("mysupersecurepass123!".to_string()).unwrap()), + None, true, - Some(AuthEmail::new("four7@aaaa.xyz".to_string()).unwrap()), + None, None, None, Some("2000-01-01".to_string()), @@ -109,6 +109,6 @@ mod test { None, ) .unwrap(); - let token = test_instance.register_account(®).await.unwrap().token; + let _ = test_instance.register_account(®).await.unwrap().token; } } diff --git a/src/api/channels/messages.rs b/src/api/channels/messages.rs index 082d822..a099418 100644 --- a/src/api/channels/messages.rs +++ b/src/api/channels/messages.rs @@ -1,8 +1,11 @@ pub mod messages { - use reqwest::Client; + use http::header::CONTENT_DISPOSITION; + use http::HeaderMap; + use reqwest::{multipart, Client}; use serde_json::to_string; use crate::api::types::{Message, PartialDiscordFileAttachment, User}; + use crate::errors::InstanceServerError; use crate::limit::LimitedRequester; impl Message { @@ -17,7 +20,6 @@ pub mod messages { # Errors * [`InstanceServerError`] - If the message cannot be sent. */ - pub async fn send<'a>( url_api: &String, channel_id: &String, @@ -44,10 +46,69 @@ pub mod messages { ) .await } else { - Err(crate::errors::InstanceServerError::InvalidFormBodyError { - error_type: "Not implemented".to_string(), - error: "Not implemented".to_string(), - }) + let mut form = reqwest::multipart::Form::new(); + let payload_json = to_string(message).unwrap(); + let mut payload_field = + reqwest::multipart::Part::text(payload_json).file_name("payload_json"); + payload_field = match payload_field.mime_str("application/json") { + Ok(part) => part, + Err(e) => { + return Err(InstanceServerError::MultipartCreationError { + error: e.to_string(), + }) + } + }; + + form = form.part("payload_json", payload_field); + + for (index, attachment) in files.unwrap().into_iter().enumerate() { + let (attachment_content, current_attachment) = attachment.move_content(); + let (attachment_filename, current_attachment) = + current_attachment.move_filename(); + let (attachment_content_type, _) = current_attachment.move_content_type(); + let part_name = format!("files[{}]", index); + let content_disposition = format!( + "form-data; name=\"{}\"'; filename=\"{}\"", + part_name, &attachment_filename + ); + let mut header_map = HeaderMap::new(); + header_map + .insert(CONTENT_DISPOSITION, content_disposition.parse().unwrap()) + .unwrap(); + + let mut part = multipart::Part::bytes(attachment_content) + .file_name(attachment_filename) + .headers(header_map); + + part = match part.mime_str( + attachment_content_type + .unwrap_or("application/octet-stream".to_string()) + .as_str(), + ) { + Ok(part) => part, + Err(e) => { + return Err(InstanceServerError::MultipartCreationError { + error: e.to_string(), + }) + } + }; + + form = form.part(part_name, part); + } + + let message_request = Client::new() + .post(format!("{}/channels/{}/messages/", url_api, channel_id)) + .bearer_auth(token) + .multipart(form); + + requester + .send_request( + message_request, + crate::api::limits::LimitType::Channel, + instance_rate_limits, + user_rate_limits, + ) + .await } } } @@ -95,7 +156,6 @@ mod test { None, None, None, - None, ); let mut instance = Instance::new( crate::URLBundle { @@ -121,9 +181,73 @@ mod test { let settings = login_result.settings; let limits = instance.limits.clone(); let mut user = crate::api::types::User::new(&mut instance, token, limits, settings, None); - let response = user + let _ = user .send_message(&mut message, &channel_id, None) .await .unwrap(); } + + #[tokio::test] + async fn send_message_two() { + let channel_id = "1104413094102290492".to_string(); + + let attachment = crate::api::types::PartialDiscordFileAttachment { + id: None, + filename: "test".to_string(), + description: None, + content_type: None, + size: None, + url: None, + proxy_url: None, + width: None, + height: None, + ephemeral: Some(false), + duration_secs: None, + waveform: None, + content: vec![8], + }; + + let mut message = crate::api::schemas::MessageSendSchema::new( + None, + Some("ashjkdhjksdfgjsdfzjkhsdvhjksdf".to_string()), + None, + None, + None, + None, + None, + None, + None, + Some(vec![attachment.clone()]), + ); + let mut instance = Instance::new( + crate::URLBundle { + api: "http://localhost:3001/api".to_string(), + wss: "ws://localhost:3001/".to_string(), + cdn: "http://localhost:3001".to_string(), + }, + LimitedRequester::new().await, + ) + .await + .unwrap(); + let login_schema: LoginSchema = LoginSchema::new( + AuthUsername::new("user1@gmail.com".to_string()).unwrap(), + "user".to_string(), + None, + None, + None, + None, + ) + .unwrap(); + let login_result = instance.login_account(&login_schema).await.unwrap(); + let token = login_result.token; + let settings = login_result.settings; + let limits = instance.limits.clone(); + let mut user = crate::api::types::User::new(&mut instance, token, limits, settings, None); + let vec_attach = vec![attachment.clone()]; + let _arg = Some(&vec_attach); + let _response = user + .send_message(&mut message, &channel_id, Some(vec![attachment.clone()])) + .await + .unwrap(); + } } diff --git a/src/api/policies/instance/instance.rs b/src/api/policies/instance/instance.rs index e08841f..21a0c89 100644 --- a/src/api/policies/instance/instance.rs +++ b/src/api/policies/instance/instance.rs @@ -51,6 +51,6 @@ mod instance_policies_schema_test { .await .unwrap(); - let schema = test_instance.instance_policies_schema().await.unwrap(); + let _schema = test_instance.instance_policies_schema().await.unwrap(); } } \ No newline at end of file diff --git a/src/api/schemas.rs b/src/api/schemas.rs index 8f151a3..edd8720 100644 --- a/src/api/schemas.rs +++ b/src/api/schemas.rs @@ -1,11 +1,11 @@ -use std::{collections::HashMap}; + use regex::Regex; use serde::{Deserialize, Serialize}; use crate::errors::FieldFormatError; -use super::{Embed}; +use super::Embed; /** A struct that represents a well-formed email address. @@ -257,8 +257,6 @@ pub struct MessageSendSchema { message_reference: Option, components: Option>, sticker_ids: Option>, - #[serde(flatten)] - files: Option>>, attachments: Option>, } @@ -274,7 +272,6 @@ impl MessageSendSchema { message_reference: Option, components: Option>, sticker_ids: Option>, - files: Option>>, attachments: Option>, ) -> MessageSendSchema { MessageSendSchema { @@ -287,7 +284,6 @@ impl MessageSendSchema { message_reference, components, sticker_ids, - files, attachments, } } diff --git a/src/api/types.rs b/src/api/types.rs index f94b458..1ad44c1 100644 --- a/src/api/types.rs +++ b/src/api/types.rs @@ -857,23 +857,94 @@ pub struct DiscordFileAttachment { ephemeral: Option, duration_secs: Option, waveform: Option, + #[serde(skip_serializing)] + content: Vec, } -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize, Clone)] pub struct PartialDiscordFileAttachment { pub id: Option, - pub filename: Option, - description: Option, - content_type: Option, - size: Option, - url: Option, - proxy_url: Option, - height: Option, - width: Option, - ephemeral: Option, - duration_secs: Option, - waveform: Option, + pub filename: String, + pub description: Option, + pub content_type: Option, + pub size: Option, + pub url: Option, + pub proxy_url: Option, + pub height: Option, + pub width: Option, + pub ephemeral: Option, + pub duration_secs: Option, + pub waveform: Option, + #[serde(skip_serializing)] + pub content: Vec, +} + +impl PartialDiscordFileAttachment { + /** + Moves `self.content` out of `self` and returns it. + # Returns + Vec + */ + pub fn move_content(self) -> (Vec, PartialDiscordFileAttachment) { + let content = self.content; + let updated_struct = PartialDiscordFileAttachment { + id: self.id, + filename: self.filename, + description: self.description, + content_type: self.content_type, + size: self.size, + url: self.url, + proxy_url: self.proxy_url, + height: self.height, + width: self.width, + ephemeral: self.ephemeral, + duration_secs: self.duration_secs, + waveform: self.waveform, + content: Vec::new(), + }; + (content, updated_struct) + } + + pub fn move_filename(self) -> (String, PartialDiscordFileAttachment) { + let filename = self.filename; + let updated_struct = PartialDiscordFileAttachment { + id: self.id, + filename: String::new(), + description: self.description, + content_type: self.content_type, + size: self.size, + url: self.url, + proxy_url: self.proxy_url, + height: self.height, + width: self.width, + ephemeral: self.ephemeral, + duration_secs: self.duration_secs, + waveform: self.waveform, + content: self.content, + }; + (filename, updated_struct) + } + + pub fn move_content_type(self) -> (Option, PartialDiscordFileAttachment) { + let content_type = self.content_type; + let updated_struct = PartialDiscordFileAttachment { + id: self.id, + filename: self.filename, + description: self.description, + content_type: None, + size: self.size, + url: self.url, + proxy_url: self.proxy_url, + height: self.height, + width: self.width, + ephemeral: self.ephemeral, + duration_secs: self.duration_secs, + waveform: self.waveform, + content: self.content, + }; + (content_type, updated_struct) + } } #[derive(Debug, Serialize, Deserialize)] diff --git a/src/errors.rs b/src/errors.rs index f74c7cd..76d16bd 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -18,6 +18,7 @@ custom_error! { CantGetInfoError{error:String} = "Something seems to be wrong with the instance. Cannot get information about the instance: {error}", InvalidFormBodyError{error_type: String, error:String} = "The server responded with: {error_type}: {error}", RateLimited = "Ratelimited.", + MultipartCreationError{error: String} = "Got an error whilst creating the form: {}", } custom_error! { diff --git a/src/limit.rs b/src/limit.rs index e2a6a59..82f5732 100644 --- a/src/limit.rs +++ b/src/limit.rs @@ -329,6 +329,6 @@ mod rate_limit { Ok(result) => result, Err(_) => panic!("Request failed"), }; - let config: Config = from_str(result.text().await.unwrap().as_str()).unwrap(); + let _config: Config = from_str(result.text().await.unwrap().as_str()).unwrap(); } }