Skip to main content

Module slack

Module slack 

Source
Expand description

The Slack Web API client: one uniform POST per method, form params, Bearer token, and the envelope rules that turn a logical failure into an error.

Slack answers {"ok": false, "error": "..."} with HTTP 200 for a logical failure, so a 200 is not success. Only a 429 arrives as a status, and it carries Retry-After.

Re-exportsยง

use std::collections::HashMap; ๐Ÿ”’
use std::collections::HashSet; ๐Ÿ”’
use std::hash::BuildHasher; ๐Ÿ”’
use std::sync::Arc; ๐Ÿ”’
use std::time::Duration; ๐Ÿ”’
use tokio::sync::oneshot; ๐Ÿ”’
use forge_primitives::slack::SlackBookmark; ๐Ÿ”’
use forge_primitives::slack::SlackConversation; ๐Ÿ”’
use forge_primitives::slack::SlackFile; ๐Ÿ”’
use forge_primitives::slack::SlackFollowedThread; ๐Ÿ”’
use forge_primitives::slack::SlackMessage; ๐Ÿ”’
use forge_primitives::slack::SlackPin; ๐Ÿ”’
use forge_primitives::slack::SlackSearchMatch; ๐Ÿ”’
use forge_primitives::slack::SlackSubscription; ๐Ÿ”’
use forge_primitives::slack::SlackSubscriptionTarget; ๐Ÿ”’
use forge_primitives::slack::SlackThreadOwner; ๐Ÿ”’
use forge_primitives::slack::SlackUser; ๐Ÿ”’
use forge_primitives::slack::SlackWatchMode; ๐Ÿ”’
use serde::Deserialize; ๐Ÿ”’
use serde::Deserialize; ๐Ÿ”’
use serde::de::DeserializeOwned; ๐Ÿ”’

Modulesยง

testing
The pumpโ€™s throttle reporter, for another crateโ€™s log-filter test.

Structsยง

AuthTest
auth.test - who the token belongs to.
BotProfile ๐Ÿ”’
A bot messageโ€™s own profile. name is the appโ€™s name, which is the only display name a bot message carries.
ConversationsPage ๐Ÿ”’
One page with the cursor normalised: Slack sends an empty string on the last page, which would otherwise read as a cursor to page from.
Envelope ๐Ÿ”’
Slack wraps every payload in ok, and reports logical failures with HTTP 200.
MessagePage
One page of a conversationโ€™s messages. Both conversations.history and conversations.replies answer with this shape.
Metadata ๐Ÿ”’
OwnIdentity ๐Ÿ”’
The workspaceโ€™s own identity, read from auth.test at most once a sweep.
PostedMessage
What chat.postMessage answers with. channel is the conversation the message actually landed in: a request addressed by a user id or a channel name does not spell the id the sweep reads it under.
RawConversationsPage ๐Ÿ”’
One page of users.conversations as Slack sends it.
RawEdited ๐Ÿ”’
The wireโ€™s edited object. Only the editโ€™s own ts is decision-bearing: whose edit it was changes nothing the sweep decides.
RawMessagePage ๐Ÿ”’
RawWireMessage ๐Ÿ”’
SearchPage
One page of search.messages, newest first.
SlackClient
Debug is hand-written because the token must never be printed.
SlackHistoryMessage
One message off conversations.history or conversations.replies.
SweepOutcome ๐Ÿ”’
What one sweep did.
ThrottleEpisode ๐Ÿ”’
Reports throttling once per episode: warn on entry, debug inside it, info when the first clean pass ends it.
UsersPage ๐Ÿ”’
One page of users.list, with Slackโ€™s empty last-page cursor normalised the way the conversations page is.

Enumsยง

SlackError
SweepScope ๐Ÿ”’
Which conversations one pass reads. The DM class covers every DM rather than a list, so it costs one history read per DM however many subscriptions exist - which is why it is swept on its own clock, and why a pass reads only what it is for.

Constantsยง

API_ROOT ๐Ÿ”’
BODY_PREFIX_LEN ๐Ÿ”’
Bounded so a gatewayโ€™s error page cannot flood the log or the tool output.
CONVERSATION_TYPES ๐Ÿ”’
types must be passed explicitly or Slack returns public channels only, and limit must be passed or the call gets a stricter cap.
FULL_THREAD_READ_INTERVAL ๐Ÿ”’
How often a participating thread is read whole. The per-tick walk is cheap - it re-reads from the newest reply - so this is the only pass that finds a correction to a reply below that one, and reading every long thread whole on every tick would spend the sweepโ€™s rate budget.
MAX_CONVERSATION_PAGES ๐Ÿ”’
A cursor Slack hands back unchanged must not spin the walk forever: at 200 per page this is far past any real workspace, and the failure mode is an endless request loop against a rate-limited API.
MAX_FULL_THREAD_READS_PER_SWEEP ๐Ÿ”’
Whole-thread reads one conversation may spend per sweep. Each pages through its thread, so the budget keeps one long thread from using calls the rest of the sweep needs.
MAX_HISTORY_PAGES ๐Ÿ”’
A cursor Slack hands back unchanged must not spin the history walk forever, the same guard the conversation walk carries.
MAX_REPLY_PAGES ๐Ÿ”’
A cursor Slack hands back unchanged must not spin the reply walk forever, the same guard the conversation walk carries.
MAX_SEARCH_PAGES ๐Ÿ”’
A cursor Slack hands back unchanged must not spin the mention walk forever, the same guard the other walks carry.
MAX_USER_PAGES ๐Ÿ”’
A cursor Slack hands back unchanged must not spin the user walk forever: at 200 per page this is far past any real workspace, and the failure mode is an endless request loop against a rate-limited API.
MENTION_CURSOR
The watermark key for a workspaceโ€™s whole mention stream. One cursor, not one per conversation: both sides share this constant so a typo cannot silently reset it and re-deliver everything.
MENTION_SEARCH_COUNT ๐Ÿ”’
Mentions fetched per search. The workspaceโ€™s newest, since the query is timestamp-sorted.
POST_LIMIT
The largest text chat.postMessage carries without truncating. The probe measured a silent cut at 4000 with ok: true, so a longer draft is split and posted in sequence rather than quietly halved.
POST_PART_BUDGET ๐Ÿ”’
Budget per part once a draft is split, leaving room for the [i/n] prefix each part carries.
REQUEST_TIMEOUT
How long one Web API call gets. The pump waits this long, and every Web API request carries it, so a post that stalls cannot pin its in-flight mark past it. File bodies are not Web API calls and keep their own transfer behaviour.
RETRY_CAP ๐Ÿ”’
Ceiling for a hostile or broken Retry-After.
RETRY_FALLBACK ๐Ÿ”’
Used when the response carries no Retry-After.
RETRY_FLOOR ๐Ÿ”’
Floor for a Retry-After that asks for no wait at all. Retry-After: 0 is legal, and retrying at once is the opposite of what it asked.
SEARCH_COUNT_CAP ๐Ÿ”’
The APIโ€™s ceiling for one search page.
SWEEP_LIMIT ๐Ÿ”’
Messages fetched per conversations.history call.
USER_PAGE_LIMIT ๐Ÿ”’
Slackโ€™s page ceiling for users.list.

Traitsยง

SlackApi
The Web API calls this connector makes, behind a trait so a sweep or an outbound call can be driven from a test double instead of a live workspace.
SlackHost
What the pump needs from the workspace. The connector holds no state of its own, so the subscription set, the watermarks and the liveness flag all live behind this.
UsersPages ๐Ÿ”’
One page of users.list. The walk is split from the HTTP client so its page cap is reachable from a double that never stops handing back a cursor.

Functionsยง

