Skip to content

openilink/openilink-sdk-csharp

Repository files navigation

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}");
}

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages