From 6766122ab20b115062f11fbfb13c3bfea00fc00d Mon Sep 17 00:00:00 2001
From: ArturSlimac
Date: Tue, 17 Feb 2026 11:12:34 +0100
Subject: [PATCH 01/37] Redesign the usage of SlackClient with Builder
---
pom.xml | 14 ++++-
readme.md | 4 +-
.../n1netails/slack/api/SlackClient.java | 14 ++++-
.../n1netails/slack/api/SlackClientImpl.java | 51 +++++++++++++++++++
.../slack/internal/SlackClientImpl.java | 30 -----------
5 files changed, 79 insertions(+), 34 deletions(-)
create mode 100644 src/main/java/com/n1netails/n1netails/slack/api/SlackClientImpl.java
delete mode 100644 src/main/java/com/n1netails/n1netails/slack/internal/SlackClientImpl.java
diff --git a/pom.xml b/pom.xml
index cd876e1..25663ed 100644
--- a/pom.xml
+++ b/pom.xml
@@ -16,8 +16,20 @@
scm:git:git://github.com/n1netails/n1netails-slack-client.gitscm:git:ssh://git@github.com:n1netails/n1netails-slack-client.git
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+ 17
+ 17
+
+
+
+
-
+ MIT Licensehttps://opensource.org/licenses/MIT
diff --git a/readme.md b/readme.md
index ded405a..827fc04 100644
--- a/readme.md
+++ b/readme.md
@@ -51,7 +51,7 @@ Here's how to use the client to send a message:
```java
import com.n1netails.n1netails.slack.api.SlackClient;
-import com.n1netails.n1netails.slack.internal.SlackClientImpl;
+import com.n1netails.n1netails.slack.api.SlackClientImpl;
import com.n1netails.n1netails.slack.model.SlackMessage;
import com.n1netails.n1netails.slack.service.BotService;
@@ -90,7 +90,7 @@ You can also send more complex messages using [Slack's Block Kit](https://api.sl
```java
import com.n1netails.n1netails.slack.api.SlackClient;
-import com.n1netails.n1netails.slack.internal.SlackClientImpl;
+import com.n1netails.n1netails.slack.api.SlackClientImpl;
import com.n1netails.n1netails.slack.model.SlackMessage;
import com.n1netails.n1netails.slack.service.BotService;
import com.slack.api.model.block.Blocks;
diff --git a/src/main/java/com/n1netails/n1netails/slack/api/SlackClient.java b/src/main/java/com/n1netails/n1netails/slack/api/SlackClient.java
index 527e8da..d8c77ed 100644
--- a/src/main/java/com/n1netails/n1netails/slack/api/SlackClient.java
+++ b/src/main/java/com/n1netails/n1netails/slack/api/SlackClient.java
@@ -5,13 +5,25 @@
/**
* Slick Client
+ *
* @author shahid foy
*/
-public interface SlackClient {
+public sealed interface SlackClient permits SlackClientImpl {
/**
* Send slack message
+ *
* @param slackMessage slack message
*/
void sendMessage(SlackMessage slackMessage) throws SlackClientException;
+
+ static Builder builder() {
+ return new SlackClientImpl.Builder();
+ }
+
+ sealed interface Builder permits SlackClientImpl.Builder {
+ Builder token(String token);
+
+ SlackClient build() throws SlackClientException;
+ }
}
diff --git a/src/main/java/com/n1netails/n1netails/slack/api/SlackClientImpl.java b/src/main/java/com/n1netails/n1netails/slack/api/SlackClientImpl.java
new file mode 100644
index 0000000..8f9aae7
--- /dev/null
+++ b/src/main/java/com/n1netails/n1netails/slack/api/SlackClientImpl.java
@@ -0,0 +1,51 @@
+package com.n1netails.n1netails.slack.api;
+
+import com.n1netails.n1netails.slack.exception.SlackClientException;
+import com.n1netails.n1netails.slack.model.SlackMessage;
+import com.n1netails.n1netails.slack.service.BotService;
+
+/**
+ * Slack Client Implementation
+ *
+ * @author shahid foy
+ */
+final class SlackClientImpl implements SlackClient {
+
+ private final BotService botService;
+
+ public SlackClientImpl(Builder builder) {
+ this.botService = new BotService(builder.token);
+ }
+
+ /**
+ * Send slack notification
+ *
+ * @param slackMessage slack message
+ */
+ @Override
+ public void sendMessage(SlackMessage slackMessage) throws SlackClientException {
+ try {
+ botService.send(slackMessage);
+ } catch (Exception e) {
+ throw new SlackClientException("Failed to send Slack message", e);
+ }
+ }
+
+ public static final class Builder implements SlackClient.Builder {
+ private String token;
+
+ @Override
+ public SlackClient.Builder token(String token) {
+ this.token = token;
+ return this;
+ }
+
+ @Override
+ public SlackClient build() throws SlackClientException {
+ if (this.token == null || this.token.isBlank())
+ throw new SlackClientException("token must be provided");
+
+ return new SlackClientImpl(this);
+ }
+ }
+}
diff --git a/src/main/java/com/n1netails/n1netails/slack/internal/SlackClientImpl.java b/src/main/java/com/n1netails/n1netails/slack/internal/SlackClientImpl.java
deleted file mode 100644
index 3550acb..0000000
--- a/src/main/java/com/n1netails/n1netails/slack/internal/SlackClientImpl.java
+++ /dev/null
@@ -1,30 +0,0 @@
-package com.n1netails.n1netails.slack.internal;
-
-import com.n1netails.n1netails.slack.api.SlackClient;
-import com.n1netails.n1netails.slack.exception.SlackClientException;
-import com.n1netails.n1netails.slack.model.SlackMessage;
-import com.n1netails.n1netails.slack.service.BotService;
-
-/**
- * Slack Client Implementation
- * @author shahid foy
- */
-public class SlackClientImpl implements SlackClient {
-
- private final BotService botService;
-
- public SlackClientImpl(BotService botService) { this.botService = botService; }
-
- /**
- * Send slack notification
- * @param slackMessage slack message
- */
- @Override
- public void sendMessage(SlackMessage slackMessage) throws SlackClientException {
- try {
- botService.send(slackMessage);
- } catch (Exception e) {
- throw new SlackClientException("Failed to send Slack message", e);
- }
- }
-}
From 63937a54af1685639bbc2803ed25e7a4668a56c1 Mon Sep 17 00:00:00 2001
From: ArturSlimac
Date: Tue, 17 Feb 2026 11:27:54 +0100
Subject: [PATCH 02/37] Update readme.md
---
readme.md | 14 +++++---------
1 file changed, 5 insertions(+), 9 deletions(-)
diff --git a/readme.md b/readme.md
index 827fc04..e112c44 100644
--- a/readme.md
+++ b/readme.md
@@ -53,7 +53,7 @@ Here's how to use the client to send a message:
import com.n1netails.n1netails.slack.api.SlackClient;
import com.n1netails.n1netails.slack.api.SlackClientImpl;
import com.n1netails.n1netails.slack.model.SlackMessage;
-import com.n1netails.n1netails.slack.service.BotService;
+import com.n1netails.n1netails.slack.api.BotService;
public class Example {
public static void main(String[] args) {
@@ -63,11 +63,8 @@ public class Example {
// The channel you want to send the message to (e.g., "#general")
String channel = "#prototype"; // or "#channel-name"
- // Create the bot service
- BotService botService = new BotService(token);
-
- // Create the Slack client
- SlackClient slackClient = new SlackClientImpl(botService);
+ // Use builder to create a client
+ SlackClient client = SlackClient.builder().token(token).build();
// Create the message
SlackMessage message = new SlackMessage();
@@ -92,7 +89,7 @@ You can also send more complex messages using [Slack's Block Kit](https://api.sl
import com.n1netails.n1netails.slack.api.SlackClient;
import com.n1netails.n1netails.slack.api.SlackClientImpl;
import com.n1netails.n1netails.slack.model.SlackMessage;
-import com.n1netails.n1netails.slack.service.BotService;
+import com.n1netails.n1netails.slack.api.BotService;
import com.slack.api.model.block.Blocks;
import com.slack.api.model.block.composition.BlockCompositions;
@@ -103,8 +100,7 @@ public class AdvancedExample {
String token = "xoxb-your-bot-token";
String channel = "#prototype";
- BotService botService = new BotService(token);
- SlackClient slackClient = new SlackClientImpl(botService);
+ SlackClient client = SlackClient.builder().token(token).build();
SlackMessage message = new SlackMessage();
message.setChannel(channel);
From 6d80c6f3fd0f13eedb12ccda7c1e279115ab39df Mon Sep 17 00:00:00 2001
From: ArturSlimac
Date: Tue, 17 Feb 2026 11:28:48 +0100
Subject: [PATCH 03/37] Encapsulation: only SlackClient is available for
creation
---
.../n1netails/slack/{service => api}/BotService.java | 6 ++++--
.../java/com/n1netails/n1netails/slack/api/SlackClient.java | 1 +
.../com/n1netails/n1netails/slack/api/SlackClientImpl.java | 1 -
3 files changed, 5 insertions(+), 3 deletions(-)
rename src/main/java/com/n1netails/n1netails/slack/{service => api}/BotService.java (94%)
diff --git a/src/main/java/com/n1netails/n1netails/slack/service/BotService.java b/src/main/java/com/n1netails/n1netails/slack/api/BotService.java
similarity index 94%
rename from src/main/java/com/n1netails/n1netails/slack/service/BotService.java
rename to src/main/java/com/n1netails/n1netails/slack/api/BotService.java
index 4d3ae8f..6696c58 100644
--- a/src/main/java/com/n1netails/n1netails/slack/service/BotService.java
+++ b/src/main/java/com/n1netails/n1netails/slack/api/BotService.java
@@ -1,4 +1,4 @@
-package com.n1netails.n1netails.slack.service;
+package com.n1netails.n1netails.slack.api;
import com.n1netails.n1netails.slack.exception.SlackClientException;
import com.n1netails.n1netails.slack.model.SlackMessage;
@@ -8,14 +8,16 @@
/**
* Slack Bot Service
+ *
* @author shahid foy
*/
-public class BotService {
+class BotService {
private final String token;
/**
* Bot Service Constructor
+ *
* @param token slack bot token
*/
public BotService(String token) {
diff --git a/src/main/java/com/n1netails/n1netails/slack/api/SlackClient.java b/src/main/java/com/n1netails/n1netails/slack/api/SlackClient.java
index d8c77ed..4cb0bd9 100644
--- a/src/main/java/com/n1netails/n1netails/slack/api/SlackClient.java
+++ b/src/main/java/com/n1netails/n1netails/slack/api/SlackClient.java
@@ -1,5 +1,6 @@
package com.n1netails.n1netails.slack.api;
+import com.n1netails.n1netails.slack.api.SlackClientImpl;
import com.n1netails.n1netails.slack.exception.SlackClientException;
import com.n1netails.n1netails.slack.model.SlackMessage;
diff --git a/src/main/java/com/n1netails/n1netails/slack/api/SlackClientImpl.java b/src/main/java/com/n1netails/n1netails/slack/api/SlackClientImpl.java
index 8f9aae7..423d8b7 100644
--- a/src/main/java/com/n1netails/n1netails/slack/api/SlackClientImpl.java
+++ b/src/main/java/com/n1netails/n1netails/slack/api/SlackClientImpl.java
@@ -2,7 +2,6 @@
import com.n1netails.n1netails.slack.exception.SlackClientException;
import com.n1netails.n1netails.slack.model.SlackMessage;
-import com.n1netails.n1netails.slack.service.BotService;
/**
* Slack Client Implementation
From d5ed27b462c3b4d420c53ddd365df78f525a4b4f Mon Sep 17 00:00:00 2001
From: ArturSlimac
Date: Tue, 17 Feb 2026 11:46:17 +0100
Subject: [PATCH 04/37] Prevents recreating client every call
---
.../java/com/n1netails/n1netails/slack/api/BotService.java | 6 ++----
1 file changed, 2 insertions(+), 4 deletions(-)
diff --git a/src/main/java/com/n1netails/n1netails/slack/api/BotService.java b/src/main/java/com/n1netails/n1netails/slack/api/BotService.java
index 6696c58..2d39cab 100644
--- a/src/main/java/com/n1netails/n1netails/slack/api/BotService.java
+++ b/src/main/java/com/n1netails/n1netails/slack/api/BotService.java
@@ -12,8 +12,7 @@
* @author shahid foy
*/
class BotService {
-
- private final String token;
+ private final MethodsClient methods;
/**
* Bot Service Constructor
@@ -21,12 +20,11 @@ class BotService {
* @param token slack bot token
*/
public BotService(String token) {
- this.token = token;
+ this.methods = Slack.getInstance().methods(token);
}
public void send(SlackMessage slackMessage) throws SlackClientException {
try {
- MethodsClient methods = Slack.getInstance().methods(token);
ChatPostMessageRequest.ChatPostMessageRequestBuilder requestBuilder = ChatPostMessageRequest.builder()
.channel(slackMessage.getChannel())
.text(slackMessage.getText());
From 53db1c853fea7198ea9dbc1341ed036240cde9e8 Mon Sep 17 00:00:00 2001
From: ArturSlimac
Date: Tue, 17 Feb 2026 11:57:04 +0100
Subject: [PATCH 05/37] Error handling
---
.../n1netails/slack/api/BotService.java | 33 +++++++++++++++----
.../n1netails/slack/api/SlackClientImpl.java | 6 +---
2 files changed, 28 insertions(+), 11 deletions(-)
diff --git a/src/main/java/com/n1netails/n1netails/slack/api/BotService.java b/src/main/java/com/n1netails/n1netails/slack/api/BotService.java
index 2d39cab..8da2f79 100644
--- a/src/main/java/com/n1netails/n1netails/slack/api/BotService.java
+++ b/src/main/java/com/n1netails/n1netails/slack/api/BotService.java
@@ -4,7 +4,11 @@
import com.n1netails.n1netails.slack.model.SlackMessage;
import com.slack.api.Slack;
import com.slack.api.methods.MethodsClient;
+import com.slack.api.methods.SlackApiException;
import com.slack.api.methods.request.chat.ChatPostMessageRequest;
+import com.slack.api.methods.response.chat.ChatPostMessageResponse;
+
+import java.io.IOException;
/**
* Slack Bot Service
@@ -24,18 +28,35 @@ public BotService(String token) {
}
public void send(SlackMessage slackMessage) throws SlackClientException {
+ validateSlackMessage(slackMessage);
try {
- ChatPostMessageRequest.ChatPostMessageRequestBuilder requestBuilder = ChatPostMessageRequest.builder()
- .channel(slackMessage.getChannel())
- .text(slackMessage.getText());
+ ChatPostMessageRequest.ChatPostMessageRequestBuilder requestBuilder =
+ ChatPostMessageRequest.builder()
+ .channel(slackMessage.getChannel())
+ .text(slackMessage.getText());
if (slackMessage.getBlocks() != null && !slackMessage.getBlocks().isEmpty()) {
requestBuilder.blocks(slackMessage.getBlocks());
}
- methods.chatPostMessage(requestBuilder.build());
- } catch (Exception e) {
- throw new SlackClientException("Failed to send Slack message", e);
+ ChatPostMessageResponse response = methods.chatPostMessage(requestBuilder.build());
+
+ if (!response.isOk()) {
+ throw new SlackClientException("Slack API error: " + response.getError());
+ }
+
+ } catch (IOException | SlackApiException e) {
+ throw new SlackClientException("Failed to send Slack message to channel: " + slackMessage.getChannel(), e);
+ }
+ }
+
+ private void validateSlackMessage(SlackMessage slackMessage) throws SlackClientException {
+ if (slackMessage == null) {
+ throw new SlackClientException("slackMessage cannot be null");
+ }
+
+ if (slackMessage.getChannel() == null || slackMessage.getChannel().isBlank()) {
+ throw new SlackClientException("Channel must be provided");
}
}
}
diff --git a/src/main/java/com/n1netails/n1netails/slack/api/SlackClientImpl.java b/src/main/java/com/n1netails/n1netails/slack/api/SlackClientImpl.java
index 423d8b7..1e9bebd 100644
--- a/src/main/java/com/n1netails/n1netails/slack/api/SlackClientImpl.java
+++ b/src/main/java/com/n1netails/n1netails/slack/api/SlackClientImpl.java
@@ -23,11 +23,7 @@ public SlackClientImpl(Builder builder) {
*/
@Override
public void sendMessage(SlackMessage slackMessage) throws SlackClientException {
- try {
- botService.send(slackMessage);
- } catch (Exception e) {
- throw new SlackClientException("Failed to send Slack message", e);
- }
+ botService.send(slackMessage);
}
public static final class Builder implements SlackClient.Builder {
From 1c4eed41ee9c56808be910ef8521bbf7ea7abe3e Mon Sep 17 00:00:00 2001
From: ArturSlimac
Date: Tue, 17 Feb 2026 13:23:01 +0100
Subject: [PATCH 06/37] Support pics and gifs
---
.../n1netails/slack/api/BotService.java | 21 +++++--
.../n1netails/slack/model/SlackBlock.java | 9 +++
.../n1netails/slack/model/SlackMessage.java | 56 +++++++++++++++++--
.../slack/model/blocks/GifBlock.java | 26 +++++++++
.../slack/model/blocks/ImageBlock.java | 29 ++++++++++
5 files changed, 130 insertions(+), 11 deletions(-)
create mode 100644 src/main/java/com/n1netails/n1netails/slack/model/SlackBlock.java
create mode 100644 src/main/java/com/n1netails/n1netails/slack/model/blocks/GifBlock.java
create mode 100644 src/main/java/com/n1netails/n1netails/slack/model/blocks/ImageBlock.java
diff --git a/src/main/java/com/n1netails/n1netails/slack/api/BotService.java b/src/main/java/com/n1netails/n1netails/slack/api/BotService.java
index 8da2f79..e1d4aec 100644
--- a/src/main/java/com/n1netails/n1netails/slack/api/BotService.java
+++ b/src/main/java/com/n1netails/n1netails/slack/api/BotService.java
@@ -1,14 +1,19 @@
package com.n1netails.n1netails.slack.api;
import com.n1netails.n1netails.slack.exception.SlackClientException;
+import com.n1netails.n1netails.slack.model.SlackBlock;
import com.n1netails.n1netails.slack.model.SlackMessage;
import com.slack.api.Slack;
import com.slack.api.methods.MethodsClient;
import com.slack.api.methods.SlackApiException;
import com.slack.api.methods.request.chat.ChatPostMessageRequest;
import com.slack.api.methods.response.chat.ChatPostMessageResponse;
+import com.slack.api.model.block.LayoutBlock;
+import lombok.Getter;
+import lombok.Setter;
import java.io.IOException;
+import java.util.List;
/**
* Slack Bot Service
@@ -34,9 +39,18 @@ public void send(SlackMessage slackMessage) throws SlackClientException {
ChatPostMessageRequest.builder()
.channel(slackMessage.getChannel())
.text(slackMessage.getText());
+ List finalBlocks = null;
+ if (slackMessage.getRawBlocks() != null && !slackMessage.getRawBlocks().isEmpty()) {
+ finalBlocks = slackMessage.getRawBlocks();
- if (slackMessage.getBlocks() != null && !slackMessage.getBlocks().isEmpty()) {
- requestBuilder.blocks(slackMessage.getBlocks());
+ } else if (slackMessage.getBlocks() != null && !slackMessage.getBlocks().isEmpty()) {
+ finalBlocks = slackMessage.getBlocks().stream()
+ .map(SlackBlock::toLayoutBlock)
+ .toList();
+ }
+
+ if (finalBlocks != null && !finalBlocks.isEmpty()) {
+ requestBuilder.blocks(finalBlocks);
}
ChatPostMessageResponse response = methods.chatPostMessage(requestBuilder.build());
@@ -55,8 +69,5 @@ private void validateSlackMessage(SlackMessage slackMessage) throws SlackClientE
throw new SlackClientException("slackMessage cannot be null");
}
- if (slackMessage.getChannel() == null || slackMessage.getChannel().isBlank()) {
- throw new SlackClientException("Channel must be provided");
- }
}
}
diff --git a/src/main/java/com/n1netails/n1netails/slack/model/SlackBlock.java b/src/main/java/com/n1netails/n1netails/slack/model/SlackBlock.java
new file mode 100644
index 0000000..ced5e45
--- /dev/null
+++ b/src/main/java/com/n1netails/n1netails/slack/model/SlackBlock.java
@@ -0,0 +1,9 @@
+package com.n1netails.n1netails.slack.model;
+
+import com.slack.api.model.block.LayoutBlock;
+
+import java.util.List;
+
+public interface SlackBlock {
+ LayoutBlock toLayoutBlock();
+}
diff --git a/src/main/java/com/n1netails/n1netails/slack/model/SlackMessage.java b/src/main/java/com/n1netails/n1netails/slack/model/SlackMessage.java
index 007f8f7..ecddfeb 100644
--- a/src/main/java/com/n1netails/n1netails/slack/model/SlackMessage.java
+++ b/src/main/java/com/n1netails/n1netails/slack/model/SlackMessage.java
@@ -4,6 +4,7 @@
import lombok.Getter;
import lombok.Setter;
+import java.util.ArrayList;
import java.util.List;
/**
@@ -11,15 +12,58 @@
* @author shahid foy
*/
@Getter
-@Setter
public class SlackMessage {
private String channel;
private String text;
- private List blocks;
+ private List blocks;
+ private List rawBlocks;
- /**
- * Slack Message Constructor
- */
- public SlackMessage() {}
+ private SlackMessage(Builder builder) {
+ this.channel = builder.channel;
+ this.text = builder.text;
+ this.blocks = List.copyOf(builder.blocks);
+ this.rawBlocks = List.copyOf(builder.rawBlocks);
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static class Builder {
+ private String channel;
+ private String text;
+ private final List blocks = new ArrayList<>();
+ private final List rawBlocks = new ArrayList<>();
+
+ public Builder channel(String channel) {
+ this.channel = channel;
+ return this;
+ }
+
+ public Builder text(String text) {
+ this.text = text;
+ return this;
+ }
+
+ public Builder addBlock(SlackBlock block) {
+ this.blocks.add(block);
+ return this;
+ }
+
+ public Builder addRawBlock(LayoutBlock block) {
+ this.rawBlocks.add(block);
+ return this;
+ }
+
+ public SlackMessage build() {
+ if (channel == null || channel.isBlank()) {
+ throw new IllegalStateException("channel is required");
+ }
+ if ((text == null || text.isBlank()) && blocks.isEmpty() && rawBlocks.isEmpty()) {
+ throw new IllegalStateException("Either text, blocks, or rawBlocks must be provided");
+ }
+ return new SlackMessage(this);
+ }
+ }
}
diff --git a/src/main/java/com/n1netails/n1netails/slack/model/blocks/GifBlock.java b/src/main/java/com/n1netails/n1netails/slack/model/blocks/GifBlock.java
new file mode 100644
index 0000000..ba55585
--- /dev/null
+++ b/src/main/java/com/n1netails/n1netails/slack/model/blocks/GifBlock.java
@@ -0,0 +1,26 @@
+package com.n1netails.n1netails.slack.model.blocks;
+
+import com.n1netails.n1netails.slack.model.SlackBlock;
+import com.slack.api.model.block.LayoutBlock;
+import com.slack.api.model.block.composition.PlainTextObject;
+
+public class GifBlock implements SlackBlock {
+ private final String imageUrl;
+ private final String altText;
+
+ public GifBlock(String imageUrl, String altText) {
+ this.imageUrl = imageUrl;
+ this.altText = altText;
+ }
+
+
+ @Override
+ public LayoutBlock toLayoutBlock() {
+ return
+ com.slack.api.model.block.ImageBlock.builder()
+ .altText(altText)
+ .imageUrl(imageUrl)
+ .build()
+ ;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/n1netails/n1netails/slack/model/blocks/ImageBlock.java b/src/main/java/com/n1netails/n1netails/slack/model/blocks/ImageBlock.java
new file mode 100644
index 0000000..1a4141c
--- /dev/null
+++ b/src/main/java/com/n1netails/n1netails/slack/model/blocks/ImageBlock.java
@@ -0,0 +1,29 @@
+package com.n1netails.n1netails.slack.model.blocks;
+
+import com.n1netails.n1netails.slack.model.SlackBlock;
+import com.slack.api.model.block.Blocks;
+import com.slack.api.model.block.LayoutBlock;
+import com.slack.api.model.block.composition.BlockCompositions;
+
+import java.util.List;
+
+public class ImageBlock implements SlackBlock {
+ private final String imageUrl;
+ private final String altText;
+
+ public ImageBlock(String imageUrl, String altText) {
+ this.imageUrl = imageUrl;
+ this.altText = altText;
+ }
+
+
+ @Override
+ public LayoutBlock toLayoutBlock() {
+ return
+ com.slack.api.model.block.ImageBlock.builder()
+ .imageUrl(imageUrl)
+ .altText(altText)
+ .build()
+ ;
+ }
+}
From 51c6ba2062ec64750d3e91efdd49ad86aafe9063 Mon Sep 17 00:00:00 2001
From: ArturSlimac
Date: Tue, 17 Feb 2026 13:42:55 +0100
Subject: [PATCH 07/37] Support text
---
.../slack/model/blocks/TextBlock.java | 20 +++++++++++++++++++
1 file changed, 20 insertions(+)
create mode 100644 src/main/java/com/n1netails/n1netails/slack/model/blocks/TextBlock.java
diff --git a/src/main/java/com/n1netails/n1netails/slack/model/blocks/TextBlock.java b/src/main/java/com/n1netails/n1netails/slack/model/blocks/TextBlock.java
new file mode 100644
index 0000000..2109046
--- /dev/null
+++ b/src/main/java/com/n1netails/n1netails/slack/model/blocks/TextBlock.java
@@ -0,0 +1,20 @@
+package com.n1netails.n1netails.slack.model.blocks;
+
+import com.n1netails.n1netails.slack.model.SlackBlock;
+import com.slack.api.model.block.LayoutBlock;
+import com.slack.api.model.block.composition.PlainTextObject;
+
+public class TextBlock implements SlackBlock {
+ private final String text;
+
+ public TextBlock(String text) {
+ this.text = text;
+ }
+
+ @Override
+ public LayoutBlock toLayoutBlock() {
+ return com.slack.api.model.block.SectionBlock.builder()
+ .text(new PlainTextObject(text, false))
+ .build();
+ }
+}
From a271562f11df23f1484ed7ba162342edcf1f51e1 Mon Sep 17 00:00:00 2001
From: ArturSlimac
Date: Tue, 17 Feb 2026 13:45:21 +0100
Subject: [PATCH 08/37] prevent usage of blocks and rawBlocks together
---
.../n1netails/n1netails/slack/api/BotService.java | 5 +++--
.../n1netails/slack/model/SlackMessage.java | 14 +++++++++++++-
2 files changed, 16 insertions(+), 3 deletions(-)
diff --git a/src/main/java/com/n1netails/n1netails/slack/api/BotService.java b/src/main/java/com/n1netails/n1netails/slack/api/BotService.java
index e1d4aec..caa22e6 100644
--- a/src/main/java/com/n1netails/n1netails/slack/api/BotService.java
+++ b/src/main/java/com/n1netails/n1netails/slack/api/BotService.java
@@ -39,7 +39,7 @@ public void send(SlackMessage slackMessage) throws SlackClientException {
ChatPostMessageRequest.builder()
.channel(slackMessage.getChannel())
.text(slackMessage.getText());
- List finalBlocks = null;
+ List finalBlocks;
if (slackMessage.getRawBlocks() != null && !slackMessage.getRawBlocks().isEmpty()) {
finalBlocks = slackMessage.getRawBlocks();
@@ -47,7 +47,8 @@ public void send(SlackMessage slackMessage) throws SlackClientException {
finalBlocks = slackMessage.getBlocks().stream()
.map(SlackBlock::toLayoutBlock)
.toList();
- }
+ } else
+ finalBlocks = null;
if (finalBlocks != null && !finalBlocks.isEmpty()) {
requestBuilder.blocks(finalBlocks);
diff --git a/src/main/java/com/n1netails/n1netails/slack/model/SlackMessage.java b/src/main/java/com/n1netails/n1netails/slack/model/SlackMessage.java
index ecddfeb..05d82a0 100644
--- a/src/main/java/com/n1netails/n1netails/slack/model/SlackMessage.java
+++ b/src/main/java/com/n1netails/n1netails/slack/model/SlackMessage.java
@@ -9,6 +9,7 @@
/**
* Slack Message
+ *
* @author shahid foy
*/
@Getter
@@ -60,10 +61,21 @@ public SlackMessage build() {
if (channel == null || channel.isBlank()) {
throw new IllegalStateException("channel is required");
}
- if ((text == null || text.isBlank()) && blocks.isEmpty() && rawBlocks.isEmpty()) {
+
+ boolean hasTextOrBlocks = (text != null && !text.isBlank())
+ || (!blocks.isEmpty())
+ || (!rawBlocks.isEmpty());
+
+ if (!hasTextOrBlocks) {
throw new IllegalStateException("Either text, blocks, or rawBlocks must be provided");
}
+
+ if (!blocks.isEmpty() && !rawBlocks.isEmpty()) {
+ throw new IllegalStateException("Cannot mix SlackBlock and rawBlocks in the same message");
+ }
+
return new SlackMessage(this);
}
+
}
}
From f545153289e21af2ad5da24454f465bdfbafc416 Mon Sep 17 00:00:00 2001
From: ArturSlimac
Date: Tue, 17 Feb 2026 13:45:39 +0100
Subject: [PATCH 09/37] update readme.md
---
readme.md | 14 ++++++++++----
1 file changed, 10 insertions(+), 4 deletions(-)
diff --git a/readme.md b/readme.md
index e112c44..255d1c1 100644
--- a/readme.md
+++ b/readme.md
@@ -47,13 +47,15 @@ implementation 'com.n1netails:n1netails-slack-client:0.2.0'
```
## Usage
-Here's how to use the client to send a message:
+Here's how to use the client to send a message, pics or gifs:
```java
import com.n1netails.n1netails.slack.api.SlackClient;
import com.n1netails.n1netails.slack.api.SlackClientImpl;
import com.n1netails.n1netails.slack.model.SlackMessage;
import com.n1netails.n1netails.slack.api.BotService;
+import com.n1netails.n1netails.slack.model.blocks.GifBlock;
+import com.n1netails.n1netails.slack.model.blocks.TextBlock;
public class Example {
public static void main(String[] args) {
@@ -67,9 +69,13 @@ public class Example {
SlackClient client = SlackClient.builder().token(token).build();
// Create the message
- SlackMessage message = new SlackMessage();
- message.setChannel(channel);
- message.setText("Hello from the N1ne Tails Slack Client!");
+ SlackMessage message = SlackMessage.builder()
+ .channel("test")
+ .text("New content 🚀") //Remember if use ANY blocks this text becomes a fallback message for notifications
+ .addBlock(new TextBlock("New content 🚀"))
+ .addBlock(new ImageBlock("YOUR IMG LINK", "YOUR ALT TEXT"))
+ .addBlock(new GifBlock("YOUR GIF LINK", "YOUR ALT TEXT"))
+ .build();
try {
// Send the message
From 330f1271caffe57d04a14fd82f8e8036b10f27e5 Mon Sep 17 00:00:00 2001
From: ArturSlimac
Date: Tue, 17 Feb 2026 20:37:09 +0100
Subject: [PATCH 10/37] Exceptions handling
---
.../n1netails/n1netails/slack/api/BotService.java | 9 ++++++---
.../n1netails/slack/api/SlackClientImpl.java | 2 +-
.../slack/exception/SlackApiExceptionWrapper.java | 14 ++++++++++++++
.../slack/exception/SlackTransportException.java | 7 +++++++
.../slack/exception/SlackValidationException.java | 7 +++++++
5 files changed, 35 insertions(+), 4 deletions(-)
create mode 100644 src/main/java/com/n1netails/n1netails/slack/exception/SlackApiExceptionWrapper.java
create mode 100644 src/main/java/com/n1netails/n1netails/slack/exception/SlackTransportException.java
create mode 100644 src/main/java/com/n1netails/n1netails/slack/exception/SlackValidationException.java
diff --git a/src/main/java/com/n1netails/n1netails/slack/api/BotService.java b/src/main/java/com/n1netails/n1netails/slack/api/BotService.java
index caa22e6..31b6d4e 100644
--- a/src/main/java/com/n1netails/n1netails/slack/api/BotService.java
+++ b/src/main/java/com/n1netails/n1netails/slack/api/BotService.java
@@ -1,5 +1,6 @@
package com.n1netails.n1netails.slack.api;
+import com.n1netails.n1netails.slack.exception.SlackApiExceptionWrapper;
import com.n1netails.n1netails.slack.exception.SlackClientException;
import com.n1netails.n1netails.slack.model.SlackBlock;
import com.n1netails.n1netails.slack.model.SlackMessage;
@@ -57,11 +58,13 @@ public void send(SlackMessage slackMessage) throws SlackClientException {
ChatPostMessageResponse response = methods.chatPostMessage(requestBuilder.build());
if (!response.isOk()) {
- throw new SlackClientException("Slack API error: " + response.getError());
+ throw new SlackApiExceptionWrapper(response.getError());
}
- } catch (IOException | SlackApiException e) {
- throw new SlackClientException("Failed to send Slack message to channel: " + slackMessage.getChannel(), e);
+ } catch (IOException e) {
+ throw new SlackClientException("Network error while calling Slack API", e);
+ } catch (SlackApiException e) {
+ throw new SlackClientException("Slack SDK failure" + slackMessage.getChannel(), e);
}
}
diff --git a/src/main/java/com/n1netails/n1netails/slack/api/SlackClientImpl.java b/src/main/java/com/n1netails/n1netails/slack/api/SlackClientImpl.java
index 1e9bebd..6b96f3c 100644
--- a/src/main/java/com/n1netails/n1netails/slack/api/SlackClientImpl.java
+++ b/src/main/java/com/n1netails/n1netails/slack/api/SlackClientImpl.java
@@ -38,7 +38,7 @@ public SlackClient.Builder token(String token) {
@Override
public SlackClient build() throws SlackClientException {
if (this.token == null || this.token.isBlank())
- throw new SlackClientException("token must be provided");
+ throw new SlackClientException("Token must be provided");
return new SlackClientImpl(this);
}
diff --git a/src/main/java/com/n1netails/n1netails/slack/exception/SlackApiExceptionWrapper.java b/src/main/java/com/n1netails/n1netails/slack/exception/SlackApiExceptionWrapper.java
new file mode 100644
index 0000000..249a358
--- /dev/null
+++ b/src/main/java/com/n1netails/n1netails/slack/exception/SlackApiExceptionWrapper.java
@@ -0,0 +1,14 @@
+package com.n1netails.n1netails.slack.exception;
+
+public class SlackApiExceptionWrapper extends SlackClientException {
+ private final String slackError;
+
+ public SlackApiExceptionWrapper(String slackError) {
+ super("Slack API error: " + slackError);
+ this.slackError = slackError;
+ }
+
+ public String getSlackError() {
+ return slackError;
+ }
+}
diff --git a/src/main/java/com/n1netails/n1netails/slack/exception/SlackTransportException.java b/src/main/java/com/n1netails/n1netails/slack/exception/SlackTransportException.java
new file mode 100644
index 0000000..5baaae9
--- /dev/null
+++ b/src/main/java/com/n1netails/n1netails/slack/exception/SlackTransportException.java
@@ -0,0 +1,7 @@
+package com.n1netails.n1netails.slack.exception;
+
+public class SlackTransportException extends SlackClientException {
+ public SlackTransportException(String message) {
+ super(message);
+ }
+}
diff --git a/src/main/java/com/n1netails/n1netails/slack/exception/SlackValidationException.java b/src/main/java/com/n1netails/n1netails/slack/exception/SlackValidationException.java
new file mode 100644
index 0000000..671c5a8
--- /dev/null
+++ b/src/main/java/com/n1netails/n1netails/slack/exception/SlackValidationException.java
@@ -0,0 +1,7 @@
+package com.n1netails.n1netails.slack.exception;
+
+public class SlackValidationException extends SlackClientException {
+ public SlackValidationException(String message) {
+ super(message);
+ }
+}
From 4bcc3d1b86f8a78c7c3d88524245c4441ba5af61 Mon Sep 17 00:00:00 2001
From: ArturSlimac
Date: Tue, 17 Feb 2026 21:16:49 +0100
Subject: [PATCH 11/37] Graceful fallback for unsupported or invalid media
---
.../slack/fallback/SlackFallbackHandler.java | 26 ++++++++++
.../n1netails/slack/model/SlackMessage.java | 50 +++++++++++++++----
.../slack/model/blocks/FallbackBlock.java | 20 ++++++++
.../slack/model/blocks/GifBlock.java | 11 ++--
.../slack/model/blocks/ImageBlock.java | 3 +-
.../slack/model/blocks/TextBlock.java | 2 +
.../slack/validation/SlackValidator.java | 7 +++
.../slack/validation/SlackValidators.java | 34 +++++++++++++
.../validation/impl/GifBlockValidator.java | 18 +++++++
.../validation/impl/ImageBlockValidator.java | 18 +++++++
.../validation/impl/TextBlockValidator.java | 15 ++++++
11 files changed, 188 insertions(+), 16 deletions(-)
create mode 100644 src/main/java/com/n1netails/n1netails/slack/fallback/SlackFallbackHandler.java
create mode 100644 src/main/java/com/n1netails/n1netails/slack/model/blocks/FallbackBlock.java
create mode 100644 src/main/java/com/n1netails/n1netails/slack/validation/SlackValidator.java
create mode 100644 src/main/java/com/n1netails/n1netails/slack/validation/SlackValidators.java
create mode 100644 src/main/java/com/n1netails/n1netails/slack/validation/impl/GifBlockValidator.java
create mode 100644 src/main/java/com/n1netails/n1netails/slack/validation/impl/ImageBlockValidator.java
create mode 100644 src/main/java/com/n1netails/n1netails/slack/validation/impl/TextBlockValidator.java
diff --git a/src/main/java/com/n1netails/n1netails/slack/fallback/SlackFallbackHandler.java b/src/main/java/com/n1netails/n1netails/slack/fallback/SlackFallbackHandler.java
new file mode 100644
index 0000000..da8d9bf
--- /dev/null
+++ b/src/main/java/com/n1netails/n1netails/slack/fallback/SlackFallbackHandler.java
@@ -0,0 +1,26 @@
+package com.n1netails.n1netails.slack.fallback;
+
+import com.n1netails.n1netails.slack.model.SlackBlock;
+import com.n1netails.n1netails.slack.model.blocks.FallbackBlock;
+import com.n1netails.n1netails.slack.model.blocks.GifBlock;
+import com.n1netails.n1netails.slack.model.blocks.ImageBlock;
+import com.n1netails.n1netails.slack.model.blocks.TextBlock;
+
+public class SlackFallbackHandler {
+ public static SlackBlock handle(SlackBlock block, Exception e) {
+
+ if (block instanceof ImageBlock img) {
+ return new FallbackBlock("Image could not be displayed: " + img.getAltText());
+ }
+
+ if (block instanceof GifBlock gif) {
+ return new FallbackBlock("GIF could not be displayed: " + gif.getAltText());
+ }
+
+ if (block instanceof TextBlock txt) {
+ return new FallbackBlock("Text could not be displayed");
+ }
+
+ return new FallbackBlock("Unsupported content");
+ }
+}
diff --git a/src/main/java/com/n1netails/n1netails/slack/model/SlackMessage.java b/src/main/java/com/n1netails/n1netails/slack/model/SlackMessage.java
index 05d82a0..05c16b5 100644
--- a/src/main/java/com/n1netails/n1netails/slack/model/SlackMessage.java
+++ b/src/main/java/com/n1netails/n1netails/slack/model/SlackMessage.java
@@ -1,5 +1,8 @@
package com.n1netails.n1netails.slack.model;
+import com.n1netails.n1netails.slack.exception.SlackValidationException;
+import com.n1netails.n1netails.slack.fallback.SlackFallbackHandler;
+import com.n1netails.n1netails.slack.validation.SlackValidators;
import com.slack.api.model.block.LayoutBlock;
import lombok.Getter;
import lombok.Setter;
@@ -47,32 +50,59 @@ public Builder text(String text) {
return this;
}
- public Builder addBlock(SlackBlock block) {
+ public Builder addBlock(SlackBlock block) throws SlackValidationException {
+ if (!rawBlocks.isEmpty()) {
+ throw new SlackValidationException(
+ "Cannot add SlackBlock when rawBlocks are already present"
+ );
+ }
this.blocks.add(block);
return this;
}
- public Builder addRawBlock(LayoutBlock block) {
+ public Builder addRawBlock(LayoutBlock block) throws SlackValidationException {
+ if (!blocks.isEmpty()) {
+ throw new SlackValidationException(
+ "Cannot add rawBlock when SlackBlocks are already present"
+ );
+ }
this.rawBlocks.add(block);
return this;
}
- public SlackMessage build() {
+ public SlackMessage build() throws SlackValidationException {
if (channel == null || channel.isBlank()) {
- throw new IllegalStateException("channel is required");
+ throw new SlackValidationException("channel is required");
}
- boolean hasTextOrBlocks = (text != null && !text.isBlank())
- || (!blocks.isEmpty())
- || (!rawBlocks.isEmpty());
+ boolean hasContent = (text != null && !text.isBlank())
+ || !blocks.isEmpty()
+ || !rawBlocks.isEmpty();
- if (!hasTextOrBlocks) {
- throw new IllegalStateException("Either text, blocks, or rawBlocks must be provided");
+ if (!hasContent) {
+ throw new SlackValidationException(
+ "Either text, blocks, or rawBlocks must be provided"
+ );
}
if (!blocks.isEmpty() && !rawBlocks.isEmpty()) {
- throw new IllegalStateException("Cannot mix SlackBlock and rawBlocks in the same message");
+ throw new SlackValidationException(
+ "Cannot mix SlackBlock and rawBlocks in the same message"
+ );
}
+ List processedBlocks = new ArrayList<>();
+
+ for (SlackBlock block : blocks) {
+ try {
+ SlackValidators.validate(block);
+ processedBlocks.add(block);
+ } catch (Exception e) {
+ processedBlocks.add(SlackFallbackHandler.handle(block, e));
+ }
+ }
+
+ this.blocks.clear();
+ this.blocks.addAll(processedBlocks);
return new SlackMessage(this);
}
diff --git a/src/main/java/com/n1netails/n1netails/slack/model/blocks/FallbackBlock.java b/src/main/java/com/n1netails/n1netails/slack/model/blocks/FallbackBlock.java
new file mode 100644
index 0000000..a483ba1
--- /dev/null
+++ b/src/main/java/com/n1netails/n1netails/slack/model/blocks/FallbackBlock.java
@@ -0,0 +1,20 @@
+package com.n1netails.n1netails.slack.model.blocks;
+
+import com.n1netails.n1netails.slack.model.SlackBlock;
+import com.slack.api.model.block.LayoutBlock;
+import com.slack.api.model.block.composition.PlainTextObject;
+
+public class FallbackBlock implements SlackBlock {
+
+ private final String text;
+
+ public FallbackBlock(String text) {
+ this.text = text;
+ }
+ @Override
+ public LayoutBlock toLayoutBlock() {
+ return com.slack.api.model.block.SectionBlock.builder()
+ .text(new PlainTextObject(text, false))
+ .build();
+ }
+}
diff --git a/src/main/java/com/n1netails/n1netails/slack/model/blocks/GifBlock.java b/src/main/java/com/n1netails/n1netails/slack/model/blocks/GifBlock.java
index ba55585..15f7398 100644
--- a/src/main/java/com/n1netails/n1netails/slack/model/blocks/GifBlock.java
+++ b/src/main/java/com/n1netails/n1netails/slack/model/blocks/GifBlock.java
@@ -2,14 +2,15 @@
import com.n1netails.n1netails.slack.model.SlackBlock;
import com.slack.api.model.block.LayoutBlock;
-import com.slack.api.model.block.composition.PlainTextObject;
+import lombok.Getter;
+@Getter
public class GifBlock implements SlackBlock {
- private final String imageUrl;
+ private final String gifUrl;
private final String altText;
- public GifBlock(String imageUrl, String altText) {
- this.imageUrl = imageUrl;
+ public GifBlock(String gifUrl, String altText) {
+ this.gifUrl = gifUrl;
this.altText = altText;
}
@@ -19,7 +20,7 @@ public LayoutBlock toLayoutBlock() {
return
com.slack.api.model.block.ImageBlock.builder()
.altText(altText)
- .imageUrl(imageUrl)
+ .imageUrl(gifUrl)
.build()
;
}
diff --git a/src/main/java/com/n1netails/n1netails/slack/model/blocks/ImageBlock.java b/src/main/java/com/n1netails/n1netails/slack/model/blocks/ImageBlock.java
index 1a4141c..2c9cff1 100644
--- a/src/main/java/com/n1netails/n1netails/slack/model/blocks/ImageBlock.java
+++ b/src/main/java/com/n1netails/n1netails/slack/model/blocks/ImageBlock.java
@@ -4,9 +4,10 @@
import com.slack.api.model.block.Blocks;
import com.slack.api.model.block.LayoutBlock;
import com.slack.api.model.block.composition.BlockCompositions;
+import lombok.Getter;
-import java.util.List;
+@Getter
public class ImageBlock implements SlackBlock {
private final String imageUrl;
private final String altText;
diff --git a/src/main/java/com/n1netails/n1netails/slack/model/blocks/TextBlock.java b/src/main/java/com/n1netails/n1netails/slack/model/blocks/TextBlock.java
index 2109046..95f3b94 100644
--- a/src/main/java/com/n1netails/n1netails/slack/model/blocks/TextBlock.java
+++ b/src/main/java/com/n1netails/n1netails/slack/model/blocks/TextBlock.java
@@ -3,7 +3,9 @@
import com.n1netails.n1netails.slack.model.SlackBlock;
import com.slack.api.model.block.LayoutBlock;
import com.slack.api.model.block.composition.PlainTextObject;
+import lombok.Getter;
+@Getter
public class TextBlock implements SlackBlock {
private final String text;
diff --git a/src/main/java/com/n1netails/n1netails/slack/validation/SlackValidator.java b/src/main/java/com/n1netails/n1netails/slack/validation/SlackValidator.java
new file mode 100644
index 0000000..d5b9091
--- /dev/null
+++ b/src/main/java/com/n1netails/n1netails/slack/validation/SlackValidator.java
@@ -0,0 +1,7 @@
+package com.n1netails.n1netails.slack.validation;
+
+import com.n1netails.n1netails.slack.exception.SlackValidationException;
+
+public interface SlackValidator {
+ void validate(T target) throws SlackValidationException;
+}
diff --git a/src/main/java/com/n1netails/n1netails/slack/validation/SlackValidators.java b/src/main/java/com/n1netails/n1netails/slack/validation/SlackValidators.java
new file mode 100644
index 0000000..d52fe30
--- /dev/null
+++ b/src/main/java/com/n1netails/n1netails/slack/validation/SlackValidators.java
@@ -0,0 +1,34 @@
+package com.n1netails.n1netails.slack.validation;
+
+import com.n1netails.n1netails.slack.exception.SlackValidationException;
+import com.n1netails.n1netails.slack.model.blocks.GifBlock;
+import com.n1netails.n1netails.slack.model.blocks.ImageBlock;
+import com.n1netails.n1netails.slack.model.blocks.TextBlock;
+import com.n1netails.n1netails.slack.validation.impl.GifBlockValidator;
+import com.n1netails.n1netails.slack.validation.impl.ImageBlockValidator;
+import com.n1netails.n1netails.slack.validation.impl.TextBlockValidator;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class SlackValidators {
+ private static final Map, SlackValidator>> VALIDATORS = new HashMap<>();
+
+ static {
+ VALIDATORS.put(ImageBlock.class, new ImageBlockValidator());
+ VALIDATORS.put(GifBlock.class, new GifBlockValidator());
+ VALIDATORS.put(TextBlock.class, new TextBlockValidator());
+ }
+
+ @SuppressWarnings("unchecked")
+ public static void validate(T target) throws SlackValidationException {
+ if (target == null) return;
+
+ SlackValidator validator =
+ (SlackValidator) VALIDATORS.get(target.getClass());
+
+ if (validator != null) {
+ validator.validate(target);
+ }
+ }
+}
diff --git a/src/main/java/com/n1netails/n1netails/slack/validation/impl/GifBlockValidator.java b/src/main/java/com/n1netails/n1netails/slack/validation/impl/GifBlockValidator.java
new file mode 100644
index 0000000..18e9f5b
--- /dev/null
+++ b/src/main/java/com/n1netails/n1netails/slack/validation/impl/GifBlockValidator.java
@@ -0,0 +1,18 @@
+package com.n1netails.n1netails.slack.validation.impl;
+
+import com.n1netails.n1netails.slack.exception.SlackValidationException;
+import com.n1netails.n1netails.slack.model.blocks.GifBlock;
+import com.n1netails.n1netails.slack.validation.SlackValidator;
+
+public class GifBlockValidator implements SlackValidator {
+ @Override
+ public void validate(GifBlock target) throws SlackValidationException {
+ if (target.getGifUrl() == null || target.getGifUrl().isBlank()) {
+ throw new SlackValidationException("Gif URL cannot be empty");
+ }
+
+ if (target.getAltText() == null || target.getAltText().isBlank()) {
+ throw new SlackValidationException("altText is required for accessibility");
+ }
+ }
+}
diff --git a/src/main/java/com/n1netails/n1netails/slack/validation/impl/ImageBlockValidator.java b/src/main/java/com/n1netails/n1netails/slack/validation/impl/ImageBlockValidator.java
new file mode 100644
index 0000000..2dacdd3
--- /dev/null
+++ b/src/main/java/com/n1netails/n1netails/slack/validation/impl/ImageBlockValidator.java
@@ -0,0 +1,18 @@
+package com.n1netails.n1netails.slack.validation.impl;
+
+import com.n1netails.n1netails.slack.exception.SlackValidationException;
+import com.n1netails.n1netails.slack.model.blocks.ImageBlock;
+import com.n1netails.n1netails.slack.validation.SlackValidator;
+
+public class ImageBlockValidator implements SlackValidator {
+ @Override
+ public void validate(ImageBlock target) throws SlackValidationException {
+ if (target.getImageUrl() == null || target.getImageUrl().isBlank()) {
+ throw new SlackValidationException("Image URL cannot be empty");
+ }
+
+ if (target.getAltText() == null || target.getAltText().isBlank()) {
+ throw new SlackValidationException("altText is required for accessibility");
+ }
+ }
+}
diff --git a/src/main/java/com/n1netails/n1netails/slack/validation/impl/TextBlockValidator.java b/src/main/java/com/n1netails/n1netails/slack/validation/impl/TextBlockValidator.java
new file mode 100644
index 0000000..4c03936
--- /dev/null
+++ b/src/main/java/com/n1netails/n1netails/slack/validation/impl/TextBlockValidator.java
@@ -0,0 +1,15 @@
+package com.n1netails.n1netails.slack.validation.impl;
+
+import com.n1netails.n1netails.slack.exception.SlackValidationException;
+import com.n1netails.n1netails.slack.model.blocks.TextBlock;
+import com.n1netails.n1netails.slack.validation.SlackValidator;
+
+public class TextBlockValidator implements SlackValidator {
+ @Override
+ public void validate(TextBlock target) throws SlackValidationException {
+ if (target.getText() == null || target.getText().isBlank()) {
+ throw new SlackValidationException("Text cannot be empty");
+ }
+
+ }
+}
From 58d1df4fd5cd4325e9d193569e2da68aee1689fa Mon Sep 17 00:00:00 2001
From: ArturSlimac
Date: Tue, 17 Feb 2026 21:23:02 +0100
Subject: [PATCH 12/37] change to RuntimeException
---
.../n1netails/slack/exception/SlackClientException.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/main/java/com/n1netails/n1netails/slack/exception/SlackClientException.java b/src/main/java/com/n1netails/n1netails/slack/exception/SlackClientException.java
index 56a103b..3906188 100644
--- a/src/main/java/com/n1netails/n1netails/slack/exception/SlackClientException.java
+++ b/src/main/java/com/n1netails/n1netails/slack/exception/SlackClientException.java
@@ -4,7 +4,7 @@
* Slack Client Exception
* @author shahid foy
*/
-public class SlackClientException extends Exception {
+public class SlackClientException extends RuntimeException {
/**
* Slack Client Exception Constructor
From fdb99f33141d81871dfcb4c776362d36ef2509f0 Mon Sep 17 00:00:00 2001
From: ArturSlimac
Date: Tue, 17 Feb 2026 21:29:23 +0100
Subject: [PATCH 13/37] Correct Exception handling
---
.../com/n1netails/n1netails/slack/api/BotService.java | 9 ++++++---
.../slack/exception/SlackApiExceptionWrapper.java | 6 ------
.../slack/exception/SlackTransportException.java | 4 ++++
3 files changed, 10 insertions(+), 9 deletions(-)
diff --git a/src/main/java/com/n1netails/n1netails/slack/api/BotService.java b/src/main/java/com/n1netails/n1netails/slack/api/BotService.java
index 31b6d4e..693644b 100644
--- a/src/main/java/com/n1netails/n1netails/slack/api/BotService.java
+++ b/src/main/java/com/n1netails/n1netails/slack/api/BotService.java
@@ -2,6 +2,8 @@
import com.n1netails.n1netails.slack.exception.SlackApiExceptionWrapper;
import com.n1netails.n1netails.slack.exception.SlackClientException;
+import com.n1netails.n1netails.slack.exception.SlackTransportException;
+import com.n1netails.n1netails.slack.exception.SlackValidationException;
import com.n1netails.n1netails.slack.model.SlackBlock;
import com.n1netails.n1netails.slack.model.SlackMessage;
import com.slack.api.Slack;
@@ -40,6 +42,7 @@ public void send(SlackMessage slackMessage) throws SlackClientException {
ChatPostMessageRequest.builder()
.channel(slackMessage.getChannel())
.text(slackMessage.getText());
+
List finalBlocks;
if (slackMessage.getRawBlocks() != null && !slackMessage.getRawBlocks().isEmpty()) {
finalBlocks = slackMessage.getRawBlocks();
@@ -62,15 +65,15 @@ public void send(SlackMessage slackMessage) throws SlackClientException {
}
} catch (IOException e) {
- throw new SlackClientException("Network error while calling Slack API", e);
+ throw new SlackTransportException("Network error while calling Slack API", e);
} catch (SlackApiException e) {
- throw new SlackClientException("Slack SDK failure" + slackMessage.getChannel(), e);
+ throw new SlackTransportException("Slack SDK failure" + slackMessage.getChannel(), e);
}
}
private void validateSlackMessage(SlackMessage slackMessage) throws SlackClientException {
if (slackMessage == null) {
- throw new SlackClientException("slackMessage cannot be null");
+ throw new SlackValidationException("slackMessage cannot be null");
}
}
diff --git a/src/main/java/com/n1netails/n1netails/slack/exception/SlackApiExceptionWrapper.java b/src/main/java/com/n1netails/n1netails/slack/exception/SlackApiExceptionWrapper.java
index 249a358..84dffdc 100644
--- a/src/main/java/com/n1netails/n1netails/slack/exception/SlackApiExceptionWrapper.java
+++ b/src/main/java/com/n1netails/n1netails/slack/exception/SlackApiExceptionWrapper.java
@@ -1,14 +1,8 @@
package com.n1netails.n1netails.slack.exception;
public class SlackApiExceptionWrapper extends SlackClientException {
- private final String slackError;
-
public SlackApiExceptionWrapper(String slackError) {
super("Slack API error: " + slackError);
- this.slackError = slackError;
- }
- public String getSlackError() {
- return slackError;
}
}
diff --git a/src/main/java/com/n1netails/n1netails/slack/exception/SlackTransportException.java b/src/main/java/com/n1netails/n1netails/slack/exception/SlackTransportException.java
index 5baaae9..642f6e2 100644
--- a/src/main/java/com/n1netails/n1netails/slack/exception/SlackTransportException.java
+++ b/src/main/java/com/n1netails/n1netails/slack/exception/SlackTransportException.java
@@ -4,4 +4,8 @@ public class SlackTransportException extends SlackClientException {
public SlackTransportException(String message) {
super(message);
}
+
+ public SlackTransportException(String message, Throwable cause) {
+ super(message, cause);
+ }
}
From db85abc62a9e3b92a80bc89fa43c7856711538c8 Mon Sep 17 00:00:00 2001
From: ArturSlimac
Date: Tue, 17 Feb 2026 21:29:43 +0100
Subject: [PATCH 14/37] bug in text validation
---
.../com/n1netails/n1netails/slack/model/SlackMessage.java | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/main/java/com/n1netails/n1netails/slack/model/SlackMessage.java b/src/main/java/com/n1netails/n1netails/slack/model/SlackMessage.java
index 05c16b5..b183406 100644
--- a/src/main/java/com/n1netails/n1netails/slack/model/SlackMessage.java
+++ b/src/main/java/com/n1netails/n1netails/slack/model/SlackMessage.java
@@ -76,8 +76,8 @@ public SlackMessage build() throws SlackValidationException {
}
boolean hasContent = (text != null && !text.isBlank())
- || !blocks.isEmpty()
- || !rawBlocks.isEmpty();
+ && (!blocks.isEmpty()
+ || !rawBlocks.isEmpty());
if (!hasContent) {
throw new SlackValidationException(
From fde56f595837a58d593f08ff0cdeac5915723676 Mon Sep 17 00:00:00 2001
From: ArturSlimac
Date: Wed, 18 Feb 2026 13:35:40 +0100
Subject: [PATCH 15/37] refactoring BotService
---
.../n1netails/slack/api/BotService.java | 60 +++++++++++--------
1 file changed, 35 insertions(+), 25 deletions(-)
diff --git a/src/main/java/com/n1netails/n1netails/slack/api/BotService.java b/src/main/java/com/n1netails/n1netails/slack/api/BotService.java
index 693644b..7cad471 100644
--- a/src/main/java/com/n1netails/n1netails/slack/api/BotService.java
+++ b/src/main/java/com/n1netails/n1netails/slack/api/BotService.java
@@ -38,43 +38,53 @@ public BotService(String token) {
public void send(SlackMessage slackMessage) throws SlackClientException {
validateSlackMessage(slackMessage);
try {
- ChatPostMessageRequest.ChatPostMessageRequestBuilder requestBuilder =
- ChatPostMessageRequest.builder()
- .channel(slackMessage.getChannel())
- .text(slackMessage.getText());
+ ChatPostMessageRequest request = buildRequest(slackMessage);
+ ChatPostMessageResponse response = executeRequest(request);
+ handleSlackResponse(response);
+ } catch (IOException e) {
+ throw new SlackTransportException("Network error while calling Slack API", e);
+ } catch (SlackApiException e) {
+ throw new SlackTransportException("Slack SDK failure" + slackMessage.getChannel(), e);
+ }
+ }
- List finalBlocks;
- if (slackMessage.getRawBlocks() != null && !slackMessage.getRawBlocks().isEmpty()) {
- finalBlocks = slackMessage.getRawBlocks();
+ private void handleSlackResponse(ChatPostMessageResponse response) throws SlackApiExceptionWrapper {
+ if (!response.isOk()) {
+ throw new SlackApiExceptionWrapper(response.getError());
+ }
+ }
- } else if (slackMessage.getBlocks() != null && !slackMessage.getBlocks().isEmpty()) {
- finalBlocks = slackMessage.getBlocks().stream()
- .map(SlackBlock::toLayoutBlock)
- .toList();
- } else
- finalBlocks = null;
+ private ChatPostMessageResponse executeRequest(ChatPostMessageRequest request) throws SlackApiException, IOException {
+ return methods.chatPostMessage(request);
+ }
- if (finalBlocks != null && !finalBlocks.isEmpty()) {
- requestBuilder.blocks(finalBlocks);
- }
+ private ChatPostMessageRequest buildRequest(SlackMessage slackMessage) {
+ ChatPostMessageRequest.ChatPostMessageRequestBuilder requestBuilder =
+ ChatPostMessageRequest.builder()
+ .channel(slackMessage.getChannel())
+ .text(slackMessage.getText());
- ChatPostMessageResponse response = methods.chatPostMessage(requestBuilder.build());
+ List blocks = resolveBlocks(slackMessage);
+ if ((blocks != null && !blocks.isEmpty()))
+ requestBuilder.blocks(blocks);
+ return requestBuilder.build();
+ }
- if (!response.isOk()) {
- throw new SlackApiExceptionWrapper(response.getError());
- }
+ private List resolveBlocks(SlackMessage slackMessage) {
+ if (slackMessage.getRawBlocks() != null && !slackMessage.getRawBlocks().isEmpty())
+ return slackMessage.getRawBlocks();
- } catch (IOException e) {
- throw new SlackTransportException("Network error while calling Slack API", e);
- } catch (SlackApiException e) {
- throw new SlackTransportException("Slack SDK failure" + slackMessage.getChannel(), e);
+ if (slackMessage.getBlocks() != null && !slackMessage.getBlocks().isEmpty()) {
+ return slackMessage.getBlocks().stream()
+ .map(SlackBlock::toLayoutBlock)
+ .toList();
}
+ return null;
}
private void validateSlackMessage(SlackMessage slackMessage) throws SlackClientException {
if (slackMessage == null) {
throw new SlackValidationException("slackMessage cannot be null");
}
-
}
}
From 0a7b3ca973fbca14eb7ac9789c4e63b002b8c7f0 Mon Sep 17 00:00:00 2001
From: ArturSlimac
Date: Wed, 18 Feb 2026 13:43:52 +0100
Subject: [PATCH 16/37] Builder for SlackMessage refactoring
---
.../n1netails/slack/model/SlackMessage.java | 28 +++++++++++++------
1 file changed, 20 insertions(+), 8 deletions(-)
diff --git a/src/main/java/com/n1netails/n1netails/slack/model/SlackMessage.java b/src/main/java/com/n1netails/n1netails/slack/model/SlackMessage.java
index b183406..2192eb5 100644
--- a/src/main/java/com/n1netails/n1netails/slack/model/SlackMessage.java
+++ b/src/main/java/com/n1netails/n1netails/slack/model/SlackMessage.java
@@ -30,6 +30,13 @@ private SlackMessage(Builder builder) {
this.rawBlocks = List.copyOf(builder.rawBlocks);
}
+ private SlackMessage(String channel, String text, List blocks, List rawBlocks) {
+ this.channel = channel;
+ this.text = text;
+ this.blocks = blocks;
+ this.rawBlocks = rawBlocks;
+ }
+
public static Builder builder() {
return new Builder();
}
@@ -76,8 +83,9 @@ public SlackMessage build() throws SlackValidationException {
}
boolean hasContent = (text != null && !text.isBlank())
- && (!blocks.isEmpty()
- || !rawBlocks.isEmpty());
+ || !blocks.isEmpty()
+ || !rawBlocks.isEmpty();
+
if (!hasContent) {
throw new SlackValidationException(
@@ -90,8 +98,16 @@ public SlackMessage build() throws SlackValidationException {
"Cannot mix SlackBlock and rawBlocks in the same message"
);
}
- List processedBlocks = new ArrayList<>();
+ List processedBlocks = processBlocks(blocks);
+
+ this.blocks.clear();
+ this.blocks.addAll(processedBlocks);
+ return new SlackMessage(channel, text, processedBlocks, List.copyOf(rawBlocks));
+ }
+
+ private List processBlocks(List blocks) {
+ List processedBlocks = new ArrayList<>();
for (SlackBlock block : blocks) {
try {
SlackValidators.validate(block);
@@ -100,11 +116,7 @@ public SlackMessage build() throws SlackValidationException {
processedBlocks.add(SlackFallbackHandler.handle(block, e));
}
}
-
- this.blocks.clear();
- this.blocks.addAll(processedBlocks);
-
- return new SlackMessage(this);
+ return List.copyOf(processedBlocks);
}
}
From d52a4838f82fdf9f0c9e2b672501cf6a95e51543 Mon Sep 17 00:00:00 2001
From: ArturSlimac
Date: Wed, 18 Feb 2026 13:47:45 +0100
Subject: [PATCH 17/37] deleted double functionality
---
.../com/n1netails/n1netails/slack/model/SlackMessage.java | 7 ++-----
1 file changed, 2 insertions(+), 5 deletions(-)
diff --git a/src/main/java/com/n1netails/n1netails/slack/model/SlackMessage.java b/src/main/java/com/n1netails/n1netails/slack/model/SlackMessage.java
index 2192eb5..4d48418 100644
--- a/src/main/java/com/n1netails/n1netails/slack/model/SlackMessage.java
+++ b/src/main/java/com/n1netails/n1netails/slack/model/SlackMessage.java
@@ -100,10 +100,7 @@ public SlackMessage build() throws SlackValidationException {
}
List processedBlocks = processBlocks(blocks);
- this.blocks.clear();
- this.blocks.addAll(processedBlocks);
-
- return new SlackMessage(channel, text, processedBlocks, List.copyOf(rawBlocks));
+ return new SlackMessage(channel, text, List.copyOf(processedBlocks), List.copyOf(rawBlocks));
}
private List processBlocks(List blocks) {
@@ -116,7 +113,7 @@ private List processBlocks(List blocks) {
processedBlocks.add(SlackFallbackHandler.handle(block, e));
}
}
- return List.copyOf(processedBlocks);
+ return processedBlocks;
}
}
From d45dd7df3369e09ef24cd4d6477d962a2194546f Mon Sep 17 00:00:00 2001
From: ArturSlimac
Date: Sun, 22 Feb 2026 10:27:58 +0100
Subject: [PATCH 18/37] remove validation behaviour from builder
---
.../n1netails/slack/model/SlackMessage.java | 22 +------------------
1 file changed, 1 insertion(+), 21 deletions(-)
diff --git a/src/main/java/com/n1netails/n1netails/slack/model/SlackMessage.java b/src/main/java/com/n1netails/n1netails/slack/model/SlackMessage.java
index 4d48418..2b09543 100644
--- a/src/main/java/com/n1netails/n1netails/slack/model/SlackMessage.java
+++ b/src/main/java/com/n1netails/n1netails/slack/model/SlackMessage.java
@@ -30,12 +30,6 @@ private SlackMessage(Builder builder) {
this.rawBlocks = List.copyOf(builder.rawBlocks);
}
- private SlackMessage(String channel, String text, List blocks, List rawBlocks) {
- this.channel = channel;
- this.text = text;
- this.blocks = blocks;
- this.rawBlocks = rawBlocks;
- }
public static Builder builder() {
return new Builder();
@@ -98,22 +92,8 @@ public SlackMessage build() throws SlackValidationException {
"Cannot mix SlackBlock and rawBlocks in the same message"
);
}
- List processedBlocks = processBlocks(blocks);
-
- return new SlackMessage(channel, text, List.copyOf(processedBlocks), List.copyOf(rawBlocks));
- }
- private List processBlocks(List blocks) {
- List processedBlocks = new ArrayList<>();
- for (SlackBlock block : blocks) {
- try {
- SlackValidators.validate(block);
- processedBlocks.add(block);
- } catch (Exception e) {
- processedBlocks.add(SlackFallbackHandler.handle(block, e));
- }
- }
- return processedBlocks;
+ return new SlackMessage(this);
}
}
From c33962271c116ec084cb0769a552f369f3838209 Mon Sep 17 00:00:00 2001
From: ArturSlimac
Date: Sun, 22 Feb 2026 10:28:44 +0100
Subject: [PATCH 19/37] place validation behaviour into Service
---
.../n1netails/slack/api/BotService.java | 56 +++++++++++++++----
.../validation/impl/TextBlockValidator.java | 1 -
2 files changed, 44 insertions(+), 13 deletions(-)
diff --git a/src/main/java/com/n1netails/n1netails/slack/api/BotService.java b/src/main/java/com/n1netails/n1netails/slack/api/BotService.java
index 7cad471..71fddf0 100644
--- a/src/main/java/com/n1netails/n1netails/slack/api/BotService.java
+++ b/src/main/java/com/n1netails/n1netails/slack/api/BotService.java
@@ -4,8 +4,10 @@
import com.n1netails.n1netails.slack.exception.SlackClientException;
import com.n1netails.n1netails.slack.exception.SlackTransportException;
import com.n1netails.n1netails.slack.exception.SlackValidationException;
+import com.n1netails.n1netails.slack.fallback.SlackFallbackHandler;
import com.n1netails.n1netails.slack.model.SlackBlock;
import com.n1netails.n1netails.slack.model.SlackMessage;
+import com.n1netails.n1netails.slack.validation.SlackValidators;
import com.slack.api.Slack;
import com.slack.api.methods.MethodsClient;
import com.slack.api.methods.SlackApiException;
@@ -16,6 +18,7 @@
import lombok.Setter;
import java.io.IOException;
+import java.util.ArrayList;
import java.util.List;
/**
@@ -40,7 +43,9 @@ public void send(SlackMessage slackMessage) throws SlackClientException {
try {
ChatPostMessageRequest request = buildRequest(slackMessage);
ChatPostMessageResponse response = executeRequest(request);
- handleSlackResponse(response);
+ if (!response.isOk()) {
+ fallbackToText(slackMessage, response);
+ }
} catch (IOException e) {
throw new SlackTransportException("Network error while calling Slack API", e);
} catch (SlackApiException e) {
@@ -48,12 +53,27 @@ public void send(SlackMessage slackMessage) throws SlackClientException {
}
}
- private void handleSlackResponse(ChatPostMessageResponse response) throws SlackApiExceptionWrapper {
- if (!response.isOk()) {
- throw new SlackApiExceptionWrapper(response.getError());
+ private void fallbackToText(SlackMessage message, ChatPostMessageResponse originalResponse) {
+ try {
+ ChatPostMessageRequest fallbackRequest =
+ ChatPostMessageRequest.builder()
+ .channel(message.getChannel())
+ .text(message.getText())
+ .build();
+
+ ChatPostMessageResponse fallbackResponse =
+ executeRequest(fallbackRequest);
+
+ if (!fallbackResponse.isOk()) {
+ throw new SlackApiExceptionWrapper(originalResponse.getError());
+ }
+
+ } catch (Exception e) {
+ throw new SlackApiExceptionWrapper(originalResponse.getError());
}
}
+
private ChatPostMessageResponse executeRequest(ChatPostMessageRequest request) throws SlackApiException, IOException {
return methods.chatPostMessage(request);
}
@@ -70,16 +90,28 @@ private ChatPostMessageRequest buildRequest(SlackMessage slackMessage) {
return requestBuilder.build();
}
- private List resolveBlocks(SlackMessage slackMessage) {
- if (slackMessage.getRawBlocks() != null && !slackMessage.getRawBlocks().isEmpty())
- return slackMessage.getRawBlocks();
+ private List resolveBlocks(SlackMessage message) {
+ if (message.getRawBlocks() != null && !message.getRawBlocks().isEmpty()) {
+ return message.getRawBlocks();
+ }
- if (slackMessage.getBlocks() != null && !slackMessage.getBlocks().isEmpty()) {
- return slackMessage.getBlocks().stream()
- .map(SlackBlock::toLayoutBlock)
- .toList();
+ if (message.getBlocks() == null || message.getBlocks().isEmpty()) {
+ return null;
}
- return null;
+
+ List result = new ArrayList<>();
+
+ for (SlackBlock block : message.getBlocks()) {
+ try {
+ SlackValidators.validate(block);
+ result.add(block.toLayoutBlock());
+ } catch (Exception e) {
+ SlackBlock fallback = SlackFallbackHandler.handle(block, e);
+ result.add(fallback.toLayoutBlock());
+ }
+ }
+
+ return result;
}
private void validateSlackMessage(SlackMessage slackMessage) throws SlackClientException {
diff --git a/src/main/java/com/n1netails/n1netails/slack/validation/impl/TextBlockValidator.java b/src/main/java/com/n1netails/n1netails/slack/validation/impl/TextBlockValidator.java
index 4c03936..3161b5d 100644
--- a/src/main/java/com/n1netails/n1netails/slack/validation/impl/TextBlockValidator.java
+++ b/src/main/java/com/n1netails/n1netails/slack/validation/impl/TextBlockValidator.java
@@ -10,6 +10,5 @@ public void validate(TextBlock target) throws SlackValidationException {
if (target.getText() == null || target.getText().isBlank()) {
throw new SlackValidationException("Text cannot be empty");
}
-
}
}
From fbd74b0d82878f6beaa3625d8cdc7ad57fdce855 Mon Sep 17 00:00:00 2001
From: ArturSlimac
Date: Sun, 22 Feb 2026 14:52:51 +0100
Subject: [PATCH 20/37] restructure to keep it simple
---
.../n1netails/slack/api/BotService.java | 76 +++++--------------
.../slack/exception/SlackErrorMapper.java | 65 ++++++++++++++++
.../slack/fallback/SlackFallbackHandler.java | 26 -------
.../n1netails/slack/model/SlackMessage.java | 3 -
.../slack/processing/SlackBlockProcessor.java | 63 +++++++++++++++
...idators.java => BasicSlackValidators.java} | 25 +++---
6 files changed, 155 insertions(+), 103 deletions(-)
create mode 100644 src/main/java/com/n1netails/n1netails/slack/exception/SlackErrorMapper.java
delete mode 100644 src/main/java/com/n1netails/n1netails/slack/fallback/SlackFallbackHandler.java
create mode 100644 src/main/java/com/n1netails/n1netails/slack/processing/SlackBlockProcessor.java
rename src/main/java/com/n1netails/n1netails/slack/validation/{SlackValidators.java => BasicSlackValidators.java} (50%)
diff --git a/src/main/java/com/n1netails/n1netails/slack/api/BotService.java b/src/main/java/com/n1netails/n1netails/slack/api/BotService.java
index 71fddf0..00616ad 100644
--- a/src/main/java/com/n1netails/n1netails/slack/api/BotService.java
+++ b/src/main/java/com/n1netails/n1netails/slack/api/BotService.java
@@ -1,24 +1,19 @@
package com.n1netails.n1netails.slack.api;
-import com.n1netails.n1netails.slack.exception.SlackApiExceptionWrapper;
+import com.n1netails.n1netails.slack.exception.SlackErrorMapper;
import com.n1netails.n1netails.slack.exception.SlackClientException;
import com.n1netails.n1netails.slack.exception.SlackTransportException;
import com.n1netails.n1netails.slack.exception.SlackValidationException;
-import com.n1netails.n1netails.slack.fallback.SlackFallbackHandler;
-import com.n1netails.n1netails.slack.model.SlackBlock;
import com.n1netails.n1netails.slack.model.SlackMessage;
-import com.n1netails.n1netails.slack.validation.SlackValidators;
+import com.n1netails.n1netails.slack.processing.SlackBlockProcessor;
import com.slack.api.Slack;
import com.slack.api.methods.MethodsClient;
import com.slack.api.methods.SlackApiException;
import com.slack.api.methods.request.chat.ChatPostMessageRequest;
import com.slack.api.methods.response.chat.ChatPostMessageResponse;
import com.slack.api.model.block.LayoutBlock;
-import lombok.Getter;
-import lombok.Setter;
import java.io.IOException;
-import java.util.ArrayList;
import java.util.List;
/**
@@ -28,6 +23,7 @@
*/
class BotService {
private final MethodsClient methods;
+ private final SlackBlockProcessor blockProcessor;
/**
* Bot Service Constructor
@@ -36,87 +32,51 @@ class BotService {
*/
public BotService(String token) {
this.methods = Slack.getInstance().methods(token);
+ this.blockProcessor = new SlackBlockProcessor();
+
}
public void send(SlackMessage slackMessage) throws SlackClientException {
- validateSlackMessage(slackMessage);
+ validateBasic(slackMessage);
try {
ChatPostMessageRequest request = buildRequest(slackMessage);
- ChatPostMessageResponse response = executeRequest(request);
+ ChatPostMessageResponse response = methods.chatPostMessage(request);
if (!response.isOk()) {
- fallbackToText(slackMessage, response);
+ throw SlackErrorMapper.map(response);
}
} catch (IOException e) {
throw new SlackTransportException("Network error while calling Slack API", e);
} catch (SlackApiException e) {
- throw new SlackTransportException("Slack SDK failure" + slackMessage.getChannel(), e);
+ throw new SlackTransportException("Slack SDK failure for channel: " + slackMessage.getChannel(), e);
}
}
- private void fallbackToText(SlackMessage message, ChatPostMessageResponse originalResponse) {
- try {
- ChatPostMessageRequest fallbackRequest =
- ChatPostMessageRequest.builder()
- .channel(message.getChannel())
- .text(message.getText())
- .build();
- ChatPostMessageResponse fallbackResponse =
- executeRequest(fallbackRequest);
+ private void validateBasic(SlackMessage message) throws SlackValidationException {
+ if (message == null) {
+ throw new SlackValidationException("SlackMessage cannot be null");
+ }
- if (!fallbackResponse.isOk()) {
- throw new SlackApiExceptionWrapper(originalResponse.getError());
- }
+ if ((message.getText() == null || message.getText().isBlank()) &&
+ (message.getBlocks() == null || message.getBlocks().isEmpty()) &&
+ (message.getRawBlocks() == null || message.getRawBlocks().isEmpty())) {
- } catch (Exception e) {
- throw new SlackApiExceptionWrapper(originalResponse.getError());
+ throw new SlackValidationException("Message must contain text or blocks");
}
}
- private ChatPostMessageResponse executeRequest(ChatPostMessageRequest request) throws SlackApiException, IOException {
- return methods.chatPostMessage(request);
- }
-
private ChatPostMessageRequest buildRequest(SlackMessage slackMessage) {
ChatPostMessageRequest.ChatPostMessageRequestBuilder requestBuilder =
ChatPostMessageRequest.builder()
.channel(slackMessage.getChannel())
.text(slackMessage.getText());
- List blocks = resolveBlocks(slackMessage);
+ List blocks = blockProcessor.process(slackMessage);
if ((blocks != null && !blocks.isEmpty()))
requestBuilder.blocks(blocks);
return requestBuilder.build();
}
- private List resolveBlocks(SlackMessage message) {
- if (message.getRawBlocks() != null && !message.getRawBlocks().isEmpty()) {
- return message.getRawBlocks();
- }
-
- if (message.getBlocks() == null || message.getBlocks().isEmpty()) {
- return null;
- }
- List result = new ArrayList<>();
-
- for (SlackBlock block : message.getBlocks()) {
- try {
- SlackValidators.validate(block);
- result.add(block.toLayoutBlock());
- } catch (Exception e) {
- SlackBlock fallback = SlackFallbackHandler.handle(block, e);
- result.add(fallback.toLayoutBlock());
- }
- }
-
- return result;
- }
-
- private void validateSlackMessage(SlackMessage slackMessage) throws SlackClientException {
- if (slackMessage == null) {
- throw new SlackValidationException("slackMessage cannot be null");
- }
- }
}
diff --git a/src/main/java/com/n1netails/n1netails/slack/exception/SlackErrorMapper.java b/src/main/java/com/n1netails/n1netails/slack/exception/SlackErrorMapper.java
new file mode 100644
index 0000000..52f1de5
--- /dev/null
+++ b/src/main/java/com/n1netails/n1netails/slack/exception/SlackErrorMapper.java
@@ -0,0 +1,65 @@
+package com.n1netails.n1netails.slack.exception;
+
+import com.slack.api.methods.response.chat.ChatPostMessageResponse;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class SlackErrorMapper {
+
+ public static SlackValidationException map(ChatPostMessageResponse response) {
+ if (response.getErrors() == null || response.getErrors().isEmpty()) {
+ return new SlackValidationException(response.getError());
+ }
+
+ List formattedErrors = new ArrayList<>();
+
+ for (String error : response.getErrors()) {
+ formattedErrors.add(format(error));
+ }
+
+ return new SlackValidationException(
+ "Slack API validation failed:\n - " + String.join("\n - ", formattedErrors)
+ );
+ }
+
+ private static String format(String rawError) {
+ try {
+ String message = rawError;
+ int pointerStart = rawError.indexOf("[json-pointer:");
+
+ if (pointerStart > -1) {
+ message = rawError.substring(0, pointerStart).trim();
+
+ String pointer = rawError.substring(pointerStart)
+ .replace("[json-pointer:", "")
+ .replace("]", "");
+
+ return mapPointer(pointer, message);
+ }
+
+ return message;
+
+ } catch (Exception e) {
+ return rawError; // fallback
+ }
+ }
+
+ private static String mapPointer(String pointer, String message) {
+ String[] parts = pointer.split("/");
+
+ if (parts.length >= 4 && "blocks".equals(parts[1])) {
+ int index = Integer.parseInt(parts[2]);
+ String field = parts[3];
+
+ return String.format(
+ "Block[%d].%s: %s",
+ index,
+ field,
+ message
+ );
+ }
+
+ return pointer + ": " + message;
+ }
+}
diff --git a/src/main/java/com/n1netails/n1netails/slack/fallback/SlackFallbackHandler.java b/src/main/java/com/n1netails/n1netails/slack/fallback/SlackFallbackHandler.java
deleted file mode 100644
index da8d9bf..0000000
--- a/src/main/java/com/n1netails/n1netails/slack/fallback/SlackFallbackHandler.java
+++ /dev/null
@@ -1,26 +0,0 @@
-package com.n1netails.n1netails.slack.fallback;
-
-import com.n1netails.n1netails.slack.model.SlackBlock;
-import com.n1netails.n1netails.slack.model.blocks.FallbackBlock;
-import com.n1netails.n1netails.slack.model.blocks.GifBlock;
-import com.n1netails.n1netails.slack.model.blocks.ImageBlock;
-import com.n1netails.n1netails.slack.model.blocks.TextBlock;
-
-public class SlackFallbackHandler {
- public static SlackBlock handle(SlackBlock block, Exception e) {
-
- if (block instanceof ImageBlock img) {
- return new FallbackBlock("Image could not be displayed: " + img.getAltText());
- }
-
- if (block instanceof GifBlock gif) {
- return new FallbackBlock("GIF could not be displayed: " + gif.getAltText());
- }
-
- if (block instanceof TextBlock txt) {
- return new FallbackBlock("Text could not be displayed");
- }
-
- return new FallbackBlock("Unsupported content");
- }
-}
diff --git a/src/main/java/com/n1netails/n1netails/slack/model/SlackMessage.java b/src/main/java/com/n1netails/n1netails/slack/model/SlackMessage.java
index 2b09543..825e81c 100644
--- a/src/main/java/com/n1netails/n1netails/slack/model/SlackMessage.java
+++ b/src/main/java/com/n1netails/n1netails/slack/model/SlackMessage.java
@@ -1,11 +1,8 @@
package com.n1netails.n1netails.slack.model;
import com.n1netails.n1netails.slack.exception.SlackValidationException;
-import com.n1netails.n1netails.slack.fallback.SlackFallbackHandler;
-import com.n1netails.n1netails.slack.validation.SlackValidators;
import com.slack.api.model.block.LayoutBlock;
import lombok.Getter;
-import lombok.Setter;
import java.util.ArrayList;
import java.util.List;
diff --git a/src/main/java/com/n1netails/n1netails/slack/processing/SlackBlockProcessor.java b/src/main/java/com/n1netails/n1netails/slack/processing/SlackBlockProcessor.java
new file mode 100644
index 0000000..723f511
--- /dev/null
+++ b/src/main/java/com/n1netails/n1netails/slack/processing/SlackBlockProcessor.java
@@ -0,0 +1,63 @@
+package com.n1netails.n1netails.slack.processing;
+
+import com.n1netails.n1netails.slack.exception.SlackValidationException;
+import com.n1netails.n1netails.slack.model.SlackBlock;
+import com.n1netails.n1netails.slack.model.SlackMessage;
+import com.n1netails.n1netails.slack.validation.BasicSlackValidators;
+import com.slack.api.model.block.LayoutBlock;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class SlackBlockProcessor {
+
+ private final BasicSlackValidators basicSlackValidators;
+
+ public SlackBlockProcessor() {
+ this.basicSlackValidators = new BasicSlackValidators();
+ }
+
+ public List process(SlackMessage message) {
+ if (message.getRawBlocks() != null && !message.getRawBlocks().isEmpty()) {
+ return message.getRawBlocks();
+ }
+
+ if (message.getBlocks() == null || message.getBlocks().isEmpty()) {
+ return null;
+ }
+
+ List result = new ArrayList<>();
+ List errors = new ArrayList<>();
+
+ int index = 0;
+
+ for (SlackBlock block : message.getBlocks()) {
+ try {
+ basicSlackValidators.validate(block);
+ result.add(block.toLayoutBlock());
+ } catch (SlackValidationException e) {
+ errors.add(formatError(index, block, e));
+ }
+ index++;
+ }
+
+ if (!errors.isEmpty()) {
+ throw new SlackValidationException(buildErrorMessage(errors));
+ }
+
+ return result;
+ }
+
+ private String formatError(int index, SlackBlock block, Exception e) {
+ return String.format(
+ "Block[%d] (%s): %s",
+ index,
+ block.getClass().getSimpleName(),
+ e.getMessage()
+ );
+ }
+
+ private String buildErrorMessage(List errors) {
+ return "Slack message validation failed:\n - " + String.join("\n - ", errors);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/n1netails/n1netails/slack/validation/SlackValidators.java b/src/main/java/com/n1netails/n1netails/slack/validation/BasicSlackValidators.java
similarity index 50%
rename from src/main/java/com/n1netails/n1netails/slack/validation/SlackValidators.java
rename to src/main/java/com/n1netails/n1netails/slack/validation/BasicSlackValidators.java
index d52fe30..c0c2c81 100644
--- a/src/main/java/com/n1netails/n1netails/slack/validation/SlackValidators.java
+++ b/src/main/java/com/n1netails/n1netails/slack/validation/BasicSlackValidators.java
@@ -11,24 +11,17 @@
import java.util.HashMap;
import java.util.Map;
-public class SlackValidators {
- private static final Map, SlackValidator>> VALIDATORS = new HashMap<>();
+public class BasicSlackValidators {
+ private final Map, SlackValidator>> validators = new HashMap<>();
- static {
- VALIDATORS.put(ImageBlock.class, new ImageBlockValidator());
- VALIDATORS.put(GifBlock.class, new GifBlockValidator());
- VALIDATORS.put(TextBlock.class, new TextBlockValidator());
+ public BasicSlackValidators() {
+ validators.put(ImageBlock.class, new ImageBlockValidator());
+ validators.put(GifBlock.class, new GifBlockValidator());
+ validators.put(TextBlock.class, new TextBlockValidator());
}
-
- @SuppressWarnings("unchecked")
- public static void validate(T target) throws SlackValidationException {
+ public void validate(T target) {
if (target == null) return;
-
- SlackValidator validator =
- (SlackValidator) VALIDATORS.get(target.getClass());
-
- if (validator != null) {
- validator.validate(target);
- }
+ SlackValidator validator = (SlackValidator) validators.get(target.getClass());
+ if (validator != null) validator.validate(target);
}
}
From e8eea5ade97f369eec350e60898eca450dac41f8 Mon Sep 17 00:00:00 2001
From: ArturSlimac
Date: Mon, 23 Feb 2026 12:28:32 +0100
Subject: [PATCH 21/37] new block
---
.../slack/model/block/ActionsBlock.java | 30 +++++++++++++++++++
1 file changed, 30 insertions(+)
create mode 100644 src/main/java/com/n1netails/n1netails/slack/model/block/ActionsBlock.java
diff --git a/src/main/java/com/n1netails/n1netails/slack/model/block/ActionsBlock.java b/src/main/java/com/n1netails/n1netails/slack/model/block/ActionsBlock.java
new file mode 100644
index 0000000..d6778eb
--- /dev/null
+++ b/src/main/java/com/n1netails/n1netails/slack/model/block/ActionsBlock.java
@@ -0,0 +1,30 @@
+package com.n1netails.n1netails.slack.model.block;
+
+import com.n1netails.n1netails.slack.model.SlackBlock;
+import com.n1netails.n1netails.slack.model.SlackElement;
+import com.slack.api.model.block.LayoutBlock;
+import lombok.Getter;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Getter
+public class ActionsBlock implements SlackBlock {
+
+ private final List elements;
+
+ public ActionsBlock(List elements) {
+ this.elements = elements;
+ }
+
+ @Override
+ public LayoutBlock toLayoutBlock() {
+ return com.slack.api.model.block.ActionsBlock.builder()
+ .elements(
+ elements.stream()
+ .map(SlackElement::toBlockElement)
+ .toList()
+ )
+ .build();
+ }
+}
From 95c010001b850de2f52440b8c51d41cd557c77c5 Mon Sep 17 00:00:00 2001
From: ArturSlimac
Date: Mon, 23 Feb 2026 12:29:00 +0100
Subject: [PATCH 22/37] first element for the ActionsBlock
---
.../slack/model/actions_element/Button.java | 32 +++++++++++++++++++
1 file changed, 32 insertions(+)
create mode 100644 src/main/java/com/n1netails/n1netails/slack/model/actions_element/Button.java
diff --git a/src/main/java/com/n1netails/n1netails/slack/model/actions_element/Button.java b/src/main/java/com/n1netails/n1netails/slack/model/actions_element/Button.java
new file mode 100644
index 0000000..92fedf1
--- /dev/null
+++ b/src/main/java/com/n1netails/n1netails/slack/model/actions_element/Button.java
@@ -0,0 +1,32 @@
+package com.n1netails.n1netails.slack.model.actions_element;
+
+import com.n1netails.n1netails.slack.exception.SlackValidationException;
+import com.n1netails.n1netails.slack.model.SlackElement;
+import com.slack.api.model.block.composition.PlainTextObject;
+import com.slack.api.model.block.element.BlockElement;
+import com.slack.api.model.block.element.ButtonElement;
+import lombok.Getter;
+
+@Getter
+public class Button implements SlackElement {
+ private final String text;
+ private final String actionId;
+ private final String url;
+
+ public Button(String text, String actionId, String url) {
+ this.text = text;
+ this.actionId = actionId;
+ this.url = url;
+ }
+
+ @Override
+ public BlockElement toBlockElement() {
+ return com.slack.api.model.block.element.ButtonElement.builder()
+ .text(com.slack.api.model.block.composition.PlainTextObject.builder()
+ .text(text)
+ .build())
+ .actionId(actionId)
+ .url(url)
+ .build();
+ }
+}
From d91ef66179b3bd9293ca4b3d83957d22d80461a7 Mon Sep 17 00:00:00 2001
From: ArturSlimac
Date: Mon, 23 Feb 2026 12:29:41 +0100
Subject: [PATCH 23/37] validators
---
.../validation/BasicSlackValidators.java | 22 ++++++++++-----
.../impl/block/ActionsBlockValidator.java | 27 +++++++++++++++++++
.../impl/element/ButtonElementValidator.java | 21 +++++++++++++++
3 files changed, 63 insertions(+), 7 deletions(-)
create mode 100644 src/main/java/com/n1netails/n1netails/slack/validation/impl/block/ActionsBlockValidator.java
create mode 100644 src/main/java/com/n1netails/n1netails/slack/validation/impl/element/ButtonElementValidator.java
diff --git a/src/main/java/com/n1netails/n1netails/slack/validation/BasicSlackValidators.java b/src/main/java/com/n1netails/n1netails/slack/validation/BasicSlackValidators.java
index c0c2c81..b01d5da 100644
--- a/src/main/java/com/n1netails/n1netails/slack/validation/BasicSlackValidators.java
+++ b/src/main/java/com/n1netails/n1netails/slack/validation/BasicSlackValidators.java
@@ -1,12 +1,13 @@
package com.n1netails.n1netails.slack.validation;
-import com.n1netails.n1netails.slack.exception.SlackValidationException;
-import com.n1netails.n1netails.slack.model.blocks.GifBlock;
-import com.n1netails.n1netails.slack.model.blocks.ImageBlock;
-import com.n1netails.n1netails.slack.model.blocks.TextBlock;
-import com.n1netails.n1netails.slack.validation.impl.GifBlockValidator;
-import com.n1netails.n1netails.slack.validation.impl.ImageBlockValidator;
-import com.n1netails.n1netails.slack.validation.impl.TextBlockValidator;
+import com.n1netails.n1netails.slack.model.block.ActionsBlock;
+import com.n1netails.n1netails.slack.model.block.GifBlock;
+import com.n1netails.n1netails.slack.model.block.ImageBlock;
+import com.n1netails.n1netails.slack.model.block.TextBlock;
+import com.n1netails.n1netails.slack.validation.impl.block.ActionsBlockValidator;
+import com.n1netails.n1netails.slack.validation.impl.block.GifBlockValidator;
+import com.n1netails.n1netails.slack.validation.impl.block.ImageBlockValidator;
+import com.n1netails.n1netails.slack.validation.impl.block.TextBlockValidator;
import java.util.HashMap;
import java.util.Map;
@@ -14,11 +15,18 @@
public class BasicSlackValidators {
private final Map, SlackValidator>> validators = new HashMap<>();
+
public BasicSlackValidators() {
+ init();
+ }
+
+ private void init() {
validators.put(ImageBlock.class, new ImageBlockValidator());
validators.put(GifBlock.class, new GifBlockValidator());
validators.put(TextBlock.class, new TextBlockValidator());
+ validators.put(ActionsBlock.class, new ActionsBlockValidator(this));
}
+
public void validate(T target) {
if (target == null) return;
SlackValidator validator = (SlackValidator) validators.get(target.getClass());
diff --git a/src/main/java/com/n1netails/n1netails/slack/validation/impl/block/ActionsBlockValidator.java b/src/main/java/com/n1netails/n1netails/slack/validation/impl/block/ActionsBlockValidator.java
new file mode 100644
index 0000000..ed5a8d6
--- /dev/null
+++ b/src/main/java/com/n1netails/n1netails/slack/validation/impl/block/ActionsBlockValidator.java
@@ -0,0 +1,27 @@
+package com.n1netails.n1netails.slack.validation.impl.block;
+
+import com.n1netails.n1netails.slack.exception.SlackValidationException;
+import com.n1netails.n1netails.slack.model.SlackElement;
+import com.n1netails.n1netails.slack.model.block.ActionsBlock;
+import com.n1netails.n1netails.slack.validation.BasicSlackValidators;
+import com.n1netails.n1netails.slack.validation.SlackValidator;
+
+public class ActionsBlockValidator implements SlackValidator {
+ private final BasicSlackValidators validators;
+
+ public ActionsBlockValidator(BasicSlackValidators validators) {
+ this.validators = validators;
+ }
+
+ @Override
+ public void validate(ActionsBlock target) throws SlackValidationException {
+ if (target.getElements() == null || target.getElements().isEmpty()) {
+ throw new SlackValidationException("ActionsBlock must contain at least one element");
+ }
+
+
+ for (SlackElement element : target.getElements()) {
+ validators.validate(element);
+ }
+ }
+}
diff --git a/src/main/java/com/n1netails/n1netails/slack/validation/impl/element/ButtonElementValidator.java b/src/main/java/com/n1netails/n1netails/slack/validation/impl/element/ButtonElementValidator.java
new file mode 100644
index 0000000..c641d5e
--- /dev/null
+++ b/src/main/java/com/n1netails/n1netails/slack/validation/impl/element/ButtonElementValidator.java
@@ -0,0 +1,21 @@
+package com.n1netails.n1netails.slack.validation.impl.element;
+
+import com.n1netails.n1netails.slack.exception.SlackValidationException;
+import com.n1netails.n1netails.slack.model.actions_element.Button;
+import com.n1netails.n1netails.slack.validation.SlackValidator;
+
+public class ButtonElementValidator implements SlackValidator
+ *
+ * @param message the Slack message to convert; must not be null
+ * @return list of {@link LayoutBlock}s or {@code null} if none are available
+ */
public List build(SlackMessage message) {
if (message.getRawBlocks() != null && !message.getRawBlocks().isEmpty()) {
diff --git a/src/main/java/com/n1netails/n1netails/slack/exception/SlackErrorMapper.java b/src/main/java/com/n1netails/n1netails/slack/exception/SlackErrorMapper.java
index 62aa629..48170d4 100644
--- a/src/main/java/com/n1netails/n1netails/slack/exception/SlackErrorMapper.java
+++ b/src/main/java/com/n1netails/n1netails/slack/exception/SlackErrorMapper.java
@@ -5,8 +5,34 @@
import java.util.ArrayList;
import java.util.List;
+/**
+ * Utility class for mapping Slack API {@link ChatPostMessageResponse} errors
+ * into human-readable {@link SlackValidationException} messages.
+ *
+ * This class parses raw Slack API error messages, including JSON pointers,
+ * and formats them for easier debugging and display in your application.
+ *
Extends {@link SlackNode}, allowing blocks to participate in SlackNode hierarchies for compositional validation.
+ *
+ *
Implementations should be immutable wherever possible.
+ *
+ * @author Artur Slimak
+ */
public interface SlackBlock extends SlackNode {
+ /**
+ * Converts this Slack block into a Slack API {@link LayoutBlock}.
+ *
+ * @return the Slack API representation of this block
+ */
LayoutBlock toLayoutBlock();
}
diff --git a/src/main/java/com/n1netails/n1netails/slack/model/SlackElement.java b/src/main/java/com/n1netails/n1netails/slack/model/SlackElement.java
index 919ef41..636c36c 100644
--- a/src/main/java/com/n1netails/n1netails/slack/model/SlackElement.java
+++ b/src/main/java/com/n1netails/n1netails/slack/model/SlackElement.java
@@ -1,7 +1,32 @@
package com.n1netails.n1netails.slack.model;
+import com.n1netails.n1netails.slack.model.actions_element.ButtonElement;
import com.slack.api.model.block.element.BlockElement;
+/**
+ * Represents a Slack interactive element that can be converted into a Slack API {@link BlockElement}.
+ *
+ * Examples of Slack elements include buttons, select menus, and other interactive UI components.
+ * All concrete elements (e.g., {@link ButtonElement}) should implement this interface.
+ *
Extends {@link SlackNode}, allowing elements to participate in SlackNode hierarchies for compositional validation.
+ *
+ *
Implementations should be immutable wherever possible.
+ *
+ * @author Artur Slimak
+ */
public interface SlackElement extends SlackNode {
+ /**
+ * Converts this Slack element into a Slack API {@link BlockElement}.
+ *
+ * @return the Slack API representation of this element
+ */
BlockElement toBlockElement();
}
diff --git a/src/main/java/com/n1netails/n1netails/slack/model/SlackMessage.java b/src/main/java/com/n1netails/n1netails/slack/model/SlackMessage.java
index 825e81c..c751556 100644
--- a/src/main/java/com/n1netails/n1netails/slack/model/SlackMessage.java
+++ b/src/main/java/com/n1netails/n1netails/slack/model/SlackMessage.java
@@ -8,9 +8,26 @@
import java.util.List;
/**
- * Slack Message
+ * Represents a Slack message that can be sent to a channel.
+ *
+ * A Slack message can contain plain text, structured {@link SlackBlock}s, or raw Slack API blocks
+ * (Block Kit Documentation).
+ * Messages are immutable once built and should be constructed via the {@link Builder}.
+ *
Cannot mix {@link SlackBlock} and raw Block Kit blocks in the same message.
+ *
+ *
All instances are immutable and thread-safe after creation.
+ *
+ * @author Shahid Foy and Artur Slimak
*/
@Getter
public class SlackMessage {
@@ -28,26 +45,63 @@ private SlackMessage(Builder builder) {
}
+ /**
+ * Returns a new {@link Builder} for constructing a {@link SlackMessage}.
+ *
+ * @return a new builder instance
+ */
public static Builder builder() {
return new Builder();
}
+ /**
+ * Builder class for creating {@link SlackMessage} instances.
+ *
+ * Allows setting the target channel, text, and adding blocks (either {@link SlackBlock} or raw {@link LayoutBlock}).
+ * Enforces validation rules to prevent invalid combinations.
+ *
+ */
public static class Builder {
private String channel;
private String text;
private final List blocks = new ArrayList<>();
private final List rawBlocks = new ArrayList<>();
+ /**
+ * Sets the target Slack channel for this message.
+ *
+ * @param channel the Slack channel name or ID
+ * @return this builder
+ */
public Builder channel(String channel) {
this.channel = channel;
return this;
}
+
+ /**
+ * Sets the plain text content of this Slack message.
+ *
+ * If one or more blocks are present in the message, this text serves as a fallback
+ * for notifications or clients that cannot render blocks.
+ *
+ *
+ * @param text the message text
+ * @return this builder
+ */
public Builder text(String text) {
this.text = text;
return this;
}
+
+ /**
+ * Adds a structured {@link SlackBlock} to this message.
+ *
+ * @param block the Slack block to add
+ * @return this builder
+ * @throws SlackValidationException if rawBlocks are already present
+ */
public Builder addBlock(SlackBlock block) throws SlackValidationException {
if (!rawBlocks.isEmpty()) {
throw new SlackValidationException(
@@ -58,6 +112,13 @@ public Builder addBlock(SlackBlock block) throws SlackValidationException {
return this;
}
+ /**
+ * Adds a raw {@link LayoutBlock} to this message.
+ *
+ * @param block the raw Slack API layout block (Block Kit Documentation)
+ * @return this builder
+ * @throws SlackValidationException if SlackBlocks are already present
+ */
public Builder addRawBlock(LayoutBlock block) throws SlackValidationException {
if (!blocks.isEmpty()) {
throw new SlackValidationException(
@@ -68,6 +129,12 @@ public Builder addRawBlock(LayoutBlock block) throws SlackValidationException {
return this;
}
+ /**
+ * Builds an immutable {@link SlackMessage} instance after performing validation.
+ *
+ * @return a new {@link SlackMessage}
+ * @throws SlackValidationException if channel is missing, content is missing, or block types are mixed
+ */
public SlackMessage build() throws SlackValidationException {
if (channel == null || channel.isBlank()) {
throw new SlackValidationException("channel is required");
diff --git a/src/main/java/com/n1netails/n1netails/slack/model/SlackNode.java b/src/main/java/com/n1netails/n1netails/slack/model/SlackNode.java
index 0752ac0..2079948 100644
--- a/src/main/java/com/n1netails/n1netails/slack/model/SlackNode.java
+++ b/src/main/java/com/n1netails/n1netails/slack/model/SlackNode.java
@@ -1,7 +1,38 @@
package com.n1netails.n1netails.slack.model;
+import com.n1netails.n1netails.slack.model.actions_element.ButtonElement;
+import com.n1netails.n1netails.slack.model.block.GifBlock;
+import com.n1netails.n1netails.slack.model.block.ImageBlock;
+import com.n1netails.n1netails.slack.model.block.TextBlock;
+
import java.util.List;
+/**
+ * Represents a node in a Slack message composition hierarchy.
+ *
+ * All Slack blocks and elements implement this interface to participate
+ * in a tree-like structure for compositional validation and traversal.
+ *
+ *
+ * Example usage:
+ *
{@code
+ * SlackNode block = TextBlock.of("Hello Slack!");
+ * List children = block.getChildren(); // returns an empty list
+ * }
+ *
+ *
Implementations should be immutable wherever possible.
+ *
+ * @author Artur SLimak
+ */
public interface SlackNode {
+ /**
+ * Returns the children of this node in the Slack composition tree.
+ *
+ * For leaf nodes (e.g., {@link TextBlock}, {@link GifBlock}, {@link ImageBlock}, {@link ButtonElement}),
+ * this method returns an empty list.
+ *
+ *
+ * @return a list of child {@link SlackNode} instances
+ */
List getChildren();
}
diff --git a/src/main/java/com/n1netails/n1netails/slack/model/actions_element/ButtonElement.java b/src/main/java/com/n1netails/n1netails/slack/model/actions_element/ButtonElement.java
index 0d8e56c..abda41f 100644
--- a/src/main/java/com/n1netails/n1netails/slack/model/actions_element/ButtonElement.java
+++ b/src/main/java/com/n1netails/n1netails/slack/model/actions_element/ButtonElement.java
@@ -7,6 +7,24 @@
import java.util.List;
+/**
+ * Represents a Slack button element.
+ *
+ * A button can either trigger an action (actionId) or open a link (url).
+ * Use the static factory methods {@link #link(String, String)} or {@link #action(String, String)}
+ * or the {@link Builder} for custom construction.
+ *
+ * An Actions Block contains a set of interactive elements, such as {@link ButtonElement}.
+ * Use {@link #of(List)} or {@link #builder()} to create instances.
+ *
Converts to Slack API block via {@link #toLayoutBlock()}.
+ *
+ *
Children elements can be retrieved using {@link #getChildren()}.
+ *
+ * @author Artur Slimak
+ */
@Getter
public class ActionsBlock implements SlackBlock {
diff --git a/src/main/java/com/n1netails/n1netails/slack/model/block/GifBlock.java b/src/main/java/com/n1netails/n1netails/slack/model/block/GifBlock.java
index ab57d0a..88718b9 100644
--- a/src/main/java/com/n1netails/n1netails/slack/model/block/GifBlock.java
+++ b/src/main/java/com/n1netails/n1netails/slack/model/block/GifBlock.java
@@ -8,6 +8,30 @@
import java.util.List;
+/**
+ * Represents a Slack Image Block containing a GIF.
+ *
+ * This block displays a GIF in a Slack message. Use {@link #of(String, String)} or the {@link Builder}
+ * to create instances. The GIF URL must be publicly accessible, and altText provides a description
+ * for accessibility and fallback display.
+ *
Converts to a Slack API {@link LayoutBlock} via {@link #toLayoutBlock()}.
+ *
+ *
This block does not contain child nodes, so {@link #getChildren()} returns an empty list.
+ *
+ *
All instances are immutable once created.
+ *
+ * @author Artur Slimak
+ */
@Getter
public class GifBlock implements SlackBlock {
private final String gifUrl;
diff --git a/src/main/java/com/n1netails/n1netails/slack/model/block/ImageBlock.java b/src/main/java/com/n1netails/n1netails/slack/model/block/ImageBlock.java
index 67d4719..606770e 100644
--- a/src/main/java/com/n1netails/n1netails/slack/model/block/ImageBlock.java
+++ b/src/main/java/com/n1netails/n1netails/slack/model/block/ImageBlock.java
@@ -8,7 +8,29 @@
import java.util.List;
-
+/**
+ * Represents a Slack Image Block.
+ *
+ * An Image Block displays an image in a Slack message with an alternative text for accessibility.
+ * Use {@link #of(String, String)} or the {@link Builder} to create instances.
+ *
Converts to a Slack API {@link LayoutBlock} via {@link #toLayoutBlock()}.
+ *
+ *
This block does not contain child nodes, so {@link #getChildren()} returns an empty list.
+ *
+ *
All instances are immutable once created.
+ *
+ * @author Artur Slimak
+ */
@Getter
public class ImageBlock implements SlackBlock {
private final String imageUrl;
diff --git a/src/main/java/com/n1netails/n1netails/slack/model/block/TextBlock.java b/src/main/java/com/n1netails/n1netails/slack/model/block/TextBlock.java
index 5af9784..1a29f33 100644
--- a/src/main/java/com/n1netails/n1netails/slack/model/block/TextBlock.java
+++ b/src/main/java/com/n1netails/n1netails/slack/model/block/TextBlock.java
@@ -9,6 +9,26 @@
import java.util.List;
+/**
+ * Represents a Slack Text Block (Section Block) containing plain text.
+ *
+ * Use {@link #of(String)} or the {@link Builder} to create instances.
+ * This block displays text in a Slack message and does not support child nodes.
+ *
Converts to a Slack API {@link LayoutBlock} via {@link #toLayoutBlock()}.
+ *
+ *
All instances are immutable once created.
+ *
+ *
{@link #getChildren()} always returns an empty list.
+ *
+ * @author Artur Slimak
+ */
@Getter
public class TextBlock implements SlackBlock {
private final String text;
diff --git a/src/main/java/com/n1netails/n1netails/slack/validation/BasicSlackValidators.java b/src/main/java/com/n1netails/n1netails/slack/validation/BasicSlackValidators.java
index ce84f1b..13516c7 100644
--- a/src/main/java/com/n1netails/n1netails/slack/validation/BasicSlackValidators.java
+++ b/src/main/java/com/n1netails/n1netails/slack/validation/BasicSlackValidators.java
@@ -1,5 +1,6 @@
package com.n1netails.n1netails.slack.validation;
+import com.n1netails.n1netails.slack.exception.SlackValidationException;
import com.n1netails.n1netails.slack.model.actions_element.ButtonElement;
import com.n1netails.n1netails.slack.model.block.ActionsBlock;
import com.n1netails.n1netails.slack.model.block.GifBlock;
@@ -14,6 +15,24 @@
import java.util.HashMap;
import java.util.Map;
+/**
+ * Registry of basic Slack validators for common blocks and elements.
+ *
+ * Provides automatic validation of:
+ *
+ *
{@link ImageBlock}
+ *
{@link GifBlock}
+ *
{@link TextBlock}
+ *
{@link ActionsBlock}
+ *
...
+ *
+ * Can be extended with custom validators for additional Slack nodes.
+ *
+ *
+ *
Validation is skipped if the target is {@code null} or no validator exists for its type.
+ *
+ * @author Artur Slimak
+ */
public class BasicSlackValidators {
private final Map, SlackValidator>> validators = new HashMap<>();
@@ -30,6 +49,17 @@ private void init() {
validators.put(ButtonElement.class, new ButtonElementValidator());
}
+ /**
+ * Validates the given target using the registered validator for its type.
+ *
+ * If no validator exists for the target type, or the target is {@code null}, validation is skipped.
+ *
+ *
+ * @param the type of object to validate
+ * @param target the object to validate
+ * @throws SlackValidationException if validation fails
+ */
+ @SuppressWarnings("unchecked")
public void validate(T target) {
if (target == null) return;
SlackValidator validator = (SlackValidator) validators.get(target.getClass());
diff --git a/src/main/java/com/n1netails/n1netails/slack/validation/SlackBlockValidator.java b/src/main/java/com/n1netails/n1netails/slack/validation/SlackBlockValidator.java
index bf33d41..b01cc3b 100644
--- a/src/main/java/com/n1netails/n1netails/slack/validation/SlackBlockValidator.java
+++ b/src/main/java/com/n1netails/n1netails/slack/validation/SlackBlockValidator.java
@@ -8,6 +8,36 @@
import java.util.ArrayList;
import java.util.List;
+/**
+ * Validates the blocks of a {@link SlackMessage}.
+ *
+ * This validator iterates over all {@link SlackBlock} instances in a message and validates
+ * each block and its children recursively using {@link BasicSlackValidators}.
+ *
+ *
+ *
Validation rules:
+ *
+ *
If {@link SlackMessage#getRawBlocks()} is non-empty, validation is skipped.
+ *
If {@link SlackMessage#getBlocks()} is empty or null, validation is skipped.
+ *
All blocks and child elements are validated using their respective {@link SlackValidator} implementations.
Throws {@link SlackValidationException} if any block or child node fails validation,
+ * with detailed error messages including the block path in the message.
+ *
+ * @author Artur Slimak
+ */
public class SlackBlockValidator {
private final BasicSlackValidators basicSlackValidators;
@@ -16,6 +46,15 @@ public SlackBlockValidator() {
this.basicSlackValidators = new BasicSlackValidators();
}
+ /**
+ * Validates all {@link SlackBlock} instances in the given Slack message.
+ *
+ * Validation is recursive: all child nodes of blocks are also validated.
+ *
+ *
+ * @param message the Slack message to validate
+ * @throws SlackValidationException if any block or child node fails validation
+ */
public void validateMessageBlocks(SlackMessage message) {
if (message.getRawBlocks() != null && !message.getRawBlocks().isEmpty()) {
return;
@@ -39,6 +78,13 @@ public void validateMessageBlocks(SlackMessage message) {
}
}
+ /**
+ * Recursively validates a {@link SlackNode} and its children, collecting any validation errors.
+ *
+ * @param node the node to validate
+ * @param path the path of the node in the message (for error reporting)
+ * @param errors the list to collect validation error messages
+ */
private void validateNode(SlackNode node, String path, List errors) {
try {
basicSlackValidators.validate(node);
diff --git a/src/main/java/com/n1netails/n1netails/slack/validation/SlackValidator.java b/src/main/java/com/n1netails/n1netails/slack/validation/SlackValidator.java
index d5b9091..c204a6b 100644
--- a/src/main/java/com/n1netails/n1netails/slack/validation/SlackValidator.java
+++ b/src/main/java/com/n1netails/n1netails/slack/validation/SlackValidator.java
@@ -2,6 +2,27 @@
import com.n1netails.n1netails.slack.exception.SlackValidationException;
+/**
+ * Generic interface for validating Slack nodes or elements.
+ *
+ * Implementations provide type-specific validation logic for Slack blocks or elements.
+ *
Both {@code actionId} and {@code url} are missing or blank
+ *
+ *
+ * @author Artur Slimak
+ */
public class ButtonElementValidator implements SlackValidator {
@Override
public void validate(ButtonElement target) throws SlackValidationException {
From e5b59267388cbe1e7252ffad4cf1e45aacfc73bd Mon Sep 17 00:00:00 2001
From: ArturSlimac
Date: Mon, 23 Feb 2026 22:14:15 +0100
Subject: [PATCH 33/37] update readme
---
readme.md | 73 +++++++++++++++++++++++++++++++++++++------------------
1 file changed, 50 insertions(+), 23 deletions(-)
diff --git a/readme.md b/readme.md
index ecfbde9..87db057 100644
--- a/readme.md
+++ b/readme.md
@@ -52,34 +52,55 @@ Here's how to use the client to send a message, pics or gifs:
```java
import com.n1netails.n1netails.slack.api.SlackClient;
import com.n1netails.n1netails.slack.model.SlackMessage;
-import com.n1netails.n1netails.slack.model.block.GifBlock;
+import com.n1netails.n1netails.slack.model.actions_element.ButtonElement;
import com.n1netails.n1netails.slack.model.block.TextBlock;
+import com.n1netails.n1netails.slack.model.block.ImageBlock;
+import com.n1netails.n1netails.slack.model.block.GifBlock;
+import com.n1netails.n1netails.slack.model.block.ActionsBlock;
+import com.n1netails.n1netails.slack.model.element.ButtonElement;
+import com.n1netails.n1netails.slack.exception.SlackClientException;
public class Example {
public static void main(String[] args) {
// Your bot token
String token = "xoxb-your-bot-token";
- // The channel you want to send the message to (e.g., "#general")
- String channel = "#prototype"; // or "#channel-name"
+ // Channel to send the message to
+ String channel = "#prototype";
- // Use builder to create a client
- SlackClient client = SlackClient.builder().token(token).build();
+ // Build the Slack client
+ SlackClient slackClient = SlackClient.builder()
+ .token(token)
+ .build();
- // Create the message
+ // Build an ActionsBlock
+ ActionsBlock actionsBlock = ActionsBlock.builder()
+ .addElement(ButtonElement.link("Visit Website", "https://example.com"))
+ .addElement(ButtonElement.builder().text("Click me").actionId("YOUR ACTION ID").build()) //Elements support 2 types of creation
+ .build();
+
+ // Build the Slack message
SlackMessage message = SlackMessage.builder()
- .channel("test")
- .text("New content 🚀") //Remember if use ANY blocks this text becomes a fallback message for notifications
- .addBlock(new TextBlock("New content 🚀"))
- .addBlock(new ImageBlock("YOUR IMG LINK", "YOUR ALT TEXT"))
- .addBlock(new GifBlock("YOUR GIF LINK", "YOUR ALT TEXT"))
+ .channel(channel)
+ .text("New content 🚀") // fallback message for notifications
+ .addBlock(TextBlock.builder().text("New content 🚀").build())
+ .addBlock(ImageBlock.builder()
+ .imageUrl("YOUR IMG LINK")
+ .altText("YOUR ALT TEXT")
+ .build())
+ .addBlock(ImageBlock.of("YOUR IMG LINK", "YOUR ALT TEXT")) //Blocks support 2 types of creation
+ .addBlock(GifBlock.builder()
+ .gifUrl("YOUR GIF LINK")
+ .altText("YOUR ALT TEXT")
+ .build())
+ .addBlock(actionsBlock) // add the actions block with buttons
.build();
try {
// Send the message
slackClient.sendMessage(message);
System.out.println("Message sent successfully!");
- } catch (Exception e) {
+ } catch (SlackClientException e) {
System.err.println("Error sending message: " + e.getMessage());
}
}
@@ -91,32 +112,38 @@ You can also send more complex messages using [Slack's Block Kit](https://api.sl
```java
import com.n1netails.n1netails.slack.api.SlackClient;
-import com.n1netails.n1netails.slack.api.SlackClientImpl;
import com.n1netails.n1netails.slack.model.SlackMessage;
-import com.n1netails.n1netails.slack.api.BotService;
import com.slack.api.model.block.Blocks;
import com.slack.api.model.block.composition.BlockCompositions;
+import com.n1netails.n1netails.slack.exception.SlackClientException;
-import java.util.Arrays;
+import java.util.List;
public class AdvancedExample {
public static void main(String[] args) {
String token = "xoxb-your-bot-token";
String channel = "#prototype";
- SlackClient client = SlackClient.builder().token(token).build();
+ // Build the Slack client
+ SlackClient slackClient = SlackClient.builder()
+ .token(token)
+ .build();
- SlackMessage message = new SlackMessage();
- message.setChannel(channel);
- message.setText("This is a fallback message for notifications.");
- message.setBlocks(Arrays.asList(
- Blocks.section(section -> section.text(BlockCompositions.markdownText("*This is a message with blocks.*")))
- ));
+ // Build a Slack message with custom Block Kit blocks
+ SlackMessage message = SlackMessage.builder()
+ .channel(channel)
+ .text("This is a fallback message for notifications.") // fallback
+ .addRawBlock(
+ Blocks.section(section ->
+ section.text(BlockCompositions.markdownText("*This is a message with custom blocks.*"))
+ )
+ )
+ .build();
try {
slackClient.sendMessage(message);
System.out.println("Advanced message sent successfully!");
- } catch (Exception e) {
+ } catch (SlackClientException e) {
System.err.println("Error sending message: " + e.getMessage());
}
}
From d9b1cb7ad9f092b361537b77ac66a2ea4fbba89b Mon Sep 17 00:00:00 2001
From: ArturSlimac
Date: Tue, 24 Feb 2026 07:49:19 +0100
Subject: [PATCH 34/37] version update
---
pom.xml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pom.xml b/pom.xml
index 25663ed..88cca8f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -4,7 +4,7 @@
com.n1netailsn1netails-slack-client
- 0.2.0
+ 0.3.0jarn1netails-slack-client
From 7adf549c6c8e00acec7673df6f0415f7c073d590 Mon Sep 17 00:00:00 2001
From: ArturSlimac
Date: Fri, 27 Feb 2026 21:25:13 +0100
Subject: [PATCH 35/37] readme updated with contributors
---
readme.md | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/readme.md b/readme.md
index 87db057..f89f47f 100644
--- a/readme.md
+++ b/readme.md
@@ -184,4 +184,10 @@ For community users, open an issue on GitHub or Join our Discord
## Contributing
-Please use the following guidelines for contributions [CONTRIBUTING](./contributing.md)
\ No newline at end of file
+Please use the following guidelines for contributions [CONTRIBUTING](./contributing.md)
+
+## N1netails Slack Client Contributors
+
+Thanks to all the amazing people who contributed to N1netails Slack Client! 💙
+
+[](https://github.com/n1netails/n1netails-slack-client/graphs/contributors)
\ No newline at end of file
From 742c409b9a907a60427c7fa72f7a87fa4dca678d Mon Sep 17 00:00:00 2001
From: shahidfoy
Date: Sat, 28 Feb 2026 11:32:39 -0700
Subject: [PATCH 36/37] updating readme fixing missing builders for gif image
and text
---
pom.xml | 2 +-
readme.md | 15 ++++++++-------
.../n1netails/slack/model/block/GifBlock.java | 4 ++++
.../n1netails/slack/model/block/ImageBlock.java | 7 +++++--
.../n1netails/slack/model/block/TextBlock.java | 5 ++++-
5 files changed, 22 insertions(+), 11 deletions(-)
diff --git a/pom.xml b/pom.xml
index 88cca8f..275e25e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -65,7 +65,7 @@
com.slack.apislack-api-client
- 1.45.4
+ 1.47.0
diff --git a/readme.md b/readme.md
index f89f47f..9d750dd 100644
--- a/readme.md
+++ b/readme.md
@@ -37,13 +37,13 @@ Install the slack client by adding the following dependency:
com.n1netailsn1netails-slack-client
- 0.2.0
+ 0.3.0
```
Gradle (Groovy)
```groovy
-implementation 'com.n1netails:n1netails-slack-client:0.2.0'
+implementation 'com.n1netails:n1netails-slack-client:0.3.0'
```
## Usage
@@ -79,19 +79,20 @@ public class Example {
.addElement(ButtonElement.builder().text("Click me").actionId("YOUR ACTION ID").build()) //Elements support 2 types of creation
.build();
+ // Build the Slack message
// Build the Slack message
SlackMessage message = SlackMessage.builder()
.channel(channel)
.text("New content 🚀") // fallback message for notifications
.addBlock(TextBlock.builder().text("New content 🚀").build())
.addBlock(ImageBlock.builder()
- .imageUrl("YOUR IMG LINK")
- .altText("YOUR ALT TEXT")
+ .imageUrl("https://n1netails.com/img/n1netails_icon_transparent.png")
+ .altText("N1netails token")
.build())
- .addBlock(ImageBlock.of("YOUR IMG LINK", "YOUR ALT TEXT")) //Blocks support 2 types of creation
+ .addBlock(ImageBlock.of("https://n1netails.com/img/quickstart/n1netails-letter.jpg", "N1netails letter")) //Blocks support 2 types of creation
.addBlock(GifBlock.builder()
- .gifUrl("YOUR GIF LINK")
- .altText("YOUR ALT TEXT")
+ .gifUrl("https://media1.giphy.com/media/v1.Y2lkPTc5MGI3NjExaDRhOWtpMnVsM2NiMzJ4aXpoOXpuamZzcHpudG4zbzIzenVlaHN0eSZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/xsE65jaPsUKUo/giphy.gif")
+ .altText("Fox GIF")
.build())
.addBlock(actionsBlock) // add the actions block with buttons
.build();
diff --git a/src/main/java/com/n1netails/n1netails/slack/model/block/GifBlock.java b/src/main/java/com/n1netails/n1netails/slack/model/block/GifBlock.java
index 88718b9..b322ef5 100644
--- a/src/main/java/com/n1netails/n1netails/slack/model/block/GifBlock.java
+++ b/src/main/java/com/n1netails/n1netails/slack/model/block/GifBlock.java
@@ -57,6 +57,10 @@ public LayoutBlock toLayoutBlock() {
;
}
+ public static Builder builder() {
+ return new Builder();
+ }
+
@Override
public List getChildren() {
return List.of();
diff --git a/src/main/java/com/n1netails/n1netails/slack/model/block/ImageBlock.java b/src/main/java/com/n1netails/n1netails/slack/model/block/ImageBlock.java
index 606770e..31afb49 100644
--- a/src/main/java/com/n1netails/n1netails/slack/model/block/ImageBlock.java
+++ b/src/main/java/com/n1netails/n1netails/slack/model/block/ImageBlock.java
@@ -1,6 +1,5 @@
package com.n1netails.n1netails.slack.model.block;
-import com.n1netails.n1netails.slack.exception.SlackValidationException;
import com.n1netails.n1netails.slack.model.SlackBlock;
import com.n1netails.n1netails.slack.model.SlackNode;
import com.slack.api.model.block.LayoutBlock;
@@ -41,7 +40,7 @@ private ImageBlock(String imageUrl, String altText) {
this.altText = altText;
}
- public ImageBlock of(String imageUrl, String altText) {
+ public static ImageBlock of(String imageUrl, String altText) {
return new ImageBlock(imageUrl, altText);
}
@@ -56,6 +55,10 @@ public LayoutBlock toLayoutBlock() {
;
}
+ public static Builder builder() {
+ return new Builder();
+ }
+
@Override
public List getChildren() {
return List.of();
diff --git a/src/main/java/com/n1netails/n1netails/slack/model/block/TextBlock.java b/src/main/java/com/n1netails/n1netails/slack/model/block/TextBlock.java
index 1a29f33..4aca9eb 100644
--- a/src/main/java/com/n1netails/n1netails/slack/model/block/TextBlock.java
+++ b/src/main/java/com/n1netails/n1netails/slack/model/block/TextBlock.java
@@ -1,6 +1,5 @@
package com.n1netails.n1netails.slack.model.block;
-import com.n1netails.n1netails.slack.exception.SlackValidationException;
import com.n1netails.n1netails.slack.model.SlackBlock;
import com.n1netails.n1netails.slack.model.SlackNode;
import com.slack.api.model.block.LayoutBlock;
@@ -48,6 +47,10 @@ public LayoutBlock toLayoutBlock() {
.build();
}
+ public static Builder builder() {
+ return new Builder();
+ }
+
@Override
public List getChildren() {
return List.of();
From 611f2d211663fb9a19cb8c4f7131e1daf273c28b Mon Sep 17 00:00:00 2001
From: ArturSlimac
Date: Sun, 1 Mar 2026 19:07:07 +0100
Subject: [PATCH 37/37] fixing some small building errors
---
pom.xml | 15 +++++++++++----
.../slack/api/builder/SlackBlockBuilder.java | 2 +-
.../slack/validation/BasicSlackValidators.java | 4 +++-
.../slack/validation/SlackBlockValidator.java | 4 ++--
4 files changed, 17 insertions(+), 8 deletions(-)
diff --git a/pom.xml b/pom.xml
index 275e25e..9863220 100644
--- a/pom.xml
+++ b/pom.xml
@@ -21,9 +21,16 @@
org.apache.maven.pluginsmaven-compiler-plugin
+ 3.15.0
- 17
- 17
+ 17
+
+
+ org.projectlombok
+ lombok
+ 1.18.42
+
+
@@ -50,8 +57,8 @@
UTF-8
- 1.8
- 1.8
+ 17
+ 17
diff --git a/src/main/java/com/n1netails/n1netails/slack/api/builder/SlackBlockBuilder.java b/src/main/java/com/n1netails/n1netails/slack/api/builder/SlackBlockBuilder.java
index 6308bc3..f5bc6f0 100644
--- a/src/main/java/com/n1netails/n1netails/slack/api/builder/SlackBlockBuilder.java
+++ b/src/main/java/com/n1netails/n1netails/slack/api/builder/SlackBlockBuilder.java
@@ -27,12 +27,12 @@ public class SlackBlockBuilder {
* Builds a list of {@link LayoutBlock}s from a {@link SlackMessage}.
*
* Priority order:
+ *
*
*
If {@code rawBlocks} are present in the message, return them as-is.
*
If {@code blocks} are present, convert each {@link SlackBlock} to {@link LayoutBlock}.
*
If neither is present, returns {@code null}.
*
- *
*
* @param message the Slack message to convert; must not be null
* @return list of {@link LayoutBlock}s or {@code null} if none are available
diff --git a/src/main/java/com/n1netails/n1netails/slack/validation/BasicSlackValidators.java b/src/main/java/com/n1netails/n1netails/slack/validation/BasicSlackValidators.java
index 13516c7..8dfa740 100644
--- a/src/main/java/com/n1netails/n1netails/slack/validation/BasicSlackValidators.java
+++ b/src/main/java/com/n1netails/n1netails/slack/validation/BasicSlackValidators.java
@@ -19,6 +19,7 @@
* Registry of basic Slack validators for common blocks and elements.
*
* Provides automatic validation of:
+ *
*
*
{@link ImageBlock}
*
{@link GifBlock}
@@ -26,6 +27,7 @@
*
{@link ActionsBlock}
*
...
*
+ *
* Can be extended with custom validators for additional Slack nodes.
*
*
@@ -55,7 +57,7 @@ private void init() {
* If no validator exists for the target type, or the target is {@code null}, validation is skipped.
*
*
- * @param the type of object to validate
+ * @param the type of object to validate
* @param target the object to validate
* @throws SlackValidationException if validation fails
*/
diff --git a/src/main/java/com/n1netails/n1netails/slack/validation/SlackBlockValidator.java b/src/main/java/com/n1netails/n1netails/slack/validation/SlackBlockValidator.java
index b01cc3b..38dfc10 100644
--- a/src/main/java/com/n1netails/n1netails/slack/validation/SlackBlockValidator.java
+++ b/src/main/java/com/n1netails/n1netails/slack/validation/SlackBlockValidator.java
@@ -17,8 +17,8 @@
*
*
Validation rules:
*
- *
If {@link SlackMessage#getRawBlocks()} is non-empty, validation is skipped.
- *
If {@link SlackMessage#getBlocks()} is empty or null, validation is skipped.
+ *
If {@link SlackMessage#rawBlocks} is non-empty, validation is skipped.
+ *
If {@link SlackMessage#blocks} is empty or null, validation is skipped.
*
All blocks and child elements are validated using their respective {@link SlackValidator} implementations.