Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
450e3fa
v2.5.0: Full API audit and feature update
M1tsumi Jun 30, 2026
dd5a704
Add context menu / nsfw / contexts support to SlashCommandBuilder and…
M1tsumi Jun 30, 2026
4fa6e97
Add Welcome Screen endpoints, all missing gateway event callbacks, fi…
M1tsumi Jun 30, 2026
34e2fb2
Add typed enums across all models + REST endpoints for New Member Wel…
M1tsumi Jun 30, 2026
a6ca82c
v2.5.0: Fix duplicate methods, update version strings, update CHANGEL…
M1tsumi Jun 30, 2026
f514642
Delete GAP_ANALYSIS.md, make Message.reply public so doc examples com…
M1tsumi Jun 30, 2026
3f8bb90
Fix build failures: add missing Hashable conformance to ChannelFlags,…
M1tsumi Jun 30, 2026
de886ac
Fix review issues: mutating builders for inout closures, bind generic…
M1tsumi Jun 30, 2026
bf21ae7
Fix CI build failures across all targets
M1tsumi Jul 1, 2026
3bead36
Fix test compilation: Interaction.type is now InteractionType enum, n…
M1tsumi Jul 1, 2026
88de2f9
Tone down README marketing language, add local doc build instructions
M1tsumi Jul 1, 2026
043b6bf
Update v2.5.0 changelog with fix entries, bump date to today
M1tsumi Jul 1, 2026
d3cb5a2
Fix local DocC preview instructions to avoid 404 when opening static …
M1tsumi Jul 1, 2026
38ca307
Merge origin/main into next (resolve README conflict)
M1tsumi Jul 1, 2026
7a92bd8
Fix README DocC command: --allow-writing-to-directory needs output di…
M1tsumi Jul 1, 2026
ad3fdc6
Release v2.6.0 — developer-ergonomics and infrastructure release
M1tsumi Jul 8, 2026
d9b5d85
Add missing gateway events and typed model enums
M1tsumi Jul 8, 2026
1f92f1c
Refactor Interaction.version to use stored _version with computed pro…
M1tsumi Jul 8, 2026
b4b01e5
Add test infrastructure: mocks, fixtures, HTTPClient + RateLimiter tests
M1tsumi Jul 8, 2026
ad19302
Fix 7 coderabbitai review issues
M1tsumi Jul 9, 2026
3c611ae
Fix CI builds and test failures
M1tsumi Jul 9, 2026
3a9d84a
Fix CI: use swift-actions/setup-swift@v2 with swift-version: 6.2
M1tsumi Jul 9, 2026
21dbd92
Fix Windows CI: per-platform Swift setup actions
M1tsumi Jul 9, 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
22 changes: 10 additions & 12 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,19 @@ jobs:
steps:
- uses: actions/checkout@v4

- name: Install Swift 6.2
- name: Install Swift 6.2 (macOS / Ubuntu)
if: matrix.os != 'windows-latest'
uses: swift-actions/setup-swift@v2
with:
swift-version: "6.2"

- name: Install Swift 6.2 (Windows)
if: matrix.os == 'windows-latest'
uses: compnerd/gha-setup-swift@main
with:
source: swift.org
swift-version: swift-6.2-release
swift-build: 6.2-RELEASE

- name: Run tests (Unix)
if: matrix.os != 'windows-latest'
run: |
chmod +x scripts/run-tests.sh || true
./scripts/run-tests.sh

- name: Run tests (Windows)
if: matrix.os == 'windows-latest'
shell: pwsh
run: |
.\scripts\run-tests.ps1
- name: Run tests
run: swift test
6 changes: 2 additions & 4 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,9 @@ jobs:
uses: actions/configure-pages@v4

- name: Install Swift 6.2
uses: compnerd/gha-setup-swift@main
uses: swift-actions/setup-swift@v2
with:
source: swift.org
swift-version: swift-6.2-release
swift-build: 6.2-RELEASE
swift-version: "6.2"

- name: Generate Documentation
run: |
Expand Down
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -83,5 +83,5 @@ zya/bug.txt
Fails/
failed/

audit-*.md
audit*.md
GAP_ANALYSIS.md
106 changes: 106 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,112 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [2.6.0] - 2026-07-08

### Overview
SwiftDisc 2.6.0 is a developer-ergonomics and infrastructure release. This audit-driven release focuses on making the library easier to use at every API surface level while ensuring the underlying machinery is robust, testable, and future-proof. No voice support is planned or will ever be added.

### Added
- **Documentation audit** — comprehensive doc comments added across Models (VanityURL, GuildWidgetSettings, GuildPreview, NewMemberWelcome, StageInstance, ScheduledEventUser, Template, RoleMemberCount, Application, AppInstallations, Monetization, Onboarding)
- **Gateway intent support** — `GatewayIntents` type and property on `DiscordConfiguration` for event subscription configuration

### Changed
- **Unified builder pattern** — all builders now follow the non-mutating `var c = self; c.x = v; return c` pattern for consistency
- **`@discardableResult` audit** — consistently applied across all mutation/creation methods on `DiscordClient`
- **Method naming standardization** — established `get`/`list`/`create`/`update`/`delete`/`send` conventions across all REST endpoints
- **GatewayClient** — `close()` changed from `internal` to `public` for API symmetry with `disconnect()`
- **GatewayClient** — `validatePrivilegedIntents` renamed to `logPrivilegedIntentWarnings` for accuracy
- **HTTPClient** — multipart methods consolidated into shared `multipartRequest(method:)` helper eliminating ~90% duplication
- **HTTPClient** — `makeRouteKey` fixed to correctly identify major parameters for multi-segment paths
- **Cache** — emoji storage changed from `[GuildID: TimedValue<[Emoji]>]` to `[EmojiID: TimedValue<Emoji>]` for granular per-emoji eviction parity with roles
- **Cache** — `ensureChannelStub(id:)` now accepts an optional `ChannelType` parameter instead of always defaulting to `.text`
- **JSONCoders** — shared encoder/decoder now configurable via `DiscordConfiguration`
- **MessagePayload** — fixed field forwarding in `send(to:_:)`, `edit(channelId:messageId:_:)`, and `respond(to:with:deferred:)` — all fields now correctly forwarded regardless of file attachment presence
- **GatewayClient** — `resumeGatewayUrl` expiry corrected from 7 days to 24 hours to match Discord's actual expiration
- **GatewayClient** — `missedHeartbeatAckCount` now correctly managed only by the sending loop (not double-incremented on OP1-triggered heartbeats)
- **LabelBuilder, RadioGroupBuilder, CheckboxGroupBuilder, CheckboxBuilder, FileUploadBuilder** — marked as `@available(*, deprecated, message: "Not yet part of Discord API — experimental")` to prevent users from building rejected payloads
- **Snowflake** — changed from `@unchecked Sendable` to unconditional `extension Snowflake: Sendable {}` since `String` is inherently `Sendable`
- **RedactedToken** — `rawValue` access narrowed to `internal` for stronger token leak protection
- **Configuration** — `GatewayCompression` `.zstdStream` and `.zlibStream` cases added as opt-in (currently experimental)
- **Configuration** — `retryPolicy` property wired from `DiscordConfiguration` to `HTTPClient`
- **Configuration** — `presence` initial presence configuration (status, activities, afk) added
- **Configuration** — `userAgentSuffix` property added for custom User-Agent identification

