1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
// Copyright © 2021 Alexandra Frydl
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

//! Post Slack messages.

mod attachment;
mod message;

pub use self::attachment::Attachment;
pub use self::message::{Message, MessageId};

use crate::api;
use af_core::prelude::*;

/// Gets a permalink to a message.
pub async fn permalink_to<'a>(client: &api::Client, id: &MessageId) -> Result<String> {
  #[derive(Serialize)]
  struct Params<'a> {
    channel: &'a str,
    message_ts: &'a str,
  }

  #[derive(Deserialize)]
  struct Response {
    permalink: String,
  }

  let params = Params { channel: &id.channel, message_ts: &id.ts };
  let res = client.get::<_, Response>("chat.getPermalink", &params).await?;

  match res {
    Ok(res) => Ok(res.permalink),
    Err(err) => match err.error.as_str() {
      "channel_not_found" => Err(Error::ChannelNotFound),
      _ => Err(Error::Other(err)),
    },
  }
}

/// Posts a message to a channel.
pub async fn post<'a>(
  client: &api::Client,
  channel: impl AsRef<str>,
  message: impl Into<Message<'a>>,
) -> Result<MessageId> {
  #[derive(Serialize)]
  struct Request<'a> {
    channel: &'a str,
    #[serde(flatten)]
    message: Message<'a>,
  }

  let req = Request { channel: channel.as_ref(), message: message.into() };
  let res = client.post::<_, MessageId>("chat.postMessage", &req).await?;

  match res {
    Ok(id) => Ok(id),
    Err(err) => match err.error.as_str() {
      "channel_not_found" => Err(Error::ChannelNotFound),
      "not_in_channel" => Err(Error::NotInChannel),
      _ => Err(Error::Other(err)),
    },
  }
}

/// Replies to an existing message thread in a channel.
pub async fn reply<'a>(
  client: &api::Client,
  thread_id: &MessageId,
  message: impl Into<Message<'a>>,
) -> Result<MessageId> {
  #[derive(Serialize)]
  struct Request<'a> {
    channel: &'a str,
    #[serde(flatten)]
    message: Message<'a>,
    thread_ts: &'a str,
  }

  let req =
    Request { channel: &thread_id.channel, message: message.into(), thread_ts: &thread_id.ts };
  let res = client.post::<_, MessageId>("chat.postMessage", &req).await?;

  match res {
    Ok(id) => Ok(id),
    Err(err) => match err.error.as_str() {
      "channel_not_found" => Err(Error::ChannelNotFound),
      "not_in_channel" => Err(Error::NotInChannel),
      _ => Err(Error::Other(err)),
    },
  }
}

/// A chat error.
#[derive(Debug, Error)]
pub enum Error {
  /// The given channel was not found.
  #[error("channel not found")]
  ChannelNotFound,
  /// The app is not in the given channel.
  #[error("not in channel")]
  NotInChannel,
  /// A low-level API error occurred.
  #[error("api error: {0}")]
  ApiError(#[from] api::Error),
  /// Some other response error occurred.
  #[error("{0:#}")]
  Other(api::ErrorResponse),
}

pub type Result<T = (), E = Error> = std::result::Result<T, E>;