Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
6766122
Redesign the usage of SlackClient with Builder
ArturSlimak Feb 17, 2026
63937a5
Update readme.md
ArturSlimak Feb 17, 2026
6d80c6f
Encapsulation: only SlackClient is available for creation
ArturSlimak Feb 17, 2026
d5ed27b
Prevents recreating client every call
ArturSlimak Feb 17, 2026
53db1c8
Error handling
ArturSlimak Feb 17, 2026
1c4eed4
Support pics and gifs
ArturSlimak Feb 17, 2026
51c6ba2
Support text
ArturSlimak Feb 17, 2026
a271562
prevent usage of blocks and rawBlocks together
ArturSlimak Feb 17, 2026
f545153
update readme.md
ArturSlimak Feb 17, 2026
330f127
Exceptions handling
ArturSlimak Feb 17, 2026
4bcc3d1
Graceful fallback for unsupported or invalid media
ArturSlimak Feb 17, 2026
58d1df4
change to RuntimeException
ArturSlimak Feb 17, 2026
fdb99f3
Correct Exception handling
ArturSlimak Feb 17, 2026
db85abc
bug in text validation
ArturSlimak Feb 17, 2026
fde56f5
refactoring BotService
ArturSlimak Feb 18, 2026
0a7b3ca
Builder for SlackMessage refactoring
ArturSlimak Feb 18, 2026
d52a483
deleted double functionality
ArturSlimak Feb 18, 2026
d45dd7d
remove validation behaviour from builder
ArturSlimak Feb 22, 2026
c339622
place validation behaviour into Service
ArturSlimak Feb 22, 2026
fbd74b0
restructure to keep it simple
ArturSlimak Feb 22, 2026
e8eea5a
new block
ArturSlimak Feb 23, 2026
95c0100
first element for the ActionsBlock
ArturSlimak Feb 23, 2026
d91ef66
validators
ArturSlimak Feb 23, 2026
db345bb
package renamed
ArturSlimak Feb 23, 2026
57407ce
Expose static factory method in ActionsBlock + builder option
ArturSlimak Feb 23, 2026
e0edfc8
Expose static factory method in the blocks
ArturSlimak Feb 23, 2026
e1d52cb
Button class rename + builder/static factory
ArturSlimak Feb 23, 2026
7236cef
internal validation on Elements is on
ArturSlimak Feb 23, 2026
428fef1
builder option for other blocks
ArturSlimak Feb 23, 2026
06a613d
separate block validation and block building
ArturSlimak Feb 23, 2026
e54adfc
a bit of encapsulation
ArturSlimak Feb 23, 2026
58baa10
javadocs
ArturSlimak Feb 23, 2026
e5b5926
update readme
ArturSlimak Feb 23, 2026
d9b1cb7
version update
ArturSlimak Feb 24, 2026
7adf549
readme updated with contributors
ArturSlimak Feb 27, 2026
742c409
updating readme fixing missing builders for gif image and text
shahidfoy Feb 28, 2026
611f2d2
fixing some small building errors
ArturSlimak Mar 1, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 24 additions & 5 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

<groupId>com.n1netails</groupId>
<artifactId>n1netails-slack-client</artifactId>
<version>0.2.0</version>
<version>0.3.0</version>
<packaging>jar</packaging>

<name>n1netails-slack-client</name>
Expand All @@ -16,8 +16,27 @@
<connection>scm:git:git://github.com/n1netails/n1netails-slack-client.git</connection>
<developerConnection>scm:git:ssh://git@github.com:n1netails/n1netails-slack-client.git</developerConnection>
</scm>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.15.0</version>
<configuration>
<release>17</release>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.42</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>

<licenses>
<licenses>
<license>
<name>MIT License</name>
<url>https://opensource.org/licenses/MIT</url>
Expand All @@ -38,8 +57,8 @@

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
</properties>

<dependencies>
Expand All @@ -53,7 +72,7 @@
<dependency>
<groupId>com.slack.api</groupId>
<artifactId>slack-api-client</artifactId>
<version>1.45.4</version>
<version>1.47.0</version>
</dependency>
</dependencies>

Expand Down
98 changes: 66 additions & 32 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,48 +37,71 @@ Install the slack client by adding the following dependency:
<dependency>
<groupId>com.n1netails</groupId>
<artifactId>n1netails-slack-client</artifactId>
<version>0.2.0</version>
<version>0.3.0</version>
</dependency>
```

Gradle (Groovy)
```groovy
implementation 'com.n1netails:n1netails-slack-client:0.2.0'
implementation 'com.n1netails:n1netails-slack-client:0.3.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.internal.SlackClientImpl;
import com.n1netails.n1netails.slack.model.SlackMessage;
import com.n1netails.n1netails.slack.service.BotService;
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"

// Create the bot service
BotService botService = new BotService(token);

// Create the Slack client
SlackClient slackClient = new SlackClientImpl(botService);
// Channel to send the message to
String channel = "#prototype";

