From 54b9ff809981ad49fa50a5fcea585e05ac00ef5f Mon Sep 17 00:00:00 2001 From: Eden Zimbelman Date: Mon, 10 Nov 2025 17:48:50 -0800 Subject: [PATCH] feat: add markdown_text to chat.{postEphemeral,postMessage,scheduleMessage,update} api methods --- .../slack/api/methods/RequestFormBuilder.java | 13 +++++- .../chat/ChatPostEphemeralRequest.java | 7 +++- .../request/chat/ChatPostMessageRequest.java | 5 +++ .../chat/ChatScheduleMessageRequest.java | 7 +++- .../request/chat/ChatUpdateRequest.java | 7 +++- .../methods/chat_Test.java | 41 ++++++++++++++++++- 6 files changed, 74 insertions(+), 6 deletions(-) diff --git a/slack-api-client/src/main/java/com/slack/api/methods/RequestFormBuilder.java b/slack-api-client/src/main/java/com/slack/api/methods/RequestFormBuilder.java index 08b38a8fd..109d63f15 100644 --- a/slack-api-client/src/main/java/com/slack/api/methods/RequestFormBuilder.java +++ b/slack-api-client/src/main/java/com/slack/api/methods/RequestFormBuilder.java @@ -1500,6 +1500,7 @@ public static FormBody.Builder toForm(ChatScheduleMessageRequest req) { warnIfEitherTextOrAttachmentFallbackIsMissing( "chat.scheduleMessage", req.getText(), + req.getMarkdownText(), req.getAttachments(), req.getAttachmentsAsString()); FormBody.Builder form = new FormBody.Builder(); @@ -1531,6 +1532,7 @@ public static FormBody.Builder toForm(ChatScheduleMessageRequest req) { form.add("attachments", json); } setIfNotNull("link_names", req.isLinkNames(), form); + setIfNotNull("markdown_text", req.getMarkdownText(), form); setIfNotNull("parse", req.getParse(), form); setIfNotNull("reply_broadcast", req.isReplyBroadcast(), form); setIfNotNull("thread_ts", req.getThreadTs(), form); @@ -1554,6 +1556,7 @@ public static FormBody.Builder toForm(ChatPostEphemeralRequest req) { warnIfEitherTextOrAttachmentFallbackIsMissing( "chat.postEphemeral", req.getText(), + req.getMarkdownText(), req.getAttachments(), req.getAttachmentsAsString()); FormBody.Builder form = new FormBody.Builder(); @@ -1583,6 +1586,7 @@ public static FormBody.Builder toForm(ChatPostEphemeralRequest req) { setIfNotNull("icon_url", req.getIconUrl(), form); setIfNotNull("username", req.getUsername(), form); setIfNotNull("link_names", req.isLinkNames(), form); + setIfNotNull("markdown_text", req.getMarkdownText(), form); setIfNotNull("parse", req.getParse(), form); return form; } @@ -1591,6 +1595,7 @@ public static FormBody.Builder toForm(ChatPostMessageRequest req) { warnIfEitherTextOrAttachmentFallbackIsMissing( "chat.postMessage", req.getText(), + req.getMarkdownText(), req.getAttachments(), req.getAttachmentsAsString()); FormBody.Builder form = new FormBody.Builder(); @@ -1599,6 +1604,7 @@ public static FormBody.Builder toForm(ChatPostMessageRequest req) { setIfNotNull("text", req.getText(), form); setIfNotNull("parse", req.getParse(), form); setIfNotNull("link_names", req.isLinkNames(), form); + setIfNotNull("markdown_text", req.getMarkdownText(), form); setIfNotNull("mrkdwn", req.isMrkdwn(), form); if (req.getMetadataAsString() != null) { @@ -1702,11 +1708,13 @@ public static FormBody.Builder toForm(ChatUpdateRequest req) { warnIfEitherTextOrAttachmentFallbackIsMissing( "chat.update", req.getText(), + req.getMarkdownText(), req.getAttachments(), req.getAttachmentsAsString()); FormBody.Builder form = new FormBody.Builder(); setIfNotNull("ts", req.getTs(), form); setIfNotNull("channel", req.getChannel(), form); + setIfNotNull("markdown_text", req.getMarkdownText(), form); setIfNotNull("text", req.getText(), form); setIfNotNull("parse", req.getParse(), form); setIfNotNull("link_names", req.isLinkNames(), form); @@ -3153,6 +3161,7 @@ private static void warnIfAttachmentWithoutFallbackDetected(String endpointName, private static void warnIfEitherTextOrAttachmentFallbackIsMissing( String endpointName, String text, + String markdownText, List attachments, String attachmentsAsString) { @@ -3165,8 +3174,8 @@ private static void warnIfEitherTextOrAttachmentFallbackIsMissing( endpointName, Arrays.asList(GSON.fromJson(attachmentsAsString, Attachment[].class))); } else { - // when attachments do not exist, the top-level text is always required - if (text == null || text.trim().isEmpty()) { + // when attachments do not exist, the top-level text or markdown_text is always required + if ((text == null || text.trim().isEmpty()) && (markdownText == null || markdownText.trim().isEmpty())) { log.warn(TEXT_WARN_MESSAGE_TEMPLATE, endpointName); } } diff --git a/slack-api-client/src/main/java/com/slack/api/methods/request/chat/ChatPostEphemeralRequest.java b/slack-api-client/src/main/java/com/slack/api/methods/request/chat/ChatPostEphemeralRequest.java index 7e5154005..d95529d88 100644 --- a/slack-api-client/src/main/java/com/slack/api/methods/request/chat/ChatPostEphemeralRequest.java +++ b/slack-api-client/src/main/java/com/slack/api/methods/request/chat/ChatPostEphemeralRequest.java @@ -112,9 +112,14 @@ public void setAsUser(Boolean asUser) { */ private boolean linkNames; + /** + * Accepts message text formatted in markdown. This argument should not be used in conjunction with blocks or text. Limit this field to 12,000 characters. + */ + private String markdownText; + /** * Change how messages are treated. Defaults to `none`. See [below](#formatting). */ private String parse; -} \ No newline at end of file +} diff --git a/slack-api-client/src/main/java/com/slack/api/methods/request/chat/ChatPostMessageRequest.java b/slack-api-client/src/main/java/com/slack/api/methods/request/chat/ChatPostMessageRequest.java index 4078af202..2ed1558fe 100644 --- a/slack-api-client/src/main/java/com/slack/api/methods/request/chat/ChatPostMessageRequest.java +++ b/slack-api-client/src/main/java/com/slack/api/methods/request/chat/ChatPostMessageRequest.java @@ -53,6 +53,11 @@ public class ChatPostMessageRequest implements SlackApiRequest { */ private boolean linkNames; + /** + * Accepts message text formatted in markdown. This argument should not be used in conjunction with blocks or text. Limit this field to 12,000 characters. + */ + private String markdownText; + /** * JSON object with event_type and event_payload fields, presented as a URL-encoded string. * Metadata you post to Slack is accessible to any app or user who is a member of that workspace. diff --git a/slack-api-client/src/main/java/com/slack/api/methods/request/chat/ChatScheduleMessageRequest.java b/slack-api-client/src/main/java/com/slack/api/methods/request/chat/ChatScheduleMessageRequest.java index 9e7a5dd27..813823b8b 100644 --- a/slack-api-client/src/main/java/com/slack/api/methods/request/chat/ChatScheduleMessageRequest.java +++ b/slack-api-client/src/main/java/com/slack/api/methods/request/chat/ChatScheduleMessageRequest.java @@ -91,6 +91,11 @@ public void setAsUser(Boolean asUser) { */ private boolean linkNames; + /** + * Accepts message text formatted in markdown. This argument should not be used in conjunction with blocks or text. Limit this field to 12,000 characters. + */ + private String markdownText; + /** * Change how messages are treated. Defaults to none. See below. */ @@ -118,4 +123,4 @@ public void setAsUser(Boolean asUser) { */ private boolean unfurlMedia; -} \ No newline at end of file +} diff --git a/slack-api-client/src/main/java/com/slack/api/methods/request/chat/ChatUpdateRequest.java b/slack-api-client/src/main/java/com/slack/api/methods/request/chat/ChatUpdateRequest.java index f03cf4846..ae8f84459 100644 --- a/slack-api-client/src/main/java/com/slack/api/methods/request/chat/ChatUpdateRequest.java +++ b/slack-api-client/src/main/java/com/slack/api/methods/request/chat/ChatUpdateRequest.java @@ -77,6 +77,11 @@ public void setAsUser(Boolean asUser) { this.asUser = asUser; } + /** + * Accepts message text formatted in markdown. This argument should not be used in conjunction with blocks or text. Limit this field to 12,000 characters. + */ + private String markdownText; + /** * JSON object with event_type and event_payload fields, presented as a URL-encoded string. * Metadata you post to Slack is accessible to any app or user who is a member of that workspace. @@ -124,4 +129,4 @@ public void setAsUser(Boolean asUser) { */ private String parse; -} \ No newline at end of file +} diff --git a/slack-api-client/src/test/java/test_with_remote_apis/methods/chat_Test.java b/slack-api-client/src/test/java/test_with_remote_apis/methods/chat_Test.java index 4dd6a4c85..41f352dc4 100644 --- a/slack-api-client/src/test/java/test_with_remote_apis/methods/chat_Test.java +++ b/slack-api-client/src/test/java/test_with_remote_apis/methods/chat_Test.java @@ -201,6 +201,16 @@ public void postMessage_user() throws Exception { assertThat(scopes, is(notNullValue())); } + @Test + public void postMessage_markdownText() throws Exception { + loadRandomChannelId(); + ChatPostMessageResponse response = slack.methods(botToken).chatPostMessage(req -> req + .channel(randomChannelId) + .markdownText("**bold**")); + assertThat(response.getError(), is(nullValue())); + assertThat(response.getMessage().getText(), is("*bold*")); + } + // https://github.com/slackapi/java-slack-sdk/issues/157 @Test public void postMessage_blocksInAttachment_do_not_work_bot() throws Exception { @@ -781,6 +791,19 @@ public void unfurl_issue_399_flickr_example() throws Exception { assertThat(unfurlResponse.getError(), is(nullValue())); } + @Test + public void chatUpdate_markdownText() throws IOException, SlackApiException { + loadRandomChannelId(); + ChatPostMessageResponse creation = slack.methods(botToken) + .chatPostMessage(r -> r.channel(randomChannelId).text("plain")); + assertThat(creation.getError(), is(nullValue())); + assertThat(creation.getMessage().getText(), is("plain")); + ChatUpdateResponse modified = slack.methods(botToken) + .chatUpdate(r -> r.channel(randomChannelId).markdownText("**bold**").ts(creation.getTs())); + assertThat(modified.getError(), is(nullValue())); + assertThat(modified.getMessage().getText(), is("*bold*")); + } + @Test public void chatUpdateWithBotToken_issue_372() throws IOException, SlackApiException { loadRandomChannelId(); @@ -988,6 +1011,17 @@ public void postEphemeral_authorship() throws Exception { assertThat(response.getError(), is(nullValue())); } + @Test + public void postEphemeral_markdownText() throws Exception { + loadRandomChannelId(); + String userId = findUser(); + ChatPostEphemeralResponse response = slack.methods(botToken).chatPostEphemeral(r -> r + .user(userId) + .channel(randomChannelId) + .markdownText("**bold**")); + assertThat(response.getError(), is(nullValue())); + } + private String findUser() throws IOException, SlackApiException { String userId = null; @@ -1043,10 +1077,15 @@ public void scheduleMessages() throws IOException, SlackApiException { .blocks(blocks)); assertNull(message3.getError()); + ChatScheduleMessageResponse message4 = slack.methods(botToken) + .chatScheduleMessage(r -> r.channel(randomChannelId).postAt(postAt) + .markdownText("**bold**")); + assertNull(message4.getError()); + ChatScheduledMessagesListResponse after = slack.methods(botToken) .chatScheduledMessagesList(r -> r.limit(100)); assertNull(after.getError()); - assertTrue(after.getScheduledMessages().size() - before.getScheduledMessages().size() == 2); + assertTrue(after.getScheduledMessages().size() - before.getScheduledMessages().size() == 3); } @Test