diff --git a/Mirai-CSharp.Example/HttpApiPlugin.cs b/Mirai-CSharp.Example/HttpApiPlugin.cs index 800ad79..55610b5 100644 --- a/Mirai-CSharp.Example/HttpApiPlugin.cs +++ b/Mirai-CSharp.Example/HttpApiPlugin.cs @@ -1,5 +1,8 @@ +using System.Collections.Generic; using System.Threading.Tasks; +using Microsoft.Extensions.Logging; using Mirai.CSharp.HttpApi.Handlers; +using Mirai.CSharp.HttpApi.Models.ChatMessages; using Mirai.CSharp.HttpApi.Models.EventArgs; using Mirai.CSharp.HttpApi.Parsers; using Mirai.CSharp.HttpApi.Parsers.Attributes; @@ -9,14 +12,26 @@ namespace Mirai.CSharp.Example { // 为此消息处理类标定所需要使用到的消息解析器 // 标定的特性仅在使用 IMessageFrameworkBuilder.AddHandler 和 IMessageFrameworkBuilder.ResolveParser 时才会被解析 - [RegisterMiraiHttpParser(typeof(DefaultMappableMiraiHttpMessageParser))] - public class HttpApiPlugin : MiraiHttpMessageHandler, // .NET Framework 只能继承 MiraiHttpMessageHandler / DedicateMiraiHttpMessageHandler - IMiraiHttpMessageHandler // .NET Core 起, 你应该直接实现 IMiraiHttpMessageHandler / IDedicateMiraiHttpMessageHandler 接口 + [RegisterMiraiHttpParser(typeof(DefaultMappableMiraiHttpMessageParser))] + public partial class HttpApiPlugin : MiraiHttpMessageHandler, // .NET Framework 只能继承 MiraiHttpMessageHandler / DedicateMiraiHttpMessageHandler + IMiraiHttpMessageHandler // .NET Core 起, 你应该直接实现 IMiraiHttpMessageHandler / IDedicateMiraiHttpMessageHandler 接口 { + private readonly ILogger _logger; + + public HttpApiPlugin(ILogger logger) + { + _logger = logger; + } + // 使用 .NET Core 时, 删去 override 和 基类继承 - public override Task HandleMessageAsync(IMiraiHttpSession session, IGroupMessageEventArgs message) + public override Task HandleMessageAsync(IMiraiHttpSession session, IFriendMessageEventArgs message) { + LogFriendMessage(_logger, message.Sender.Name, message.Sender.Id, string.Join(null, (IEnumerable)message.Chain)); + // / 来源QQ昵称 / / 来源QQ号 / / 消息链的字符串表示 / return Task.CompletedTask; } + + [LoggerMessage(EventId = 0, Level = LogLevel.Information, Message = "{name}[{fromQQ}]:{message}")] + protected static partial void LogFriendMessage(ILogger logger, string name, long fromQQ, string message); } } diff --git a/Mirai-CSharp.Example/MiraiPlugin.cs b/Mirai-CSharp.Example/MiraiPlugin.cs index 6271030..07d2b5e 100644 --- a/Mirai-CSharp.Example/MiraiPlugin.cs +++ b/Mirai-CSharp.Example/MiraiPlugin.cs @@ -1,5 +1,8 @@ +using System.Collections.Generic; using System.Threading.Tasks; +using Microsoft.Extensions.Logging; using Mirai.CSharp.Handlers; +using Mirai.CSharp.Models.ChatMessages; using Mirai.CSharp.Models.EventArgs; using Mirai.CSharp.Session; @@ -7,13 +10,25 @@ namespace Mirai.CSharp.Example { // 从 (I)MiraiMessageHandler 继承(实现), 且 TMessage 位于 Mirai.CSharp.Models.EventArgs 时, 将处理任何实现框架的消息, 包括但不限于 HttpApi, Native // 意味着你无需引用 Mirai-CSharp.HttpApi - public class MiraiPlugin : MiraiMessageHandler, // .NET Framework 只能继承 MiraiMessageHandler - IMiraiMessageHandler // .NET Core 起, 你应该直接实现 IMiraiMessageHandler 接口 + public partial class MiraiPlugin : MiraiMessageHandler, // .NET Framework 只能继承 MiraiMessageHandler + IMiraiMessageHandler // .NET Core 起, 你应该直接实现 IMiraiMessageHandler 接口 { + private readonly ILogger _logger; + + public MiraiPlugin(ILogger logger) + { + _logger = logger; + } + // 使用 .NET Core 时, 删去 override 和 基类继承 public override Task HandleMessageAsync(IMiraiSession session, IGroupMessageEventArgs message) { + LogGroupMessage(_logger, message.Sender.Group.Id, message.Sender.Name, message.Sender.Id, string.Join(null, (IEnumerable)message.Chain)); + // / 来源群号 / / 来源QQ昵称 / / 来源QQ号 / / 消息链的字符串表示 / return Task.CompletedTask; } + + [LoggerMessage(EventId = 0, Level = LogLevel.Information, Message = "[{groupNumber}] {name}[{fromQQ}]:{message}")] + protected static partial void LogGroupMessage(ILogger logger, long groupNumber, string name, long fromQQ, string message); } } diff --git a/README.md b/README.md index 0e6b3d1..b731fa1 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@
- + @@ -18,97 +18,228 @@ 这是一个帮助C#开发者与 [Mirai](https://github.com/mamoe/mirai) 交互的项目 它通过调用 [mirai-api-http](https://github.com/mamoe/mirai-api-http) 提供的 http-api 与其交互 - - ## 开始使用 ### 安装 -最简单的方式是从 nuget 上获取 Mirai-CSharp 包, 并且我们也推荐你在 nuget 包管理器中为项目安装它, 不过你也可以手动克隆项目, 编译, 并直接引用链接库. +最简单的方式是从 nuget 上获取 Mirai-CSharp 的相关包, 并且我们也推荐你在 nuget 包管理器中为项目安装它, 不过你也可以手动克隆项目, 编译, 并直接引用链接库. -> 在使用 nuget 安装包时, 如若要使用最新功能, 请勾选 "包括发行版" +> 在使用 nuget 安装包时, 如若要使用最新功能, 请勾选 "包括预发行版" > -> 注意, 最新版本已将包分离为 Mirai-CSharp 以及 Mirai-CSharp.HttpApi, 其中第一个中只包含程序接口之类的, 第二个中包含的是其实现, 并且在该预览版中, 与正式版发布的内容差异较大, 项目结构有巨大改变 +> 从2.X版本开始, 本项目将使用依赖注入框架, 必须先注册相关服务才能使用。可选择这些常用的依赖注入框架: `Microsoft.Extensions.DependencyInjection`, `AutoFac` + +### 内部实现解释 +从2.X版本起, 作者已将包分离为 `Mirai-CSharp` 以及 `Mirai-CSharp.HttpApi`, 其中第一个中只包含框架的基本抽象, 第二个中包含的是其特定实现, 并且在2.X版本中, 与1.X版本发布的内容差异较大, 项目结构有巨大改变 +在2.X版本中, 本框架将使用作者自己实现的消息框架进行消息分发。框架有以下五个组件:构建器(Builder)、调度器(Invoker)、处理器(Handler)、可选:解析器(Parser)、客户端(Client)。各组件功能如下: +1. 构建器(Builder): 负责在依赖注入框架中注册相关组件 +2. 调度器(Invoker): 负责将消息实例传给合适的处理器(Handler), 也可使用合适的解析器(Parser)先处理原始数据为相应消息实例 +3. 处理器(Handler): 负责处理相关客户端和消息实例的实现 +4. 解析器(Parser): 负责解析原始数据为相应的消息实例 +5. 客户端(Client): 负责收发原始数据,并调用调度器(Invoker)进行消息处理 +以 `Mirai-CSharp.HttpApi` 为例, 从原始数据到消息处理器的简化版流程如下: + +``` + mirai-api-http + | + (json) + v +Mirai.CSharp.HttpApi.Session.IMiraiHttpSession (客户端) + | + (JsonElement) + v +Mirai.CSharp.HttpApi.Invoking.IMiraiHttpMessageHandlerInvoker (调度器) + | + (JsonElement) + v +Mirai.CSharp.HttpApi.Parsers.IMiraiHttpMessageParser (解析器) + | + (Mirai.CSharp.HttpApi.Models.IMiraiHttpMessage) + v + Mirai.CSharp.HttpApi.Invoking.IMiraiHttpMessageHandlerInvoker (调度器) + | + (Mirai.CSharp.HttpApi.Models.IMiraiHttpMessage) + v +Mirai.CSharp.HttpApi.Handlers.IMiraiHttpMessageHandlerBase (处理器) +``` ### 示例 +> 以下示例将使用这三个包: `Microsoft.Extensions.DependencyInjection`, `Mirai-CSharp`, `Mirai-CSharp.HttpApi`, 请用户先行安装 下面以一个最简单的控制台程序为示例, 对 QQ 内的任何 at 自己了的群聊消息响应 "Hello world" 文本消息 -在目前已正式发布的最新版本中, Mirai-CSharp 的常用核心组件位于 Mirai_CSharp 以及 Mirai_CSharp.Models 命名空间中. - -首先我们可以引用它, 下面是基础框架: +在目前已正式发布的最新版本2.X中, `Mirai.CSharp` 的基础核心组件位于 `Mirai.CSharp`, `Mirai.CSharp.Models` 以及 `Mirai.CSharp.Builders` 命名空间中. +首先我们定义一个基于所有特定实现的用于打印 QQ 内的任何群聊消息类 ```csharp -using System; -using System.Linq; -using Mirai.CSharp; -using Mirai.CSharp.Models; - -namespace TestProj +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Mirai.CSharp.Handlers; +using Mirai.CSharp.Models.ChatMessages; +using Mirai.CSharp.Models.EventArgs; +using Mirai.CSharp.Session; + +namespace Mirai.CSharp.Example { - class Program + // 从 (I)MiraiMessageHandler 继承(实现), 且 TMessage 位于 Mirai.CSharp.Models.EventArgs 时, 将处理任何实现框架的消息, 包括但不限于 HttpApi, Native + // 意味着你无需引用 Mirai-CSharp.HttpApi + public partial class MiraiPlugin : MiraiMessageHandler, // .NET Framework 只能继承 MiraiMessageHandler + IMiraiMessageHandler // .NET Core 起, 你应该直接实现 IMiraiMessageHandler 接口 { - static void Main(string[] args) + private readonly ILogger _logger; + + public MiraiPlugin(ILogger logger) { - + _logger = logger; } - } -} -``` -Mirai-CSharp 是要与 Mirai 的 mira-http-api 进行交互的, 所以我们接下来创建一个会话(Session), 并连接到在 Mirai 中已经登录的 QQ + // 使用 .NET Core 时, 删去 override 和 基类继承 + public override Task HandleMessageAsync(IMiraiSession session, IGroupMessageEventArgs message) + { + LogGroupMessage(_logger, message.Sender.Group.Id, message.Sender.Name, message.Sender.Id, string.Join(null, (IEnumerable)message.Chain)); + // / 来源群号 / / 来源QQ昵称 / / 来源QQ号 / / 消息链的字符串表示 / + return Task.CompletedTask; + } -```csharp -// 下面是位于 Main 方法的代码 -MiraiHttpSession session = new MiraiHttpSession(); // 创建会话 -session.ConnectAsync( // 连接并等待 - new MiraiHttpSessionOptions("localhost", 1234, "authKey"), // 连接选项, 地址, 端口, 以及验证密钥, 这些均位于 mirai-http-api 配置文件中 - 1234567890).Wait(); // Mirai 中已经登录的 QQ 机器人的 QQ 号码 + [LoggerMessage(EventId = 0, Level = LogLevel.Information, Message = "[{groupNumber}] {name}[{fromQQ}]:{message}")] + protected static partial void LogGroupMessage(ILogger logger, long groupNumber, string name, long fromQQ, string message); + } +} ``` -下面为 session 添加群聊成员消息时间的处理方法: - +也可定义一个基于 `Mirai-CSharp.HttpApi`(`mirai-api-http`) 实现的用于打印 QQ 内的任何私聊消息类, 并为其标注所需要使用的消息解析器 ```csharp -// 下面是位于 Main 方法的代码 -session.GroupMessageEvt += async (sender, e) => -{ - await sender.SendGroupMessageAsync(e.Sender.Group.Id, new PlainMessage("Hello world")); // 在消息发送者所在的群聊内发送 Hello world - return false; -}; -session.GroupMessageEvt += async (sender, e) => // Mirai-CSharp 的事件处理应该是纯异步的, 我们应该使用异步方法(返回Task) +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Mirai.CSharp.HttpApi.Handlers; +using Mirai.CSharp.HttpApi.Models.ChatMessages; +using Mirai.CSharp.HttpApi.Models.EventArgs; +using Mirai.CSharp.HttpApi.Parsers; +using Mirai.CSharp.HttpApi.Parsers.Attributes; +using Mirai.CSharp.HttpApi.Session; + +namespace Mirai.CSharp.Example { - if (e.Chain.Where(v => v is AtMessage atMsg && atMsg.Target == session.QQNumber).Any()) // 判断是否 at 自己 - await sender.SendGroupMessageAsync(e.Sender.Group.Id, new PlainMessage("Hello world")); // 发送 "Hello world" - - // PlainMessage 位于 Mirai_CSharp.Models 命名空间下, 基于 IMessage - - return false; // Task 的返回结果标识当前事件是否被阻断, 如果返回 true, 那么后面的事件订阅者将不会收到事件 (这里返回false表示不阻断) -}; -``` - + // 为此消息处理类标定所需要使用到的消息解析器 + // 标定的特性仅在使用 IMessageFrameworkBuilder.AddHandler 和 IMessageFrameworkBuilder.ResolveParser 时才会被解析 + [RegisterMiraiHttpParser(typeof(DefaultMappableMiraiHttpMessageParser))] + public partial class HttpApiPlugin : MiraiHttpMessageHandler, // .NET Framework 只能继承 MiraiHttpMessageHandler / DedicateMiraiHttpMessageHandler + IMiraiHttpMessageHandler // .NET Core 起, 你应该直接实现 IMiraiHttpMessageHandler / IDedicateMiraiHttpMessageHandler 接口 + { + private readonly ILogger _logger; + public HttpApiPlugin(ILogger logger) + { + _logger = logger; + } -### 改动 + // 使用 .NET Core 时, 删去 override 和 基类继承 + public override Task HandleMessageAsync(IMiraiHttpSession session, IFriendMessageEventArgs message) + { + LogFriendMessage(_logger, message.Sender.Name, message.Sender.Id, string.Join(null, (IEnumerable)message.Chain)); + // / 来源QQ昵称 / / 来源QQ号 / / 消息链的字符串表示 / + return Task.CompletedTask; + } -在当前的最新版本(预览版)中, 包已经分离开来, 由旧的只有一个 "Mirai-CSharp" 就包含所有功能, 变更为 "Mirai-CSharp" 提供基本接口, "Mirai-CSharp.HttpApi" 提供接口实现. + [LoggerMessage(EventId = 0, Level = LogLevel.Information, Message = "{name}[{fromQQ}]:{message}")] + protected static partial void LogFriendMessage(ILogger logger, string name, long fromQQ, string message); + } +} -并且一些类型的命名空间也有所改动, 例如原来的 MiraiHttpSession 被移动到 Mirai.CSharp.HttpApi.Models 命名空间. 所以在使用最新预览版时应注意命名空间的更改. +``` +然后我们需要注册 `Mirai-CSharp` 和 `Mirai-CSharp.HttpApi` 的基础服务, 并注册上述消息处理器。 +定义注册必要服务的方法和实际使用的方法, 具体请看注释: +```csharp +using System; +using System.Collections; +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Mirai.CSharp.Builders; +using Mirai.CSharp.HttpApi.Builder; +using Mirai.CSharp.HttpApi.Invoking; +using Mirai.CSharp.HttpApi.Models.ChatMessages; +using Mirai.CSharp.HttpApi.Options; +using Mirai.CSharp.HttpApi.Session; + +namespace Mirai.CSharp.Example +{ + public static class Program // 前提: nuget Mirai-CSharp, 版本需要 >= 2.0.0 + { + public static Task Main() + { + IServiceCollection sc = new ServiceCollection(); // 如果你使用 ASP.NET Core 相关, 在 Startup 中执行以下方法来注册服务即可 + RegisterServices(sc); + IServiceProvider services = sc.BuildServiceProvider(); + return ConnectAndWaitExitAsync(services); + } + private static void RegisterServices(IServiceCollection services) + { + services.AddMiraiBaseFramework() // 表示使用基于基础框架的构建器 + .AddHandler() // 虽然可以把 HttpApiPlugin 作为泛型参数塞入, 但不建议这么做 + .Services + .AddDefaultMiraiHttpFramework() // 表示使用 mirai-api-http 实现的构建器 + .AddInvoker() // 使用默认的调度器 + .AddHandler() // 可以如此添加更多消息处理器 + .AddClient() // 使用默认的客户端 + .AddParser>() // 由于 MiraiPlugin 是基于所有平台的消息处理器, 一般不在该类上标注特定平台实现所需用的消息解析器, 所以我们手动添加所需的消息解析器 + .Services + // 由于 MiraiHttpSession 使用 IOptions, 其作为 Singleton 被注册 + // 配置此项将配置基于此 IServiceProvider 全局的连接配置 + // 如果你想一个作用域一个配置的话 + // 需要自行做一个实现类, 继承MiraiHttpSession, 构造参数中使用 IOptionsSnapshot + // 并将其传递给父类的构造参数 + // 然后在每一个作用域中!先!配置好 IOptionsSnapshot, 再尝试获取 IMiraiHttpSession + .Configure(options => + { + options.Host = "127.0.0.1"; // 主机/IP + options.Port = 33111; // 端口 + options.AuthKey = "yourkey"; // 凭据 + }) + .AddLogging(); + } + public static async Task ConnectAndWaitExitAsync(IServiceProvider services) + { + // 由于注册时使用了默认的客户端生命周期:Scoped, 我们需要创建一个IServiceScope + IServiceScope scope = services.CreateAsyncScope(); + await using var x = (IAsyncDisposable)scope; + //await using AsyncServiceScope scope = services.CreateAsyncScope(); // 自 .NET 6.0 起才可以如此操作代替上边两句 + services = scope.ServiceProvider; + // 大部分服务都基于接口注册, 请使用接口作为类型解析 + IMiraiHttpSession session = services.GetRequiredService(); + await session.ConnectAsync(0); // 填入期望连接到的机器人QQ号 + while (true) + { + // 接下来就万事大吉了, 消息框架会自动实例化消息处理器并在收到相应的消息时调用它们 + if (Console.ReadLine() == "exit") + { + break; + } + } + } + } +} +``` ## 注意事项 -- 本项目使用`C# 9.0`编写, 你需要至少`.NET Core 2.0` 或 `.NET Framework 4.6.1`才能使用本项目, 其中所有的api均为**异步**方法 - - +- 本项目使用`C# 9.0`编写, 你需要至少`.NET Core 2.0` 或 `.NET Framework 4.6.1`才能使用本项目, 其中所有的api均为**异步**方法 +- 无论是在 `Mirai-CSharp` 或是 `Mirai-CSharp.HttpApi` 中, 各组件的默认生命周期如下: + 1. 调度器(Invoker): Scoped + 2. 处理器(Handler): Scoped + 3. 解析器(Parser): Singleton + 4. 客户端(Client): Scoped ## 使用例子 - [基于任何实现框架的插件](https://github.com/Executor-Cheng/Mirai-CSharp/blob/master/Mirai-CSharp.Example/MiraiPlugin.cs) - [基于特定实现框架的插件](https://github.com/Executor-Cheng/Mirai-CSharp/blob/master/Mirai-CSharp.Example/HttpApiPlugin.cs) -- [对动态增删的注释](https://github.com/Executor-Cheng/Mirai-CSharp/blob/master/Mirai-CSharp.Example/DynamicPlugin.cs) +- [对动态增删消息处理器的注释](https://github.com/Executor-Cheng/Mirai-CSharp/blob/master/Mirai-CSharp.Example/DynamicPlugin.cs) - [配置Session](https://github.com/Executor-Cheng/Mirai-CSharp/tree/master/Mirai-CSharp.Example/Program.cs) - [处理好友消息](https://github.com/Executor-Cheng/Mirai-CSharp/blob/master/Mirai-CSharp.Example/ExamplePlugin.FriendMessage.cs) - [处理群消息](https://github.com/Executor-Cheng/Mirai-CSharp/blob/master/Mirai-CSharp.Example/ExamplePlugin.GroupMessage.cs)