| Home | API Documentation |
Internal: Mug · ThorsMug · ThorsSlack · NisseBolt · NisseServer · NisseHTTP · ThorsSocket · ThorsCrypto · ThorsSerializer · ThorsMongo · ThorsLogging
Detailed implementation of the Bolt-style Slack bot framework, covering the standalone server (NB) and the Mug plugin adapter (NisseBolt).
Source: third/NisseBolt/src/NB/ (standalone server) and third/NisseBolt/src/NisseBolt/ (Mug plugin)
┌──────────────────────────────────────────────────┐
│ User Bot Application │
├──────────────────────────────────────────────────┤
│ NisseBolt │
│ NBServer · App · Ack · Say · Respond · View │
├──────────────────────────────────────────────────┤
│ ThorsSlack │
│ Client · EventHandler · BlockKit │
├──────────────────────────────────────────────────┤
│ NisseHTTP · NisseServer · ThorsSocket │
└──────────────────────────────────────────────────┘
NisseBolt sits between the user’s bot logic and the ThorsSlack/Nisse stack. It provides Bolt-style handler registration (similar to Slack’s Node.js Bolt framework) while delegating protocol parsing to EventHandler and async I/O to NisseServer.
NB::NBServer)A self-contained executable. NBServer inherits from both NisseServer (event loop) and EventHandler (Slack protocol). The user constructs it with an NBConfig, registers handlers, and calls run().
NisseBolt::App)A shared library loaded by the Mug server. App inherits from MugPluginSimple and registers three HTTP routes (events, slash commands, user actions) via getAction(). Multiple bots can coexist in the same Mug process, each occupying a unique slot URL prefix.
File: NB/NBConfig.h
Serializable configuration struct. Deserialized from JSON using ThorsAnvil_MakeTrait:
| Field | Type | Default | Description |
|---|---|---|---|
port |
int |
required | Listening port (0 = test mode, no socket) |
domainName |
string |
required | Domain for TLS cert lookup |
certRoot |
string |
"" |
TLS certificate root directory |
botToken |
string |
required | Slack bot token (xoxb-...) |
signingSecret |
string |
required | HMAC signing secret |
basePath |
string |
"/slack" |
URL prefix for the four Slack routes |
Files: NB/NBServer.h, NB/NBServer.cpp
Dual-inherits NisseServer and Slack::EventHandler.
Construction sequence:
NisseServer with 4 worker threads.EventHandler with the signing secret.Client with the bot token.registerRoutes() to mount the four Slack endpoints on the internal HTTPHandler.port != 0, resolves TLS certificates and calls listen().Route registration (registerRoutes):
All routes use the same HTTPValidate callback that delegates to EventHandler::validateRequest() for HMAC signature verification.
| Route | Handler |
|---|---|
POST {basePath}/events |
EventHandler::handleEvent() |
POST {basePath}/commands |
EventHandler::handleSlashCommand() |
POST {basePath}/actions |
EventHandler::handleUserActions() |
Slash command override:
NBServer overrides EventHandler::handleSlashWithCommand() to inject the Bolt dispatch layer:
commandHandlers.false.Ack, Respond, and Context objects.errorHandler (or returns 500).| Member | Type | Key |
|---|---|---|
commandHandlers |
unordered_map<string, CommandHandler> |
command name (e.g. /deploy) |
messageListeners |
vector<MessageMatcher> |
sequential match (substring or regex) |
actionHandlers |
unordered_map<string, ActionHandler> |
action_id |
shortcutHandlers |
unordered_map<string, ShortcutHandler> |
callback_id |
viewHandlers |
unordered_map<string, ViewHandler> |
view callback_id |
optionsHandlers |
unordered_map<string, OptionsHandler> |
action_id |
middleware |
vector<Middleware> |
ordered chain |
struct MessageMatcher {
std::regex pattern;
MessageHandler handler;
bool isRegex;
std::string substring;
};
When isRegex is false, dispatch checks event.text.find(substring) != npos. When true, it uses std::regex_search.
File: NisseBolt/AppConfig.h
| Field | Description |
|---|---|
botToken |
Slack bot token |
userToken |
Slack user token |
signingSecret |
HMAC signing secret |
The slot (URL prefix) is now passed as a constructor parameter to App rather than being part of the config.
Files: NisseBolt/App.h, NisseBolt/App.cpp
Inherits from MugPluginSimple. Owns a Client, EventHandler, and handler maps for events, slash commands, actions, and views.
Construction:
slot for URL prefix routing.Client with bot and user tokens.EventHandler with the signing secret and all handler maps.addEventHandlers() to register all known Slack event types.getAction() returns three routes:
| Route | Handler |
|---|---|
POST {slot}/event |
slackHandler.handleEvent() |
POST {slot}/slash/{CommandString} |
slackHandler.handleSlashCommand() |
POST {slot}/useraction |
slackHandler.handleUserActions() |
All routes include a validation callback for HMAC signature checking.
The THORS_ANVIL_NISSE_BOLT_SERVER_INIT(Config, Server) macro exports:
extern "C" MugPlugin* mugCreateInstance(int init, char const* configStr)
This calls mugCreateBoltInstance<Server, Config>() which:
App in the static getServerInfo() map keyed by slot. Throws if slot is already occupied.App from the map (sets the unique_ptr to null).Multiple bots can coexist because each occupies a unique slot prefix.
Slack webhook POST → EventHandler::handleEvent()
→ eventHandlerMap[typeName] → App::handleEventMessage()
→ for each (filter, handler) in messageHandlers:
if filter(message):
handler(event, Say{client, Where{channel, ts}})
Slack POST /commands → EventHandler::handleSlashCommand()
→ NBServer::handleSlashWithCommand() [override]
→ middleware chain
→ commandHandlers[command.command](command, ack, respond, ctx)
using CommandHandler = function<void(SlashCommand const&, Ack&, Respond&, Context&)>;
using MessageHandler = function<void(Event::EventCallback const&, Ack&, Say&, Context&)>;
using ActionHandler = function<void(API::BlockActions const&, Ack&, Respond&, Context&)>;
using ShortcutHandler = function<void(API::BlockActions const&, Ack&, Context&)>;
using ViewHandler = function<void(API::Views::ViewSubmission const&, Ack&, Context&)>;
using OptionsHandler = function<void(API::BlockActions const&, Ack&, Context&)>;
using Middleware = function<bool(Context&)>;
using ErrorHandler = function<void(exception const&, Context&)>;
using Filter = function<bool(Event::Message const&)>;
using MessageRunner = EventRunner<Event::Message>;
using SlashCommandRunner = function<void(Ack const&, Response const&, SlashCommand const&)>;
using ActionRunner = function<void(Ack const&, Response const&, API::BlockActions const&, string const& value)>;
using ViewSubmitRunner = function<void(Ack const&, Response const&, API::Views::ViewSubmission const&)>;
using ViewClosedRunner = function<void(Ack const&, Response const&, API::Views::ViewClosed const&)>;
NisseBolt delegates all Slack protocol concerns to the ThorsSlack library:
| Concern | ThorsSlack Class |
|---|---|
| HMAC request verification | EventHandler::validateRequest() |
| Event payload parsing & dispatch | EventHandler::handleEvent() |
| Slash command parsing | EventHandler::handleSlashCommand() |
| Action/view interaction parsing | EventHandler::handleUserActions() |
| REST API calls (chat.postMessage, views.open, etc.) | Client::sendMessage() |
| Block Kit UI types | BlockKit.h |
Say wraps Client::sendMessage() to send chat.postMessage calls. It auto-populates channel and thread_ts from the originating event’s Where struct. Supports plain text and Block Kit messages.
Ack writes an HTTP response to acknowledge receipt within Slack’s 3-second window. SlashAck extends Ack to support JSON body responses with Content-Type: application/json for slash command feedback.
App::viewOpen() and App::viewPush() call Client::sendMessage() with Views::Open / Views::Push payloads and register the returned view.id in viewHandlerMap for subsequent view_submission events. viewPush() also tracks previous_view_id for view stack navigation.
src/NB/)| File | Purpose |
|---|---|
NB.cpp |
main() entry point: reads config, constructs NBServer, runs |
NBConfig.h |
NBConfig struct with ThorsSerializer traits |
NBServer.h |
NBServer class declaration with all handler types |
NBServer.cpp |
Construction, route registration, slash command dispatch, listener storage |
NBContext.h |
Per-request context (team, user, channel, client access) |
src/NisseBolt/)| File | Purpose |
|---|---|
App.h |
App class, mugCreateBoltInstance() template, THORS_ANVIL_NISSE_BOLT_SERVER_INIT macro |
App.cpp |
Construction, event handler registration, message/command/action/view dispatch |
AppConfig.h |
AppConfig struct with ThorsSerializer traits |
Runners.h |
Type aliases for all runner function signatures, AnyEventRunner variant |
Say.h / Say.cpp |
Say class: sends messages via Client |
Ack.h / Ack.cpp |
Ack and SlashAck: HTTP response acknowledgement |
Response.h |
Response placeholder struct |
View.h / View.cpp |
View class: modal display + submit/close handlers |
NisseBoltConfig.h |
Build configuration (generated by configure) |
src/ThorsSlack/)| File | Purpose |
|---|---|
Client.h |
HTTP client for Slack REST API calls |
EventHandler.h |
HMAC verification, event/command/action dispatch |
BlockKit.h |
Block Kit UI element types |
SlashCommand.h |
Slash command payload types |
API*.h |
Typed Slack API request/response structs |
Event*.h |
Typed event callback payload structs |