@@ -8,12 +8,24 @@ public class IrcBroadcaster : IChatBroadcaster
```
Broadcasts chat events to IRC clients by translating application-level messages and room/user events into IRC protocol lines and sending them via the IrcGatewayService. Decrypts transport-layer-encrypted message content so IRC clients (which do not support the application's app-layer encryption) receive readable text; end-to-end room ciphertext markers (e.g. $RC1$) are preserved. Use this class when you need to mirror server chat rooms and user lifecycle events to connected IRC clients.
Bridges application chat events into IRC wire-protocol messages and sends them to connected IRC clients. Use `IrcBroadcaster` when you need chat activity (messages, joins/parts, kicks/bans, topic changes) reflected on an IRC gateway so traditional IRC clients see the room as an IRC channel; prefer using a plain chat broadcaster when you do not need IRC-formatted output.
## Remarks
IrcBroadcaster is the IRC-specific implementation of the IChatBroadcaster contract and acts as the bridge between the chat model and the IRC wire format. It relies on IMessageEncryptionService to remove transport-layer encryption for IRC consumers and on IrcMessageFormatter to produce IRC-compliant lines (including splitting long messages and formatting reply prefixes/embeds). Messages are dispatched by enumerating connections returned from the gateway and sending formatted lines to each connection; the broadcaster also follows IRC conventions such as avoiding echoing a message back to the originating connection.
`IrcBroadcaster` is an [`IChatBroadcaster`](../EchoHub.Core/Contracts/IChatBroadcaster.cs.md) implementation that adapts the application's chat model to IRC conventions. It pulls active connections from the [`IrcGatewayService`](IrcGatewayService.cs.md) (`GetConnectionsInChannel` / `GetAllConnections`), formats user-visible text with `IrcMessageFormatter.FormatMessage` (using the gateway `Options.PublicBaseUrl` for absolute links), and sends one or more IRC lines to each connection via `conn.SendAsync`. Because IRC clients cannot handle app-layer encryption, the broadcaster uses the injected [`IMessageEncryptionService`](../EchoHub.Core/Contracts/IMessageEncryptionService.cs.md) to decrypt transport-encrypted payloads before formatting; end-to-end room ciphertext markers (`$RC1$`) are left intact. The implementation also follows IRC conventions for echo suppression (it excludes the originating connection by `excludeConnectionId`) and for addressing (matching by `ConnectionId` or `Nickname` where appropriate).
## Example
```csharp
// given existing instances of IrcGatewayService and IMessageEncryptionService
-Message content is decrypted before formatting; E2E room ciphertext (explicit markers like $RC1$) is left unchanged so room-encrypted messages are not exposed.
-The broadcaster avoids echoing by comparing connection IDs (excludeConnectionId) when sending normal messages. Be aware this requires callers to pass the originating connection id to suppress local echoes correctly when appropriate.
- Sends are awaited sequentially per connection/line (each connection's SendAsync is awaited in a loop). Under high fan-out this can introduce latency; consider batching or parallelization at the caller/gateway level if latency becomes an issue.
-`IrcBroadcaster` explicitly decrypts transport-layer encrypted content using the injected [`IMessageEncryptionService`](../EchoHub.Core/Contracts/IMessageEncryptionService.cs.md); this is necessary because IRC clients do not support the application's app-layer encryption. Messages that contain E2E room ciphertext markers (`$RC1$`) are preserved as-is.
-Echo suppression is performed by comparing `conn.ConnectionId` to the provided `excludeConnectionId`. This avoids re-sending a message to the origin connection while still delivering it to other sessions belonging to the same user (different connections/nicknames).
-`IrcMessageFormatter.FormatMessage` may split a single logical message into multiple IRC lines; each resulting line is sent individually with `conn.SendAsync`, so large messages can result in multiple network writes.
-`SendUserStatusChangedAsync` is a no-op because IRC lacks an active presence broadcast; clients discover away/idle state via `WHO`/`WHOIS` instead.
@@ -8,15 +8,13 @@ public sealed class IrcClientConnection : IAsyncDisposable
```
Manages a single IRC client TCP connection: wraps a TcpClient/Stream pair, serializes outgoing IRC lines, provides simple registration and channel membership state, and exposes read/write helpers for the IRC protocol. Use this when handling a single connected client in server code so you get consistent CRLF framing, UTF-8 encoding without BOM, and serialized writes.
Manages a single IRC-over-TCP client connection: reads and writes CRLF-delimited lines, holds simple registration state, and tracks joined channels. Use `IrcClientConnection` when the server needs a lightweight, per-socket representation of a connected IRC client (rather than passing raw `TcpClient`/`Stream` around).
## Remarks
This type represents the per-connection state and I/O for one IRC client. It centralizes the socket-level StreamReader/StreamWriter setup (UTF-8 without BOM, CRLF line endings, AutoFlush) and enforces serialized writes with an internal SemaphoreSlim. The class keeps mutable registration and presence properties (nickname, username, authentication flags, away message, joined channels) so higher-level command handlers and broadcaster threads can consult or update a single source of truth. Channel membership access is guarded by a private lock and exposed via snapshot methods so broadcaster threads can read without additional synchronization.
`IrcClientConnection` encapsulates the I/O and minimal protocol state for one IRC client. It creates `StreamReader`/`StreamWriter` pair configured with UTF-8 without BOM (via `UTF8Encoding`) and `StreamWriter` options `AutoFlush` and `NewLine = "\r\n"` so callers can read/write logical IRC lines. Outgoing writes are serialized using the private `SemaphoreSlim` (`_writeLock`) so concurrent senders do not interleave data. Channel membership is stored in the private `_joinedChannels` and guarded by `_channelLock`, allowing safe reads by broadcaster threads while command handlers mutate the set. Connection identity is exposed through `ConnectionId` (prefixed with `HubConstants.IrcConnectionIdPrefix` and a `Guid`), and a simple `Hostmask` string is provided based on `Nickname` and `Username`.
## Notes
- ReadLineAsync swallows read exceptions and returns null; treat a null result as "connection closed" or irrecoverable read error.
- SendAsync swallows write exceptions (connection lost) after serializing via an internal semaphore; callers cannot observe write failures directly.
-Registration properties (Nickname, Username, UserId, IsRegistered, etc.) are not synchronized by this class — callers should coordinate concurrent access if needed.
- Channel membership APIs (JoinChannel, LeaveChannel, IsInChannel, GetJoinedChannels) are thread-safe: the implementation takes a lock and GetJoinedChannels returns a snapshot list to avoid callers iterating the internal set directly.
- Hostmask composes Nickname and Username; Username may be null so Hostmask uses Username ?? Nickname in its string formatting.
- DisposeAsync closes the underlying TcpClient and disposes the reader/writer and semaphore; do not use the connection after disposing.
-`ReadLineAsync` returns `null` on error or when the underlying read fails; callers must treat `null` as a disconnected/error condition rather than a valid empty line.
-`SendAsync` and the numeric helpers (`SendNumericAsync`) swallow exceptions raised while writing (connection loss is silently ignored), so sending failures will not throw — design choice to avoid bubbling socket errors to callers.
-Only channel-related state (`_joinedChannels`) is synchronized. Properties such as `Nickname`, `Username`, `IsRegistered`, `IsAuthenticated`, and `AwayMessage` are not individually thread-safe; if you access them concurrently from multiple threads, add external synchronization.
- `DisposeAsync` closes the underlying `TcpClient` and disposes the reader, writer, and `_writeLock`, but does not attempt to coordinate or await in-flight operations beyond disposing those resources.
@@ -46,15 +46,15 @@ public sealed class IrcCommandHandler
```
Acts as the main dispatcher that translates incoming IRC protocol messages from a single IrcClientConnection into actions against the server-side chat, user and channel services. Reach for this class when you need the gateway that accepts an IRC client, performs registration/authentication (SASL or PASS), sends the welcome/MOTD burst, and maps channel/query commands (JOIN, PART, PRIVMSG, NAMES, TOPIC, WHO/WHOIS, LIST, etc.) into the server's chat/channel/user APIs.
Handles and dispatches IRC client commands for a single connected client and bridges them to the server-side chat services. Reach for `IrcCommandHandler` when you need an adapter that translates IRC commands (registration, authentication, channel operations, queries like `NAMES`/`WHOIS`, messaging) into calls on the backend services ([`IChatService`](../EchoHub.Core/Contracts/IChatService.cs.md), [`IUserService`](../EchoHub.Core/Contracts/IUserService.cs.md), [`IChannelService`](../EchoHub.Core/Contracts/IChannelService.cs.md)) and emits the corresponding IRC replies (welcome burst, MOTD, topic/NAMES lists, etc.).
## Remarks
This sealed handler centralizes IRC protocol handling for one client connection. It coordinates authentication (including SASL and PASS fallbacks), completes IRC registration (NICK/USER), and then routes post-registration commands to the underlying IChatService, IUserService, and IChannelService. It also enforces gateway-specific policies mentioned in the source comments: encrypted rooms are not readable over IRC (so joins are blocked), system channels are not proxied to IRC, and private channels are omitted from LIST results. Message history replay must be decrypted via IMessageEncryptionService before being sent to the IRC client.
`IrcCommandHandler` is the protocol-layer coordinator for an IRC gateway: it receives parsed [`IrcMessage`](IrcMessage.cs.md) instances from the [`IrcClientConnection`](IrcClientConnection.cs.md), interprets IRC semantics (registration flows, SASL vs PASS authentication, channel join/part, PMs, queries), and invokes the appropriate backend services and helpers ([`IMessageEncryptionService`](../EchoHub.Core/Contracts/IMessageEncryptionService.cs.md) for decrypting history, `ILogger` for diagnostics). The class groups responsibilities into logical regions — authentication, welcome/MOTD, channel operations, and query commands — and exposes a long-running `RunAsync` loop driven by a `CancellationToken` to process client messages until the connection ends. Many of the private handlers (`HandleCapAsync`, `HandleAuthenticateAsync`, `HandleNickAsync`, `HandleUserAsync`, `HandleJoinAsync`, `HandlePrivmsgAsync`, `HandleNamesAsync`, `HandleTopicAsync`, `HandleWhoisAsync`, etc.) encapsulate the IRC-to-service mapping and the reply generation.
## Notes
-Encrypted rooms are intentionally blocked from JOIN over the IRC gateway: the gateway does not hold room keys and therefore cannot expose encrypted room contents to IRC clients.
-Private channels are hidden from LIST to match the SignalR client's channel visibility; expect LIST to only include public/discoverable channels.
-Registration and authentication have multiple paths (SASL, PASS, or account registration fallback); callers should expect asynchronous authentication flow and that RunAsync accepts a CancellationToken to stop processing.
-Registration/authentication ordering is important: the handler supports both SASL (`AUTHENTICATE`) and legacy `PASS` flows and contains explicit fallbacks (e.g. try to auto-register on auth failure). Callers should not assume a client is fully registered until the registration completion path in `TryCompleteRegistrationAsync` completes.
-Encrypted and system channels are treated specially: the handler deliberately prevents joining channels that cannot be safely proxied (end-to-end encrypted rooms or server-only system channels), and history replay requires decrypting messages via [`IMessageEncryptionService`](../EchoHub.Core/Contracts/IMessageEncryptionService.cs.md) before sending them to the IRC client.
-All public operations are async and driven by `RunAsync(CancellationToken)`: callers should respect the `CancellationToken` and be prepared for async exceptions to surface from the handlers; the class uses `ILogger` for recording failures and important state transitions.
---
@@ -86,14 +86,10 @@ public IrcCommandHandler(
| `logger` | `ILogger` | — |
The constructor initializes an IrcCommandHandler by wiring together its required collaborators: IrcClientConnection, IrcOptions, IChatService, IUserService, IChannelService, IMessageEncryptionService, and ILogger. It stores these dependencies in private fields so the command-handling logic can access the IRC connection, configuration, chat and user/channel services, encryption features, and logging throughout command processing. This pattern follows dependency injection, enabling easy testing with mocks and seamless composition by the application’s DI container at startup.
Initializes a new `IrcCommandHandler` by injecting all required dependencies: [`IrcClientConnection`](IrcClientConnection.cs.md), [`IrcOptions`](IrcOptions.cs.md), [`IChatService`](../EchoHub.Core/Contracts/IChatService.cs.md), [`IUserService`](../EchoHub.Core/Contracts/IUserService.cs.md), [`IChannelService`](../EchoHub.Core/Contracts/IChannelService.cs.md), [`IMessageEncryptionService`](../EchoHub.Core/Contracts/IMessageEncryptionService.cs.md), and `ILogger`. The constructor stores these in private fields so the commandhandler can coordinate chat, user, and channel operations, apply encryption, and log activity when processing IRC commands. This initialization pattern is typically used by the dependency injection container or in tests to assemble a fully wired, ready-to-run handler.
## Remarks
By aggregating these collaborators, the constructor centralizes the wiring of core capabilities—network I/O, configuration, domain services for chat, user and channel state, cryptographic operations, and observability—so command processing remains focused on business logic rather than setup. This design promotes testability, consistency, and clear separation of concerns within the IRC subsystem.
## Notes
- The constructor as shown does not perform null checks; ensure the DI container enforces non-null registrations or add guards in production code.
- Be mindful of lifetime management: the handler should typically share lifetimes with its collaborators or be disposed in tandem to avoid resource leaks.
The constructor serves as a wiring point that decouples `IrcCommandHandler` from concrete implementations, enabling substitution in tests and different runtime configurations. By wiring `_conn`, `_options`, `_chatService`, `_userService`, `_channelService`, `_encryption`, and `_logger`, it ensures the handler has immediate access to the resources needed to parse and route IRC commands, manage users and channels, apply encryption, and emit logs. It does not execute command logic itself; its purpose is to provide a fully initialized, ready-to-use instance for later operation.
This private read-only property exposes the server name configured in the handler’s options by forwarding to _options.ServerName. It should be used whenever the command handler needs the target IRC server name, offering a single indirection point if the source of that value changes in the future.
The `ServerName` property is a private, read-only accessor that forwards to `_options.ServerName` to obtain the configured IRC server name. It serves as an internal convenience within the `IrcCommandHandler` class, enabling consistent access to the server name without coupling to the `_options` object.
## Remarks
By wrapping the access in ServerName, you decouple usage from the underlying options data. This centralization makes future changes (like deriving the server name from a different config source or applying normalization) localized to this property. It also communicates that the server name is a configuration concern and not a computed field of the handler itself.
## Notes
- The property simply forwards to _options.ServerName; it does not perform validation or mutation.
- If _options.ServerName can change at runtime, callers may observe updates on subsequent accesses.
This private indirection isolates the `IrcCommandHandler` from changes to where the server name is stored. If `_options`' structure changes or the server name is sourced from elsewhere, update only this member and keep the rest of the class intact. It also clarifies intent by naming and exposing the concept of 'server name' as a single retrieval point for internal command handling.
Handles SASL authentication for a connected IRC client by processing the SASL-related AUTHENTICATE messages. It supports initiating SASL with PLAIN, aborting SASL, and performing the actual PLAIN payload verification, ultimately authenticating or registering the user, and then updating the connection state and sending appropriate IRC numeric replies.
Handles the SASL authentication flow for an IRC connection. When invoked, it interprets the first parameter to drive a SASL PLAIN exchange: it can prompt the client to provide credentials, abort SASL, or process a base64-encoded payload to authenticate or register a user, updating the connection state on success and replying with appropriate IRC numerics on failure. The method encapsulates the end-to-end SASL Plain handling, including error signaling and logging for traceability.
## Remarks
This method centralizes SASL negotiation within the command handler, bridging the IRC SASL protocol with the application's user store. It redacts the password in logs and relies on the user service to either authenticate or register the user, enabling a smooth first-time login flow. It validates payload structure and wraps the process in a catch block to translate unexpected errors into SASL failure feedback while preserving a consistent connection state.
This method centralizes the SASL PLAIN authentication handshake for a client, coordinating between the incoming [`IrcMessage`](IrcMessage.cs.md) payload, the server connection state (`_conn`), the user service (`_userService`), and server numerics. It performs decoding and validation of the SASL payload, derives a username from the payload, and attempts authentication first, then automatic registration as a fallback. On success, it binds the authenticated user to the connection (setting `Nickname`, `UserId`, and `IsAuthenticated`) and notifies the client with both `RPL_LOGGEDIN` and `RPL_SASLSUCCESS`. The structured exception handling ensures a consistent failure path with an `ERR_SASLFAIL` response and logging for operational visibility.
## Notes
-The SASL payload must decode to a null-delimited string yielding at least three parts; malformed payloads trigger an ERR_SASLFAIL response.
-The code derives the username from parts[1] when present, otherwise parts[0], and normalizes it to lowercase; the actual password is sourced from a redacted variable and is not logged.
-On success, the connection's Nickname and UserId are populated, the connection is marked authenticated, and the client receives both a LOGGEDIN notice and a SASL success reply; failures emit ERR_SASLFAIL and are logged for auditing.
-Malformed SASL payloads (e.g., payloads that do not yield at least three parts after decoding) trigger an authentication failure early, signaling to the client via `ERR_SASLFAIL`.
-If initial authentication fails, the flow transparently attempts to register a new user with the extracted credentials; if registration also fails, it reports the error back to the client and logs a warning.
-The password is sourced from the SASL PLAIN payload; ensure that credential handling complies with your security requirements and that secrets are managed appropriately within the `_userService`.
HandleAwayAsync processes a user's away status in response to the IRC AWAY command. It first ensures the caller is registered; if not, it exits early. When a non-empty parameter is supplied, it stores that string as the away message on the connection, updates the user's status to Away via the chat service, and sends a RPL_NOWAWAY reply to the client. If no parameter is provided, it clears the away message, updates the status to Online with a null message, and sends a RPL_UNAWAY reply. The method is asynchronous, so it does not block the command handling path while performing persistence and network communication.
This private async method handles the AWAY command for the current connection. After confirming the user is registered via `RequireRegisteredAsync`, it checks for a non-empty first parameter on `msg.Parameters`; if present, it stores the away message on ``_conn`` (i.e., `_conn`), updates the user's status to `UserStatus.Away` via `_chatService`, and sends the `IrcNumericReply.RPL_NOWAWAY`. If no message is supplied, it clears the away message, updates the status to `UserStatus.Online`, and sends the `IrcNumericReply.RPL_UNAWAY`.
## Remarks
Consolidates away-state handling in a single place so all callers see the same effecton status and client notification. It keeps IrcCommandHandler lean by delegating away management and relies on _chatService to persist user state. It uses a simple, deterministic flow based on whether a message parameter is provided.
This method centralizes away-state management for the connected user by coordinating the connection state (`_conn`), persistence/update semantics (`_chatService`), and client feedback via numeric replies ([`IrcNumericReply`](IrcNumericReply.cs.md)). It ensures that providing an away message both reflects in server-side status and informs the client promptly.
## Notes
- Accessing _conn.UserId with the null-forgiving operator assumes RequireRegisteredAsync succeeded; calling this method without a valid registered session could throw a NullReferenceException.
- This method sends numeric replies (RPL_NOWAWAY / RPL_UNAWAY) to the connected client; ensure ServerName and _conn are valid at call time.
- Rapid, repeated calls may race with the chat service updates; consider sequencing on the caller side or adding concurrency guards.
HandleCapAsync processes CAP negotiation commands from the IRC server. It requires at least one parameter; if none are provided, it returns without action. It switches on the upper-cased first parameter to implement the SASL/capability handshake: on LS it requests the sasl capability and marks negotiation as in progress; on REQ it either acknowledges the 'sasl' request and enables SASL, or responds with NAK for the requested capability; on END it ends negotiation and, if identity information is available and the client is not yet registered, triggers a registration attempt via TryCompleteRegistrationAsync.
HandleCapAsync processes IRC CAP negotiation messages related to SASL authentication. It inspects the first element of `msg.Parameters` to drive a small, centralized CAP flow: starting negotiation with `LS`, acknowledging or declining a SASL request with `REQ`, and ending negotiation with `END`. The method updates internal connection state via `_conn.CapNegotiating`, `_conn.IsSasl`, and coordinates with registration by triggering `TryCompleteRegistrationAsync()` when appropriate. Messages are sent back to the server using `_conn.SendAsync`, built from the current `ServerName` (e.g. `":{ServerName} CAP * LS :sasl"`) and reflecting the outcome of each branch. The logic short-circuits on insufficient parameters and handles case-insensitive comparisons for SASL requests.
## Remarks
This method centralizes the CAP negotiation lifecycle for the IRC connection, coordinating with the connection state (_conn) to track whether a CAP negotiation is underway, whether SASL is engaged, and whether registration has completed. By encapsulating the protocol specifics here, it avoids scattering CAP handling logic across multiple handlers and ensures correct sequencing between CAP negotiation, SASL activation, and user registration.
## Notes
- The method short-circuits when there are no parameters, avoiding potential null-reference issues.
- The REQ path treats a missing or non-matching second parameter as a NAK for the requested capability, preserving protocol safety.
- END clears the negotiation flag and only triggers registration if Nickname and Username are non-null and the client is not already registered, preventing premature or repeated registration attempts.
The typical flow is:
- LS starts capability negotiation and marks the connection as negotiating.
- REQ sasl acknowledges SASL capability and enables SASL, while any other requested capability prompts a NAK with the requested name.
- END ends negotiation and, if credentials are present (non-null `Nickname` and `Username`) but the client is not yet registered, proceeds to complete registration via `TryCompleteRegistrationAsync()`.
Dispatches incoming IRC commands by normalizing the command to uppercase and routing to the corresponding per-command asynchronous handler, centralizing the IRC command handling logic (e.g., CAP -> HandleCapAsync, PRIVMSG -> HandlePrivmsgAsync). If the command is unknown, it responds with the ERR_UNKNOWNCOMMAND numeric back to the client.
## Remarks
Centralizes command dispatch behind a single switch expression, mapping command strings to their asynchronous handlers. This design makes it straightforward to extend support for new commands by adding a new case to the switch. It returns a Task to support asynchronous work and relies on the private _conn to send numeric replies back to the client; some branches return Task.CompletedTask to represent no-op work for certain commands (e.g., PONG).
## Example
```csharp
// Example: dispatch flow for a known command
IrcMessagemsg=/* ... */;
awaitHandleCommandAsync(msg);// if msg.Command == "PRIVMSG" this path invokes HandlePrivmsgAsync(msg)
```
## Notes
- Unknown commands trigger an ERR_UNKNOWNCOMMAND reply, authored with the server name and the raw command.
- The PONG path is treated as a no-op by returning Task.CompletedTask, avoiding unnecessary asynchronous work.
Handles an incoming IRC command by normalizing the textual command to upper-case and dispatching to the corresponding asynchronous handler. As the central router, it maps pre-registration commands (such as `CAP`, `AUTHENTICATE`, `PASS`, `NICK`, [`USER`](../EchoHub.Core/Models/User.cs.md)) and post-registration commands (such as `PING`, `JOIN`, `PART`, `PRIVMSG`, `QUIT`, `NAMES`, `TOPIC`, `WHO`, `WHOIS`, `AWAY`, `LIST`, `MODE`, `MOTD`, and related aliases) to their dedicated `HandleXAsync` methods, returning the resulting `Task`. For the `PONG` case it completes synchronously with `Task.CompletedTask`; for any unknown command, it responds via `_conn.SendNumericAsync` using `IrcNumericReply.ERR_UNKNOWNCOMMAND` and the command text. This design provides a single, maintainable dispatch point that enforces consistent routing and error reporting across all IRC commands.
Handles an IRC JOIN request for a registered user, performing parameter validation, channel-name mapping, and policy checks before joining the user to each requested channel. It delegates to backend services to perform the join, then updates the client with a JOIN confirmation, channel topic, NAMES list, and a decrypted history replay.
## Remarks
This function centralizes the join workflow for the IRC gateway and enforces privacy and policy constraints: end-to-end encrypted channels and server-managed system channels are blocked from IRC joins, ensuring the EchoHub client remains the source of truth for restricted channels. It coordinates with the connection object, channel service, and chat service to validate input, perform joins per channel (supporting RFC 1459-style per-channel keys), and synchronize the IRC client view (JOIN message, topic, NAMES, and history).
## Notes
- Requires the user to be registered; if not, the method exits early and no join is attempted.
- If there are fewer than one parameter, the gateway responds with ERR_NEEDMOREPARAMS to indicate insufficient input.
- For each channel, invalid channel names yield ERR_NOSUCHCHANNEL with an invalid channel notice.
- End-to-end encrypted channels are blocked from IRC joins; use the EchoHub client for such channels.
- System channels are blocked from IRC joins because they stream content over SignalR; use the EchoHub client for access.
- When a channel join requires a password and the provided key is incorrect or missing, the gateway responds with ERR_BADCHANNELKEY.
- History is replayed after joining, with Content and any embedded replies decrypted for proper IRC presentation.
`HandleJoinAsync` processes the IRC `JOIN` command for a connected user. It validates that the user is registered, requires at least one channel parameter, and then parses comma-separated channel names with optional per-channel keys; for each channel it translates the raw IRC channelname to the internal channel identifier, blocks end-to-end encrypted channels (which must be joined via the EchoHub client) and system channels (server-managed) from IRC, delegates the actual join to `_chatService.JoinChannelAsync` with the user's connection and identity, and on success updates the connection state, announces the join, and replays the channel topic, NAMES list, and decrypted history to the IRC client. On failure, it returns the appropriate IRC error (e.g. `ERR_BADCHANNELKEY` or `ERR_NOSUCHCHANNEL`).
Implements the IRC LIST command for the server. After verifying the client is registered, it fetches the channel list from the channel service, filters to public channels, and sends one RPL_LIST reply per channel containing the channel name, online user count, and topic. If a channel is protected, a [+k] lock hint is prefixed to the topic. Private channels are intentionally hidden to match what the SignalR client sees. Once all public channels have been reported, it sends RPL_LISTEND to signal completion.
HandleListAsync processes the IRC LIST command by emitting the list of public channels to the connected client. It first ensures the caller is registered via `RequireRegisteredAsync()`; if not, it returns immediately. It then retrieves the channel collection from `_channelService.GetChannelListAsync()` and sends an `RPL_LIST` line for each channel that has `IsPublic` set to true, formatting the line as `#{ch.Name} {ch.OnlineCount} :{lockHint}{ch.Topic ?? ""}` where `lockHint` is `[+k] ` when `IsProtected` is true. After enumerating all public channels, it issues `RPL_LISTEND` with End of LIST to finish.
## Remarks
This handler encapsulates the server-side semantics of channel discovery separate from the client protocol encoding. By filtering to IsPublic channels, it keeps private channels from being exposed to clients, preserving privacy where appropriate. The lock indicator (+k) encodes channel protection state in the LIST output, while the Topic is plumbed directly into the listing, enabling clients to present useful metadata without additional requests. The approach keeps channel management in _channelService and I/O in _conn, promoting testability and a clean separation between data retrieval and protocol signaling.
By filtering to `IsPublic` channels, private channels are hidden from discovery, aligning the server's LIST output with the SignalR client's channel exposure. The `[+k]` indicator communicates a protected channel requiring a key and is propagated in the line alongside the channel's `Topic` (or an empty string if no topic is set). This method coordinates a read-only view of channel state and relies on [`IrcNumericReply`](IrcNumericReply.cs.md)-provided numeric codes (`RPL_LIST` and `RPL_LISTEND`).
## Notes
- The method requires a registered user; unauthenticated users will cause the method to return early without emitting LIST data due to the initial RequireRegisteredAsync check.
- Private channels are hidden by design via the IsPublic filter; modify the filter only if you intend to expose private channels and ensure client expectations are updated accordingly.
- The handler short-circuits if the user is not registered, so no LIST data is sent to unregistered users.
HandleModeAsync processes incoming MODE commands for channels and queries. It first ensures the caller is registered, then validates parameters and resolves the IRC target. For channel targets, it either returns the current channel mode or applies mode changes (notably +k to set a channel password and -k to clear it), persisting changes through the channel service and signaling results with the appropriate IRC numerics. When the target is not a channel, it responds with the user-mode indicator (+) to indicate no user modes are reported. If the channel cannot be resolved, it returns ERR_NOSUCHCHANNEL. When querying a channel's mode (MODE #channel with no extra parameters), it responds with RPL_CHANNELMODEIS and, if the channel is protected, indicates +k. For mode changes, it handles +k (requiring a key) and -k (clearing the key); unknown modes yield ERR_UNKNOWNMODE. A small, targeted behavior detail is that probing the ban list returns an empty list via RPL_ENDOFBANLIST to mirror common client expectations during join.
Handles the IRC `MODE` command for a target in the gateway. It first ensures the caller is registered via `RequireRegisteredAsync` and returns user-mode information with `RPL_UMODEIS` when the target isn’t a channel, emitting a leading `+` in that case. For channel targets, it resolves the internal channel name with `IrcToEchoHubChannel`, validates the channel, and then either reports the current mode with `RPL_CHANNELMODEIS` or processes mode changes such as ban-list probes (`b`/`+b`) and password changes (`+k`/`-k`) by delegating to `_channelService.SetChannelPasswordAsync` and broadcasting results through `_conn`; unknown modes yield `ERR_UNKNOWNMODE`.
HandleNamesAsync processes an incoming NAMES-like query for the IRC command handler. It first ensures the caller is registered by awaiting RequireRegisteredAsync; if the user is not registered, the method exits early to prevent exposing channel membership information to unauthorized callers. It then requires at least one parameter; if none are provided, it returns without a response. It converts the first parameter to the internal EchoHub channel using IrcToEchoHubChannel; if this mapping yields null, the method again exits. When all preconditions succeed, it issues the names response for the mapped channel by calling SendNamesReplyAsync with that channel.
HandleNamesAsync is a private asynchronous method that processes a NAMES query. It begins by verifying the caller is registered using `RequireRegisteredAsync()`, returning early if not. It then validates that a parameter is provided (`msg.Parameters.Count < 1`); if not, it returns. The first parameter is converted to the internal channel name by `IrcToEchoHubChannel`, and if this conversion yields `null`, the method exits. Otherwise, it calls `SendNamesReplyAsync(channelName)` to emit the names list for the channel.
## Remarks
This method centralizes the NAMES query flow, isolating authentication, input validation, and channel-nameresolution from the response formatting logic. It enforces that only authenticated, well-formed requests proceed to produce a response, contributing to predictable and secure command handling.
This method encapsulates the precondition checks for name-related queries and centralizes the channel-name translation, keeping the response logic contained in `SendNamesReplyAsync`.
## Notes
- Silent declines: if preconditions fail (not registered, missing parameters, or invalid channel mapping), the method returns without emitting a response.
- The mapping function (IrcToEchoHubChannel) determines whether an IRC channel reference has a corresponding internal EchoHub channel; a null result means no valid target was found, and no response is produced.
- If `IrcToEchoHubChannel` cannot map the input to a channel, no response is sent.
- The method relies on `Parameters` being provided by [`IrcMessage`](IrcMessage.cs.md) and uses early returns to avoid unnecessary work.
Handles the NICK command from a connected IRC client. It validates that a nickname parameter is supplied, enforces the server's username rules, stores a canonical lowercase nickname on the connection, and, when applicable, advances the registration flow by attempting to complete registration if a username is already present.
`HandleNickAsync` handles the NICK command by validating input and updating the connection state. If no nickname is supplied, it sends `ERR_NONICKNAMEGIVEN` with a "No nickname given" message. If the nickname fails the policy check against `ValidationConstants.UsernameRegex()`, it responds with `ERR_ERRONEUSNICKNAME` and a descriptive error like "Erroneous nickname (must be 3-50 chars: a-z, 0-9, _, -)". On success, it normalizes the nickname to lowercase via `ToLowerInvariant()` and assigns it to `_conn.Nickname`. Finally, if the connection is not yet registered but already has a `Username`, it advances the registration by calling `TryCompleteRegistrationAsync()`.
## Remarks
Centralizes nickname processing in the command handler to ensure consistent validation, normalization, and state progression. It uses numeric replies to communicate issues back to the client (missing nickname or invalid nickname) and coordinates with the registration logic via TryCompleteRegistrationAsync once the client is partially authenticated. Normalizing to lowercase provides a stable internal identity, independent of the client's casing.
## Notes
- The error text for invalid nicknames lists allowed characters and length; confirm that UsernameRegex() and the user-visible message remain in sync to avoid misleading users.
By encapsulating parameter validation, nickname syntax enforcement, normalization, and the progression toward registration, this method centralizes the Nick command workflow. It coordinates with `_conn` to store the chosen nickname, uses [`IrcNumericReply`](IrcNumericReply.cs.md) values to emit exact IRC error codes for invalid or missing nicknames, and triggers `TryCompleteRegistrationAsync()` when appropriate, ensuring a cohesive startup sequence.
Leaves one or more IRC channels as requested by an incoming IrcMessage. It first ensures the caller is registered; if not, it exits early without issuing any IRC traffic. It expects at least one parameter; the first parameter is a comma-separated list of raw channel names, and an optional second parameter carries a part message to be appended after PART. For each channel in the list, the method translates the raw channel into an internal channel name using IrcToEchoHubChannel; if mapping returns null, that channel is skipped. It then tells the chat service to leave the mapped channel, updates the local connection state by calling LeaveChannel, and finally emits the IRC PART command for that channel, including the optional message.
Handles a PART command from a registered IRC client by parsing a comma-separated list of channels from the first parameter and an optional part message from the second parameter, then leaving each channel both in the internal chat state and by sending an IRC PART message back to the client.
Channels are mapped from their raw IRC name to the internal Echo Hub channel via `IrcToEchoHubChannel`; invalid mappings are skipped. For each valid channel, the method first awaits `_chatService.LeaveChannelAsync(_conn.ConnectionId, _conn.Nickname!, channelName)`, then updates the local connection state with `_conn.LeaveChannel(channelName)`, and finally emits the IRC PART notice `":{_conn.Hostmask} PART #{channelName}"` with an optional payload appended if a part message was supplied.
## Remarks
Acts as a coordination boundary between the IRC protocol and the application's connection state. It encapsulates registration verification, channel translation, state mutation, and protocol emission in a single command path. Because it awaits each channel in sequence, multiple PARTs are issued in order rather than in parallel.
Coordinates internal state with the external IRC protocol to keep the user’s channel memberships in sync across both domains. The `IrcToEchoHubChannel` mapping acts as a guardrail, ensuring only recognized channels are processed and leaving others untouched.
## Notes
- Early returns ensure no actions occur if the user is not registered or if no channels are specified.
- Channels that cannot be translated via IrcToEchoHubChannel are skipped without error.
- The emitted PART command uses the hostmask and a '#channel' target, and appends an optional message if provided.
- If `IrcToEchoHubChannel` yields `null` for a channel, that channel is ignored rather than causing an exception.
HandlePassAsync is a private helper in the IRC command handling flow that processes the PASS command for a connection. It blocks re-registration by sending ERR_ALREADYREGISTERED when the connection is already registered, and if a password parameter is provided, it routes that parameter to the connection’s password handling path (the actual value is redacted in this snippet). In cases where neither condition applies, it completes without performing additional work.
HandlePassAsync processes the IRC PASS command for the current connection. If the connection is already registered (`_conn.IsRegistered`), it replies with `ERR_ALREADYREGISTERED` by calling `_conn.SendNumericAsync(ServerName, IrcNumericReply.ERR_ALREADYREGISTERED, ":You may not reregister")`; if a password parameter is provided, it stores the password on the connection (the exact storage is redacted in the source). The method always completes by returning a `Task`—the send task when replying, or `Task.CompletedTask` when no action is needed.
## Remarks
This abstraction centralizes PASS command handling within the command handler to ensure consistent protocol error signaling and password processing across the handshake sequence. It delegates state management and messaging to the underlying connection object, which keeps the command dispatch logic focused and testable. The explicit redaction of the password demonstrates a security-conscious approach to handling sensitive data, avoiding exposure in logs or snapshots. By returning a Task, the method remains composable with the asynchronous command pipeline.
Centralizes PASS command handling within the `IrcCommandHandler` and enforces the re-registration guard in one place. It updates the connection state when a parameter is present, separating command validation from the subsequent authentication flow.
## Notes
- If a PASS parameter is provided, ensure proper validation and secure handling of the credential; the actual value is redacted here, so verify correctness in your environment.
- The method relies on external state (_conn.IsRegistered) and may either complete synchronously or proceed asynchronously via SendNumericAsync; callers should await as appropriate to preserve command-ordering guarantees.
- This function does not perform full authentication itself; it coordinates with the connection object for state and output, acting as a gateway in the PASS handling path.
- The method is not declared `async`; it returns a `Task` and may complete synchronously via `Task.CompletedTask` when no password parameter is supplied.
- The password value is written to a connection field whose exact name is redacted; handling of this sensitive data should be reviewed in the surrounding authentication flow.
Responds to IRC PING messages by sending a corresponding PONG back to the server to keep the connection alive. It chooses the token to include in the PONG from the incoming message: if a parameter is present, that token is used; otherwise it falls back to the server name. The response is sent using the underlying connection with the format :<ServerName> PONG <ServerName> :<token>.
## Remarks
Internally, this method serves as the keep-alive handler for the IRC command flow. By basing the PONG on ServerName and the received parameters, it guarantees a consistent reply format and avoids leaking raw protocol details to higher layers. It relies on the ServerName and Parameters dependencies and on the underlying connection to transmit the response.
## Example
```csharp
// Example: a PING with a token results in a PONG containing that token
// The actual send occurs via _conn.SendAsync in HandlePingAsync
```
## Notes
- If msg.Parameters is empty, token defaults to ServerName.
- The method is private; usage is internal to the IrcCommandHandler and not exposed publicly.
- Exceptions from SendAsync propagate; callers may need to log or retry as part of larger connection management.
`HandlePingAsync` handles an IRC `PING` by replying with a `PONG` to keep the connection alive. It reads the first parameter from the incoming `IrcMessage.Parameters` as the token, or falls back to `ServerName` if none is provided, and sends the response via `_conn.SendAsync` using the IRC format `":{ServerName} PONG {ServerName} :{token}"`.
Handles the PRIVMSG command by validating parameters, ensuring the sender is registered, and routing channel-targeted messages to the EchoHub chat service. It rejects private messages (targets that do not start with '#') with an appropriate error and surfaces delivery failures back to the IRC client.
Handles an IRC PRIVMSG from a connected user by first ensuring the user is registered, then validating that the message has enough parameters and targets a channel. If parameters are missing, it replies with `ERR_NEEDMOREPARAMS`; if the target is not a channel (does not start with `#`), it replies with `ERR_NOSUCHNICK` and instructs to use channels. It then maps the IRC channel to an internal EchoHub channel via `IrcToEchoHubChannel` and forwards the message content to the chat service through [`SendMessageAsync`](../EchoHub.Server/Services/ChatService.cs.md), supplying the current connection's user id, nickname, channel name, message content, and connection id. If the chat service reports an error, it communicates it back to the client with `ERR_CANNOTSENDTOCHAN` for the affected channel.
## Remarks
This method acts as a boundary between IRC protocol handling and the EchoHub chat system. It enforces channel-only messaging for PRIVMSG, consolidates parameter validation and error reporting via IRC numeric replies, and delegates the actual delivery to a dedicated chat service. By encapsulating channel-name translation (IrcToEchoHubChannel) and the delivery call (SendMessageAsync), it keeps command handling focused and testable, while remaining resilient to mapping failures and chat-service errors.
## Notes
- Requires the current connection to be registered; otherwise the operation is short-circuited.
- If the PRIVMSG target does not begin with '#', a private-message error is returned: ERR_NOSUCHNICK with a hint to use channels.
- If channel name mapping returns null, the method exits without performing delivery.
- If SendMessageAsync reports an error, the client receives ERR_CANNOTSENDTOCHAN to indicate delivery failure to the channel.
In short, it acts as the IRC surface to the EchoHub chat layer for channel-based private messages, performing parameter validation, channel resolution, and error propagation in a single, cohesive flow.
Handles a quit event by sending an IRC ERROR line to the active connection that signals the closing of the link. It derives the quit reason from the first parameter of the incoming IrcMessage when provided, otherwise it uses 'Client quit' as a default.
## Remarks
Centralizes the termination messaging for quit scenarios, ensuring a consistent closing notice across paths that terminate a connection. It formats the message with the current nickname and the resolved quit reason, and delegates the actual network transmission to _conn.SendAsync, keeping the higher-level quit flow simple and testable.
## Example
```csharp
// Example usage within the same class (quit with a reason)
- This method only sends the closing line; it does not by itself terminate the connection. The caller should close the connection after the message is sent.
- It relies on msg.Parameters[0] as the quit reason; if there are multiple parameters, only the first is used.
- Assumes msg.Parameters is non-null; if it's null, this will throw a NullReferenceException.
HandleQuitAsync is a private async method that processes a quit request by sending an IRC `ERROR` message to close the client connection. It derives the quit reason from the first element of the [`IrcMessage`](IrcMessage.cs.md)'s `Parameters` (falling back to the literal `Client quit` if none is provided) and includes the nickname via `_conn.Nickname` in the response by calling `_conn.SendAsync` with the string `ERROR :Closing Link: <nickname> (<quitMessage>)`.
HandleTopicAsync processes the IRC TOPIC command for a channel. It verifies the caller is registered, resolves the channel name from the command parameters, and either sends the current topic or updates it via the channel service using the provided topic text. On a successful update, it broadcasts the change to SignalR clients and echoes the topic back to the IRC client; on failure it returns an appropriate IRC numeric error.
HandleTopicAsync processes an IRC TOPIC command for a channel. It ensures the caller is registered, validates parameters, converts the IRC channel name to the internal channel via `IrcToEchoHubChannel`, and then either returns the current topic with `SendChannelTopicAsync` when only the channel is provided or updates the topic via `_channelService.UpdateTopicAsync` using the current user's ID, passing `null` for an empty topic. If the update fails, it replies with an IRC numeric error—`ERR_NOSUCHCHANNEL` when the channel is not found, otherwise `ERR_CHANOPRIVSNEEDED`—including the error message; on success, it broadcasts the updated channel to connected web clients via `_chatService.BroadcastChannelUpdatedAsync` and echoes the new topic back to the IRC client with `SendAsync` using the `TOPIC` command.
## Remarks
This method acts as the integration point between IRC command handling, domain services, and client notifications. It relies on the authentication check (RequireRegisteredAsync) and uses the channel service to persist topic changes while informing connected clients through the chat service. Numeric errors are produced through IrcNumericReply based on the nature of the failure (non-existent channel vs. insufficient privileges), ensuring correct IRC protocol behavior. When a topic is cleared, a whitespace topic is treated as null and passed to UpdateTopicAsync, signaling a topic removal.
This method centralizes the TOPIC command flow: it validates the caller, resolves the channel, performs the update, and coordinates notification to both SignalR clients and the IRC client. It also maps domain errors to IRC numeric replies to preserve protocol semantics across layers.
## Notes
- The method returns early if the caller is not registered or if there are insufficient parameters, preventing unintended state changes.
- It uses a null-forgiving operator on UserId when updating the topic; preconditions ensure a valid user context.
- Topic clearing is achieved by passing null to UpdateTopicAsync when the provided topic string is whitespace.
- Error handling maps ChannelError.NotFound to ERR_NOSUCHCHANNEL and all other failure cases to ERR_CHANOPRIVSNEEDED, aligning with IRC protocol expectations.
Handles the USER command as part of the IRC registration handshake. It first rejects re-registration attempts, then validates that enough parameters are present, stores the provided username and real name on the connection, and finally triggers registration completion if a nickname has already been supplied.
## Remarks
This method encapsulates the user-side portion of the registration flow, coordinating between the incoming command data (via IrcMessage.Parameters) and the connection state. By separating the completion trigger (TryCompleteRegistrationAsync) from initial USER parsing, it keeps the registration logic cohesive and allows the NICK/USER agreement to occur in any order. It relies on the server-generated numeric replies to communicate errors back to the client and uses the connection state to decide when registration can advance.
## Notes
- Parameter indexing assumes four parameters for a valid USER command; if fewer are provided, the handler responds with ERR_NEEDMOREPARAMS. The RealName is taken from Parameters[3], which is a potential source of off-by-one mistakes if the protocol is extended or parameters are reformatted.
- A full registration is only completed when a nickname is already present; otherwise, the method merely populates Username and RealName and leaves completion to a later trigger when Nickname arrives.
- The code does not validate that Username or RealName are non-empty; additional validation may be needed if stricter user data integrity is required.
HandleUserAsync is the private asynchronous handler for processing the IRC USER command as part of the client registration flow. It first guards against re-registration by sending the IRC numeric `ERR_ALREADYREGISTERED` via `_conn.SendNumericAsync` when `_conn.IsRegistered` is true, and then returns. If there are fewer than four parameters, it responds with `ERR_NEEDMOREPARAMS` and terminates early. When invoked with a valid parameter set, it assigns the username from `msg.Parameters[0]` to `_conn.Username` and the real name from `msg.Parameters[3]` to `_conn.RealName`. Finally, if a nickname has already been established (`_conn.Nickname` is not null), it awaits `TryCompleteRegistrationAsync()` to advance the registration process.
Responds to an IRC WHO request for a channel by listing online users and signaling completion. It is invoked when a registered client asks for the current participants of a channel; it maps the supplied channel parameter to EchoHub's channel, retrieves online users via the chat service, and streams RPL_WHOREPLY rows followed by RPL_ENDOFWHO to the client.
HandleWhoAsync processes a WHO request for a channel by validating the caller and translating the IRC channel into the EchoHub channel, then streaming the current online users. It first ensures the client is registered, validates that a channel parameter is provided, and derives the internal channel name with `IrcToEchoHubChannel`. If any of these steps fail, it exits without emitting data. When a valid channel is obtained, it fetches online users via `_chatService.GetOnlineUsersAsync(channelName)` and, for each user, sends a `RPL_WHOREPLY` using [`IrcNumericReply`](IrcNumericReply.cs.md) data, encoding the channel, user, host (`echohub`), server, user nickname, away state, hop count, and display name (falling back to the username when necessary). After listing all users, it signals completion with `RPL_ENDOFWHO`.
This method is the IRC-facing surface that translates EchoHub's online-user model into IRC protocol replies, making it the point of integration for WHO-style channel listings. The flow is fully asynchronous and relies on the [`UserStatus`](../EchoHub.Core/Models/UserStatus.cs.md) enum to determine the away flag, as well as the defined `RPL_WHOREPLY`/`RPL_ENDOFWHO` numeric replies for protocol correctness.
HandleWhoisAsync processes the IRC WHOIS command by ensuring the requester is registered, validating the target nick parameter, and then assembling and sending the standard WHOIS information for that user. It fetches the user profile, emits the appropriate WHOIS numeric replies (and the away/idle data when available), and gracefully reports when the target nick does not exist.
## Remarks
By translating EchoHub's channel membership into IRC WHO semantics, this method acts as the bridge between the IRC protocol and the chat model. It performs early guards (registration and parameter validation) before querying the chat service, ensuring consistent behavior and preventing unnecessary work for unauthenticated callers. Each user is emitted with a RPL_WHOREPLY line containing their nick, username, server, and away/here flag, followed by a final EndOfWho line to signal completion.
This method acts as a protocol adapter that wires together user data and channel memberships to produce a coherent WHOIS response. It coordinates between `_userService` for profile data, `_chatService` for channel membership, and `_conn` for sending IRC numerics, encapsulating the protocol-specific choreography in a single, testable unit. The logic defensively handles missing profile data and optional information (channels, away message) to align with RFC-like WHOIS expectations while keeping the flow readable and isolated from business rules.
## Notes
- Always emits an End of WHO line even if the channel has no online users.
- Away vs. here status is encoded as 'G' for away and 'H' for present, matching IRC conventions.
Converts a raw IRC channel into a canonical EchoHub channel name by stripping the leading '#', lowercasing, and trimming the remainder, returning null if the result does not satisfy ValidationConstants.ChannelNameRegex. This is used when bridging IRC channels to EchoHub to obtain a policy-compliant channel identifier.
## Remarks
Centralizes the logic for translating IRC-style channels into EchoHub identifiers and enforces channel naming policy via ValidationConstants.ChannelNameRegex. It returns a lowercase, trimmed name when valid, or null when the input cannot be mapped, allowing callers to handle non-mappable channels explicitly.
## Notes
- If ircChannel is null, this method will throw a NullReferenceException; callers should ensure a non-null value before calling.
- Results are always lowercase due to ToLowerInvariant, providing a consistent channel namespace.
- A non-matching input yields null rather than an exception, signaling an unmapped channel to the caller.
IrcToEchoHubChannel converts a raw IRC channel name into EchoHub's internal channel identifier, returning null when the input cannot be mapped. It requires the input to start with the '#' prefix and to be at least two characters long; it then drops the leading '#', lowercases the remainder invariantly, trims whitespace, and validates the result against the central channel-name pattern provided by `ValidationConstants.ChannelNameRegex()`. If the name matches, the canonical, lowercased name is returned; otherwise null. This function is typically invoked when translating IRC channel references into EchoHub's normalized channel namespace, ensuring downstream logic always works with validated, consistent channel names rather than arbitrary IRC inputs.
RequireRegisteredAsync is a small helper that enforces a precondition: the client connection must be registered before proceeding with commands that require registration. It returns true when the connection is already registered; otherwise it sends the IRC error reply ERR_NOTREGISTERED and returns false. Callers await this method to guard subsequent operations and avoid duplicating boilerplate checks across command handlers.
`RequireRegisteredAsync` checks whether the IRC connection is registered and returns true when it is. If not registered, it sends the standard `ERR_NOTREGISTERED` reply using `SendNumericAsync` with `ServerName`, `IrcNumericReply.ERR_NOTREGISTERED`, and the message `":You have not registered"`, then returns false.
## Remarks
This abstraction centralizes the registration precondition and the associated user feedback. It guarantees consistent behavior by issuing the standard ERR_NOTREGISTERED along with the message You have not registered, matching the IRC protocol's expectations, and it short-circuits command execution when the precondition isn’t met.
## Example
```csharp
// Usage: ensure the user is registered before issuing a command that requires registration
if(!awaitRequireRegisteredAsync())
{
return;// bail out if not registered
}
// proceed with the operation that requires registration
```
## Notes
- Ensure the caller returns immediately when RequireRegisteredAsync() returns false to avoid sending duplicate replies.
- This helper assumes the underlying connection (_conn) and the server name (ServerName) are initialized; null references may occur if called too early.
Conceptually, this method centralizes the precondition for commands that require a registered session, avoiding duplicated checks across handlers. It relies on `_conn` to inspect `IsRegistered`, and on `ServerName` and `IrcNumericReply.ERR_NOTREGISTERED` to deliver a consistent IRC-compliant error. By returning a boolean, it makes the caller's flow straightforward: proceed when true, bail when false.
---
@@ -738,15 +608,15 @@ public async Task RunAsync(CancellationToken ct)
**Returns:** `Task`
Runs an asynchronous loop that continuously reads lines from the IRC connection, trims trailing CR/LF, ignores blank lines, and dispatches each non-empty message to the IRC command handler until cancellation is requested. This is the core IO loop for processing incoming IRC traffic in the command handler lifecycle; you start it to begin processing and cancel it to stop.
Runs an asynchronous, cancellation-aware loop that reads lines from the IRC connection via `_conn.ReadLineAsync(ct)`, stops when `line` is `null`, trims trailing CR/LF, skips blank lines, logs each received line with `_logger.LogDebug("IRC < {Id}: {Line}", _conn.ConnectionId, line)`, parses the line into an [`IrcMessage`](IrcMessage.cs.md) using `IrcMessage.Parse(line)`, and dispatches the resulting message to `HandleCommandAsync(msg)`. This method is the central inbound processor for an IRC connection: it bridges the raw socket input to the higher-level command handling logic and continues running until the provided `CancellationToken ct` signals cancellation or the connection ends.
## Remarks
RunAsync is the primary lifecycle loop for the IRC command processor. It reads a raw line via _conn.ReadLineAsync(ct), cleans trailing CR/LF, and skips empty lines before turning the line into an IrcMessage with IrcMessage.Parse. The resulting message is passed to HandleCommandAsync for per-command processing, and any exceptions thrown during that processing are caught and logged to avoid tearing down the loop. Only the HandleCommandAsync call is wrapped in the try-catch; errors in reading, parsing, or line pre-processing may bubble up if they throw, which means callers should supervise the task accordingly.
This method is the primary inbound processor for a single IRC connection, isolating IO, parsing, and command dispatch from higher-level application logic. It logs critical diagnostic information: per-line debugging via `_logger.LogDebug` and per-command failures via `_logger.LogError`, including the command name and the nick when available. By catching exceptions only around `HandleCommandAsync(msg)` it ensures that a failure in handling one command does not crash the entire loop, preserving resilience.
## Notes
- Exceptions from ReadLineAsync or IrcMessage.Parse are not caught here; they could terminate the loop.
- The loop ends when a null line is read (end of stream) or when the cancellation token is canceled.
- Whitespace-only lines are ignored; lines are trimmed before parsing.
- The call to `IrcMessage.Parse(line)` occurs outside the `try` block that guards `HandleCommandAsync(msg)`; a parsing error could bubble up and terminate the loop. Consider moving parsing inside the try/catch or adding its own guard.
Fetches the current topic for the specified channel and sends the corresponding IRC numeric to the client. It queries the channel service for (topic, exists) and, if the channel exists, emits RPL_TOPIC when a topic is set or RPL_NOTOPIC when no topic is configured; if the channel doesn't exist, it returns without replying.
This private helper fetches the current topic for a channel and, if the channel exists, delivers the appropriate IRC numeric reply to the connected client. It calls `_channelService.GetChannelTopicAsync(channelName)` to obtain `(topic, exists)` and, depending on the result, returns early when the channel doesn't exist, sends `RPL_TOPIC` with `#<channelName> :<topic>` when a topic is set, or sends `RPL_NOTOPIC` with `#<channelName> :No topic is set` when there is no topic.
## Remarks
By centralizing the topic-resolution and numeric-emission logic in a single private method, this symbol encapsulates the IRC topic-response behavior for channel-related command flow. It hides the implementation details of IrcNumericReply mappings behind a concise interface and ensures consistent message formatting (channel name prefixed with '#', topic payload prefixed with ':') when interacting with the connection and channel services.
This method acts as a small integration point between channel-data access and IRC protocol messaging. By encapsulating the topic-notification logic, it coordinates `_channelService` (data) and `_conn` (connection) to produce consistent numeric replies via [`IrcNumericReply`](IrcNumericReply.cs.md) constants, reducing duplication across the command-handling code. Its private scope signals it's an internal helper used by higher-level IRC commands, keeping the channel-topic flow centralized.
## Notes
- If exists is false, the method returns early with no notification to the client.
- When a channel exists but has no topic, a RPL_NOTOPIC reply is sent with the message "No topic is set".
- If the channel does not exist (`exists` is false), the method returns without sending any reply, which can appear as a missing response to the client; callers should ensure channel existence or handle this case.
## Dependencies
- IrcNumericReply
## Dependency APIs (verified signatures)
- class [`IrcNumericReply`](IrcNumericReply.cs.md) (`src/EchoHub.Server.Irc/IrcNumericReply.cs`)
Translates a channel operation error into the corresponding IRC numeric response and sends it to the client for the specified channel. It chooses the numeric based on result.Error (NotFound -> ERR_NOSUCHCHANNEL, Forbidden -> ERR_CHANOPRIVSNEEDED, otherwise ERR_KEYSET) and delivers a message containing the channel (prefixed with '#') and the human-readable error via _conn.SendNumericAsync(ServerName, numeric, `#${channelName} :${result.ErrorMessage}`). This method centralizes the error reporting for channel-mode operations so callers don't duplicate the mapping and formatting logic.
## Remarks
By centralizing the error-to-numeric mapping, this method ensures consistent client feedback and prevents duplication of channel-name formatting and error-message construction across callers. It relies on the surrounding class’s _conn and ServerName being available; changes to the mapping or messaging format would affect all mode-error reports produced by this helper.
## Notes
- The error mapping is not exhaustive: any ChannelError value not explicitly NotFound or Forbidden will default to ERR_KEYSET.
- This method is private and intended solely for internal command-handling use; it is not part of the public API.
Translates a channel-mode operation failure into the appropriate IRC numeric for the target channel and forwards it to the server. When a mode operation fails for the given `channelName`, the method maps the domain error to an IRC numeric using a switch over [`ChannelError`](../EchoHub.Core/DTOs/CommonDtos.cs.md) (NotFound -> `IrcNumericReply.ERR_NOSUCHCHANNEL`, Forbidden -> `IrcNumericReply.ERR_CHANOPRIVSNEEDED`, otherwise `IrcNumericReply.ERR_KEYSET`) and sends a message using `_conn.SendNumericAsync(ServerName, numeric, `$"#{channelName} :{result.ErrorMessage}"`)`.
SendMotdAsync is an internal helper that transmits the server's Message of the Day (MOTD) to the connected client. It validates the configured Motd; if it is missing or whitespace it replies with ERR_NOMOTD and stops. Otherwise it sends a MOTD banner with RPL_MOTDSTART, then each newline-delimited line as an RPL_MOTD, trimming CR characters, and ends with RPL_ENDOFMOTD.
This method consolidates MOTD delivery behind a private surface, so higher-level Irc command handlers don't need to know the exact numeric codes or line-breaking semantics. It depends on _conn for transport and _options for the Motd value, and it's a private method intended to be invoked by the MOTD-related command flow.
SendMotdAsync is a private asynchronous helper that delivers the server's Message of the Day to the current IRC connection. It checks the configured `Motd` on `_options` and, if missing, responds with the IRC error code `ERR_NOMOTD`; otherwise it streams the MOTD lines between `RPL_MOTDSTART` and `RPL_ENDOFMOTD` using `RPL_MOTD` for each line. This method formats each line by trimming a trailing carriage return and sends one line per message, adhering to the IRC protocol expectations.
## Remarks
Encapsulates the formatting and transport of MOTD to ensure consistent behavior across the server. By isolating the MOTD delivery, it keeps the command-handling code focused on protocol logic rather than presentation details.
`SendMotdAsync` centralizes MOTD delivery to ensure consistent IRC protocol formatting and behavior. It relies on `_conn` to emit numeric replies and on the [`IrcNumericReply`](IrcNumericReply.cs.md) constants to signal the start, each line, and the end of the MOTD, while consulting the configured `Motd` via the `_options` object. This encapsulation prevents duplication and makes it straightforward to adjust MOTD formatting in one place.
## Notes
- If Motd is null or whitespace, the method sends ERR_NOMOTD and returns without sending any MOTD lines.
- Each MOTD line is sent as a separate RPL_MOTD message; the code splits on '\n' and trims a trailing '\r' from each line to normalize Windows-style endings. A trailing newline in Motd may produce an empty MOTD line.
- All sends are awaited asynchronous calls to the connection; exceptions propagate to the caller.
- The method trims trailing carriage returns (`'\r'`) from each MOTD line to gracefully handle Windows-style line endings when sending lines via `RPL_MOTD`.
- If `_options.Motd` is null or whitespace, the method short-circuits and emits `ERR_NOMOTD` before attempting any `RPL_MOTD` messages.
- MOTD lines are sent individually in order, one `RPL_MOTD` message per line, followed by `RPL_ENDOFMOTD` to mark completion.
Sends the channel’s NAMES list to the IRC client by querying the chat service for online users in the channel, producing a space-separated set of nicknames, and then emitting two standard IRC numerics: RPL_NAMREPLY with the channel and nicklist, and RPL_ENDOFNAMES to mark completion. This method is invoked when handling a NAMES request for a channel, and it centralizes the formatting and numeric-codes so callers don't have to build the response themselves.
Responds to an IRC NAMES request for a channel by collecting the currently online users and emitting the standard numeric replies that enumerate channel members. It calls `_chatService.GetOnlineUsersAsync(channelName)` to obtain user objects, builds a space-separated list of their `Username`s, and sends two numeric replies: first `IrcNumericReply.RPL_NAMREPLY` with the channel's nicklist via `_conn.SendNumericAsync`, and then `IrcNumericReply.RPL_ENDOFNAMES` to mark the end of the list.
## Remarks
This keeps NAMES formatting centralized and aligns with the IRC protocol surface exposed by IrcNumericReply. It delegates data retrieval to _chatService and transmission to _conn, making the implementation resilient to channel naming and user list changes. It also ensures the end-of-list is always signaled after the list is sent, which is essential for IRC clients to know the response is complete.
This method encapsulates the protocol details of responding to the IRC `NAMES` command for a channel. It isolates the discovery of online users from the formatting and emission of the numeric replies, ensuring consistent NAMES responses and simplifying the caller's responsibilities.
## Notes
- If no online users are found, the NAMES reply will carry an empty nicklist while still issuing EndOfNames; clients should handle an empty list gracefully.
- Any exceptions raised by GetOnlineUsersAsync or SendNumericAsync bubble up to the caller, so this method assumes the surrounding command handler will decide how to respond to errors.
- If there are no online users, the constructed `nicks` string will be empty, but an `RPL_NAMREPLY` line will still be emitted followed by `RPL_ENDOFNAMES`.
- The method is private and relies on `_chatService` and `_conn` being available; callers must ensure the surrounding context handles validation and errors appropriately.
Sends the IRC welcome burst to a newly connected client by issuing the standard numeric replies (RPL_WELCOME, RPL_YOURHOST, RPL_CREATED, RPL_MYINFO, RPL_ISUPPORT) and then starts the MOTD flow via SendMotdAsync. It uses the current connection's nickname and the server name to populate the messages, and awaits each dispatch to preserve the canonical handshake order.
This private async method emits the initial IRC handshake to the connected client by sending a series of standard numeric replies. It reads the nickname from `_conn.Nickname`, uses `ServerName` as the server identity, and dispatches the numerics `RPL_WELCOME`, `RPL_YOURHOST`, `RPL_CREATED`, `RPL_MYINFO`, and `RPL_ISUPPORT` via `_conn.SendNumericAsync`. After sending these banners, it calls `SendMotdAsync` to deliver the MOTD and complete the handshake.
## Remarks
It centralizes the initial handshake, ensuring a consistent greeting sequence for every new user. By consuming IrcNumericReply codes and composing messages with the live nickname, server name, and current time, it guarantees the client receives both identification and capability information before proceeding. The method delegates the final output of the MOTD to SendMotdAsync, keeping the handshake concerns isolated from the MOTD generation.
This method centralizes the handshake so every connection receives a consistent welcome, isolating IRC protocol formatting from higher-level command handling. It relies on the [`IrcNumericReply`](IrcNumericReply.cs.md) constants to produce the standard numerics and on `_conn` to transmit messages, keeping the transport details out of the handshake logic. The method assumes `_conn.Nickname` is non-null at the time it runs, as evidenced by the null-forgiving read.
## Notes
- Relies on _conn and Nickname being non-null; the null-forgiving operator means a null nickname could yield a greeting with an empty nickname.
- RPL_CREATED uses DateTimeOffset.UtcNow; this stamps the handshake time rather than the server creation date, which may be intentional for the MOTD moment but can be misleading if interpreted as server age.
- Be aware that `_conn.Nickname` is read with a null-forgiving operator; if nickname isn't set yet, a runtime `NullReferenceException` could occur. Ensure the nickname is established earlier in the connection sequence before calling this method.
Finalizes the user's registration by completing the authentication handshake and establishing an active session. It guards against concurrent registration work by returning early if capability negotiation is still in progress or the user is already registered. If SASL-based authentication has already succeeded (IsAuthenticated and UserId is not null), it marks the connection as registered, notifies the chat service of the connected user, and sends the welcome burst to complete onboarding.
If SASL authentication is not yet complete, it enforces a password-based login: a missing password results in an IRC error and authentication failure. When a password is supplied, it delegates to the user service to authenticate; if that fails, it attempts to register a new user with the provided nickname and password. On a successful outcome, it stores the resulting UserId and Username on the connection, marks the connection as authenticated and registered, signals the chat service that the user has connected, and sends the welcome burst.
The method is a private helper used during the IRC session setup to ensure the connection transitions to a fully authenticated and registered state before normal chat activity begins.
Handles an IRC WHOIS command by querying the target nick's user profile and returning the standard WHOIS information to the requester. It validates the connection is registered, extracts the nick from the message, fetches the user profile via _userService, and then dispatches a sequence of numeric replies: WHOIS user, WHOISSERVER, and optionally WHOISCHANNELS, RPL_AWAY if the user is away, and RPL_WHOISIDLE with idle and sign-on times, finishing with RPL_ENDOFWHOIS. If no profile exists for the nick, it replies with ERR_NOSUCHNICK. The method relies on asynchronous services and formats times using the profile's LastSeenAt and CreatedAt to populate idle and sign-on data.
Asynchronously completes a client's registration by deciding whether SASL authentication has already succeeded, or whether to perform PASS-based login to complete or create the user. If SASL is already authenticated ( `_conn.IsAuthenticated` and `_conn.UserId` is not null ), it marks the connection as registered, notifies the chat subsystem via [`UserConnectedAsync`](../EchoHub.Server/Services/ChatService.cs.md), and then triggers the welcome sequence with `SendWelcomeBurstAsync`. If not SASL-authenticated, it requires a password; if missing, it returns `ERR_PASSWDMISMATCH` and a generic error. Otherwise it calls `_userService.AuthenticateUserAsync(_conn.Nickname, _conn.Password)` and, on failure, falls back to `_userService.RegisterUserAsync(_conn.Nickname, _conn.Password)`; on success it binds the resulting user to the connection, updates `_conn.UserId`, `_conn.Nickname`, and flags `_conn.IsAuthenticated` and `_conn.IsRegistered`, then notifies the chat service and sends the welcome burst.
## Remarks
This method centralizes the WHOIS response logic for a given nickname, encapsulating the sequence of IRC numeric replies required to convey user information. It coordinates multiple collaborators (the connection, user service, and chat service) to assemble a consistent, standards-compliant response stream without leaking implementation details to callers. The precondition that the connection must be registered is enforced up front, ensuring WHOIS handling only occurs in an appropriate session context.
This method encapsulates the end-to-end registration/authentication handoff, coordinating between the connection state, the user service, and the chat subsystem. It guards against re-entrancy by exiting early when capability negotiation is in progress or the connection is already registered, and it ensures a consistent welcome sequence is delivered once authentication or registration succeeds.
## Notes
- If the target profile cannot be found, the handler emits ERR_NOSUCHNICK and aborts further replies.
- Idle time is calculated from LastSeenAt and sign-on time from CreatedAt; both are emitted via RPL_WHOISIDLE when available.
- The RPL_WHOISCHANNELS reply is sent only when the user belongs to one or more channels; otherwise this section is omitted.
- Away status (RPL_AWAY) is emitted only if the profile.Status is Away and a StatusMessage exists.
- Be mindful that the initial logging emits user-identifying state (e.g. `_conn.Nickname`, `_conn.Username`); ensure logging remains appropriate for your privacy and security policy.
RunListenerCore["RunListenerAsync body"] --> StartListener["Start TcpListener and log listening"]
StartListener --> RegisterCancel["Register ct to stop listener"] --> ListenerLoop{"ct.IsCancellationRequested"}
ListenerLoop -->|"no"| AcceptClient["AcceptTcpClientAsync"] --> SpawnHandle["Spawn HandleClientAsync(tcpClient, useTls) as fire and forget"] --> ListenerLoop
ListenerLoop -->|"yes"| StopListener["Stop listener and return from RunListenerAsync"]
SpawnHandle --> HandleClientStart["HandleClientAsync: get stream"] --> UseTls{"useTls"}
GetInChannel["GetConnectionsInChannel(channelName) returns authenticated IrcClientConnection in channel"]
EndHandle --> End
LogDisabled --> End
StopListener --> End
AddConnection --> EndHandle
```
```csharp
@@ -37,12 +50,12 @@ public sealed class IrcGatewayService : BackgroundService
```
Provides a hosted IRC gateway that listens for incoming TCP (and optional TLS) client connections and dispatches each to an IrcCommandHandler that bridges IRC protocol traffic to the application's chat, user and channel services. Start this BackgroundService when you want the application to accept IRC client connections without manually managing TcpListeners, TLS handshakes, or per-connection handler wiring.
An always-on hosted gateway that accepts raw TCP (optionally TLS) connections and exposes an IRC-compatible surface backed by the EchoHub services. `IrcGatewayService` reads configuration from [`IrcOptions`](IrcOptions.cs.md), listens on the configured ports, accepts incoming `TcpClient` connections, wraps them in [`IrcClientConnection`](IrcClientConnection.cs.md) objects, and hands each connection to an [`IrcCommandHandler`](IrcCommandHandler.cs.md) that bridges IRC commands to the application services ([`IChatService`](../EchoHub.Core/Contracts/IChatService.cs.md), [`IUserService`](../EchoHub.Core/Contracts/IUserService.cs.md), [`IChannelService`](../EchoHub.Core/Contracts/IChannelService.cs.md), [`IMessageEncryptionService`](../EchoHub.Core/Contracts/IMessageEncryptionService.cs.md)). Reach for `IrcGatewayService` when you want to run an IRC-facing adapter for the EchoHub system rather than implementing socket handling and protocol dispatch yourself.
## Remarks
This BackgroundService reads configuration from IrcOptions and opens one or two listeners (plain and optionally TLS) for the ports configured. For every accepted TcpClient it creates an IrcClientConnection, stores it in an internal ConcurrentDictionary keyed by ConnectionId, and constructs an IrcCommandHandler (using IChatService, IUserService, IChannelService and IMessageEncryptionService from DI) to drive the connection. The service centralizes lifecycle concerns: listener startup/shutdown, TLS handshake and per-connection dispatching so higher-level application code can focus on chat/user/channel logic implemented in the injected services.
`IrcGatewayService` is a long-running `BackgroundService` that centralizes network-level concerns for the IRC gateway: socket listening, optional TLS handshake, acceptance of clients, and registration of active connections in the concurrent `_connections` map. It delegates protocol parsing and business-logic handling to [`IrcCommandHandler`](IrcCommandHandler.cs.md), resolving the required domain services from the DI `IServiceProvider` per connection so the gateway stays thin and focused on I/O and lifecycle. The service uses [`IrcOptions`](IrcOptions.cs.md) to control whether the gateway is enabled, which ports to bind, and whether to offer TLS; listeners are run as independent tasks and shut down when the host cancellation token is triggered.
## Notes
-If IrcOptions.Enabled is false the service logs and returns immediately; no listeners are started.
-TLS is only attempted when TlsEnabled is true and TlsCertPath is provided; TLS handshake failures are logged and the client connection is closed.
- The Connections collection is a ConcurrentDictionary and entries are added when clients connect. Public helper methods (GetAllConnections, GetConnectionsInChannel) filter by IrcClientConnection.IsAuthenticated — use those to obtain the set of active, authenticated clients rather than inspecting the raw dictionary directly.
-TLS requires a valid `IrcOptions.TlsCertPath` and password when `IrcOptions.TlsEnabled` is true; a failed TLS handshake will be logged and the connection closed (the code logs "TLS handshake failed" on exception).
-Active connections are tracked in the `ConcurrentDictionary``_connections` and can be inspected via `GetConnectionsInChannel` and `GetAllConnections`; the dictionary makes concurrent adds/removes safe, but callers should expect the set to change while enumerating.
- The provided source was truncated inside `HandleClientAsync` in the task payload; I could not verify whether each [`IrcClientConnection`](IrcClientConnection.cs.md) is always removed from `_connections` and whether streams/clients are always disposed on disconnect. If you rely on deterministic cleanup, inspect the full `HandleClientAsync` implementation to confirm that connections are removed and resources are disposed on normal disconnect and on error.
IrcMessage is a parsed representation of an IRC protocol line that exposes the optional Prefix, the Command, and the Parameters that form the line's arguments; if a trailing payload is present, Trailing provides access to it. Use IrcMessage.Parse to convert a raw line into a structured object and inspect the command and its arguments without manual parsing.
IrcMessage is a parsed representation of a single IRC protocol line. It exposes an optional `Prefix`, the `Command`, and the list of `Parameters` extracted from the line, with `Trailing` representing the last parameter when present; use `Parse` to convert a raw IRC line into this structured form so you can inspect the command and its arguments without manual parsing.
## Remarks
IrcMessage encapsulates the parsing result and keeps IRC-logic separate from application code. Its properties are immutable (init-only), which makes parsed messages safe to share across components after parsing. Trailing is a derived convenience that reflects the trailing payload via the Parameters collection, aligning with the IRC grammar without introducing extra mutable state.
IrcMessage centralizes IRC line parsing by translating the textual format into explicit properties. The `Prefix` is optional, `Command` is the verb, and `Parameters` preserve order, with the final parameter commonly used as the trailing content in IRC messages. Accessing `Trailing` provides a convenient single point for the trailing payload without scanning the list; be mindful that `Parameters` is a `List<string>` and can be mutated if you obtain a reference.
- Trailing property returns the last parameter when any parameters exist; it's a convenience for the trailing payload and assumes a leading ':' in the raw line to populate it. If there was no trailing parameter in the line, Trailing will reflect the final parameter but may not be semantically a trailing payload.
- The `Parameters` collection is a mutable `List<string>`; if you need a stable, immutable view, clone it before usage.
- If there are no parameters, `Trailing` will be `null`; the property simply reflects the last entry of `Parameters` when any parameters exist.
@@ -8,14 +8,4 @@ public static class IrcMessageFormatter
```
IrcMessageFormatter is a small, focused helper that converts a MessageDto into IRC PRIVMSG lines suitable for delivery in an IRC channel. It handles plain text and CTCP ACTION content, prefixes replies with the standard '> nick: snippet | ' format, and renders attachments as separate URL lines with concise type tags, using an absolute URL when a public base URL is supplied.
## Remarks
It centralizes the IRC-specific formatting and line-breaking logic used by the server when presenting messages to IRC clients, shielding callers from the quirks of the IRC protocol (such as per-line length limits and CTCP wrapping). The private FormatReplyPrefix creates a consistent context string for replies, including redaction of room ciphertext when needed and truncating long snippets to a safe length. Attachments are surfaced as individual lines with a small tag ([Image: ...], [Audio: ...], or [File: ...]) followed by an absolute URL, aligning with common IRC client behavior and improving link reliability. Embeds are appended using FormatEmbed, enabling rich previews where supported.
## Notes
- The FormatMessage path enforces line-length constraints via MaxIrcLineContentBytes, causing long content to be split across multiple PRIVMSG lines as needed.
- Encrypted-reply content is masked by the ciphertext-detection logic (e.g., [encrypted]) to avoid leaking room ciphertext in IRC.
- Absolute URL generation relies on ToAbsoluteUrl and the optional publicBaseUrl; without a base URL, attachments may render with their original (potentially relative) URLs.
IrcMessageFormatter formats a [`MessageDto`](../EchoHub.Core/DTOs/ChatDtos.cs.md) into IRC `PRIVMSG` lines for an EchoHub channel. It splits content into IRC-friendly chunks (up to 400 bytes per line), handles CTCP ACTION content, and prefixes replies with the compact ``> nick: snippet | `` prefix when a reply exists. Attachments are emitted as separate lines with absolute URLs (constructed from the optional `publicBaseUrl`) and labeled by kind (Image, Audio, or File). If present, embeds are appended via the embed formatter. When a reply references encrypted room content, the snippet is shown as `[encrypted]` and non-encrypted snippets are truncated to 80 characters to fit IRC constraints.
@@ -8,17 +8,11 @@ public static class IrcNumericReply
```
IrcNumericReply is a static container of string constants that encode the standard IRC protocol numeric replies. It centralizes the protocol’s numeric codes so developers can reference them by name (e.g., RPL_WELCOME, ERR_UNKNOWNCOMMAND) instead of sprinkling literal strings throughout the codebase. The constants are organized by functional areas such as registration, MOTD, channel operations, list operations, WHO/WHOIS, away status, mode, errors, and SASL.
IrcNumericReply is a centralized, static container of IRC protocol numeric reply codes represented as strings. It defines constants for common server replies and errors, organized by category (Connection registration, MOTD, Channel operations, LIST, WHO/WHOIS, AWAY, MODE, Errors, SASL). Developers reference these constants, such as `IrcNumericReply.RPL_WELCOME` or `IrcNumericReply.ERR_NOSUCHNICK`, when constructing or interpreting IRC protocol messages instead of hard-coding literals. This reduces repetition, prevents typos, and makes maintenance safer if the IRC spec evolves or expands the set of recognized replies.
## Remarks
Having all codes in one static class provides a single source of truth and makes it straightforward to update or extend the set as the IRC spec evolves. It also clarifies intent at call sites: emitting an IRC reply uses the corresponding constant rather than a magic string, and parsing branches can compare against these constants with confidence. This abstraction keeps server and client code aligned on canonical codes without duplicating literals.
## Example
```csharp
// Example: emit a welcome reply using the canonical code
stringcode=IrcNumericReply.RPL_WELCOME;// "001"
stringreply=$":server {code} Welcome to the IRC network";
```
IrcNumericReply provides a canonical reference for IRC numeric codes, solving the problem of scattered, magic string literals across message handling, parsing, and logging. It fits with any component that reads or writes server messages, allowing consistent checks for `IrcNumericReply.RPL_WELCOME` and other replies without duplicating numeric literals.
## Notes
- The constants are strings, not integers; avoid parsing them as numbers if you need to preserve leading zeros (e.g., "001").
- The constants are string values representing the IRC wire codes; use `IrcNumericReply.*` wherever you compare or emit these codes to avoid accidental mismatches.
- This class contains no behavior beyond constants; place any parsing or dispatch logic elsewhere.
IrcOptions is a simple configuration container that aggregates the settings controlling EchoHub's IRC bridge. It exposes toggles and values for enabling IRC, selecting ports for non-TLS and TLS connections, TLS certificate details, the server identity, an optional MOTD, and how attachment URLs are resolved via a public base URL. An application binds this object from configuration to influence how the IRC integration is started and how clients connect securely.
IrcOptions is a lightweight configuration container for the EchoHub IRC integration. It groups together all IRC-related settings that govern whether the IRC feature is active, which ports to listen on for plain and TLS connections, optional TLS credentials, the IRC server identity, an optional Motd, and how attachment URLs are resolved for IRC clients. This class is typically populated from the `Irc` configuration section (as indicated by the `SectionName` constant) and consumed by the startup logic that initializes the IRC subsystem, allowing developers to tailor IRC behavior without touching runtime code.
## Remarks
This class acts as a plain data container that centralizes IRC-related settings, separating configuration concerns from connection logic. The SectionName constant indicates the configuration section used when binding settings, while PublicBaseUrl affects how attachment URLs are translated for IRC clients—absolute URLs when set, otherwise relative paths. It is designed to be a simple DTO bound from configuration rather than responsible for validation or side effects.
## Notes
- If TLS is enabled but a certificate path or password is missing or invalid, TLS connections may fail; ensure a valid certificate and credentials are supplied when TlsEnabled is true.
- PublicBaseUrl, when set, makes attachment URLs absolute for IRC clients; if left unset, attachment lines fall back to the relative path.
- The defaults describe typical behavior: Port = 6667, TlsPort = 6697, and ServerName = "echohub".
IrcOptions is a pure data carrier with defaults that reflect common IRC conventions: `Port` defaults to 6667, `TlsPort` to 6697, and [`ServerName`](IrcCommandHandler.cs.md) to `echohub`. TLS-related fields (`TlsEnabled`, `TlsPort`, `TlsCertPath`, `TlsCertPassword`) indicate TLS support is optional and configured here; the runtime code uses these values to establish TLS-protected connections when enabled. The `PublicBaseUrl` property governs how attachment URLs are rendered for IRC clients: when set, it converts relative paths to absolute links using the provided base URL; when unset, attachments fall back to their relative paths. The `Motd` field exposes an optional IRC message of the day that can be surfaced to connected clients if the IRC subsystem is started.
@@ -8,12 +8,11 @@ public static class IrcServiceExtensions
```
Extends WebApplicationBuilder with AddIrcGateway to wire up IRC gateway support. It reads a configuration flag to enable or disable the gateway and wires the necessary services when enabled, returning the builder for fluent startup configuration.
Extends `WebApplicationBuilder` with `AddIrcGateway` to wire IRC gateway support into an ASP.NET Core app. It configures [`IrcOptions`](IrcOptions.cs.md) from configuration and, when `Irc:Enabled` is true, registers [`IrcGatewayService`](IrcGatewayService.cs.md) as a singleton, wires [`IChatBroadcaster`](../EchoHub.Core/Contracts/IChatBroadcaster.cs.md) to [`IrcBroadcaster`](IrcBroadcaster.cs.md), and adds the gateway as a hosted service, returning the original builder for fluent chaining.
## Remarks
Centralizes startup concerns for the IRC gateway: the extension reads IrcOptions from a configured section and conditionally registers the gateway components, enabling the feature via configuration. It keeps startup code concise and tests-focused by encapsulating the wiring behind a single extension method.
This extension encapsulates opt-in startup logic and centralizes the wiring of the IRC gateway, ensuring consistent DI lifetimes and configuration handling across the app. It coordinates the lifecycle of [`IrcGatewayService`](IrcGatewayService.cs.md) and the broadcaster ([`IChatBroadcaster`](../EchoHub.Core/Contracts/IChatBroadcaster.cs.md) implemented by [`IrcBroadcaster`](IrcBroadcaster.cs.md)) by hosting the gateway as a background service.
## Notes
-IrcGatewayService and IrcBroadcaster registrations are conditional on Irc:Enabled; if false, IRC components are not registered.
- Ensure IrcOptions.SectionName matches your configuration so there is a valid section to bind from.
- Returning the builder enables fluent chaining like builder.AddIrcGateway().<other extensions>()
-Calling `AddIrcGateway` multiple times can register multiple hosted services and singletons; call it once during startup to avoid duplicate registrations.
- The extension only activates when `Irc:Enabled` is true. If the flag is false or missing, it will configure [`IrcOptions`](IrcOptions.cs.md) but will not start or register the gateway components. Ensure configuration sources are loaded before invocation.
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.