OpenILink.SDK 1.0.0

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

OpenILink.SDK

微信 iLink Bot API 的 C# / .NET SDK。

这版文档默认按现代 C# 写示例:

  • using var
  • var
  • 顶层语句
  • 局部函数 / 方法组优先

完整可运行示例见 examples/OpenILink.ConsoleEchoBot。

安装

dotnet add package OpenILink.SDK

兼容性

  • net462
  • netstandard2.0
  • net8.0

同一个 NuGet 包可以同时用于老的 .NET Framework、.NET Core 和现代 .NET。

快速开始

using OpenILink.SDK;

var tokenPath = "bot_token.txt";
var bufferPath = "get_updates_buf.txt";

using var client = OpenILinkClient.Create(ReadText(tokenPath));

if (string.IsNullOrWhiteSpace(client.Token))
{
    var login = await client.LoginWithQrAsync(ShowQrCode, OnScanned);
    if (!login.Connected)
    {
        Console.Error.WriteLine($"登录失败: {login.Message}");
        return;
    }

    File.WriteAllText(tokenPath, login.BotToken ?? string.Empty);
}

await client.MonitorAsync(HandleMessageAsync, new MonitorOptions
{
    InitialBuffer = ReadText(bufferPath),
    OnBufferUpdated = SaveBuffer,
    OnError = ReportError,
    OnSessionExpired = ReportSessionExpired
});

Task HandleMessageAsync(WeixinMessage message)
{
    var text = message.ExtractText();
    if (string.IsNullOrWhiteSpace(text))
    {
        return Task.CompletedTask;
    }

    Console.WriteLine($"[{message.FromUserId}] {text}");
    return client.ReplyTextAsync(message, $"echo: {text}");
}

void ShowQrCode(string qrCodeImage)
{
    Console.WriteLine(qrCodeImage);
}

void OnScanned()
{
    Console.WriteLine("已扫码,请在微信端确认。");
}

void SaveBuffer(string buffer)
{
    File.WriteAllText(bufferPath, buffer);
}

void ReportError(Exception exception)
{
    Console.Error.WriteLine(exception.Message);
}

void ReportSessionExpired()
{
    Console.Error.WriteLine("会话过期,请重新登录。");
}

static string ReadText(string path)
{
    return File.Exists(path) ? File.ReadAllText(path).Trim() : string.Empty;
}

创建客户端

最常用的三种写法:

using var client = OpenILinkClient.Create(token);
using var client = new OpenILinkClient(token);
using var httpClient = new HttpClient();

using var client = OpenILinkClient.Builder()
    .Token(token)
    .BaseUri("https://ilinkai.weixin.qq.com/")
    .CdnBaseUri("https://novac2c.cdn.weixin.qq.com/c2c/")
    .RouteTag("gray-route")
    .HttpClient(httpClient)
    .ApiTimeout(TimeSpan.FromSeconds(15))
    .LongPollingTimeout(TimeSpan.FromSeconds(35))
    .Build();

如果你是从配置系统里读参数,直接用 OpenILinkClientOptions:

var options = new OpenILinkClientOptions(token)
{
    BaseUri = new Uri("https://ilinkai.weixin.qq.com/"),
    CdnBaseUri = new Uri("https://novac2c.cdn.weixin.qq.com/c2c/"),
    RouteTag = "gray-route",
    LoginTimeout = TimeSpan.FromMinutes(8)
};

using var client = new OpenILinkClient(options);

登录

首次启动通常没有 bot_token,直接扫码:

var login = await client.LoginWithQrAsync(ShowQrCode, OnScanned, OnExpired);

if (login.Connected)
{
    File.WriteAllText("bot_token.txt", login.BotToken ?? string.Empty);
}

void ShowQrCode(string qrCodeImage)
{
    Console.WriteLine(qrCodeImage);
}

void OnScanned()
{
    Console.WriteLine("已扫码,请确认。");
}

void OnExpired(int attempt, int maxAttempt)
{
    Console.WriteLine($"二维码过期,正在刷新 ({attempt}/{maxAttempt})");
}

