Skip to main content

slack_rs/commands/
msg.rs

1//! Message command implementations
2
3use crate::api::{ApiClient, ApiError, ApiMethod, ApiResponse};
4use crate::commands::guards::{check_write_allowed, confirm_destructive_with_hint};
5use serde_json::json;
6use std::collections::HashMap;
7
8/// Post a message to a channel
9///
10/// # Arguments
11/// * `client` - API client
12/// * `channel` - Channel ID
13/// * `text` - Message text
14/// * `thread_ts` - Optional thread timestamp to reply to
15/// * `reply_broadcast` - Whether to broadcast thread reply to channel
16///
17/// # Returns
18/// * `Ok(ApiResponse)` with posted message information
19/// * `Err(ApiError)` if the operation fails
20pub async fn msg_post(
21    client: &ApiClient,
22    channel: String,
23    text: String,
24    thread_ts: Option<String>,
25    reply_broadcast: bool,
26) -> Result<ApiResponse, ApiError> {
27    check_write_allowed()?;
28
29    let mut params = HashMap::new();
30    params.insert("channel".to_string(), json!(channel));
31    params.insert("text".to_string(), json!(text));
32
33    if let Some(ts) = thread_ts {
34        params.insert("thread_ts".to_string(), json!(ts));
35        if reply_broadcast {
36            params.insert("reply_broadcast".to_string(), json!(true));
37        }
38    }
39
40    client.call_method(ApiMethod::ChatPostMessage, params).await
41}
42
43/// Update a message
44///
45/// # Arguments
46/// * `client` - API client
47/// * `channel` - Channel ID
48/// * `ts` - Message timestamp
49/// * `text` - New message text
50/// * `yes` - Skip confirmation prompt
51/// * `non_interactive` - Whether running in non-interactive mode
52///
53/// # Returns
54/// * `Ok(ApiResponse)` with updated message information
55/// * `Err(ApiError)` if the operation fails
56pub async fn msg_update(
57    client: &ApiClient,
58    channel: String,
59    ts: String,
60    text: String,
61    yes: bool,
62    non_interactive: bool,
63) -> Result<ApiResponse, ApiError> {
64    check_write_allowed()?;
65
66    // Build hint with example command for non-interactive mode
67    let hint = format!(
68        "Example: slack-rs msg update {} {} \"new text\" --yes",
69        channel, ts
70    );
71    confirm_destructive_with_hint(yes, "update this message", non_interactive, Some(&hint))?;
72
73    let mut params = HashMap::new();
74    params.insert("channel".to_string(), json!(channel));
75    params.insert("ts".to_string(), json!(ts));
76    params.insert("text".to_string(), json!(text));
77
78    client.call_method(ApiMethod::ChatUpdate, params).await
79}
80
81/// Delete a message
82///
83/// # Arguments
84/// * `client` - API client
85/// * `channel` - Channel ID
86/// * `ts` - Message timestamp
87/// * `yes` - Skip confirmation prompt
88/// * `non_interactive` - Whether running in non-interactive mode
89///
90/// # Returns
91/// * `Ok(ApiResponse)` with deletion confirmation
92/// * `Err(ApiError)` if the operation fails
93pub async fn msg_delete(
94    client: &ApiClient,
95    channel: String,
96    ts: String,
97    yes: bool,
98    non_interactive: bool,
99) -> Result<ApiResponse, ApiError> {
100    check_write_allowed()?;
101
102    // Build hint with example command for non-interactive mode
103    let hint = format!("Example: slack-rs msg delete {} {} --yes", channel, ts);
104    confirm_destructive_with_hint(yes, "delete this message", non_interactive, Some(&hint))?;
105
106    let mut params = HashMap::new();
107    params.insert("channel".to_string(), json!(channel));
108    params.insert("ts".to_string(), json!(ts));
109
110    client.call_method(ApiMethod::ChatDelete, params).await
111}
112
113#[cfg(test)]
114mod tests {
115    use super::*;
116    use serial_test::serial;
117
118    #[tokio::test]
119    #[serial(write_guard)]
120    async fn test_msg_post_with_env_false() {
121        std::env::set_var("SLACKCLI_ALLOW_WRITE", "false");
122        let client = ApiClient::with_token("test_token".to_string());
123        let result = msg_post(
124            &client,
125            "C123456".to_string(),
126            "test message".to_string(),
127            None,
128            false,
129        )
130        .await;
131        assert!(result.is_err());
132        assert!(matches!(result.unwrap_err(), ApiError::WriteNotAllowed));
133        std::env::remove_var("SLACKCLI_ALLOW_WRITE");
134    }
135
136    #[tokio::test]
137    #[serial(write_guard)]
138    async fn test_msg_update_with_env_false() {
139        std::env::set_var("SLACKCLI_ALLOW_WRITE", "false");
140        let client = ApiClient::with_token("test_token".to_string());
141        let result = msg_update(
142            &client,
143            "C123456".to_string(),
144            "1234567890.123456".to_string(),
145            "updated text".to_string(),
146            true,
147            false,
148        )
149        .await;
150        assert!(result.is_err());
151        assert!(matches!(result.unwrap_err(), ApiError::WriteNotAllowed));
152        std::env::remove_var("SLACKCLI_ALLOW_WRITE");
153    }
154
155    #[tokio::test]
156    #[serial(write_guard)]
157    async fn test_msg_delete_with_env_false() {
158        std::env::set_var("SLACKCLI_ALLOW_WRITE", "false");
159        let client = ApiClient::with_token("test_token".to_string());
160        let result = msg_delete(
161            &client,
162            "C123456".to_string(),
163            "1234567890.123456".to_string(),
164            true,
165            false,
166        )
167        .await;
168        assert!(result.is_err());
169        assert!(matches!(result.unwrap_err(), ApiError::WriteNotAllowed));
170        std::env::remove_var("SLACKCLI_ALLOW_WRITE");
171    }
172}