SaltyMilky.Net 1.1.5

dotnet add package SaltyMilky.Net --version 1.1.5
                    
NuGet\Install-Package SaltyMilky.Net -Version 1.1.5
                    
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="SaltyMilky.Net" Version="1.1.5" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="SaltyMilky.Net" Version="1.1.5" />
                    
Directory.Packages.props
<PackageReference Include="SaltyMilky.Net" />
                    
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 SaltyMilky.Net --version 1.1.5
                    
#r "nuget: SaltyMilky.Net, 1.1.5"
                    
#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 SaltyMilky.Net@1.1.5
                    
#: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=SaltyMilky.Net&version=1.1.5
                    
Install as a Cake Addin
#tool nuget:?package=SaltyMilky.Net&version=1.1.5
                    
Install as a Cake Tool

SaltyMilky.Net

SaltyMilky.Net is a .NET 8 SDK for the Milky protocol. It provides a BCL-only library with HTTP API invocation, typed convenience methods, message segment models, event parsing, and event transport helpers for SSE, WebSocket, and WebHook communication.

Install

Reference the project or pack it locally:

dotnet add reference ./SaltyMilky.Net/SaltyMilky.Net.csproj

HTTP API Session

using SaltyMilky.Net;
using System.Text.Json.Nodes;

using MilkyHttpSession session = new(new MilkyHttpSessionOptions
{
    BaseAddress = new Uri("http://127.0.0.1:3000/"),
    AccessToken = "optional-token",
    EventHandlerConcurrency = 4,
    EventQueueCapacity = 100,
    EventHandlerExceptionHandler = (exception, milkyEvent) =>
        Console.Error.WriteLine($"Failed to handle {milkyEvent.EventType}: {exception}")
});

MilkyActionResult<MilkyLoginInfoResult>? login = await session.GetLoginInfoAsync();
if (login?.IsSuccess == true)
{
    Console.WriteLine($"Logged in as {login.Data?.Nickname} ({login.Data?.Uin})");
}

The built-in event loops process events concurrently with a bounded queue. An unhandled exception from one event is reported through EventHandlerExceptionHandler and does not stop the transport loop or later events. Queue backpressure prevents an event burst from creating an unbounded number of tasks.

Send Messages

MilkyMessage message = new(
    new MilkyTextSegment("hello "),
    new MilkyMentionSegment(123456789),
    new MilkyImageSegment("file:///tmp/cat.png", summary: "cat"));

MilkyActionResult<MilkySendMessageResult>? result = await session.SendGroupMessageAsync(987654321, message);
Console.WriteLine(result?.Data?.MessageSeq);

Generic Future API Invocation

MilkyActionResult<MilkyResourceTempUrlResult>? response = await session.InvokeApiAsync<MilkyResourceTempUrlResult>(
    "get_resource_temp_url",
    new JsonObject { ["resource_id"] = "resource-id" });

Receive Events

Milky supports three event push models on top of HTTP API calls: SSE, WebSocket, and WebHook.

For SSE, connect to the protocol-side /event endpoint:

await foreach (MilkyEvent milkyEvent in session.ReadSseEventsAsync(cancellationToken))
{
    await session.EventPipeline.ExecuteAsync(milkyEvent, cancellationToken);
}

For WebSocket, connect to the same /event endpoint using the WebSocket transport:

await foreach (MilkyEvent milkyEvent in session.ReadWebSocketEventsAsync(cancellationToken))
{
    await session.EventPipeline.ExecuteAsync(milkyEvent, cancellationToken);
}

You can also run either event loop directly:

await session.RunSseEventLoopAsync(cancellationToken);
await session.RunWebSocketEventLoopAsync(cancellationToken);

For long-running bots, use reconnecting loops:

await session.RunReconnectingSseEventLoopAsync(cancellationToken: cancellationToken);
await session.RunReconnectingWebSocketEventLoopAsync(cancellationToken: cancellationToken);

By default, reconnecting loops keep an idle event connection open and reconnect only after a transport failure. If a deployment explicitly needs an idle watchdog, pass idleTimeout:

await session.RunReconnectingSseEventLoopAsync(
    reconnectDelay: TimeSpan.FromSeconds(3),
    idleTimeout: TimeSpan.FromMinutes(2),
    cancellationToken: cancellationToken);

For WebHook integrations, validate the incoming Authorization header and parse the request body:

MilkyEvent? milkyEvent = await MilkyCommunication.ReadWebhookEventAsync(
    request.Body,
    request.Headers.Authorization.ToString(),
    accessToken,
    cancellationToken);

if (milkyEvent is not null)
{
    await session.EventPipeline.ExecuteAsync(milkyEvent, cancellationToken);
}

For a lightweight self-hosted WebHook endpoint:

await MilkyCommunication.RunWebhookListenerAsync(
    "http://127.0.0.1:8080/milky-webhook/",
    session.EventPipeline,
    accessToken,
    cancellationToken);

Parse Event Payloads

string json = """
{
  "event_type": "message_receive",
  "time": 1710000000,
  "self_id": 10000,
  "data": {
    "message_scene": "friend",
    "peer_id": 123456789,
    "message_seq": 42,
    "sender_id": 123456789,
    "time": 1710000000,
    "segments": [{ "type": "text", "text": "ping" }]
  }
}
""";

MilkyEvent? parsed = MilkyEventParser.ParseJson(json);
if (parsed?.Data is MilkyMessageReceiveEventData received)
{
    Console.WriteLine(received.Message.Segments.OfType<MilkyIncomingTextSegment>().FirstOrDefault()?.Text);
}

if (parsed?.Data is MilkyGroupMemberIncreaseEventData increased)
{
    Console.WriteLine($"{increased.UserId} joined {increased.GroupId}");
}

For existing transports, parse JSON payloads with MilkyEventParser.ParseJson or accumulated SSE text blocks with MilkyEventParser.ParseSseEvents; only data: lines are parsed as Milky event JSON.

The SDK tracks the public Milky v1.2.2 documentation surface for API helpers, event payloads, incoming/outgoing message segments, and group notification variants. Unknown event subtypes are preserved as MilkyUnknownEventData; unknown incoming message segments are converted to a text segment such as [unsupported Milky segment: future_segment] to match Milky compatibility guidance.

Event Pipeline and Plugins

MilkyHttpSession also exposes an event pipeline. Feed parsed events into the pipeline from WebSocket, SSE, or WebHook integrations:

public sealed class MyMilkyPlugin : MilkyEventPlugin
{
    protected override async Task OnGroupMessageReceivedAsync(MilkyGroupMessageContext context)
    {
        Console.WriteLine($"{context.SenderId}: {context.Text}");
        await context.ReplyAsync(MilkyMessageHelpers.Text("received"));
    }
}

session.UsePlugin(new MyMilkyPlugin());
session.UseMessageReceived((data, evt) =>
{
    Console.WriteLine($"message from {data.Message.SenderId}");
});

MilkyEvent? parsed = MilkyEventParser.ParseJson(json);
if (parsed is not null)
{
    await session.EventPipeline.ExecuteAsync(parsed);
}

Context callbacks bind each event to the IMilkyActionSession that received it. The SDK provides a named context for every known Milky event, including group/private messages and recalls, friend and group requests, notices, bot-offline events, and unknown future events. Request contexts expose accept/reject operations; message contexts expose reply/send operations. MilkyEventContextFactory.Create is available when manually dispatching parsed events.

The original (data, event) callbacks and delegate middleware remain supported. Reusable context middleware can derive from MilkyEventMiddleware and register with session.UseMiddleware(...). Cancellation-aware delegate middleware can use MilkyEventPipelineMiddleware; its token is also available through MilkyEventContext.CancellationToken and is forwarded by context operations.

Scope

This package intentionally does not include a CLI or UI. It focuses on Milky's HTTP /api/{apiName} action contract, event transport clients, a lightweight WebHook listener, and event JSON parsing surface.

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.
  • net8.0

    • No dependencies.

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
1.1.5 108 8/13/2026
1.1.4 101 8/13/2026
1.1.3 95 8/12/2026
1.1.2 117 8/3/2026
1.1.1 98 8/3/2026
1.1.0 103 7/30/2026
1.0.4 112 6/23/2026
1.0.3 124 6/11/2026
1.0.2 114 6/11/2026
1.0.1 122 6/6/2026
1.0.0 121 6/6/2026