Documentation
¶
Overview ¶
Package messaging posts team announcements — to Slack with a rotating user token or through an incoming webhook, and to Teams, Discord or a plain webhook through theirs — and asks whether a Slack user token works. A webhook has no equivalent question — see ErrWebhookUncheckable.
Index ¶
- Constants
- Variables
- func RenderMarkdown(kind config.MessagingKind, text string) string
- type Announcement
- type Client
- func (c Client) AuthTest(ctx context.Context) (Identity, error)
- func (c Client) ChannelID(ctx context.Context, name string) (string, error)
- func (c Client) ChannelMembers(ctx context.Context, channelID string) ([]SlackUserID, error)
- func (c Client) Post(ctx context.Context, channel, text string) error
- func (c Client) User(ctx context.Context, user SlackUserID) (SlackTarget, error)
- func (c Client) UserGroups(ctx context.Context) ([]SlackTarget, error)
- func (c Client) Users(ctx context.Context) ([]SlackTarget, error)
- func (c Client) WithToken(source TokenSource) Client
- type Doer
- type Grant
- type Identity
- type Length
- type Mentions
- type MissingScopeError
- type Moment
- type PostRefusedError
- type SlackGroupID
- type SlackTarget
- type SlackUserID
- type TokenSource
Constants ¶
const APIBase = "https://slack.com/api"
APIBase is where the Slack Web API lives. Unlike Jira, it is the same for everyone, so it is a constant rather than a setting — but it stays a parameter of New so a test can point the client somewhere it controls.
const MaxPages = 100
MaxPages is the most pages one directory read follows. A cursor that never runs out — a workspace past it, or a server that loops — ends the read rather than the session's patience.
const UserListPages = 20
UserListPages is the most pages Users reads before calling the workspace too large to list whole. users.list is Slack's Tier 2, about 20 requests a minute, so a workspace past 20 pages — 4,000 people at 200 a page — would meet the rate limit rather than the page cap; its channels are better labeled member by member through users.info, Tier 4 at 100 or more a minute and bounded by the channel's size.
Variables ¶
var ( // ErrMissingScope reports a token without the scope a read needs; a // MissingScopeError carrying the scope's name wraps it. ErrMissingScope = errors.New("the Slack token lacks a scope") // ErrNoUserGroups reports a workspace or token that has no user groups to // read: a free workspace, or a token Slack will not answer them for. ErrNoUserGroups = errors.New("this Slack workspace has no user groups") // ErrLookupRefused reports a directory read Slack would not answer, for a // reason that is neither the token nor its scopes. ErrLookupRefused = errors.New("the lookup was refused") // ErrChannelNotFound reports a channel name none of the user's channels has. ErrChannelNotFound = errors.New("no channel of yours has that name") // ErrDirectoryTooLarge reports a read still paging after its page cap: // MaxPages, or UserListPages for Users. ErrDirectoryTooLarge = errors.New("the Slack directory is too large to read") // ErrNotTaggable reports a user who is not a person to tag: deactivated, // a bot, or Slackbot. ErrNotTaggable = errors.New("not a Slack user who can be tagged") // ErrInvalidSlackID reports a value that is not a Slack user or group ID. ErrInvalidSlackID = errors.New("not a Slack ID") )
Errors the directory reads return, beside the ones every Slack call shares.
var ( // ErrNoCredential reports that messaging has no credential to post with: // neither transport is set, or the user token has no source to give one. ErrNoCredential = errors.New("messaging has no credential") // ErrWebhookUncheckable reports that a webhook cannot be verified. ErrWebhookUncheckable = errors.New("an incoming webhook cannot be checked without posting with it") // ErrRejected reports a credential the service would not accept: Slack's no // to a token, or a webhook a service answered a post to with a 401, a 403, // a 404, or one of Slack's codes for a webhook that no longer works. ErrRejected = errors.New("the credential was not accepted") // ErrTokenExpired reports a user token Slack called expired after the // source was asked for a newer one, which it answers as ErrRejected too. ErrTokenExpired = errors.New("the Slack user token has expired") // ErrPostRefused reports a message the service would not deliver, for a // reason that is not about the credential: Slack's for its channel, or any // other 4xx a webhook answers a post with, such as a message too long. Its // words name no verb, because an announcement and the Summary both post // through here. ErrPostRefused = errors.New("the message was refused") // ErrUnexpectedStatus reports a response status the API does not document. ErrUnexpectedStatus = errors.New("unexpected response status") // ErrUnreachable reports a request that never got an answer. ErrUnreachable = errors.New("could not reach the messaging service") // ErrNoWorkspace reports an auth.test answer naming no Slack workspace, or // one not shaped like a workspace's ID. ErrNoWorkspace = errors.New("auth.test named no Slack workspace for the token") )
Errors this package returns. Callers distinguish them with errors.Is. A refused redirect and a rate limit come back as httpx's ErrRedirected and ErrRateLimited, which every client shares.
var ErrInsecureWebhook = errors.New("messaging.webhook_url is not an https URL")
ErrInsecureWebhook reports a webhook that is not an https URL. The URL is the credential, and it is not sent in the clear.
var ErrTooLong = errors.New("too long")
ErrTooLong reports a message longer than its service takes.
Functions ¶
func RenderMarkdown ¶ added in v0.7.0
func RenderMarkdown(kind config.MessagingKind, text string) string
RenderMarkdown is Markdown text — the Summary as it is copied — as kind shows it: Slack's mrkdwn has no headings or lists, so a heading is a bold line and an item a bullet, every line escaped as an announcement's values are; a Teams webhook's text shows no heading, so it is bold there too; Discord shows three levels and the rest are bold; and a plain webhook, which reads no markup, has a heading's words alone. An empty or unknown kind renders for Slack.
Types ¶
type Announcement ¶
type Announcement struct {
Author string
PullRequestURL string
PullRequestTitle string
IssueKey string
IssueSummary string
IssueURL string
// Noun is what the forge calls the change — "pull request" or "merge
// request". Empty defaults to "pull request".
Noun string
// Moment is what the message marks: ready for review, or merged. The zero
// value is ready for review.
Moment Moment
// Kind is the service the message is rendered for: it decides the link markup
// — Slack's <url|text>, Markdown's [text](url), or a bare URL — and the
// escaping. Empty renders for Slack.
Kind config.MessagingKind
// Template shapes the ready-for-review Slack message from named placeholders —
// {author}, {noun}, {title}, {url}, {key}, {summary}, {issue_url} — for a team
// with a house style. Empty, for any other moment, or for a non-Slack kind,
// uses the built-in text.
Template string
}
Announcement is the message telling a channel where a change stands.
func (Announcement) Text ¶
func (a Announcement) Text() string
Text is the announcement rendered for its kind. Every substituted value is escaped for the kind's markup — Slack mrkdwn, or Markdown for Teams and Discord — and a plain webhook, which reads no markup, takes it as written: a title is anyone's to write, and unescaped, "<!channel>" in one pings a whole Slack channel, and a ">" ends a link early.
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client talks to a messaging service — Slack's Web API, or an incoming webhook for Slack, Teams, Discord or a plain endpoint.
func (Client) ChannelID ¶ added in v0.5.0
ChannelID is the ID of the channel the user is in by name, with or without its leading "#". A value already shaped like a channel ID is the answer itself, and nothing is asked.
func (Client) ChannelMembers ¶ added in v0.5.0
ChannelMembers is the user ID of everyone in the channel, leaving out any value Slack sent that is not shaped like one.
func (Client) Post ¶
Post sends text to the configured messaging service: to channel when it is a Slack user-token post naming one, or to the configured default otherwise. A webhook carries its own channel, so channel does not apply to it.
func (Client) User ¶ added in v0.5.0
func (c Client) User(ctx context.Context, user SlackUserID) (SlackTarget, error)
User is one person by their ID, labeled as Users labels them. Someone Users would leave out — deactivated, a bot, Slackbot — is ErrNotTaggable.
func (Client) UserGroups ¶ added in v0.5.0
func (c Client) UserGroups(ctx context.Context) ([]SlackTarget, error)
UserGroups is every enabled user group in the workspace, labeled by its handle, or its name when it has none. A workspace without user groups is ErrNoUserGroups.
func (Client) Users ¶ added in v0.5.0
func (c Client) Users(ctx context.Context) ([]SlackTarget, error)
Users is every person in the workspace who can be tagged, labeled by the name Slack shows for them. Deactivated users, bots and Slackbot are left out. A workspace past UserListPages pages is ErrDirectoryTooLarge.
func (Client) WithToken ¶ added in v0.5.0
func (c Client) WithToken(source TokenSource) Client
WithToken is the client asking source for the Slack user token on every call, since a rotating token can expire between two of them.
type Grant ¶ added in v0.5.0
type Grant struct {
// contains filtered or unexported fields
}
Grant is the scopes Slack granted a token, as auth.test lists them.
type Identity ¶
type Identity struct {
// OK is Slack's verdict. It is the field that matters: the Web API answers
// a dead token with HTTP 200 and ok:false, so the status line lies.
OK bool `json:"ok"`
Error string `json:"error"`
User string `json:"user"`
Team string `json:"team"`
// TeamID is the workspace's ID, which, unlike its name, never changes.
TeamID string `json:"team_id"`
// Granted is the scopes Slack lists the token as granted.
Granted Grant `json:"-"`
}
Identity is the workspace and user a token belongs to.
type Length ¶ added in v0.7.0
Length is how long a message is as its service measures it, and the most the service takes: Limit is 0 for a plain webhook, which names none.
func MessageLength ¶ added in v0.7.0
func MessageLength(kind config.MessagingKind, rendered string) Length
MessageLength is rendered — a message as RenderMarkdown leaves it for kind — measured as kind's service measures it: Teams by the bytes of the whole payload, as its limit is put; every other service by characters.
type Mentions ¶ added in v0.5.0
type Mentions struct {
Users []SlackUserID
Groups []SlackGroupID
}
Mentions is who an announcement tags: users and user groups, by validated ID alone. Its IDs come only from ParseSlackUser and ParseSlackGroup, which is what lets Line place them in markup without escaping.
func NewMentions ¶ added in v0.5.0
NewMentions validates every user and group ID, refusing the lot with ErrInvalidSlackID when any one is not shaped like its kind.
func (Mentions) Line ¶ added in v0.5.0
Line is the Slack line that tags everyone once — "cc <@U…> <!subteam^S…>" — or nothing when there is no one to tag. It carries no labels: Slack shows each tag by its current name, and a label is anyone's to write, so it has no place inside the markup. It is appended after the escaped announcement text, never passed through slackEscape, which would break the tags.
type MissingScopeError ¶ added in v0.5.0
type MissingScopeError struct {
// Needed is Slack's own name for the scope, such as "usergroups:read".
Needed string
}
MissingScopeError is Slack's missing_scope, naming the scope it needs.
func (*MissingScopeError) Error ¶ added in v0.5.0
func (e *MissingScopeError) Error() string
Error names the scope the token lacks.
func (*MissingScopeError) Unwrap ¶ added in v0.5.0
func (e *MissingScopeError) Unwrap() error
Unwrap makes every MissingScopeError an ErrMissingScope to errors.Is.
type PostRefusedError ¶ added in v0.8.0
type PostRefusedError struct {
Reason string
}
PostRefusedError is a message the service would not deliver, with the reason: from Slack, the fix for its channel, or Slack's own code for one this does not explain; from a webhook, the service's own words, cut short, with the webhook masked. The reason never names the webhook, so every surface can show it.
func (PostRefusedError) Error ¶ added in v0.8.0
func (e PostRefusedError) Error() string
Error says the message was refused, and why.
func (PostRefusedError) Unwrap ¶ added in v0.8.0
func (PostRefusedError) Unwrap() error
Unwrap lets errors.Is match ErrPostRefused.
type SlackGroupID ¶ added in v0.5.0
type SlackGroupID struct {
// contains filtered or unexported fields
}
SlackGroupID is a validated Slack user group ID. It is made only by ParseSlackGroup, so one placed in a mention cannot carry markup.
func ParseSlackGroup ¶ added in v0.5.0
func ParseSlackGroup(raw string) (SlackGroupID, error)
ParseSlackGroup accepts a user group ID: S, then two or more of A–Z and 0–9.
func (SlackGroupID) String ¶ added in v0.5.0
func (g SlackGroupID) String() string
String is the ID itself.
type SlackTarget ¶ added in v0.5.0
SlackTarget is someone a message can tag: a user or a user group, by its ID and the name to show for it.
type SlackUserID ¶ added in v0.5.0
type SlackUserID struct {
// contains filtered or unexported fields
}
SlackUserID is a validated Slack user ID. It is made only by ParseSlackUser, so one placed in a mention cannot carry markup.
func ParseSlackUser ¶ added in v0.5.0
func ParseSlackUser(raw string) (SlackUserID, error)
ParseSlackUser accepts a user ID: U or W, then two or more of A–Z and 0–9.
func (SlackUserID) String ¶ added in v0.5.0
func (u SlackUserID) String() string
String is the ID itself.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package directory reads the Slack directory a session tags people from: a channel's members, the workspace's users and user groups, and which workspace the token is for, each read once however many ask at a time and held for a while, waiting out Slack's rate limits within its patience.
|
Package directory reads the Slack directory a session tags people from: a channel's members, the workspace's users and user groups, and which workspace the token is for, each read once however many ask at a time and held for a while, waiting out Slack's rate limits within its patience. |