# Messaging | EchoHub Documentation

# Messaging

## Sending a Message (SignalR)

A message typed in the TUI travels through encryption, the SignalR hub, `ChatService` validation, database storage, and fan-out to both SignalR and IRC clients.

    sequenceDiagram
        participant UI as MainWindow (TUI)
        participant AO as AppOrchestrator
        participant EHC as EchoHubConnection
        participant Hub as ChatHub
        participant CS as ChatService
        participant LE as LinkEmbedService
        participant DB as SQLite
        participant SRB as SignalRBroadcaster
        participant IRCB as IrcBroadcaster
        participant Clients as Other Clients

        UI->>AO: OnMessageSubmitted(channel, text)
        AO->>AO: IsCommand(text)? → No
        AO->>EHC: SendMessageAsync(channel, text)
        EHC->>EHC: Encrypt(text) → ciphertext
        EHC->>Hub: InvokeAsync("SendMessage", channel, ciphertext)
        Hub->>CS: SendMessageAsync(userId, username, channel, ciphertext)
        CS->>CS: Decrypt(ciphertext) → plaintext
        CS->>CS: Validate (length, newlines, channel exists)
        CS->>DB: Check mute status
        CS->>LE: TryGetEmbedsAsync(plaintext)
        LE->>LE: Extract URLs, fetch OG tags
        LE-->>CS: List<EmbedDto> (or null)
        CS->>DB: INSERT Message (encrypted at rest)
        CS->>CS: Re-encrypt plaintext for transport
        CS->>CS: Build MessageDto with embeds

        par Fan-out to all broadcasters
            CS->>SRB: SendMessageToChannelAsync(channel, dto)
            SRB->>Clients: HubContext.Group(channel).ReceiveMessage(dto)
        and
            CS->>IRCB: SendMessageToChannelAsync(channel, dto)
            IRCB->>IRCB: Decrypt → format as PRIVMSG lines
            IRCB->>Clients: Send to each IRC conn (skip sender)
        end

## Sending a Message (IRC)

Messages from IRC clients follow the same `ChatService` path but enter as plaintext (no app-layer encryption).

    sequenceDiagram
        participant IRC as IRC Client
        participant CH as IrcCommandHandler
        participant CS as ChatService
        participant DB as SQLite
        participant SRB as SignalRBroadcaster
        participant IRCB as IrcBroadcaster

        IRC->>CH: PRIVMSG #channel :Hello world
        CH->>CH: Parse target + content
        CH->>CH: IrcToEchoHubChannel("#channel") → "channel"
        CH->>CS: SendMessageAsync(userId, username, "channel", "Hello world")
        CS->>CS: Decrypt("Hello world") → passthrough (no $ENC$ prefix)
        CS->>CS: Validate, check mute, fetch embeds
        CS->>DB: INSERT Message
        CS->>CS: Encrypt plaintext for SignalR transport

        par Fan-out
            CS->>SRB: SendMessageToChannelAsync (encrypted for SignalR)
        and
            CS->>IRCB: SendMessageToChannelAsync (decrypt → PRIVMSG)
            IRCB->>IRCB: Skip sender (IRC echo suppression)
        end

## Receiving a Message (TUI Client)

When a message arrives via SignalR, the client decrypts it, adds it to the chat view, and optionally plays a notification sound for @mentions.

    sequenceDiagram
        participant SRB as SignalRBroadcaster
        participant EHC as EchoHubConnection
        participant AO as AppOrchestrator
        participant MM as ChatMessageManager
        participant UI as MainWindow

        SRB->>EHC: ReceiveMessage(MessageDto)
        EHC->>EHC: Decrypt(message.Content)
        EHC-->>AO: OnMessageReceived(decrypted dto)
        AO->>AO: InvokeUI (thread-safe)
        AO->>MM: AddMessage(message)
        MM->>UI: Render in chat ListView
        alt Message contains @username
            AO->>AO: PlayAsync() notification sound
        end

## Command Execution

Slash commands (`/status`, `/nick`, `/kick`, etc.) are parsed client-side and dispatched to appropriate handlers, which call REST APIs or SignalR methods.

    sequenceDiagram
        participant UI as MainWindow
        participant AO as AppOrchestrator
        participant CMD as CommandHandler
        participant API as ApiClient
        participant EHC as EchoHubConnection
        participant Server as Server (API/Hub)

        UI->>AO: OnMessageSubmitted(channel, "/kick baduser")
        AO->>CMD: IsCommand("/kick baduser")? → true
        AO->>CMD: HandleAsync("/kick baduser")
        CMD->>CMD: Parse → command="kick", args="baduser"

        alt API command (kick, ban, mute, nick, etc.)
            CMD-->>AO: Fire OnKickRequested("baduser")
            AO->>API: KickUserAsync("baduser")
            API->>Server: POST /api/moderation/kick/baduser
        else Hub command (status, join, leave, etc.)
            CMD-->>AO: Fire OnSetStatus / OnJoinChannel / etc.
            AO->>EHC: UpdateStatusAsync() / JoinChannelAsync() / etc.
            EHC->>Server: SignalR Invoke
        else Local command (theme, help, quit)
            CMD-->>AO: Fire OnThemeChanged / etc.
            AO->>UI: Apply locally (no server call)
        end

        AO->>UI: AddSystemMessage(result)

Available commands:

Command | Type | Handler
--- | --- | ---
`/status <status> [message]` | Hub | `UpdateStatusAsync`
`/nick <name>` | API | `UpdateProfileAsync`
`/color <hex>` | API | `UpdateProfileAsync`
`/join <channel>` | Hub | `JoinChannelAsync`
`/leave` | Hub | `LeaveChannelAsync`
`/topic <text>` | API | `UpdateChannelTopicAsync`
`/kick <user>` | API | `POST /api/moderation/kick/{user}`
`/ban <user>` | API | `POST /api/moderation/ban/{user}`
`/unban <user>` | API | `POST /api/moderation/unban/{user}`
`/mute <user> [mins]` | API | `POST /api/moderation/mute/{user}`
`/unmute <user>` | API | `POST /api/moderation/unmute/{user}`
`/role <user> <role>` | API | `PUT /api/moderation/role/{user}`
`/nuke` | API | `DELETE /api/channels/{channel}/messages`
`/send <file>` | API | `POST /api/channels/{channel}/upload`
`/profile [user]` | Local | Show profile dialog
`/avatar` | API | `POST /api/users/avatar`
`/theme <name>` | Local | `ThemeManager.SetTheme()`
`/servers` | API | `GET /api/serverdir/servers`
`/users` | Local | Show userlist
`/help` | Local | Show help text
`/quit` | Local | Exit application