api

package
v0.4.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 15, 2026 License: MIT Imports: 17 Imported by: 0

Documentation

Index

Constants

View Source
const DefaultBaseURL = "https://slack.com/api"

DefaultBaseURL is the public Web API root. Method URLs are <base>/<method>.

Variables

This section is empty.

Functions

func IsCode

func IsCode(err error, code string) bool

IsCode reports whether err is an APIError with the given Slack error code.

func RedactToken

func RedactToken(token string) string

RedactToken masks a Slack token, keeping only the kind prefix (e.g. "xoxb-****").

Types

type APIError

type APIError struct {
	StatusCode int    // HTTP status (usually 200 even on failure; 429 on rate limit)
	Code       string // Slack error code, e.g. "channel_not_found"
	Warning    string // Slack's non-fatal warning, when present
	Needed     string // missing_scope: the scope the call needed
	Provided   string // missing_scope: the scopes the token actually has
	Body       string // raw body, for --verbose debugging
	Method     string // the Web API method that failed, for context
	RetryAfter int    // seconds from the Retry-After header on a 429, or 0
}

APIError is a typed Slack Web API failure. Slack answers HTTP 200 with {"ok":false,"error":"snake_case_code",...} for almost every failure — the HTTP status only matters for 429s and infrastructure errors — so the string Code is the primary key. We surface all of it and, crucially, append an actionable hint keyed by the code so the user knows the next move instead of staring at a bare "request failed" (GOAL.md §1).

func (*APIError) Error

func (e *APIError) Error() string

type Authenticator

type Authenticator interface {
	// Apply sets the credential on the request.
	Apply(req *http.Request)
	// Redacted returns the Authorization value masked, for --dry-run and logs.
	Redacted() string
	// Raw returns the Authorization value for --show-token dry-runs. Never log it elsewhere.
	Raw() string
	// ExtraHeaders returns credential headers beyond Authorization (the session cookie),
	// masked unless redact is false — so the --dry-run curl reproduces the real request.
	ExtraHeaders(redact bool) map[string]string
	// Method is the non-secret auth method name recorded in the profile.
	Method() string
}

Authenticator applies a credential to an outgoing Web API request. Slack has one primary wire scheme (an `Authorization: Bearer` header) carried by several token kinds — bot (xoxb-), user (xoxp-), app-level (xapp-, Socket Mode only) — plus the browser-session scheme (an xoxc- bearer token + a `Cookie: d=xoxd-…` header) that Slack's own web client uses. We keep the interface from the cliwright standard so redaction lives in one place and the design scales across kinds (GOAL.md §1 "auth providers").

type Bool

type Bool bool

Bool accepts a real JSON bool or the strings "true"/"false"/"1"/"0"/"yes"/"no".

func (Bool) MarshalJSON

func (b Bool) MarshalJSON() ([]byte, error)

func (*Bool) UnmarshalJSON

func (b *Bool) UnmarshalJSON(data []byte) error

type Client

type Client struct {
	DryRun    bool
	ShowToken bool
	Verbose   bool
	// contains filtered or unexported fields
}

Client is the single typed gateway to the Slack Web API. Every command goes through it, so auth, retries, rate limiting, and dry-run live in exactly one place (GOAL.md §2).

func New

func New(auth Authenticator, opts ...Option) *Client

New builds a Client for the given authenticator. Sensible defaults are applied; override with Options.

func (*Client) AuthTest

func (c *Client) AuthTest(ctx context.Context) (*Identity, error)

AuthTest verifies the token against auth.test and returns who it belongs to. It is the verification step behind `auth login`, `auth status`, and `doctor`.

func (*Client) BaseURL

func (c *Client) BaseURL() string

BaseURL returns the configured base URL.

func (*Client) Call

func (c *Client) Call(ctx context.Context, method string, params map[string]any, idempotent bool) (json.RawMessage, error)

Call invokes a Web API method and returns the raw response body (the full envelope, since Slack nests payloads as siblings of ok). idempotent marks read methods: they are sent as GET with query params — which also lets the retry policy safely replay them — while writes go as a form-encoded POST and are never auto-retried except on 429 (rejected, not processed). In dry-run mode it prints the equivalent curl and returns (nil, nil).

func (*Client) CallAllPages

func (c *Client) CallAllPages(ctx context.Context, method string, params map[string]any, resultKey string, max int) (json.RawMessage, error)

CallAllPages follows Slack's cursor pagination (response_metadata.next_cursor; empty string = done) and returns the pages' resultKey arrays merged into a single JSON array. max caps the total items collected; max <= 0 means all. The per-request limit is set by the caller via params["limit"] (Slack recommends 100-200).

func (*Client) CallInto

func (c *Client) CallInto(ctx context.Context, method string, params map[string]any, idempotent bool, out any) error

CallInto is Call plus a JSON decode of the response into out.

func (*Client) Close added in v0.3.0

func (c *Client) Close() error

Close releases resources an attached Recorder holds (e.g. the store's SQLite handle) if it implements io.Closer. Safe to defer unconditionally: a nil or non-Closer recorder is a no-op. This matters on Windows, where an open handle blocks deleting/renaming the file.

func (*Client) DialHeaders

func (c *Client) DialHeaders() http.Header

DialHeaders returns the credential headers a WebSocket handshake must carry beyond the URL ticket. For a browser-session credential this is the `Cookie: d=<xoxd>` header, which the RTM gateway re-validates on the WebSocket UPGRADE (not just on the rtm.connect HTTP call) — without it the gateway answers invalid_auth. For a plain token it is empty (the URL ticket authenticates), so this is safe to apply for every transport.

func (*Client) FetchAuthed added in v0.2.0

func (c *Client) FetchAuthed(ctx context.Context, url string, w io.Writer) (int64, error)

FetchAuthed streams a Slack-hosted private URL (url_private / url_private_download) to w, applying the credential. Slack file URLs require the same Authorization header as the API.

func (*Client) OpenRTMURL

func (c *Client) OpenRTMURL(ctx context.Context) (string, error)

OpenRTMURL asks rtm.connect for a fresh Real Time Messaging wss:// URL. RTM works with a user/session token (xoxc+xoxd) — a browser web-client credential — so it backs `slackctl listen` when no app-level token is available. RTM is a legacy API and is not officially supported for xoxc tokens; a workspace may return method_deprecated or not_allowed_token_type, which the caller surfaces with its hint. URLs are single-use and must be connected within ~30s, so callers fetch one per (re)connection.

func (*Client) OpenSocketURL

func (c *Client) OpenSocketURL(ctx context.Context) (string, error)

OpenSocketURL asks apps.connections.open for a fresh Socket Mode wss:// URL. It requires an app-level token (xapp-, scope connections:write); Slack's URLs are single-use tickets, so callers fetch one per (re)connection attempt.

func (*Client) UploadFile added in v0.2.0

func (c *Client) UploadFile(ctx context.Context, path string, opts UploadOptions) (*UploadResult, error)

UploadFile runs Slack's three-step external upload flow (files.upload was sunset Nov 2025):

  1. files.getUploadURLExternal — reserve an upload URL + file id for (filename, length);
  2. POST the file bytes to that URL (multipart, field "file");
  3. files.completeUploadExternal — finalize and share the file.

It returns the completed file id. path must be a readable regular file.

type ID

type ID string

ID unmarshals from a JSON string OR number and always marshals as a string. Telegram chat/user/message IDs are int64 that overflow JS's 2^53 safe integer, so we never let them round-trip through float64 and we render them consistently in tables.

func (ID) MarshalJSON

func (id ID) MarshalJSON() ([]byte, error)

MarshalJSON always emits a quoted string so tables and JSON output are stable.

func (ID) String

func (id ID) String() string

func (*ID) UnmarshalJSON

func (id *ID) UnmarshalJSON(b []byte) error

UnmarshalJSON accepts `123`, `"123"`, or null.

type Identity

type Identity struct {
	URL          string `json:"url"`
	Team         string `json:"team"`
	User         string `json:"user"`
	TeamID       string `json:"team_id"`
	UserID       string `json:"user_id"`
	BotID        string `json:"bot_id,omitempty"`
	EnterpriseID string `json:"enterprise_id,omitempty"`
}

Identity is auth.test's answer — the whoami payload. A bot token adds bot_id; Enterprise Grid orgs add enterprise_id.

type Int

type Int int64

Int accepts a JSON number or a numeric string and stores an exact int64. It decodes the integer form before any float path so values above 2^53 keep full precision, and it rejects NaN/Inf and non-integer numbers.

func (Int) Int64

func (n Int) Int64() int64

func (Int) MarshalJSON

func (n Int) MarshalJSON() ([]byte, error)

func (*Int) UnmarshalJSON

func (n *Int) UnmarshalJSON(b []byte) error

type Option

type Option func(*Client)

Option configures a Client.

func WithBaseURL

func WithBaseURL(u string) Option

func WithDryRun

func WithDryRun(v bool) Option

func WithDryRunWriter

func WithDryRunWriter(w io.Writer) Option

func WithHTTPClient

func WithHTTPClient(h *http.Client) Option

func WithRPS

func WithRPS(rps float64) Option

func WithRecorder added in v0.3.0

func WithRecorder(r Recorder) Option

WithRecorder attaches an observer notified after every successful, non-dry-run call — the local message-history hook. internal/api stays generic (it knows nothing about SQLite) by depending only on this narrow interface.

func WithRetryPolicy

func WithRetryPolicy(p retryPolicy) Option

func WithShowToken

func WithShowToken(v bool) Option

func WithVerbose

func WithVerbose(v bool) Option

type Recorder added in v0.3.0

type Recorder interface {
	Record(ctx context.Context, method string, params map[string]any, result json.RawMessage)
}

Recorder observes successful Web API calls so a caller can persist a local message history (the `slackctl log` store) without internal/api depending on internal/store — the client stays a generic HTTP core (GOAL.md §2). Record is called once per successful, non-dry-run call with the same context the command passed in.

Record must not block the caller on its own failures: an implementation filters to the methods it cares about and swallows/logs its own errors — a broken store must never fail a command.

type SessionAuth

type SessionAuth struct {
	Token  string // xoxc-…
	Cookie string // the d cookie value, xoxd-…
}

SessionAuth authenticates as a real Slack user with browser-session credentials: an xoxc- token in the Authorization header plus the paired xoxd- cookie. This is the scheme Slack's web client uses; an xoxc token WITHOUT its cookie is rejected by Slack with invalid_auth. It carries the user's own identity, so it satisfies user-token-only methods (search.*, stars.*) too.

func NewSessionAuth

func NewSessionAuth(token, cookie string) (*SessionAuth, error)

NewSessionAuth validates the pair's shape: both halves are required.

func (*SessionAuth) Apply

func (a *SessionAuth) Apply(req *http.Request)

func (*SessionAuth) ExtraHeaders

func (a *SessionAuth) ExtraHeaders(redact bool) map[string]string

func (*SessionAuth) Method

func (a *SessionAuth) Method() string

Method reports "session-token": user-grade identity from a browser session.

func (*SessionAuth) Raw

func (a *SessionAuth) Raw() string

func (*SessionAuth) Redacted

func (a *SessionAuth) Redacted() string

type TokenAuth

type TokenAuth struct {
	Token string
}

TokenAuth authenticates with a Slack token via the Authorization header.

func NewTokenAuth

func NewTokenAuth(token string) (*TokenAuth, error)

NewTokenAuth validates the token shape loosely: Slack tokens start with "xox" (xoxb-, xoxp-, xoxe.xoxp-, ...) or "xapp-". Reject only the obviously wrong, not unknown future prefixes.

func (*TokenAuth) Apply

func (a *TokenAuth) Apply(req *http.Request)

func (*TokenAuth) ExtraHeaders

func (a *TokenAuth) ExtraHeaders(bool) map[string]string

ExtraHeaders is empty for plain tokens — the bearer header is the whole credential.

func (*TokenAuth) Method

func (a *TokenAuth) Method() string

Method reports the token kind from its prefix ("bot-token", "user-token", "app-token").

func (*TokenAuth) Raw

func (a *TokenAuth) Raw() string

func (*TokenAuth) Redacted

func (a *TokenAuth) Redacted() string

Redacted keeps the token-kind prefix — it is not secret and answers the debugging question "was this the bot or the user token?" — and masks the rest.

type UploadOptions added in v0.2.0

type UploadOptions struct {
	Filename       string // defaults to the base name of the path
	Title          string
	Channels       string // comma-separated channel ids to share into
	InitialComment string
	ThreadTS       string
	SnippetType    string // for code snippets (e.g. "text", "python")
}

UploadOptions carries the sharing parameters for a file upload.

type UploadResult added in v0.2.0

type UploadResult struct {
	FileID string `json:"file_id"`
	Raw    json.RawMessage
}

UploadResult is the outcome of a completed external file upload.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL