IRCDotNet.Core 2.0.2

There is a newer version of this package available.
See the version list below for details.
dotnet add package IRCDotNet.Core --version 2.0.2
                    
NuGet\Install-Package IRCDotNet.Core -Version 2.0.2
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="IRCDotNet.Core" Version="2.0.2" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="IRCDotNet.Core" Version="2.0.2" />
                    
Directory.Packages.props
<PackageReference Include="IRCDotNet.Core" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add IRCDotNet.Core --version 2.0.2
                    
#r "nuget: IRCDotNet.Core, 2.0.2"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package IRCDotNet.Core@2.0.2
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=IRCDotNet.Core&version=2.0.2
                    
Install as a Cake Addin
#tool nuget:?package=IRCDotNet.Core&version=2.0.2
                    
Install as a Cake Tool

IRCDotNet.Core — Modern .NET 8 IRC Client Library

A production-ready, thread-safe IRC (Internet Relay Chat) client library for .NET 8 with comprehensive IRCv3 support, full IntelliSense documentation, and zero external dependencies beyond Microsoft.Extensions.Logging.Abstractions.

Build chat applications, notification relays, channel monitors, IRC-to-Discord bridges, or any tool that needs real-time IRC connectivity.

Installation

dotnet add package IRCDotNet.Core

Features

  • RFC 1459 + Modern IRC — Complete protocol implementation with ISUPPORT parsing
  • IRCv3 Capabilities — SASL, message-tags, server-time, away-notify, account-notify, extended-join, cap-notify, chghost, batch, labeled-response, echo-message, monitor, setname
  • SASL Authentication — PLAIN and EXTERNAL mechanisms with automatic CAP negotiation
  • NickServ IDENTIFY — Reactive identification triggered by NickServ prompts
  • Rate Limiting — Configurable token-bucket algorithm prevents flood protection kicks
  • Auto-Reconnect — Exponential backoff with automatic channel rejoin
  • Thread-SafeConcurrentDictionary, ConcurrentHashSet, semaphore-controlled sends, volatile state
  • Event-Driven — 30+ event types with threaded, sequential, or background dispatch strategies
  • Fluent BuilderIrcClientOptionsBuilder for type-safe configuration
  • Dependency InjectionIServiceCollection integration with AddIrcClient() and AddIrcBotManager()
  • Multi-Client ManagementIrcBotManager as an IHostedService for managing multiple connections
  • Protocol Utilities — IRC case mapping, message validation, encoding helpers, formatting strippers
  • Full IntelliSense — Every public member documented with <summary>, <param>, and <returns> tags

Quick Start

using IRCDotNet;
using IRCDotNet.Configuration;

var options = new IrcClientOptions
{
    Server = "irc.libera.chat",
    Port = 6697,
    UseSsl = true,
    Nick = "MyApp",
    UserName = "myapp",
    RealName = "My IRC Application"
};

await using var client = new IrcClient(options);

client.Connected += (s, e) =>
{
    Console.WriteLine($"Connected to {e.Network} as {e.Nick}");
    _ = client.JoinChannelAsync("#mychannel");
};

client.PrivateMessageReceived += (s, e) =>
    Console.WriteLine($"[{e.Target}] <{e.Nick}> {e.Text}");

client.ChannelJoinFailed += (s, e) =>
    Console.WriteLine($"Failed to join {e.Channel}: {e.Reason}");

await client.ConnectAsync();

// Keep running until Ctrl+C
var cts = new CancellationTokenSource();
Console.CancelKeyPress += (_, e) => { e.Cancel = true; cts.Cancel(); };
try { await Task.Delay(Timeout.Infinite, cts.Token); } catch (OperationCanceledException) { }

Fluent Builder

var options = new IrcClientOptionsBuilder()
    .WithNick("MyApp")
    .WithUserName("myapp")
    .WithRealName("My IRC Application")
    .AddServer("irc.libera.chat", 6697, useSsl: true)
    .AddAutoJoinChannels("#channel1", "#channel2")
    .AddAlternativeNick("MyApp_")
    .WithAutoReconnect(maxAttempts: 5)
    .WithSaslAuthentication("myapp", "<your-password>")
    .Build();