### Fixed
- **`Collectors` task leaks** — all 11 event collector methods now store and cancel their spawned tasks, preventing unbounded resource growth
- **`ComponentCollector` task leak** — same fix applied
- **`ViewManager` double-start race** — boolean flag set synchronously before spawning listening task
- **`ViewManager` detached task strong reference** — changed to `[weak self]` capture
- **`ViewManager` handler tasks** — grouped under a `TaskGroup` for collective cancellation
- **`ViewManager` repeated error-handling blocks** — extracted into shared helper method
- **`ViewManager.stop()`** — added public method to tear down listening task and unregister all views
- **`GatewayClient.connectReadyContinuation` double-resume** — guarded by `didResumeConnectReady` boolean flag
- **`GatewayClient.readLoop` decode error busy-loop** — added exponential backoff on decode failures
- **`GatewayClient` detached task cancellation** — `readLoop` task now cancelled when `connect()` task is cancelled
- **`HTTPClient.executeWithRetry` semaphore deadlock** — semaphore signal now uses direct continuation resume instead of `Task { ... }` in `defer {}`
- **`AsyncSemaphore` cancellation handling** — cancelled waiters now properly removed from the waiter list
- **`RateLimiter` global timestamp accounting** — timestamps now appended AFTER backoff completes, not before
- **`RateLimiter` duplicate header crash** — `lowercasedHeaders` changed to safe `reduce(into:)` pattern instead of `Dictionary(uniqueKeysWithValues:)`
- **`RateLimiter.clearBucket(routeKey:)** — now also clears the route-to-bucket mapping, preventing stale state
- **`DiscordClient.eventContinuation` data race** — `AsyncStream` continuation now initialized lazily from actor-isolated context
- **`DiscordClient` login task isolation** — `Task { [self] in }` changed to `Task.detached` with explicit `await` for all actor accesses
- **`DiscordClient._internalSetCurrentUserId` comment** — corrected from "Internal voice wiring" to "Internal: records the current bot user's ID after the READY event"
- **`MessagePayload.respond` 204 handling** — now accepts empty 204 No Content responses without crashing
- **`CommandRouter` quoted-argument parsing** — text commands now support `"hello world"` as single arguments
- **`EmbedBuilder` ISO8601DateFormatter** — now cached as a static let instead of allocating on every call
- **`Converters.discordOrange`** — corrected from `0xEB459E` (fuchsia) to `0xFEE75C` (Discord's actual orange)
- **`Converters` invite code validation** — expanded from 6-10 to 6-25 character range
- **`Converters` mention regexes** — tightened from `[0-9]{5,}` to `[0-9]{17,19}` to avoid false matches on non-ID numbers
- **`CooldownManager` auto-cleanup** — now starts lazily on first `setCooldown` call (was stuck at init)
- **`CooldownManager` compound key collision** — changed from `"\(command)::\(key)"` to `"\(command)\0\(key)"` with null-byte separator
- **`CooldownManager` strong reference cycle** — `cleanupTask` now uses `[weak self]` capture
- **`RetryPolicy` backoff jitter** — added `jitter: Double` (default 0.1 = 10% randomization) to prevent thundering herd
- **`Cache` message accumulation** — `EventDispatcher.messageUpdate` now updates in-place instead of appending
- **`JSONValue.number` precision** — large integer values (>2^53) now stored as `Int64` instead of `Double` to preserve snowflake precision
- **`OptionalField`** — added `Decodable` conformance for round-trip encoding/decoding support
- **`OptionalField` single-value encoder** — `.absent` now correctly omits the key in single-value containers (was encoding as `null`)
- **`WebhookClient` rate limiter** — changed from single global instance to per-instance rate limiter
- **`WebhookClient` hardcoded API version** — now reads from `DiscordConfiguration.apiVersion`
- **`DefaultsDiscordLogger` Sendable concurrency safety** — `print()` calls now wrapped with `await` for Swift 6 conformance
- **`StringSelectMenuBuilder` removed** — exact duplicate of `SelectMenuBuilder`; use `SelectMenuBuilder` directly
- **`User.username`** — changed from non-optional `String` to `String?` since Discord returns `null` for deleted users
- **`Interaction.version`** — changed from `Int?` to non-optional `Int` with default `1`
- **`SelectMenu.Option.emoji`** — changed from `String?` to `PartialEmoji?` (Discord API returns a partial emoji object, not a string)
- **`Webhook.type`, `Sticker.type`, `Sticker.format_type`, `StickerItem.format_type`, `Invite.type`, `Entitlement.type`** — migrated from raw `Int`/`Int?` to typed enums with `unknown` fallback cases
- **`Entitlement.subscription_id`** — changed from `String?` to `AppSubscriptionID`
- **`AppInstallation.id` and `AppSubscription.id`** — changed phantom type from the struct itself to dedicated tag types (`AppInstallationTag`, `AppSubscriptionTag`)
- **`UserPrimaryGuild.guild_id` and `identity_guild_id`** — changed from `String?` to `GuildID?`
- **`Invite.InviteChannel.type`** — changed from `Int?` to `ChannelType?`
- **`RadioGroup.RadioOption.init`** — parameter renamed from `isDefault` (stored as `default`) to `default` for naming consistency
- **`MessageComponents.defaultUsers()` and `defaultRoles()`** — now additive (accumulate) instead of each overwriting the previous value
- **`Message.referenced_message`** — `Box<T>` conformance changed from `@unchecked Sendable` to conditional `extension Box: @unchecked Sendable where T: Sendable {}`
- **`PermissionBitset.all` mask** — updated to `(1 << 64) - 1` for forward-compatibility with new permission bits
- **`EventDispatcher` raw event** — now forwarded as `.raw(String, Data)` instead of silently discarded (`break`)
- **`RadioGroup` / `CheckboxGroup` / `Label` / `Checkbox`** `MessageComponent` decoding — unknown types now decoded as `.unknown(Data)` preserving raw data instead of lossy `.button` fallback
- **`Guild.init` compactMap** — changed to `map` since `GuildFeature.init(rawValue:)` never returns nil
- **`GatewayModels.Presence`** — populated from empty struct to full model with proper fields
- **`DiscordClient`** — split from single 4361-line file into domain-specific extensions (DiscordClient+Messages, +Guilds, +Channels, +Interactions, +Commands, +Monetization, +VoiceEvents)
- **`AHCTransport`** — changed from `@unchecked Sendable` to explicit `Sendable` conformance
- **`URLSessionTransport.LockedBox`** — replaced with `OSAllocatedUnfairLock` for safe concurrency
- **`URLSessionTransport.close()`** — now waits for close-frame handshake before calling `invalidateAndCancel()`
- **`AHCTransport.deinit`** — explicit `shutdown()` method added; `deinit` no longer calls `syncShutdown()`
- **`RegionRateLimiter`** — `ContinuousClock` used instead of `Date` for monotonic interval measurement
- **EventDispatcher** — added `onRawGatewayPayload` callback for low-level gateway access
- **EventDispatcher** — `sessionInvalidated` now clears the cache

### Security
- **RedactedToken.rawValue** — narrowed to `internal` access, preventing accidental token leakage outside the module
- **Token validation** — `RedactedToken.init` now validates token format (3 base64 segments) and warns on double `"Bot "` prefix
- **URLSessionTransport header sanitization** — now strips all CRLF variants including embedded `\r\n` sequences

### Deprecated
- **`DiscordUtils.Mentions.userNickname(_:)`** — deprecated in favor of `user(_:)` (renders identically in modern Discord clients)
- **`LabelBuilder`, `RadioGroupBuilder`, `CheckboxGroupBuilder`, `CheckboxBuilder`, `FileUploadBuilder`** — experimental, not part of Discord's current API

### Notes
- Voice support is not planned and will never be implemented. Voice-adjacent model fields (channel bitrate, voice permission flags, voice message flags, voice audit log events, voice scheduled event type) are retained solely for Discord API compliance — they are passive model fields, not functional voice implementation.
- All audit findings and analysis artifacts are tracked in `audit.md` (excluded from version control via `.gitignore`).

## [2.5.0] - 2026-06-30

### Overview
Expand Down
2 changes: 1 addition & 1 deletion Examples/ShardingBot.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ struct ShardingBotMain {
identifyConcurrency: .respectDiscordLimits,
fallbackPresence: .init(
activities: [
.init(name: "with shards", type: 0)
.init(name: "with shards", type: .game)
],
status: "online",
afk: false
Expand Down
12 changes: 9 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -322,14 +322,20 @@ print(await cache.summary)
| `SwiftDiscAHCTransport` | Optional AsyncHTTPClient transport. Add `.product(name: "SwiftDiscAHCTransport", package: "SwiftDisc")` to use it. Supports proxies on Linux |
| [**CODE_OF_CONDUCT.md**](CODE_OF_CONDUCT.md) | Community standards and expectations |

You can also build the docs locally:
You can also build and preview the docs locally:

```bash
# Requires swift-docc-plugin (add it to Package.swift first)
swift package --allow-writing-to-directory generate-documentation --target SwiftDisc --output-path docs --transform-for-static-hosting
# Preview with a local web server (hot-reloads on source changes):
swift package --disable-sandbox preview-documentation --target SwiftDisc

# Or build static HTML and serve it:
swift package --allow-writing-to-directory docs generate-documentation --target SwiftDisc --output-path docs --transform-for-static-hosting --hosting-base-path /
cd docs && python3 -m http.server 8080
# Then open http://localhost:8080 in a browser
```

Then open `docs/index.html` in a browser.
The CI workflow publishes to **GitHub Pages** at `https://M1tsumi.github.io/SwiftDisc/` on every push to `main`. The `--hosting-base-path SwiftDisc` flag in the workflow matches the repo's subpath on Pages.

## Community and support

Expand Down
Loading
Loading