From 5a27b47668d1ba0dca3d6e1e2296bb99b9ef1a7a Mon Sep 17 00:00:00 2001 From: b1acksoil Date: Sun, 27 Nov 2022 14:05:10 +0800 Subject: [PATCH 1/2] ci: update publish ci --- .github/workflows/publish.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index ad9fe53..966c496 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -3,8 +3,6 @@ on: push: branches: [ main ] - pull_request: - branches: [ main ] jobs: publish: @@ -27,7 +25,7 @@ jobs: VERSION_FILE_PATH: src/Flandre.Core/Flandre.Core.csproj VERSION_REGEX: ^\s*(.*)<\/PackageVersion>\s*$ TAG_COMMIT: true - TAG_FORMAT: v* + TAG_FORMAT: core_v* NUGET_KEY: ${{secrets.NUGET_API_KEY}} - name: Publish Adapters.Konata From 9bd3e46aab8eb4eb14f0e7591ab1496472779d23 Mon Sep 17 00:00:00 2001 From: b1acksoil Date: Tue, 29 Nov 2022 16:00:13 +0800 Subject: [PATCH 2/2] refactor: split project into two packages --- .github/workflows/publish.yml | 14 +- .github/workflows/test.yml | 14 +- Flandre.sln | 24 +- README.NuGet.md | 166 +-------- README.md | 38 +- .../Flandre.Adapters.Konata.csproj | 4 +- src/Flandre.Adapters.Konata/KonataAdapter.cs | 38 +- src/Flandre.Adapters.Konata/KonataBot.cs | 30 +- src/Flandre.Adapters.Mock/Extensions.cs | 2 +- .../Flandre.Adapters.Mock.csproj | 4 +- src/Flandre.Adapters.Mock/MockAdapter.cs | 3 +- src/Flandre.Adapters.Mock/MockBot.cs | 14 +- src/Flandre.Adapters.Mock/MockClient.cs | 6 +- src/Flandre.Adapters.OneBot/CqCodeParser.cs | 2 +- .../Flandre.Adapters.OneBot.csproj | 4 +- src/Flandre.Adapters.OneBot/GuildBot.cs | 16 +- src/Flandre.Adapters.OneBot/OneBotAdapter.cs | 9 +- src/Flandre.Adapters.OneBot/OneBotBot.cs | 9 +- src/Flandre.Adapters.OneBot/OneBotUtils.cs | 12 +- src/Flandre.Adapters.OneBot/WebSocketBot.cs | 16 +- src/Flandre.Core/AssemblyInfo.cs | 4 + .../Attributes/PluginAttribute.cs | 22 -- src/Flandre.Core/Common/Bot.cs | 71 +--- src/Flandre.Core/Common/BotEvents.cs | 64 ++++ src/Flandre.Core/Common/BotLogLevel.cs | 17 + src/Flandre.Core/Common/Context.cs | 9 +- src/Flandre.Core/Common/IAdapter.cs | 2 +- src/Flandre.Core/Common/IModule.cs | 8 - src/Flandre.Core/Common/Plugin.cs | 158 -------- .../Events/App/AppCommandInvokedEvent.cs | 38 -- .../Events/App/AppCommandInvokingEvent.cs | 32 -- .../Events/App/AppCommandParsedEvent.cs | 25 -- .../Events/App/AppCommandParsingEvent.cs | 38 -- src/Flandre.Core/Events/App/AppReadyEvent.cs | 11 - .../Events/App/AppStartingEvent.cs | 11 - .../Events/App/AppStoppedEvent.cs | 11 - .../{Bot => }/BotFriendRequestedEvent.cs | 2 +- .../Events/{Bot => }/BotGuildInvitedEvent.cs | 2 +- .../{Bot => }/BotGuildJoinRequestedEvent.cs | 2 +- src/Flandre.Core/Events/BotLoggingEvent.cs | 25 ++ .../{Bot => }/BotMessageReceivedEvent.cs | 2 +- src/Flandre.Core/Events/CancellableEvent.cs | 12 - .../Events/Logger/LoggerLoggingEvent.cs | 17 - .../Events/Plugin/PluginStartingEvent.cs | 17 - .../Events/Plugin/PluginStoppedEvent.cs | 17 - .../Extensions/ContextExtensions.cs | 19 - src/Flandre.Core/Flandre.Core.csproj | 6 +- src/Flandre.Core/FlandreApp.cs | 336 ------------------ src/Flandre.Core/FlandreAppConfig.cs | 19 - src/Flandre.Core/Messaging/MessageContext.cs | 5 +- src/Flandre.Core/Middlewares.cs | 38 -- .../{FlandreUtils.cs => FlandreCoreUtils.cs} | 2 +- src/Flandre.Core/Utils/Logger.cs | 147 -------- src/Flandre.Framework/AssemblyInfo.cs | 3 + .../Attributes/AliasAttribute.cs | 4 +- .../Attributes/CommandAttribute.cs | 14 +- .../Attributes/OptionAttribute.cs | 3 +- .../Attributes/ShortcutAttribute.cs | 2 +- .../Common/ArgumentManager.cs | 2 +- .../Common/Command.cs | 33 +- .../Common/MiddlewareContext.cs | 15 + .../Common/OptionManager.cs | 2 +- .../Common/ParsedArgs.cs | 2 +- src/Flandre.Framework/Common/Plugin.cs | 42 +++ src/Flandre.Framework/Events/AppReadyEvent.cs | 13 + .../Events/AppStartingEvent.cs | 13 + .../Events/AppStoppedEvent.cs | 13 + .../Extensions/FlandreAppExtensions.cs} | 7 +- .../Flandre.Framework.csproj | 42 +++ src/Flandre.Framework/FlandreApp.cs | 220 ++++++++++++ src/Flandre.Framework/FlandreAppBuilder.cs | 77 ++++ src/Flandre.Framework/FlandreAppConfig.cs | 12 + src/Flandre.Framework/FlandreAppEvents.cs | 15 + src/Flandre.Framework/InternalMiddlewares.cs | 92 +++++ .../Utils/CommandUtils.cs | 14 +- .../CommonTests/PluginTests.cs | 5 - .../Flandre.Framework.Tests.csproj | 29 ++ .../FlandreAppTests.cs | 29 +- .../MiddlewareTests.cs | 22 +- .../TestPlugin.cs | 15 +- tests/Flandre.Framework.Tests/Usings.cs | 1 + .../UtilsTests/CommandUtilsTests.cs | 4 +- 82 files changed, 935 insertions(+), 1424 deletions(-) create mode 100644 src/Flandre.Core/AssemblyInfo.cs delete mode 100644 src/Flandre.Core/Attributes/PluginAttribute.cs create mode 100644 src/Flandre.Core/Common/BotEvents.cs create mode 100644 src/Flandre.Core/Common/BotLogLevel.cs delete mode 100644 src/Flandre.Core/Common/IModule.cs delete mode 100644 src/Flandre.Core/Common/Plugin.cs delete mode 100644 src/Flandre.Core/Events/App/AppCommandInvokedEvent.cs delete mode 100644 src/Flandre.Core/Events/App/AppCommandInvokingEvent.cs delete mode 100644 src/Flandre.Core/Events/App/AppCommandParsedEvent.cs delete mode 100644 src/Flandre.Core/Events/App/AppCommandParsingEvent.cs delete mode 100644 src/Flandre.Core/Events/App/AppReadyEvent.cs delete mode 100644 src/Flandre.Core/Events/App/AppStartingEvent.cs delete mode 100644 src/Flandre.Core/Events/App/AppStoppedEvent.cs rename src/Flandre.Core/Events/{Bot => }/BotFriendRequestedEvent.cs (95%) rename src/Flandre.Core/Events/{Bot => }/BotGuildInvitedEvent.cs (97%) rename src/Flandre.Core/Events/{Bot => }/BotGuildJoinRequestedEvent.cs (96%) create mode 100644 src/Flandre.Core/Events/BotLoggingEvent.cs rename src/Flandre.Core/Events/{Bot => }/BotMessageReceivedEvent.cs (92%) delete mode 100644 src/Flandre.Core/Events/CancellableEvent.cs delete mode 100644 src/Flandre.Core/Events/Logger/LoggerLoggingEvent.cs delete mode 100644 src/Flandre.Core/Events/Plugin/PluginStartingEvent.cs delete mode 100644 src/Flandre.Core/Events/Plugin/PluginStoppedEvent.cs delete mode 100644 src/Flandre.Core/Extensions/ContextExtensions.cs delete mode 100644 src/Flandre.Core/FlandreApp.cs delete mode 100644 src/Flandre.Core/FlandreAppConfig.cs delete mode 100644 src/Flandre.Core/Middlewares.cs rename src/Flandre.Core/Utils/{FlandreUtils.cs => FlandreCoreUtils.cs} (97%) delete mode 100644 src/Flandre.Core/Utils/Logger.cs create mode 100644 src/Flandre.Framework/AssemblyInfo.cs rename src/{Flandre.Core => Flandre.Framework}/Attributes/AliasAttribute.cs (86%) rename src/{Flandre.Core => Flandre.Framework}/Attributes/CommandAttribute.cs (82%) rename src/{Flandre.Core => Flandre.Framework}/Attributes/OptionAttribute.cs (96%) rename src/{Flandre.Core => Flandre.Framework}/Attributes/ShortcutAttribute.cs (92%) rename src/{Flandre.Core => Flandre.Framework}/Common/ArgumentManager.cs (97%) rename src/{Flandre.Core => Flandre.Framework}/Common/Command.cs (89%) create mode 100644 src/Flandre.Framework/Common/MiddlewareContext.cs rename src/{Flandre.Core => Flandre.Framework}/Common/OptionManager.cs (95%) rename src/{Flandre.Core => Flandre.Framework}/Common/ParsedArgs.cs (96%) create mode 100644 src/Flandre.Framework/Common/Plugin.cs create mode 100644 src/Flandre.Framework/Events/AppReadyEvent.cs create mode 100644 src/Flandre.Framework/Events/AppStartingEvent.cs create mode 100644 src/Flandre.Framework/Events/AppStoppedEvent.cs rename src/{Flandre.Core/Extensions/AppExtensions.cs => Flandre.Framework/Extensions/FlandreAppExtensions.cs} (86%) create mode 100644 src/Flandre.Framework/Flandre.Framework.csproj create mode 100644 src/Flandre.Framework/FlandreApp.cs create mode 100644 src/Flandre.Framework/FlandreAppBuilder.cs create mode 100644 src/Flandre.Framework/FlandreAppConfig.cs create mode 100644 src/Flandre.Framework/FlandreAppEvents.cs create mode 100644 src/Flandre.Framework/InternalMiddlewares.cs rename src/{Flandre.Core => Flandre.Framework}/Utils/CommandUtils.cs (88%) delete mode 100644 tests/Flandre.Core.Tests/CommonTests/PluginTests.cs create mode 100644 tests/Flandre.Framework.Tests/Flandre.Framework.Tests.csproj rename tests/{Flandre.Core.Tests => Flandre.Framework.Tests}/FlandreAppTests.cs (84%) rename tests/{Flandre.Core.Tests => Flandre.Framework.Tests}/MiddlewareTests.cs (67%) rename tests/{Flandre.Core.Tests => Flandre.Framework.Tests}/TestPlugin.cs (83%) create mode 100644 tests/Flandre.Framework.Tests/Usings.cs rename tests/{Flandre.Core.Tests => Flandre.Framework.Tests}/UtilsTests/CommandUtilsTests.cs (97%) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 966c496..b59bc68 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -28,6 +28,18 @@ jobs: TAG_FORMAT: core_v* NUGET_KEY: ${{secrets.NUGET_API_KEY}} + - name: Publish Framework + id: publish-fx + uses: alirezanet/publish-nuget@v3.0.4 + with: + PROJECT_FILE_PATH: src/Flandre.Framework/Flandre.Framework.csproj + PACKAGE_NAME: Flandre.Framework + VERSION_FILE_PATH: src/Flandre.Framework/Flandre.Framework.csproj + VERSION_REGEX: ^\s*(.*)<\/PackageVersion>\s*$ + TAG_COMMIT: true + TAG_FORMAT: fx_v* + NUGET_KEY: ${{secrets.NUGET_API_KEY}} + - name: Publish Adapters.Konata id: publish-adapter-konata uses: alirezanet/publish-nuget@v3.0.4 @@ -49,7 +61,7 @@ jobs: VERSION_REGEX: ^\s*(.*)<\/PackageVersion>\s*$ TAG_COMMIT: false NUGET_KEY: ${{secrets.NUGET_API_KEY}} - + - name: Publish Adapters.Mock id: publish-adapter-mock uses: alirezanet/publish-nuget@v3.0.4 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c85921d..f28b200 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -5,7 +5,7 @@ on: branches: [ dev ] pull_request: branches: [ dev ] - + jobs: unit-test: name: Unit Test @@ -17,14 +17,18 @@ jobs: uses: actions/setup-dotnet@v1 with: dotnet-version: 6.x.x - + - name: Test Flandre.Core run: | dotnet test tests/Flandre.Core.Tests/ --collect:"XPlat Code Coverage" -r code-coverage/ mv code-coverage/**/coverage.cobertura.xml code-coverage/core.xml - + + - name: Test Flandre.Framework + run: | + dotnet test tests/Flandre.Framework.Tests/ --collect:"XPlat Code Coverage" -r code-coverage/ + mv code-coverage/**/coverage.cobertura.xml code-coverage/fx.xml + - name: Upload Code Coverage to Codecov uses: codecov/codecov-action@v3 with: - files: code-coverage/core.xml - fail_ci_if_error: true \ No newline at end of file + files: code-coverage/core.xml,code-coverage/fx.xml diff --git a/Flandre.sln b/Flandre.sln index e24b411..1b5775b 100644 --- a/Flandre.sln +++ b/Flandre.sln @@ -4,12 +4,16 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Flandre.Core", "src\Flandre EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Flandre.Adapters.Konata", "src\Flandre.Adapters.Konata\Flandre.Adapters.Konata.csproj", "{723E8DD2-55F1-4095-93D7-E6AD600A63EE}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Flandre.Core.Tests", "tests\Flandre.Core.Tests\Flandre.Core.Tests.csproj", "{22726A9B-8E54-4513-AB04-61B4A34DDC2A}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Flandre.Adapters.Mock", "src\Flandre.Adapters.Mock\Flandre.Adapters.Mock.csproj", "{18C926FE-1F45-421C-A19B-BB914DBD7437}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Flandre.Adapters.OneBot", "src\Flandre.Adapters.OneBot\Flandre.Adapters.OneBot.csproj", "{49047181-70A7-44F9-BC70-992BD0289C97}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Flandre.Framework", "src\Flandre.Framework\Flandre.Framework.csproj", "{76195FF7-5683-451D-9D22-955CE0CA20EF}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Flandre.Framework.Tests", "tests\Flandre.Framework.Tests\Flandre.Framework.Tests.csproj", "{23B5856D-C230-4EC6-A3C0-A31E56AABFC9}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Flandre.Core.Tests", "tests\Flandre.Core.Tests\Flandre.Core.Tests.csproj", "{99CA905D-7B6C-4615-BF50-F06CAB14941D}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -24,10 +28,6 @@ Global {723E8DD2-55F1-4095-93D7-E6AD600A63EE}.Debug|Any CPU.Build.0 = Debug|Any CPU {723E8DD2-55F1-4095-93D7-E6AD600A63EE}.Release|Any CPU.ActiveCfg = Release|Any CPU {723E8DD2-55F1-4095-93D7-E6AD600A63EE}.Release|Any CPU.Build.0 = Release|Any CPU - {22726A9B-8E54-4513-AB04-61B4A34DDC2A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {22726A9B-8E54-4513-AB04-61B4A34DDC2A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {22726A9B-8E54-4513-AB04-61B4A34DDC2A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {22726A9B-8E54-4513-AB04-61B4A34DDC2A}.Release|Any CPU.Build.0 = Release|Any CPU {18C926FE-1F45-421C-A19B-BB914DBD7437}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {18C926FE-1F45-421C-A19B-BB914DBD7437}.Debug|Any CPU.Build.0 = Debug|Any CPU {18C926FE-1F45-421C-A19B-BB914DBD7437}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -36,5 +36,17 @@ Global {49047181-70A7-44F9-BC70-992BD0289C97}.Debug|Any CPU.Build.0 = Debug|Any CPU {49047181-70A7-44F9-BC70-992BD0289C97}.Release|Any CPU.ActiveCfg = Release|Any CPU {49047181-70A7-44F9-BC70-992BD0289C97}.Release|Any CPU.Build.0 = Release|Any CPU + {76195FF7-5683-451D-9D22-955CE0CA20EF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {76195FF7-5683-451D-9D22-955CE0CA20EF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {76195FF7-5683-451D-9D22-955CE0CA20EF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {76195FF7-5683-451D-9D22-955CE0CA20EF}.Release|Any CPU.Build.0 = Release|Any CPU + {23B5856D-C230-4EC6-A3C0-A31E56AABFC9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {23B5856D-C230-4EC6-A3C0-A31E56AABFC9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {23B5856D-C230-4EC6-A3C0-A31E56AABFC9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {23B5856D-C230-4EC6-A3C0-A31E56AABFC9}.Release|Any CPU.Build.0 = Release|Any CPU + {99CA905D-7B6C-4615-BF50-F06CAB14941D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {99CA905D-7B6C-4615-BF50-F06CAB14941D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {99CA905D-7B6C-4615-BF50-F06CAB14941D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {99CA905D-7B6C-4615-BF50-F06CAB14941D}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/README.NuGet.md b/README.NuGet.md index d1df033..95d214b 100644 --- a/README.NuGet.md +++ b/README.NuGet.md @@ -1,175 +1,25 @@ # Flandre -.NET 6 实现的跨平台,低耦合的聊天机器人框架 +.NET 6 实现的跨平台,现代化聊天机器人框架 一次编写,多处运行 [![License](https://img.shields.io/github/license/FlandreDevs/Flandre?label=License&style=flat&color=42a5f5)](https://github.com/FlandreDevs/Flandre/blob/main/LICENSE) [![Stars](https://img.shields.io/github/stars/FlandreDevs/Flandre?label=Stars&style=flat&color=1976d2)](https://github.com/FlandreDevs/Flandre/stargazers) -[![Contributors](https://img.shields.io/github/contributors/FlandreDevs/Flandre?label=Contributors&style=flat&color=ab47bc)](https://github.com/FlandreDevs/Flandre/graphs/contributors) -[![NuGet](https://img.shields.io/nuget/vpre/Flandre.Core?style=flat&label=NuGet&color=f06292)](https://www.nuget.org/packages/Flandre.Core/) -[![NuGet Downloads](https://img.shields.io/nuget/dt/Flandre.Core?style=flat&label=Downloads&color=ffb300)](https://www.nuget.org/packages/Flandre.Core/) +[![Contributors](https://img.shields.io/github/contributors/FlandreDevs/Flandre?label=Contributors&style=flat&color=9866ca)](https://github.com/FlandreDevs/Flandre/graphs/contributors) [![.NET Version](https://img.shields.io/badge/.NET-6-ffe57f?style=flat)](https://www.nuget.org/packages/Flandre.Core/) [![Codecov](https://img.shields.io/codecov/c/gh/FlandreDevs/Flandre/dev?style=flat&color=a5d6a7&label=Coverage)](https://app.codecov.io/gh/FlandreDevs/Flandre) -· [使用文档](https://flandredevs.github.io/) · +\- **[使用文档](https://flandredevs.github.io/)** - 本项目的名称来源于东方 Project 中的角色芙兰朵露 · 斯卡雷特 (Flandre Scarlet) ~~(番茄炒蛋)~~ --- -**项目仍在早期开发阶段,功能尚未完善,且处于快速迭代过程中。** -**如果您对项目的开发感兴趣,诚挚欢迎您的改进建议或 PR 贡献。** - -**1.0 版本发布前随时可能发生 API 的非兼容性变更,不建议用于生产环境。** - -## 特性 - -### 跨平台 - -Flandre 自设计之初就是为了跨平台,对聊天平台的结构进行抽象化,采用适配器模式兼容各大聊天平台,同时提供了良好的开发体验。 - -目前已经实现的适配器: - -- [Flandre.Adapters.Konata](https://github.com/FlandreDevs/Flandre/blob/dev/src/Flandre.Adapters.Konata/README.md) - QQ - 协议适配,基于 [Konata.Core](https://github.com/KonataDev/Konata.Core) - -### 指令系统 - -得益于内置的指令解析系统,开发者可以方便地掌控指令的参数信息,包括但不限于参数数量检查,类型检查,参数默认值等等。而所有的定义可以在一个字符串内完成,例如: - -```csharp -[Command("example [bar:double] [baz:int=114514]")] -``` - -### 事件驱动 - -Flandre 内部采用各类事件控制,开发者可以轻松地通过订阅事件/重写相关方法的方式控制应用的运行流程。 -_注:事件系统仍在完善当中_ - -~~才发布没多久的项目再吹就吹过了~~ - -## 起步 - -遵循不知道哪里来的惯例,我们以一个复读小程序开始: - -```csharp -using Flandre.Core; -using Flandre.Adapters.Konata; -using Konata.Core.Common; - -var app = new FlandreApp(); - -var config = new KonataAdapterConfig(); -config.Bots.Add(new KonataBotConfig -{ - KeyStore = new BotKeyStore("", "<密码>") -}); - -class ExamplePlugin : Plugin -{ - public override void OnMessageReceived(MessageContext ctx) - => ctx.Bot.SendMessage(ctx.Message); -} - -app - .UseKonataAdapter(config) - .Use(new ExamplePlugin()) - .Start(); -``` - -运行程序,向我们 bot 的 QQ 号发送一条消息,bot 会将消息原封不动地发回来。 ~~复读不仅仅是人类的本质.jpg~~ - -### 基本指令解析 - -来个高级点的例子,我们定义一条指令: - -```csharp -class ExamplePlugin2 : Plugin -{ - [Command("example [bar]")] - public MessageContent OnExample(MessageContext ctx, ParsedArgs args) - { - var foo = args.GetArgument("foo"); - var bar = args.GetArgument("bar"); - - if (string.IsNullOrWhiteSpace(bar)) - bar = "(empty)"; - - var mb = new MessageBuilder(); - - mb.Text($"Foo: {foo}, ") - .Text($"Bar: {bar}"); +**项目的完整 README 可[在 GitHub 上查看](https://github.com/FlandreDevs/Flandre/)。** - return mb; - } -} -``` - -这个插件包含一条有两个参数的指令,类型都为 `string`,其中 `foo` 为必选参数,`bar` -为可选参数。如果调用指令时未提供可选参数,参数将被初始化为类型默认值;如果为提供必选参数,bot 将向其发送一条提示信息并停止执行指令。 - -向 bot 发送 `example qwq ovo`(~~随便什么~~),bot 会将参数的值发送回来。 - -### 类型约束 - -如果我们不对指令的参数进行类型约束,那么参数的类型将默认为 `string`。如要添加参数,可以在参数名称后添加 `:` 号和类型名称。类型名称支持 -C# 中绝大多数的基本类型,如 `int`, `double`, `long`, `bool` 等等,在解析过程中会自动进行类型检查和转换。 - -举个例子: - -```csharp -[Command("example ")] -public MessageContent? OnExample(MessageContext ctx, ParsedArgs args) -{ - var foo = args.GetArgument("foo"); - var bar = args.GetArgument("bar"); - - Logger.Info(foo.GetType().Name); // Double - Logger.Info(bar.GetType().Name); // Boolean - - return null; -} -``` - -### 参数默认值 - -有时我们需要对参数指定默认值,可以在定义中使用 `=` 号: - -```csharp -[Command("example [foo:int=1145] [bar:bool=true]")] -``` - -如果不人为指定默认值,参数将被初始化为 C# 中的类型默认值(即 `default(T)`)。`string` -比较特殊,在参数中它的默认值是空字符串,而不是 `null`。 - -### 灵活的表现形式 - -Flandre 内置的指令解析器允许留下空格。如果你觉得参数的各种定义挤在一起乱糟糟的,可以适度空开: - -```csharp -[Command("example [foo: int = 1145] [bar: bool = true]")] -``` - -这样写的缺点是可能导致指令定义过于冗长,可以结合实际情况选择。 - -## 分支说明 - -项目目前有两个主要分支: - -- `main` 分支 - 包含上一个发布版本的源代码,`dev` 分支会在版本发布时合并过来 -- `dev` 分支 - 开发分支,包含最新更改,但可能不稳定。 - -向仓库贡献代码时,请确保目前正处于 `dev` 分支上。 - -## 致谢 - -项目编写过程中参考了许多开源项目,没有它们就没有 Flandre 的诞生: - -- [koishijs/koishi](https://github.com/koishijs/koishi) -- [KonataDev/Konata.Core](https://github.com/KonataDev/Konata.Core) - -(按字母排序) +--- -## License +**项目仍在早期开发阶段,功能尚未完善,且处于快速迭代过程中。** +**如果您对项目的开发感兴趣,诚挚欢迎您的改进建议或 PR 贡献。** -本项目以 [MIT 许可证](https://github.com/FlandreDevs/Flandre/blob/main/LICENSE) 开源 (′▽\`)╭(′▽\`)╯ \ No newline at end of file +**1.0 版本发布前随时可能发生 API 的非兼容性变更,不建议用于生产环境。** \ No newline at end of file diff --git a/README.md b/README.md index 4641dbf..6fe0e9b 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@
- + # Flandre @@ -9,9 +9,9 @@ [![License](https://img.shields.io/github/license/FlandreDevs/Flandre?label=License&style=flat&color=42a5f5)](https://github.com/FlandreDevs/Flandre/blob/main/LICENSE) [![Stars](https://img.shields.io/github/stars/FlandreDevs/Flandre?label=Stars&style=flat&color=1976d2)](https://github.com/FlandreDevs/Flandre/stargazers) -[![Contributors](https://img.shields.io/github/contributors/FlandreDevs/Flandre?label=Contributors&style=flat&color=ab47bc)](https://github.com/FlandreDevs/Flandre/graphs/contributors) -[![NuGet](https://img.shields.io/nuget/vpre/Flandre.Core?style=flat&label=NuGet&color=f06292)](https://www.nuget.org/packages/Flandre.Core/) -[![NuGet Downloads](https://img.shields.io/nuget/dt/Flandre.Core?style=flat&label=Downloads&color=ffb300)](https://www.nuget.org/packages/Flandre.Core/) +[![Contributors](https://img.shields.io/github/contributors/FlandreDevs/Flandre?label=Contributors&style=flat&color=9866ca)](https://github.com/FlandreDevs/Flandre/graphs/contributors) +[![Flandre.Framework Version](https://img.shields.io/nuget/vpre/Flandre.Core?style=flat&label=Framework&color=f06292)](https://www.nuget.org/packages/Flandre.Core/) +[![Flandre.Core Version](https://img.shields.io/nuget/vpre/Flandre.Core?style=flat&label=Core&color=e65943)](https://www.nuget.org/packages/Flandre.Core/) [![.NET Version](https://img.shields.io/badge/.NET-6-ffe57f?style=flat)](https://www.nuget.org/packages/Flandre.Core/) [![Codecov](https://img.shields.io/codecov/c/gh/FlandreDevs/Flandre/dev?style=flat&color=a5d6a7&label=Coverage)](https://app.codecov.io/gh/FlandreDevs/Flandre) @@ -44,9 +44,27 @@ Flandre 为跨平台而生,对聊天平台的结构进行抽象化,采用适 | Telegram | 计划中... | | Discord | 计划中... | +### 🧩 灵活的开发方式 +Flandre 提供两种开发方式,分别是完整的开发框架 `Framework`,以及易于嵌入已有程序的 `Core`。 +#### Flandre.Framework +[![NuGet](https://img.shields.io/nuget/vpre/Flandre.Framework?style=flat&label=NuGet&color=9866ca)](https://www.nuget.org/packages/Flandre.Framework/) +[![NuGet Downloads](https://img.shields.io/nuget/dt/Flandre.Framework?style=flat&label=Downloads&color=42a5f5)](https://www.nuget.org/packages/Flandre.Framework/) + +`Flandre.Framework` 是一个使用方便、功能全面的 Bot 开发框架,在核心包 `Core` 的基础上集成了插件、指令、中间件等系统,并提供依赖注入、日志管理等等实用功能。对于一个全新的 Bot 项目,我们推荐您直接使用 `Framework` 进行开发。 + +#### Flandre.Core +[![NuGet](https://img.shields.io/nuget/vpre/Flandre.Core?style=flat&label=NuGet&color=9866ca)](https://www.nuget.org/packages/Flandre.Core/) +[![NuGet Downloads](https://img.shields.io/nuget/dt/Flandre.Core?style=flat&label=Downloads&color=42a5f5)](https://www.nuget.org/packages/Flandre.Core/) + +`Flandre.Core` 是整个框架的核心组件,包含了适配器、机器人等重要内容,提供直接操作 Bot 进行平台交互的功能。相比 `Framework`,`Core` 作为一个轻量化的模块,能更容易地嵌入进已有项目中,成为功能的一部分。 + +> 不需要代入 .NET Framework / Core 命名方式的意义。在 Flandre 中,两者只意味着开发方式的不同,都处于积极维护中。 + +下文将主要介绍 `Flandre.Framework` 的各类特性。如果你需要关于 `Flandre.Core` 的详细说明,请~~参照这里的文档~~。(还没写x) + ### 📦 开箱即用的指令系统 -Flandre 实现了一套开箱即用的指令解析系统,而无需开发者自己造轮子。 +Flandre.Framework 实现了一套开箱即用的指令解析系统,而无需开发者自己造轮子。 开发者可以方便地掌控指令的参数信息,包括但不限于参数数量检查,类型检查,参数默认值等等。而所有的定义可以在一个字符串内完成,例如: ```csharp @@ -59,7 +77,9 @@ Flandre 内部采用各类事件控制,开发者可以轻松地通过订阅事 ## 🚀 起步 -遵循不知道哪里来的惯例,我们以一个复读小程序开始: +遵循不知道哪里来的惯例,我们以一个复读小程序开始。 + +首先通过 NuGet 包管理器引用 `Flandre.Framework` 和 `Flandre.Adapters.Konata` 包,然后: ```csharp using Flandre.Core; @@ -81,12 +101,12 @@ app class ExamplePlugin : Plugin { - public override void OnMessageReceived(MessageContext ctx) - => ctx.Bot.SendMessage(ctx.Message); + public override async Task OnMessageReceived(MessageContext ctx) + => await ctx.Bot.SendMessage(ctx.Message); } ``` -运行程序,向我们 bot 的 QQ 号发送一条消息,bot 会将消息原封不动地发回来。 ~~复读不仅仅是人类的本质.jpg~~ +运行程序,向我们 Bot 的 QQ 号发送一条消息,bot 会将消息原封不动地发回来。 ~~复读不仅仅是人类的本质.jpg~~ ### 基本指令解析 diff --git a/src/Flandre.Adapters.Konata/Flandre.Adapters.Konata.csproj b/src/Flandre.Adapters.Konata/Flandre.Adapters.Konata.csproj index 0eb1b41..271d82c 100644 --- a/src/Flandre.Adapters.Konata/Flandre.Adapters.Konata.csproj +++ b/src/Flandre.Adapters.Konata/Flandre.Adapters.Konata.csproj @@ -3,11 +3,11 @@ Flandre.Adapters.Konata $(Title) - 1.0.0-alpha.2 + 2.0.0-alpha.1 $(PackageVersion) b1acksoil Konata.Core (QQ Protocol) adapter for Flandre project. - bot + bot;chatbot;flandre;adapter MIT avatar.jpg README.md diff --git a/src/Flandre.Adapters.Konata/KonataAdapter.cs b/src/Flandre.Adapters.Konata/KonataAdapter.cs index 2c1e0a8..2f1f944 100644 --- a/src/Flandre.Adapters.Konata/KonataAdapter.cs +++ b/src/Flandre.Adapters.Konata/KonataAdapter.cs @@ -1,6 +1,4 @@ -using Flandre.Core; -using Flandre.Core.Common; -using Flandre.Core.Utils; +using Flandre.Core.Common; namespace Flandre.Adapters.Konata; @@ -9,7 +7,6 @@ namespace Flandre.Adapters.Konata; /// public class KonataAdapter : IAdapter { - private readonly Logger _logger = new("KonataAdapter"); private readonly List _bots = new(); private readonly KonataAdapterConfig _config; @@ -22,27 +19,18 @@ public KonataAdapter(KonataAdapterConfig config) _config = config; _config.Bots.ForEach(bot => - _bots.Add(new KonataBot(bot, _logger))); + _bots.Add(new KonataBot(bot))); } /// /// 启动适配器 /// - public async Task Start() - { - _logger.Info("Starting Konata Adapter..."); - await Task.WhenAll(_bots.ConvertAll(bot => bot.Start())); - _logger.Info("Konata Adapter started."); - } + public Task Start() => Task.WhenAll(_bots.ConvertAll(bot => bot.Start())); /// /// 停止适配器 /// - public async Task Stop() - { - await Task.WhenAll(_bots.ConvertAll(bot => bot.Stop())); - _logger.Info("Konata Adapter stopped."); - } + public Task Stop() => Task.WhenAll(_bots.ConvertAll(bot => bot.Stop())); /// /// 获取 bot 列表 @@ -53,7 +41,7 @@ public async Task Stop() /// /// Konata 适配器配置 /// -public class KonataAdapterConfig +public sealed class KonataAdapterConfig { /// /// 构造 Konata 适配器配置 @@ -76,20 +64,4 @@ public KonataAdapterConfig(List bots) /// bot 配置列表 /// public List Bots { get; init; } -} - -/// -/// FlandreApp 扩展方法 -/// -public static class FlandreAppExtensions -{ - /// - /// 使用 Konata 适配器 - /// - /// FlandreApp 实例 - /// Konata 适配器配置 - public static FlandreApp UseKonataAdapter(this FlandreApp app, KonataAdapterConfig config) - { - return app.Use(new KonataAdapter(config)); - } } \ No newline at end of file diff --git a/src/Flandre.Adapters.Konata/KonataBot.cs b/src/Flandre.Adapters.Konata/KonataBot.cs index 2c8d6e1..00c5c08 100644 --- a/src/Flandre.Adapters.Konata/KonataBot.cs +++ b/src/Flandre.Adapters.Konata/KonataBot.cs @@ -1,12 +1,13 @@ -using Flandre.Core.Events.Bot; +using Flandre.Core.Common; +using Flandre.Core.Events; using Flandre.Core.Messaging; using Flandre.Core.Models; -using Flandre.Core.Utils; using Konata.Core.Common; using Konata.Core.Events; using Konata.Core.Events.Model; using Konata.Core.Interfaces; using Konata.Core.Interfaces.Api; +using BotConfig = Konata.Core.Common.BotConfig; using FlandreBotConfig = Flandre.Core.Common.BotConfig; using FlandreBot = Flandre.Core.Common.Bot; using KonataInternalBot = Konata.Core.Bot; @@ -31,12 +32,8 @@ public sealed class KonataBot : FlandreBot /// public KonataInternalBot Internal { get; } - private readonly Logger _logger; private readonly KonataBotConfig _config; - /// - protected override Logger GetLogger() => _logger; - /// public override event BotEventHandler? OnMessageReceived; @@ -49,9 +46,8 @@ public sealed class KonataBot : FlandreBot /// public override event BotEventHandler? OnFriendRequested; - internal KonataBot(KonataBotConfig config, Logger logger) + internal KonataBot(KonataBotConfig config) { - _logger = logger; Internal = BotFather.Create(config.Konata, config.Device, config.KeyStore); _config = config; @@ -74,15 +70,15 @@ internal KonataBot(KonataBotConfig config, Logger logger) /// public override async Task Start() { - _logger.Info("Starting Konata Bot..."); + Log(BotLogLevel.Debug, $"Starting bot {_config.SelfId}..."); if (!await Internal.Login()) { - _logger.Warning($"{_config.SelfId} 登陆失败。"); + Log(BotLogLevel.Warning, $"Bot {_config.SelfId} login failed."); return; } _config.KeyStore = Internal.KeyStore; - _logger.Info("Konata Bot started."); + Log(BotLogLevel.Information, $"Bot {_config.SelfId} started."); } /// @@ -275,17 +271,19 @@ private void InnerOnFriendRequest(KonataInternalBot bot, FriendRequestEvent e) private void InnerOnCaptcha(KonataInternalBot bot, CaptchaEvent e) { - _logger.Warning($"Bot {_config.SelfId} 需要进行登录验证。"); + Log(BotLogLevel.Warning, $"Bot {_config.SelfId} needs login verification."); switch (e.Type) { case CaptchaEvent.CaptchaType.Sms: - _logger.Warning($"手机验证码已发送至 {e.Phone},请注意查收,输入验证码后按 Enter 继续:"); + Log(BotLogLevel.Warning, + $"The phone verify code has been sent to {e.Phone}. Please input the code and press Enter."); Internal.SubmitSmsCode(Console.ReadLine()); break; case CaptchaEvent.CaptchaType.Slider: - _logger.Warning($"滑动验证码 URL: {e.SliderUrl},输入 Ticket 后按 Enter 继续:"); + Log(BotLogLevel.Warning, + $"The Slider Captcha URL is: {e.SliderUrl}. Please input the ticket and press Enter."); Internal.SubmitSliderTicket(Console.ReadLine()); break; } @@ -296,12 +294,12 @@ private void InnerOnLog(KonataInternalBot bot, LogEvent e) switch (e.Level) { case LogLevel.Warning: - _logger.Warning(e.EventMessage); + Log(BotLogLevel.Warning, e.EventMessage); break; case LogLevel.Exception: case LogLevel.Fatal: - _logger.Error(e.EventMessage); + Log(BotLogLevel.Error, e.EventMessage); break; } } diff --git a/src/Flandre.Adapters.Mock/Extensions.cs b/src/Flandre.Adapters.Mock/Extensions.cs index 47fa242..bae8dff 100644 --- a/src/Flandre.Adapters.Mock/Extensions.cs +++ b/src/Flandre.Adapters.Mock/Extensions.cs @@ -2,7 +2,7 @@ namespace Flandre.Adapters.Mock; -public static class FlandreTestingExtensions +public static class MockAdapterExtensions { public static MockClient GetChannelClient(this MockAdapter adapter, string guildId, string channelId, string userId) diff --git a/src/Flandre.Adapters.Mock/Flandre.Adapters.Mock.csproj b/src/Flandre.Adapters.Mock/Flandre.Adapters.Mock.csproj index e1ab702..f64ecec 100644 --- a/src/Flandre.Adapters.Mock/Flandre.Adapters.Mock.csproj +++ b/src/Flandre.Adapters.Mock/Flandre.Adapters.Mock.csproj @@ -3,10 +3,11 @@ Flandre.Adapters.Mock $(Title) - 0.5.0 + 0.7.0 $(PackageVersion) b1acksoil Mock client for Project Flandre. + bot;chatbot;flandre;adapter MIT avatar.jpg @@ -15,6 +16,7 @@ enable Library true + CS1591 https://github.com/FlandreDevs/Flandre https://github.com/FlandreDevs/Flandre.git diff --git a/src/Flandre.Adapters.Mock/MockAdapter.cs b/src/Flandre.Adapters.Mock/MockAdapter.cs index 4013175..2450cc1 100644 --- a/src/Flandre.Adapters.Mock/MockAdapter.cs +++ b/src/Flandre.Adapters.Mock/MockAdapter.cs @@ -1,5 +1,4 @@ using Flandre.Core.Common; -using Flandre.Core.Utils; #pragma warning disable CS1998 @@ -7,7 +6,7 @@ namespace Flandre.Adapters.Mock; public class MockAdapter : IAdapter { - internal readonly MockBot Bot = new(new Logger("MockAdapter")); + internal readonly MockBot Bot = new(); public Task Start() => Task.CompletedTask; diff --git a/src/Flandre.Adapters.Mock/MockBot.cs b/src/Flandre.Adapters.Mock/MockBot.cs index d7e2f00..0241b0b 100644 --- a/src/Flandre.Adapters.Mock/MockBot.cs +++ b/src/Flandre.Adapters.Mock/MockBot.cs @@ -1,8 +1,7 @@ using Flandre.Core.Common; -using Flandre.Core.Events.Bot; +using Flandre.Core.Events; using Flandre.Core.Messaging; using Flandre.Core.Models; -using Flandre.Core.Utils; #pragma warning disable CS1998 @@ -17,19 +16,10 @@ public class MockBot : Bot public override string SelfId => _selfId; - private readonly Logger _logger; - private readonly string _selfId = Guid.NewGuid().ToString(); private readonly Dictionary> _tcsDict = new(); - protected override Logger GetLogger() => _logger; - - internal MockBot(Logger logger) - { - _logger = logger; - } - internal void ReceiveMessage(Message message, TaskCompletionSource tcs, TimeSpan timeout) { _tcsDict[message.MessageId] = tcs; @@ -85,4 +75,4 @@ internal void ReceiveMessage(Message message, TaskCompletionSource? OnGuildInvited; public override event BotEventHandler? OnGuildJoinRequested; public override event BotEventHandler? OnFriendRequested; -} +} \ No newline at end of file diff --git a/src/Flandre.Adapters.Mock/MockClient.cs b/src/Flandre.Adapters.Mock/MockClient.cs index 2572a21..2311181 100644 --- a/src/Flandre.Adapters.Mock/MockClient.cs +++ b/src/Flandre.Adapters.Mock/MockClient.cs @@ -7,9 +7,9 @@ public class MockClient { private readonly MockAdapter _adapter; - public string GuildId { get; internal init; } = ""; - public string ChannelId { get; internal init; } = ""; - public string UserId { get; internal init; } = ""; + public string GuildId { get; internal init; } = string.Empty; + public string ChannelId { get; internal init; } = string.Empty; + public string UserId { get; internal init; } = string.Empty; public MessageSourceType EnvironmentType { get; internal init; } diff --git a/src/Flandre.Adapters.OneBot/CqCodeParser.cs b/src/Flandre.Adapters.OneBot/CqCodeParser.cs index 23a925d..77c9e2b 100644 --- a/src/Flandre.Adapters.OneBot/CqCodeParser.cs +++ b/src/Flandre.Adapters.OneBot/CqCodeParser.cs @@ -146,7 +146,7 @@ private static OneBotImageSegment ParseImage(string[] data) return segment; } - + private static AtSegment ParseAt(string[] data) { return new AtSegment(data[0][3..]); // qq=xxx diff --git a/src/Flandre.Adapters.OneBot/Flandre.Adapters.OneBot.csproj b/src/Flandre.Adapters.OneBot/Flandre.Adapters.OneBot.csproj index c9c72bc..2d783c0 100644 --- a/src/Flandre.Adapters.OneBot/Flandre.Adapters.OneBot.csproj +++ b/src/Flandre.Adapters.OneBot/Flandre.Adapters.OneBot.csproj @@ -3,11 +3,11 @@ Flandre.Adapters.OneBot $(Title) - 1.0.0-alpha.2 + 2.0.0-alpha.1 $(PackageVersion) b1acksoil OneBot protocol adapter for Flandre project. - bot + bot;chatbot;flandre;adapter MIT avatar.jpg README.md diff --git a/src/Flandre.Adapters.OneBot/GuildBot.cs b/src/Flandre.Adapters.OneBot/GuildBot.cs index 621d0c4..4bd4394 100644 --- a/src/Flandre.Adapters.OneBot/GuildBot.cs +++ b/src/Flandre.Adapters.OneBot/GuildBot.cs @@ -1,9 +1,8 @@ using Flandre.Adapters.OneBot.Models; using Flandre.Core.Common; -using Flandre.Core.Events.Bot; +using Flandre.Core.Events; using Flandre.Core.Messaging; using Flandre.Core.Models; -using Flandre.Core.Utils; #pragma warning disable CS0067 @@ -18,15 +17,11 @@ public class OneBotGuildBot : Bot public override string SelfId => _selfId; - private string _selfId = ""; + private string _selfId = string.Empty; private bool _isSelfIdSet; public OneBotGuildInternalBot Internal { get; } - private readonly OneBotBot _mainBot; - - protected override Logger GetLogger() => _mainBot.Logger; - public override event BotEventHandler? OnMessageReceived; public override event BotEventHandler? OnGuildInvited; public override event BotEventHandler? OnGuildJoinRequested; @@ -35,7 +30,6 @@ public class OneBotGuildBot : Bot internal OneBotGuildBot(OneBotBot mainBot) { Internal = new OneBotGuildInternalBot(mainBot); - _mainBot = mainBot; } internal void InvokeMessageEvent(OneBotApiGuildMessageEvent e) @@ -68,7 +62,7 @@ internal void InvokeMessageEvent(OneBotApiGuildMessageEvent e) if (sourceType == MessageSourceType.Channel) return await Internal.SendGuildChannelMessage(guildId!, channelId!, content); - _mainBot.Logger.Warning("qqguild 平台暂不支持发送频道私聊消息。"); + Log(BotLogLevel.Warning, "Platform qqguild does not support sending private message."); return null; } @@ -91,8 +85,8 @@ internal void InvokeMessageEvent(OneBotApiGuildMessageEvent e) public override Task GetUser(string userId, string? guildId = null) { - _mainBot.Logger.Warning( - $"qqguild 平台暂不支持 {nameof(GetUser)} 方法。若您需要获取频道成员信息,请使用 {nameof(GetGuildMember)} 方法代替。"); + Log(BotLogLevel.Warning, + $"Platform qqguild does not support method {nameof(GetUser)}. If you need to get the information of guild member, please use method {nameof(GetGuildMember)} instead."); return Task.FromResult(null); } diff --git a/src/Flandre.Adapters.OneBot/OneBotAdapter.cs b/src/Flandre.Adapters.OneBot/OneBotAdapter.cs index 1141813..190fe0c 100644 --- a/src/Flandre.Adapters.OneBot/OneBotAdapter.cs +++ b/src/Flandre.Adapters.OneBot/OneBotAdapter.cs @@ -1,5 +1,4 @@ using Flandre.Core.Common; -using Flandre.Core.Utils; namespace Flandre.Adapters.OneBot; @@ -9,8 +8,6 @@ public class OneBotAdapter : IAdapter private readonly OneBotAdapterConfig _config; - private readonly Logger _logger = new("OneBotAdapter"); - public OneBotAdapter(OneBotAdapterConfig config) { _config = config; @@ -20,14 +17,14 @@ public OneBotAdapter(OneBotAdapterConfig config) { case "websocket": case "ws": - var obb = new OneBotWebSocketBot(bot, _logger); + var obb = new OneBotWebSocketBot(bot); _bots.Add(obb); _bots.Add(obb.GuildBot); break; default: - _logger.Warning($"OneBotAdapter 仅支持 websocket / ws 协议。正在跳过 Bot {bot.SelfId} 的初始化。"); - break; + throw new NotSupportedException( + $"OneBot adapter only supports \"websocket\" / \"ws\" protocol. Skipping initialization of bot {bot.SelfId}..."); } } diff --git a/src/Flandre.Adapters.OneBot/OneBotBot.cs b/src/Flandre.Adapters.OneBot/OneBotBot.cs index 7d2b342..3b81d58 100644 --- a/src/Flandre.Adapters.OneBot/OneBotBot.cs +++ b/src/Flandre.Adapters.OneBot/OneBotBot.cs @@ -1,9 +1,8 @@ using System.Text.Json; using Flandre.Core.Common; -using Flandre.Core.Events.Bot; +using Flandre.Core.Events; using Flandre.Core.Messaging; using Flandre.Core.Models; -using Flandre.Core.Utils; namespace Flandre.Adapters.OneBot; @@ -18,21 +17,17 @@ public abstract class OneBotBot : Bot public override string SelfId { get; } internal readonly OneBotGuildBot GuildBot; - internal readonly Logger Logger; /// /// 内部 Bot,包含大量 OneBot 平台专属方法 /// public OneBotInternalBot Internal { get; } - protected override Logger GetLogger() => Logger; - - internal OneBotBot(string selfId, Logger logger) + internal OneBotBot(string selfId) { SelfId = selfId; Internal = new OneBotInternalBot(this); GuildBot = new OneBotGuildBot(this); - Logger = logger; } internal abstract Task SendApiRequest(string action, object? @params = null); diff --git a/src/Flandre.Adapters.OneBot/OneBotUtils.cs b/src/Flandre.Adapters.OneBot/OneBotUtils.cs index 91a9069..4555c1a 100644 --- a/src/Flandre.Adapters.OneBot/OneBotUtils.cs +++ b/src/Flandre.Adapters.OneBot/OneBotUtils.cs @@ -1,6 +1,4 @@ -using Flandre.Core; - -namespace Flandre.Adapters.OneBot; +namespace Flandre.Adapters.OneBot; public static class OneBotUtils { @@ -33,14 +31,6 @@ public static string GetUserAvatar(long userId) } } -public static class FlandreAppExtensions -{ - public static FlandreApp UseOneBotAdapter(this FlandreApp app, OneBotAdapterConfig config) - { - return app.Use(new OneBotAdapter(config)); - } -} - public class OneBotApiException : Exception { public OneBotApiException(string message) : base(message) diff --git a/src/Flandre.Adapters.OneBot/WebSocketBot.cs b/src/Flandre.Adapters.OneBot/WebSocketBot.cs index c6287e9..16e21a1 100644 --- a/src/Flandre.Adapters.OneBot/WebSocketBot.cs +++ b/src/Flandre.Adapters.OneBot/WebSocketBot.cs @@ -2,10 +2,10 @@ using System.Net.WebSockets; using System.Text.Json; using Flandre.Adapters.OneBot.Models; -using Flandre.Core.Events.Bot; +using Flandre.Core.Common; +using Flandre.Core.Events; using Flandre.Core.Messaging; using Flandre.Core.Models; -using Flandre.Core.Utils; using Websocket.Client; using Websocket.Client.Exceptions; @@ -25,7 +25,7 @@ public class OneBotWebSocketBot : OneBotBot public override event BotEventHandler? OnGuildJoinRequested; public override event BotEventHandler? OnFriendRequested; - internal OneBotWebSocketBot(OneBotBotConfig config, Logger logger) : base(config.SelfId, logger) + internal OneBotWebSocketBot(OneBotBotConfig config) : base(config.SelfId) { _config = config; @@ -40,13 +40,17 @@ internal OneBotWebSocketBot(OneBotBotConfig config, Logger logger) : base(config _wsClient.DisconnectionHappened.Subscribe(_ => { if (_clientStopped) return; - Logger.Warning($"WebSocket 连接丢失,将在 {_config.WebSocketReconnectTimeout} 秒后尝试重连..."); + Log(BotLogLevel.Warning, + $"WebSocket connection lost, reconnecting in {_config.WebSocketReconnectTimeout}s..."); foreach (var tcs in _apiTasks.Values) - tcs.SetException(new WebsocketException("WebSocket 连接丢失。")); + tcs.SetException(new WebsocketException("WebSocket connection lost.")); _apiTasks.Clear(); }); - _wsClient.ReconnectionHappened.Subscribe(_ => { Logger.Success("成功连接至 WebSocket 服务器。"); }); + _wsClient.ReconnectionHappened.Subscribe(_ => + { + Log(BotLogLevel.Information, "Successfully connected to WebSocket server."); + }); } #region WebSocket 交互 diff --git a/src/Flandre.Core/AssemblyInfo.cs b/src/Flandre.Core/AssemblyInfo.cs new file mode 100644 index 0000000..3c29403 --- /dev/null +++ b/src/Flandre.Core/AssemblyInfo.cs @@ -0,0 +1,4 @@ +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("Flandre.Framework")] +[assembly: InternalsVisibleTo("Flandre.Core.Tests")] \ No newline at end of file diff --git a/src/Flandre.Core/Attributes/PluginAttribute.cs b/src/Flandre.Core/Attributes/PluginAttribute.cs deleted file mode 100644 index bd0128b..0000000 --- a/src/Flandre.Core/Attributes/PluginAttribute.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace Flandre.Core.Attributes; - -/// -/// 插件定义 -/// -[AttributeUsage(AttributeTargets.Class)] -public class PluginAttribute : Attribute -{ - /// - /// 插件名称 - /// - public string Name { get; } - - /// - /// 定义插件 - /// - /// 插件名称 - public PluginAttribute(string name) - { - Name = name; - } -} \ No newline at end of file diff --git a/src/Flandre.Core/Common/Bot.cs b/src/Flandre.Core/Common/Bot.cs index 1028e7b..e7a68ce 100644 --- a/src/Flandre.Core/Common/Bot.cs +++ b/src/Flandre.Core/Common/Bot.cs @@ -1,14 +1,13 @@ -using Flandre.Core.Events.Bot; +using Flandre.Core.Events; using Flandre.Core.Messaging; using Flandre.Core.Models; -using Flandre.Core.Utils; namespace Flandre.Core.Common; /// /// 机器人 /// -public abstract class Bot +public abstract partial class Bot { /// /// Bot 所在平台名称 @@ -21,9 +20,14 @@ public abstract class Bot public abstract string SelfId { get; } /// - /// 获取 Logger + /// 日志记录 /// - protected abstract Logger GetLogger(); + /// 日志等级 + /// 日志消息 + public void Log(BotLogLevel level, string message) + { + OnLogging?.Invoke(this, new BotLoggingEvent(level, message)); + } /// /// 启动 Bot 实例 @@ -42,7 +46,7 @@ public abstract class Bot /// Task.CompletedTask protected Task LogNotSupported(string method) { - GetLogger().Warning($"{Platform} 平台暂不支持 {method} 方法。"); + Log(BotLogLevel.Warning, $"Platform {Platform} does not support method {method}."); return Task.CompletedTask; } @@ -54,7 +58,7 @@ protected Task LogNotSupported(string method) /// Task.FromResult<TResult>(result) protected Task LogNotSupported(string method, TResult result) { - GetLogger().Warning($"{Platform} 平台暂不支持 {method} 方法。"); + Log(BotLogLevel.Warning, $"Platform {Platform} does not support method {method}."); return Task.FromResult(result); } @@ -175,59 +179,6 @@ public virtual Task> GetGuildMemberList(string guildId) /// 群组 ID public virtual Task> GetChannelList(string guildId) => LogNotSupported>(nameof(GetGuildList), Array.Empty()); - - /// - /// Bot 事件委托 - /// - /// 事件类型 - public delegate void BotEventHandler(Bot bot, TEvent e); - - /// - /// 收到消息 - /// - public abstract event BotEventHandler? OnMessageReceived; - - /// - /// 收到群组邀请 - /// - public abstract event BotEventHandler? OnGuildInvited; - - /// - /// 收到加群申请 - /// - public abstract event BotEventHandler? OnGuildJoinRequested; - - /// - /// 收到好友申请 - /// - public abstract event BotEventHandler? OnFriendRequested; - - /// - /// 处理拉群邀请 - /// - /// 拉群邀请事件 - /// 是否同意 - /// 附加说明 - public virtual Task HandleGuildInvitation(BotGuildInvitedEvent e, bool approve, string? comment = null) - => LogNotSupported(nameof(HandleGuildInvitation)); - - /// - /// 处理加群申请 - /// - /// 加群申请事件 - /// 是否同意 - /// 附加说明 - public virtual Task HandleGuildJoinRequest(BotGuildJoinRequestedEvent e, bool approve, string? comment = null) - => LogNotSupported(nameof(HandleGuildJoinRequest)); - - /// - /// 处理好友申请 - /// - /// 好友申请事件 - /// 是否同意 - /// 附加说明 - public virtual Task HandleFriendRequest(BotFriendRequestedEvent e, bool approve, string? comment = null) - => LogNotSupported(nameof(HandleFriendRequest)); } /// diff --git a/src/Flandre.Core/Common/BotEvents.cs b/src/Flandre.Core/Common/BotEvents.cs new file mode 100644 index 0000000..708549e --- /dev/null +++ b/src/Flandre.Core/Common/BotEvents.cs @@ -0,0 +1,64 @@ +using Flandre.Core.Events; + +namespace Flandre.Core.Common; + +public abstract partial class Bot +{ + /// + /// Bot 事件委托 + /// + /// 事件类型 + public delegate void BotEventHandler(Bot bot, TEvent e) where TEvent : BaseEvent; + + /// + /// 日志记录 + /// + public event BotEventHandler? OnLogging; + + /// + /// 收到消息 + /// + public abstract event BotEventHandler? OnMessageReceived; + + /// + /// 收到群组邀请 + /// + public abstract event BotEventHandler? OnGuildInvited; + + /// + /// 收到加群申请 + /// + public abstract event BotEventHandler? OnGuildJoinRequested; + + /// + /// 收到好友申请 + /// + public abstract event BotEventHandler? OnFriendRequested; + + /// + /// 处理拉群邀请 + /// + /// 拉群邀请事件 + /// 是否同意 + /// 附加说明 + public virtual Task HandleGuildInvitation(BotGuildInvitedEvent e, bool approve, string? comment = null) + => LogNotSupported(nameof(HandleGuildInvitation)); + + /// + /// 处理加群申请 + /// + /// 加群申请事件 + /// 是否同意 + /// 附加说明 + public virtual Task HandleGuildJoinRequest(BotGuildJoinRequestedEvent e, bool approve, string? comment = null) + => LogNotSupported(nameof(HandleGuildJoinRequest)); + + /// + /// 处理好友申请 + /// + /// 好友申请事件 + /// 是否同意 + /// 附加说明 + public virtual Task HandleFriendRequest(BotFriendRequestedEvent e, bool approve, string? comment = null) + => LogNotSupported(nameof(HandleFriendRequest)); +} \ No newline at end of file diff --git a/src/Flandre.Core/Common/BotLogLevel.cs b/src/Flandre.Core/Common/BotLogLevel.cs new file mode 100644 index 0000000..b807de7 --- /dev/null +++ b/src/Flandre.Core/Common/BotLogLevel.cs @@ -0,0 +1,17 @@ +#pragma warning disable CS1591 + +namespace Flandre.Core.Common; + +/// +/// 日志等级,与 Microsoft.Extension.Logging.LogLevel 兼容。 +/// +public enum BotLogLevel +{ + Trace = 0, + Debug = 1, + Information = 2, + Warning = 3, + Error = 4, + Critical = 5, + None = 6 +} \ No newline at end of file diff --git a/src/Flandre.Core/Common/Context.cs b/src/Flandre.Core/Common/Context.cs index 23aae57..1b86297 100644 --- a/src/Flandre.Core/Common/Context.cs +++ b/src/Flandre.Core/Common/Context.cs @@ -5,11 +5,6 @@ /// public class Context { - /// - /// FlandreApp 实例 - /// - public FlandreApp App { get; } - /// /// 当前 bot 实例 /// @@ -18,11 +13,9 @@ public class Context /// /// 构造上下文 /// - /// FlandreApp 实例 /// bot 实例 - public Context(FlandreApp app, Bot bot) + public Context(Bot bot) { - App = app; Bot = bot; } diff --git a/src/Flandre.Core/Common/IAdapter.cs b/src/Flandre.Core/Common/IAdapter.cs index 5c65efb..cb95fe6 100644 --- a/src/Flandre.Core/Common/IAdapter.cs +++ b/src/Flandre.Core/Common/IAdapter.cs @@ -3,7 +3,7 @@ /// /// 适配器接口 /// -public interface IAdapter : IModule +public interface IAdapter { /// /// 启动适配器 diff --git a/src/Flandre.Core/Common/IModule.cs b/src/Flandre.Core/Common/IModule.cs deleted file mode 100644 index 2bf7e18..0000000 --- a/src/Flandre.Core/Common/IModule.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Flandre.Core.Common; - -/// -/// 模块抽象接口 -/// -public interface IModule -{ -} \ No newline at end of file diff --git a/src/Flandre.Core/Common/Plugin.cs b/src/Flandre.Core/Common/Plugin.cs deleted file mode 100644 index 902bc70..0000000 --- a/src/Flandre.Core/Common/Plugin.cs +++ /dev/null @@ -1,158 +0,0 @@ -using System.Reflection; -using Flandre.Core.Attributes; -using Flandre.Core.Events.App; -using Flandre.Core.Events.Bot; -using Flandre.Core.Events.Logger; -using Flandre.Core.Events.Plugin; -using Flandre.Core.Messaging; -using Flandre.Core.Utils; - -namespace Flandre.Core.Common; - -/// -/// 模块基类 -/// -public abstract class Plugin : IModule -{ - /// - /// 插件 Logger,默认使用插件名称 - /// - public Logger Logger { get; } - - /// - /// 插件的指令 - /// - public List Commands { get; } = new(); - - /// - /// 插件信息 - /// - public PluginAttribute PluginInfo { get; } - - /// - /// 插件基类构造函数 - /// - public Plugin() - { - var type = GetType(); - PluginInfo = type.GetCustomAttribute() ?? new PluginAttribute(type.Name); - Logger = new Logger(PluginInfo.Name); - - foreach (var method in type.GetMethods()) - { - var attr = method.GetCustomAttribute(); - if (attr is null) continue; - - var options = method.GetCustomAttributes().ToList(); - var shortcuts = method.GetCustomAttributes().ToList(); - var aliases = method.GetCustomAttributes().ToList(); - - Commands.Add(new Command(this, attr, method, options, shortcuts, aliases)); - } - } - - /// - /// 插件初始化 - /// - public virtual Task Start() => Task.CompletedTask; - - /// - /// 插件清理操作 - /// - /// - public virtual Task Stop() => Task.CompletedTask; - - internal MessageContent GetHelp() - { - throw new NotImplementedException(); - } - - /// - /// 处理应用启动事件 - /// - /// 应用实例 - /// 应用启动事件 - public virtual void OnAppStarting(FlandreApp app, AppStartingEvent e) - { - } - - /// - /// 处理应用就绪事件 - /// - /// 应用实例 - /// 应用就绪事件 - public virtual void OnAppReady(FlandreApp app, AppReadyEvent e) - { - } - - /// - /// 处理应用停止事件 - /// - /// 应用实例 - /// 应用停止事件 - public virtual void OnAppStopped(FlandreApp app, AppStoppedEvent e) - { - } - - /// - /// 处理插件启动事件 - /// - /// 应用实例 - /// 插件启动事件 - public virtual void OnPluginStarting(FlandreApp app, PluginStartingEvent e) - { - } - - /// - /// 处理插件停止事件 - /// - /// 应用实例 - /// 插件停止事件 - public virtual void OnPluginStopped(FlandreApp app, PluginStoppedEvent e) - { - } - - /// - /// 处理消息事件 - /// - /// 当前消息上下文 - public virtual void OnMessageReceived(MessageContext ctx) - { - } - - /// - /// 收到拉群邀请 - /// - /// 当前上下文 - /// 拉群邀请事件 - public virtual void OnGuildInvited(Context ctx, BotGuildInvitedEvent e) - { - } - - /// - /// 收到入群申请 - /// - /// 当前上下文 - /// 入群申请事件 - public virtual void OnGuildJoinRequested(Context ctx, BotGuildJoinRequestedEvent e) - { - } - - /// - /// 收到好友申请 - /// - /// 当前上下文 - /// 好友申请事件 - public virtual void OnFriendRequested(Context ctx, BotFriendRequestedEvent e) - { - } - - /// - /// 日志记录前触发的事件 - /// - /// 当前上下文 - /// 日志记录前事件 - public virtual void OnLoggerLogging(Context ctx, LoggerLoggingEvent e) - { - } -} \ No newline at end of file diff --git a/src/Flandre.Core/Events/App/AppCommandInvokedEvent.cs b/src/Flandre.Core/Events/App/AppCommandInvokedEvent.cs deleted file mode 100644 index 4a121f5..0000000 --- a/src/Flandre.Core/Events/App/AppCommandInvokedEvent.cs +++ /dev/null @@ -1,38 +0,0 @@ -using Flandre.Core.Common; -using Flandre.Core.Messaging; - -namespace Flandre.Core.Events.App; - -/// -/// 指令调用事件 -/// -public class AppCommandInvokedEvent : BaseEvent -{ - /// - /// 即将执行的指令 - /// - public Command Command { get; } - - /// - /// 当前事件的消息上下文 - /// - public MessageContext Context { get; } - - /// - /// 解析后的指令 - /// - public ParsedArgs ParsedArgs { get; } - - /// - /// 回复的消息内容 - /// - public MessageContent? ReplyContent { get; } - - internal AppCommandInvokedEvent(Command command, MessageContext ctx, ParsedArgs args, MessageContent? content) - { - Command = command; - Context = ctx; - ParsedArgs = args; - ReplyContent = content; - } -} \ No newline at end of file diff --git a/src/Flandre.Core/Events/App/AppCommandInvokingEvent.cs b/src/Flandre.Core/Events/App/AppCommandInvokingEvent.cs deleted file mode 100644 index 7cc49a5..0000000 --- a/src/Flandre.Core/Events/App/AppCommandInvokingEvent.cs +++ /dev/null @@ -1,32 +0,0 @@ -using Flandre.Core.Common; -using Flandre.Core.Messaging; - -namespace Flandre.Core.Events.App; - -/// -/// 指令调用事件 -/// -public class AppCommandInvokingEvent : BaseEvent -{ - /// - /// 即将执行的指令 - /// - public Command Command { get; } - - /// - /// 当前事件的消息上下文 - /// - public MessageContext Context { get; } - - /// - /// 解析后的指令 - /// - public ParsedArgs ParsedArgs { get; } - - internal AppCommandInvokingEvent(Command command, MessageContext ctx, ParsedArgs args) - { - Command = command; - Context = ctx; - ParsedArgs = args; - } -} \ No newline at end of file diff --git a/src/Flandre.Core/Events/App/AppCommandParsedEvent.cs b/src/Flandre.Core/Events/App/AppCommandParsedEvent.cs deleted file mode 100644 index 6e374ab..0000000 --- a/src/Flandre.Core/Events/App/AppCommandParsedEvent.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Flandre.Core.Common; - -namespace Flandre.Core.Events.App; - -/// -/// 指令解析后 -/// -public class AppCommandParsedEvent : BaseEvent -{ - /// - /// 解析完毕的参数 - /// - public ParsedArgs ParsedArgs { get; } - - /// - /// 解析失败的提示。如果解析成功,此项为 null。 - /// - public string? Error { get; } - - internal AppCommandParsedEvent(ParsedArgs args, string? error) - { - ParsedArgs = args; - Error = error; - } -} \ No newline at end of file diff --git a/src/Flandre.Core/Events/App/AppCommandParsingEvent.cs b/src/Flandre.Core/Events/App/AppCommandParsingEvent.cs deleted file mode 100644 index 203ddc5..0000000 --- a/src/Flandre.Core/Events/App/AppCommandParsingEvent.cs +++ /dev/null @@ -1,38 +0,0 @@ -using Flandre.Core.Common; -using Flandre.Core.Messaging; -using Flandre.Core.Utils; - -namespace Flandre.Core.Events.App; - -/// -/// 指令解析前 -/// -public class AppCommandParsingEvent : CancellableEvent -{ - /// - /// 当前事件的消息上下文 - /// - public MessageContext Context { get; } - - /// - /// 匹配到的指令 - /// - public Command Command { get; } - - /// - /// 指令解析使用的字符串解析器,包含待解析的参数和选项 - /// - public StringParser Parser { get; } - - /// - /// 自定义解析参数 - /// - public ParsedArgs? CustomArgs { get; set; } = null; - - internal AppCommandParsingEvent(MessageContext ctx, Command command, StringParser parser) - { - Context = ctx; - Command = command; - Parser = parser; - } -} \ No newline at end of file diff --git a/src/Flandre.Core/Events/App/AppReadyEvent.cs b/src/Flandre.Core/Events/App/AppReadyEvent.cs deleted file mode 100644 index 77afb8b..0000000 --- a/src/Flandre.Core/Events/App/AppReadyEvent.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Flandre.Core.Events.App; - -/// -/// 应用就绪事件 -/// -public class AppReadyEvent : BaseEvent -{ - internal AppReadyEvent() - { - } -} \ No newline at end of file diff --git a/src/Flandre.Core/Events/App/AppStartingEvent.cs b/src/Flandre.Core/Events/App/AppStartingEvent.cs deleted file mode 100644 index 6873a38..0000000 --- a/src/Flandre.Core/Events/App/AppStartingEvent.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Flandre.Core.Events.App; - -/// -/// 应用正在启动事件 -/// -public class AppStartingEvent : CancellableEvent -{ - internal AppStartingEvent() - { - } -} \ No newline at end of file diff --git a/src/Flandre.Core/Events/App/AppStoppedEvent.cs b/src/Flandre.Core/Events/App/AppStoppedEvent.cs deleted file mode 100644 index 2b307a5..0000000 --- a/src/Flandre.Core/Events/App/AppStoppedEvent.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Flandre.Core.Events.App; - -/// -/// 应用退出事件 -/// -public class AppStoppedEvent : BaseEvent -{ - internal AppStoppedEvent() - { - } -} \ No newline at end of file diff --git a/src/Flandre.Core/Events/Bot/BotFriendRequestedEvent.cs b/src/Flandre.Core/Events/BotFriendRequestedEvent.cs similarity index 95% rename from src/Flandre.Core/Events/Bot/BotFriendRequestedEvent.cs rename to src/Flandre.Core/Events/BotFriendRequestedEvent.cs index f4cc474..412b28f 100644 --- a/src/Flandre.Core/Events/Bot/BotFriendRequestedEvent.cs +++ b/src/Flandre.Core/Events/BotFriendRequestedEvent.cs @@ -1,4 +1,4 @@ -namespace Flandre.Core.Events.Bot; +namespace Flandre.Core.Events; /// /// bot 好友申请事件 diff --git a/src/Flandre.Core/Events/Bot/BotGuildInvitedEvent.cs b/src/Flandre.Core/Events/BotGuildInvitedEvent.cs similarity index 97% rename from src/Flandre.Core/Events/Bot/BotGuildInvitedEvent.cs rename to src/Flandre.Core/Events/BotGuildInvitedEvent.cs index f81126e..ccc9c62 100644 --- a/src/Flandre.Core/Events/Bot/BotGuildInvitedEvent.cs +++ b/src/Flandre.Core/Events/BotGuildInvitedEvent.cs @@ -1,4 +1,4 @@ -namespace Flandre.Core.Events.Bot; +namespace Flandre.Core.Events; /// /// bot 收到 Guild 邀请事件 diff --git a/src/Flandre.Core/Events/Bot/BotGuildJoinRequestedEvent.cs b/src/Flandre.Core/Events/BotGuildJoinRequestedEvent.cs similarity index 96% rename from src/Flandre.Core/Events/Bot/BotGuildJoinRequestedEvent.cs rename to src/Flandre.Core/Events/BotGuildJoinRequestedEvent.cs index 2a10f66..61da339 100644 --- a/src/Flandre.Core/Events/Bot/BotGuildJoinRequestedEvent.cs +++ b/src/Flandre.Core/Events/BotGuildJoinRequestedEvent.cs @@ -1,4 +1,4 @@ -namespace Flandre.Core.Events.Bot; +namespace Flandre.Core.Events; /// /// 入群申请事件 diff --git a/src/Flandre.Core/Events/BotLoggingEvent.cs b/src/Flandre.Core/Events/BotLoggingEvent.cs new file mode 100644 index 0000000..0062374 --- /dev/null +++ b/src/Flandre.Core/Events/BotLoggingEvent.cs @@ -0,0 +1,25 @@ +using Flandre.Core.Common; + +namespace Flandre.Core.Events; + +/// +/// Bot 日志记录事件 +/// +public class BotLoggingEvent : BaseEvent +{ + /// + /// 日志等级 + /// + public BotLogLevel LogLevel { get; } + + /// + /// 日志消息 + /// + public string LogMessage { get; } + + internal BotLoggingEvent(BotLogLevel level, string message) + { + LogLevel = level; + LogMessage = message; + } +} \ No newline at end of file diff --git a/src/Flandre.Core/Events/Bot/BotMessageReceivedEvent.cs b/src/Flandre.Core/Events/BotMessageReceivedEvent.cs similarity index 92% rename from src/Flandre.Core/Events/Bot/BotMessageReceivedEvent.cs rename to src/Flandre.Core/Events/BotMessageReceivedEvent.cs index 1169082..3cedf2d 100644 --- a/src/Flandre.Core/Events/Bot/BotMessageReceivedEvent.cs +++ b/src/Flandre.Core/Events/BotMessageReceivedEvent.cs @@ -1,6 +1,6 @@ using Flandre.Core.Messaging; -namespace Flandre.Core.Events.Bot; +namespace Flandre.Core.Events; /// /// 消息接收事件 diff --git a/src/Flandre.Core/Events/CancellableEvent.cs b/src/Flandre.Core/Events/CancellableEvent.cs deleted file mode 100644 index dd985b7..0000000 --- a/src/Flandre.Core/Events/CancellableEvent.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Flandre.Core.Events; - -/// -/// 可取消的事件 -/// -public class CancellableEvent : BaseEvent -{ - /// - /// 事件是否被取消。设置为 true 来取消事件。 - /// - public bool IsCancelled { get; set; } -} \ No newline at end of file diff --git a/src/Flandre.Core/Events/Logger/LoggerLoggingEvent.cs b/src/Flandre.Core/Events/Logger/LoggerLoggingEvent.cs deleted file mode 100644 index c4c8821..0000000 --- a/src/Flandre.Core/Events/Logger/LoggerLoggingEvent.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace Flandre.Core.Events.Logger; - -/// -/// 日志记录前触发的事件 -/// -public class LoggerLoggingEvent : CancellableEvent -{ - /// - /// 默认格式的日志信息,可覆盖。 - /// - public string Message { get; set; } - - internal LoggerLoggingEvent(string message) - { - Message = message; - } -} \ No newline at end of file diff --git a/src/Flandre.Core/Events/Plugin/PluginStartingEvent.cs b/src/Flandre.Core/Events/Plugin/PluginStartingEvent.cs deleted file mode 100644 index beac997..0000000 --- a/src/Flandre.Core/Events/Plugin/PluginStartingEvent.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace Flandre.Core.Events.Plugin; - -/// -/// 插件启动事件 -/// -public class PluginStartingEvent : CancellableEvent -{ - /// - /// 将要启动的插件 - /// - public Common.Plugin Plugin { get; } - - internal PluginStartingEvent(Common.Plugin plugin) - { - Plugin = plugin; - } -} \ No newline at end of file diff --git a/src/Flandre.Core/Events/Plugin/PluginStoppedEvent.cs b/src/Flandre.Core/Events/Plugin/PluginStoppedEvent.cs deleted file mode 100644 index 9e512da..0000000 --- a/src/Flandre.Core/Events/Plugin/PluginStoppedEvent.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace Flandre.Core.Events.Plugin; - -/// -/// 插件关闭事件 -/// -public class PluginStoppedEvent : BaseEvent -{ - /// - /// 将要关闭的插件 - /// - public Common.Plugin Plugin { get; } - - internal PluginStoppedEvent(Common.Plugin plugin) - { - Plugin = plugin; - } -} \ No newline at end of file diff --git a/src/Flandre.Core/Extensions/ContextExtensions.cs b/src/Flandre.Core/Extensions/ContextExtensions.cs deleted file mode 100644 index 5c8f4ba..0000000 --- a/src/Flandre.Core/Extensions/ContextExtensions.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Flandre.Core.Messaging; - -namespace Flandre.Core.Extensions; - -/// -/// 上下文扩展方法 -/// -public static class ContextExtensions -{ - /// - /// 将 Bot 设置为群组主 Bot - /// - /// 消息上下文 - public static void SetBotAsGuildAssignee(this MessageContext ctx) - { - if (ctx.GuildId is null) return; - ctx.App.SetGuildAssignee(ctx.Platform, ctx.GuildId, ctx.SelfId); - } -} \ No newline at end of file diff --git a/src/Flandre.Core/Flandre.Core.csproj b/src/Flandre.Core/Flandre.Core.csproj index 4653000..479ab8b 100644 --- a/src/Flandre.Core/Flandre.Core.csproj +++ b/src/Flandre.Core/Flandre.Core.csproj @@ -3,11 +3,11 @@ Flandre.Core $(Title) - 0.6.0 + 0.7.0 $(PackageVersion) b1acksoil - 跨平台,低耦合的聊天机器人框架,一次编写,多处运行。 - bot + 跨平台聊天机器人框架核心,提供基本交互操作,容易嵌入到已有项目中。 + bot;chatbot;flandre MIT avatar.jpg README.NuGet.md diff --git a/src/Flandre.Core/FlandreApp.cs b/src/Flandre.Core/FlandreApp.cs deleted file mode 100644 index 1338bdd..0000000 --- a/src/Flandre.Core/FlandreApp.cs +++ /dev/null @@ -1,336 +0,0 @@ -using System.Collections.Concurrent; -using System.Runtime.CompilerServices; -using Flandre.Core.Common; -using Flandre.Core.Events.App; -using Flandre.Core.Events.Plugin; -using Flandre.Core.Messaging; -using Flandre.Core.Utils; - -[assembly: InternalsVisibleTo("Flandre.Core.Tests")] -[assembly: InternalsVisibleTo("Flandre.Adapters.Mock")] - -// ReSharper disable EventNeverSubscribedTo.Global -// ReSharper disable MemberCanBePrivate.Global -// ReSharper disable UnusedAutoPropertyAccessor.Global - -namespace Flandre.Core; - -/// -/// 应用基本框架 -/// -public partial class FlandreApp -{ - internal readonly List Adapters = new(); - internal readonly List> Middlewares = new(); - internal readonly List Plugins = new(); - internal readonly List Bots = new(); - - private readonly ManualResetEvent _exitEvent = new(false); - - internal static Logger Logger { get; } = new("App"); - - internal Dictionary CommandMap { get; } = new(); - - internal Dictionary ShortcutMap { get; } = new(); - - internal ConcurrentDictionary GuildAssignees { get; } = new(); - - /// - /// App 配置 - /// - public FlandreAppConfig Config { get; } - - #region Events - - /// - /// App 事件委托 - /// - /// - public delegate void AppEventHandler(FlandreApp app, TEvent e); - - /// - /// 应用准备启动事件 - /// - public event AppEventHandler? OnAppStarting; - - /// - /// 应用退出事件 - /// - public event AppEventHandler? OnAppStopped; - - /// - /// 应用就绪事件 - /// - public event AppEventHandler? OnAppReady; - - /// - /// 插件启动事件 - /// - public event AppEventHandler? OnPluginStarting; - - /// - /// 插件停止事件 - /// - public event AppEventHandler? OnPluginStopped; - - /// - /// 指令解析前事件 - /// - public event AppEventHandler? OnCommandParsing; - - /// - /// 指令解析完成事件 - /// - public event AppEventHandler? OnCommandParsed; - - /// - /// 指令调用前事件 - /// - public event AppEventHandler? OnCommandInvoking; - - /// - /// 指令调用完成事件 - /// - public event AppEventHandler? OnCommandInvoked; - - #endregion - - /// - /// 构造应用实例 - /// - /// 应用配置 - public FlandreApp(FlandreAppConfig? config = null) - { - Config = config ?? new FlandreAppConfig(); - - // Ctrl+C - Console.CancelKeyPress += (_, _) => Stop(); - - // AppDomain.CurrentDomain.UnhandledException += (_, args) => - // Logger.Error((Exception)args.ExceptionObject); - } - - /// - /// 注册适配器 - /// - /// 适配器 - /// 应用实例本身 - public FlandreApp Use(IAdapter adapter) - { - Adapters.Add(adapter); - Bots.AddRange(adapter.GetBots()); - return this; - } - - /// - /// 注册插件 - /// - /// 需要注册的模块的插件 - /// 应用实例本身 - public FlandreApp Use(Plugin plugin) - { - Plugins.Add(plugin); - foreach (var command in plugin.Commands) - { - CommandMap[command.CommandInfo.Command] = command; - foreach (var shortcut in command.Shortcuts) - ShortcutMap[shortcut.Shortcut] = command; - foreach (var alias in command.Aliases) - CommandMap[alias.Alias] = command; - } - - return this; - } - - /// - /// 注册异步中间件 - /// - /// 中间件 - /// 应用实例本身 - public FlandreApp Use(Func middleware) - { - Middlewares.Add(middleware); - return this; - } - - /// - /// 注册同步中间件 - /// - /// 中间件 - /// 应用实例本身 - public FlandreApp Use(Action middleware) - { - Middlewares.Add((ctx, next) => - { - middleware.Invoke(ctx, next); - return Task.CompletedTask; - }); - return this; - } - - /// - /// 启动应用实例 - /// - public void Start() - { - SubscribeEvents(); // 注册事件 - - var startingEvent = new AppStartingEvent(); - OnAppStarting?.Invoke(this, startingEvent); - if (startingEvent.IsCancelled) return; - - Logger.Info("Starting App..."); - - // 启动所有模块 - Task.WaitAll(Adapters.Select(adapter => adapter.Start()).ToArray()); - Task.WaitAll(Plugins.Select(plugin => Task.Run(async () => - { - var e = new PluginStartingEvent(plugin); - OnPluginStarting?.Invoke(this, e); - if (!e.IsCancelled) await plugin.Start(); - })).ToArray()); - - OnAppReady?.Invoke(this, new AppReadyEvent()); - - _exitEvent.WaitOne(); - } - - /// - /// 停止应用实例 - /// - public void Stop() - { - Task.WaitAll(Adapters.Select(adapter => adapter.Stop()).ToArray()); - Task.WaitAll(Plugins.Select(plugin => Task.Run(async () => - { - await plugin.Stop(); - OnPluginStopped?.Invoke(this, new PluginStoppedEvent(plugin)); - })).ToArray()); - OnAppStopped?.Invoke(this, new AppStoppedEvent()); - _exitEvent.Set(); - } - - private void SubscribeEvents() - { - void CatchAndLog(Action action) => Task.Run(() => - { - try - { - action(); - } - catch (AggregateException ae) - { - Logger.Error(ae.InnerException ?? ae); - } - catch (Exception e) - { - Logger.Error(e); - } - }); - - foreach (var plugin in Plugins) - { - OnAppStarting += plugin.OnAppStarting; - OnAppReady += plugin.OnAppReady; - OnAppStopped += plugin.OnAppStopped; - } - - foreach (var bot in Bots) - { - bot.OnMessageReceived += (_, e) => CatchAndLog(() => - ExecuteMiddlewares(new MessageContext(this, bot, e.Message), 0)); - - var ctx = new Context(this, bot); - - foreach (var plugin in Plugins) - { - Logger.DefaultLoggingHandlers.Add(e => - plugin.OnLoggerLogging(ctx, e)); - - bot.OnGuildInvited += (_, e) => CatchAndLog(() => - plugin.OnGuildInvited(ctx, e)); - bot.OnGuildJoinRequested += (_, e) => CatchAndLog(() => - plugin.OnGuildJoinRequested(ctx, e)); - bot.OnFriendRequested += (_, e) => CatchAndLog(() => - plugin.OnFriendRequested(ctx, e)); - } - } - - // 群组 assignee 检查 - Use(CheckAssigneeMiddleware); - - // 插件 OnMessageReceived - Use(PluginMessageReceivedMiddleware); - - // 指令解析中间件 - Use(ParseCommandMiddleware); - } - - private void ExecuteMiddlewares(MessageContext ctx, int index) - { - if (Middlewares.Count < index + 1) return; - Middlewares[index].Invoke(ctx, () => ExecuteMiddlewares(ctx, index + 1)).Wait(); - } - - private MessageContent? ParseCommand(MessageContext ctx) - { - MessageContent? ParseAndInvoke(Command c, StringParser p) - { - var parsingEvent = new AppCommandParsingEvent(ctx, c, p); - OnCommandParsing?.Invoke(this, parsingEvent); - if (parsingEvent.IsCancelled) return null; - var args = parsingEvent.CustomArgs; - if (args is null) - { - (args, var error) = c.ParseCommand(p); - OnCommandParsed?.Invoke(this, new AppCommandParsedEvent(args, error)); - if (error is not null) - return error; - } - - var invokingEvent = new AppCommandInvokingEvent(c, ctx, args); - OnCommandInvoking?.Invoke(this, invokingEvent); - - var content = c.InvokeCommand(ctx, args); - - var invokedEvent = new AppCommandInvokedEvent(c, ctx, args, content); - OnCommandInvoked?.Invoke(this, invokedEvent); - - return content; - } - - var commandStr = ctx.Message.GetText().Trim(); - if (commandStr == Config.CommandPrefix) - return null; - - var parser = new StringParser(commandStr); - - var root = parser.SkipSpaces().Read(' '); - - if (ShortcutMap.TryGetValue(root, out var command)) - return ParseAndInvoke(command, parser); - - if (!string.IsNullOrWhiteSpace(Config.CommandPrefix) - && !root.StartsWith(Config.CommandPrefix)) - return null; - root = root.TrimStart(Config.CommandPrefix); - parser.SkipSpaces(); - - while (true) - { - if (CommandMap.TryGetValue(root, out command) && - (parser.IsEnd() || !CommandMap.Keys.Any(cmd => - cmd.StartsWith($"{root}.{parser.Peek(' ')}")))) - return ParseAndInvoke(command, parser); - - if (parser.SkipSpaces().IsEnd()) break; - root = $"{root}.{parser.Read(' ')}"; - } - - if (string.IsNullOrWhiteSpace(Config.CommandPrefix)) return null; - if (!Config.IgnoreUndefinedCommand.Equals("no", StringComparison.OrdinalIgnoreCase)) return null; - ctx.Bot.SendMessage(ctx.Message, $"未找到指令:{root}。").Wait(); - - return null; - } -} \ No newline at end of file diff --git a/src/Flandre.Core/FlandreAppConfig.cs b/src/Flandre.Core/FlandreAppConfig.cs deleted file mode 100644 index 2484182..0000000 --- a/src/Flandre.Core/FlandreAppConfig.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace Flandre.Core; - -/// -/// 应用配置 -/// -public class FlandreAppConfig -{ - /// - /// 全局指令前缀 - /// - public string CommandPrefix { get; set; } = ""; - - /// - /// 忽略未定义指令的调用。可用值为: - ///
no - (默认)不忽略,调用未定义指令时发出警告信息 - ///
root - 忽略根指令(顶级指令) - ///
- public string IgnoreUndefinedCommand { get; set; } = "no"; -} \ No newline at end of file diff --git a/src/Flandre.Core/Messaging/MessageContext.cs b/src/Flandre.Core/Messaging/MessageContext.cs index 226176c..7736ae3 100644 --- a/src/Flandre.Core/Messaging/MessageContext.cs +++ b/src/Flandre.Core/Messaging/MessageContext.cs @@ -15,11 +15,10 @@ public class MessageContext : Context /// /// 构造消息上下文 /// - /// FlandreApp 实例 /// bot 实例 /// 消息 - public MessageContext(FlandreApp app, Bot bot, Message message) - : base(app, bot) + public MessageContext(Bot bot, Message message) + : base(bot) { Message = message; } diff --git a/src/Flandre.Core/Middlewares.cs b/src/Flandre.Core/Middlewares.cs deleted file mode 100644 index 7000dd1..0000000 --- a/src/Flandre.Core/Middlewares.cs +++ /dev/null @@ -1,38 +0,0 @@ -using Flandre.Core.Messaging; -using Flandre.Core.Messaging.Segments; - -namespace Flandre.Core; - -public partial class FlandreApp -{ - internal void CheckAssigneeMiddleware(MessageContext ctx, Action next) - { - var segment = ctx.Message.Content.Segments.FirstOrDefault(); - if (segment is AtSegment ats) - { - if (ats.UserId == ctx.SelfId) - next(); - } - // 如果没找到群组的 assignee - else if (!GuildAssignees.TryGetValue($"{ctx.Platform}:{ctx.GuildId}", out var assignee)) - next(); - // 如果找到了群组的 assignee,且是自己 - else if (ctx.SelfId == assignee) - next(); - } - - internal void PluginMessageReceivedMiddleware(MessageContext ctx, Action next) - { - foreach (var plugin in Plugins) - plugin.OnMessageReceived(ctx); - next(); - } - - internal void ParseCommandMiddleware(MessageContext ctx, Action next) - { - var content = ParseCommand(ctx); - if (content is not null) - ctx.Bot.SendMessage(ctx.Message, content); - next(); - } -} \ No newline at end of file diff --git a/src/Flandre.Core/Utils/FlandreUtils.cs b/src/Flandre.Core/Utils/FlandreCoreUtils.cs similarity index 97% rename from src/Flandre.Core/Utils/FlandreUtils.cs rename to src/Flandre.Core/Utils/FlandreCoreUtils.cs index 16c4b12..eaa904b 100644 --- a/src/Flandre.Core/Utils/FlandreUtils.cs +++ b/src/Flandre.Core/Utils/FlandreCoreUtils.cs @@ -5,7 +5,7 @@ namespace Flandre.Core.Utils; /// /// Flandre 工具方法 /// -public static class FlandreUtils +public static class FlandreCoreUtils { /// /// 自动检测 的属性并获取或下载资源。 diff --git a/src/Flandre.Core/Utils/Logger.cs b/src/Flandre.Core/Utils/Logger.cs deleted file mode 100644 index 798b7b8..0000000 --- a/src/Flandre.Core/Utils/Logger.cs +++ /dev/null @@ -1,147 +0,0 @@ -using Flandre.Core.Events.Logger; - -namespace Flandre.Core.Utils; - -/// -/// 日志记录器 -/// -public class Logger -{ - /// - /// 全局日志处理,在日志记录前调用 - /// - public static readonly List> DefaultLoggingHandlers = new(); - - /// - /// 日志类别 - /// - public string Name { get; set; } - - /// - /// 记录异常时抛出。建议在单元测试时使用。 - /// - public static bool ThrowOnError { get; set; } - - /// - /// 构造日志记录器 - /// - /// 日志类别 - public Logger(string name) - { - Name = name; - } - - /// - /// 记录日志 - /// - /// 日志等级 - /// 日志消息 - protected void Log(LogLevels logLevel, string message) - { - var loggingEvent = - new LoggerLoggingEvent($"{DateTime.Now:HH:mm:ss} [{logLevel.ToString()[0]}/{Name}] {message}"); - - DefaultLoggingHandlers.ForEach(logging => logging(loggingEvent)); - OnLoggerLogging?.Invoke(this, loggingEvent); - - if (!loggingEvent.IsCancelled) Console.WriteLine(loggingEvent.Message); - } - - /// - /// 记录信息 - /// - public void Info(string message) - { - Console.ForegroundColor = ConsoleColor.Cyan; - Log(LogLevels.Info, message); - } - - /// - /// 记录成功信息 - /// - public void Success(string message) - { - Console.ForegroundColor = ConsoleColor.Green; - Log(LogLevels.Success, message); - } - - /// - /// 记录警告信息 - /// - /// - public void Warning(string message) - { - Console.ForegroundColor = ConsoleColor.Yellow; - Log(LogLevels.Warning, message); - } - - /// - /// 记录错误信息 - /// - public void Error(string message) - { - Console.ForegroundColor = ConsoleColor.Red; - Log(LogLevels.Error, message); - } - - /// - /// 记录异常 - /// - public void Error(Exception exception) - { - Console.ForegroundColor = ConsoleColor.Red; - Log(LogLevels.Error, $"{exception.GetType().Name}: {exception.Message}\n{exception.StackTrace}"); - if (ThrowOnError) throw exception; - } - - /// - /// 记录调试信息 - /// - public void Debug(string message) - { - Console.ForegroundColor = ConsoleColor.DarkGray; - Log(LogLevels.Debug, message); - } - - /// - /// 日志事件委托 - /// - /// 事件类型 - public delegate void LoggerEventHandler(Logger logger, TEvent e); - - /// - /// 日志记录前 - /// - public event LoggerEventHandler? OnLoggerLogging; -} - -/// -/// 日志等级 -/// -public enum LogLevels -{ - /// - /// 调试 - /// - Debug = 0, - - /// - /// 信息 - /// - Info = 1, - - /// - /// 成功 - /// - Success = 2, - - /// - /// 警告 - /// - Warning = 3, - - /// - /// 错误 - /// - Error = 4 -} \ No newline at end of file diff --git a/src/Flandre.Framework/AssemblyInfo.cs b/src/Flandre.Framework/AssemblyInfo.cs new file mode 100644 index 0000000..cac520a --- /dev/null +++ b/src/Flandre.Framework/AssemblyInfo.cs @@ -0,0 +1,3 @@ +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("Flandre.Framework.Tests")] \ No newline at end of file diff --git a/src/Flandre.Core/Attributes/AliasAttribute.cs b/src/Flandre.Framework/Attributes/AliasAttribute.cs similarity index 86% rename from src/Flandre.Core/Attributes/AliasAttribute.cs rename to src/Flandre.Framework/Attributes/AliasAttribute.cs index c948c06..fac2565 100644 --- a/src/Flandre.Core/Attributes/AliasAttribute.cs +++ b/src/Flandre.Framework/Attributes/AliasAttribute.cs @@ -1,6 +1,6 @@ -using Flandre.Core.Utils; +using Flandre.Framework.Utils; -namespace Flandre.Core.Attributes; +namespace Flandre.Framework.Attributes; /// /// 指令别名特性 diff --git a/src/Flandre.Core/Attributes/CommandAttribute.cs b/src/Flandre.Framework/Attributes/CommandAttribute.cs similarity index 82% rename from src/Flandre.Core/Attributes/CommandAttribute.cs rename to src/Flandre.Framework/Attributes/CommandAttribute.cs index 0690a4b..67b1f2f 100644 --- a/src/Flandre.Core/Attributes/CommandAttribute.cs +++ b/src/Flandre.Framework/Attributes/CommandAttribute.cs @@ -1,12 +1,14 @@ using Flandre.Core.Utils; +using Flandre.Framework.Utils; +using Microsoft.Extensions.Logging; -namespace Flandre.Core.Attributes; +namespace Flandre.Framework.Attributes; /// /// 指令定义 /// [AttributeUsage(AttributeTargets.Method)] -public class CommandAttribute : Attribute +public sealed class CommandAttribute : Attribute { /// /// 指令名称 @@ -24,6 +26,8 @@ public class CommandAttribute : Attribute /// 指令格式定义 public CommandAttribute(string pattern) { + FlandreApp.InternalLogger?.LogWarning(pattern); + var parser = new StringParser(pattern); Command = CommandUtils.NormalizeCommandDefinition(parser.Read(' ')); parser.SkipSpaces(); @@ -50,7 +54,9 @@ public CommandAttribute(string pattern) if (info.IsRequired) { if (isNotRequiredParamAdded) - FlandreApp.Logger.Warning($"在指令 {Command} 的定义中,必选参数应位于可选参数之前,否则将造成难以预料的错误!"); + FlandreApp.InternalLogger?.LogWarning( + "The required argument must be places before the optional argument, in command \"{Command}\" definition.", + Command); } else { @@ -58,7 +64,7 @@ public CommandAttribute(string pattern) } Parameters.Add(info); - + parser.SkipSpaces(); } } diff --git a/src/Flandre.Core/Attributes/OptionAttribute.cs b/src/Flandre.Framework/Attributes/OptionAttribute.cs similarity index 96% rename from src/Flandre.Core/Attributes/OptionAttribute.cs rename to src/Flandre.Framework/Attributes/OptionAttribute.cs index 1560451..393c16c 100644 --- a/src/Flandre.Core/Attributes/OptionAttribute.cs +++ b/src/Flandre.Framework/Attributes/OptionAttribute.cs @@ -1,6 +1,7 @@ using Flandre.Core.Utils; +using Flandre.Framework.Utils; -namespace Flandre.Core.Attributes; +namespace Flandre.Framework.Attributes; /// /// 指令选项特性 diff --git a/src/Flandre.Core/Attributes/ShortcutAttribute.cs b/src/Flandre.Framework/Attributes/ShortcutAttribute.cs similarity index 92% rename from src/Flandre.Core/Attributes/ShortcutAttribute.cs rename to src/Flandre.Framework/Attributes/ShortcutAttribute.cs index 8660b97..7ef736f 100644 --- a/src/Flandre.Core/Attributes/ShortcutAttribute.cs +++ b/src/Flandre.Framework/Attributes/ShortcutAttribute.cs @@ -1,4 +1,4 @@ -namespace Flandre.Core.Attributes; +namespace Flandre.Framework.Attributes; /// /// 为指令添加快捷方式 diff --git a/src/Flandre.Core/Common/ArgumentManager.cs b/src/Flandre.Framework/Common/ArgumentManager.cs similarity index 97% rename from src/Flandre.Core/Common/ArgumentManager.cs rename to src/Flandre.Framework/Common/ArgumentManager.cs index 5e00602..7549ac2 100644 --- a/src/Flandre.Core/Common/ArgumentManager.cs +++ b/src/Flandre.Framework/Common/ArgumentManager.cs @@ -1,6 +1,6 @@ using System.Collections; -namespace Flandre.Core.Common; +namespace Flandre.Framework.Common; /// /// 指令参数 diff --git a/src/Flandre.Core/Common/Command.cs b/src/Flandre.Framework/Common/Command.cs similarity index 89% rename from src/Flandre.Core/Common/Command.cs rename to src/Flandre.Framework/Common/Command.cs index b194eaa..bbd3111 100644 --- a/src/Flandre.Core/Common/Command.cs +++ b/src/Flandre.Framework/Common/Command.cs @@ -1,9 +1,11 @@ using System.Reflection; -using Flandre.Core.Attributes; using Flandre.Core.Messaging; using Flandre.Core.Utils; +using Flandre.Framework.Attributes; +using Flandre.Framework.Utils; +using Microsoft.Extensions.Logging; -namespace Flandre.Core.Common; +namespace Flandre.Framework.Common; /// /// 指令 @@ -35,12 +37,12 @@ public class Command /// public List Aliases { get; } - private readonly Plugin _plugin; + internal Type PluginType { get; } - internal Command(Plugin plugin, CommandAttribute info, MethodInfo innerMethod, + internal Command(Type pluginType, CommandAttribute info, MethodInfo innerMethod, List options, List shortcuts, List aliases) { - _plugin = plugin; + PluginType = pluginType; CommandInfo = info; InnerMethod = innerMethod; Options = options; @@ -139,7 +141,7 @@ internal Command(Plugin plugin, CommandAttribute info, MethodInfo innerMethod, args.Arguments.ArgumentList.Add( new KeyValuePair(param.Name, parser.ReadQuoted())); break; - + case "text": args.Arguments.ArgumentList.Add( new KeyValuePair(param.Name, parser.ReadToEnd())); @@ -175,29 +177,22 @@ internal Command(Plugin plugin, CommandAttribute info, MethodInfo innerMethod, return (args, null); } - internal MessageContent? InvokeCommand(MessageContext ctx, ParsedArgs args) + internal MessageContent? InvokeCommand(Plugin plugin, MessageContext ctx, ParsedArgs args, ILogger logger) { try { var cmdResult = InnerMethod.Invoke( - _plugin, new object[] { ctx, args }[..InnerMethod.GetParameters().Length]); + plugin, new object[] { ctx, args }[..InnerMethod.GetParameters().Length]); var content = cmdResult as MessageContent ?? (cmdResult as Task)?.Result ?? null; return content; } - catch (TargetInvocationException te) - { - _plugin.Logger.Error(te.InnerException ?? te); - } - catch (AggregateException ae) - { - _plugin.Logger.Error(ae.InnerException ?? ae); - } catch (Exception e) { - _plugin.Logger.Error(e); + logger.LogError(e.InnerException ?? e, + "Error occurred while invoking command {CommandName} (method {MethodName}).", + CommandInfo.Command, InnerMethod.Name); + return null; } - - return null; } } \ No newline at end of file diff --git a/src/Flandre.Framework/Common/MiddlewareContext.cs b/src/Flandre.Framework/Common/MiddlewareContext.cs new file mode 100644 index 0000000..e44f32e --- /dev/null +++ b/src/Flandre.Framework/Common/MiddlewareContext.cs @@ -0,0 +1,15 @@ +using Flandre.Core.Common; +using Flandre.Core.Messaging; + +namespace Flandre.Framework.Common; + +public class MiddlewareContext : MessageContext +{ + public MessageContent? Response { get; set; } + + internal MiddlewareContext(Bot bot, Message message, MessageContent? resp) + : base(bot, message) + { + Response = resp; + } +} \ No newline at end of file diff --git a/src/Flandre.Core/Common/OptionManager.cs b/src/Flandre.Framework/Common/OptionManager.cs similarity index 95% rename from src/Flandre.Core/Common/OptionManager.cs rename to src/Flandre.Framework/Common/OptionManager.cs index 35e17e6..72ac199 100644 --- a/src/Flandre.Core/Common/OptionManager.cs +++ b/src/Flandre.Framework/Common/OptionManager.cs @@ -1,6 +1,6 @@ using System.Collections; -namespace Flandre.Core.Common; +namespace Flandre.Framework.Common; /// /// 指令选项 diff --git a/src/Flandre.Core/Common/ParsedArgs.cs b/src/Flandre.Framework/Common/ParsedArgs.cs similarity index 96% rename from src/Flandre.Core/Common/ParsedArgs.cs rename to src/Flandre.Framework/Common/ParsedArgs.cs index 9eab59b..04f4484 100644 --- a/src/Flandre.Core/Common/ParsedArgs.cs +++ b/src/Flandre.Framework/Common/ParsedArgs.cs @@ -1,4 +1,4 @@ -namespace Flandre.Core.Common; +namespace Flandre.Framework.Common; /// /// 解析后的指令参数/选项 diff --git a/src/Flandre.Framework/Common/Plugin.cs b/src/Flandre.Framework/Common/Plugin.cs new file mode 100644 index 0000000..e1ab40d --- /dev/null +++ b/src/Flandre.Framework/Common/Plugin.cs @@ -0,0 +1,42 @@ +using Flandre.Core.Common; +using Flandre.Core.Events; +using Flandre.Core.Messaging; +using Microsoft.Extensions.Logging; + +namespace Flandre.Framework.Common; + +public abstract class Plugin +{ + /// + /// 重写此方法,yixiang + /// + /// + public virtual ILogger? GetLogger() => null; + + /// + /// 处理消息事件 + /// + /// 当前消息上下文 + public virtual Task OnMessageReceived(MessageContext ctx) => Task.CompletedTask; + + /// + /// 收到拉群邀请 + /// + /// 当前上下文 + /// 拉群邀请事件 + public virtual Task OnGuildInvited(Context ctx, BotGuildInvitedEvent e) => Task.CompletedTask; + + /// + /// 收到入群申请 + /// + /// 当前上下文 + /// 入群申请事件 + public virtual Task OnGuildJoinRequested(Context ctx, BotGuildJoinRequestedEvent e) => Task.CompletedTask; + + /// + /// 收到好友申请 + /// + /// 当前上下文 + /// 好友申请事件 + public virtual Task OnFriendRequested(Context ctx, BotFriendRequestedEvent e) => Task.CompletedTask; +} \ No newline at end of file diff --git a/src/Flandre.Framework/Events/AppReadyEvent.cs b/src/Flandre.Framework/Events/AppReadyEvent.cs new file mode 100644 index 0000000..9d33b0c --- /dev/null +++ b/src/Flandre.Framework/Events/AppReadyEvent.cs @@ -0,0 +1,13 @@ +using Flandre.Core.Events; + +namespace Flandre.Framework.Events; + +/// +/// 应用就绪事件 +/// +public sealed class AppReadyEvent : BaseEvent +{ + internal AppReadyEvent() + { + } +} \ No newline at end of file diff --git a/src/Flandre.Framework/Events/AppStartingEvent.cs b/src/Flandre.Framework/Events/AppStartingEvent.cs new file mode 100644 index 0000000..0b62d87 --- /dev/null +++ b/src/Flandre.Framework/Events/AppStartingEvent.cs @@ -0,0 +1,13 @@ +using Flandre.Core.Events; + +namespace Flandre.Framework.Events; + +/// +/// 应用正在启动事件 +/// +public sealed class AppStartingEvent : BaseEvent +{ + internal AppStartingEvent() + { + } +} \ No newline at end of file diff --git a/src/Flandre.Framework/Events/AppStoppedEvent.cs b/src/Flandre.Framework/Events/AppStoppedEvent.cs new file mode 100644 index 0000000..6aaa65c --- /dev/null +++ b/src/Flandre.Framework/Events/AppStoppedEvent.cs @@ -0,0 +1,13 @@ +using Flandre.Core.Events; + +namespace Flandre.Framework.Events; + +/// +/// 应用退出事件 +/// +public sealed class AppStoppedEvent : BaseEvent +{ + internal AppStoppedEvent() + { + } +} \ No newline at end of file diff --git a/src/Flandre.Core/Extensions/AppExtensions.cs b/src/Flandre.Framework/Extensions/FlandreAppExtensions.cs similarity index 86% rename from src/Flandre.Core/Extensions/AppExtensions.cs rename to src/Flandre.Framework/Extensions/FlandreAppExtensions.cs index 6b72460..226ec8e 100644 --- a/src/Flandre.Core/Extensions/AppExtensions.cs +++ b/src/Flandre.Framework/Extensions/FlandreAppExtensions.cs @@ -1,9 +1,6 @@ -namespace Flandre.Core.Extensions; +namespace Flandre.Framework.Extensions; -/// -/// 的扩展方法 -/// -public static class AppExtensions +public static class FlandreAppExtensions { /// /// 设置群组代理 Bot(主 Bot) diff --git a/src/Flandre.Framework/Flandre.Framework.csproj b/src/Flandre.Framework/Flandre.Framework.csproj new file mode 100644 index 0000000..8470677 --- /dev/null +++ b/src/Flandre.Framework/Flandre.Framework.csproj @@ -0,0 +1,42 @@ + + + + Flandre.Framework + $(Title) + 0.7.0 + $(PackageVersion) + b1acksoil + 现代化、跨平台的聊天机器人框架,一次编写,多处运行。 + bot;chatbot;flandre;framework + MIT + avatar.jpg + README.NuGet.md + + net6.0 + enable + enable + Library + true + + https://github.com/FlandreDevs/Flandre + https://github.com/FlandreDevs/Flandre.git + git + b1acksoil (C) 2022-present + + + + + + + + + + + + + + + + + + diff --git a/src/Flandre.Framework/FlandreApp.cs b/src/Flandre.Framework/FlandreApp.cs new file mode 100644 index 0000000..4288373 --- /dev/null +++ b/src/Flandre.Framework/FlandreApp.cs @@ -0,0 +1,220 @@ +using System.Collections.Concurrent; +using System.Reflection; +using Flandre.Core.Common; +using Flandre.Framework.Attributes; +using Flandre.Framework.Common; +using Flandre.Framework.Events; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace Flandre.Framework; + +public sealed partial class FlandreApp +{ + public IServiceProvider Services { get; } + public ILogger Logger { get; } + + internal static ILogger? InternalLogger { get; private set; } + + private readonly FlandreAppConfig _config; + + private readonly List _adapters; + private readonly List _bots = new(); + private readonly List _pluginTypes; + private readonly List> _middlewares = new(); + + internal readonly Dictionary CommandMap = new(); + internal readonly Dictionary ShortcutMap = new(); + + internal ConcurrentDictionary GuildAssignees { get; } = new(); + + private readonly ManualResetEvent _exitEvent = new(false); + + internal FlandreApp(FlandreAppConfig config, IServiceProvider serviceProvider, + List pluginTypes, List adapters) + { + _config = config; + Services = serviceProvider; + _pluginTypes = pluginTypes; + _adapters = adapters; + + foreach (var adapter in _adapters) + _bots.AddRange(adapter.GetBots()); + + Logger = Services.GetRequiredService>(); + InternalLogger ??= Logger; + + Console.CancelKeyPress += (_, _) => Stop(); + } + + #region 初始化步骤 + + private void SubscribeEvents() + { + void WithCatch(Type pluginType, Func subscriber, string? eventName = null) => Task.Run(() => + { + try + { + var plugin = (Plugin)Services.GetRequiredService(pluginType); + subscriber.Invoke(plugin).Wait(); + } + catch (Exception e) + { + var logger = Services.GetRequiredService().CreateLogger(pluginType); + logger.LogError(e, "Error occurred while handling {EventName}.", eventName ?? "event"); + } + }); + + foreach (var bot in _bots) + { + bot.OnMessageReceived += (_, e) => Task.Run(() => + { + var middlewareCtx = new MiddlewareContext(bot, e.Message, null); + ExecuteMiddlewares(middlewareCtx, 0); // Wait for all middlewares' execution + if (middlewareCtx.Response is not null) + bot.SendMessage(e.Message, middlewareCtx.Response); + }); + + var ctx = new Context(bot); + + foreach (var pluginType in _pluginTypes) + { + bot.OnGuildInvited += (_, e) => WithCatch(pluginType, + plugin => plugin.OnGuildInvited(ctx, e), + nameof(bot.OnGuildInvited)); + + bot.OnGuildJoinRequested += (_, e) => WithCatch(pluginType, + plugin => plugin.OnGuildJoinRequested(ctx, e), + nameof(bot.OnFriendRequested)); + + bot.OnFriendRequested += (_, e) => WithCatch(pluginType, + plugin => plugin.OnFriendRequested(ctx, e), + nameof(bot.OnFriendRequested)); + } + + Logger.LogDebug("All bot events subscribed."); + } + } + + private void MapCommands() + { + var pluginCount = 0; + var commandCount = 0; + var aliasCount = 0; + var shortcutCount = 0; + foreach (var pluginType in _pluginTypes) + { + pluginCount++; + foreach (var method in pluginType.GetMethods()) + { + var cmdAttr = method.GetCustomAttribute(); + if (cmdAttr is null) continue; + + var options = method.GetCustomAttributes().ToList(); + var shortcuts = method.GetCustomAttributes().ToList(); + var aliases = method.GetCustomAttributes().ToList(); + + var command = new Command(pluginType, cmdAttr, method, options, shortcuts, aliases); + + CommandMap[cmdAttr.Command] = command; + commandCount++; + + foreach (var shortcut in shortcuts) + { + ShortcutMap[shortcut.Shortcut] = command; + shortcutCount++; + } + + foreach (var alias in aliases) + { + CommandMap[alias.Alias] = command; + aliasCount++; + } + } + } + + Logger.LogDebug( + "Total {PluginCount} plugins, {CommandCount} commands, {ShortcutCount} shortcuts, {AliasCount} aliases mapped.", + pluginCount, commandCount, shortcutCount, aliasCount); + } + + #endregion + + /// + /// 按顺序执行中间件,遵循洋葱模型 + /// + /// 中间件上下文 + /// 中间件索引 + private void ExecuteMiddlewares(MiddlewareContext ctx, int index) + { + try + { + if (_middlewares.Count < index + 1) return; + _middlewares[index].Invoke(ctx, () => ExecuteMiddlewares(ctx, index + 1)).Wait(); + } + catch (Exception e) + { + Logger.LogError(e, "Error occurred while processing middleware {MiddlewareName}.", + _middlewares[index].Method.Name); + } + } + + /// + /// 在最外层插入异步中间件 + /// + /// 中间件方法 + public FlandreApp UseMiddleware(Func middleware) + { + _middlewares.Insert(0, middleware); + return this; + } + + /// + /// 在最外层插入同步中间件 + /// + /// 中间件方法 + public FlandreApp UseMiddleware(Action middleware) + { + _middlewares.Insert(0, (ctx, next) => + { + middleware.Invoke(ctx, next); + return Task.CompletedTask; + }); + return this; + } + + /// + /// 运行应用实例,阻塞当前线程。 + /// + public void Run() + { + OnStarting?.Invoke(this, new AppStartingEvent()); + Logger.LogDebug("Starting app..."); + Logger.LogDebug("Total bots: {BotCount}, total plugins: {PluginCount}", + _bots.Count, _pluginTypes.Count); + + Task.WaitAll(_adapters.Select(adapter => adapter.Start()).ToArray()); + + SubscribeEvents(); + MapCommands(); + + UseMiddleware(ParseCommandMiddleware); + UseMiddleware(PluginMessageEventMiddleware); + UseMiddleware(CheckGuildAssigneeMiddleware); + + Logger.LogDebug("App started."); + OnReady?.Invoke(this, new AppReadyEvent()); + + _exitEvent.WaitOne(); + } + + /// + /// 停止应用实例 + /// + public void Stop() + { + Task.WaitAll(_adapters.Select(adapter => adapter.Stop()).ToArray()); + _exitEvent.Set(); + OnStopped?.Invoke(this, new AppStoppedEvent()); + } +} \ No newline at end of file diff --git a/src/Flandre.Framework/FlandreAppBuilder.cs b/src/Flandre.Framework/FlandreAppBuilder.cs new file mode 100644 index 0000000..9354d04 --- /dev/null +++ b/src/Flandre.Framework/FlandreAppBuilder.cs @@ -0,0 +1,77 @@ +using Flandre.Core.Common; +using Flandre.Framework.Common; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace Flandre.Framework; + +public sealed class FlandreAppBuilder +{ + /// + /// 服务集合 + /// + public IServiceCollection Services { get; } + + private readonly List _pluginTypes = new(); + + private readonly List _adapters = new(); + + private readonly FlandreAppConfig _appConfig; + + public FlandreAppBuilder(FlandreAppConfig? appConfig = null) + { + _appConfig = appConfig ?? new FlandreAppConfig(); + Services = new ServiceCollection(); + + Services.AddLogging(builder => { builder.AddConsole(); }); + } + + /// + /// 使用一个不带配置的插件。 + /// + /// 插件类型 + public FlandreAppBuilder UsePlugin() where TPlugin : Plugin + => UsePlugin(null); + + /// + /// 使用一个带配置的插件 + /// + /// 插件配置对象 + /// 带配置的插件类型 + public FlandreAppBuilder UsePlugin(object? config) where TPlugin : Plugin + => UsePlugin(config); + + /// + /// 使用一个带配置的插件。
+ /// 与 不同的是,本重载指定了配置类型作为参数类型,减小出错概率。 + ///
+ /// 插件配置对象 + /// 带插件的配置类型 + /// 插件配置类型 + public FlandreAppBuilder UsePlugin(TPluginConfig? config) where TPlugin : Plugin + { + var pluginType = typeof(TPlugin); + _pluginTypes.Add(pluginType); + Services.AddTransient(pluginType); + + if (config is not null) + Services.AddSingleton(config.GetType(), config); + + return this; + } + + public FlandreAppBuilder UseAdapter(IAdapter adapter) + { + _adapters.Add(adapter); + return this; + } + + /// + /// 建造 实例。 + /// + public FlandreApp Build() + { + var sp = Services.BuildServiceProvider(); + return new FlandreApp(_appConfig, sp, _pluginTypes, _adapters); + } +} \ No newline at end of file diff --git a/src/Flandre.Framework/FlandreAppConfig.cs b/src/Flandre.Framework/FlandreAppConfig.cs new file mode 100644 index 0000000..9d158a1 --- /dev/null +++ b/src/Flandre.Framework/FlandreAppConfig.cs @@ -0,0 +1,12 @@ +namespace Flandre.Framework; + +/// +/// 应用配置 +/// +public class FlandreAppConfig +{ + /// + /// 全局指令前缀 + /// + public string CommandPrefix { get; set; } = string.Empty; +} \ No newline at end of file diff --git a/src/Flandre.Framework/FlandreAppEvents.cs b/src/Flandre.Framework/FlandreAppEvents.cs new file mode 100644 index 0000000..1f08447 --- /dev/null +++ b/src/Flandre.Framework/FlandreAppEvents.cs @@ -0,0 +1,15 @@ +using Flandre.Core.Events; +using Flandre.Framework.Events; + +namespace Flandre.Framework; + +public sealed partial class FlandreApp +{ + public delegate void AppEventHandler(FlandreApp app, TEvent e) where TEvent : BaseEvent; + + public event AppEventHandler? OnStarting; + + public event AppEventHandler? OnReady; + + public event AppEventHandler? OnStopped; +} \ No newline at end of file diff --git a/src/Flandre.Framework/InternalMiddlewares.cs b/src/Flandre.Framework/InternalMiddlewares.cs new file mode 100644 index 0000000..c8df640 --- /dev/null +++ b/src/Flandre.Framework/InternalMiddlewares.cs @@ -0,0 +1,92 @@ +using Flandre.Core.Messaging; +using Flandre.Core.Messaging.Segments; +using Flandre.Core.Utils; +using Flandre.Framework.Common; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace Flandre.Framework; + +public sealed partial class FlandreApp +{ + internal void CheckGuildAssigneeMiddleware(MiddlewareContext ctx, Action next) + { + var segment = ctx.Message.Content.Segments.FirstOrDefault(); + if (segment is AtSegment ats) + { + if (ats.UserId == ctx.SelfId) + next(); + } + // 如果没找到群组的 assignee + else if (!GuildAssignees.TryGetValue($"{ctx.Platform}:{ctx.GuildId}", out var assignee)) + { + next(); + } + // 如果找到了群组的 assignee,且是自己 + else if (ctx.SelfId == assignee) + { + next(); + } + } + + internal async Task PluginMessageEventMiddleware(MiddlewareContext ctx, Action next) + { + await Task.WhenAll(_pluginTypes + .Select(p => ((Plugin)Services.GetRequiredService(p)).OnMessageReceived(ctx)) + .ToArray()); + next(); + } + + internal void ParseCommandMiddleware(MiddlewareContext ctx, Action next) + { + ctx.Response = ParseCommand(ctx); + next(); + } + + internal MessageContent? ParseCommand(MessageContext ctx) + { + MessageContent? ParseAndInvoke(Command cmd, StringParser p) + { + var (args, error) = cmd.ParseCommand(p); + if (error is not null) return error; + + var plugin = (Plugin)Services.GetRequiredService(cmd.PluginType); + var pluginLogger = Services.GetRequiredService().CreateLogger(cmd.PluginType); + + return cmd.InvokeCommand(plugin, ctx, args, pluginLogger); + } + + var commandStr = ctx.Message.GetText().Trim(); + if (commandStr == _config.CommandPrefix) + return null; + + var parser = new StringParser(commandStr); + + var root = parser.SkipSpaces().Read(' '); + + if (ShortcutMap.TryGetValue(root, out var command)) + return ParseAndInvoke(command, parser); + + if (!string.IsNullOrWhiteSpace(_config.CommandPrefix) + && !root.StartsWith(_config.CommandPrefix)) + return null; + root = root.TrimStart(_config.CommandPrefix); + parser.SkipSpaces(); + + while (true) + { + if (CommandMap.TryGetValue(root, out command) && + (parser.IsEnd() || !CommandMap.Keys.Any(cmd => + cmd.StartsWith($"{root}.{parser.Peek(' ')}")))) + return ParseAndInvoke(command, parser); + + if (parser.SkipSpaces().IsEnd()) break; + root = $"{root}.{parser.Read(' ')}"; + } + + if (string.IsNullOrWhiteSpace(_config.CommandPrefix)) return null; + ctx.Bot.SendMessage(ctx.Message, $"未找到指令:{root}。").Wait(); + + return null; + } +} \ No newline at end of file diff --git a/src/Flandre.Core/Utils/CommandUtils.cs b/src/Flandre.Framework/Utils/CommandUtils.cs similarity index 88% rename from src/Flandre.Core/Utils/CommandUtils.cs rename to src/Flandre.Framework/Utils/CommandUtils.cs index 93a3ab9..e4339a5 100644 --- a/src/Flandre.Core/Utils/CommandUtils.cs +++ b/src/Flandre.Framework/Utils/CommandUtils.cs @@ -1,6 +1,8 @@ -using Flandre.Core.Attributes; +using Flandre.Core.Utils; +using Flandre.Framework.Attributes; +using Microsoft.Extensions.Logging; -namespace Flandre.Core.Utils; +namespace Flandre.Framework.Utils; internal static class CommandUtils { @@ -26,7 +28,9 @@ internal static ParameterInfo ParseParameterSection(string section, string defau if (TryParseType(innerRight[1].Trim(), info.Type, out var result)) info.DefaultValue = result; else - FlandreApp.Logger.Warning($"指令 {cmdName} 的参数 {info.Name} 默认值与类型不匹配,已自动回退至类型默认值。"); + FlandreApp.InternalLogger?.LogWarning( + "The default value's type of argument {ArgumentName} cannot be match, in command {CommandName}.", + info.Name, cmdName); } return info; @@ -128,7 +132,9 @@ internal static bool TryParseType(string section, string type, out object result break; default: - FlandreApp.Logger.Warning($"无法匹配参数类型 {type},请检查指令定义。"); + FlandreApp.InternalLogger?.LogWarning( + "Cannot match the argument type {ArgumentType}, please check the command definition.", + type); break; } diff --git a/tests/Flandre.Core.Tests/CommonTests/PluginTests.cs b/tests/Flandre.Core.Tests/CommonTests/PluginTests.cs deleted file mode 100644 index 0dd8d81..0000000 --- a/tests/Flandre.Core.Tests/CommonTests/PluginTests.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace Flandre.Core.Tests.CommonTests; - -public class PluginTests -{ -} \ No newline at end of file diff --git a/tests/Flandre.Framework.Tests/Flandre.Framework.Tests.csproj b/tests/Flandre.Framework.Tests/Flandre.Framework.Tests.csproj new file mode 100644 index 0000000..bce8cd5 --- /dev/null +++ b/tests/Flandre.Framework.Tests/Flandre.Framework.Tests.csproj @@ -0,0 +1,29 @@ + + + + net6.0 + enable + enable + + false + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + diff --git a/tests/Flandre.Core.Tests/FlandreAppTests.cs b/tests/Flandre.Framework.Tests/FlandreAppTests.cs similarity index 84% rename from tests/Flandre.Core.Tests/FlandreAppTests.cs rename to tests/Flandre.Framework.Tests/FlandreAppTests.cs index 4dbb16e..bd8cb0e 100644 --- a/tests/Flandre.Core.Tests/FlandreAppTests.cs +++ b/tests/Flandre.Framework.Tests/FlandreAppTests.cs @@ -1,23 +1,26 @@ using Flandre.Adapters.Mock; -using Flandre.Core.Utils; // ReSharper disable StringLiteralTypo -namespace Flandre.Core.Tests; +namespace Flandre.Framework.Tests; public class FlandreAppTests { [Fact] public void TestFlandreApp() { - Logger.ThrowOnError = true; - var app = new FlandreApp(); + var builder = new FlandreAppBuilder(); var adapter = new MockAdapter(); var channelClient = adapter.GetChannelClient(); var friendClient = adapter.GetFriendClient(); - app.OnAppReady += (_, _) => + var app = builder + .UseAdapter(adapter) + .UsePlugin() + .Build(); + + app.OnReady += (_, _) => { var content = channelClient.SendForReply("OMR:114514").Result; Assert.Equal("OMR:114514", content?.GetText()); @@ -34,7 +37,7 @@ public void TestFlandreApp() content = friendClient.SendForReply("test1 -bo 111.444 --no-trueopt false").Result; Assert.Equal("arg1: False opt: 111.444 b: True t: False", content?.GetText()); - + content = friendClient.SendForReply("test2 some text aaa bbb ").Result; Assert.Equal("some text aaa bbb", content?.GetText()); @@ -42,19 +45,23 @@ public void TestFlandreApp() app.Stop(); }; - app.Use(adapter).Use(new TestPlugin()).Start(); + app.Run(); } [Fact] public void TestShortcutAndAlias() { - Logger.ThrowOnError = true; - var app = new FlandreApp(); + var builder = new FlandreAppBuilder(); var adapter = new MockAdapter(); var channelClient = adapter.GetChannelClient(); - app.OnAppReady += (_, _) => + var app = builder + .UseAdapter(adapter) + .UsePlugin() + .Build(); + + app.OnReady += (_, _) => { Assert.Equal(6, app.CommandMap.Count); Assert.Equal(2, app.ShortcutMap.Count); @@ -83,6 +90,6 @@ public void TestShortcutAndAlias() app.Stop(); }; - app.Use(adapter).Use(new TestPlugin()).Start(); + app.Run(); } } \ No newline at end of file diff --git a/tests/Flandre.Core.Tests/MiddlewareTests.cs b/tests/Flandre.Framework.Tests/MiddlewareTests.cs similarity index 67% rename from tests/Flandre.Core.Tests/MiddlewareTests.cs rename to tests/Flandre.Framework.Tests/MiddlewareTests.cs index bb5741e..bffd608 100644 --- a/tests/Flandre.Core.Tests/MiddlewareTests.cs +++ b/tests/Flandre.Framework.Tests/MiddlewareTests.cs @@ -1,26 +1,29 @@ using Flandre.Adapters.Mock; -using Flandre.Core.Utils; -namespace Flandre.Core.Tests; +namespace Flandre.Framework.Tests; public class MiddlewareTests { [Fact] public void TestMiddleware() { - Logger.ThrowOnError = true; - var app = new FlandreApp(); + var builder = new FlandreAppBuilder(); var adapter = new MockAdapter(); - app.Use((ctx, next) => + var client = adapter.GetChannelClient(); + + var app = builder + .UseAdapter(adapter) + .UsePlugin() + .Build(); + + app.UseMiddleware((ctx, next) => { if (ctx.Message.GetText() == "OMR:pass me") next(); }); - var client = adapter.GetChannelClient(); - - app.OnAppReady += (_, _) => + app.OnReady += (_, _) => { var content1 = client.SendForReply("OMR:pass me").Result; Assert.NotNull(content1); @@ -30,7 +33,6 @@ public void TestMiddleware() app.Stop(); }; - - app.Use(adapter).Use(new TestPlugin()).Start(); + app.Run(); } } \ No newline at end of file diff --git a/tests/Flandre.Core.Tests/TestPlugin.cs b/tests/Flandre.Framework.Tests/TestPlugin.cs similarity index 83% rename from tests/Flandre.Core.Tests/TestPlugin.cs rename to tests/Flandre.Framework.Tests/TestPlugin.cs index ed07b8e..c40b19a 100644 --- a/tests/Flandre.Core.Tests/TestPlugin.cs +++ b/tests/Flandre.Framework.Tests/TestPlugin.cs @@ -1,18 +1,17 @@ -using Flandre.Core.Common; -using Flandre.Core.Messaging; -using Flandre.Core.Attributes; +using Flandre.Core.Messaging; +using Flandre.Framework.Attributes; +using Flandre.Framework.Common; // ReSharper disable StringLiteralTypo -namespace Flandre.Core.Tests; +namespace Flandre.Framework.Tests; -[Plugin("Test")] public class TestPlugin : Plugin { - public override void OnMessageReceived(MessageContext ctx) + public override async Task OnMessageReceived(MessageContext ctx) { if (ctx.Message.GetText().StartsWith("OMR:")) - ctx.Bot.SendMessage(ctx.Message); + await ctx.Bot.SendMessage(ctx.Message); } [Command("test1 ")] @@ -29,7 +28,7 @@ public static MessageContent OnTest1(MessageContext ctx, ParsedArgs args) var trueOpt = args.GetOption("trueopt"); return $"arg1: {arg1} opt: {opt} b: {boolOpt} t: {trueOpt}"; } - + [Command("test2 ")] public static MessageContent OnTest2(MessageContext _, ParsedArgs args) { diff --git a/tests/Flandre.Framework.Tests/Usings.cs b/tests/Flandre.Framework.Tests/Usings.cs new file mode 100644 index 0000000..8c927eb --- /dev/null +++ b/tests/Flandre.Framework.Tests/Usings.cs @@ -0,0 +1 @@ +global using Xunit; \ No newline at end of file diff --git a/tests/Flandre.Core.Tests/UtilsTests/CommandUtilsTests.cs b/tests/Flandre.Framework.Tests/UtilsTests/CommandUtilsTests.cs similarity index 97% rename from tests/Flandre.Core.Tests/UtilsTests/CommandUtilsTests.cs rename to tests/Flandre.Framework.Tests/UtilsTests/CommandUtilsTests.cs index 456a170..d6165f8 100644 --- a/tests/Flandre.Core.Tests/UtilsTests/CommandUtilsTests.cs +++ b/tests/Flandre.Framework.Tests/UtilsTests/CommandUtilsTests.cs @@ -1,6 +1,6 @@ -using Flandre.Core.Utils; +using Flandre.Framework.Utils; -namespace Flandre.Core.Tests.UtilsTests; +namespace Flandre.Framework.Tests.UtilsTests; public class CommandUtilsTests {