Dependency Injection

// Single client
services.AddIrcClient(builder =>
{
    builder.WithNick("MyApp")
           .WithUserName("myapp")
           .WithRealName("My IRC Application")
           .AddServer("irc.libera.chat", 6697, useSsl: true);
});

// Multi-client manager (runs as IHostedService)
services.AddIrcBotManager(manager =>
{
    manager.AddBot("server1", b => b
        .WithNick("AppClient")
        .WithUserName("appclient")
        .WithRealName("My App - Server 1")
        .AddServer("irc.libera.chat", 6697, useSsl: true));
});

Events

Category Events
Connection Connected, Disconnected, CapabilitiesNegotiated, SaslAuthentication
Messages PrivateMessageReceived, NoticeReceived, MessageTagsReceived
Channels UserJoinedChannel, ExtendedUserJoinedChannel, UserLeftChannel, UserKicked, TopicChanged, ChannelUsersReceived, ChannelJoinFailed, ChannelListReceived, ChannelListEndReceived
Users NickChanged, NicknameCollision, UserQuit, UserAwayStatusChanged, UserAccountChanged, UserHostnameChanged
Advanced RawMessageReceived, BatchReceived, WhoReceived, WhoWasReceived
Enhanced OnEnhancedMessage, OnEnhancedConnected, OnEnhancedDisconnected, OnEnhancedUserJoined, OnGenericMessage, OnPreSendMessage

Usage Examples

Logging

Pass any ILogger or ILogger<IrcClient> to get structured diagnostics:

using Microsoft.Extensions.Logging;

using var loggerFactory = LoggerFactory.Create(b => b.AddConsole().SetMinimumLevel(LogLevel.Debug));
var logger = loggerFactory.CreateLogger<IrcClient>();

await using var client = new IrcClient(options, logger);

Send Messages and Respond to Commands

client.PrivateMessageReceived += (s, e) =>
{
    if (e.IsChannelMessage && e.Text.StartsWith("!hello"))
    {
        _ = client.SendMessageAsync(e.Target, $"Hello, {e.Nick}!");
    }
};

Cancellation-Safe Messaging

All send methods accept CancellationToken for responsive shutdown:

await client.SendMessageWithCancellationAsync("#channel", "Hello!", cts.Token);
await client.SendNoticeWithCancellationAsync("someone", "Private notice", cts.Token);
await client.SendRawWithCancellationAsync("PRIVMSG #dev :test", cts.Token);

Browse Available Channels

GetChannelListAsync collects all LIST entries into a single awaitable result:

var channels = await client.GetChannelListAsync(timeout: TimeSpan.FromSeconds(15));

foreach (var ch in channels.OrderByDescending(c => c.UserCount).Take(10))
{
    Console.WriteLine($"{ch.Channel} ({ch.UserCount} users): {ch.Topic}");
}

SASL Authentication with NickServ Fallback

var options = new IrcClientOptionsBuilder()
    .WithNick("SecureClient")
    .WithUserName("secureclient")
    .WithRealName("Secure IRC Client")
    .AddServer("irc.libera.chat", 6697, useSsl: true)
    .WithSaslAuthentication("secureclient", "<your-password>", required: false)
    .WithNickServPassword("<your-password>")  // fallback if SASL unavailable
    .Build();

var client = new IrcClient(options);

client.SaslAuthentication += (s, e) =>
{
    if (e.IsSuccessful)
        Console.WriteLine($"SASL {e.Mechanism} succeeded");
    else
        Console.WriteLine($"SASL failed: {e.ErrorMessage}");
};

Enhanced Events with Built-in Response Methods

Enhanced events carry a client reference for fluent replies:

client.OnEnhancedMessage += async (s, e) =>
{
    if (e.Text.StartsWith("!ping"))
    {
        await e.RespondAsync("Pong!");               // replies in channel or PM
        await e.RespondToUserAsync("Pong!");          // prefixes nick in channel
        await e.ReplyPrivatelyAsync("Secret pong!");  // always via PM
    }
};

