Build a Python tool that detects shouty Slack messages and runs them through /v1/whisper before display — for the colleague who treats Caps Lock as a personality trait.
Use this file to discover all available pages before exploring further.
Some people send every Slack message in ALL CAPS!!!! This walkthrough builds
a small Python tool that detects those messages, runs them through
/v1/whisper to lowercase them, and emits a calmer
version. You can drop the result into a chat display, an internal feed, or a
late-night digest where everyone is too tired for shouting.Two paths are covered:
Batch mode. Read a Slack export (or any list of messages) from JSON,
rewrite the shouty ones, write the result back. Runnable without any Slack
credentials.
Live mode. A Slack Bolt app that listens to channel messages and posts
a calmed reply in-thread when shoutiness crosses a threshold.
[ {"user": "U01ALEX", "ts": "1714060800.000100", "text": "GOOD MORNING TEAM!!! WHO IS READY TO SHIP?!?!"}, {"user": "U02JANE", "ts": "1714060860.000200", "text": "morning! coffee first, ship second"}, {"user": "U01ALEX", "ts": "1714060920.000300", "text": "I AM ALREADY ON MY THIRD CUP LET'S GOOOOO"}, {"user": "U03SAM", "ts": "1714061040.000400", "text": "the staging deploy is green, fyi"}]
The tool produces quiet.json:
[ {"user": "U01ALEX", "ts": "1714060800.000100", "text": "good morning team!!! who is ready to ship?!?!", "calmed": true}, {"user": "U02JANE", "ts": "1714060860.000200", "text": "morning! coffee first, ship second", "calmed": false}, {"user": "U01ALEX", "ts": "1714060920.000300", "text": "i am already on my third cup let's gooooo", "calmed": true}, {"user": "U03SAM", "ts": "1714061040.000400", "text": "the staging deploy is green, fyi", "calmed": false}]
Lowercase messages pass through unchanged. Only the screamers get whispered.
Before calling the API, decide which messages deserve a whisper. A simple
heuristic combines two signals: how much of the message is uppercase, and
how many exclamation marks it carries.
shoutiness.py
import stringdef uppercase_ratio(text: str) -> float: letters = [c for c in text if c in string.ascii_letters] if not letters: return 0.0 upper = sum(1 for c in letters if c.isupper()) return upper / len(letters)def exclamation_density(text: str) -> float: if not text: return 0.0 return text.count("!") / len(text)def is_shouty(text: str, *, min_chars: int = 8) -> bool: if len(text) < min_chars: return False # Single words are allowed to be loud. return uppercase_ratio(text) > 0.6 or exclamation_density(text) > 0.05
The thresholds are deliberately conservative. A message is only flagged as
shouty if more than 60% of its letters are uppercase or if exclamation
marks make up more than 5% of the text. Tune them to taste — a more
permissive 0.4 ratio also catches mixed-case yelling like “Why Are We
Doing This Again.”A quick sanity check:
For production code, fold in retry handling for 429 and 5xx as shown in
the errors reference. For this walkthrough, the bare call keeps
the focus on the use case.
The shouty messages now read at conversational volume. Original timestamps,
user IDs, and any other fields you carry through are preserved untouched —
only text is rewritten and a calmed boolean is added so downstream code
can highlight or count rewrites.
Skip this section if batch processing is enough. For real-time calming, use
Slack Bolt for Python with Socket Mode so
you do not need a public URL.
quiet_bot.py
import osfrom slack_bolt import Appfrom slack_bolt.adapter.socket_mode import SocketModeHandlerfrom shoutiness import is_shoutyfrom whisperer import whisperapp = App(token=os.environ["SLACK_BOT_TOKEN"])@app.event("message")def calm_if_needed(event, client): if event.get("subtype") or event.get("bot_id"): return # Ignore edits, joins, and the bot's own posts. text = event.get("text", "") if not is_shouty(text): return client.chat_postMessage( channel=event["channel"], thread_ts=event["ts"], text=f"_at conversational volume:_\n> {whisper(text)}", )if __name__ == "__main__": SocketModeHandler(app, os.environ["SLACK_APP_TOKEN"]).start()
A few choices worth calling out:
Reply in-thread, do not edit. Slack’s API only lets a bot edit messages
it posted itself. Posting a calmed reply in-thread keeps the original
message intact and lets the author see the contrast.
Skip subtypes and bot messages. Channel joins, edits, and the bot’s
own posts arrive as the same message event. Filtering them prevents
loops and noise.
Italics for the prefix._at conversational volume:_ reads as a
gentle aside in Slack, not a callout.
Required scopes for the bot: chat:write, channels:history,
groups:history, im:history, mpim:history, plus the corresponding
message.* event subscriptions. Install the app to a test channel before
turning it loose on #general.
Auto-rewriting other people’s messages — even as a thread reply — can
feel passive-aggressive. Pilot the bot in an opt-in channel first, and
consider a slash command (/whisper) as a less invasive alternative.
Before turning the bot on, run the heuristic against a varied corpus to
make sure the threshold is tuned for your team. The included fixture
covers the usual cases:
smoke.py
import jsonfrom pathlib import Pathfrom shoutiness import is_shoutymessages = json.loads(Path("messages.json").read_text())for m in messages: flag = "SHOUTY" if is_shouty(m["text"]) else "ok" print(f"{flag:>6} {m['text']}")
Expected output on the sample fixture:
SHOUTY GOOD MORNING TEAM!!! WHO IS READY TO SHIP?!?! ok morning! coffee first, ship secondSHOUTY I AM ALREADY ON MY THIRD CUP LET'S GOOOOO ok the staging deploy is green, fyi
Adjust thresholds in shoutiness.py until your own corpus produces
sensible flags before connecting to a live workspace.
Heuristic before API call. A cheap local check (is_shouty) keeps
most messages out of the request path entirely. Only the loud ones cost
a quota slot.
Preserve metadata, mutate only what you must. Carrying user, ts,
and any other fields through unchanged makes the calmed output a
drop-in replacement for the original.
Reply, do not rewrite. Posting a thread reply respects authorship
and Slack’s API permissions.
Pilot before you scale. Auto-rewriting tone is a social intervention
as much as a technical one. Start in a small, consenting channel.