Documentation
¶
Overview ¶
Package slack implements the Slack adapter for the orb chat gateway: Events API ingress over HTTP (v0 request signing, url_verification handshake, echo filtering), Web API delivery with a bot token (streamed preview edits via chat.update, mrkdwn finalization with chunking), and authenticated file download.
The adapter speaks the Events API webhook, not Socket Mode, so it needs a public endpoint plus the app's signing secret. Slack has no typing indicator for bot messages: the streamed preview message is the activity signal and Typing is a no-op.
Index ¶
- Constants
- func ChunkText(text string, limit int) []string
- func FormatText(markdown string) string
- type APIError
- type Adapter
- func (a *Adapter) Account() string
- func (a *Adapter) Download(ctx context.Context, ref chat.AttachmentRef) (io.ReadCloser, string, error)
- func (a *Adapter) NewDelivery(key chat.ConversationKey, replyTo, resumePreviewID string) chat.Delivery
- func (a *Adapter) Platform() string
- func (a *Adapter) Webhook(publish func(chat.Message) error) http.Handler
- type Options
Constants ¶
const DefaultBaseURL = "https://slack.com"
DefaultBaseURL is the production Web API endpoint.
Variables ¶
This section is empty.
Functions ¶
func ChunkText ¶
ChunkText splits text into chunks of at most limit characters, at line boundaries where possible (long lines split at spaces, then hard). Code fences are chunk-aware: a split inside a fence closes it with ``` and reopens ``` at the start of the next chunk. Empty input yields no chunks.
func FormatText ¶
FormatText converts standard markdown to Slack mrkdwn: `**b**`/`__b__` → `*b*`, `*i*` → `_i_`, `~~s~~` → `~s~`, `[t](u)` → `<u|t>`, headings → `*bold*` lines, `- ` bullets → `• `, with `&`, `<`, `>` escaped in body text before any `<...>` construct is inserted. Fenced code blocks keep their triple-backtick markers (language tokens are dropped — mrkdwn would display them literally); fence content is escaped but otherwise untouched.
ponytail: line/regex transform, not a goldmark AST walk; inline-code contents are transformed like normal text, and spaced single asterisks can over-italicize — accepted ceilings.
Types ¶
type APIError ¶
type APIError struct {
Method string
Status int
Code string
// RetryAfter is the server-requested pause (Retry-After header on 429,
// a default 1s on a body-level "ratelimited"), zero otherwise.
RetryAfter time.Duration
}
APIError is a decoded Web API failure: either an HTTP 429 or an ok:false envelope. Code carries the Slack error token ("ratelimited", "edit_window_closed", ...).
type Adapter ¶
type Adapter struct {
// contains filtered or unexported fields
}
Adapter is the Slack implementation of chat.Adapter.
func New ¶
New builds the adapter. It refuses to construct without Token and SigningSecret. When Options.BotUserID is empty, the bot identity is resolved (and cached for the adapter's lifetime) via one auth.test call — the id drives echo filtering, so the adapter cannot run without it.
func (*Adapter) Account ¶
Account returns the bot user id (auth.test user_id), matching the Account set on every normalized message.
func (*Adapter) Download ¶
func (a *Adapter) Download(ctx context.Context, ref chat.AttachmentRef) (io.ReadCloser, string, error)
Download implements chat.Adapter: the attachment id is the file's url_private_download, fetched with the Bearer token (kept across the slack.com → files.slack.com redirect). The MIME type is the one carried on the ref (the download response is generic).
func (*Adapter) NewDelivery ¶
func (a *Adapter) NewDelivery(key chat.ConversationKey, replyTo, resumePreviewID string) chat.Delivery
NewDelivery implements chat.Adapter. The conversation ThreadID (thread_ts or "" for top-level DM replies) threads every outbound message; a non-empty resumePreviewID makes Finalize edit that message instead of posting a new one (crash recovery).
func (*Adapter) Webhook ¶
Webhook returns the Events API ingress handler. Every request — including url_verification — is authenticated by the v0 signature over the raw body (constant-time compare, replay window enforced), 401 on failure. Events are normalized and published synchronously, then acked: publish is a durable-enqueue seam and must never wait on turn processing (Slack requires a 2xx within 3 seconds). A publish failure answers 500 so Slack redelivers; the "sl:<channel>:<ts>" EventID dedupes downstream — the same key also collapses the app_mention/message.channels double delivery of one mention (their event_ids differ, channel+ts do not).
type Options ¶
type Options struct {
// Token is the bot token (xoxb-...). Required.
Token string
// SigningSecret verifies inbound Events API requests (v0 signature).
// Required: unsigned webhooks are not an option.
SigningSecret string
// BaseURL overrides the Web API endpoint (tests). Default
// https://slack.com.
BaseURL string
// BotUserID pre-seeds the bot user identity used for echo filtering and
// mention gating; resolved via auth.test in [New] when empty.
BotUserID string
// HTTPClient overrides the HTTP client. Default: 30s timeout.
HTTPClient *http.Client
// PreviewMinInterval rate-limits chat.update preview edits per channel.
// Default 1s (chat.update is Tier 3, ~1/sec sustained).
PreviewMinInterval time.Duration
// ReplayWindow bounds |now - X-Slack-Request-Timestamp| on inbound
// events. Default 5m (Slack's documented replay window).
ReplayWindow time.Duration
// Logger receives non-fatal diagnostics. Optional.
Logger *slog.Logger
}
Options configures New.