Skip to content

Next#16

Merged
M1tsumi merged 130 commits into
mainfrom
next
Jun 14, 2026
Merged

Next#16
M1tsumi merged 130 commits into
mainfrom
next

Conversation

@M1tsumi

@M1tsumi M1tsumi commented May 12, 2026

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

  • New Features

    • Typed application emoji management
    • Paginated poll-answer voter retrieval
    • Bulk member banning (client-side cap: 200)
    • Soundboard playback in voice channels
    • Slash command localization and richer choice values
  • Improvements

    • Audit-log reason supported for REST and multipart requests
    • Improved request concurrency and rate-limit handling
    • Guild member search now uses POST-based search
    • Sticker creation accepts audit reasons
    • Text input style now uses raw numeric values
    • Comprehensive DocC API docs + GitHub Pages deployment; version bumped to 2.3.1
  • Chores

    • Examples packaging fixes; local failed/ ignored in .gitignore

Review Change Stack

M1tsumi added 26 commits May 12, 2026 12:42
Move local development files to .local/ directory
and update .gitignore to ignore the folder instead of
individual files. This keeps local development artifacts
out of the repository.
Replace [String:Int] decoding with SeqProbe struct to correctly
extract sequence number from gateway dispatch frames. The previous
implementation failed because real frames contain op, d (object), and
t (string) fields, not just integers. This fixes RESUME functionality
and heartbeat sequence tracking.
Include major parameters (channel_id, guild_id, webhook_id) in
rate-limit bucket keys to properly isolate buckets per resource.
Previous implementation lumped all channels/guilds into the same
bucket, causing serialization of unrelated requests and 429 errors.
Now uses format: METHOD:path|major=major_param
Add ensureChannelStub method to Cache that only inserts a stub
channel when the key is missing. Update EventDispatcher to use this
instead of upsert, preventing overwrites of full Channel objects with
stubs that only contain id and type. This preserves channel metadata
like name, topic, and permission_overwrites for permission checks.
Decode the boolean d field from INVALID_SESSION opcode to determine
if the session is resumable. Only clear session state if d is false.
If d is true, sleep 1-5 seconds and retry RESUME instead of
unconditionally throwing away recoverable sessions.
Remove deprecated $ prefix from os, browser, and device keys
in IdentifyConnectionProperties. Discord's current spec uses os,
browser, and device without the $ prefix. The deprecated keys are
tolerated but flagged as deprecated client by anti-spam systems.
Set maximumMessageSize to 16 MiB to handle large gateway
payloads like GUILD_CREATE. The default 1 MB limit can cause
socket closure with code 1009 for large guilds with many
members, channels, roles, threads, and stickers.
Ensure URLError.cancelled is checked before retry logic in the
outer catch block. Cancellation should always be terminal and
should not trigger retry attempts. The check now happens before
the attempt < maxAttempts check.
Add disconnected(reason:) event to DiscordEvent to surface fatal
disconnects. Add closeCode property to WebSocketClient protocol
and implement it in adapters. Check for fatal 4000-series close
codes before attempting reconnection and surface descriptive
errors. Stop reconnection on fatal codes like 4004 (auth failed),
4011 (sharding required), 4013/4014 (invalid intents). Surface
disconnected event when max reconnect attempts are reached.
Add resume_gateway_url field to ReadyEvent and store it in
GatewayClient. Use the resume URL when connecting instead of
the default gateway URL. This is required by Discord for v10
resumes and reduces latency by avoiding redirects.
Mark zlib-stream and zstd-stream compression options as
unavailable until decompression is implemented. Enabling these
currently silently breaks the bot since the readLoop only handles
string and data frames as raw JSON, not compressed binary frames.
Move attachment descriptors into payload_json instead of creating
separate multipart parts per file. Discord expects a single
attachments JSON array embedded in the payload_json part, not
multiple attachments form-data parts. This fixes the issue where
multiple files with descriptions would create duplicate multipart
parts that Discord rejects.
Fix four compilation errors introduced in v2.3 correctness patch:
1. Add disconnected case to EventDispatcher switch for exhaustiveness
2. Fix resume_gateway_url String? to URL conversion
3. Replace non-existent attemptResume with attemptReconnect
4. Remove optional chaining from non-optional URLSessionWebSocketTask.closeCode
Remove stray code after actor closing brace and remove non-existent
onDisconnect callback. The disconnected case now simply breaks
to satisfy switch exhaustiveness without calling undefined properties.
The prior syntax-fix commit restored the actor's closing brace and the func's closing brace but left the switch statement unterminated, so all three CI builds (macOS, Ubuntu, Windows) failed with cascading 'expected ''}''' errors and spurious 'EventDispatcher has no member ''process''' diagnostics in DiscordClient.swift.