登录成功后 SDK 会自动更新:

  • client.Token
  • client.BaseUri

下次启动时直接复用 bot_token 即可。

接收消息

await client.MonitorAsync(HandleMessageAsync, new MonitorOptions
{
    InitialBuffer = ReadText("get_updates_buf.txt"),
    OnBufferUpdated = buffer => File.WriteAllText("get_updates_buf.txt", buffer),
    OnError = exception => Console.Error.WriteLine(exception.Message),
    OnSessionExpired = () => Console.Error.WriteLine("会话过期")
});

Task HandleMessageAsync(WeixinMessage message)
{
    var text = message.ExtractText();
    if (string.IsNullOrWhiteSpace(text))
    {
        return Task.CompletedTask;
    }

    return client.ReplyTextAsync(message, $"收到: {text}");
}

MonitorAsync 会自动:

  • 重试和退避
  • 跟进服务端返回的 longpolling_timeout_ms
  • 缓存每个用户的 contextToken
  • 推进 get_updates_buf

回复和主动推送

收到消息后,优先直接回复:

await client.ReplyTextAsync(message, "你好");

需要主动推送时:

if (client.CanPushTo(userId))
{
    await client.PushTextAsync(userId, "这是一条主动消息");
}

也可以显式读取缓存的上下文:

var contextToken = client.GetContextToken(userId);

输入状态和 Bot 配置

var config = await client.GetConfigAsync(userId, contextToken);
await client.SendTypingAsync(userId, config.TypingTicket ?? string.Empty, TypingStatus.Typing);

媒体上传和发送

最省心的写法:

var bytes = File.ReadAllBytes("photo.jpg");
await client.SendMediaFileAsync(toUserId, contextToken, bytes, "photo.jpg", "看看这张图");

需要手动控制上传和发送时:

var bytes = File.ReadAllBytes("photo.jpg");
var upload = await client.UploadFileAsync(bytes, toUserId, UploadMediaType.Image);

await client.SendImageAsync(toUserId, contextToken, upload);

同理也可以调用:

  • SendVideoAsync
  • SendFileAttachmentAsync

下载文件和语音

下载文件:

var plaintext = await client.DownloadFileAsync(
    media.EncryptQueryParam ?? string.Empty,
    media.AesKey ?? string.Empty);

下载语音前,需要先注入 ISilkDecoder:

public sealed class MySilkDecoder : ISilkDecoder
{
    public Task<byte[]> DecodeAsync(byte[] silkData, int sampleRate, CancellationToken cancellationToken)
    {
        return Task.FromResult(Array.Empty<byte>());
    }
}

using var client = OpenILinkClient.Builder()
    .Token(token)
    .SilkDecoder(new MySilkDecoder())
    .Build();

var wav = await client.DownloadVoiceAsync(voiceItem);

工具方法

var text = message.ExtractText();
var isMedia = MessageUtilities.IsMediaItem(item);
var mime = MimeUtilities.MimeFromFilename("photo.jpg");
var extension = MimeUtilities.ExtensionFromMime("video/mp4");
var isImage = MimeUtilities.IsImageMime("image/png");
var isVideo = MimeUtilities.IsVideoMime("video/mp4");

异常处理

try
{
    await client.PushTextAsync(userId, "hello");
}
catch (MissingContextTokenException)
{
    Console.WriteLine("该用户还没有可用的 contextToken。");
}
catch (OpenILinkApiException exception) when (exception.IsSessionExpired())
{
    Console.WriteLine("会话过期,请重新登录。");
}
catch (OpenILinkHttpException exception)
{
    Console.WriteLine($"HTTP 状态码: {exception.StatusCode}");
}
Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  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. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net461 was computed.  net462 is compatible.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos 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 (1)

Showing the top 1 popular GitHub repositories that depend on OpenILink.SDK:

Repository Stars
lindexi/lindexi_gd
博客用到的代码
Version Downloads Last Updated
1.0.0 276 3/23/2026