author_of ๐Ÿ”’
The name to show for a messageโ€™s author, asked once per user per conversation, or None when no name is known - the block drops the clause rather than printing a raw id.
body_prefix ๐Ÿ”’
covers ๐Ÿ”’
Whether one subscription watches this conversation at all, whatever its mode and whoever wrote the message.
decode_bookmarks ๐Ÿ”’
Split out of the async path so the decode is testable without HTTP.
decode_conversations_page ๐Ÿ”’
Split out of the async path so the paging rules are testable without HTTP.
decode_envelope ๐Ÿ”’
Split out of call so the envelope rules are testable without HTTP.
decode_file ๐Ÿ”’
Split out of the async path so the decode is testable without HTTP.
decode_message_page ๐Ÿ”’
Split out of the async path so the decode is testable without HTTP.
decode_pins ๐Ÿ”’
Split out of the async path so the decode is testable without HTTP.
decode_search ๐Ÿ”’
Split out of the async path so the decode is testable without HTTP.
decode_user ๐Ÿ”’
Split out of the async path so the decode is testable without HTTP.
decode_users_page ๐Ÿ”’
Split out of the async path so the decode is testable without HTTP.
encode_form ๐Ÿ”’
application/x-www-form-urlencoded body for one call.
fetch_history ๐Ÿ”’
Every history message after each page boundary, following the cursor. One page is SWEEP_LIMIT messages, and a sweep that read only the first would advance the watermark past everything it never fetched, losing those messages silently.
fetch_recent ๐Ÿ”’
The conversationโ€™s newest page, and only that page. Not fetch_history: walking back from a cursor would page through the channelโ€™s whole past, and this is a window over what is recent rather than a read position.
fetch_replies ๐Ÿ”’
Every reply in a thread, following the cursor. Dedupe is the callerโ€™s: Slack repeats the parent on every page, so the caller takes the newest by comparing ts rather than by taking the last one it read.
first_bold_closer ๐Ÿ”’
The first * after open able to close a span opened there: non-space before it, no word character after it, and no ability to open a span of its own - otherwise the two stars of a pair of globs would close a span between them.
flatten_mrkdwn
The prose a message reads as when a card quotes it: Slackโ€™s mrkdwn is flattened - a mention is @name from names (the bare id when it cannot be named), a link its label or address, and *text* its text. A token this does not understand is left exactly as written. Flattening stops once limit non-space characters have been emitted, which is enough for the excerpt bound; a single long token still scans the text after the bound.
flatten_token ๐Ÿ”’
One <...> token flattened. A mention takes its name from names, a link its label or address; a channel reference, a broadcast and any token this does not recognise keep their own text.
followed_parent_of ๐Ÿ”’
The thread a delivered message anchors: its own thread when it is a reply, the thread it starts when it is a parent that already has replies, and nothing for a plain top-level message - or every message would grow the tracked set.
history_params ๐Ÿ”’
The params conversations.history is called with. Slack calls the newest bound latest, and both bounds are exclusive.
in_scope ๐Ÿ”’
Whether this pass is for this conversation. targets has already said the conversation is watched by someone; this narrows that to the clock the sweep is running on.
is_markup_token ๐Ÿ”’
Whether <token> is one of Slackโ€™s markup tokens rather than text that happens to sit between two brackets.
is_newer ๐Ÿ”’
Whether ts is strictly after watermark. Timestamps are compared as the strings Slack sent: they carry a microsecond fraction, and parsing one to a number loses it and silently moves the cursor.
is_news ๐Ÿ”’
Whether a message carries news the cursor has not passed: the message is newer than the cursor, or it was edited after the cursor. An edit keeps the messageโ€™s own ts, so the ts test alone drops every correction.
is_thread_parent ๐Ÿ”’
Whether a message is a threadโ€™s parent, which is what decides whose replies the sweep fetches. Slack sends a parent with thread_ts equal to its own ts; thread_ts being present therefore says nothing about whether a message is a reply, and a parent is instead the message that reports replies and is its own thread root.
is_user_id ๐Ÿ”’
Whether a token is a Slack user id rather than some other <@...> text.
just_below ๐Ÿ”’
The exclusive read position just below ts.
matches ๐Ÿ”’
Whether one subscription wants this message. forge posts as the user, so a message is refused by identity - its ts in the outbound ledger - and never by author: refusing every message the user wrote drops what Ved types himself, and a thread he is in then reads one-sided.
mention_ids
Every user id a messageโ€™s text tags, once each, in order of appearance. Slack writes a mention as <@U...>, optionally carrying a display label after a |; a channel reference and a link start with a different sigil and are not people.
mentions
Whether text carries a mention of user_id.
newest_stamp ๐Ÿ”’
The newest cursor value a message owns: its ts, or the later ts of its newest edit. A cursor advanced over a handed edit has to clear the edit too, or the next sweep re-offers the message forever and, once the delivery memory lapses, redispatches it.
next_interval ๐Ÿ”’
The wait before the next sweep: never shorter than the configured interval, and at least the clamped Retry-After when one was reported.
one_line_excerpt
One flattened line of a messageโ€™s text, for a card that quotes it: the whitespace collapses, and a line past limit characters stops at the last space before the bound where there is one, marked with an ellipsis.
opens_bold ๐Ÿ”’
A * opens a bold span only at a word boundary, with text after it and no doubled marker either side - the ** of a literal and the * of a glob are literals rather than openers. The rule mirrors strip_emphasis in forge-serverโ€™s envelope.rs; a change there should move here.
own_name ๐Ÿ”’
The name the tokenโ€™s own user goes by. A failed auth.test falls back to the name the host already learned; None only when neither is known, and the caller then uses whatever the wire offered.
owns_threads ๐Ÿ”’
Whether this subscription wants a threadโ€™s replies. Watching a conversation is not the same as wanting everything in it: a mentions-mode subscription asked to be told when it is named, and it is told through the match, so it owns no thread - or every thread the sweep anchors would deliver every reply to a session that asked for mentions. A Mentions target is swept by search and owns no conversation at all.
page_from_raw ๐Ÿ”’
participating ๐Ÿ”’
Whether a thread is one the user is in, read from its replies: his own message, or one that names him.
read_thread
Every reply in a thread, the parent first, for a caller reading the thread rather than advancing a cursor. Slack repeats the parent at the top of every page, so the result is deduped by ts; the first occurrence wins, which keeps the parent where the contract puts it. limit bounds the rows returned, and the walk stops as soon as it has them.
resolve_author ๐Ÿ”’
Resolves one author outright, without the per-conversation memo author_of keeps.
retry_after_of ๐Ÿ”’
Slackโ€™s whole-second Retry-After, when the response carries one.
retry_delay ๐Ÿ”’
What to wait after a 429. Slack sends whole seconds.
run_dm_pump ๐Ÿ”’
Sweep the DM class on its own task and its own clock, until shutdown fires. The first pass runs at once - a restart has a gap to catch up on - and every one after it waits the interval.
run_workspace_pump
Sweep one workspace forever, until shutdown fires. The first sweep is delayed by the interval so a boot with many workspaces does not burst.
search_params ๐Ÿ”’
The params search.messages is always called with. sort=timestamp because the default is relevance, which during the probe ranked a 22-hour-old match above a 2.6-hour-old one.
shared_messages
Every message a file was shared as, as (conversation, ts). The conversation is the shareโ€™s own - the channel Slack posted into, which need not be the string the upload was addressed with. Both the uploadโ€™s answer and files.info carry these when Slack fills them.
split_for_post
Split text into parts each under POST_LIMIT, numbered so a reader can see the order. Text at or under the limit is one part.
status_failure ๐Ÿ”’
A non-success status other than 429. The body may be an HTML error page, which the envelope decoder would report as a JSON parse failure
sweep_mentions ๐Ÿ”’
Sweep the workspaceโ€™s mention stream. Returns at once when no Mentions target exists, so a workspace with only conversation subscriptions never spends a search call on every tick.
sweep_scope ๐Ÿ”’
One pass over the conversations in scope: fetch what is newer than each conversationโ€™s watermark, follow the threads of the parents that report replies, and hand every survivor to the host.
targets ๐Ÿ”’
Whether any subscription names this conversation, ignoring the mode and the author. Decides which conversations a sweep fetches history for: a mentions-only subscription still has to look, or it would never see a mention arrive.
thread_parent
The parent message of the thread ts starts, which is the message a draftโ€™s card names. An error is the callerโ€™s to draw around: the card names the thread โ€œa threadโ€ rather than inventing a name.
user_display_name
The display name for a user id, from the hostโ€™s cache or a users.info lookup that is then remembered. None when the id cannot be named - the caller draws the id rather than a guess. A failure that is not settled is not remembered, so a later ask tries again.
walk_users ๐Ÿ”’
Every member in the workspace, following the cursor. A cursor that never ends returns what was read, the way the history and reply walks do, rather than failing the whole lookup.
wants
Whether any of subscriptions wants this message.