Pixel art desktop mascot that surfaces Slack DMs and @mentions as animated on-screen notifications
Slack Buddy is a single-file Python app that places a small pixel art robot in the bottom-right corner of your screen. It connects to Slack via Socket Mode and watches for direct messages and @mentions. When one arrives, the robot switches to an alert sprite, wiggles, and floats a speech bubble with the message preview — no window-switching required.
The entire UI is built on Tkinter (Python's standard library GUI toolkit), which means there's no Electron, no Chromium, and no native framework beyond Python itself.
- Two animation states — an idle sprite (
sprite.svg) used during normal operation and an alert sprite (sprite_alert.svg) shown when a notification is active. SVGs are rendered to RGBA PIL images at startup using CairoSVG so they scale to any DPI without blurriness. - Idle bob animation — continuous gentle vertical bobbing driven by a
tkinter.afterloop (BOB_INTERVAL = 80 ms, amplitude 6 px). The mascot alternates direction each time it reaches the amplitude boundary, creating a smooth floating effect. - Alert wiggle animation — on notification arrival the mascot switches to the alert sprite and performs 8 steps of random-offset jitter (
±4 pxhorizontal,±2 pxvertical) overWIGGLE_DURATION = 600 ms. Each step is scheduled withroot.afterso it doesn't block the Tkinter event loop. - Speech bubble overlay — a rounded rectangle drawn directly on the Canvas using a polygon with
smooth=True. The bubble includes a downward-pointing tail triangle and renders word-wrapped text up to 32 characters per line. Platform-specific fonts are used automatically: SF Pro on macOS, Segoe UI on Windows/Linux. - Auto-dismiss — the speech bubble and alert state clear automatically after
ALERT_DURATION = 8000 ms(8 seconds), returning the mascot to the idle bob. - Notification queue — notifications arriving while an alert is already active are queued in memory and displayed sequentially. The queue is drained via a 500 ms
root.afterpolling loop (_check_queue), ensuring no messages are dropped during a burst. - Frameless, decoration-free window —
overrideredirect(True)removes the title bar. The window floats above all other windows via-topmost Truewith no taskbar entry. - Transparent background — uses
-transparentcolor blackon Windows and asystemTransparentbackground fallback on macOS so the black canvas background disappears, leaving only the sprite and bubble visible. - Always on top — the Tk window is created with
-topmost True, so the buddy remains visible over full-screen apps and always-on-top windows on all three platforms. - Draggable — left-click anywhere on the canvas to start dragging; the window repositions in real time as the cursor moves. The drag origin is captured on
ButtonPress-1and the delta applied onB1-Motionwith no movement threshold. - Right-click to quit — both
ButtonPress-2(middle) andButtonPress-3(right) callquit(), which callsroot.quit()thenroot.destroy()to exit cleanly. - Slack Socket Mode listener — runs in a background
threading.Thread(daemonized) so it never blocks the Tkinter main loop. Handles three event types:messagewithchannel_type: im(direct messages),messagecontaining the bot's user ID (channel @mentions), andapp_mentionevents. Responds to each Socket Mode request with an immediateSocketModeResponseacknowledgement before processing. - Bot echo suppression — events from the bot's own user ID or carrying a
bot_idfield are silently ignored to prevent notification loops. - Display name resolution — sender user IDs are resolved to real names via
users.infoand cached in a dict for the session lifetime, avoiding repeated API calls. - Channel name resolution — for channel @mention notifications, the channel name is resolved via
conversations.infoand displayed as#channel-name. - Message truncation — previews are capped at 80 characters with a
…suffix. The bot's own user ID is replaced with@youin mention text before display. - Demo mode — run with
--demoto skip all Slack configuration and cycle through five realistic fake notifications at random intervals between 6 and 12 seconds. Useful for testing sprite rendering, animations, and bubble layout without a Slack workspace. - Config template auto-generation — if
config.jsonis missing at launch, the app writes a filled-in template and exits with a clear error message, so users always know exactly what file to edit.
| Layer | Technology | Version |
|---|---|---|
| Language | Python | 3.10+ |
| GUI framework | Tkinter | stdlib |
| SVG rendering | CairoSVG | 2.7+ |
| Image handling | Pillow | 10.0+ |
| Slack connectivity | slack-sdk (Socket Mode) | 3.27+ |
| WebSocket transport | websocket-client | 1.7+ |
Platform notes:
- macOS — Cairo must be installed separately:
brew install cairo. The Pythoncairosvgwheel links against it dynamically. - Windows — Cairo is bundled inside the
cairosvgwheel on most Windows configurations. If you see a DLL error, install the GTK+ runtime. - Linux — install Cairo via your package manager:
sudo apt install libcairo2(Debian/Ubuntu) orsudo dnf install cairo(Fedora).
config.json
│
▼
load_config() ─── missing/template → exit with instructions
│
▼
SlackBuddy.__init__()
├── Tk window (frameless, transparent, always-on-top)
├── Canvas with sprite image item
├── _animate_bob() ─── root.after(80ms) loop — vertical bob
└── _check_queue() ─── root.after(500ms) loop — drain notification queue
start_slack_listener() ─── daemon thread
├── WebClient(bot_token) ─── auth_test() → bot_user_id
└── SocketModeClient(app_token)
│
▼ on each event
handle_event()
├── SocketModeResponse(ack) ── immediate acknowledgement
├── filter bot messages
├── DM? → buddy.notify("💬 DM from {name}:\"{preview}\"")
├── @mention? → buddy.notify("📢 #{channel} — {name}:\"{preview}\"")
└── app_mention? → buddy.notify(...)
buddy.notify(message)
└── notification_queue.append(message)
_check_queue() (every 500ms)
└── if queue and not alerting → _trigger_alert(message)
├── _set_sprite(alert)
├── _show_bubble(message)
├── _wiggle(steps=8)
└── root.after(8000, _end_alert)
└── _set_sprite(idle) + _hide_bubble()
The Slack listener runs in a background daemon thread. It calls buddy.notify() which is thread-safe because it only appends to a Python list; the Tkinter loop drains that list on the main thread via root.after, keeping all widget operations on the GUI thread.
- Python 3.10+
- pip
- A Slack workspace where you can create and install apps
- Cairo (for SVG rendering — see platform notes above)
brew install cairopip install -r requirements.txtOr install manually:
pip install "slack-sdk>=3.27.0" "websocket-client>=1.7.0" "cairosvg>=2.7.0" "Pillow>=10.0.0"python3 slack_buddy.py --demoThis cycles through five pre-written fake notifications at random 6–12 second intervals so you can verify the sprite, animations, and bubble layout before connecting to Slack.
python3 slack_buddy.pyOn first run with no config.json, the app writes a template file and exits:
config.json has been created at: /path/to/slack-buddy/config.json
Edit the file with your tokens, then run again.
Slack Buddy reads config.json from the project root. Both fields are required.
{
"SLACK_BOT_TOKEN": "xoxb-your-bot-token-here",
"SLACK_APP_TOKEN": "xapp-your-app-token-here"
}| Field | Type | Description |
|---|---|---|
SLACK_BOT_TOKEN |
string | Bot User OAuth Token from your Slack app's OAuth & Permissions page. Starts with xoxb-. |
SLACK_APP_TOKEN |
string | App-Level Token with the connections:write scope, generated from your app's Basic Information / Socket Mode page. Starts with xapp-. |
Do not commit config.json — it contains live Slack tokens. It is already listed in .gitignore.
-
Go to api.slack.com/apps and click Create New App → From scratch. Name it (e.g., "Slack Buddy") and select your workspace.
-
Under Settings → Socket Mode, toggle Socket Mode on. When prompted to generate an App-Level Token, name it anything and add the scope
connections:write. Copy the token — it starts withxapp-1-. This is yourSLACK_APP_TOKEN. -
Under Features → OAuth & Permissions → Bot Token Scopes, add:
Scope Required for im:historyReading direct messages im:readListing DM channels channels:historyReading channel messages (for @mention text) groups:historyReading private channel messages users:readResolving sender display names via users.infochannels:readResolving channel names via conversations.infogroups:readResolving private channel names -
Under Features → Event Subscriptions, enable events and subscribe to these Bot Events:
Event Trigger message.imDirect messages to the bot app_mention@mentions of the bot in any channel message.channelsChannel messages (needed to catch @mentions via messagetype)message.groupsPrivate channel messages -
Click Install to Workspace (or Reinstall if already installed) and authorize. Copy the Bot User OAuth Token from the OAuth & Permissions page — it starts with
xoxb-. This is yourSLACK_BOT_TOKEN. -
Paste both tokens into
config.jsonand runpython3 slack_buddy.py. -
To receive @mention notifications in a channel, invite the bot:
/invite @YourBotName.
| Action | How |
|---|---|
| Move the buddy | Left-click and drag |
| Quit | Right-click (or middle-click) the buddy |
Run with the --demo flag to test the app without any Slack credentials:
python3 slack_buddy.py --demoDemo mode skips load_config() entirely and starts a background thread that calls buddy.notify() with one of five pre-written messages every 6–12 seconds (randomized). It exercises the full animation and speech bubble pipeline:
"💬 DM from Sarah:\n\"Hey, got a minute?\""
"📢 #engineering — Alex:\n\"@you Can you review this PR?\""
"💬 DM from Jordan:\n\"The deploy looks good 🚀\""
"📢 #general — Pat:\n\"@you lunch today?\""
"💬 DM from Morgan:\n\"Quick question about the API...\""
Right-click the buddy to quit as usual.
slack-buddy/
├── slack_buddy.py # Entire application — ~350 lines
│ # SlackBuddy class (Tkinter window + animations)
│ # start_slack_listener() (background thread)
│ # run_demo() (fake notification loop)
│ # main() (entry point)
├── config.json # Slack tokens — DO NOT COMMIT
├── requirements.txt # Python dependencies
└── assets/
├── sprite.svg # Idle pixel art robot sprite
└── sprite_alert.svg # Alert pixel art robot sprite (eyes wide, antenna lit)
MIT