Skip to content

Commit

Permalink
Add attachments to Slack
Browse files Browse the repository at this point in the history
  • Loading branch information
GamePad64 committed Dec 15, 2024
1 parent 79a1b97 commit 5af595c
Show file tree
Hide file tree
Showing 8 changed files with 189 additions and 62 deletions.
7 changes: 7 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions notifico-attachment/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ pub struct AttachedFile {
pub file: File,
pub file_name: String,
pub mime_type: Mime,
pub size: u64,
pub plugin_values: HashMap<String, String>,
}

Expand Down Expand Up @@ -103,10 +104,13 @@ impl AttachmentPlugin {
let guessed_mimes = mime_guess::from_path(&file_name);
let mime_type = guessed_mimes.first_or(mime::APPLICATION_OCTET_STREAM);

let size = file.metadata().await?.len();

return Ok(AttachedFile {
file,
file_name,
mime_type,
size,
plugin_values: HashMap::new(),
});
}
Expand Down
2 changes: 1 addition & 1 deletion notifico-transports/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ pub fn all_transports(
));
plugins.push((waba_plugin.clone(), waba_plugin.clone()));

let slack_transport = Arc::new(SlackTransport::new(http.clone()));
let slack_transport = Arc::new(SlackTransport::new(http.clone(), attachments.clone()));
let slack_plugin = Arc::new(SimpleTransportWrapper::new(
slack_transport,
credentials.clone(),
Expand Down
8 changes: 8 additions & 0 deletions transports/notifico-slack/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,16 @@ edition = "2021"

[dependencies]
notifico-core = { path = "../../notifico-core" }
notifico-attachment = { path = "../../notifico-attachment" }

reqwest = { workspace = true }
serde = "1.0.215"
async-trait = "0.1.83"
serde_json = "1.0.133"
thiserror = "2.0.6"
tokio = { version = "1.42.0", features = ["fs"] }
tokio-util = "0.7.13"
url = { workspace = true }
serde_urlencoded = "0.7.1"
log = "0.4.22"
tracing = "0.1.41"
22 changes: 21 additions & 1 deletion transports/notifico-slack/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,26 @@ mod slackapi;

use async_trait::async_trait;
use credentials::SlackCredentials;
use notifico_attachment::AttachmentPlugin;
use notifico_core::contact::{RawContact, TypedContact};
use notifico_core::credentials::RawCredential;
use notifico_core::engine::{Message, PipelineContext};
use notifico_core::error::EngineError;
use notifico_core::simpletransport::SimpleTransport;
use notifico_core::templater::RenderedTemplate;
use serde::{Deserialize, Serialize};
use std::sync::Arc;

pub struct SlackTransport {
client: slackapi::SlackApi,
attachments: Arc<AttachmentPlugin>,
}

impl SlackTransport {
pub fn new(client: reqwest::Client) -> Self {
pub fn new(client: reqwest::Client, attachments: Arc<AttachmentPlugin>) -> Self {
SlackTransport {
client: slackapi::SlackApi::new(client),
attachments,
}
}
}
Expand All @@ -45,6 +49,22 @@ impl SimpleTransport for SlackTransport {
.chat_post_message(&credential.token, slack_message)
.await
.map_err(|e| EngineError::InternalError(e.into()))?;

for attachment in message.attachments {
let file = self.attachments.get_attachment(&attachment).await?;

self.client
.upload_file(
&credential.token,
file.file,
&file.file_name,
file.size,
&contact.channel_id,
)
.await
.map_err(|e| EngineError::InternalError(e.into()))?;
}

Ok(())
}

Expand Down
60 changes: 0 additions & 60 deletions transports/notifico-slack/src/slackapi.rs

This file was deleted.

123 changes: 123 additions & 0 deletions transports/notifico-slack/src/slackapi/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
mod schemas;

use crate::slackapi::schemas::{
CompleteUploadExternalRequest, CompleteUploadExternalRequestFile, GetUploadUrlExternalRequest,
GetUploadUrlExternalResponse,
};
use reqwest::header::AUTHORIZATION;
use reqwest::Body;
use serde::{Deserialize, Serialize};
use thiserror::Error;
use tokio::fs::File;
use tokio_util::codec::{BytesCodec, FramedRead};

#[derive(Deserialize, Debug)]
pub struct SlackStatusResponse {
ok: bool,
error: Option<String>,
}

impl SlackStatusResponse {
fn into_result(self) -> Result<Self, SlackError> {
match self.ok {
true => Ok(self),
false => Err(SlackError::ApiError {
error: self.error.unwrap_or_default(),
}),
}
}
}

#[derive(Error, Debug)]
pub enum SlackError {
#[error("{0}")]
Request(#[from] reqwest::Error),
#[error("Slack API returned error: {error:?}")]
ApiError { error: String },
}

pub struct SlackApi {
client: reqwest::Client,
}

#[derive(Serialize)]
#[serde(untagged)]
pub enum SlackMessage {
Text { channel: String, text: String },
}

impl SlackApi {
pub fn new(client: reqwest::Client) -> Self {
Self { client }
}

pub async fn chat_post_message(
&self,
token: &str,
message: SlackMessage,
) -> Result<SlackStatusResponse, SlackError> {
let resp = self
.client
.post("https://slack.com/api/chat.postMessage")
.header(AUTHORIZATION, String::from("Bearer ") + token)
.json(&message)
.send()
.await?;

resp.json::<SlackStatusResponse>().await?.into_result()
}

pub async fn upload_file(
&self,
token: &str,
file: File,
filename: &str,
length: u64,
channel: &str,
) -> Result<(), SlackError> {
let req = GetUploadUrlExternalRequest {
filename: filename.to_string(),
length,
};

let req_url = "https://slack.com/api/files.getUploadURLExternal?".to_string()
+ &serde_urlencoded::to_string(req).unwrap();

let resp = self
.client
.get(req_url)
.header(AUTHORIZATION, String::from("Bearer ") + token)
.send()
.await?;

let resp = resp.json::<GetUploadUrlExternalResponse>().await?;
let file_id = resp.file_id;

let stream = FramedRead::new(file, BytesCodec::new());

let _resp = self
.client
.post(resp.upload_url)
.header(AUTHORIZATION, String::from("Bearer ") + token)
.body(Body::wrap_stream(stream))
.send()
.await?;

let req = CompleteUploadExternalRequest {
files: vec![CompleteUploadExternalRequestFile { id: file_id }],
channel_id: Some(channel.to_string()),
};

let resp = self
.client
.post("https://slack.com/api/files.completeUploadExternal")
.header(AUTHORIZATION, String::from("Bearer ") + token)
.json(&req)
.send()
.await?;

resp.json::<SlackStatusResponse>().await?.into_result()?;

Ok(())
}
}
25 changes: 25 additions & 0 deletions transports/notifico-slack/src/slackapi/schemas.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
use serde::{Deserialize, Serialize};
use url::Url;

#[derive(Serialize)]
pub struct GetUploadUrlExternalRequest {
pub filename: String,
pub length: u64,
}

#[derive(Deserialize)]
pub struct GetUploadUrlExternalResponse {
pub upload_url: Url,
pub file_id: String,
}

#[derive(Serialize)]
pub struct CompleteUploadExternalRequestFile {
pub id: String,
}

#[derive(Serialize)]
pub struct CompleteUploadExternalRequest {
pub files: Vec<CompleteUploadExternalRequestFile>,
pub channel_id: Option<String>,
}

0 comments on commit 5af595c

Please sign in to comment.