Add the missing '}' after the final '.disconnected' case so the switch, func, and actor all close correctly, restoring a parsable module and resolving every downstream error in the CI logs.
Bump version to 2.3.0 in DiscordConfiguration.swift and update
CHANGELOG.md with comprehensive fix details including EventDispatcher
syntax corrections, Swift 6.2 compilation fixes, multipart attachment
handling, gateway connection improvements, and WebSocket reliability
enhancements. Update README.md to reference version 2.3.0 and document
debugging capabilities including gateway decode diagnostics, rate limit
observability, typed error handling, router error handlers, and default
error logging.
Update isFatalCloseCode to return true only for explicit fatal codes
(4004, 4010, 4011, 4012, 4013, 4014) instead of treating the entire
4000-4999 range as fatal. This allows reconnection attempts for recoverable
close codes like 4000, 4007, 4008, 4009 while still blocking reconnection
for truly fatal authentication, sharding, and intent errors.
Fixed two P0 rate-limiting issues:

1. Bucket key generation now preserves major parameters
   (channel_id, guild_id, webhook_id) instead of replacing all
   snowflakes with :id. This ensures each channel/guild gets its
   own bucket, preventing unrelated requests from being serialized
   together.

2. Added per-bucket AsyncSemaphore to limit concurrent requests
   before bucket limits are known from headers. Defaults to 50
   concurrent requests per bucket (Discord's typical limit) and
   updates dynamically based on X-RateLimit-Limit headers.

These fixes prevent 429 errors in multi-guild scenarios by ensuring
proper bucket isolation and request serialization.

Resolves P0 bugs §1.3 and §1.11 from developer analysis.
Added optional reason parameter to all HTTPClient methods for
audit log tracking on destructive operations (bans, kicks,
deletions, etc.). The reason is URL-encoded and sent via the
X-Audit-Log-Reason header as specified by Discord's API.

This allows bot developers to provide context for moderation
actions in the guild audit log, improving accountability and
debugging capabilities.

Breaking change: All HTTPClient method signatures now include
an optional reason parameter. This is backward compatible
since the parameter has a default value of nil.

Resolves P1 feature §2.11 from developer analysis.
Implemented POST /guilds/{id}/bulk-ban endpoint supporting up to
200 user IDs per call. This is a 2024 Discord API endpoint that
significantly improves efficiency when banning multiple users.

The new bulkBanMembers method:
- Accepts an array of up to 200 user IDs
- Supports delete_message_seconds parameter (0-604800)
- Uses the new X-Audit-Log-Reason header for audit logging
- Returns BulkBanResponse with list of successfully banned users
- Validates the 200-user limit before making the request

Added BulkBanResponse model to GuildBan.swift with banned_users
array containing the IDs of users that were successfully banned.

This resolves P1 feature §2.15 from developer analysis.
Updated searchGuildMembers to use the 2024+ POST endpoint
/guilds/{id}/members-search instead of the deprecated GET
endpoint with query parameter.

The new POST endpoint:
- Uses request body instead of query parameters
- More consistent with Discord's modern API patterns
- Replaces the old GET /guilds/{id}/members/search?query=

This resolves P1 feature §2.4 from developer analysis.
Implemented GET /channels/{id}/polls/{message.id}/answers/{answer_id}
endpoint for paginated retrieval of users who voted for a specific
poll answer.

The new getPollAnswerVoters method:
- Accepts channelId, messageId, and answerId parameters
- Supports pagination via 'after' user ID and 'limit' (1-100)
- Returns PollAnswerUsers with user list and pagination metadata
- Default limit is 25 users per page

Added PollAnswerUsers model to Message.swift containing:
- has_more: Boolean indicating if more users exist
- users: Array of User objects who voted
- after: User ID for next pagination request

This enables efficient retrieval of poll voters for large polls
without fetching all data at once.

Resolves P1 feature §2.5 from developer analysis.
Updated application emoji methods to use correct Discord API path
/applications/{id}/emojis instead of the incorrect /app-emojis.

Changes:
- Renamed createAppEmoji to createApplicationEmoji (breaking change)
- Renamed updateAppEmoji to updateApplicationEmoji (breaking change)
- Renamed deleteAppEmoji to deleteApplicationEmoji (breaking change)
- Added listApplicationEmojis method to fetch all app emojis
- Changed return types from JSONValue to proper Emoji types
- Added typed parameters (name, roles) instead of generic JSONValue
- Changed emojiId parameter type from String to EmojiID

The application emoji endpoints allow bots to create and manage
emojis that are usable across all guilds, not just within
a single guild.

Resolves P1 feature §2.3 from developer analysis.
Fixed createGuildSticker to use Discord's specific multipart format
with individual form-data fields (name, description, tags, file)
instead of the standard payload_json shape.

Added postStickerMultipart method to HTTPClient that builds multipart
body with Discord's exact field layout:
- name: form-data field for sticker name
- description: optional form-data field for sticker description
- tags: form-data field for comma-separated tag string
- file: form-data field with the sticker image file

Updated createGuildSticker to use the new method and added support
for audit log reason parameter.

This fixes sticker uploads which were using the wrong format and
would be rejected by Discord's API.

Resolves P1 feature §2.14 from developer analysis.
Implemented POST /channels/{id}/send-soundboard-sound endpoint
for playing soundboard sounds in voice channels.

The new playSoundboardSound method:
- Accepts channelId (voice channel), soundId, and guildId
- Sends POST request with sound_id and guild_id in body
- Returns the SoundboardSound object
- Enables bots to play soundboard sounds programmatically

This complements the existing soundboard management methods
(list, create, modify, delete) by adding the ability to actually
play sounds in voice channels.

Resolves P1 feature §2.16 from developer analysis.
Added name_localizations and description_localizations fields to
ApplicationCommandCreate and ApplicationCommandOption structs to
support localization during command creation.

Changes:
- Added name_localizations: [String: String]? to ApplicationCommandCreate
- Added description_localizations: [String: String]? to ApplicationCommandCreate
- Added name_localizations: [String: String]? to ApplicationCommandOption
- Added description_localizations: [String: String]? to ApplicationCommandOption
- Added name_localizations: [String: String]? to ApplicationCommandOption.Choice

The setCommandLocalizations method already existed for updating
localizations after creation, but this allows setting localizations
during initial command creation, which is more efficient and follows
Discord's recommended pattern.

This enables bots to provide localized command names, descriptions,
and option names for different locales (e.g., en-US, es-ES, fr-FR).

Resolves P1 feature §2.2 from developer analysis.
@coderabbitai

coderabbitai Bot commented May 12, 2026

Copy link
Copy Markdown

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds per-route semaphores, threads optional audit reason through HTTP methods (including multipart/sticker), rewrites route bucketing to preserve major IDs, and implements REST helpers/endpoints and model updates (application emojis, poll voters, bulk bans, search POST, stickers, soundboard), plus extensive docs and minor model tweaks.

Changes

HTTP Client and REST API Enhancements

Layer / File(s) Summary
Core models and contracts
Sources/SwiftDisc/Models/*
Adds BulkBanResponse and PollAnswerUsers, and minor model field additions (Channel.last_pin_timestamp, ThreadMetadata.create_timestamp, ShardStatusSnapshot.lastSequence, ShardingHealth.totalGuilds, DiscordConfiguration.version).
HTTPClient concurrency & headers
Sources/SwiftDisc/REST/HTTPClient.swift
Adds AsyncSemaphore and BucketSemaphores, per-route semaphore acquisition in executeWithRetry, and threading of optional reason into all REST and multipart helpers with X-Audit-Log-Reason header application.
Sticker multipart and route bucketing
Sources/SwiftDisc/REST/HTTPClient.swift
Adds postStickerMultipart/buildStickerMultipartBody and rewrites makeRouteKey to preserve major snowflakes and include `
DiscordClient REST surface changes
Sources/SwiftDisc/DiscordClient.swift
Adds raw REST passthroughs (rawGET/rawPOST/rawPATCH/rawPUT/rawDELETE), typed application emoji endpoints, getPollAnswerVoters, bulkBanMembers (≤200 client-side), playSoundboardSound, changes searchGuildMembers to POST body, and routes createGuildSticker through postStickerMultipart with optional reason.
Slash command payload updates
Sources/SwiftDisc/HighLevel/SlashCommandBuilder.swift, Sources/SwiftDisc/DiscordClient.swift
Adds localization fields (name_localizations/description_localizations) and changes Choice.value from String to JSONValue with initializer updates.
Message component API change
Sources/SwiftDisc/Models/MessageComponents.swift
MessageComponent.TextInput.style changed from TextInput.Style enum to raw Int; initializer updated accordingly.
Package, CI, and gitignore
Package.swift, .github/workflows/docs.yml, .gitignore
Adds example exclude helpers in manifest, adds Documentation GitHub Action to build/deploy DocC docs, and adds failed/ to .gitignore.
Documentation sweep
Sources/SwiftDisc/... (many files)
Extensive DocC/SwiftDoc additions across gateway, models, high-level APIs, and internal modules; mostly comment-only changes preserving behavior except where noted above.

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant HTTPClient
  participant applyCustomHeaders
  participant DiscordAPI
  Caller->>HTTPClient: post(path, body, reason: "violation")
  HTTPClient->>applyCustomHeaders: applyCustomHeaders(headers, auditReason: reason)
  applyCustomHeaders->>applyCustomHeaders: URL-encode reason
  applyCustomHeaders-->>HTTPClient: X-Audit-Log-Reason header set
  HTTPClient->>DiscordAPI: POST with audit header
  DiscordAPI-->>HTTPClient: response
  HTTPClient-->>Caller: return response
Loading
sequenceDiagram
  participant HTTPClient
  participant BucketSemaphores
  participant AsyncSemaphore
  participant DiscordAPI
  HTTPClient->>BucketSemaphores: get(for: routeKey)
  BucketSemaphores->>AsyncSemaphore: get or create semaphore
  HTTPClient->>AsyncSemaphore: wait()
  AsyncSemaphore-->>HTTPClient: acquired permit (or await)
  HTTPClient->>DiscordAPI: execute request
  DiscordAPI-->>HTTPClient: response + X-RateLimit-Limit
  HTTPClient->>BucketSemaphores: updatePermits(limit)
  HTTPClient->>AsyncSemaphore: signal() via defer
  AsyncSemaphore-->>HTTPClient: release permit
  HTTPClient-->>HTTPClient: return response
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • M1tsumi/SwiftDisc#15: Overlapping changes to Sources/SwiftDisc/REST/HTTPClient.swift—multipart handling and route-bucketing key logic.

Suggested labels

Review effort 3/5

Poem

🐰 I threaded reasons through each call,
Semaphores stand guard to pace them all,
Emojis, polls, and stickers sing,
Docs sprout wings and examples bring,
Hopped in to ship v2.3.1—hip, hop, hooray!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive The title 'Next' is vague and does not clearly convey the specific changes in this pull request about documentation fixes and type annotation restoration. Use a more descriptive title such as 'Fix DocC documentation and restore missing type annotations' to clearly communicate the PR's primary objectives.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed Docstring coverage is 80.17% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch next

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@M1tsumi

M1tsumi commented May 12, 2026

Copy link
Copy Markdown
Owner Author

@copilot resolve the merge conflicts in this pull request

Co-authored-by: M1tsumi <88093506+M1tsumi@users.noreply.github.com>
M1tsumi added 29 commits June 13, 2026 11:38
- Fix P0 bug: EventDispatcher never called _internalEmitEvent(), so
  client.events AsyncStream was always empty. Now events flow to both
  closure callbacks and the async stream.
- Add DiscordError.rateLimited (429), .forbidden (403), .notFound (404)
  dedicated cases with proper descriptions and isTransient/isRateLimited
- Update HTTPClient.makeAPIError to map 429/403/404 to new error cases
- Add @discardableResult to all sendMessage overloads
- Set sensible default cache limits (maxUsers=50K, maxGuilds=10K, etc.)
  to prevent unbounded memory growth on large bots
- Add audit-*.md to .gitignore
Introduces HTTPTransport and WebSocketTransport protocols that let users
swap out the default URLSession networking for custom implementations.
The primary driver: URLSession on Linux/Windows does not support proxies,
but AsyncHTTPClient does.

- HTTPTransport protocol with HTTPResponse (includes response headers)
- WebSocketTransport protocol (inherits WebSocketClient + Sendable)
- URLSessionHTTPTransport / URLSessionWebSocketTransport (defaults,
  matches existing behaviour exactly)
- HTTPTransport protocol conformance for HTTPClient
- WebSocketTransport support in GatewayClient
- httpTransport / webSocketTransport properties on DiscordConfiguration
- Duplicate WebSocketClient/WebSocketMessage types consolidated into
  HTTPTransport.swift; removed redundant URLSessionWebSocketAdapter
- SwiftDiscAHCTransport: optional in-tree AsyncHTTPClient transport
  with ProxyConfiguration support
- CHANGELOG and README updated
…rUpdate DiscordEvent case, actor-isolation in EventDispatcher
…udit-reason fix

HIGH:
- DiscordClient.token backed by RedactedToken (prevents debug description leaks)
- getRaw now passes X-Audit-Log-Reason through makeRequestHeaders

MEDIUM:
- ShardingGatewayManager stores token as RedactedToken
- Fallback HTTPClient stub stores token as RedactedToken
- URLSessionTransport sanitizes CRLF in header values (prevents header injection)
- DefaultDiscordLogger uses private os_log format (%@ instead of %{public}@)
- Fix all Swift compiler warnings (var->let, unused results, spurious try)
- Update CHANGELOG 2.4.1 with security fixes and date bump
- Modernize ComponentsExample with fluent builder chaining
- Suppress unused result warnings in WebhookBot and ComponentsV2Bot
- Clean up test warnings (var->let, remove unnecessary try/await)
@M1tsumi M1tsumi merged commit d580712 into main Jun 14, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants