SwiftDisc

0.10.0

SwiftDisc is a lightweight, native Swift library designed for building powerful Discord bots on iOS and macOS
M1tsumi/SwiftDisc

What's New

SwiftDisc v0.10.0

2025-11-14T19:58:42Z

[v0.10.0] - 2025-11-14

This release introduces experimental voice support, zero-dependency encryption, and developer-friendly utilities โ€” alongside scaffolding for Discordโ€™s latest features like Components V2 and Polls. Caelum continues to evolve as a Swift-native Discord toolkit for bots, apps, and automation.

๐ŸŒŸ Highlights

  • Developer Discord Utils: Mentions, emoji helpers, timestamps, markdown escaping.
  • Experimental Voice (macOS/iOS): Connect flow, UDP IP discovery, protocol select, Session Description, speaking.
  • Zero-dependency encryption: Pure-Swift Secretbox (XSalsa20-Poly1305) and RTP sender.
  • Music-bot friendly: Opus-in pipeline with VoiceAudioSource and PipeOpusSource for stdin piping on macOS.
  • Components V2 & Polls scaffolding: Generic JSON and typed envelopes to use latest Discord features now.

๐Ÿ†• Added

Internal

  • Internal/DiscordUtils.swift: Mentions, EmojiUtils, DiscordTimestamp, MessageFormat.
  • Internal/JSONValue.swift: Lightweight JSONValue for flexible payload composition (Components V2, Polls, future APIs).

Voice

  • DiscordConfiguration.enableVoiceExperimental feature flag.
  • Voice Gateway handshake and UDP IP discovery (Network.framework).
  • VoiceGateway, VoiceClient, VoiceSender (RTP), Secretbox (XSalsa20-Poly1305), AudioSource protocol, PipeOpusSource.
  • Public API on DiscordClient: joinVoice, leaveVoice, playVoiceOpus, play(source:).

Examples

  • Examples/VoiceStdin.swift: Stream framed Opus from stdin to a voice channel.

Models

  • Models/AdvancedMessagePayloads.swift: V2MessagePayload, PollPayload typed envelopes.

REST

  • Components V2:
    • postMessage(channelId:payload:) (generic)
    • sendComponentsV2Message(channelId:payload:) (typed envelope)
  • Polls:
    • createPollMessage(channelId:content:poll:...) (generic)
    • createPollMessage(channelId:payload:...) (typed envelope)
  • Localization:
    • setCommandLocalizations(applicationId:commandId:nameLocalizations:descriptionLocalizations:)
  • Forwarding:
    • forwardMessageByReference(targetChannelId:sourceChannelId:messageId:)
  • Application-scoped resources:
    • post/patch/deleteApplicationResource(...) (generic)
  • App Emoji wrappers:
    • createAppEmoji(...), updateAppEmoji(...), deleteAppEmoji(...)
  • UserApps wrappers:
    • createUserAppResource(...), updateUserAppResource(...), deleteUserAppResource(...)

โœ๏ธ Changed

  • README:
    • Expanded Voice section (usage, requirements, macOS ffmpeg piping, iOS guidance)
    • Added Components V2 (generic + typed envelope) examples
    • Added Polls (generic + typed envelope) examples
    • Added Localization and Forwarding usage
    • Added Generic Application Resources, App Emoji, and UserApps usage
  • advaithpr.ts: Updated feature matrix (Components V2, Polls, Localization, Forwarding, App Emoji, UserApps โ†’ โœ…)

๐Ÿ“ Notes

  • Voice is send-only; input must be Opus packets (48kHz, ~20ms).
  • No external dependencies were added.
  • iOS cannot spawn ffmpeg; provide Opus from your app/backend.

SwiftDisc Typing

SwiftDisc

A modern, Swift-native Discord API library for building powerful bots

Build Discord bots and integrations with the elegance of Swift โ€” fully async, strongly typed, and production-ready.

Join our Discord

Discord Swift Version License CI GitHub Stars

Documentation Quick Start Examples

Why SwiftDisc?

SwiftDisc brings the power of modern Swift to Discord bot development. Whether you're building a simple utility bot or a complex multi-server application, SwiftDisc provides the tools you need with an API that feels natural to Swift developers.

