correspond.channels.slack#
Slack: drafts in Slack’s own formatting; reading, polling and posting with a token; posting through a webhook.
Reference: slack:<channel>, a Slack channel by its name (# optional, lower-cased;
1-80 of a-z, 0-9, - and _) or by its id (C024BE91L, kept as Slack
writes it). A post goes through the channel’s incoming webhook, which Slack ties to one
channel when it is created: $CORRESPOND_SLACK_WEBHOOK_<CHANNEL> (upper case, - as
_ and _ as __, so no two channels share a variable), else on macOS the Keychain
item correspond-slack-webhook-<channel>. The webhook URL is a credential: it never
appears in a reference, a plan or an error.
Three uses, one channel, each needing a little more set up:
A draft the operator sends.
send(..., dry_run=True)needs nothing configured: the plan holds the text rendered as Slackmrkdwn(to_mrkdwn()), its length against Slack’s limit, and says nothing was sent. That is the whole interaction when an agent writes the reply and a person pastes it, including on a workspace where no app will ever be installed.A post through a webhook. With the channel’s webhook,
sendposts the same text there, as the app that owns the webhook. A webhook cannot reply in a thread.The Web API, with a token (
CORRESPOND_SLACK_TOKEN: a bot tokenxoxb-acts as the app, a user tokenxoxp-as the person).readreturns a channel’s latest top-level messages (conversations.history),listenpolls for newer ones (the cursor is a messagets), andsendposts withchat.postMessage, replying in a thread whenreply_tois a message’sts. A channel given by name is looked up once withconversations.list. Install the app in your own workspace: Slack limits a commercially distributed app outside its Marketplace to one history request a minute.Slack.verify()checks a request Slack sends (X-Slack-Signature, with thesigning secret), graded
crypto. A GovSlack workspace setsapi_basetohttps://slack-gov.com/api(CORRESPOND_SLACK_API_BASE); the token then goes only to*.slack-gov.comhosts, Socket Mode included.
Slack’s mrkdwn is not Markdown: *bold*, _italic_, ~strike~, <url|text>
links, no headings, no tables, and &, < and > escaped. to_mrkdwn()
converts the common Markdown into it, so a draft written as GitHub Markdown does not paste
as literal asterisks and pipes. Because every < in the text is escaped, a draft cannot
carry a Slack mention (<!channel>, <@U...>) that would notify people.
Messages read are platform: Slack authenticated the account, and correspond fetched
them itself. Neither the token nor a webhook URL appears in a plan or an error.
>>> to_mrkdwn("**Fixed** in [#12](https://example.org/12) & shipped")
'*Fixed* in <https://example.org/12|#12> & shipped'
Module Attributes
Slack truncates a message's text past this many characters. |
Functions
|
Common Markdown as Slack |
|
The environment variable holding one name's webhook URL. |
Classes
|
Slack: drafts in Slack's own formatting; reading, polling and posting with a token; posting through a webhook. |
- correspond.channels.slack.MAX_TEXT_CHARS = 40000#
Slack truncates a message’s text past this many characters.
- class correspond.channels.slack.Slack(*, http=<function urllib_http>, keychain=None, run=<function run>, clock=<built-in function time>, sleep=<built-in function sleep>, events=None, socket=<function socket_window>, stamps=None)[source]#
Bases:
objectSlack: drafts in Slack’s own formatting; reading, polling and posting with a token; posting through a webhook.
- audience(ref, *, draft=None)[source]#
The channel’s members, never listed; a shared channel reaches other workspaces.
- Return type:
- property capabilities: Capabilities#
post; with nothing: a draft.
- Type:
With a token
- Type:
read, poll, post and reply in threads; with a webhook
- property events: MutableMapping[str, Any]#
What the Events API receiver stored (read when
deliveryisevents).
- parse_ref(id)[source]#
A channel’s id as Slack writes it, or its name,
#optional, lower-cased;/<thread ts>after it names one thread.- Return type:
- poll(ref, *, cursor=None, limit=None)[source]#
Top-level messages newer than
cursor, oldest first; a first poll looks back a day.Slack returns history newest first, so a poll walks forward through time windows (
oldest= the cursor’sts,latest= it plus a width, both included): each window comes back whole and is delivered before the next, and the cursor moves to its end, never past Slack’s own clock (itsDate) less a minute. A window holding a full page is narrowed, from that page’s own spread, and asked again; an empty one widens the next. The cursor is<ts>/<width>, so a poll that only narrowed or widened still moves forward, and a plaints(an older cursor) is read too. A poll stops afterMAX_REQUESTS_PER_POLLrequests, atlimitevents, or at a rate limit, keeping what it delivered: the next resumes there.
- read(ref, *, since=None, limit=None)[source]#
The channel’s latest top-level messages (
limit, else 50), oldest first; for a thread, its parent and replies.
- send(ref, draft, *, dry_run=False)[source]#
Render
draftas mrkdwn, then post it with the token (chat.postMessage), else through the channel’s webhook.draft.reply_to(a message’sts) posts in that thread with a token; through a webhook, which cannot thread, it renders in a dry run for the operator to paste.- Return type:
- property stamps: Any#
shared with the Events API receiver (
correspond.stores.arrival_stamps()).- Type:
Where a Socket Mode listen takes arrival stamps
- verify(headers, body)[source]#
cryptowhenX-Slack-Signaturematches the signing secret over a fresh timestamp,forgedotherwise.Raises
MissingRequirementwhen the signing secret is unset (a configuration error, never a verdict on the request). Anything that is not a well-formed signed request (headers that are not a mapping, a body that is not bytes, a timestamp that is not a short run of digits) is gradedforged, never raised. Replay protection is the five-minute window only: act idempotently on an event’s id.- Return type:
- correspond.channels.slack.to_mrkdwn(markdown)[source]#
Common Markdown as Slack
mrkdwn: bold, italic, strike, links, headings, lists, code, quotes, tables as code.Only
http,httpsandmailtotargets become links: a Markdown link to!channelor@U123stays text, so no draft can notify a channel or a person.>>> to_mrkdwn("# Release\n- **new**: _faster_ export\n- ~~old~~ gone") '*Release*\n• *new*: _faster_ export\n• ~old~ gone' >>> to_mrkdwn("> quoted <b> & `a < b`") '> quoted <b> & `a < b`' >>> to_mrkdwn("[ping](!channel)") 'ping (!channel)'
- Return type: