Skip to content

Latest commit

 

History

History
401 lines (342 loc) · 16.3 KB

File metadata and controls

401 lines (342 loc) · 16.3 KB

SlackServer Architecture

🏗️ System Overview

┌─────────────────────────────────────────────────────────────────┐
│                        Slack Workspace                           │
│                                                                   │
│  User: "@MyBot what is the weather?"                            │
└────────────────────────┬────────────────────────────────────────┘
                         │ Socket Mode (WebSocket)
                         │
┌────────────────────────▼────────────────────────────────────────┐
│                     SlackServer (.NET)                           │
│                                                                   │
│  ┌──────────────────────────────────────────────────────────┐  │
│  │ SlackListenerService                                      │  │
│  │  • Receives Slack events via Socket Mode                 │  │
│  │  • Matches messages against bot patterns                 │  │
│  │  • Finds matching bots                                    │  │
│  └────────────┬─────────────────────────────────────────────┘  │
│               │                                                  │
│  ┌────────────▼─────────────────────────────────────────────┐  │
│  │ BotExecutor                                              │  │
│  │  • Spawns bot process (pwsh/python/dotnet/etc)         │  │
│  │  • Sets environment variables (channel, user, message) │  │
│  │  • Streams stdout line-by-line                          │  │
│  │  • Captures stderr for logging                          │  │
│  └────────────┬─────────────────────────────────────────────┘  │
│               │ JSON Lines                                      │
│  ┌────────────▼─────────────────────────────────────────────┐  │
│  │ ActionProcessor                                          │  │
│  │  • Parses JSON from bot stdout                           │  │
│  │  • Validates action types                                │  │
│  │  • Calls Slack API                                       │  │
│  │  • Stores message references (for updates)              │  │
│  └────────────┬─────────────────────────────────────────────┘  │
│               │                                                  │
│  ┌────────────▼─────────────────────────────────────────────┐  │
│  │ MessageReferenceStore                                    │  │
│  │  • msg1 → ts:1234567890.123456                          │  │
│  │  • progress → ts:1234567891.234567                       │  │
│  └──────────────────────────────────────────────────────────┘  │
│                                                                   │
└────────────────────────┬────────────────────────────────────────┘
                         │ Slack Web API
                         │
        ┌────────────────┼────────────────┐
        │                │                │
┌───────▼──────┐  ┌─────▼──────┐  ┌─────▼──────┐
│ PowerShell   │  │  Python    │  │  C# CLI    │
│ Bot Script   │  │  Bot       │  │  Tool      │
│              │  │            │  │            │
│ Outputs JSON │  │ Prints JSON│  │ WriteLine  │
└──────────────┘  └────────────┘  └────────────┘

📊 Message Flow

1. Slack User sends message:
   "@MyBot what is the weather?"

2. Slack sends app_mention event via Socket Mode
   ↓
3. SlackListenerService receives event:
   {
     channel: "C01234",
     user: "U56789",
     text: "@MyBot what is the weather?",
     ts: "1234567890.123456"
   }

4. Match against bot patterns:
   ✅ "Question Bot" matches: <@\w+>\s+.*

5. BotExecutor starts process:
   pwsh ./scripts/question-bot.ps1

   Environment:
   - TRIGGER_MESSAGE="@MyBot what is the weather?"
   - TRIGGER_CHANNEL="C01234"
   - TRIGGER_TS="1234567890.123456"
   - TRIGGER_USER="U56789"
   - TRIGGER_USER_NAME="john_doe"

6. Bot script processes and outputs JSON:
   {"action":"post_message","channel":"C01234","text":"Checking...","store_as":"msg1"}
   {"action":"update_message","channel":"C01234","message_ref":"msg1","text":"Weather is sunny!"}

7. ActionProcessor parses each line:
   Line 1: Post message → Store ts as "msg1"
   Line 2: Update message → Get ts from "msg1" → Update

8. Slack API calls:
   POST /chat.postMessage
   POST /chat.update

9. User sees in Slack:
   "Weather is sunny!"

🔄 Bot Execution Lifecycle

┌─────────────────────────────────────────────────────────────┐
│ 1. Message Received                                          │
│    • Parse event data                                        │
│    • Extract: channel, user, text, timestamp                │
└────────────┬────────────────────────────────────────────────┘
             │
┌────────────▼────────────────────────────────────────────────┐
│ 2. Pattern Matching                                          │
│    • Loop through all bots                                   │
│    • Check bot.type (channel/bot_dm/all)                    │
│    • Test bot.messagePattern regex                           │
│    • Collect matching bots                                   │
└────────────┬────────────────────────────────────────────────┘
             │
┌────────────▼────────────────────────────────────────────────┐
│ 3. Bot Execution (for each matching bot)                    │
│    • Create MessageReferenceStore instance                   │
│    • Create ActionProcessor instance                         │
│    • Create BotExecutor instance                             │
│    • Start process with environment variables                │
└────────────┬────────────────────────────────────────────────┘
             │
┌────────────▼────────────────────────────────────────────────┐
│ 4. Stream Processing                                         │
│    • Read stdout line-by-line                                │
│    • For each line:                                          │
│      - Parse JSON                                            │
│      - Validate action                                       │
│      - Execute Slack API call                                │
│      - Store/retrieve message references                     │
│    • Capture stderr for logging                              │
└────────────┬────────────────────────────────────────────────┘
             │
┌────────────▼────────────────────────────────────────────────┐
│ 5. Completion                                                │
│    • Wait for process exit                                   │
│    • Log execution summary:                                  │
│      - Exit code                                             │
│      - Actions processed count                               │
│      - Elapsed time                                          │
│      - Timeout status                                        │
│    • Clean up resources                                      │
└─────────────────────────────────────────────────────────────┘

🧩 Component Responsibilities

SlackListenerService

Purpose: Main entry point and Slack connection

Responsibilities:

  • Load config.yaml
  • Connect to Slack via Socket Mode
  • Register event handlers
  • Route messages to BotExecutor
  • Manage bot lifecycle

Key Methods:

  • ExecuteAsync() - Main service loop
  • Handle(MessageEvent) - Process incoming messages

BotExecutor

Purpose: Execute bot scripts and manage processes

Responsibilities:

  • Spawn bot process
  • Set environment variables
  • Stream stdout/stderr
  • Handle timeouts
  • Process cancellation

Key Methods:

  • ExecuteAsync() - Execute bot and return result
  • CreateProcessStartInfo() - Configure process

ActionProcessor

Purpose: Parse JSON and execute Slack actions

Responsibilities:

  • Parse JSON from stdout
  • Validate action schema
  • Execute Slack API calls
  • Handle errors gracefully
  • Log action execution

Key Methods:

  • ProcessActionAsync() - Parse and execute single action
  • HandlePostMessageAsync() - Post message to Slack
  • HandleUpdateMessageAsync() - Update existing message
  • HandlePostBlocksAsync() - Post rich formatted message

MessageReferenceStore

Purpose: Store message timestamps for updates

Responsibilities:

  • Store message ID → timestamp mapping
  • Retrieve timestamps by reference name
  • Clear references after bot execution

Key Methods:

  • Store(name, timestamp) - Store reference
  • Get(name) - Retrieve timestamp
  • Clear() - Clear all references

📦 Data Models

BotDefinition

class BotDefinition {
    string Name;                      // Bot display name
    string? Description;              // Optional description
    string Type;                      // channel | bot_dm | all
    string? ChannelId;                // Required for type: channel
    string MessagePattern;            // Regex pattern
    string Script;                    // Command to execute
    string? WorkingDirectory;         // Optional working dir
    int Timeout = 30;                 // Seconds
    Dictionary<string, string>? Environment;  // Custom env vars
}

BotAction (JSON Protocol)

// Base action
class BotAction {
    string Action;  // Action type
}