โœจ What Makes SwiftDisc Special

  • ๐ŸŽฏ Swift-First Design โ€” Built from the ground up for Swift, leveraging async/await, actors, and structured concurrency
  • ๐Ÿ”’ Type Safety โ€” Comprehensive type-safe models that catch errors at compile time
  • ๐ŸŒ Truly Cross-Platform โ€” Deploy on iOS, macOS, tvOS, watchOS, and Windows with the same codebase
  • โšก Production Ready โ€” Automatic rate limiting, connection resilience, and sharding support out of the box
  • ๐ŸŽจ Developer Friendly โ€” Intuitive APIs inspired by discord.py, adapted for Swift's strengths

๐ŸŽฏ Perfect For

  • First-time bot developers looking for a modern, well-documented library
  • Swift developers wanting to leverage their existing skills
  • Cross-platform projects requiring deployment flexibility
  • Production applications demanding reliability and performance

๐Ÿš€ Quick Start

Get your first bot running in minutes:

import SwiftDisc

@main
struct MyFirstBot {
    static func main() async {
        let token = ProcessInfo.processInfo.environment["DISCORD_BOT_TOKEN"] ?? ""
        let client = DiscordClient(token: token)
        
        do {
            try await client.loginAndConnect(intents: [.guilds, .guildMessages, .messageContent])
            
            for await event in client.events {
                switch event {
                case .ready(let info):
                    print("โœ… Bot is online as \(info.user.username)!")
                    
                case .messageCreate(let message) where message.content == "!hello":
                    try await client.sendMessage(
                        channelId: message.channel_id,
                        content: "๐Ÿ‘‹ Hello, \(message.author.username)!"
                    )
                    
                default:
                    break
                }
            }
        } catch {
            print("โŒ Error: \(error)")
        }
    }
}

App Emoji (typed top-level)

// Create (image should be data URI string, e.g. "data:image/png;base64,<...>")
let created = try await client.createAppEmoji(
  applicationId: appId,
  name: "party",
  imageBase64: "data:image/png;base64,....",
  options: ["roles": .array([])] // optional extras
)

// Update
let updated = try await client.updateAppEmoji(
  applicationId: appId,
  emojiId: "1234567890",
  updates: ["name": .string("party_blob")] 
)

// Delete
try await client.deleteAppEmoji(
  applicationId: appId,
  emojiId: "1234567890"
)

UserApps (typed wrapper names)

// Create resource under your application scope
let res = try await client.createUserAppResource(
  applicationId: appId,
  relativePath: "directory/listings",
  payload: ["title": .string("My Awesome App"), "enabled": .bool(true)]
)

// Update resource
let upd = try await client.updateUserAppResource(
  applicationId: appId,
  relativePath: "directory/listings/abc",
  payload: ["enabled": .bool(false)]
)

// Delete resource
try await client.deleteUserAppResource(
  applicationId: appId,
  relativePath: "directory/listings/abc"
)

That's it! You now have a working Discord bot. ๐ŸŽ‰


๐Ÿ“ฆ Installation

Swift Package Manager

Add SwiftDisc to your Package.swift:

dependencies: [
    .package(url: "https://github.com/M1tsumi/SwiftDisc.git", from: "0.8.0")
]

Then include it in your target:

targets: [
    .target(name: "YourBot", dependencies: ["SwiftDisc"])
]

Platform Requirements

Platform Minimum Version Status
iOS 14.0+ โœ… Fully Supported
macOS 11.0+ โœ… Fully Supported
tvOS 14.0+ โœ… Fully Supported
watchOS 7.0+ โœ… Fully Supported
Windows Swift 5.9+ โœ… Fully Supported

๐ŸŽ“ Learn by Example

We've created comprehensive examples to help you get started:

๐Ÿ“Œ Simple Ping Bot

Perfect for understanding the basics of event handling and message responses.

// Responds to "!ping" with the bot's latency
case .messageCreate(let message) where message.content == "!ping":
    try await client.sendMessage(
        channelId: message.channel_id,
        content: "๐Ÿ“ Pong! Latency: 42ms"
    )

View Full Example โ†’

๐ŸŽฎ Command Handler Bot

Learn how to build a command system with prefix routing and help commands.

let router = CommandRouter(prefix: "!")
router.register("help") { context in
    try await context.reply("Available commands: !help, !userinfo, !serverinfo")
}

View Full Example โ†’

โšก Slash Commands & Autocomplete

Discover modern Discord interactions with slash commands and autocomplete.

let slash = SlashCommandRouter()
slash.register("greet") { interaction in
    try await interaction.reply("Hello from SwiftDisc! ๐Ÿ‘‹")
}

