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..ec0ecb3 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()), diff --git a/src/api/channels/messages.rs b/src/api/channels/messages.rs index 082d822..3f34100 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::{header, 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,12 +20,11 @@ pub mod messages { # Errors * [`InstanceServerError`] - If the message cannot be sent. */ - pub async fn send<'a>( url_api: &String, channel_id: &String, message: &mut crate::api::schemas::MessageSendSchema, - files: Option>, + files: Option<&'static Vec>, token: &String, user: &mut User<'a>, ) -> Result { @@ -44,10 +46,76 @@ 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.iter().enumerate() { + let part_name = format!("files[{}]", index); + let content_disposition = format!( + "form-data; name=\"{}\"'; filename=\"{}\"", + part_name, + attachment + .get(index) + .unwrap() + .filename + .as_deref() + .unwrap_or("file") + ); + let mut header_map = HeaderMap::new(); + header_map + .insert(CONTENT_DISPOSITION, content_disposition.parse().unwrap()) + .unwrap(); + + let mut part = + multipart::Part::bytes(attachment.get(index).unwrap().content.as_slice()) + .file_name( + attachment + .get(index) + .unwrap() + .filename + .as_deref() + .unwrap_or("file"), + ) + .headers(header_map); + + part = match part.mime_str("application/octet-stream") { + 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 + // TODO: Deallocate the darn memory leak! } } } @@ -57,7 +125,7 @@ pub mod messages { &mut self, message: &mut crate::api::schemas::MessageSendSchema, channel_id: &String, - files: Option>, + files: Option<&'static Vec>, ) -> Result { let token = self.token().clone(); Message::send( @@ -75,6 +143,8 @@ pub mod messages { #[cfg(test)] mod test { + use std::borrow::Cow; + use crate::{ api::{AuthUsername, LoginSchema}, instance::Instance, @@ -95,7 +165,6 @@ mod test { None, None, None, - None, ); let mut instance = Instance::new( crate::URLBundle { @@ -126,4 +195,68 @@ mod test { .await .unwrap(); } + + #[tokio::test] + async fn send_message_two() { + let channel_id = "1104413094102290492".to_string(); + + let attachment = crate::api::types::PartialDiscordFileAttachment { + id: None, + filename: Some("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, arg) + .await + .unwrap(); + } } diff --git a/src/api/schemas.rs b/src/api/schemas.rs index 8f151a3..ccdff53 100644 --- a/src/api/schemas.rs +++ b/src/api/schemas.rs @@ -1,11 +1,11 @@ -use std::{collections::HashMap}; +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 e52a2f8..00d47a7 100644 --- a/src/api/types.rs +++ b/src/api/types.rs @@ -839,23 +839,27 @@ 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 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, } #[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! {