// Post message
class PostMessageAction : BotAction {
    string Channel;
    string Text;
    string? ThreadTs;
    string? StoreAs;
}

// Update message
class UpdateMessageAction : BotAction {
    string Channel;
    string MessageRef;
    string Text;
}

// Post blocks
class PostBlocksAction : BotAction {
    string Channel;
    string Text;
    object[] Blocks;
    string? ThreadTs;
    string? StoreAs;
}

🔐 Security Considerations

Environment Variables

  • Never expose sensitive data in environment variables
  • Bot scripts have access to all server environment variables
  • Use secrets management for sensitive configuration

Process Isolation

  • Each bot runs in separate process
  • Processes are killed on timeout
  • Scripts can't access other bot's data directly

Input Validation

  • All Slack input is treated as untrusted
  • Bot patterns use regex with timeout protection
  • JSON parsing has error handling

Token Security

  • Tokens stored in config.yaml (should be gitignored)
  • Consider using environment variables for tokens
  • Rotate tokens regularly

📈 Performance Characteristics

Concurrency

  • Multiple bots can match and execute simultaneously
  • Each bot execution is independent
  • Thread-safe MessageReferenceStore per execution

Resource Usage

  • One process per bot execution
  • Streaming stdout (low memory)
  • Timeouts prevent hung processes
  • Automatic process cleanup

Scalability

  • Socket Mode: 1 connection per SlackServer instance
  • Can handle multiple channels
  • Bot executions are asynchronous
  • Limited by system process limits

🔍 Logging

Log Levels

Debug      - Detailed action execution, JSON parsing
Information - Bot matching, execution start/end
Warning     - Failed actions, timeouts, API errors
Error       - Fatal errors, connection failures

Log Output

[2026-03-26 08:30:15] info: Loaded 3 bot(s) from config.yaml
[2026-03-26 08:30:15] info: ✅ Connected to Slack!
[2026-03-26 08:30:20] info: Bot 'Question Bot' matched message in C01234
[2026-03-26 08:30:20] info: [Question Bot] Executing: pwsh ./scripts/question-bot.ps1
[2026-03-26 08:30:20] debug: [Question Bot] Processing action: post_message
[2026-03-26 08:30:21] info: [Question Bot] Posted message to C01234
[2026-03-26 08:30:21] info: [Question Bot] Execution finished: 2 actions in 1.2s

🧪 Testing Strategy

Unit Tests

  • ActionProcessor JSON parsing
  • MessageReferenceStore operations
  • Pattern matching logic

Integration Tests

  • Full bot execution flow
  • Slack API mocking
  • Timeout handling
  • Error scenarios

Manual Tests

  • Test bots (PowerShell, C#)
  • Real Slack workspace testing
  • Various message patterns
  • Error conditions

🚀 Deployment Options

Standalone Process

dotnet run

Windows Service

sc.exe create SlackServer binPath="C:\SlackServer\SlackServer.exe"

Systemd Service (Linux)

[Service]
Type=notify
ExecStart=/usr/bin/dotnet /opt/slackserver/SlackServer.dll

Docker Container

FROM mcr.microsoft.com/dotnet/runtime:10.0
COPY publish/ /app
WORKDIR /app
ENTRYPOINT ["dotnet", "SlackServer.dll"]

📚 Extension Points

Custom Actions

Add new action types in ActionProcessor:

case "custom_action":
    await HandleCustomActionAsync(jsonLine, botName);
    break;

Custom Event Types

Extend SlackListenerService to handle more events:

.RegisterEventHandler<ReactionAddedEvent>(_ => new ReactionHandler(...))

Bot State Management

Implement IBotStateStore interface:

interface IBotStateStore {
    Task<string?> GetStateAsync(string botName, string key);
    Task SetStateAsync(string botName, string key, string value);
}

Middleware Pipeline

Add bot execution middleware:

interface IBotMiddleware {
    Task<BotExecutionResult> ExecuteAsync(
        BotDefinition bot,
        BotContext context,
        Func<Task<BotExecutionResult>> next);
}