基于 ASP.NET Core 的 OneBot 机器人框架,底层协议处理由 NapPlana.NET 提供。
NapArona 的核心思路是把写 QQ 机器人变成写 Controller —— 和写 Web API 一样的体验,用特性标记方法,框架负责路由和参数绑定。
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddNapArona(opts =>
{
opts.WebSocketPath = "/onebot";
opts.AuthenticateAsync = async ctx =>
ctx.Request.Headers.Authorization.ToString() == "Bearer your-secret-token";
});
builder.Services.AddNapAronaControllers();
var app = builder.Build();
app.UseWebSockets();
app.MapNapArona();
app.UseNapAronaControllers();
app.Run();NapCat 以反向 WebSocket 模式连接到这个服务,连接建立后框架自动完成会话识别和事件订阅。
继承 BotController,用 [Command] 标记方法即可处理命令。控制器是 Transient 的,每次命令触发都会从 DI 容器创建新实例。
public class MyController : BotController
{
[Command("/ping")]
public async Task Ping()
{
await ReplyTextAsync("pong!");
}
[Command("/greet")]
public async Task Greet(string name, int count = 1)
{
for (int i = 0; i < count; i++)
await ReplyTextAsync($"你好,{name}!");
}
}命令文本中空格分隔的部分会按顺序绑定到方法参数上,支持 string、int、long、double、bool 的自动转换,也支持默认值和 params string[]。
[Command] 设置 IsRegex = true 后,用正则表达式匹配消息,命名捕获组自动绑定到同名参数:
[Command(@"^/roll (?<count>\d+)d(?<sides>\d+)$", IsRegex = true)]
public async Task Roll(int count, int sides)
{
// count 和 sides 从正则命名组中提取并转换为 int
}方法参数声明为 Match 类型时,可以拿到完整的正则匹配结果。
除了消息命令,还可以用 [OnEvent] 或预定义的事件特性监听 OneBot 通知事件:
[OnGroupPoke]
public async Task OnPoke()
{
await ReplyTextAsync("别戳我!");
}
[OnGroupIncrease]
public async Task OnNewMember(GroupIncreaseNoticeEvent ev)
{
await ReplyTextAsync($"欢迎 {ev.UserId} 加入群聊!");
}预定义特性包括 [OnGroupPoke]、[OnFriendPoke]、[OnGroupIncrease]、[OnGroupDecrease],也可以直接用 [OnEvent(typeof(...))] 监听任意事件类型。
方法参数声明为对应的事件类型时,框架会自动注入事件对象。
[GroupOnly] 和 [PrivateOnly] 可以标记在方法或整个控制器类上,限制命令的触发场景:
[Command("/secret")]
[PrivateOnly]
public async Task Secret() { /* 只在私聊中响应 */ }使用 ASP.NET Core 的 [Authorize] 特性控制命令的访问权限,可以标记在方法或整个控制器类上。
框架根据群消息中的发送者身份自动解析以下内置角色:
Builtin:GroupOwner— 群主(同时拥有 GroupAdmin 和 GroupMember)Builtin:GroupAdmin— 群管理员(同时拥有 GroupMember)Builtin:GroupMember— 群成员
using Microsoft.AspNetCore.Authorization;
using NapArona.Controllers.Authorization;
[Command("/shutup")]
[GroupOnly]
[Authorize(Roles = BuiltinRoles.GroupAdmin)]
public async Task ShutUp()
{
await ReplyTextAsync("已开启全体禁言");
}单个 [Authorize] 内的多个 Roles 是 OR 关系(满足其一即可),多个 [Authorize] 之间是 AND 关系(都要满足),与 ASP.NET Core 行为一致。
实现 IAuthorizationProvider 接口并通过 DI 注册,即可提供自定义角色:
public class MyAuthProvider : IAuthorizationProvider
{
private static readonly HashSet<long> SuperUsers = [123456789, 987654321];
public Task<IReadOnlySet<string>> GetRolesAsync(BotContext context)
{
var roles = new HashSet<string>(StringComparer.Ordinal);
if (SuperUsers.Contains(context.UserId))
roles.Add("SuperUser");
return Task.FromResult<IReadOnlySet<string>>(roles);
}
}builder.Services.AddSingleton<IAuthorizationProvider, MyAuthProvider>();然后就可以在命令上使用自定义角色:
[Command("/sudo")]
[Authorize(Roles = "SuperUser")]
public async Task Sudo(params string[] args)
{
await ReplyTextAsync($"[sudo] 已执行: {string.Join(' ', args)}");
}自定义角色和内置角色可以混用。授权失败时默认静默忽略,也可以在 IAuthorizationProvider 中重写 OnUnauthorizedAsync 自定义失败行为。
每个命令处理方法执行时,Context 属性提供当前消息的完整上下文:
Context.SelfId— 机器人 QQ 号Context.GroupId— 群号(私聊时为 null)Context.UserId— 发送者 QQ 号Context.TextContent— 提取后的纯文本Context.Messages— 原始消息链Context.Bot— 用于主动调用 API(发消息、撤回等)
如果不想用控制器模式,也可以直接订阅 NapAronaEventBus 上的事件:
var eventBus = app.Services.GetRequiredService<NapAronaEventBus>();
eventBus.OnGroupMessage += botEvent =>
{
logger.LogInformation("群 {GroupId} 收到消息", botEvent.Event.GroupId);
};事件总线覆盖了 OneBot 协议中的所有事件类型,包括消息、通知、元事件等。
框架通过 BotSessionManager 管理多个 Bot 的连接。多个 NapCat 实例可以同时连接到同一个 WebSocket 端点,每个连接通过 lifecycle 事件自动识别身份,事件总线会携带 SelfId 区分来源。
NapArona.Hosting— ASP.NET Core 集成层:WebSocket 端点、会话管理、事件总线NapArona.Controllers— 控制器框架:路由表、命令解析、参数绑定、特性定义NapArona.Example— 示例项目
依赖 .NET 9 和 NapPlana.NET。