Async Slack bot scaffold for the ZATech community. Powered by Bolt for Python, FastAPI, and Socket Mode with a plugin-first architecture and built-in admin dashboard.
-
Prepare environment variables
cp .env.example .env # edit .env to add your Slack tokens (adjust DATABASE_URL if you want a different location) # For docker compose + Postgres, or without Docker (you'll need to setup a database locally) set: # DATABASE_URL=postgresql+asyncpg://postgres:postgres@db:5432/zatech_bot
-
Install dependencies (using uv is recommended, but plain
pipworks too)# using uv (recommended) uv pip install -r requirements.txt # or with pip python3 -m venv .venv source .venv/bin/activate pip install -r requirements.txt
Heads-up: Postgres support requires
asyncpgandpsycopg[binary], both included inrequirements.txt. If your environment blocks binary wheels, install them manually from a whitelisted source. -
Run the bot
uv run uvicorn app:api --reload --port 3000
or with plain
uvicornuvicorn app:api --reload --port 3000
- The process starts the FastAPI server and opens a Socket Mode connection to Slack.
- Visit
http://localhost:3000/healthfor a simple readiness probe. - SQLite persistence writes to
bot.dbby default; change or clearDATABASE_URLto point elsewhere or fall back to in-memory storage.
-
Use the dashboard
- Open
http://localhost:3000/adminto explore the admin UI. - The
autoresponderplugin manages greetings in #introductions (templated + AI) and pattern-based auto-responses, while themodlogplugin captures moderation events.
- Open
-
Try it out
- Post a message in a channel named
#introductionsand watch the bot greet the user automatically. - Visit the AutoResponder tab to customize the greeting template or add auto-response rules.
- Add a rule (e.g., pattern
(?i)\bhelp\b, response "Need assistance? Ask in #support!") and see the bot reply when users match the pattern. - Edit or delete messages to see modlog capture the events.
- (Optional) Configure the AutoResponder → Greetings tab with an OpenAI API key and pick between the gpt-5-nano or gpt-5-mini models for AI-powered personalized greetings that suggest relevant channels based on user introductions.
- Post a message in a channel named
- FastAPI runs the HTTP surface (
/slack/events,/health,/admin/*) and lifecycle hooks. Socket Mode starts/stops inside the FastAPI startup/shutdown events. - Bolt AsyncApp (Socket Mode) receives Slack events without exposing a public URL. All message handlers live in plugins.
- Dashboard registry (Jinja2 templates + shared layout) lets plugins register tabs, each with their own template namespace under
plugins/<name>/templates.
- Backed by SQLite via SQLModel by default (
DATABASE_URL=sqlite+aiosqlite:///./bot.db). Use Postgres withpostgresql+asyncpg://user:pass@host/db. Unknown backends fall back to the in-memory store. - The
StorageAPI is key/value based. Plugins must choose a unique namespace and key—for example:autorespondernamespace stores greeting settings, greeting counter, and auto-response rule objects.modlognamespace stores moderation log settings (configured channel ID).
- Because namespaces are independent, adding a new plugin only requires picking a new namespace; no schema migrations collide.
- If a plugin needs structured tables later, it can declare its own SQLModel models and use the shared engine created by
SQLiteStorage.
- Discover:
PluginManager.discover()scansPLUGIN_PACKAGESfor<pkg>/<slug>/plugin.pyexposing aBasePlugininstance. - Register:
plugin.register(context)hooks Slack listeners, event subscribers, or dashboard tabs. - Routes/UI:
plugin.register_routes(context)registers FastAPI endpoints (e.g., admin forms). - Startup/shutdown: optional async hooks for background jobs, migrations, etc.
The PluginContext bundles logging, storage, event router, Slack app, FastAPI app, and dashboard helpers so features stay decoupled.
flowchart LR
SlackEvent((Slack Event)) -->|Socket Mode| Bolt[AsyncApp]
Bolt -->|PluginManager dispatch| PluginA[Plugin A]
Bolt --> PluginB[Plugin B]
PluginA -->|use context.storage| Storage[(SQLModel/Memory)]
PluginB -->|render| Dashboard[/Admin Tabs/]
PluginA -->|publish| EventRouter
EventRouter --> PluginB
- Slack delivers an event over Socket Mode.
- Bolt resolves matching listeners (registered by plugins).
- Listener executes, optionally reading/writing plugin state through
context.storageand sending replies withsay(). - If a plugin raises internal events, it can publish to the shared
EventRouterfor other plugins to consume, enabling cross-plugin coordination.
- Create a new directory under
plugins/, add aplugin.pythat exposes aplugininstance derived fromBasePlugin. - Register Slack listeners in
register, FastAPI routes inregister_routes, and optional startup/shutdown hooks. - Provide admin UI assets by calling
context.dashboard.add_template_dir("<name>", path)and referencing templates as<name>/your_tab.html. - Use
context.storagefor per-plugin state (seeplugins/autoresponderfor an example with saved settings).
Handles greetings in #introductions (templated + AI) and pattern-based auto-responses.
- Features:
- Automatic welcome messages for new members posting in #introductions
- Configurable greeting template with
{mention}placeholder - Optional AI-powered greetings that personalise the response and suggest relevant channels
- Pattern-based auto-responses using regular expressions
- Track greeting counts in the admin dashboard (templated + AI)
- Configuration:
- Visit
/admin/tabs/autoresponderto manage pattern rules - Visit
/admin/tabs/autoresponder_greeterto configure greeter templates and AI settings
- Visit
Captures and logs moderation events like message edits and deletions.
- Features:
- Automatically logs message modifications and deletions
- Posts logs to a configured channel
- Includes user info, original content, and timestamps
- Configuration: Visit
/admin/tabs/modlogto set the logging channel
Optional environment knobs:
PLUGIN_PACKAGES— comma-separated namespaces to scan (defaults toplugins).ENABLED_PLUGINS— comma-separated plugin keys to load; leave empty to use each plugin’senabled_by_defaultflag.LOG_LEVEL— adjust logging verbosity (INFO,DEBUG, ...).DATABASE_URL— change the connection string. Supports SQLite (sqlite+aiosqlite:///path.db) and Postgres (postgresql+asyncpg://user:pass@host/db). Leave blank to disable persistence.HOST/PORT— override the default bind address/port when running via Docker.UVICORN_LOG_LEVEL— adjust the server logging inside the container.
docker compose up --buildThe provided compose stack launches:
app: the bot, exposed onhttp://localhost:3000, with automatic Socket Mode startup.db: Postgres 16, exposed onlocalhost:5432for inspection.
Checklist:
.envmust includeSLACK_BOT_TOKEN,SLACK_APP_TOKEN, andDATABASE_URL=postgresql+asyncpg://postgres:postgres@db:5432/zatech_bot.- Data is stored in the
postgres-datanamed volume (docker volume rm zatech-bot_postgres-dataresets it). - Compose healthchecks delay bot boot until Postgres passes
pg_isready.
docker build -t zatech-bot:latest .
docker run --env-file .env -p 3000:3000 zatech-bot:latestMount -v $(pwd)/bot.db:/app/bot.db for SQLite persistence or point DATABASE_URL at an external Postgres instance.
- No responses? Ensure the bot is invited to the channel/DM, tokens are correct, and the app is reinstalled after manifest tweaks. Set
LOG_LEVEL=DEBUGto inspect incoming events. - Form submissions fail? The project ships with
python-multipart; reinstall dependencies if you see “python-multipart required”. - Socket Mode shutdown warnings? The server now calls
close_async()for clean disconnects. If issues persist, stop the process and restart.
- Build moderation, logging, and channel governance plugins.
- Introduce persistent storage (SQLite/Postgres) via
core.storage. - Extend the admin UI with authentication and richer analytics.
Happy hacking! 🎉