Pre-Send Interception (Filtering / Logging)

Inspect or cancel outgoing messages before they hit the wire:

client.OnPreSendMessage += (e) =>
{
    // Log all outgoing messages
    logger.LogDebug("-> {Target}: {Message}", e.Target, e.Message);

    // Block messages containing forbidden words
    if (e.Message.Contains("badword", StringComparison.OrdinalIgnoreCase))
        e.IsCancelled = true;
};

Track User Presence with IRCv3 Capabilities

var options = new IrcClientOptionsBuilder()
    .WithNick("PresenceApp")
    .WithUserName("presenceapp")
    .WithRealName("Presence Tracker")
    .AddServer("irc.libera.chat", 6697, useSsl: true)
    .AddCapabilities("away-notify", "account-notify", "extended-join")
    .Build();

var client = new IrcClient(options);

client.UserAwayStatusChanged += (s, e) =>
    Console.WriteLine($"{e.Nick} is {(e.IsAway ? "away" : "back")}: {e.AwayMessage}");

client.UserAccountChanged += (s, e) =>
    Console.WriteLine($"{e.Nick} {(e.Account is null ? "logged out" : $"logged in as {e.Account}")}");

client.ExtendedUserJoinedChannel += (s, e) =>
    Console.WriteLine($"{e.Nick} joined {e.Channel} (account: {e.Account}, realname: {e.RealName})");

Graceful Shutdown with Async Dispose

await using var client = new IrcClient(options);

client.Connected += (s, e) => _ = client.JoinChannelAsync("#mychannel");

await client.ConnectAsync();

// ... run until shutdown signal ...

// DisposeAsync sends QUIT and cleans up all resources

Server Feature Detection via ISUPPORT

After connecting, query server-advertised limits:

client.Connected += (s, e) =>
{
    var network = client.GetServerNetworkName();       // e.g. "Libera.Chat"
    var maxNick = client.GetServerMaxNicknameLength();  // e.g. 16
    var types = client.GetServerChannelTypes();         // e.g. "#&"

    // Case-insensitive comparison using server rules
    bool same = client.NicknamesEqual("MyApp", "myapp"); // true
};

Validate Input Before Sending

if (client.IsValidNickname(userInput))
    await client.ChangeNickAsync(userInput);

if (client.IsValidChannelName(channelInput))
    await client.JoinChannelAsync(channelInput);

Auto-Reconnect with Status Tracking

The library reconnects automatically with exponential backoff. Track connection state in your UI:

var options = new IrcClientOptionsBuilder()
    .WithNick("MyApp")
    .WithUserName("myapp")
    .WithRealName("My App")
    .AddServer("irc.libera.chat", 6697, useSsl: true)
    .WithAutoReconnect(enabled: true, maxAttempts: 10,
        initialDelay: TimeSpan.FromSeconds(5),
        maxDelay: TimeSpan.FromMinutes(2))
    .Build();

var client = new IrcClient(options);

client.Connected += (s, e) =>
    UpdateStatusBar($"Connected as {e.Nick}");

client.Disconnected += (s, e) =>
    UpdateStatusBar($"Disconnected: {e.Reason} — reconnecting...");

Nickname Fallback

Configure alternative nicknames in case your preferred nick is taken:

var options = new IrcClientOptionsBuilder()
    .WithNick("MyApp")
    .WithUserName("myapp")
    .WithRealName("My App")
    .AddServer("irc.libera.chat", 6697, useSsl: true)
    .AddAlternativeNick("MyApp_")
    .AddAlternativeNick("MyApp__")
    .Build();

var client = new IrcClient(options);

// The client tries alternatives automatically during registration.
// Listen for collisions to track what happened:
client.NicknameCollision += (s, e) =>
    Console.WriteLine($"Nick '{e.CollidingNick}' taken, switched to '{e.FallbackNick}'");

Connect to Private Servers with Self-Signed Certificates

var options = new IrcClientOptions
{
    Server = "my-private-server.local",
    Port = 6697,
    UseSsl = true,
    AcceptInvalidSslCertificates = true,  // accept self-signed certs
    Password = "server-password",          // server PASS command
    Nick = "MyApp",
    UserName = "myapp",
    RealName = "My App"
};

Track Channel Membership

The Channels property provides a live snapshot of joined channels and their users:

// Check who is in a channel
if (client.Channels.TryGetValue("#mychannel", out var users))
{
    Console.WriteLine($"#mychannel has {users.Count} users:");
    foreach (var nick in users)
        Console.WriteLine($"  {nick}");
}

// Check if a specific user is in a channel
bool isOnline = client.Channels.TryGetValue("#mychannel", out var members)
    && members.Contains("SomeUser");

// List all channels the client is in
foreach (var channel in client.Channels.Keys)
    Console.WriteLine(channel);

Guard Sends with Connection State

Check IsConnected and IsRegistered before sending in UI applications:

async Task SendSafeAsync(IrcClient client, string target, string message)
{
    if (!client.IsConnected)
    {
        ShowError("Not connected to server");
        return;
    }

    if (!client.IsRegistered)
    {
        ShowError("Still registering with server, please wait...");
        return;
    }

    await client.SendMessageAsync(target, message);
}

Handle Protocol Errors

Channel join failures arrive via the ChannelJoinFailed event with the IRC error code:

client.ChannelJoinFailed += (s, e) =>
{
    Console.WriteLine($"Cannot join {e.Channel}: {e.Reason} (code: {e.ErrorCode})");
    // e.ErrorCode is the IRC numeric: "473" (invite-only), "474" (banned),
    // "475" (bad key), "471" (full)
};

For protocol-level error handling, the library provides a typed exception hierarchy via IrcErrorHandler:

using IRCDotNet.Protocol;

var error = IrcErrorHandler.HandleNumericError(rawMessage);
if (error is IrcChannelBannedException) { /* banned */ }
else if (error is IrcNicknameInUseException) { /* nick taken */ }
else if (error is IrcAuthenticationException) { /* auth failed */ }

Exception hierarchy:

  • IrcProtocolException — base class (carries NumericCode)
    • IrcChannelException — channel errors (IrcChannelBannedException, IrcChannelInviteOnlyException, IrcChannelFullException, IrcChannelKeyException, IrcChannelPermissionException, IrcNotOnChannelException)
    • IrcNicknameException — nickname errors (IrcNicknameInUseException, IrcInvalidNicknameException, IrcNicknameCollisionException)
    • IrcAuthenticationException — password/SASL failures
    • IrcTargetNotFoundException, IrcChannelNotFoundException — target doesn't exist
    • IrcUnknownCommandException, IrcNotRegisteredException, IrcAlreadyRegisteredException

Configure Rate Limiting

Rate limiting prevents the server from kicking you for flooding. It's enabled by default:

using IRCDotNet.Protocol;

// Use a custom rate limit (2 messages/sec, burst of 10)
var options = new IrcClientOptionsBuilder()
    .WithNick("MyApp")
    .WithUserName("myapp")
    .WithRealName("My App")
    .AddServer("irc.libera.chat", 6697, useSsl: true)
    .WithRateLimit(enabled: true, new RateLimitConfig(
        refillRate: 2.0,   // tokens per second
        bucketSize: 10))   // max burst
    .Build();

// Or disable entirely for testing
var testOptions = new IrcClientOptionsBuilder()
    .WithNick("TestApp")
    .WithUserName("testapp")
    .WithRealName("Test")
    .AddServer("localhost", 6667)
    .WithoutRateLimit()
    .Build();

Parse and Inspect Raw Messages

Use IrcMessage for low-level protocol work:

using IRCDotNet.Protocol;

// Parse a raw IRC line
var msg = IrcMessage.Parse(":nick!user@host PRIVMSG #channel :Hello world");
Console.WriteLine(msg.Command);        // "PRIVMSG"
Console.WriteLine(msg.Source);         // "nick!user@host"
Console.WriteLine(msg.Parameters[0]);  // "#channel"
Console.WriteLine(msg.Parameters[1]);  // "Hello world"

// Build and serialize a message
var outgoing = new IrcMessage
{
    Command = IrcCommands.PRIVMSG,
    Parameters = { "#channel", "Hello!" }
};
Console.WriteLine(outgoing.Serialize()); // "PRIVMSG #channel :Hello!"

// Listen for any raw message
client.RawMessageReceived += (s, e) =>
{
    if (e.Message.Command == IrcNumericReplies.RPL_TOPIC)
        Console.WriteLine($"Topic: {e.Message.Parameters[2]}");
};

Strip IRC Formatting

Remove mIRC color codes, bold, italic, and underline from messages:

using IRCDotNet.Protocol;

client.PrivateMessageReceived += (s, e) =>
{
    // e.Text may contain \x02bold\x02 or \x0304,01red text\x03
    var plainText = IrcEncoding.StripIrcFormatting(e.Text);
    Console.WriteLine(plainText); // clean text without formatting
};

Protocol Constants

Use typed constants instead of magic strings with SendRawAsync and RawMessageReceived:

using IRCDotNet.Protocol;

// IRC commands
await client.SendRawAsync($"{IrcCommands.PRIVMSG} #channel :Hello");
await client.SendRawAsync($"{IrcCommands.NAMES} #channel");

// Numeric replies in event handlers
client.RawMessageReceived += (s, e) =>
{
    switch (e.Message.Command)
    {
        case IrcNumericReplies.RPL_TOPIC:      // "332"
        case IrcNumericReplies.RPL_TOPICWHOTIME: // "333"
            break;
        case IrcNumericReplies.ERR_BANNEDFROMCHAN: // "474"
            break;
    }
};

// IRCv3 capability names
if (client.EnabledCapabilities.Contains(IrcCapabilities.SERVER_TIME))
    Console.WriteLine("Server-time is available");

API Reference

Properties

Property Type Description
IsConnected bool Whether the client is currently connected
IsRegistered bool Whether IRC registration (NICK/USER) is complete
CurrentNick string The client's current nickname on the server
Channels IReadOnlyDictionary Snapshot of joined channels mapped to user sets
EnabledCapabilities IReadOnlySet<string> IRCv3 capabilities enabled for this connection
Configuration IrcClientOptions The configuration used to create this client

Methods

SendRawAsync, SendMessageAsync, and SendNoticeAsync also have *WithCancellationAsync variants that accept a CancellationToken. ConnectAsync and GetChannelListAsync accept CancellationToken directly.

Category Methods
Connection ConnectAsync, DisconnectAsync, SendRawAsync
Channels JoinChannelAsync, LeaveChannelAsync, SetTopicAsync, GetTopicAsync, GetChannelUsersAsync, GetChannelListAsync, RequestChannelListAsync
Messaging SendMessageAsync, SendNoticeAsync, SendMessageWithTagsAsync, SendTagMessageAsync
Queries WhoAsync, WhoWasAsync, GetUserInfoAsync, ListChannelsAsync
User ChangeNickAsync, SetAwayAsync, SetRealNameAsync, SetUserModeAsync
Admin KickUserAsync, InviteUserAsync, SetChannelModeAsync, GetChannelModeAsync
IRCv3 MonitorNickAsync, UnmonitorNickAsync
Utilities NicknamesEqual, ChannelNamesEqual, IsValidNickname, IsValidChannelName, EncodeMessage
Server Info GetServerNetworkName, GetServerMaxNicknameLength, GetServerMaxChannelLength, GetServerChannelTypes
Lifecycle Dispose, DisposeAsync

Requirements

  • .NET 8.0 or later (targets net8.0; compatible with .NET 9 and .NET 10 but not explicitly tested)
  • No platform-specific dependencies — works on Windows, macOS, and Linux

License

MIT — see LICENSE for details.

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
2.5.1 101 4/17/2026
2.5.0 104 4/14/2026
2.4.0 99 4/12/2026
2.3.1 99 4/12/2026
2.3.0 94 4/11/2026
2.2.0 90 4/11/2026
2.1.1 97 4/11/2026
2.1.0 95 4/10/2026
2.0.2 98 4/10/2026
2.0.1 96 4/9/2026
2.0.0 102 4/9/2026