diff --git a/CHANGELOG.md b/CHANGELOG.md index ca646d8..2ac3861 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,17 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## [Unreleased] -_No unreleased changes at this time._ +### Changed + +- **`README.md`** — Expanded the rate-limiting section to name + `maxRateLimitRetries` explicitly, reworded the opening paragraph to + call out the zero-dependency WebSocket client and panic-recovered + dispatcher, added a dedicated "Vendored builds" subsection under + Installation, and extended the Security section with the + `maxFramePayload` guard and the `url.PathEscape` reaction-path fix. +- **`rest.go`** — Fixed the stale file-header comment that claimed 429 + responses were "retried once"; the actual behaviour (3 retries, + bounded by `maxRateLimitRetries`) is now documented in the header. --- diff --git a/README.md b/README.md index 54f8fc4..1d83ed6 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ [![CI](https://github.com/hilleywyn/godiscord/actions/workflows/ci.yml/badge.svg)](https://github.com/hilleywyn/godiscord/actions/workflows/ci.yml) [![License](https://img.shields.io/badge/license-MIT-green)](LICENSE) -GoDiscord implements [Discord Gateway v10](https://discord.com/developers/docs/topics/gateway) and the Discord REST API v10 using only the Go standard library — no `github.com/gorilla/websocket`, no `github.com/bwmarrin/discordgo`, no external packages at all. +GoDiscord implements [Discord Gateway v10](https://discord.com/developers/docs/topics/gateway) and the Discord REST API v10 using only the Go standard library — no `github.com/gorilla/websocket`, no `github.com/bwmarrin/discordgo`, no external packages at all. It ships its own RFC 6455 WebSocket client and a typed event dispatcher, and every handler runs under panic recovery so one misbehaving callback can't take the bot down. --- @@ -64,7 +64,22 @@ See [`example/basic/`](example/basic/) for a runnable starter bot and [`example/ go get github.com/hilleywyn/godiscord ``` -GoDiscord requires **Go 1.21 or later**. +GoDiscord requires **Go 1.21 or later**. There are no transitive +dependencies: after `go get`, `go.sum` lists only GoDiscord itself. + +### Vendored builds + +For self-contained deployments (e.g. scratch Docker images), vendor +the module and build with `-mod=vendor`: + +```bash +GOWORK=off go mod tidy +GOWORK=off go mod vendor +go build -mod=vendor ./... +``` + +The `GOWORK=off` disables workspace mode so a sibling `go.work` file +doesn't pull in live-dev paths during vendoring. --- @@ -258,8 +273,9 @@ modPerms := discord.Permission(0).Add( GoDiscord handles `429 Too Many Requests` responses automatically. When Discord returns a rate-limit response the client reads the `Retry-After` header, sleeps for the indicated duration, and retries the request. Retries are capped at -**3 attempts per call**; if the budget is exhausted a `*APIError` with -`StatusCode == 429` is returned so you can decide how to proceed. +**3 attempts per call** (`maxRateLimitRetries`); if the budget is exhausted a +`*APIError` with `StatusCode == 429` is returned so you can decide how to +proceed. ```go var apiErr *discord.APIError @@ -268,6 +284,9 @@ if errors.As(err, &apiErr) && apiErr.IsRateLimit() { } ``` +The retry budget applies per-request, not per-process, so an occasional +429 on one endpoint doesn't starve the next call. + ## Error Handling REST calls return `*APIError` on failure, which can be inspected with `errors.As`: @@ -356,7 +375,8 @@ intent** (`GuildMembers`, `GuildPresences`, `MessageContent`) is declared in code but not enabled in the Developer Portal. Go to [Discord Developer Portal](https://discord.com/developers/applications) → your application → **Bot** → **Privileged Gateway Intents** and enable the -intents your bot requests. +intents your bot requests. GoDiscord surfaces the 4014 close code in the +gateway log so it's straightforward to recognise in traces. ### Messages are received but `m.Content` is always empty @@ -391,10 +411,16 @@ GoDiscord is a framework library. Its security posture: - **No SQL, no shell execution** — there is no injection surface beyond what bot code introduces itself. - **TLS only** — the Gateway and REST client connect exclusively over TLS. -- **Bounded allocation** — WebSocket frame payloads are capped at 64 MiB to - prevent memory-exhaustion attacks from a compromised gateway connection. -- **Bounded retries** — Rate-limit retries are capped to prevent infinite - recursion from a non-compliant server. +- **Bounded allocation** — WebSocket frame payloads are capped at 64 MiB + (`maxFramePayload`) to prevent memory-exhaustion attacks from a + compromised gateway connection. Negative payload lengths (8-byte length + field with its high bit set) are rejected before allocation. +- **Bounded retries** — Rate-limit retries are capped at + `maxRateLimitRetries` to prevent infinite recursion from a non-compliant + server. +- **Path-safe REST** — `AddReaction` and `RemoveReaction` pass the emoji + parameter through `url.PathEscape`, blocking path-injection via a + crafted emoji string. - **Token isolation** — The bot token is stored in an unexported field and never logged; it appears only in `Authorization` headers. diff --git a/rest.go b/rest.go index d446273..95990dc 100644 --- a/rest.go +++ b/rest.go @@ -7,7 +7,9 @@ package discord // - Failed requests return *APIError with the HTTP status and Discord JSON // error code, so callers can branch with errors.As(). // - 429 Too Many Requests is handled transparently: the client sleeps for -// Retry-After seconds and retries once. +// Retry-After seconds and retries up to maxRateLimitRetries times, then +// returns an *APIError with StatusCode == 429 once the budget is +// exhausted so callers can back off at a higher level. // - All public methods have descriptive godoc comments. import (