1use clap::Subcommand;
2use crate::client::ClickUpClient;
3use crate::commands::auth::resolve_token;
4use crate::commands::workspace::resolve_workspace;
5use crate::error::CliError;
6use crate::output::OutputConfig;
7use crate::Cli;
8
9#[derive(Subcommand)]
10pub enum ChatCommands {
11 #[command(name = "channel-list")]
13 ChannelList {
14 #[arg(long)]
16 include_closed: bool,
17 },
18 #[command(name = "channel-create")]
20 ChannelCreate {
21 #[arg(long)]
23 name: String,
24 #[arg(long)]
26 visibility: Option<String>,
27 },
28 #[command(name = "channel-get")]
30 ChannelGet {
31 id: String,
33 },
34 #[command(name = "channel-update")]
36 ChannelUpdate {
37 id: String,
39 #[arg(long)]
41 name: Option<String>,
42 #[arg(long)]
44 topic: Option<String>,
45 },
46 #[command(name = "channel-delete")]
48 ChannelDelete {
49 id: String,
51 },
52 #[command(name = "channel-followers")]
54 ChannelFollowers {
55 id: String,
57 },
58 #[command(name = "channel-members")]
60 ChannelMembers {
61 id: String,
63 },
64 Dm {
66 user_ids: Vec<String>,
68 },
69 #[command(name = "message-list")]
71 MessageList {
72 #[arg(long)]
74 channel: String,
75 },
76 #[command(name = "message-send")]
78 MessageSend {
79 #[arg(long)]
81 channel: String,
82 #[arg(long)]
84 text: String,
85 #[arg(long, default_value = "message")]
87 r#type: String,
88 },
89 #[command(name = "message-update")]
91 MessageUpdate {
92 id: String,
94 #[arg(long)]
96 text: String,
97 },
98 #[command(name = "message-delete")]
100 MessageDelete {
101 id: String,
103 },
104 #[command(name = "reaction-list")]
106 ReactionList {
107 msg_id: String,
109 },
110 #[command(name = "reaction-add")]
112 ReactionAdd {
113 msg_id: String,
115 #[arg(long)]
117 emoji: String,
118 },
119 #[command(name = "reaction-remove")]
121 ReactionRemove {
122 msg_id: String,
124 emoji: String,
126 },
127 #[command(name = "reply-list")]
129 ReplyList {
130 msg_id: String,
132 },
133 #[command(name = "reply-send")]
135 ReplySend {
136 msg_id: String,
138 #[arg(long)]
140 text: String,
141 },
142 #[command(name = "tagged-users")]
144 TaggedUsers {
145 msg_id: String,
147 },
148}
149
150const CHANNEL_FIELDS: &[&str] = &["id", "name", "visibility", "type"];
151const MESSAGE_FIELDS: &[&str] = &["id", "content", "type", "date"];
152
153pub async fn execute(command: ChatCommands, cli: &Cli) -> Result<(), CliError> {
154 let token = resolve_token(cli)?;
155 let client = ClickUpClient::new(&token, cli.timeout)?;
156 let ws_id = resolve_workspace(cli)?;
157 let output = OutputConfig::from_cli(&cli.output, &cli.fields, cli.no_header, cli.quiet);
158 let base = format!("/v3/workspaces/{}/chat", ws_id);
159
160 match command {
161 ChatCommands::ChannelList { include_closed } => {
162 let query = if include_closed {
163 "?include_closed=true"
164 } else {
165 ""
166 };
167 let resp = client
168 .get(&format!("{}/channels{}", base, query))
169 .await?;
170 let mut channels = resp
171 .get("channels")
172 .and_then(|v| v.as_array())
173 .cloned()
174 .unwrap_or_default();
175 if let Some(limit) = cli.limit {
176 channels.truncate(limit);
177 }
178 output.print_items(&channels, CHANNEL_FIELDS, "id");
179 Ok(())
180 }
181 ChatCommands::ChannelCreate { name, visibility } => {
182 let mut body = serde_json::json!({ "name": name });
183 if let Some(v) = visibility {
184 body["visibility"] = serde_json::Value::String(v);
185 }
186 let resp = client
187 .post(&format!("{}/channels", base), &body)
188 .await?;
189 output.print_single(&resp, CHANNEL_FIELDS, "id");
190 Ok(())
191 }
192 ChatCommands::ChannelGet { id } => {
193 let resp = client
194 .get(&format!("{}/channels/{}", base, id))
195 .await?;
196 output.print_single(&resp, CHANNEL_FIELDS, "id");
197 Ok(())
198 }
199 ChatCommands::ChannelUpdate { id, name, topic } => {
200 let mut body = serde_json::Map::new();
201 if let Some(n) = name {
202 body.insert("name".into(), serde_json::Value::String(n));
203 }
204 if let Some(t) = topic {
205 body.insert("topic".into(), serde_json::Value::String(t));
206 }
207 let resp = client
208 .patch(
209 &format!("{}/channels/{}", base, id),
210 &serde_json::Value::Object(body),
211 )
212 .await?;
213 output.print_single(&resp, CHANNEL_FIELDS, "id");
214 Ok(())
215 }
216 ChatCommands::ChannelDelete { id } => {
217 client
218 .delete(&format!("{}/channels/{}", base, id))
219 .await?;
220 output.print_message(&format!("Channel {} deleted", id));
221 Ok(())
222 }
223 ChatCommands::ChannelFollowers { id } => {
224 let resp = client
225 .get(&format!("{}/channels/{}/followers", base, id))
226 .await?;
227 if cli.output == "json" {
228 println!("{}", serde_json::to_string_pretty(&resp).unwrap());
229 } else {
230 println!("{}", serde_json::to_string_pretty(&resp).unwrap());
231 }
232 Ok(())
233 }
234 ChatCommands::ChannelMembers { id } => {
235 let resp = client
236 .get(&format!("{}/channels/{}/members", base, id))
237 .await?;
238 if cli.output == "json" {
239 println!("{}", serde_json::to_string_pretty(&resp).unwrap());
240 } else {
241 println!("{}", serde_json::to_string_pretty(&resp).unwrap());
242 }
243 Ok(())
244 }
245 ChatCommands::Dm { user_ids } => {
246 let body = serde_json::json!({ "user_ids": user_ids });
247 let resp = client
248 .post(&format!("{}/channels/direct_message", base), &body)
249 .await?;
250 output.print_single(&resp, CHANNEL_FIELDS, "id");
251 Ok(())
252 }
253 ChatCommands::MessageList { channel } => {
254 let resp = client
255 .get(&format!("{}/channels/{}/messages", base, channel))
256 .await?;
257 let mut messages = resp
258 .get("messages")
259 .and_then(|v| v.as_array())
260 .cloned()
261 .unwrap_or_else(|| {
262 resp.as_array().cloned().unwrap_or_default()
264 });
265 if let Some(limit) = cli.limit {
266 messages.truncate(limit);
267 }
268 output.print_items(&messages, MESSAGE_FIELDS, "id");
269 Ok(())
270 }
271 ChatCommands::MessageSend {
272 channel,
273 text,
274 r#type,
275 } => {
276 let body = serde_json::json!({ "content": text, "type": r#type });
277 let resp = client
278 .post(&format!("{}/channels/{}/messages", base, channel), &body)
279 .await?;
280 output.print_single(&resp, MESSAGE_FIELDS, "id");
281 Ok(())
282 }
283 ChatCommands::MessageUpdate { id, text } => {
284 let body = serde_json::json!({ "content": text });
285 let resp = client
286 .patch(&format!("{}/messages/{}", base, id), &body)
287 .await?;
288 output.print_single(&resp, MESSAGE_FIELDS, "id");
289 Ok(())
290 }
291 ChatCommands::MessageDelete { id } => {
292 client
293 .delete(&format!("{}/messages/{}", base, id))
294 .await?;
295 output.print_message(&format!("Message {} deleted", id));
296 Ok(())
297 }
298 ChatCommands::ReactionList { msg_id } => {
299 let resp = client
300 .get(&format!("{}/messages/{}/reactions", base, msg_id))
301 .await?;
302 println!("{}", serde_json::to_string_pretty(&resp).unwrap());
303 Ok(())
304 }
305 ChatCommands::ReactionAdd { msg_id, emoji } => {
306 let body = serde_json::json!({ "emoji": emoji });
307 let resp = client
308 .post(&format!("{}/messages/{}/reactions", base, msg_id), &body)
309 .await?;
310 println!("{}", serde_json::to_string_pretty(&resp).unwrap());
311 Ok(())
312 }
313 ChatCommands::ReactionRemove { msg_id, emoji } => {
314 client
315 .delete(&format!("{}/messages/{}/reactions/{}", base, msg_id, emoji))
316 .await?;
317 output.print_message(&format!("Reaction '{}' removed from message {}", emoji, msg_id));
318 Ok(())
319 }
320 ChatCommands::ReplyList { msg_id } => {
321 let resp = client
322 .get(&format!("{}/messages/{}/replies", base, msg_id))
323 .await?;
324 let mut replies = resp
325 .get("replies")
326 .and_then(|v| v.as_array())
327 .cloned()
328 .unwrap_or_else(|| resp.as_array().cloned().unwrap_or_default());
329 if let Some(limit) = cli.limit {
330 replies.truncate(limit);
331 }
332 output.print_items(&replies, MESSAGE_FIELDS, "id");
333 Ok(())
334 }
335 ChatCommands::ReplySend { msg_id, text } => {
336 let body = serde_json::json!({ "content": text });
337 let resp = client
338 .post(&format!("{}/messages/{}/replies", base, msg_id), &body)
339 .await?;
340 output.print_single(&resp, MESSAGE_FIELDS, "id");
341 Ok(())
342 }
343 ChatCommands::TaggedUsers { msg_id } => {
344 let resp = client
345 .get(&format!("{}/messages/{}/tagged_users", base, msg_id))
346 .await?;
347 println!("{}", serde_json::to_string_pretty(&resp).unwrap());
348 Ok(())
349 }
350 }
351}