// Create the message
SlackMessage message = new SlackMessage();
message.setChannel(channel);
message.setText("Hello from the N1ne Tails Slack Client!");
// Build the Slack client
SlackClient slackClient = SlackClient.builder()
.token(token)
.build();

// 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
// 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("https://n1netails.com/img/n1netails_icon_transparent.png")
.altText("N1netails token")
.build())
.addBlock(ImageBlock.of("https://n1netails.com/img/quickstart/n1netails-letter.jpg", "N1netails letter")) //Blocks support 2 types of creation
.addBlock(GifBlock.builder()
.gifUrl("https://media1.giphy.com/media/v1.Y2lkPTc5MGI3NjExaDRhOWtpMnVsM2NiMzJ4aXpoOXpuamZzcHpudG4zbzIzenVlaHN0eSZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/xsE65jaPsUKUo/giphy.gif")
.altText("Fox GIF")
.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());
}
}
Expand All @@ -90,33 +113,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.internal.SlackClientImpl;
import com.n1netails.n1netails.slack.model.SlackMessage;
import com.n1netails.n1netails.slack.service.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";

BotService botService = new BotService(token);
SlackClient slackClient = new SlackClientImpl(botService);

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 the Slack client
SlackClient slackClient = SlackClient.builder()
.token(token)
.build();

// 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());
}
}
Expand Down Expand Up @@ -157,4 +185,10 @@ For community users, open an issue on GitHub or Join our Discord

## Contributing

Please use the following guidelines for contributions [CONTRIBUTING](./contributing.md)
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! 💙

[![Contributors](https://contrib.rocks/image?repo=n1netails/n1netails-slack-client)](https://github.com/n1netails/n1netails-slack-client/graphs/contributors)
120 changes: 120 additions & 0 deletions src/main/java/com/n1netails/n1netails/slack/api/BotService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package com.n1netails.n1netails.slack.api;

import com.n1netails.n1netails.slack.api.builder.SlackBlockBuilder;
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.model.SlackMessage;
import com.n1netails.n1netails.slack.validation.SlackBlockValidator;
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 java.io.IOException;
import java.util.List;

/**
* Service responsible for sending messages to Slack via the Slack SDK.
* <p>
* Encapsulates the Slack {@link MethodsClient}, performs message validation,
* and builds message blocks using {@link SlackBlockBuilder}.
* Handles transport and validation exceptions internally.
* </p>
* <p>
* Example usage:
* <pre>{@code
* BotService botService = new BotService("xoxb-your-token");
* SlackMessage message = new SlackMessage("general", "Hello, Slack!");
* botService.send(message);
* }</pre>
*
* <p>All messages are validated before sending. Text or blocks must be present
* in the {@link SlackMessage}, otherwise a {@link SlackValidationException} is thrown.</p>
*
* <p>Exceptions thrown:</p>
* <ul>
* <li>{@link SlackTransportException} – for network/SDK errors</li>
* </ul>
*
* @author Shahid Foy and Artur Slimak
*/
final public class BotService {
private final MethodsClient methods;
private final SlackBlockValidator blockValidator;
private final SlackBlockBuilder blockBuilder;

/**
* Constructs a new {@link BotService} with the given Slack bot token.
*
* @param token Slack bot token for authentication; must not be null or blank
*/
public BotService(String token) {
this.methods = Slack.getInstance().methods(token);
this.blockValidator = new SlackBlockValidator();
this.blockBuilder = new SlackBlockBuilder();

}


/**
* Sends a {@link SlackMessage} to Slack.
* <p>
* The message is validated for basic requirements (text or blocks present),
* and all blocks are validated via {@link SlackBlockValidator}.
* </p>
*
* @param slackMessage the message to send; must not be null
* @throws SlackClientException if validation fails or sending fails due to network/Slack API errors
*/
public void send(SlackMessage slackMessage) throws SlackClientException {
validateBasic(slackMessage);

blockValidator.validateMessageBlocks(slackMessage);

try {
List<LayoutBlock> blocks = blockBuilder.build(slackMessage);

ChatPostMessageRequest request =
ChatPostMessageRequest.builder()
.channel(slackMessage.getChannel())
.text(slackMessage.getText())
.blocks(blocks)
.build();

ChatPostMessageResponse response = methods.chatPostMessage(request);
if (!response.isOk()) {
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 for channel: " + slackMessage.getChannel(), e);
}
}

/**
* Performs basic validation on a {@link SlackMessage}.
* <p>
* Ensures that the message is not null and contains either text, blocks, or raw blocks.
* </p>
*
* @param message the Slack message to validate
* @throws SlackValidationException if the message is null or contains no text/blocks
*/
private void validateBasic(SlackMessage message) throws SlackValidationException {
if (message == null) {
throw new SlackValidationException("SlackMessage cannot be null");
}

if ((message.getText() == null || message.getText().isBlank()) &&
(message.getBlocks() == null || message.getBlocks().isEmpty()) &&
(message.getRawBlocks() == null || message.getRawBlocks().isEmpty())) {

throw new SlackValidationException("Message must contain text or blocks");
}
}
}
Loading