#![allow(clippy::field_reassign_with_default)]
use std::collections::HashMap;
use std::env;
use std::error;
use std::fmt;
use std::fmt::Debug;
use std::sync::Arc;
use chrono::offset::Utc;
use chrono::serde::ts_seconds;
use chrono::DateTime;
use reqwest::{header, Client, Method, Request, StatusCode, Url};
use serde::{Deserialize, Serialize};
const ENDPOINT: &str = "https://slack.com/api/";
pub struct Slack {
token: String,
workspace_id: String,
client: Arc<Client>,
}
impl Slack {
pub fn new<K, B>(token: K, workspace_id: B) -> Self
where
K: ToString,
B: ToString,
{
let client = Client::builder().build();
match client {
Ok(c) => Self {
token: token.to_string(),
workspace_id: workspace_id.to_string(),
client: Arc::new(c),
},
Err(e) => panic!("creating client failed: {:?}", e),
}
}
pub fn new_from_env() -> Self {
let token = env::var("SLACK_TOKEN").unwrap();
let workspace_id = env::var("SLACK_WORKSPACE_ID").unwrap_or_default();
Slack::new(token, workspace_id)
}
fn request<B>(&self, method: Method, path: &str, body: B, query: Option<Vec<(&str, String)>>) -> Request
where
B: Serialize,
{
let base = Url::parse(ENDPOINT).unwrap();
let url = base.join(path).unwrap();
let bt = format!("Bearer {}", self.token);
let bearer = header::HeaderValue::from_str(&bt).unwrap();
let mut headers = header::HeaderMap::new();
headers.append(header::AUTHORIZATION, bearer);
headers.append(header::CONTENT_TYPE, header::HeaderValue::from_static("application/json"));
let mut rb = self.client.request(method.clone(), url).headers(headers);
match query {
None => (),
Some(val) => {
rb = rb.query(&val);
}
}
if method != Method::GET && method != Method::DELETE {
rb = rb.json(&body);
}
rb.build().unwrap()
}
pub async fn list_users(&self) -> Result<Vec<User>, APIError> {
let request = self.request(Method::GET, "users.list", (), Some(vec![("limit", "100".to_string())]));
let resp = self.client.execute(request).await.unwrap();
match resp.status() {
StatusCode::OK => (),
s => {
return Err(APIError {
status_code: s,
body: resp.text().await.unwrap(),
})
}
};
let r: APIResponse = resp.json().await.unwrap();
Ok(r.users)
}
pub async fn invite_user(&self, invite: UserInvite) -> Result<(), APIError> {
let request = self.request(Method::POST, "admin.users.invite", invite, None);
let resp = self.client.execute(request).await.unwrap();
match resp.status() {
StatusCode::OK => (),
s => {
return Err(APIError {
status_code: s,
body: resp.text().await.unwrap(),
})
}
};
Ok(())
}
pub async fn remove_user(&self, user_id: &str) -> Result<(), APIError> {
let mut body: HashMap<&str, &str> = HashMap::new();
body.insert("team_id", &self.workspace_id);
body.insert("user_id", user_id);
let request = self.request(Method::POST, "admin.users.remove", body, None);
let resp = self.client.execute(request).await.unwrap();
match resp.status() {
StatusCode::OK => (),
s => {
return Err(APIError {
status_code: s,
body: resp.text().await.unwrap(),
})
}
};
Ok(())
}
pub async fn update_user_profile(&self, user_id: &str, profile: UserProfile) -> Result<(), APIError> {
let request = self.request(Method::POST, "users.profile.set", UpdateUserProfileRequest { user: user_id.to_string(), profile }, None);
let resp = self.client.execute(request).await.unwrap();
match resp.status() {
StatusCode::OK => (),
s => {
return Err(APIError {
status_code: s,
body: resp.text().await.unwrap(),
})
}
};
Ok(())
}
}
pub struct APIError {
pub status_code: StatusCode,
pub body: String,
}
impl fmt::Display for APIError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "APIError: status code -> {}, body -> {}", self.status_code.to_string(), self.body)
}
}
impl fmt::Debug for APIError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "APIError: status code -> {}, body -> {}", self.status_code.to_string(), self.body)
}
}
impl error::Error for APIError {
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
None
}
}
#[derive(Debug, Deserialize, Serialize)]
pub struct MessageResponse {
pub response_type: MessageResponseType,
pub text: String,
}
#[derive(Debug, Deserialize, Serialize)]
pub enum MessageResponseType {
#[serde(rename = "ephemeral")]
Ephemeral,
#[serde(rename = "in_channel")]
InChannel,
}
impl Default for MessageResponseType {
fn default() -> Self {
MessageResponseType::Ephemeral
}
}
#[derive(Debug, Default, Deserialize, Serialize)]
pub struct BotCommand {
pub user_name: String,
pub command: String,
pub text: String,
pub api_app_id: String,
pub response_url: String,
pub trigger_id: String,
pub channel_name: String,
pub team_domain: String,
pub team_id: String,
pub token: String,
pub channel_id: String,
pub user_id: String,
}
#[derive(Debug, Deserialize, Serialize)]
pub struct FormattedMessage {
#[serde(default, skip_serializing_if = "String::is_empty")]
pub channel: String,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub blocks: Vec<MessageBlock>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub attachments: Vec<MessageAttachment>,
}
#[derive(Debug, Default, Deserialize, Serialize)]
pub struct MessageBlock {
#[serde(rename = "type")]
pub block_type: MessageBlockType,
#[serde(skip_serializing_if = "Option::is_none")]
pub text: Option<MessageBlockText>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub elements: Vec<MessageBlockText>,
#[serde(default, skip_serializing_if = "String::is_empty")]
pub block_id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub accessory: Option<MessageBlockAccessory>,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub fields: Vec<MessageBlockText>,
}
#[derive(Debug, Deserialize, Serialize)]
pub enum MessageBlockType {
#[serde(rename = "section")]
Section,
#[serde(rename = "context")]
Context,
#[serde(rename = "divider")]
Divider,
}
impl Default for MessageBlockType {
fn default() -> Self {
MessageBlockType::Section
}
}
#[derive(Debug, Default, Deserialize, Serialize)]
pub struct MessageBlockText {
#[serde(rename = "type")]
pub text_type: MessageType,
pub text: String,
}
#[derive(Debug, Deserialize, Serialize)]
pub enum MessageType {
#[serde(rename = "mrkdwn")]
Markdown,
#[serde(rename = "image")]
Image,
}
impl Default for MessageType {
fn default() -> Self {
MessageType::Markdown
}
}
#[derive(Debug, Default, Deserialize, Serialize)]
pub struct MessageBlockAccessory {
#[serde(rename = "type")]
pub accessory_type: MessageType,
pub image_url: String,
pub alt_text: String,
}
#[derive(Debug, Deserialize, Serialize)]
pub struct MessageAttachment {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub blocks: Vec<MessageBlock>,
#[serde(default, skip_serializing_if = "String::is_empty")]
pub author_icon: String,
#[serde(default, skip_serializing_if = "String::is_empty")]
pub author_link: String,
#[serde(default, skip_serializing_if = "String::is_empty")]
pub author_name: String,
#[serde(default, skip_serializing_if = "String::is_empty")]
pub color: String,
#[serde(default, skip_serializing_if = "String::is_empty")]
pub fallback: String,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub fields: Vec<MessageAttachmentField>,
#[serde(default, skip_serializing_if = "String::is_empty")]
pub footer: String,
#[serde(default, skip_serializing_if = "String::is_empty")]
pub footer_icon: String,
#[serde(default, skip_serializing_if = "String::is_empty")]
pub image_url: String,
#[serde(default, skip_serializing_if = "String::is_empty")]
pub pretext: String,
#[serde(default, skip_serializing_if = "String::is_empty")]
pub text: String,
#[serde(default, skip_serializing_if = "String::is_empty")]
pub thumb_url: String,
#[serde(default, skip_serializing_if = "String::is_empty")]
pub title: String,
#[serde(default, skip_serializing_if = "String::is_empty")]
pub title_link: String,
#[serde(with = "ts_seconds")]
pub ts: DateTime<Utc>,
}
#[derive(Debug, Deserialize, Serialize)]
pub struct MessageAttachmentField {
pub short: bool,
pub title: String,
pub value: String,
}
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct UserProfile {
#[serde(default, skip_serializing_if = "String::is_empty")]
pub avatar_hash: String,
#[serde(default, skip_serializing_if = "String::is_empty")]
pub display_name: String,
#[serde(default, skip_serializing_if = "String::is_empty")]
pub display_name_normalized: String,
#[serde(default, skip_serializing_if = "String::is_empty")]
pub email: String,
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
pub fields: HashMap<String, UserProfileFields>,
#[serde(default, skip_serializing_if = "String::is_empty")]
pub first_name: String,
#[serde(default, skip_serializing_if = "String::is_empty")]
pub guest_channels: String,
#[serde(default, skip_serializing_if = "String::is_empty")]
pub image_192: String,
#[serde(default, skip_serializing_if = "String::is_empty")]
pub image_24: String,
#[serde(default, skip_serializing_if = "String::is_empty")]
pub image_32: String,
#[serde(default, skip_serializing_if = "String::is_empty")]
pub image_48: String,
#[serde(default, skip_serializing_if = "String::is_empty")]
pub image_512: String,
#[serde(default, skip_serializing_if = "String::is_empty")]
pub image_72: String,
#[serde(default, skip_serializing_if = "String::is_empty")]
pub image_original: String,
#[serde(default, skip_serializing_if = "String::is_empty")]
pub last_name: String,
#[serde(default, skip_serializing_if = "String::is_empty")]
pub phone: String,
#[serde(default, skip_serializing_if = "String::is_empty")]
pub real_name: String,
#[serde(default, skip_serializing_if = "String::is_empty")]
pub real_name_normalized: String,
#[serde(default, skip_serializing_if = "String::is_empty")]
pub skype: String,
#[serde(default, skip_serializing_if = "String::is_empty")]
pub status_emoji: String,
#[serde(default, skip_serializing_if = "String::is_empty")]
pub status_text: String,
#[serde(default, skip_serializing_if = "String::is_empty")]
pub team: String,
#[serde(default, skip_serializing_if = "String::is_empty")]
pub title: String,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct UserProfileFields {
pub alt: String,
pub label: String,
pub value: String,
}
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct UserInvite {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub channel_ids: Vec<String>,
pub email: String,
pub team_id: String,
#[serde(default, skip_serializing_if = "String::is_empty")]
pub custom_message: String,
pub is_restricted: bool,
pub is_ultra_restricted: bool,
pub real_name: String,
pub resend: bool,
}
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct APIResponse {
pub ok: bool,
#[serde(default, skip_serializing_if = "Vec::is_empty", alias = "members")]
pub users: Vec<User>,
}
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct User {
pub id: String,
#[serde(default, skip_serializing_if = "String::is_empty")]
pub team_id: String,
#[serde(default, skip_serializing_if = "String::is_empty")]
pub email: String,
#[serde(default)]
pub is_admin: bool,
#[serde(default)]
pub is_owner: bool,
#[serde(default)]
pub is_primary_owner: bool,
#[serde(default)]
pub is_restricted: bool,
#[serde(default)]
pub is_ultra_restricted: bool,
#[serde(default)]
pub is_bot: bool,
#[serde(default)]
pub deleted: bool,
#[serde(default)]
pub is_stranger: bool,
#[serde(default)]
pub is_app_user: bool,
#[serde(default)]
pub is_invited_user: bool,
#[serde(default)]
pub has_2fa: bool,
#[serde(default, skip_serializing_if = "String::is_empty")]
pub name: String,
#[serde(default, skip_serializing_if = "String::is_empty")]
pub real_name: String,
#[serde(default, skip_serializing_if = "String::is_empty")]
pub tz: String,
#[serde(default, skip_serializing_if = "String::is_empty")]
pub tz_label: String,
#[serde(default)]
pub tz_offset: i64,
#[serde(default)]
pub profile: UserProfile,
#[serde(default, skip_serializing_if = "String::is_empty")]
pub locale: String,
}
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct UpdateUserProfileRequest {
pub user: String,
pub profile: UserProfile,
}