Conversation
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.
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds per-route semaphores, threads optional audit ChangesHTTP Client and REST API Enhancements
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
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested labels
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 inconclusive)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
|
@copilot resolve the merge conflicts in this pull request |
Co-authored-by: M1tsumi <88093506+M1tsumi@users.noreply.github.com>
- 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
…ut param, use withUnsafeReadableBytes)
…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)
Summary by CodeRabbit
New Features
Improvements
Chores