Slash Bot โ†’

๐Ÿ”Ž Autocomplete Provider

Dynamic suggestions for command options using AutocompleteRouter.

Autocomplete Bot โ†’

๐Ÿ“Ž File Uploads with Embeds

Multipart uploads with content-type detection and size guardrails.

File Upload Bot โ†’

๐Ÿงต Threads & Scheduled Events Listener

Listen to thread lifecycle and guild scheduled events.

Threads & Scheduled Events โ†’


๐Ÿ“š Comprehensive Documentation

Getting Started

Our Wiki provides in-depth guides for:

  • ๐ŸŽฏ Core Concepts โ€” Understanding intents, events, and the Discord API
  • ๐Ÿ”ง Configuration โ€” Setting up your bot for development and production
  • ๐ŸŽจ Message Features โ€” Embeds, components, attachments, and more
  • โš™๏ธ Sharding โ€” Scaling your bot across multiple servers
  • ๐Ÿš€ Deployment โ€” Best practices for production environments

Need Help?


๐ŸŒŸ Features

Gateway & Events

  • โœ… Full WebSocket gateway implementation
  • โœ… Automatic heartbeat and session management
  • โœ… Resume support for connection recovery
  • โœ… Structured event system with AsyncSequence
  • โœ… Presence updates and status management
  • โœ… Threads and Scheduled Events (create/update/delete, members add/remove)
  • โœ… 100% event visibility via DiscordEvent.raw(String, Data) fallback for unmodeled dispatches

REST API Coverage

  • โœ… Channels โ€” Create, modify, delete channels and threads
  • โœ… Messages โ€” Send, edit, delete with embeds and components
  • โœ… Guilds โ€” Full server management capabilities
  • โœ… Members & Roles โ€” User and permission management
  • โœ… Slash Commands โ€” Create and manage application commands
  • โœ… Webhooks โ€” Create and execute webhooks
  • โœ… Auto Moderation โ€” Configure moderation rules
  • โœ… Scheduled Events โ€” Create and manage server events
  • โœ… Forum Channels โ€” Create threads and posts
  • โœ… Raw coverage helpers: rawGET/POST/PATCH/PUT/DELETE for any unsupported endpoint

Member Timeouts

// Timeout a member until a specific ISO8601 timestamp
let in10Min = Date().addingTimeInterval(10 * 60)
let updated: GuildMember = try await client.setMemberTimeout(guildId: guildId, userId: userId, until: in10Min)

// Clear timeout
let cleared: GuildMember = try await client.clearMemberTimeout(guildId: guildId, userId: userId)

Advanced Features

  • โœ… Per-route rate limit handling with automatic retries
  • โœ… Global rate limit detection and backoff
  • โœ… Sharding support with automatic shard count
  • โœ… Health monitoring and shard management
  • โœ… Typed command routing (prefix and slash) + Autocomplete router
  • โœ… Rich embed builder and message components (buttons, select menus)
  • โœ… File uploads: multipart with content-type detection and configurable guardrails (maxUploadBytes)
  • โœ… Advanced caching: configurable TTLs and per-channel message LRU
  • โœ… Extensions/Cogs: simple plugin protocol and Cog helper; DiscordClient.loadExtension(_:)
  • โœ… Permissions utilities: effective permission calculator with channel overwrites

Components V2 (generic payload)

Use postMessage(channelId:payload:) with JSONValue to send Components V2 while keeping SwiftDisc zero-dependency and future-proof. Paste the payload from the Discord docs.

// Example skeleton โ€“ replace with the latest Components V2 JSON from docs
let payload: [String: JSONValue] = [
  "content": .string("Hello with Components V2"),
  "flags": .int(1 << 15), // if docs require enabling V2 via flag
  "components": .array([
    .object(["type": .int(1), "children": .array([ /* ... */ ])])
  ])
]
let msg = try await client.postMessage(channelId: channelId, payload: payload)

Typed envelope helper:

let v2 = V2MessagePayload(
  content: "Hello with V2",
  flags: 1 << 15, // if required by docs
  components: [
    .object(["type": .int(1), "children": .array([ /* ... */ ])])
  ]
)
let msg = try await client.sendComponentsV2Message(channelId: channelId, payload: v2)

Polls (generic payload)

Use createPollMessage(channelId:content:poll:flags:components:) with a poll object conforming to the Poll Resource schema.

// Example skeleton โ€“ replace with the Poll Resource JSON from docs
let poll: [String: JSONValue] = [
  "question": .object(["text": .string("Your favorite language?")]),
  "answers": .array([
    .object(["answer_id": .int(1), "poll_media": .object(["text": .string("Swift")])]),
    .object(["answer_id": .int(2), "poll_media": .object(["text": .string("Kotlin")])])
  ]),
  "allow_multiple": .bool(false),
  "duration": .int(600) // seconds
]
let msg = try await client.createPollMessage(channelId: channelId, content: "Vote now!", poll: poll)

Typed envelope helper:

let pollPayload = PollPayload(
  question: "Your favorite language?",
  answers: ["Swift", "Kotlin"],
  durationSeconds: 600,
  allowMultiple: false
)
let msg = try await client.createPollMessage(channelId: channelId, payload: pollPayload, content: "Vote now!")

Localization (Application Commands)

Update command name/description localizations:

let updated = try await client.setCommandLocalizations(
  applicationId: appId,
  commandId: cmdId,
  nameLocalizations: [
    "en-US": "ping",
    "ja": "ใƒ”ใƒณ"
  ],
  descriptionLocalizations: [
    "en-US": "Check latency",
    "ja": "ใƒฌใ‚คใƒ†ใƒณใ‚ทใƒผใ‚’็ขบ่ช"
  ]
)

Forwarding

Post a message in another channel that references an existing message (portable forward):

let forwarded = try await client.forwardMessageByReference(
  targetChannelId: targetChannelId,
  sourceChannelId: sourceChannelId,
  messageId: messageId
)

Generic Application Resources (UserApps/App Emoji)

Use these helpers to call application-scoped endpoints with JSONValue payloads. This keeps SwiftDisc current as Discord evolves.

// POST /applications/{appId}/{relativePath}
let createRes = try await client.postApplicationResource(
  applicationId: appId,
  relativePath: "some/feature",
  payload: ["key": .string("value")]
)

// PATCH /applications/{appId}/{relativePath}
let patchRes = try await client.patchApplicationResource(
  applicationId: appId,
  relativePath: "some/feature/id",
  payload: ["enabled": .bool(true)]
)

// DELETE /applications/{appId}/{relativePath}
try await client.deleteApplicationResource(
  applicationId: appId,
  relativePath: "some/feature/id"
)

Developer Utilities

  • โœ… Mentions: Mentions.user(_:), Mentions.channel(_:), Mentions.role(_:), Mentions.slashCommand(name:id:)
  • โœ… Emoji helpers: EmojiUtils.custom(name:id:animated:)
  • โœ… Timestamps: DiscordTimestamp.format(date:style:), format(unixSeconds:style:)
  • โœ… Escaping: MessageFormat.escapeSpecialCharacters(_:)

๐ŸŽฏ Production Ready

SwiftDisc is built for real-world applications:

Reliability

  • Automatic Reconnection โ€” Handles network issues gracefully
  • Rate Limit Compliance โ€” Respects Discord's limits automatically
  • Session Resume โ€” Maintains connection state across reconnects

Scalability

  • Sharding Support โ€” Built-in multi-shard management
  • Health Monitoring โ€” Track shard status and latency
  • Graceful Shutdown โ€” Clean disconnection handling

Developer Experience

  • Comprehensive Logging โ€” Detailed logs for debugging
  • Type-Safe APIs โ€” Catch errors at compile time
  • Clear Error Messages โ€” Actionable error descriptions
// Automatic sharding for large bots
let manager = await ShardingGatewayManager(
    token: token,
    configuration: .init(
        shardCount: .automatic,
        connectionDelay: .staggered(interval: 1.5)
    ),
    intents: [.guilds, .guildMessages]
)

try await manager.connect()

// Monitor health across all shards
let health = await manager.healthCheck()
print("Ready shards: \(health.readyShards)/\(health.totalShards)")

๐Ÿ’ฌ Join Our Community

We're building SwiftDisc together with the community! Whether you're a beginner looking to create your first bot or an experienced developer with feature requests, we'd love to have you.

Get help, share your projects, and connect with other SwiftDisc developers!

What you'll find:

  • ๐Ÿ†˜ Support channels for troubleshooting
  • ๐Ÿ’ก Showcase your bots and get feedback
  • ๐Ÿ“ข Stay updated with the latest releases
  • ๐Ÿค Collaborate with other developers

๐Ÿ›ฃ๏ธ Roadmap

We're actively developing SwiftDisc with these priorities:

Current Focus (v0.9.x)

  • Autocomplete
  • File uploads polish (MIME + guardrails)
  • Gateway parity: Threads & Scheduled Events + raw fallback
  • Advanced caching & permissions utilities
  • Extensions/Cogs

Future Plans

  • Voice support (optional module)
  • Voice support (sendโ€‘only MVP)
  • Performance optimizations

Want to influence the roadmap? Join the Discord server and share your ideas!

๐Ÿ”Š Voice (Experimental)

Initial voice support is available behind a configuration flag. This is a send-only implementation that connects to Discord Voice, performs UDP IP discovery, negotiates xsalsa20_poly1305, and can transmit Opus frames (no external dependencies required).

Enable and use:

let config = DiscordConfiguration(enableVoiceExperimental: true)
let client = DiscordClient(token: token, configuration: config)

try await client.joinVoice(guildId: guildId, channelId: channelId)

// Option A: Push individual Opus packets (20ms @ 48kHz)
try await client.playVoiceOpus(guildId: guildId, data: opusPacket)

// Option B: Stream from a VoiceAudioSource
struct MySource: VoiceAudioSource {
    func nextFrame() async throws -> OpusFrame? { /* return OpusFrame(data:packet,durationMs:20) */ }
}
try await client.play(source: MySource(), guildId: guildId)

try await client.leaveVoice(guildId: guildId)

Whatโ€™s included:

  • Voice Gateway handshake (Hello โ†’ Identify โ†’ Ready โ†’ Heartbeat)
  • UDP IP discovery (Network.framework on Apple platforms)
  • Protocol selection (xsalsa20_poly1305)
  • Session Description key handling
  • RTP packetization + pure-Swift Secretbox encryption (no SwiftPM deps)
  • Speaking flag management

Requirements:

  • Input must be Opus-encoded packets at 48kHz (20ms recommended). SwiftDisc does not bundle an Opus encoder or media demuxer to maintain zero dependencies.

macOS streaming with ffmpeg (no Swift dependencies):

Use an external ffmpeg (system-installed) to demux/encode your source (YouTube, SoundCloud, etc.) to raw Opus packets and pipe them into SwiftDisc. Implement a small wrapper to length-prefix packets (u32 LE) or use a helper that outputs framed Opus.

Example framing expected by PipeOpusSource:

  • Frame format: [u32 little-endian length][<length> bytes] repeated.

Use PipeOpusSource:

import Foundation

let source = PipeOpusSource(handle: FileHandle.standardInput)
try await client.play(source: source, guildId: guildId)

Then run your bot and feed framed Opus via stdin. For example, you can create a small CLI that transforms ffmpeg output to the framed format and pipe to the bot. This keeps SwiftDisc zero-dependency.

iOS guidance:

  • iOS cannot spawn ffmpeg. Provide Opus packets from your app/backend over your own transport (e.g., HTTPS/WebSocket) and feed them to a VoiceAudioSource.

Security and correctness:

  • SwiftDisc vendors a pure-Swift Secretbox (XSalsa20-Poly1305) implementation with Poly1305 MAC and Salsa20 core. Nonce derivation follows the Discord RTP header convention. We recommend validating with your own test vectors as part of your CI.

๐Ÿค Contributing

Contributions make SwiftDisc better for everyone! Here's how you can help:

  • ๐Ÿ› Report Bugs โ€” Found an issue? Open an issue
  • ๐Ÿ’ก Suggest Features โ€” Have an idea? Start a discussion
  • ๐Ÿ“ Improve Docs โ€” Documentation improvements are always welcome
  • ๐Ÿ”ง Submit PRs โ€” Code contributions are appreciated!

Check our Contributing Guidelines for more details.

๐Ÿ“„ License

SwiftDisc is released under the MIT License. See LICENSE for details.

In short: You're free to use SwiftDisc for personal and commercial projects, with attribution.

Ready to build your Discord bot?

๐Ÿ“– Read the Docs โ€ข ๐Ÿ’ฌ Join Discord โ€ข ๐Ÿš€ View Examples

โญ Star us on GitHub if you find SwiftDisc helpful!

Description

  • Swift Tools 5.9.0
View More Packages from this Author

Dependencies

  • None
Last updated: Sat Apr 25 2026 08:19:09 GMT-0900 (Hawaii-Aleutian Daylight Time)