Skip to content

Latest commit

 

History

History
378 lines (287 loc) · 8.44 KB

File metadata and controls

378 lines (287 loc) · 8.44 KB

SlackKit

SlackKit Logo

CI Version License Platform Swift Dependencies

Swift package for sending messages to Slack via Incoming Webhooks with full support for Block Kit.

Also check out MattermostKit - A companion package for sending messages to Mattermost with Slack-compatible attachments.

Features

  • Modern Swift API - Built with Swift 6, async/await, and strict concurrency
  • Type-Safe - Full Codable support with compile-time safety
  • Block Kit - Complete support for Slack's Block Kit API
  • Flexible - Send simple text messages or rich interactive messages

Requirements

  • macOS 12.0+
  • iOS 15.0+
  • tvOS 15.0+
  • watchOS 8.0+
  • Swift 6.0+

Installation

Swift Package Manager

Add SlackKit to your Package.swift file:

dependencies: [
    .package(url: "https://github.com/diegotl/SlackKit.git", from: "1.0.0")
]

Or add it directly in Xcode:

  1. File → Add Package Dependencies
  2. Enter the repository URL
  3. Select the version rule

Quick Start

import SlackKit

// Create a webhook client
let client = try SlackWebhookClient.create(
    webhookURLString: "https://hooks.slack.com/services/YOUR/WEBHOOK/URL"
)

// Send a simple message
try await client.send(Message(text: "Hello, Slack!"))

Convenience Builder API

SlackKit includes a builder API for cleaner, more readable message construction:

// Clean, declarative syntax
let message = Message {
    Header("Deployment Complete!")
    Section("Build *#123* was deployed to *production*")
    Divider()
    SectionBlock(
        fields: [
            .markdown("*Environment:*\nProduction"),
            .markdown("*Version:*\nv2.4.1")
        ]
    )
}
try await client.send(message)

Usage

Simple Text Message

let message = Message(text: "Deployment completed successfully!")
try await client.send(message)

Message with Blocks

let message = Message {
    Header("Deployment Complete!")
    Section(markdown: "Build *#123* was deployed to *production*")
    Divider()

    Section {
        Field.markdown("*Environment:*\nProduction")
        Field.markdown("*Version:*\nv2.4.1")
        Field.markdown("*Duration:*\n5m 32s")
        Field.markdown("*Status:*\n:white_check_mark: Success")
    }
}
try await client.send(message)

With custom username and icon:

let message = Message(
    username: "DeployBot",
    iconEmoji: ":rocket:"
) {
    Header("Deployment Complete!")
    Section("Build *#123* was deployed to *production*")
    Divider()
}
try await client.send(message)

Message with Actions

let message = Message(text: "Approval required for production deployment") {
    Section("Deploy to production?")
    Actions {
        ButtonElement(text: .plainText("Approve"), style: .primary, value: "approve")
        ButtonElement(text: .plainText("Reject"), style: .danger, value: "reject")
    }
}
try await client.send(message)

Message with Attachments (Legacy)

let message = Message(
    text: "Build results",
    attachments: [
        Attachment(
            color: "good",
            title: "Build #123",
            text: "Succeeded in 5m 32s",
            fields: [
                AttachmentField(title: "Branch", value: "main", short: true),
                AttachmentField(title: "Commit", value: "abc123", short: true)
            ]
        )
    ]
)
try await client.send(message)

Threaded Message

let message = Message(
    text: "This is a threaded reply",
    threadTimestamp: "1234567890.123456"
)
try await client.send(message)

Block Types

Section Block

Text sections with optional fields:

Section("Some *formatted* text")
// Or with markdown
Section(markdown: "Some *formatted* text")

With fields using the result builder:

Section {
    Field.markdown("*Field 1*\nValue 1")
    Field.plainText("Field 2")
}

Header Block

Large header text:

Header("Important Announcement")

Divider Block

Horizontal line divider:

Divider()

Image Block

Display an image:

Image(url: "https://example.com/image.png", altText: "An example image")

Actions Block

Interactive buttons:

Actions {
    ButtonElement(text: .plainText("Click Me"), actionID: "button_1", value: "button_value", style: .primary)
}

The builder also supports conditionals and loops:

Actions {
    ButtonElement(text: .plainText("Approve"), actionID: "approve", value: "yes")

    if needsReview {
        ButtonElement(text: .plainText("Request Review"), actionID: "review", value: "review")
    }

    for option in options {
        ButtonElement(text: .plainText(option), actionID: "opt_\(option)", value: option)
    }
}

Context Block

Contextual information with text and images:

// Simple text context
Context("Created by @john", "2 minutes ago")

// Or with elements using the builder
Context {
    TextContextElement(text: "Created by @john")
    ImageContextElement(imageURL: "https://example.com/avatar.png", altText: "Avatar")
}

Input Block (Modals)

Input blocks for collecting user input in modals:

Input(
    label: "Task description",
    element: PlainTextInputElement(placeholder: "Enter task details...", multiline: true)
)

Interactive Elements

Button

ButtonElement(
    text: .plainText("Click Me"),
    actionID: "button_1",
    value: "button_value",
    style: .primary
)

Select Menu

StaticSelectElement(placeholder: .plainText("Choose an option")) {
    Option(text: .plainText("Option 1"), value: "opt1")
    Option(text: .plainText("Option 2"), value: "opt2")
}

Multi-Select Menu

MultiStaticSelectElement(placeholder: .plainText("Select options"), maxSelectedItems: 3) {
    Option(text: .plainText("Option 1"), value: "opt1")
    Option(text: .plainText("Option 2"), value: "opt2")
}

Date Picker

DatePickerElement(
    actionID: "date_picker_1",
    placeholder: .plainText("Select a date")
)

Users Select

UsersSelectElement(
    placeholder: .plainText("Select a user"),
    initialUser: "U1234567890"
)

Conversations Select

ConversationsSelectElement(
    placeholder: .plainText("Select a conversation"),
    filter: ConversationFilter(
        include: [.public, .private],
        excludeBotUsers: true
    )
)

Text Objects

Plain Text

TextObject.plainText("Simple text", emoji: true)

Markdown

TextObject.markdown("*Bold* and `code`")

Error Handling

do {
    try await client.send(message)
} catch SlackError.invalidURL(let url) {
    print("Invalid URL: \(url)")
} catch SlackError.invalidResponse(let code, let body) {
    print("HTTP \(code): \(body ?? "No body")")
} catch SlackError.rateLimitExceeded(let retryAfter) {
    print("Rate limited. Retry after \(retryAfter) seconds")
} catch SlackError.encodingError(let error) {
    print("Failed to encode message: \(error)")
} catch SlackError.networkError(let error) {
    print("Network error: \(error)")
}

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

License

This project is licensed under the MIT License - see the LICENSE file for details.

Acknowledgments

Resources