├── .config
└── dotnet-tools.json
├── .github
├── ISSUE_TEMPLATE
│ └── bug--.md
├── dependabot.yml
└── workflows
│ └── pr-build-check.yml
├── .gitignore
├── LICENSE
├── README.md
└── src
├── Benchmarks
└── CommandLineBenchmark
│ ├── Benchmarks.cs
│ ├── CommandLineBenchmark.csproj
│ ├── Program.cs
│ ├── UrlEncodeTest.cs
│ └── passwords.txt
├── Directory.Build.props
├── Directory.Build.targets
├── MilkiBotFramework.Aspnetcore
├── AspnetcoreBotBuilder.cs
├── AspnetcoreConnector.cs
├── HttpMiddleware.cs
├── MilkiBotFramework.Aspnetcore.csproj
└── ReverseWebsocketMiddleware.cs
├── MilkiBotFramework.Imaging.Avalonia
├── AssemblyInfo.cs
├── AvaImageHelper.cs
├── AvaRenderingControl.cs
├── AvaRenderingProcessor.cs
├── AvaloniaOptions.cs
├── DefaultApp.axaml
├── DefaultApp.axaml.cs
├── DpiDecorator.cs
├── Internal
│ ├── Delegates.cs
│ ├── DrawingWindow.cs
│ └── UiThreadHelper.cs
├── LocalFontManager.cs
├── LocaleComparer.cs
├── MilkiBotFramework.Imaging.Avalonia.csproj
├── RangeObservableCollection.cs
└── app.manifest
├── MilkiBotFramework.Imaging.Wpf
├── DpiDecorator.cs
├── Internal
│ ├── Delegates.cs
│ ├── HiddenWindow.cs
│ └── UiThreadHelper.cs
├── MilkiBotFramework.Imaging.Wpf.csproj
├── WpfDrawingControl.cs
├── WpfDrawingProcessor.cs
└── WpfImageHelper.cs
├── MilkiBotFramework.sln
├── MilkiBotFramework.sln.DotSettings
├── MilkiBotFramework
├── Bot.cs
├── BotBuilder.cs
├── BotBuilderBase.cs
├── BotOptions.cs
├── ConfigLoggerProvider.cs
├── Connecting
│ ├── ConnectionType.cs
│ ├── IConnector.cs
│ ├── IConnectorConfigurable.cs
│ ├── IMessageApi.cs
│ ├── IWebSocketConnector.cs
│ ├── LightHttpClient.cs
│ ├── LightHttpClientCreationOptions.cs
│ ├── RequestContext.cs
│ ├── StandardIoConnector.cs
│ ├── WebSocketAsyncMessage.cs
│ ├── WebSocketClientConnector.cs
│ ├── WebSocketMessageFilter.cs
│ ├── WebSocketMessageSession.cs
│ ├── WebSocketMessageSessionManager.cs
│ └── WebSocketServerConnector.cs
├── ContactsManaging
│ ├── ContactsManagerBase.cs
│ ├── ContactsManagerExtensions.cs
│ ├── ContactsUpdateEvent.cs
│ ├── ContactsUpdateSingleEvent.cs
│ ├── IContactsManager.cs
│ ├── Models
│ │ ├── Avatar.cs
│ │ ├── ChannelInfo.cs
│ │ ├── MemberInfo.cs
│ │ ├── MemberRole.cs
│ │ ├── PrivateInfo.cs
│ │ └── SelfInfo.cs
│ └── Results
│ │ ├── ChannelInfoResult.cs
│ │ ├── ContactsUpdateInfo.cs
│ │ ├── ContactsUpdateRole.cs
│ │ ├── ContactsUpdateType.cs
│ │ ├── MemberInfoResult.cs
│ │ ├── PrivateInfoResult.cs
│ │ ├── ResultInfoBase.cs
│ │ └── SelfInfoResult.cs
├── Data
│ ├── IInvokableViewModel.cs
│ ├── InvokableViewModelExtensions.cs
│ └── ViewModelBase.cs
├── Dispatching
│ ├── DispatchMessageEvent.cs
│ ├── DispatcherBase.cs
│ └── IDispatcher.cs
├── Event
│ ├── EventBus.cs
│ └── IEventBusEvent.cs
├── FrameworkConstants.cs
├── Imaging
│ ├── GifFrame.cs
│ ├── GifProcessOptions.cs
│ ├── IDrawingProcessor.cs
│ ├── ImageHelper.cs
│ └── ImageType.cs
├── Messaging
│ ├── AsyncMessage.cs
│ ├── AsyncMessageResponse.cs
│ ├── AsyncMessageTimeoutException.cs
│ ├── DefaultRichMessageConverter.cs
│ ├── IAsyncMessage.cs
│ ├── IAsyncMessageResponse.cs
│ ├── IResponse.cs
│ ├── IRichMessageConverter.cs
│ ├── MessageAuthority.cs
│ ├── MessageContext.cs
│ ├── MessageIdentity.cs
│ ├── MessageResponse.cs
│ ├── MessageTimeoutException.cs
│ ├── MessageType.cs
│ ├── MessageUserIdentity.cs
│ └── RichMessages
│ │ ├── At.cs
│ │ ├── FileImage.cs
│ │ ├── IRichMessage.cs
│ │ ├── LinkImage.cs
│ │ ├── MemoryImage.cs
│ │ ├── Reply.cs
│ │ ├── RichMessage.cs
│ │ ├── Text.cs
│ │ ├── UriText.cs
│ │ └── Voice.cs
├── MilkiBotFramework.csproj
├── Plugining
│ ├── Attributes
│ │ ├── ArgumentAttribute.cs
│ │ ├── CommandHandlerAttribute.cs
│ │ ├── OptionAttribute.cs
│ │ ├── ParameterAttribute.cs
│ │ ├── PluginIdentifierAttribute.cs
│ │ ├── PluginLifetimeAttribute.cs
│ │ ├── RegexCommandAttribute.cs
│ │ └── RegexCommandHandlerAttribute.cs
│ ├── BasicPlugin.cs
│ ├── BindingException.cs
│ ├── BindingFailureType.cs
│ ├── BindingSource.cs
│ ├── CommandInjector.cs
│ ├── CommandLine
│ │ ├── CommandLineAnalyzer.cs
│ │ ├── CommandLineAuthority.cs
│ │ ├── CommandLineException.cs
│ │ ├── CommandLineResult.cs
│ │ ├── ICommandLineAnalyzer.cs
│ │ └── StreamCommandLineAnalyzer.cs
│ ├── Configuration
│ │ ├── CommentGatheringTypeInspector.cs
│ │ ├── CommentsObjectDescriptor.cs
│ │ ├── CommentsObjectGraphVisitor.cs
│ │ ├── Configuration.cs
│ │ ├── ConfigurationBase.cs
│ │ ├── ConfigurationFactory.cs
│ │ ├── DateTimeOffsetConverter.cs
│ │ ├── IConfiguration.cs
│ │ ├── MessageIdentityConverter.cs
│ │ └── YamlConverter.cs
│ ├── Database
│ │ └── PluginDbContext.cs
│ ├── IMessagePlugin.cs
│ ├── Loading
│ │ ├── AssemblyContext.cs
│ │ ├── CommandInfo.cs
│ │ ├── CommandParameterInfo.cs
│ │ ├── CommandReturnType.cs
│ │ ├── DefaultParameterConverter.cs
│ │ ├── IParameterConverter.cs
│ │ ├── LoaderContext.cs
│ │ ├── ModelBindingInfo.cs
│ │ └── PluginInfo.cs
│ ├── PluginBase.cs
│ ├── PluginLifetime.cs
│ ├── PluginManager.Initialization.cs
│ ├── PluginManager.cs
│ ├── PluginMetadata.cs
│ ├── PluginType.cs
│ └── ServicePlugin.cs
├── StaticTypes.cs
├── Tasking
│ ├── BotTaskScheduler.cs
│ ├── DateTimeExtensions.cs
│ ├── TaskContext.cs
│ ├── TaskExecutionHandler.cs
│ ├── TaskInstance.cs
│ ├── TaskOption.cs
│ ├── TaskOptionBuilder.cs
│ ├── TimestampOffset.cs
│ ├── Trigger.cs
│ └── TriggerTimestampUnit.cs
└── Utils
│ ├── AssemblyHelper.cs
│ ├── AsyncLock.cs
│ ├── HttpEncoder.cs
│ ├── HttpHelperExtensions.cs
│ ├── Utilities.cs
│ ├── ValueListBuilder.cs
│ └── WaitHandleExtensions.cs
├── Platforms
├── MilkiBotFramework.Platforms.GoCqHttp
│ ├── BotBuilderExtensions.cs
│ ├── Connecting
│ │ ├── GoCqApi.cs
│ │ ├── GoCqApiException.cs
│ │ ├── GoCqClient.cs
│ │ ├── GoCqKestrelConnector.cs
│ │ ├── GoCqServer.cs
│ │ ├── GoCqWebsocketHelper.cs
│ │ ├── IGoCqConnector.cs
│ │ ├── RequestModel
│ │ │ ├── FriendAddRequest.cs
│ │ │ ├── GoCqRequest.cs
│ │ │ ├── GroupAddRequest.cs
│ │ │ ├── GroupMsgResponse.cs
│ │ │ ├── PrivateMsgResponse.cs
│ │ │ ├── SendDiscussMsg.cs
│ │ │ ├── SendGroupMsg.cs
│ │ │ └── SendPrivateMsg.cs
│ │ └── ResponseModel
│ │ │ ├── ChannelType.cs
│ │ │ ├── FriendInfo.cs
│ │ │ ├── GetMsgResponse.cs
│ │ │ ├── GoCqApiResponse.cs
│ │ │ ├── GroupInfo.cs
│ │ │ ├── GroupMemberInfo.cs
│ │ │ ├── Guild
│ │ │ ├── GetGuildMembersResponse.cs
│ │ │ ├── GuildBrief.cs
│ │ │ ├── GuildInfo.cs
│ │ │ ├── GuildMember.cs
│ │ │ ├── SlowMode.cs
│ │ │ └── SubChannelInfo.cs
│ │ │ ├── LoginInfo.cs
│ │ │ ├── MsgResponse.cs
│ │ │ └── StrangerInfo.cs
│ ├── ContactsManaging
│ │ └── GoCqContactsManager.cs
│ ├── Dispatching
│ │ └── GoCqDispatcher.cs
│ ├── GoCqBotOptions.cs
│ ├── GoCqConnection.cs
│ ├── GoCqParameterConverter.cs
│ ├── Internal
│ │ ├── Int64ToStringConverter.cs
│ │ └── UnixDateTimeConverter.cs
│ ├── Messaging
│ │ ├── CqCodes
│ │ │ ├── CQAt.cs
│ │ │ ├── CQCodeHelper.cs
│ │ │ ├── CQDice.cs
│ │ │ ├── CQFace.cs
│ │ │ ├── CQImage.cs
│ │ │ ├── CQImageType.cs
│ │ │ ├── CQMusic.cs
│ │ │ ├── CQRecord.cs
│ │ │ ├── CQReply.cs
│ │ │ ├── CQRps.cs
│ │ │ ├── CQShare.cs
│ │ │ └── CQUnknown.cs
│ │ ├── Events
│ │ │ ├── EventBase.cs
│ │ │ ├── FriendAdd.cs
│ │ │ ├── FriendRequest.cs
│ │ │ ├── GroupAdminChange.cs
│ │ │ ├── GroupFileUpload.cs
│ │ │ ├── GroupInvite.cs
│ │ │ ├── GroupMemberChange.cs
│ │ │ ├── GroupMessage.cs
│ │ │ ├── GuildMessage.cs
│ │ │ ├── IDetailedSenderMessage.cs
│ │ │ ├── MessageBase.cs
│ │ │ ├── PrivateMessage.cs
│ │ │ └── SenderBase.cs
│ │ ├── GoCqMessageContext.cs
│ │ ├── GoCqMessageConverter.cs
│ │ └── RangeComparer.cs
│ ├── MilkiBotFramework.Platforms.GoCqHttp.csproj
│ └── Utils
│ │ ├── EncodingHelper.cs
│ │ └── RegexHelper.cs
└── MilkiBotFramework.Platforms.QQ
│ ├── BotBuilderExtensions.cs
│ ├── Connecting
│ ├── Intents.cs
│ ├── OpCode.cs
│ ├── QApi.cs
│ ├── QApiConnector.cs
│ └── QApiException.cs
│ ├── ContactsManaging
│ └── QContactsManager.cs
│ ├── Dispatching
│ └── QDispatcher.cs
│ ├── Messaging
│ ├── MinIOController.cs
│ ├── QMessageContext.cs
│ ├── QMessageConverter.cs
│ └── RichMessages
│ │ └── ImagePlaceholder.cs
│ ├── MilkiBotFramework.Platforms.QQ.csproj
│ ├── MinIOOptions.cs
│ ├── QConnection.cs
│ ├── QParameterConverter.cs
│ └── QQBotOptions.cs
├── Samples
├── DemoBot
│ ├── DemoBot.csproj
│ ├── DemoPlugin.cs
│ ├── DemoPlugin2.cs
│ ├── DemoServicePlugin.cs
│ ├── MyPluginDbContext.cs
│ ├── Program.cs
│ ├── TestConfiguration.cs
│ ├── WeatherForecast.cs
│ └── WeatherForecastController.cs
└── DemoPlugin
│ ├── AnotherServicePlugin.cs
│ └── DemoPlugin.csproj
└── Tests
├── AvaHeadlessTest
├── App.axaml
├── App.axaml.cs
├── Assets
│ └── Fonts
│ │ └── readme.md
├── AvaHeadlessTest.csproj
├── AvaTestControl.axaml
├── AvaTestControl.axaml.cs
├── Program.cs
└── Properties
│ └── launchSettings.json
├── MinioTest
├── MinioTest.csproj
└── Program.cs
└── UnitTests
├── CommandLineTests.cs
├── UnitTests.csproj
└── passwords.txt
/.config/dotnet-tools.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": 1,
3 | "isRoot": true,
4 | "tools": {
5 | "jetbrains.resharper.globaltools": {
6 | "version": "2023.3.3",
7 | "commands": [
8 | "jb"
9 | ]
10 | },
11 | "nvika": {
12 | "version": "3.0.0",
13 | "commands": [
14 | "nvika"
15 | ]
16 | }
17 | }
18 | }
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug--.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: bug报告
3 | about: 请提交一个明确是bug的报告。若非明确bug,请至discussion
4 | title: "[BUG]"
5 | labels: bug-pending
6 | assignees: ''
7 |
8 | ---
9 |
10 | **准确、简要的BUG说明**
11 | ...
12 |
13 |
14 | **还原步骤或异常信息**
15 | ```
16 | 1. Write xxx code '...'
17 | 2. See error
18 | ```
19 |
20 | **期望行为**
21 | *请注意是站在框架角度期望的行为,而不是个人的期望行为*
22 |
23 |
24 | **环境**
25 | - .NET版本: [e.g. 6.0.200]
26 | - 操作系统: [e.g. Windows 10 21H2]
27 |
28 | **其他补充**
29 | ...
30 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | # To get started with Dependabot version updates, you'll need to specify which
2 | # package ecosystems to update and where the package manifests are located.
3 | # Please see the documentation for all configuration options:
4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
5 |
6 | version: 2
7 | updates:
8 | - package-ecosystem: "nuget" # See documentation for possible values
9 | directory: "/" # Location of package manifests
10 | schedule:
11 | interval: "daily"
12 | target-branch: "master"
13 | labels:
14 | - "dependency"
15 |
--------------------------------------------------------------------------------
/.github/workflows/pr-build-check.yml:
--------------------------------------------------------------------------------
1 | name: .NET
2 |
3 | on:
4 | pull_request:
5 | branches: [ "master" ]
6 |
7 | jobs:
8 | build:
9 | name: Build & Check
10 | runs-on: windows-latest
11 |
12 | steps:
13 | - uses: actions/checkout@v4
14 |
15 | # FIXME: Tools won't run in .NET 6.0 unless you install 3.1.x LTS side by side.
16 | # https://itnext.io/how-to-support-multiple-net-sdks-in-github-actions-workflows-b988daa884e
17 | - name: Install .NET 3.1.x LTS
18 | uses: actions/setup-dotnet@v4
19 | with:
20 | dotnet-version: |
21 | 3.1.x
22 | 6.0.x
23 | 8.0.x
24 |
25 | - name: Restore Tools
26 | run: dotnet tool restore
27 |
28 | - name: Restore inspectcode cache
29 | uses: actions/cache@v4
30 | with:
31 | path: ${{ github.workspace }}/inspectcode
32 | key: inspectcode-${{ hashFiles('.config/dotnet-tools.json', '.github/workflows/pr-build-check.yml', 'src/MilkiBotFramework.sln*') }}
33 |
34 | - name: Restore dependencies
35 | run: dotnet restore ./src
36 |
37 | - name: Build
38 | run: dotnet build ./src --no-restore
39 |
40 | - name: InspectCode
41 | run: dotnet jb inspectcode ./src/MilkiBotFramework.sln --no-build --output="inspectcodereport.xml" --caches-home="inspectcode" --verbosity=WARN
42 |
43 | - name: NVika
44 | run: dotnet nvika parsereport "${{github.workspace}}/inspectcodereport.xml" --treatwarningsaserrors
45 |
46 |
--------------------------------------------------------------------------------
/src/Benchmarks/CommandLineBenchmark/CommandLineBenchmark.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net8.0
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | PreserveNewest
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/src/Benchmarks/CommandLineBenchmark/Program.cs:
--------------------------------------------------------------------------------
1 | // ReSharper disable All
2 | #pragma warning disable CS1998
3 | #nullable disable
4 |
5 | using System;
6 | using BenchmarkDotNet.Attributes;
7 | using BenchmarkDotNet.Jobs;
8 | using BenchmarkDotNet.Order;
9 | using BenchmarkDotNet.Running;
10 | using MilkiBotFramework;
11 | using MilkiBotFramework.Plugining.CommandLine;
12 |
13 | namespace CommandLineBenchmark
14 | {
15 | internal class Program
16 | {
17 | static void Main(string[] args)
18 | {
19 | _ = BenchmarkRunner.Run();
20 | //var summary = BenchmarkRunner.Run();
21 | }
22 | }
23 |
24 | [Orderer(SummaryOrderPolicy.FastestToSlowest)]
25 | [MemoryDiagnoser]
26 | [SimpleJob(RuntimeMoniker.Net60)]
27 | public class GeneralTask
28 | {
29 | private string _command;
30 |
31 | [GlobalSetup]
32 | public void Setup()
33 | {
34 | _command = " test:1 -option [2] -wow -what 234 -hehe \"tt:t ttadfv\" 125 fdgdsahf \"114514 191980:\" -heihei:3 -sbsb ";
35 | }
36 |
37 | [Benchmark]
38 | [Obsolete("Obsolete")]
39 | public object OldAnalyzer()
40 | {
41 | var a = new StreamCommandLineAnalyzer();
42 | a.TryAnalyze(_command, out var result, out _);
43 | return result;
44 | }
45 |
46 | [Benchmark(Baseline = true)]
47 | public object NewAnalyzer()
48 | {
49 | var a = new CommandLineAnalyzer(new BotOptions());
50 | a.TryAnalyze(_command, out var result, out _);
51 | return result;
52 | }
53 |
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/Benchmarks/CommandLineBenchmark/UrlEncodeTest.cs:
--------------------------------------------------------------------------------
1 | #nullable disable
2 |
3 | using System;
4 | using System.Collections.Generic;
5 | using System.IO;
6 | using BenchmarkDotNet.Attributes;
7 | using BenchmarkDotNet.Jobs;
8 | using BenchmarkDotNet.Order;
9 | using MilkiBotFramework.Connecting;
10 | using MilkiBotFramework.Utils;
11 |
12 | namespace CommandLineBenchmark;
13 |
14 | [Orderer(SummaryOrderPolicy.FastestToSlowest)]
15 | [MemoryDiagnoser]
16 | [SimpleJob(RuntimeMoniker.Net60)]
17 | public class UrlEncodeTest
18 | {
19 | private Dictionary _lines;
20 |
21 | [GlobalSetup]
22 | public void Setup()
23 | {
24 | var lines = File.ReadAllLines("passwords.txt");
25 | var count = lines.Length - lines.Length % 2;
26 | var dic = new Dictionary();
27 | for (int i = 0; i < count; i += 2)
28 | {
29 | var line1 = lines[i];
30 | var line2 = lines[i + 1];
31 | dic.Add(line1, line2);
32 | }
33 |
34 | _lines = dic;
35 | }
36 |
37 | [Benchmark(Baseline = true)]
38 | public object New()
39 | {
40 | return LightHttpClient.BuildQueries(_lines);
41 | }
42 |
43 | [Benchmark]
44 | [Obsolete("Obsolete")]
45 | public object Old()
46 | {
47 | return _lines.ToUrlParamString();
48 | }
49 | }
--------------------------------------------------------------------------------
/src/Directory.Build.props:
--------------------------------------------------------------------------------
1 |
2 |
3 | 12.0
4 | enable
5 | 1.0
6 | v
7 |
8 |
--------------------------------------------------------------------------------
/src/Directory.Build.targets:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | all
5 | runtime; build; native; contentfiles; analyzers; buildtransitive
6 |
7 |
8 |
--------------------------------------------------------------------------------
/src/MilkiBotFramework.Aspnetcore/HttpMiddleware.cs:
--------------------------------------------------------------------------------
1 | using System.Text;
2 | using Microsoft.AspNetCore.Http;
3 | using Microsoft.Extensions.Logging;
4 | using MilkiBotFramework.Connecting;
5 | using MilkiBotFramework.Dispatching;
6 |
7 | namespace MilkiBotFramework.Aspnetcore;
8 |
9 | public class HttpMiddleware
10 | {
11 | private readonly RequestDelegate _next;
12 | private readonly IDispatcher _dispatcher;
13 | private readonly ILogger _logger;
14 | private readonly string _path;
15 |
16 | public HttpMiddleware(RequestDelegate next,
17 | IConnector connector,
18 | IDispatcher dispatcher,
19 | ILogger logger)
20 | {
21 | _next = next;
22 | _dispatcher = dispatcher;
23 | _logger = logger;
24 | _path = connector.BindingPath!;
25 | }
26 |
27 | public async Task InvokeAsync(HttpContext context)
28 | {
29 | if (context.Request.Path.Equals(_path, StringComparison.OrdinalIgnoreCase))
30 | {
31 | if (context.Request.Method.Equals("POST", StringComparison.OrdinalIgnoreCase))
32 | {
33 | // context.Request.EnableBuffering(); // context using several time the stream in ASP.Net Core
34 | using var reader = new StreamReader(context.Request.Body, Encoding.UTF8, true, 1024, true);
35 | var bodyStr = await reader.ReadToEndAsync();
36 | //_logger.LogDebug("!!!POST STR: " + bodyStr);
37 | try
38 | {
39 | await _dispatcher.InvokeRawMessageReceived(bodyStr);
40 | }
41 | catch (Exception ex)
42 | {
43 | _logger.LogError(ex, "Error occurs while executing dispatcher");
44 | }
45 | }
46 | else
47 | {
48 | context.Response.StatusCode = StatusCodes.Status405MethodNotAllowed;
49 | }
50 | }
51 | else
52 | {
53 | await _next(context);
54 | }
55 | }
56 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework.Aspnetcore/MilkiBotFramework.Aspnetcore.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Library
5 | net8.0
6 | enable
7 | enable
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | all
21 | runtime; build; native; contentfiles; analyzers; buildtransitive
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/src/MilkiBotFramework.Aspnetcore/ReverseWebsocketMiddleware.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Http;
2 | using MilkiBotFramework.Connecting;
3 |
4 | namespace MilkiBotFramework.Aspnetcore;
5 |
6 | public class ReverseWebSocketMiddleware
7 | {
8 | private readonly RequestDelegate _next;
9 | private readonly AspnetcoreConnector _connector;
10 |
11 | public ReverseWebSocketMiddleware(RequestDelegate next, IConnector connector)
12 | {
13 | _next = next;
14 | _connector = (AspnetcoreConnector)connector;
15 | }
16 |
17 | public async Task InvokeAsync(HttpContext context)
18 | {
19 | if (context.Request.Path.Equals(_connector.BindingPath, StringComparison.OrdinalIgnoreCase))
20 | {
21 | if (context.Request.Method.Equals("GET", StringComparison.OrdinalIgnoreCase))
22 | {
23 | if (context.WebSockets.IsWebSocketRequest)
24 | {
25 | var webSocket = await context.WebSockets.AcceptWebSocketAsync();
26 | await _connector.OnWebSocketOpen(webSocket);
27 | }
28 | else
29 | {
30 | context.Response.StatusCode = StatusCodes.Status400BadRequest;
31 | }
32 | }
33 | else
34 | {
35 | context.Response.StatusCode = StatusCodes.Status405MethodNotAllowed;
36 | }
37 | }
38 | else
39 | {
40 | await _next(context);
41 | }
42 | }
43 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework.Imaging.Avalonia/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.CompilerServices;
2 |
3 | [assembly: InternalsVisibleTo("AvaHeadlessTest")]
--------------------------------------------------------------------------------
/src/MilkiBotFramework.Imaging.Avalonia/AvaloniaOptions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading.Tasks;
3 | using Avalonia;
4 |
5 | namespace MilkiBotFramework.Imaging.Avalonia;
6 |
7 | public static class AvaloniaOptions
8 | {
9 | // ReSharper disable once InconsistentNaming
10 | public static Func? GetApplicationFunc;
11 | public static Func? CustomConfigureFunc;
12 |
13 | public static AppBuilder CustomConfigure(this AppBuilder builder)
14 | {
15 | return CustomConfigureFunc == null ? builder : CustomConfigureFunc(builder);
16 | }
17 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework.Imaging.Avalonia/DefaultApp.axaml:
--------------------------------------------------------------------------------
1 |
5 |
6 | Light
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/src/MilkiBotFramework.Imaging.Avalonia/DefaultApp.axaml.cs:
--------------------------------------------------------------------------------
1 | using System.Diagnostics;
2 | using System.Threading.Tasks;
3 | using Avalonia;
4 | using Avalonia.Controls;
5 | using Avalonia.Controls.ApplicationLifetimes;
6 | using Avalonia.Markup.Xaml;
7 | using Avalonia.Threading;
8 |
9 | namespace MilkiBotFramework.Imaging.Avalonia;
10 |
11 | // ReSharper disable once PartialTypeWithSinglePart
12 | public partial class DefaultApp : Application
13 | {
14 | private readonly TaskCompletionSource _setupFinished;
15 |
16 | public DefaultApp()
17 | {
18 | _setupFinished = new TaskCompletionSource();
19 | }
20 |
21 | public DefaultApp(TaskCompletionSource setupFinished)
22 | {
23 | _setupFinished = setupFinished;
24 | }
25 |
26 | public override void Initialize()
27 | {
28 | AvaloniaXamlLoader.Load(this);
29 | }
30 |
31 | public override void OnFrameworkInitializationCompleted()
32 | {
33 | if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
34 | {
35 | desktop.ShutdownMode = ShutdownMode.OnExplicitShutdown;
36 | desktop.Startup += Desktop_Startup;
37 | Dispatcher.UIThread.UnhandledException += UIThread_UnhandledException;
38 | }
39 |
40 | base.OnFrameworkInitializationCompleted();
41 | }
42 |
43 | private void Desktop_Startup(object? sender, ControlledApplicationLifetimeStartupEventArgs e)
44 | {
45 | _setupFinished.SetResult();
46 | }
47 |
48 | private void UIThread_UnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)
49 | {
50 | Debug.Fail("Exception on UI thread!", e.Exception.ToString());
51 | e.Handled = true;
52 | }
53 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework.Imaging.Avalonia/DpiDecorator.cs:
--------------------------------------------------------------------------------
1 | using Avalonia.Controls;
2 | using Avalonia.Interactivity;
3 | using Avalonia.Media;
4 |
5 | namespace MilkiBotFramework.Imaging.Avalonia;
6 |
7 | public class DpiDecorator : LayoutTransformControl
8 | {
9 | public DpiDecorator()
10 | {
11 | Loaded += OnLoaded;
12 | }
13 |
14 | private void OnLoaded(object? o, RoutedEventArgs routedEventArgs)
15 | {
16 | var topLevel = TopLevel.GetTopLevel(this);
17 | if (topLevel is not WindowBase window) return;
18 |
19 | var screenFromVisual = window.Screens.ScreenFromVisual(this);
20 | if (screenFromVisual == null) return;
21 |
22 | var scaling = screenFromVisual.Scaling;
23 | var dpiTransform = new ScaleTransform(1 / scaling, 1 / scaling);
24 | LayoutTransform = dpiTransform;
25 | }
26 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework.Imaging.Avalonia/Internal/Delegates.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading.Tasks;
3 |
4 | namespace MilkiBotFramework.Imaging.Avalonia.Internal;
5 |
6 | internal delegate Task RenderFinishDelegate(object? sender, EventArgs e);
--------------------------------------------------------------------------------
/src/MilkiBotFramework.Imaging.Avalonia/Internal/DrawingWindow.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading.Tasks;
3 | using Avalonia.Controls;
4 | using Avalonia.Interactivity;
5 | using Avalonia.Media;
6 |
7 | namespace MilkiBotFramework.Imaging.Avalonia.Internal;
8 |
9 | internal class DrawingWindow : Window
10 | {
11 | public DrawingWindow()
12 | {
13 | SizeToContent = SizeToContent.WidthAndHeight;
14 | //Width = 0;
15 | //Height = 0;
16 | //AllowsTransparency = true;
17 | //WindowStyle = WindowStyle.None;
18 | ShowInTaskbar = false;
19 | ShowActivated = false;
20 | //Opacity = 0;
21 | }
22 |
23 | public bool IsShown { get; private set; }
24 |
25 | ///
26 | /// 窗体显示事件
27 | ///
28 | public static readonly RoutedEvent ShownEvent =
29 | RoutedEvent.Register(
30 | nameof(Shown), RoutingStrategies.Tunnel | RoutingStrategies.Bubble);
31 |
32 | ///
33 | /// 当窗体显示时发生。
34 | ///
35 | public event EventHandler? Shown
36 | {
37 | add => AddHandler(ShownEvent, value);
38 | remove => RemoveHandler(ShownEvent, value);
39 | }
40 |
41 | public override void Render(DrawingContext context)
42 | {
43 | base.Render(context);
44 | if (IsShown)
45 | return;
46 |
47 | IsShown = true;
48 |
49 | var args = new RoutedEventArgs(ShownEvent, this);
50 | RaiseEvent(args);
51 | }
52 |
53 | public async Task WaitForShown()
54 | {
55 | if (IsShown) return;
56 | var tcs = new TaskCompletionSource();
57 | Shown += (_, _) => tcs.SetResult();
58 | if (IsShown) return;
59 | await tcs.Task;
60 | }
61 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework.Imaging.Avalonia/Internal/UiThreadHelper.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Diagnostics;
3 | using System.Threading;
4 | using System.Threading.Tasks;
5 | using Avalonia;
6 | using Avalonia.Headless;
7 | using MilkiBotFramework.Utils;
8 |
9 | namespace MilkiBotFramework.Imaging.Avalonia.Internal;
10 |
11 | internal static class UiThreadHelper
12 | {
13 | private static Thread? _uiThread;
14 | private static readonly AsyncLock AsyncLock = new();
15 | private static readonly TaskCompletionSource WaitComplete = new();
16 |
17 | public static async Task EnsureUiThreadAsync()
18 | {
19 | using (await AsyncLock.LockAsync())
20 | {
21 | if (_uiThread is { IsAlive: true })
22 | {
23 | return;
24 | }
25 |
26 | _uiThread = new Thread(() =>
27 | {
28 | var appBuilder = AppBuilder.Configure(() => AvaloniaOptions.GetApplicationFunc == null
29 | ? new DefaultApp(WaitComplete)
30 | : AvaloniaOptions.GetApplicationFunc(WaitComplete))
31 | .CustomConfigure()
32 | .UseSkia() // enable Skia renderer
33 | .UseHeadless(new AvaloniaHeadlessPlatformOptions
34 | {
35 | UseHeadlessDrawing = false // disable headless drawing
36 | });
37 |
38 | try
39 | {
40 | appBuilder.StartWithClassicDesktopLifetime(Array.Empty());
41 | }
42 | catch (Exception ex)
43 | {
44 | Debug.Fail("UI线程异常且不可恢复", ex.ToString());
45 | throw new Exception("UI thread shutdown.", ex);
46 | }
47 | })
48 | {
49 | IsBackground = true
50 | };
51 | if (OperatingSystem.IsWindows()) _uiThread.SetApartmentState(ApartmentState.STA);
52 | _uiThread.Start();
53 | await WaitComplete.Task;
54 | }
55 | }
56 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework.Imaging.Avalonia/LocaleComparer.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace MilkiBotFramework.Imaging.Avalonia;
4 |
5 | public class LocaleComparer : IComparer
6 | {
7 | private LocaleComparer()
8 | {
9 | }
10 |
11 | public static LocaleComparer Instance { get; } = new();
12 |
13 | public int Compare(string? x, string? y)
14 | {
15 | if (x == "en-US" && y == "en-US") return 0;
16 | if (x == "en-US") return -1;
17 | if (y == "en-US") return 1;
18 | return 0;
19 | }
20 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework.Imaging.Avalonia/MilkiBotFramework.Imaging.Avalonia.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | net8.0
4 | enable
5 | true
6 | app.manifest
7 | true
8 | true
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | DefaultApp.axaml
30 |
31 |
32 |
33 |
34 |
35 | all
36 | runtime; build; native; contentfiles; analyzers; buildtransitive
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/src/MilkiBotFramework.Imaging.Avalonia/app.manifest:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 |
9 |
10 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/src/MilkiBotFramework.Imaging.Wpf/DpiDecorator.cs:
--------------------------------------------------------------------------------
1 | using System.Windows;
2 | using System.Windows.Controls;
3 | using System.Windows.Media;
4 |
5 | namespace MilkiBotFramework.Imaging.Wpf;
6 |
7 | public class DpiDecorator : Decorator
8 | {
9 | public DpiDecorator()
10 | {
11 | Loaded += OnLoaded;
12 | }
13 |
14 | private void OnLoaded(object o, RoutedEventArgs routedEventArgs)
15 | {
16 | var presentationSource = PresentationSource.FromVisual(this);
17 | if (presentationSource?.CompositionTarget == null) return;
18 |
19 | var matrix = presentationSource.CompositionTarget.TransformToDevice;
20 | var dpiTransform = new ScaleTransform(1 / matrix.M11, 1 / matrix.M22);
21 |
22 | if (dpiTransform.CanFreeze) dpiTransform.Freeze();
23 |
24 | LayoutTransform = dpiTransform;
25 | }
26 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework.Imaging.Wpf/Internal/Delegates.cs:
--------------------------------------------------------------------------------
1 | namespace MilkiBotFramework.Imaging.Wpf.Internal;
2 |
3 | internal delegate Task RenderFinishDelegate(object? sender, EventArgs e);
--------------------------------------------------------------------------------
/src/MilkiBotFramework.Imaging.Wpf/Internal/HiddenWindow.cs:
--------------------------------------------------------------------------------
1 | using System.Windows;
2 | using System.Windows.Interop;
3 |
4 | namespace MilkiBotFramework.Imaging.Wpf.Internal;
5 |
6 | internal class HiddenWindow : Window
7 | {
8 | public HiddenWindow()
9 | {
10 | SizeToContent = SizeToContent.WidthAndHeight;
11 | Width = 0;
12 | Height = 0;
13 | AllowsTransparency = true;
14 | WindowStyle = WindowStyle.None;
15 | ShowInTaskbar = false;
16 | ShowActivated = false;
17 | Opacity = 0;
18 | }
19 |
20 | public bool IsShown { get; private set; }
21 |
22 | ///
23 | /// 窗体显示事件
24 | ///
25 | public static readonly RoutedEvent ShownEvent = EventManager.RegisterRoutedEvent
26 | ("Shown", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(HiddenWindow));
27 |
28 | ///
29 | /// 当窗体显示时发生。
30 | ///
31 | public event RoutedEventHandler Shown
32 | {
33 | add => AddHandler(ShownEvent, value);
34 | remove => RemoveHandler(ShownEvent, value);
35 | }
36 |
37 | protected sealed override void OnContentRendered(EventArgs e)
38 | {
39 | base.OnContentRendered(e);
40 |
41 | if (IsShown)
42 | return;
43 |
44 | IsShown = true;
45 |
46 | var args = new RoutedEventArgs(ShownEvent, this);
47 | RaiseEvent(args);
48 | }
49 |
50 | public async Task WaitForShown()
51 | {
52 | if (IsShown) return;
53 | var tcs = new TaskCompletionSource();
54 | Shown += (_, _) => tcs.SetResult();
55 | if (IsShown) return;
56 | await tcs.Task;
57 | }
58 |
59 | protected override void OnSourceInitialized(EventArgs e)
60 | {
61 | base.OnSourceInitialized(e);
62 |
63 | var source = PresentationSource.FromVisual(this) as HwndSource;
64 | source?.AddHook(WndProc);
65 | }
66 |
67 | // ReSharper disable once InconsistentNaming
68 | // ReSharper disable once IdentifierTypo
69 | private const int WM_DPICHANGED = 0x02E0;
70 |
71 | private static IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
72 | {
73 | if (msg == WM_DPICHANGED)
74 | {
75 | handled = true;
76 | }
77 |
78 | return IntPtr.Zero;
79 | }
80 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework.Imaging.Wpf/Internal/UiThreadHelper.cs:
--------------------------------------------------------------------------------
1 | using System.Diagnostics;
2 | using System.Windows;
3 | using MilkiBotFramework.Utils;
4 |
5 | namespace MilkiBotFramework.Imaging.Wpf.Internal;
6 |
7 | public static class UiThreadHelper
8 | {
9 | private static Thread? _uiThread;
10 | internal static Application? Application;
11 | private static readonly AsyncLock AsyncLock = new();
12 | private static readonly TaskCompletionSource WaitComplete = new();
13 |
14 | // ReSharper disable once InconsistentNaming
15 | public static Func? GetApplicationFunc;
16 |
17 | public static async Task EnsureUiThreadAsync()
18 | {
19 | using (await AsyncLock.LockAsync())
20 | {
21 | if (_uiThread is { IsAlive: true })
22 | {
23 | return;
24 | }
25 |
26 | _uiThread = new Thread(() =>
27 | {
28 | Application = GetApplicationFunc == null ? new Application() : GetApplicationFunc();
29 | Application.ShutdownMode = ShutdownMode.OnExplicitShutdown;
30 | Application.Startup += (_, _) => WaitComplete.SetResult(true);
31 | Application.DispatcherUnhandledException += (_, e) =>
32 | {
33 | Debug.Fail("UI线程异常", e.Exception.ToString());
34 | e.Handled = true;
35 | };
36 |
37 | for (int i = 0; i < 10; i++)
38 | {
39 | try
40 | {
41 | Application.Run();
42 | return;
43 | }
44 | catch
45 | {
46 | Application.Shutdown();
47 | }
48 | }
49 |
50 | throw new Exception("UI thread failing for too much times.");
51 | })
52 | {
53 | IsBackground = true
54 | };
55 | _uiThread.SetApartmentState(ApartmentState.STA);
56 | _uiThread.Start();
57 | await WaitComplete.Task;
58 | }
59 | }
60 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework.Imaging.Wpf/MilkiBotFramework.Imaging.Wpf.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net8.0-windows
5 | true
6 | enable
7 | enable
8 | true
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | all
18 | runtime; build; native; contentfiles; analyzers; buildtransitive
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/src/MilkiBotFramework.Imaging.Wpf/WpfImageHelper.cs:
--------------------------------------------------------------------------------
1 | using System.IO;
2 | using System.Windows.Media.Imaging;
3 | using SixLabors.ImageSharp;
4 | using SixLabors.ImageSharp.Formats.Png;
5 |
6 | namespace MilkiBotFramework.Imaging.Wpf;
7 |
8 | internal static class WpfImageHelper
9 | {
10 | public static BitmapSource GetBitmapImageFromImageSharp(Image image)
11 | {
12 | using var ms = new MemoryStream(); // todo: using?
13 | image.Save(ms, new PngEncoder());
14 |
15 | var bitmapSource = new BitmapImage();
16 | bitmapSource.BeginInit();
17 | bitmapSource.StreamSource = ms;
18 | bitmapSource.EndInit();
19 |
20 | //freeze bitmapSource and clear memory to avoid memory leaks
21 | bitmapSource.Freeze();
22 | return bitmapSource;
23 | }
24 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework/BotBuilder.cs:
--------------------------------------------------------------------------------
1 | namespace MilkiBotFramework;
2 |
3 | public sealed class BotBuilder : BotBuilderBase
4 | {
5 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework/BotOptions.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel;
2 | using MilkiBotFramework.Connecting;
3 | using MilkiBotFramework.Plugining.Configuration;
4 |
5 | namespace MilkiBotFramework;
6 |
7 | public class BotOptions : ConfigurationBase
8 | {
9 | private LightHttpClientCreationOptions? _httpOptions;
10 |
11 | public LightHttpClientCreationOptions HttpOptions
12 | {
13 | get => _httpOptions ?? new LightHttpClientCreationOptions();
14 | set => _httpOptions = value;
15 | }
16 |
17 | [Description("自定义变量,支持在插件的 [DescriptionAttribute] 和 IResponse(Text) 中进行替换")]
18 | public Dictionary Variables { get; set; } = new()
19 | {
20 | ["BotCode"] = "MilkiBot",
21 | ["BotNick"] = "MilkiBot",
22 | };
23 |
24 | [Description("命令触发前缀")]
25 | public char CommandFlag { get; set; } = '/';
26 |
27 | [Description("Root权限账号")]
28 | // ReSharper disable once CollectionNeverUpdated.Global
29 | public HashSet RootAccounts { get; set; } = new();
30 |
31 | [Description("插件目录")]
32 | public string PluginBaseDir { get; set; } = "./plugins";
33 |
34 | [Description("插件资源目录")]
35 | public string PluginDataDir { get; set; } = "./data";
36 |
37 | [Description("插件资源目录是否使用Guid")]
38 | public bool PluginDataUseGuid { get; set; }
39 |
40 | [Description("插件数据库目录")]
41 | public string PluginDatabaseDir { get; set; } = "./databases";
42 |
43 | [Description("插件配置目录")]
44 | public string PluginConfigurationDir { get; set; } = "./configurations";
45 |
46 | [Description("缓存图片目录")]
47 | public string CacheImageDir { get; set; } = "./caches/images";
48 |
49 | [Description("gifsicle插件位置")]
50 | public string? GifSiclePath { get; set; }
51 |
52 | [Description("ffmpeg插件位置")]
53 | public string? FfMpegPath { get; set; }
54 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework/ConfigLoggerProvider.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Extensions.Logging;
2 |
3 | namespace MilkiBotFramework;
4 |
5 | public class ConfigLoggerProvider
6 | {
7 | public Action ConfigureLogger { get; }
8 |
9 | public ConfigLoggerProvider(Action configureLogger)
10 | {
11 | ConfigureLogger = configureLogger;
12 | }
13 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework/Connecting/ConnectionType.cs:
--------------------------------------------------------------------------------
1 | namespace MilkiBotFramework.Connecting;
2 |
3 | public enum ConnectionType
4 | {
5 | Http, WebSocket, ReverseWebSocket
6 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework/Connecting/IConnector.cs:
--------------------------------------------------------------------------------
1 | namespace MilkiBotFramework.Connecting;
2 |
3 | public interface IConnector : IConnectorConfigurable
4 | {
5 | event Func? RawMessageReceived;
6 | Task ConnectAsync();
7 | Task DisconnectAsync();
8 | Task SendMessageAsync(string message, string state);
9 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework/Connecting/IConnectorConfigurable.cs:
--------------------------------------------------------------------------------
1 | using System.Text;
2 |
3 | namespace MilkiBotFramework.Connecting;
4 |
5 | public interface IConnectorConfigurable
6 | {
7 | public ConnectionType ConnectionType { get; set; }
8 | public string? TargetUri { get; set; }
9 | public string? BindingPath { get; set; }
10 | public TimeSpan ErrorReconnectTimeout { get; set; }
11 |
12 | ///
13 | /// 消息超时时间。
14 | /// 对于一些长消息超时的情况,请适量增大此值。
15 | ///
16 | public TimeSpan MessageTimeout { get; set; }
17 |
18 | public Encoding? Encoding { get; set; }
19 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework/Connecting/IMessageApi.cs:
--------------------------------------------------------------------------------
1 | using MilkiBotFramework.Messaging;
2 | using MilkiBotFramework.Messaging.RichMessages;
3 |
4 | namespace MilkiBotFramework.Connecting;
5 |
6 | public interface IMessageApi
7 | {
8 | Task SendPrivateMessageAsync(string userId, string message, IRichMessage? richMessage, MessageContext messageContext);
9 | Task SendChannelMessageAsync(string channelId, string message, IRichMessage? richMessage, MessageContext messageContext, string? subChannelId);
10 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework/Connecting/IWebSocketConnector.cs:
--------------------------------------------------------------------------------
1 | namespace MilkiBotFramework.Connecting;
2 |
3 | public interface IWebSocketConnector : IConnector
4 | {
5 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework/Connecting/LightHttpClientCreationOptions.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel;
2 |
3 | namespace MilkiBotFramework.Connecting;
4 |
5 | public sealed class LightHttpClientCreationOptions
6 | {
7 | [Description("代理服务器地址")]
8 | public string? ProxyUrl { get; set; }
9 |
10 | [Description("默认超时")]
11 | public TimeSpan Timeout { get; set; } = TimeSpan.FromSeconds(8);
12 |
13 | [Description("自动重试次数")]
14 | public int RetryCount { get; set; } = 3;
15 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework/Connecting/RequestContext.cs:
--------------------------------------------------------------------------------
1 | namespace MilkiBotFramework.Connecting;
2 |
3 | internal class RequestContext
4 | {
5 | public string RequestUri { get; set; }
6 |
7 | public RequestContext(string requestUri)
8 | {
9 | RequestUri = requestUri;
10 | }
11 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework/Connecting/StandardIoConnector.cs:
--------------------------------------------------------------------------------
1 | using System.Text;
2 | using Microsoft.Extensions.Logging;
3 | using MilkiBotFramework.Utils;
4 |
5 | namespace MilkiBotFramework.Connecting
6 | {
7 | internal class StandardIoConnector : IConnector, IDisposable
8 | {
9 | public event Func? RawMessageReceived;
10 | private readonly ILogger _logger;
11 | private readonly AsyncLock _singletonIoLock = new();
12 | private bool _enable;
13 |
14 | public StandardIoConnector(ILogger logger)
15 | {
16 | _logger = logger;
17 | _logger.LogInformation("StandardIoConnector for debugging usage!");
18 | }
19 |
20 | public ConnectionType ConnectionType { get; set; }
21 | public string? TargetUri { get; set; }
22 | public string? BindingPath { get; set; }
23 | public TimeSpan ErrorReconnectTimeout { get; set; }
24 | public TimeSpan MessageTimeout { get; set; }
25 | public Encoding? Encoding { get; set; }
26 |
27 | public Task ConnectAsync()
28 | {
29 | _enable = true;
30 | return Task.CompletedTask;
31 | }
32 |
33 | public Task DisconnectAsync()
34 | {
35 | _enable = false;
36 | return Task.CompletedTask;
37 | }
38 |
39 | public async Task SendMessageAsync(string message, string state)
40 | {
41 | if (!_enable) throw new Exception("StandardIo is not ready. Try to connect before sending message.");
42 | using (await _singletonIoLock.LockAsync())
43 | {
44 | Console.Write("Received request: \r\n" + message + "\r\nEnter single-lined response:");
45 | var response = await Console.In.ReadLineAsync();
46 | return response ?? "";
47 | }
48 | }
49 |
50 | public void Dispose()
51 | {
52 | _singletonIoLock.Dispose();
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/MilkiBotFramework/Connecting/WebSocketAsyncMessage.cs:
--------------------------------------------------------------------------------
1 | namespace MilkiBotFramework.Connecting;
2 |
3 | public class WebSocketAsyncMessage
4 | {
5 | public WebSocketAsyncMessage(string message)
6 | {
7 | Message = message;
8 | }
9 |
10 | public string Message { get; set; }
11 | public bool IsHandled { get; set; }
12 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework/Connecting/WebSocketMessageSession.cs:
--------------------------------------------------------------------------------
1 | namespace MilkiBotFramework.Connecting;
2 |
3 | public sealed class WebSocketMessageSession
4 | {
5 | public TaskCompletionSource TaskCompletionSource { get; set; }
6 | public string? Response { get; set; }
7 |
8 | public WebSocketMessageSession(TaskCompletionSource taskCompletionSource)
9 | {
10 | TaskCompletionSource = taskCompletionSource;
11 | }
12 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework/ContactsManaging/ContactsManagerExtensions.cs:
--------------------------------------------------------------------------------
1 | using MilkiBotFramework.Messaging;
2 |
3 | namespace MilkiBotFramework.ContactsManaging;
4 |
5 | public static class ContactsManagerExtensions
6 | {
7 | public static async Task GetIdentityName(this IContactsManager contactsManager, MessageIdentity messageIdentity)
8 | {
9 | if (messageIdentity.MessageType == MessageType.Private)
10 | {
11 | return (await contactsManager.TryGetOrAddPrivateInfo(messageIdentity.Id!)).PrivateInfo?.Nickname;
12 | }
13 |
14 | if (messageIdentity.MessageType == MessageType.Channel)
15 | {
16 | return (await contactsManager.TryGetOrAddChannelInfo(messageIdentity.Id!, messageIdentity.SubId)).ChannelInfo?.Name;
17 | }
18 |
19 | return null;
20 | }
21 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework/ContactsManaging/ContactsUpdateEvent.cs:
--------------------------------------------------------------------------------
1 | using MilkiBotFramework.Event;
2 |
3 | namespace MilkiBotFramework.ContactsManaging;
4 |
5 | public sealed class ContactsUpdateEvent : IEventBusEvent
6 | {
7 | public IReadOnlyList Events { get; init; } = Array.Empty();
8 |
9 | public static explicit operator ContactsUpdateEvent(ContactsUpdateSingleEvent single)
10 | {
11 | return new ContactsUpdateEvent
12 | {
13 | Events = new[] { single }
14 | };
15 | }
16 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework/ContactsManaging/ContactsUpdateSingleEvent.cs:
--------------------------------------------------------------------------------
1 | using System.Diagnostics;
2 | using System.Text;
3 | using MilkiBotFramework.ContactsManaging.Models;
4 | using MilkiBotFramework.ContactsManaging.Results;
5 |
6 | namespace MilkiBotFramework.ContactsManaging;
7 |
8 | [DebuggerDisplay("{DebuggerDisplay}")]
9 | public sealed class ContactsUpdateSingleEvent
10 | {
11 | public string? ChangedPath { get; init; }
12 | public MemberInfo? MemberInfo { get; init; }
13 | public PrivateInfo? PrivateInfo { get; init; }
14 | public ChannelInfo? ChannelInfo { get; init; }
15 | public ChannelInfo? SubChannelInfo { get; init; }
16 | public ContactsUpdateRole UpdateRole { get; init; }
17 | public ContactsUpdateType UpdateType { get; init; }
18 |
19 | private string DebuggerDisplay
20 | {
21 | get
22 | {
23 | var sb = new StringBuilder($"Role={UpdateRole};Type={UpdateType};");
24 | switch (UpdateRole)
25 | {
26 | case ContactsUpdateRole.Channel:
27 | sb.Append($"Id={ChannelInfo!.ChannelId}");
28 | break;
29 | case ContactsUpdateRole.SubChannel:
30 | sb.Append($"Id={ChannelInfo!.ChannelId}.{ChannelInfo.SubChannelId}");
31 | break;
32 | case ContactsUpdateRole.Member:
33 | sb.Append($"Id={MemberInfo!.ChannelId}.{MemberInfo.UserId}");
34 | break;
35 | case ContactsUpdateRole.Private:
36 | sb.Append($"Id={PrivateInfo!.UserId}");
37 | break;
38 | default:
39 | throw new ArgumentOutOfRangeException();
40 | }
41 |
42 | return sb.ToString();
43 | }
44 | }
45 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework/ContactsManaging/IContactsManager.cs:
--------------------------------------------------------------------------------
1 | using MilkiBotFramework.ContactsManaging.Models;
2 | using MilkiBotFramework.ContactsManaging.Results;
3 |
4 | namespace MilkiBotFramework.ContactsManaging;
5 |
6 | public interface IContactsManager
7 | {
8 | void InitializeTasks();
9 | Task TryGetOrUpdateSelfInfo();
10 | Task TryGetOrAddMemberInfo(string channelId, string userId, string? subChannelId = null);
11 | Task TryGetOrAddChannelInfo(string channelId, string? subChannelId = null);
12 | Task TryGetOrAddPrivateInfo(string userId);
13 | IEnumerable GetAllChannels();
14 | IEnumerable GetAllMembers(string channelId, string? subChannelId = null);
15 | IEnumerable GetAllPrivates();
16 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework/ContactsManaging/Models/Avatar.cs:
--------------------------------------------------------------------------------
1 | namespace MilkiBotFramework.ContactsManaging.Models;
2 |
3 | public sealed class Avatar
4 | {
5 | public string? AvatarThumbPath { get; set; }
6 | public string? AvatarPath { get; set; }
7 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework/ContactsManaging/Models/ChannelInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Concurrent;
2 |
3 | namespace MilkiBotFramework.ContactsManaging.Models;
4 |
5 | public sealed class ChannelInfo
6 | {
7 | public ChannelInfo(string channelId, IEnumerable? members = null)
8 | {
9 | ChannelId = channelId;
10 | if (members != null)
11 | Members = new ConcurrentDictionary(
12 | members.ToDictionary(k => k.UserId, k => k)
13 | );
14 | }
15 |
16 | public string ChannelId { get; }
17 | public string? SubChannelId { get; set; }
18 | public string? Name { get; set; }
19 | public Avatar? Avatar { get; set; }
20 |
21 | public ConcurrentDictionary Members { get; } = new();
22 | public bool IsRootChannel => SubChannelId == null;
23 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework/ContactsManaging/Models/MemberInfo.cs:
--------------------------------------------------------------------------------
1 | namespace MilkiBotFramework.ContactsManaging.Models;
2 |
3 | public sealed class MemberInfo
4 | {
5 | public MemberInfo(string channelId, string userId, string? subChannelId)
6 | {
7 | ChannelId = channelId;
8 | UserId = userId;
9 | SubChannelId = subChannelId;
10 | }
11 |
12 | public string ChannelId { get; }
13 | public string UserId { get; }
14 | public string? SubChannelId { get; }
15 | public string? Card { get; set; }
16 | public string? Nickname { get; set; }
17 | public MemberRole MemberRole { get; set; }
18 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework/ContactsManaging/Models/MemberRole.cs:
--------------------------------------------------------------------------------
1 | namespace MilkiBotFramework.ContactsManaging.Models;
2 |
3 | public enum MemberRole
4 | {
5 | Owner, Admin, SubAdmin, Member
6 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework/ContactsManaging/Models/PrivateInfo.cs:
--------------------------------------------------------------------------------
1 | namespace MilkiBotFramework.ContactsManaging.Models;
2 |
3 | public sealed class PrivateInfo
4 | {
5 | public PrivateInfo(string userId)
6 | {
7 | UserId = userId;
8 | }
9 |
10 | public string UserId { get; }
11 | public string? Remark { get; set; }
12 | public string? Nickname { get; set; }
13 | public Avatar? Avatar { get; set; }
14 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework/ContactsManaging/Models/SelfInfo.cs:
--------------------------------------------------------------------------------
1 | namespace MilkiBotFramework.ContactsManaging.Models;
2 |
3 | public sealed class SelfInfo
4 | {
5 | public string UserId { get; init; } = null!;
6 | public string? Nickname { get; init; }
7 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework/ContactsManaging/Results/ChannelInfoResult.cs:
--------------------------------------------------------------------------------
1 | using MilkiBotFramework.ContactsManaging.Models;
2 |
3 | namespace MilkiBotFramework.ContactsManaging.Results;
4 |
5 | public sealed class ChannelInfoResult : ResultInfoBase
6 | {
7 | public ChannelInfo? ChannelInfo { get; init; }
8 | public static ChannelInfoResult Fail { get; } = new();
9 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework/ContactsManaging/Results/ContactsUpdateInfo.cs:
--------------------------------------------------------------------------------
1 | using MilkiBotFramework.ContactsManaging.Models;
2 |
3 | namespace MilkiBotFramework.ContactsManaging.Results;
4 |
5 | public sealed class ContactsUpdateInfo
6 | {
7 | public ContactsUpdateInfo(string id)
8 | {
9 | Id = id;
10 | }
11 |
12 | public string Id { get; }
13 | public string? SubId { get; init; }
14 | public string? UserId { get; init; }
15 | public string? Remark { get; init; }
16 | public string? Name { get; init; }
17 | public ContactsUpdateRole ContactsUpdateRole { get; init; }
18 | public ContactsUpdateType ContactsUpdateType { get; init; }
19 | public MemberRole? MemberRole { get; set; }
20 | public IEnumerable? Members { get; set; }
21 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework/ContactsManaging/Results/ContactsUpdateRole.cs:
--------------------------------------------------------------------------------
1 | namespace MilkiBotFramework.ContactsManaging.Results;
2 |
3 | public enum ContactsUpdateRole
4 | {
5 | Channel, SubChannel, Member, Private
6 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework/ContactsManaging/Results/ContactsUpdateType.cs:
--------------------------------------------------------------------------------
1 | namespace MilkiBotFramework.ContactsManaging.Results;
2 |
3 | public enum ContactsUpdateType
4 | {
5 | Changed, Added, Removed
6 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework/ContactsManaging/Results/MemberInfoResult.cs:
--------------------------------------------------------------------------------
1 | using MilkiBotFramework.ContactsManaging.Models;
2 |
3 | namespace MilkiBotFramework.ContactsManaging.Results;
4 |
5 | public sealed class MemberInfoResult : ResultInfoBase
6 | {
7 | public MemberInfo? MemberInfo { get; init; }
8 |
9 | public static MemberInfoResult Fail { get; } = new();
10 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework/ContactsManaging/Results/PrivateInfoResult.cs:
--------------------------------------------------------------------------------
1 | using MilkiBotFramework.ContactsManaging.Models;
2 |
3 | namespace MilkiBotFramework.ContactsManaging.Results;
4 |
5 | public sealed class PrivateInfoResult : ResultInfoBase
6 | {
7 | public PrivateInfo? PrivateInfo { get; init; }
8 | public static PrivateInfoResult Fail { get; } = new();
9 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework/ContactsManaging/Results/ResultInfoBase.cs:
--------------------------------------------------------------------------------
1 | namespace MilkiBotFramework.ContactsManaging.Results;
2 |
3 | public abstract class ResultInfoBase
4 | {
5 | public bool IsSuccess { get; init; }
6 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework/ContactsManaging/Results/SelfInfoResult.cs:
--------------------------------------------------------------------------------
1 | using MilkiBotFramework.ContactsManaging.Models;
2 |
3 | namespace MilkiBotFramework.ContactsManaging.Results;
4 |
5 | public sealed class SelfInfoResult : ResultInfoBase
6 | {
7 | public SelfInfo? SelfInfo { get; init; }
8 | public static SelfInfoResult Fail { get; } = new();
9 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework/Data/IInvokableViewModel.cs:
--------------------------------------------------------------------------------
1 | namespace MilkiBotFramework.Data;
2 |
3 | public interface IInvokableViewModel
4 | {
5 | internal void RaisePropertyChanged(string propertyName);
6 | internal void RaisePropertyChanging(string propertyName);
7 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework/Data/InvokableViewModelExtensions.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.CompilerServices;
2 |
3 | namespace MilkiBotFramework.Data;
4 |
5 | public static class InvokableViewModelExtensions
6 | {
7 | public static TRet RaiseAndSetIfChanged(
8 | this TObj reactiveObject,
9 | ref TRet backingField,
10 | TRet newValue,
11 | [CallerMemberName] string? propertyName = null, params string[] additionalChangedMembers)
12 | where TObj : IInvokableViewModel
13 | {
14 | if (propertyName is null)
15 | {
16 | throw new ArgumentNullException(nameof(propertyName));
17 | }
18 |
19 | if (EqualityComparer.Default.Equals(backingField, newValue))
20 | {
21 | return newValue;
22 | }
23 |
24 | reactiveObject.RaisePropertyChanging(propertyName);
25 | backingField = newValue;
26 | reactiveObject.RaisePropertyChanged(propertyName);
27 | foreach (var member in additionalChangedMembers)
28 | {
29 | reactiveObject.RaisePropertyChanged(member);
30 | }
31 |
32 | return newValue;
33 | }
34 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework/Data/ViewModelBase.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel;
2 | using System.Runtime.CompilerServices;
3 |
4 | namespace MilkiBotFramework.Data;
5 |
6 | ///
7 | /// ViewModel基础类
8 | ///
9 | public abstract class ViewModelBase : IInvokableViewModel, INotifyPropertyChanged, INotifyPropertyChanging
10 | {
11 | ///
12 | public event PropertyChangedEventHandler? PropertyChanged;
13 |
14 | ///
15 | public event PropertyChangingEventHandler? PropertyChanging;
16 |
17 | protected bool SetField(ref T field, T value, [CallerMemberName] string? propertyName = null)
18 | {
19 | if (EqualityComparer.Default.Equals(field, value)) return false;
20 | field = value;
21 | OnPropertyChanged(propertyName);
22 | return true;
23 | }
24 |
25 | ///
26 | /// 通知UI更新操作
27 | ///
28 | /// 属性名称
29 | protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
30 | {
31 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
32 | }
33 |
34 | void IInvokableViewModel.RaisePropertyChanged(string propertyName)
35 | {
36 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
37 | }
38 |
39 | void IInvokableViewModel.RaisePropertyChanging(string propertyName)
40 | {
41 | PropertyChanging?.Invoke(this, new PropertyChangingEventArgs(propertyName));
42 | }
43 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework/Dispatching/DispatchMessageEvent.cs:
--------------------------------------------------------------------------------
1 | using MilkiBotFramework.Event;
2 | using MilkiBotFramework.Messaging;
3 |
4 | namespace MilkiBotFramework.Dispatching;
5 |
6 | public sealed class DispatchMessageEvent : IEventBusEvent
7 | {
8 | public DispatchMessageEvent(MessageContext messageContext, MessageType messageType)
9 | {
10 | MessageContext = messageContext;
11 | MessageType = messageType;
12 | }
13 |
14 | public MessageContext MessageContext { get; }
15 | public MessageType MessageType { get; }
16 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework/Dispatching/IDispatcher.cs:
--------------------------------------------------------------------------------
1 | namespace MilkiBotFramework.Dispatching
2 | {
3 | public interface IDispatcher
4 | {
5 | Task InvokeRawMessageReceived(string rawMessage);
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/MilkiBotFramework/Event/IEventBusEvent.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json;
2 |
3 | namespace MilkiBotFramework.Event
4 | {
5 | ///
6 | /// 空接口限制,以增加维护性
7 | ///
8 | public interface IEventBusEvent
9 | {
10 | // ReSharper disable once ReturnTypeCanBeNotNullable
11 | public string? ToString() => JsonSerializer.Serialize(this);
12 | }
13 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework/FrameworkConstants.cs:
--------------------------------------------------------------------------------
1 | namespace MilkiBotFramework
2 | {
3 | public static class FrameworkConstants
4 | {
5 | public const int MaxStackArrayLength = 256;
6 | public const int ManualExitCode = 1;
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/src/MilkiBotFramework/Imaging/GifFrame.cs:
--------------------------------------------------------------------------------
1 | using SixLabors.ImageSharp;
2 |
3 | namespace MilkiBotFramework.Imaging;
4 |
5 | public sealed class GifFrame
6 | {
7 | public readonly Image Image;
8 |
9 | public GifFrame(Image image, TimeSpan delay)
10 | {
11 | Image = image;
12 | Delay = delay;
13 | }
14 |
15 | public TimeSpan Delay { get; set; }
16 |
17 | public void Deconstruct(out Image image, out TimeSpan delay)
18 | {
19 | image = Image;
20 | delay = Delay;
21 | }
22 |
23 | public override string ToString()
24 | {
25 | return Delay.TotalMilliseconds / 10 + ": " + Image;
26 | }
27 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework/Imaging/GifProcessOptions.cs:
--------------------------------------------------------------------------------
1 | using SixLabors.ImageSharp;
2 |
3 | namespace MilkiBotFramework.Imaging;
4 |
5 | public sealed class GifProcessOptions
6 | {
7 | public readonly TimeSpan Interval;
8 | public readonly bool Repeat;
9 | public readonly Image? Image;
10 |
11 | public GifProcessOptions(TimeSpan interval, Image? image = null, bool repeat = true)
12 | {
13 | Interval = interval;
14 | Image = image;
15 | Repeat = repeat;
16 | }
17 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework/Imaging/IDrawingProcessor.cs:
--------------------------------------------------------------------------------
1 | using SixLabors.ImageSharp;
2 |
3 | namespace MilkiBotFramework.Imaging;
4 |
5 | public interface IDrawingProcessor where TViewModel : class
6 | {
7 | Task ProcessAsync(TViewModel viewModel, string locale = "en-US", Image? sourceImage = null);
8 | Task ProcessGifAsync(TViewModel viewModel, TimeSpan interval, string locale = "en-US", Image? sourceImage = null, bool repeat = true);
9 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework/Imaging/ImageType.cs:
--------------------------------------------------------------------------------
1 | namespace MilkiBotFramework.Imaging;
2 |
3 | public enum ImageType
4 | {
5 | Unknown,
6 | Jpeg,
7 | Bmp,
8 | Gif,
9 | Png,
10 | //Pdf
11 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework/Messaging/AsyncMessage.cs:
--------------------------------------------------------------------------------
1 | namespace MilkiBotFramework.Messaging;
2 |
3 | internal class AsyncMessage : IAsyncMessage
4 | {
5 | private readonly object _lock = new();
6 | private TaskCompletionSource? _taskCompleteSource;
7 | internal IAsyncMessageResponse? Response { get; set; }
8 |
9 | public void SetMessage(IAsyncMessageResponse message)
10 | {
11 | lock (_lock)
12 | {
13 | Response = message;
14 | }
15 |
16 | _taskCompleteSource?.TrySetResult();
17 | }
18 |
19 | public Task GetNextMessageAsync(int seconds = 10)
20 | {
21 | return GetNextMessageAsync(TimeSpan.FromSeconds(seconds));
22 | }
23 |
24 | public async Task GetNextMessageAsync(TimeSpan dueTime)
25 | {
26 | lock (_lock)
27 | {
28 | if (Response != null)
29 | {
30 | return Response;
31 | }
32 | }
33 |
34 | try
35 | {
36 | if (_taskCompleteSource == null)
37 | {
38 | using var cts = new CancellationTokenSource(dueTime);
39 | _taskCompleteSource = new TaskCompletionSource();
40 | cts.Token.Register(() => _taskCompleteSource.TrySetCanceled());
41 | await _taskCompleteSource.Task;
42 | }
43 | else
44 | {
45 | await _taskCompleteSource.Task;
46 | }
47 | }
48 | catch (TaskCanceledException)
49 | {
50 | throw new AsyncMessageTimeoutException("Async message timeout after " + dueTime.TotalSeconds + "s");
51 | }
52 |
53 | return Response!;
54 | }
55 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework/Messaging/AsyncMessageResponse.cs:
--------------------------------------------------------------------------------
1 | using MilkiBotFramework.Messaging.RichMessages;
2 | using MilkiBotFramework.Plugining.CommandLine;
3 |
4 | namespace MilkiBotFramework.Messaging;
5 |
6 | internal class AsyncMessageResponse : IAsyncMessageResponse
7 | {
8 | private readonly Func _getRichDelegate;
9 | private readonly Func _getCommandLineDelegate;
10 |
11 | public AsyncMessageResponse(string messageId,
12 | string textMessage,
13 | DateTimeOffset receivedTime,
14 | Func getRichDelegate,
15 | Func getCommandLineDelegate)
16 | {
17 | MessageId = messageId;
18 | TextMessage = textMessage;
19 | ReceivedTime = receivedTime;
20 | _getRichDelegate = getRichDelegate;
21 | _getCommandLineDelegate = getCommandLineDelegate;
22 | }
23 |
24 | public string MessageId { get; }
25 | public string TextMessage { get; }
26 | public DateTimeOffset ReceivedTime { get; }
27 | public RichMessage GetRichMessage() => _getRichDelegate(TextMessage);
28 | public CommandLineResult? GetCommandLineResult() => _getCommandLineDelegate(TextMessage);
29 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework/Messaging/AsyncMessageTimeoutException.cs:
--------------------------------------------------------------------------------
1 | namespace MilkiBotFramework.Messaging;
2 |
3 | public class AsyncMessageTimeoutException : MessageTimeoutException
4 | {
5 | public AsyncMessageTimeoutException(string message) : base(message)
6 | {
7 | }
8 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework/Messaging/DefaultRichMessageConverter.cs:
--------------------------------------------------------------------------------
1 | using MilkiBotFramework.Messaging.RichMessages;
2 |
3 | namespace MilkiBotFramework.Messaging;
4 |
5 | public class DefaultRichMessageConverter : IRichMessageConverter
6 | {
7 | public async ValueTask EncodeAsync(IRichMessage message)
8 | {
9 | return await message.EncodeAsync();
10 | }
11 |
12 | public RichMessage Decode(ReadOnlyMemory message)
13 | {
14 | return new RichMessage(new Text(message.ToString()));
15 | }
16 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework/Messaging/IAsyncMessage.cs:
--------------------------------------------------------------------------------
1 | namespace MilkiBotFramework.Messaging;
2 |
3 | public interface IAsyncMessage
4 | {
5 | Task GetNextMessageAsync(int seconds = 10);
6 | Task GetNextMessageAsync(TimeSpan dueTime);
7 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework/Messaging/IAsyncMessageResponse.cs:
--------------------------------------------------------------------------------
1 | using MilkiBotFramework.Messaging.RichMessages;
2 | using MilkiBotFramework.Plugining.CommandLine;
3 |
4 | namespace MilkiBotFramework.Messaging;
5 |
6 | public interface IAsyncMessageResponse
7 | {
8 | string MessageId { get; }
9 | string TextMessage { get; }
10 | DateTimeOffset ReceivedTime { get; }
11 | RichMessage GetRichMessage();
12 | CommandLineResult? GetCommandLineResult();
13 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework/Messaging/IResponse.cs:
--------------------------------------------------------------------------------
1 | using MilkiBotFramework.Messaging.RichMessages;
2 |
3 | namespace MilkiBotFramework.Messaging;
4 |
5 | public interface IResponse
6 | {
7 | string? Id { get; }
8 | string? SubId { get; }
9 | MessageType? MessageType { get; }
10 | IRichMessage? Message { get; set; }
11 | bool? TryReply { get; set; }
12 | bool IsHandled { get; set; }
13 | bool? IsForced { get; set; }
14 | string? TryAt { get; set; }
15 | IAsyncMessage? AsyncMessage { get; }
16 | MessageContext? MessageContext { get; }
17 |
18 | public IResponse Handled()
19 | {
20 | IsHandled = true;
21 | return this;
22 | }
23 |
24 | public IResponse AvoidRepeat()
25 | {
26 | IsForced = false;
27 | return this;
28 | }
29 |
30 | public IResponse Forced()
31 | {
32 | IsForced = true;
33 | return this;
34 | }
35 |
36 | public IResponse At(string? id)
37 | {
38 | TryAt = id;
39 | return this;
40 | }
41 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework/Messaging/IRichMessageConverter.cs:
--------------------------------------------------------------------------------
1 | using MilkiBotFramework.Messaging.RichMessages;
2 |
3 | namespace MilkiBotFramework.Messaging;
4 |
5 | public interface IRichMessageConverter
6 | {
7 | ValueTask EncodeAsync(IRichMessage message);
8 | RichMessage Decode(ReadOnlyMemory message);
9 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework/Messaging/MessageAuthority.cs:
--------------------------------------------------------------------------------
1 | namespace MilkiBotFramework.Messaging;
2 |
3 | public enum MessageAuthority
4 | {
5 | Unspecified, Public, SubAdmin, Admin, Root
6 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework/Messaging/MessageContext.cs:
--------------------------------------------------------------------------------
1 | using MilkiBotFramework.ContactsManaging.Models;
2 | using MilkiBotFramework.Messaging.RichMessages;
3 | using MilkiBotFramework.Plugining.CommandLine;
4 | using MilkiBotFramework.Plugining.Loading;
5 |
6 | namespace MilkiBotFramework.Messaging;
7 |
8 | ///
9 | /// 表示一个类,用以传递单条消息的上下文信息。
10 | ///
11 | public class MessageContext
12 | {
13 | private readonly IRichMessageConverter _richMessageConverter;
14 |
15 | public MessageContext(IRichMessageConverter richMessageConverter)
16 | {
17 | _richMessageConverter = richMessageConverter;
18 | }
19 |
20 | public string RawTextMessage { get; internal set; } = null!;
21 |
22 | public string? MessageId { get; set; }
23 | public virtual string? TextMessage { get; set; }
24 |
25 | public MemberInfo? MemberInfo { get; set; }
26 | public ChannelInfo? ChannelInfo { get; set; }
27 | public PrivateInfo? PrivateInfo { get; set; }
28 |
29 | public MessageUserIdentity? MessageUserIdentity { get; set; }
30 | public MessageIdentity? MessageIdentity { get; set; }
31 | public MessageAuthority Authority { get; set; }
32 | public DateTimeOffset ReceivedTime { get; set; }
33 |
34 | public IReadOnlyList ExecutedPlugins { get; } = new List();
35 | public List NextPlugins { get; internal set; } = new();
36 | public CommandLineResult? CommandLineResult { get; internal set; }
37 |
38 | public RichMessage GetRichMessage()
39 | {
40 | return _richMessageConverter.Decode(TextMessage.AsMemory());
41 | }
42 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework/Messaging/MessageResponse.cs:
--------------------------------------------------------------------------------
1 | using MilkiBotFramework.Messaging.RichMessages;
2 |
3 | namespace MilkiBotFramework.Messaging;
4 |
5 | internal class MessageResponse : IResponse
6 | {
7 | public string? Id { get; }
8 | public string? SubId { get; }
9 | public MessageType? MessageType { get; }
10 | public IRichMessage? Message { get; set; }
11 | public bool? TryReply { get; set; }
12 | public bool IsHandled { get; set; }
13 | public bool? IsForced { get; set; }
14 | public string? TryAt { get; set; }
15 | public AsyncMessage? AsyncMessage { get; init; }
16 | public MessageContext? MessageContext { get; internal set; }
17 |
18 | public MessageResponse(string id, string? subId, IRichMessage autoMessage, MessageType messageType)
19 | {
20 | Id = id;
21 | SubId = subId;
22 | Message = autoMessage;
23 | MessageType = messageType;
24 | }
25 |
26 | public MessageResponse(IRichMessage autoMessage, bool tryReply = true)
27 | {
28 | Message = autoMessage;
29 | TryReply = tryReply;
30 | }
31 |
32 | public MessageResponse(bool nextBlocked)
33 | {
34 | IsHandled = nextBlocked;
35 | }
36 |
37 | IAsyncMessage? IResponse.AsyncMessage => AsyncMessage;
38 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework/Messaging/MessageTimeoutException.cs:
--------------------------------------------------------------------------------
1 | namespace MilkiBotFramework.Messaging;
2 |
3 | public class MessageTimeoutException : Exception
4 | {
5 | public MessageTimeoutException(string message) : base(message)
6 | {
7 | }
8 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework/Messaging/MessageType.cs:
--------------------------------------------------------------------------------
1 | namespace MilkiBotFramework.Messaging;
2 |
3 | [Flags]
4 | public enum MessageType
5 | {
6 | Private = 1, Channel = 2, Notice = 4, Meta = 8
7 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework/Messaging/MessageUserIdentity.cs:
--------------------------------------------------------------------------------
1 | namespace MilkiBotFramework.Messaging;
2 |
3 | public sealed class MessageUserIdentity
4 | {
5 | public MessageUserIdentity(MessageIdentity messageIdentity, string userId)
6 | {
7 | MessageIdentity = messageIdentity;
8 | UserId = userId;
9 | }
10 |
11 | public override bool Equals(object? obj)
12 | {
13 | if (ReferenceEquals(null, obj)) return false;
14 | if (ReferenceEquals(this, obj)) return true;
15 | if (obj.GetType() != this.GetType()) return false;
16 | return Equals((MessageUserIdentity)obj);
17 | }
18 |
19 | private bool Equals(MessageUserIdentity other)
20 | {
21 | return MessageIdentity.Equals(other.MessageIdentity) && UserId == other.UserId;
22 | }
23 |
24 | public override int GetHashCode()
25 | {
26 | return HashCode.Combine(MessageIdentity, UserId);
27 | }
28 |
29 | public static bool operator ==(MessageUserIdentity? left, MessageUserIdentity? right)
30 | {
31 | return Equals(left, right);
32 | }
33 |
34 | public static bool operator !=(MessageUserIdentity? left, MessageUserIdentity? right)
35 | {
36 | return !Equals(left, right);
37 | }
38 |
39 | public MessageIdentity MessageIdentity { get; }
40 | public string UserId { get; }
41 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework/Messaging/RichMessages/At.cs:
--------------------------------------------------------------------------------
1 | namespace MilkiBotFramework.Messaging.RichMessages;
2 |
3 | public class At : IRichMessage
4 | {
5 | public At(string userId) => UserId = userId;
6 | public string UserId { get; set; }
7 | public virtual ValueTask EncodeAsync() => ValueTask.FromResult($"[At {UserId}]");
8 |
9 | public override string ToString()
10 | {
11 | return $"[At {UserId}]";
12 | }
13 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework/Messaging/RichMessages/FileImage.cs:
--------------------------------------------------------------------------------
1 | using MilkiBotFramework.Imaging;
2 | using SixLabors.ImageSharp;
3 |
4 | namespace MilkiBotFramework.Messaging.RichMessages;
5 |
6 | public class FileImage : IRichMessage
7 | {
8 | public FileImage(string path) => Path = path;
9 | public string Path { get; set; }
10 |
11 | public async Task ToMemoryImageAsync()
12 | {
13 | var bytes = await File.ReadAllBytesAsync(Path);
14 | var imageType = ImageHelper.GetKnownImageType(bytes);
15 |
16 | var stream = new MemoryStream(bytes);
17 | var bitmap = await Image.LoadAsync(stream);
18 |
19 | return new MemoryImage(bitmap, imageType);
20 | }
21 |
22 | public virtual ValueTask EncodeAsync() => ValueTask.FromResult("[FileImage]");
23 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework/Messaging/RichMessages/IRichMessage.cs:
--------------------------------------------------------------------------------
1 | namespace MilkiBotFramework.Messaging.RichMessages;
2 |
3 | public interface IRichMessage
4 | {
5 | ///
6 | /// Warn: The operator will change the original object
7 | ///
8 | ///
9 | ///
10 | ///
11 | public static IRichMessage operator +(IRichMessage a, IRichMessage b)
12 | {
13 | if (a is RichMessage rich1 && b is RichMessage rich2)
14 | {
15 | rich1.RichMessages.AddRange(rich2.RichMessages);
16 | return rich1;
17 | }
18 |
19 | if (a is RichMessage r1)
20 | {
21 | r1.RichMessages.Add(b);
22 | return r1;
23 | }
24 |
25 | if (b is RichMessage r2)
26 | {
27 | r2.RichMessages.Insert(0, a);
28 | return r2;
29 | }
30 |
31 | return new RichMessage(a, b);
32 | }
33 |
34 | ValueTask EncodeAsync();
35 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework/Messaging/RichMessages/LinkImage.cs:
--------------------------------------------------------------------------------
1 | using MilkiBotFramework.Connecting;
2 | using SixLabors.ImageSharp;
3 |
4 | namespace MilkiBotFramework.Messaging.RichMessages;
5 |
6 | public class LinkImage : IRichMessage
7 | {
8 | public LinkImage(string uri) => Uri = uri;
9 | public string Uri { get; set; }
10 |
11 | public async Task ToMemoryImageAsync(LightHttpClient client)
12 | {
13 | var (bytes, imageType) = await client.GetImageBytesFromUrlAsync(Uri);
14 |
15 | var stream = new MemoryStream(bytes);
16 | var bitmap = await Image.LoadAsync(stream);
17 |
18 | return new MemoryImage(bitmap, imageType);
19 | }
20 |
21 | public virtual ValueTask EncodeAsync() => ValueTask.FromResult($"[Image: {Uri}]");
22 |
23 | public override string ToString()
24 | {
25 | return "[网址图片]";
26 | }
27 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework/Messaging/RichMessages/MemoryImage.cs:
--------------------------------------------------------------------------------
1 | using MilkiBotFramework.Imaging;
2 | using SixLabors.ImageSharp;
3 |
4 | namespace MilkiBotFramework.Messaging.RichMessages;
5 |
6 | public class MemoryImage : IRichMessage, IDisposable
7 | {
8 | public MemoryImage(Image imageSource, ImageType imageType)
9 | {
10 | ImageSource = imageSource;
11 | ImageType = imageType;
12 | }
13 |
14 | public Image ImageSource { get; }
15 | public ImageType ImageType { get; }
16 | public void Dispose() => ImageSource.Dispose();
17 | public virtual ValueTask EncodeAsync() => ValueTask.FromResult("[Image]");
18 |
19 | public override string ToString()
20 | {
21 | return "[合成图片]";
22 | }
23 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework/Messaging/RichMessages/Reply.cs:
--------------------------------------------------------------------------------
1 | namespace MilkiBotFramework.Messaging.RichMessages;
2 |
3 | public class Reply : IRichMessage
4 | {
5 | public Reply(string messageId) => MessageId = messageId;
6 | public string MessageId { get; set; }
7 | public virtual ValueTask EncodeAsync() => ValueTask.FromResult($"[Reply {MessageId}]");
8 |
9 | public override string ToString()
10 | {
11 | return "[回复]";
12 | }
13 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework/Messaging/RichMessages/Text.cs:
--------------------------------------------------------------------------------
1 | namespace MilkiBotFramework.Messaging.RichMessages;
2 |
3 | public class Text : IRichMessage
4 | {
5 | public Text(string content) => Content = content;
6 | public string Content { get; set; }
7 |
8 | public static implicit operator Text(string content) => new(content);
9 | public virtual ValueTask EncodeAsync() => ValueTask.FromResult(Content);
10 | public override string ToString() => Content;
11 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework/Messaging/RichMessages/UriText.cs:
--------------------------------------------------------------------------------
1 | namespace MilkiBotFramework.Messaging.RichMessages;
2 |
3 | public class UriText : IRichMessage
4 | {
5 | public UriText(string content) => Content = content;
6 | public string Content { get; set; }
7 | public virtual ValueTask EncodeAsync() => ValueTask.FromResult(Content);
8 | public override string ToString() => "[链接]";
9 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework/Messaging/RichMessages/Voice.cs:
--------------------------------------------------------------------------------
1 | namespace MilkiBotFramework.Messaging.RichMessages;
2 |
3 | public class Voice : IRichMessage
4 | {
5 | public Voice(string path) => Path = path;
6 | public string Path { get; set; }
7 | public virtual ValueTask EncodeAsync() => ValueTask.FromResult("[Voice]");
8 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework/MilkiBotFramework.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net8.0
5 | enable
6 | enable
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | all
25 | runtime; build; native; contentfiles; analyzers; buildtransitive
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/src/MilkiBotFramework/Plugining/Attributes/ArgumentAttribute.cs:
--------------------------------------------------------------------------------
1 | namespace MilkiBotFramework.Plugining.Attributes;
2 |
3 | [AttributeUsage(AttributeTargets.Property | AttributeTargets.Parameter)]
4 | public class ArgumentAttribute : ParameterAttribute
5 | {
6 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework/Plugining/Attributes/CommandHandlerAttribute.cs:
--------------------------------------------------------------------------------
1 | using MilkiBotFramework.Messaging;
2 |
3 | namespace MilkiBotFramework.Plugining.Attributes;
4 |
5 | [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
6 | public sealed class CommandHandlerAttribute : Attribute
7 | {
8 | public CommandHandlerAttribute(string? command = null)
9 | {
10 | Command = command;
11 | }
12 |
13 | public string? Command { get; }
14 | public MessageAuthority Authority { get; set; } = MessageAuthority.Public;
15 | public MessageType AllowedMessageType { get; set; } = MessageType.Private | MessageType.Channel;
16 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework/Plugining/Attributes/OptionAttribute.cs:
--------------------------------------------------------------------------------
1 | namespace MilkiBotFramework.Plugining.Attributes;
2 |
3 | [AttributeUsage(AttributeTargets.Property | AttributeTargets.Parameter)]
4 | public sealed class OptionAttribute : ParameterAttribute
5 | {
6 | public OptionAttribute(string name)
7 | {
8 | Name = name;
9 | }
10 |
11 | public char? Abbreviate { get; set; }
12 | public string Name { get; set; }
13 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework/Plugining/Attributes/ParameterAttribute.cs:
--------------------------------------------------------------------------------
1 | using MilkiBotFramework.Messaging;
2 | using MilkiBotFramework.Plugining.Loading;
3 |
4 | namespace MilkiBotFramework.Plugining.Attributes;
5 |
6 | public abstract class ParameterAttribute : Attribute
7 | {
8 | private Type? _converter;
9 | public object? DefaultValue { get; set; } = DBNull.Value;
10 | public MessageAuthority Authority { get; set; } = MessageAuthority.Public;
11 |
12 | public Type? Converter
13 | {
14 | get => _converter;
15 | set
16 | {
17 | if (value == null)
18 | {
19 | _converter = null;
20 | return;
21 | }
22 |
23 | if (value.GetInterface(nameof(IParameterConverter)) == null)
24 | throw new InvalidOperationException();
25 | _converter = value;
26 | }
27 | }
28 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework/Plugining/Attributes/PluginIdentifierAttribute.cs:
--------------------------------------------------------------------------------
1 | namespace MilkiBotFramework.Plugining.Attributes;
2 |
3 | [AttributeUsage(AttributeTargets.Class)]
4 | public sealed class PluginIdentifierAttribute : Attribute
5 | {
6 | public PluginIdentifierAttribute(string guid, string? name = null)
7 | {
8 | Guid = guid;
9 | Name = name;
10 | }
11 |
12 | public string? Scope { get; init; }
13 | public string Guid { get; }
14 | public string? Name { get; }
15 | public string? Authors { get; init; }
16 |
17 | ///
18 | /// 插件优先级,越小则优先级越高
19 | ///
20 | public int Index { get; init; }
21 |
22 | public bool AllowDisable { get; init; } = true;
23 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework/Plugining/Attributes/PluginLifetimeAttribute.cs:
--------------------------------------------------------------------------------
1 | namespace MilkiBotFramework.Plugining.Attributes;
2 |
3 | [AttributeUsage(AttributeTargets.Class)]
4 | public sealed class PluginLifetimeAttribute : Attribute
5 | {
6 | public PluginLifetimeAttribute(PluginLifetime lifetime)
7 | {
8 | Lifetime = lifetime;
9 | }
10 |
11 | public PluginLifetime Lifetime { get; }
12 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework/Plugining/Attributes/RegexCommandAttribute.cs:
--------------------------------------------------------------------------------
1 | namespace MilkiBotFramework.Plugining.Attributes;
2 |
3 | [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
4 | public class RegexCommandAttribute : Attribute
5 | {
6 | public RegexCommandAttribute(string regex, string alias)
7 | {
8 | RegexString = regex;
9 | Alias = alias;
10 | }
11 |
12 | public string RegexString { get; }
13 | public string Alias { get; }
14 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework/Plugining/Attributes/RegexCommandHandlerAttribute.cs:
--------------------------------------------------------------------------------
1 | namespace MilkiBotFramework.Plugining.Attributes;
2 |
3 | [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
4 | public sealed class RegexCommandHandlerAttribute : Attribute
5 | {
6 | public RegexCommandHandlerAttribute(string regex)
7 | {
8 | RegexString = regex;
9 | }
10 |
11 | public string RegexString { get; }
12 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework/Plugining/BasicPlugin.cs:
--------------------------------------------------------------------------------
1 | using MilkiBotFramework.Messaging;
2 | using MilkiBotFramework.Plugining.Attributes;
3 |
4 | namespace MilkiBotFramework.Plugining;
5 |
6 | [PluginLifetime(PluginLifetime.Scoped)]
7 | public abstract class BasicPlugin : PluginBase, IMessagePlugin
8 | {
9 | public sealed override PluginType PluginType => PluginType.Basic;
10 | #pragma warning disable CS1998
11 | public virtual async IAsyncEnumerable OnMessageReceived(MessageContext context) { yield break; }
12 | #pragma warning restore CS1998
13 | public virtual Task OnBindingFailed(BindingException bindingException, MessageContext context) => Task.FromResult(null);
14 | }
15 |
16 | [PluginLifetime(PluginLifetime.Scoped)]
17 | public abstract class BasicPlugin : PluginBase, IMessagePlugin
18 | where TContext : MessageContext
19 | {
20 | public sealed override PluginType PluginType => PluginType.Basic;
21 | #pragma warning disable CS1998
22 | public virtual async IAsyncEnumerable OnMessageReceived(TContext request) { yield break; }
23 | #pragma warning restore CS1998
24 | public virtual Task OnBindingFailed(BindingException bindingException, MessageContext context) => Task.FromResult(null);
25 |
26 | IAsyncEnumerable IMessagePlugin.OnMessageReceived(MessageContext context) =>
27 | OnMessageReceived((TContext)context);
28 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework/Plugining/BindingException.cs:
--------------------------------------------------------------------------------
1 | namespace MilkiBotFramework.Plugining;
2 |
3 | public class BindingException : Exception
4 | {
5 | public BindingSource BindingSource { get; }
6 | public BindingFailureType BindingFailureType { get; }
7 |
8 | public BindingException(string message,
9 | BindingSource bindingSource,
10 | BindingFailureType bindingFailureType,
11 | Exception? innerException = null)
12 | : base(message, innerException)
13 | {
14 | BindingSource = bindingSource;
15 | BindingFailureType = bindingFailureType;
16 | }
17 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework/Plugining/BindingFailureType.cs:
--------------------------------------------------------------------------------
1 | namespace MilkiBotFramework.Plugining;
2 |
3 | public enum BindingFailureType
4 | {
5 | None, ConvertError, Mismatch, AuthenticationFailed, MessageTypeFailed
6 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework/Plugining/BindingSource.cs:
--------------------------------------------------------------------------------
1 | using MilkiBotFramework.Plugining.Loading;
2 |
3 | namespace MilkiBotFramework.Plugining;
4 |
5 | public sealed class BindingSource
6 | {
7 | public BindingSource(CommandInfo commandInfo,
8 | CommandParameterInfo? parameterInfo)
9 | {
10 | CommandInfo = commandInfo;
11 | ParameterInfo = parameterInfo;
12 | }
13 |
14 | public CommandInfo CommandInfo { get; }
15 | public CommandParameterInfo? ParameterInfo { get; }
16 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework/Plugining/CommandLine/CommandLineAuthority.cs:
--------------------------------------------------------------------------------
1 | namespace MilkiBotFramework.Plugining.CommandLine;
2 |
3 | public enum CommandLineAuthority
4 | {
5 | Public = 1, Admin = 3, Root = 4
6 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework/Plugining/CommandLine/CommandLineException.cs:
--------------------------------------------------------------------------------
1 | namespace MilkiBotFramework.Plugining.CommandLine;
2 |
3 | public class CommandLineException : Exception
4 | {
5 | public CommandLineException(string? message) : base(message)
6 | {
7 | }
8 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework/Plugining/CommandLine/CommandLineResult.cs:
--------------------------------------------------------------------------------
1 | using System.Text;
2 |
3 | namespace MilkiBotFramework.Plugining.CommandLine;
4 |
5 | public sealed class CommandLineResult
6 | {
7 | public CommandLineResult(CommandLineAuthority authority,
8 | ReadOnlyMemory? command,
9 | Dictionary, ReadOnlyMemory?> options,
10 | List> arguments,
11 | ReadOnlyMemory simpleArgument)
12 | {
13 | Authority = authority;
14 | Command = command;
15 | Options = options;
16 | Arguments = arguments;
17 | SimpleArgument = simpleArgument;
18 | }
19 |
20 | public CommandLineAuthority Authority { get; }
21 | public ReadOnlyMemory? Command { get; }
22 | public Dictionary, ReadOnlyMemory?> Options { get; }
23 | public List> Arguments { get; }
24 | public ReadOnlyMemory SimpleArgument { get; }
25 |
26 | public override string ToString()
27 | {
28 | var sb = new StringBuilder();
29 | if (Command != null)
30 | {
31 | sb.Append(GetArgumentString(Command) + " ");
32 | }
33 |
34 | if (Arguments is { Count: > 0 })
35 | {
36 | sb.Append(string.Join(" ", Arguments.Select(k => GetArgumentString(k))));
37 | sb.Append(' ');
38 | }
39 |
40 | if (Options is { Count: > 0 })
41 | sb.Append(string.Join(" ", Options
42 | .OrderBy(k => k.Key.ToString())
43 | .Select(k =>
44 | {
45 | return k.Value == null
46 | ? $"-{GetArgumentString(k.Key)}"
47 | : $"-{GetArgumentString(k.Key)} {GetArgumentString(k.Value)}";
48 | })
49 | ));
50 |
51 | if (sb.Length == 0)
52 | return "";
53 |
54 | if (sb[^1] == ' ')
55 | sb.Remove(sb.Length - 1, 1);
56 | return sb.ToString();
57 | }
58 |
59 | private static string GetArgumentString(ReadOnlyMemory? k)
60 | {
61 | if (k == null) return "";
62 | return (k.Value.Span.Contains(' ') || k.Value.Span.Contains(':')) ? $"\"{k}\"" : k.Value.ToString();
63 | }
64 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework/Plugining/CommandLine/ICommandLineAnalyzer.cs:
--------------------------------------------------------------------------------
1 | using System.Diagnostics.CodeAnalysis;
2 | using MilkiBotFramework.Plugining.Loading;
3 |
4 | namespace MilkiBotFramework.Plugining.CommandLine
5 | {
6 | public interface ICommandLineAnalyzer
7 | {
8 | IParameterConverter DefaultParameterConverter { get; set; }
9 |
10 | bool TryAnalyze(string input,
11 | [NotNullWhen(true)] out CommandLineResult? result,
12 | out CommandLineException? exception);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/MilkiBotFramework/Plugining/Configuration/CommentsObjectDescriptor.cs:
--------------------------------------------------------------------------------
1 | using YamlDotNet.Core;
2 | using YamlDotNet.Serialization;
3 |
4 | namespace MilkiBotFramework.Plugining.Configuration;
5 |
6 | public sealed class CommentsObjectDescriptor : IObjectDescriptor
7 | {
8 | private readonly IObjectDescriptor _innerDescriptor;
9 |
10 | public CommentsObjectDescriptor(IObjectDescriptor innerDescriptor, string comment)
11 | {
12 | _innerDescriptor = innerDescriptor;
13 | Comment = comment;
14 | }
15 |
16 | public string Comment { get; private set; }
17 |
18 | public object? Value => _innerDescriptor.Value;
19 | public Type Type => _innerDescriptor.Type;
20 | public Type StaticType => _innerDescriptor.StaticType;
21 | public ScalarStyle ScalarStyle => _innerDescriptor.ScalarStyle;
22 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework/Plugining/Configuration/CommentsObjectGraphVisitor.cs:
--------------------------------------------------------------------------------
1 | using YamlDotNet.Core;
2 | using YamlDotNet.Serialization;
3 | using YamlDotNet.Serialization.ObjectGraphVisitors;
4 |
5 | namespace MilkiBotFramework.Plugining.Configuration;
6 |
7 | public class CommentsObjectGraphVisitor : ChainedObjectGraphVisitor
8 | {
9 | public CommentsObjectGraphVisitor(IObjectGraphVisitor nextVisitor)
10 | : base(nextVisitor)
11 | {
12 | }
13 |
14 | public override bool EnterMapping(IPropertyDescriptor key, IObjectDescriptor value, IEmitter context, ObjectSerializer serializer)
15 | {
16 | if (value is CommentsObjectDescriptor { Comment: { } } commentsDescriptor)
17 | {
18 | context.Emit(new YamlDotNet.Core.Events.Comment(commentsDescriptor.Comment, false));
19 | }
20 |
21 | return base.EnterMapping(key, value, context, serializer);
22 | }
23 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework/Plugining/Configuration/Configuration.cs:
--------------------------------------------------------------------------------
1 | using MilkiBotFramework.Plugining.Loading;
2 |
3 | namespace MilkiBotFramework.Plugining.Configuration;
4 |
5 | internal class Configuration : IConfiguration where T : ConfigurationBase
6 | {
7 | public Configuration(LoaderContext? loaderContext, ConfigurationFactory configurationFactory)
8 | {
9 | Instance = configurationFactory.GetConfiguration(loaderContext?.Name ?? "Host");
10 | }
11 |
12 | public T Instance { get; }
13 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework/Plugining/Configuration/ConfigurationBase.cs:
--------------------------------------------------------------------------------
1 | using System.Text;
2 | using YamlDotNet.Serialization;
3 |
4 | namespace MilkiBotFramework.Plugining.Configuration;
5 |
6 | public class ConfigurationBase
7 | {
8 | [YamlIgnore]
9 | public virtual Encoding Encoding { get; } = Encoding.UTF8;
10 |
11 | [YamlIgnore]
12 | internal Func? SaveAction;
13 |
14 | public async Task SaveAsync()
15 | {
16 | if (SaveAction != null) await SaveAction();
17 | }
18 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework/Plugining/Configuration/DateTimeOffsetConverter.cs:
--------------------------------------------------------------------------------
1 | using YamlDotNet.Core;
2 | using YamlDotNet.Core.Events;
3 | using YamlDotNet.Serialization;
4 |
5 | namespace MilkiBotFramework.Plugining.Configuration;
6 |
7 | public class DateTimeOffsetConverter : IYamlTypeConverter
8 | {
9 | private static readonly Type MemberInfo = typeof(DateTimeOffset);
10 |
11 | public bool Accepts(Type type)
12 | {
13 | return type == MemberInfo;
14 | }
15 |
16 | public object ReadYaml(IParser parser, Type type, ObjectDeserializer rootDeserializer)
17 | {
18 | var s = parser.Consume();
19 | var str = s.Value;
20 | return DateTimeOffset.Parse(str);
21 | }
22 |
23 | public void WriteYaml(IEmitter emitter, object? value, Type type, ObjectSerializer serializer)
24 | {
25 | emitter.Emit(new Scalar(value?.ToString() ?? ""));
26 | }
27 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework/Plugining/Configuration/IConfiguration.cs:
--------------------------------------------------------------------------------
1 | namespace MilkiBotFramework.Plugining.Configuration;
2 |
3 | public interface IConfiguration where T : ConfigurationBase
4 | {
5 | public T Instance { get; }
6 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework/Plugining/Configuration/MessageIdentityConverter.cs:
--------------------------------------------------------------------------------
1 | using MilkiBotFramework.Messaging;
2 | using YamlDotNet.Core;
3 | using YamlDotNet.Core.Events;
4 | using YamlDotNet.Serialization;
5 |
6 | namespace MilkiBotFramework.Plugining.Configuration;
7 |
8 | public class MessageIdentityConverter : IYamlTypeConverter
9 | {
10 | private static readonly Type MemberInfo = typeof(MessageIdentity);
11 |
12 | public bool Accepts(Type type)
13 | {
14 | return type == MemberInfo;
15 | }
16 |
17 | public object ReadYaml(IParser parser, Type type, ObjectDeserializer rootDeserializer)
18 | {
19 | var s = parser.Consume();
20 | var str = s.Value;
21 | return MessageIdentity.Parse(str);
22 | }
23 |
24 | public void WriteYaml(IEmitter emitter, object? value, Type type, ObjectSerializer serializer)
25 | {
26 | emitter.Emit(new Scalar(value?.ToString() ?? ""));
27 | }
28 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework/Plugining/Database/PluginDbContext.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Data.Sqlite;
2 | using Microsoft.EntityFrameworkCore;
3 |
4 | namespace MilkiBotFramework.Plugining.Database
5 | {
6 | public abstract class PluginDbContext : DbContext
7 | {
8 | protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
9 | {
10 | if (optionsBuilder.IsConfigured)
11 | {
12 | return;
13 | }
14 |
15 | var connectionStringBuilder = new SqliteConnectionStringBuilder
16 | {
17 | DataSource = TemporaryDbPath,
18 | Mode = SqliteOpenMode.ReadWriteCreate,
19 | Cache = SqliteCacheMode.Shared
20 | };
21 |
22 | optionsBuilder.UseSqlite(connectionStringBuilder.ToString());
23 | }
24 |
25 | internal string? TemporaryDbPath { get; set; }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/MilkiBotFramework/Plugining/IMessagePlugin.cs:
--------------------------------------------------------------------------------
1 | using MilkiBotFramework.Messaging;
2 |
3 | namespace MilkiBotFramework.Plugining;
4 |
5 | public interface IMessagePlugin
6 | {
7 | IAsyncEnumerable OnMessageReceived(MessageContext context);
8 | Task OnBindingFailed(BindingException bindingException, MessageContext context);
9 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework/Plugining/Loading/AssemblyContext.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 |
3 | namespace MilkiBotFramework.Plugining.Loading;
4 |
5 | public class AssemblyContext
6 | {
7 | public Assembly Assembly { get; init; } = null!;
8 | public IReadOnlyList PluginInfos { get; init; } = null!;
9 | public IReadOnlyList DbContextTypes { get; init; } = null!;
10 | public string Version { get; init; } = null!;
11 | public string? Product { get; init; }
12 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework/Plugining/Loading/CommandInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using MilkiBotFramework.Messaging;
3 |
4 | namespace MilkiBotFramework.Plugining.Loading
5 | {
6 | public sealed class CommandInfo
7 | {
8 | public CommandInfo(string command,
9 | string description,
10 | MethodInfo methodInfo,
11 | CommandReturnType commandReturnType,
12 | MessageAuthority authority,
13 | MessageType messageType,
14 | IReadOnlyList parameterInfos)
15 | {
16 | Command = command;
17 | Description = description;
18 | MethodInfo = methodInfo;
19 | CommandReturnType = commandReturnType;
20 | Authority = authority;
21 | MessageType = messageType;
22 | ParameterInfos = parameterInfos;
23 | }
24 |
25 | public string Command { get; }
26 | public string Description { get; }
27 | public MethodInfo MethodInfo { get; }
28 | public CommandReturnType CommandReturnType { get; }
29 | public MessageAuthority Authority { get; }
30 | public MessageType MessageType { get; }
31 | public IReadOnlyList ParameterInfos { get; }
32 | public ModelBindingInfo? ModelBindingInfo { get; internal set; }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/MilkiBotFramework/Plugining/Loading/CommandParameterInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using MilkiBotFramework.Messaging;
3 |
4 | namespace MilkiBotFramework.Plugining.Loading;
5 |
6 | public sealed class CommandParameterInfo
7 | {
8 | public MessageAuthority Authority { get; internal set; }
9 | public string? Name { get; internal set; }
10 | public string ParameterName { get; internal init; } = null!;
11 | public Type ParameterType { get; internal init; } = null!;
12 | public PropertyInfo PropertyInfo { get; internal init; } = null!;
13 |
14 | public char? Abbr { get; internal set; }
15 | public bool IsArgument { get; internal set; }
16 |
17 | public IParameterConverter ValueConverter { get; internal set; } = DefaultParameterConverter.Instance;
18 |
19 | public object? DefaultValue { get; internal set; }
20 | public string? Description { get; internal set; }
21 | public bool IsServiceArgument { get; internal set; }
22 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework/Plugining/Loading/CommandReturnType.cs:
--------------------------------------------------------------------------------
1 | // ReSharper disable InconsistentNaming
2 |
3 | namespace MilkiBotFramework.Plugining.Loading;
4 |
5 | public enum CommandReturnType
6 | {
7 | Void,
8 | Task,
9 | ValueTask,
10 | IResponse,
11 | Task_IResponse,
12 | ValueTask_IResponse,
13 | IEnumerable_IResponse,
14 | IAsyncEnumerable_IResponse,
15 | Task_,
16 | ValueTask_,
17 | Unknown
18 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework/Plugining/Loading/IParameterConverter.cs:
--------------------------------------------------------------------------------
1 | namespace MilkiBotFramework.Plugining.Loading;
2 |
3 | public interface IParameterConverter
4 | {
5 | object Convert(Type actualType, ReadOnlyMemory source);
6 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework/Plugining/Loading/LoaderContext.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.Loader;
2 | using Microsoft.Extensions.DependencyInjection;
3 |
4 | namespace MilkiBotFramework.Plugining.Loading;
5 |
6 | public class LoaderContext
7 | {
8 | public string Name { get; init; } = null!;
9 | public bool IsRuntimeContext { get; init; }
10 | public IReadOnlyDictionary AssemblyContexts { get; init; } = null!;
11 |
12 | internal IServiceCollection ServiceCollection { get; init; } = null!;
13 | internal ServiceProvider? ServiceProvider { get; private set; }
14 | internal AssemblyLoadContext AssemblyLoadContext { get; init; } = null!;
15 |
16 | internal ServiceProvider BuildServiceProvider()
17 | {
18 | if (ServiceProvider != null) return ServiceProvider;
19 | return this.ServiceProvider = ServiceCollection.BuildServiceProvider();
20 | }
21 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework/Plugining/Loading/ModelBindingInfo.cs:
--------------------------------------------------------------------------------
1 | namespace MilkiBotFramework.Plugining.Loading;
2 |
3 | public class ModelBindingInfo
4 | {
5 | public ModelBindingInfo(Type targetType, List parameterInfos)
6 | {
7 | TargetType = targetType;
8 | ParameterInfos = parameterInfos;
9 | }
10 |
11 | public Type TargetType { get; }
12 | public List ParameterInfos { get; }
13 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework/Plugining/Loading/PluginInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.ObjectModel;
2 |
3 | namespace MilkiBotFramework.Plugining.Loading;
4 |
5 | public sealed class PluginInfo
6 | {
7 | public PluginInfo(PluginMetadata metadata,
8 | Type type,
9 | Type baseType,
10 | PluginLifetime lifetime,
11 | ReadOnlyDictionary commands,
12 | int index,
13 | string pluginHome,
14 | bool allowDisable)
15 | {
16 | Metadata = metadata;
17 | Type = type;
18 | BaseType = baseType;
19 | Lifetime = lifetime;
20 | Commands = commands;
21 | Index = index;
22 | PluginHome = pluginHome;
23 | AllowDisable = allowDisable;
24 | if (baseType == StaticTypes.BasicPlugin || baseType == StaticTypes.BasicPlugin_)
25 | PluginType = PluginType.Basic;
26 | else if (baseType == StaticTypes.ServicePlugin)
27 | PluginType = PluginType.Service;
28 | else
29 | PluginType = PluginType.Unspecified;
30 | }
31 |
32 |
33 | public bool InitializationFailed { get; internal set; }
34 | public PluginMetadata Metadata { get; }
35 | public Type Type { get; }
36 | public Type BaseType { get; }
37 | public PluginLifetime Lifetime { get; }
38 | public ReadOnlyDictionary Commands { get; }
39 | public int Index { get; }
40 | public string PluginHome { get; }
41 | public bool AllowDisable { get; }
42 | public PluginType PluginType { get; }
43 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework/Plugining/PluginLifetime.cs:
--------------------------------------------------------------------------------
1 | namespace MilkiBotFramework.Plugining;
2 |
3 | public enum PluginLifetime
4 | {
5 | Singleton, Scoped, Transient
6 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework/Plugining/PluginMetadata.cs:
--------------------------------------------------------------------------------
1 | namespace MilkiBotFramework.Plugining;
2 |
3 | public class PluginMetadata
4 | {
5 | public PluginMetadata(Guid guid, string name, string? description, string authors, string scope)
6 | {
7 | Guid = guid;
8 | Name = name;
9 | Description = description;
10 | Authors = authors;
11 | Scope = scope;
12 | }
13 |
14 | public Guid Guid { get; }
15 | public string Name { get; }
16 | public string? Description { get; }
17 | public string Authors { get; }
18 | public string Scope { get; }
19 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework/Plugining/PluginType.cs:
--------------------------------------------------------------------------------
1 | namespace MilkiBotFramework.Plugining;
2 |
3 | public enum PluginType
4 | {
5 | Unspecified, Basic, Service
6 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework/Plugining/ServicePlugin.cs:
--------------------------------------------------------------------------------
1 | using MilkiBotFramework.Messaging;
2 | using MilkiBotFramework.Plugining.Attributes;
3 | using MilkiBotFramework.Plugining.Loading;
4 |
5 | namespace MilkiBotFramework.Plugining;
6 |
7 | [PluginLifetime(PluginLifetime.Singleton)]
8 | public abstract class ServicePlugin : PluginBase
9 | {
10 | public sealed override PluginType PluginType => PluginType.Service;
11 | public virtual Task BeforeSend(PluginInfo pluginInfo, IResponse response) => Task.FromResult(true);
12 | public virtual Task OnNoticeReceived(MessageContext messageContext) => Task.FromResult(default);
13 | public virtual Task OnPluginException(Exception exception, MessageContext context) => Task.FromResult(default);
14 | public virtual Task OnBindingFailed(BindingException bindingException, MessageContext context) => Task.FromResult(default);
15 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework/StaticTypes.cs:
--------------------------------------------------------------------------------
1 | using MilkiBotFramework.Messaging;
2 | using MilkiBotFramework.Plugining;
3 |
4 | // ReSharper disable InconsistentNaming
5 |
6 | namespace MilkiBotFramework;
7 |
8 | public static class StaticTypes
9 | {
10 | public static readonly Type IResponse = typeof(IResponse);
11 | public static readonly Type BasicPlugin = typeof(BasicPlugin);
12 | public static readonly Type BasicPlugin_ = typeof(BasicPlugin<>);
13 | public static readonly Type ServicePlugin = typeof(ServicePlugin);
14 | public static readonly Type MessageContext = typeof(MessageContext);
15 |
16 | public static readonly Type Void = typeof(void);
17 | public static readonly Type Task = typeof(Task);
18 | public static readonly Type ValueTask = typeof(ValueTask);
19 | public static readonly Type Task_ = typeof(Task<>);
20 | public static readonly Type ValueTask_ = typeof(ValueTask<>);
21 | public static readonly Type IEnumerable_ = typeof(IEnumerable<>);
22 | public static readonly Type IAsyncEnumerable_ = typeof(IAsyncEnumerable<>);
23 |
24 | public static readonly Type Boolean = typeof(bool);
25 | public static readonly Type Byte = typeof(byte);
26 | public static readonly Type Sbyte = typeof(sbyte);
27 | public static readonly Type UInt16 = typeof(ushort);
28 | public static readonly Type UInt32 = typeof(uint);
29 | public static readonly Type UInt64 = typeof(ulong);
30 | public static readonly Type Int16 = typeof(short);
31 | public static readonly Type Int32 = typeof(int);
32 | public static readonly Type Int64 = typeof(long);
33 | public static readonly Type Single = typeof(float);
34 | public static readonly Type Double = typeof(double);
35 | public static readonly Type String = typeof(string);
36 | public static readonly Type ReadOnlyMemory_Char = typeof(ReadOnlyMemory);
37 |
38 | public static readonly Type TypeNullable = typeof(Nullable<>);
39 | public static readonly Type Enum = typeof(Enum);
40 |
41 | public static readonly Type Timespan = typeof(TimeSpan);
42 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework/Tasking/TaskContext.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Extensions.Logging;
2 |
3 | namespace MilkiBotFramework.Tasking
4 | {
5 | ///
6 | /// 计划任务上下文
7 | ///
8 | public sealed class TaskContext
9 | {
10 | ///
11 | /// 任务Id
12 | ///
13 | public Guid TaskId { get; init; }
14 |
15 | ///
16 | /// 任务名称
17 | ///
18 | public string TaskName { get; init; } = null!;
19 |
20 | ///
21 | /// 本次任务执行的触发器
22 | /// 当Startup触发时为null。
23 | ///
24 | public Trigger? Trigger { get; init; }
25 |
26 | ///
27 | /// 是否是Startup触发
28 | ///
29 | public bool IsStartupTrigger { get; init; }
30 |
31 | ///
32 | /// 任务触发时间
33 | /// 请以此时间为准,传到委托后调用DateTime.Now会有一定偏差。
34 | ///
35 | public DateTime TriggerTime { get; init; }
36 |
37 | ///
38 | /// 任务触发器列表
39 | ///
40 | public IReadOnlyList Triggers { get; init; } = null!;
41 |
42 | ///
43 | /// 以触发器为单位,上次执行的时间列表
44 | ///
45 | public IReadOnlyList LastTriggerTimes { get; init; } = null!;
46 |
47 | ///
48 | /// 以触发器为单位,下次执行的时间列表
49 | ///
50 | public IReadOnlyList NextTriggerTimes { get; init; } = null!;
51 |
52 | public ILogger Logger { get; set; } = null!;
53 | }
54 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework/Tasking/TaskExecutionHandler.cs:
--------------------------------------------------------------------------------
1 | namespace MilkiBotFramework.Tasking
2 | {
3 | ///
4 | /// 任务执行Delegate
5 | ///
6 | /// 任务执行时的计划任务上下文
7 | /// 传入的。
8 | /// 用于外部调用.Cancel()时,取消内部任务
9 | public delegate void TaskExecutionHandler(TaskContext context, CancellationToken token);
10 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework/Tasking/TaskInstance.cs:
--------------------------------------------------------------------------------
1 | namespace MilkiBotFramework.Tasking
2 | {
3 | ///
4 | /// 任务实例,当任务创建时进行创建
5 | ///
6 | public sealed class TaskInstance
7 | {
8 | public TaskInstance(TaskOption option)
9 | {
10 | Option = option;
11 | }
12 |
13 | ///
14 | /// 任务的设置信息
15 | ///
16 | public TaskOption Option { get; }
17 |
18 | ///
19 | /// 后台执行的实际任务
20 | ///
21 | public Task Task { get; set; } = null!;
22 |
23 | ///
24 | /// 任务的信号
25 | ///
26 | public CancellationTokenSource CancellationTokenSource { get; } = new();
27 | }
28 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework/Tasking/TaskOption.cs:
--------------------------------------------------------------------------------
1 | namespace MilkiBotFramework.Tasking
2 | {
3 | ///
4 | /// 任务设置
5 | ///
6 | public sealed class TaskOption
7 | {
8 | ///
9 | /// 任务Id
10 | ///
11 | public Guid Id { get; init; }
12 |
13 | ///
14 | /// 任务名称
15 | ///
16 | public string Name { get; init; } = null!;
17 |
18 | ///
19 | /// 是否在初始化时执行任务
20 | ///
21 | public bool TriggerOnStartup { get; set; }
22 |
23 | ///
24 | /// 触发器列表
25 | ///
26 | public List Triggers { get; set; } = new();
27 |
28 | ///
29 | /// 执行操作回调
30 | ///
31 | public TaskExecutionHandler? Handler { get; set; }
32 |
33 | public bool UseLogging { get; set; } = true;
34 | }
35 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework/Tasking/Trigger.cs:
--------------------------------------------------------------------------------
1 | namespace MilkiBotFramework.Tasking
2 | {
3 | ///
4 | /// 计划任务触发器。
5 | /// 当前仅时间实现。
6 | ///
7 | public sealed class Trigger
8 | {
9 | ///
10 | /// 触发器的时间单位类型
11 | ///
12 | public TriggerTimestampUnit TimestampUnit { get; init; }
13 |
14 | ///
15 | /// 触发器的偏移量
16 | ///
17 | public TimestampOffset TimestampOffset { get; init; }
18 |
19 | ///
20 | /// 获取下次执行的时间
21 | ///
22 | /// 计算的相对时间
23 | ///
24 | public DateTime GetNextExecutionTime(DateTime dateTime)
25 | {
26 | return dateTime.ComputeExecutionTime(this, true);
27 | }
28 |
29 | ///
30 | /// 获取上次执行的时间
31 | ///
32 | /// 计算的相对时间
33 | ///
34 | public DateTime GetLastExecutionTime(DateTime dateTime)
35 | {
36 | return dateTime.ComputeExecutionTime(this, false);
37 | }
38 | }
39 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework/Tasking/TriggerTimestampUnit.cs:
--------------------------------------------------------------------------------
1 | namespace MilkiBotFramework.Tasking
2 | {
3 | ///
4 | /// 表示的时间单位
5 | ///
6 | public enum TriggerTimestampUnit
7 | {
8 | ///
9 | /// 表示以年为单位
10 | /// Start from 1, timestamp as month of year
11 | ///
12 | Year,
13 |
14 | ///
15 | /// 表示以月为单位
16 | /// Start from 1, timestamp as day of month
17 | ///
18 | Month,
19 |
20 | ///
21 | /// 表示以周为单位
22 | /// Start from 1, timestamp as day of week
23 | ///
24 | Week,
25 |
26 | ///
27 | /// 表示以天为单位
28 | /// Start from 1, timestamp as hour
29 | ///
30 | Day,
31 |
32 | ///
33 | /// 表示以小时为单位
34 | /// Start from 0, timestamp as minute
35 | ///
36 | Hour,
37 |
38 | ///
39 | /// 表示以分为单位
40 | /// Start from 0, timestamp as second
41 | ///
42 | Minute,
43 |
44 | ///
45 | /// 表示绝对时间
46 | /// Directly convert from unix timestamp
47 | ///
48 | Absolute,
49 |
50 | ///
51 | /// 表示固定间隔
52 | /// Directly convert from unix timestamp
53 | ///
54 | Interval
55 | }
56 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework/Utils/AsyncLock.cs:
--------------------------------------------------------------------------------
1 | namespace MilkiBotFramework.Utils;
2 |
3 | ///
4 | /// SemaphoreSlim为核心的lock()异步实现
5 | ///
6 | public class AsyncLock : IDisposable
7 | {
8 | private class AsyncLockImpl : IDisposable
9 | {
10 | private readonly AsyncLock _parent;
11 | public AsyncLockImpl(AsyncLock parent) => _parent = parent;
12 | public void Dispose() => _parent._semaphoreSlim.Release();
13 | }
14 |
15 | private readonly AsyncLockImpl _asyncLockImpl;
16 | private readonly SemaphoreSlim _semaphoreSlim;
17 | private readonly Task _completeTask;
18 |
19 | public AsyncLock()
20 | {
21 | _semaphoreSlim = new SemaphoreSlim(1, 1);
22 | _asyncLockImpl = new AsyncLockImpl(this);
23 | _completeTask = Task.FromResult((IDisposable)_asyncLockImpl);
24 | }
25 |
26 | public IDisposable Lock()
27 | {
28 | _semaphoreSlim.Wait();
29 | return _asyncLockImpl;
30 | }
31 |
32 | public Task LockAsync(CancellationToken cancellationToken = default)
33 | {
34 | var task = _semaphoreSlim.WaitAsync(cancellationToken);
35 | return task.IsCompleted
36 | ? _completeTask
37 | : task.ContinueWith(
38 | (_, state) => (IDisposable)((AsyncLock)state!)._asyncLockImpl,
39 | this, CancellationToken.None,
40 | TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);
41 | }
42 |
43 | public void Dispose()
44 | {
45 | _semaphoreSlim.Dispose();
46 | }
47 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework/Utils/HttpHelperExtensions.cs:
--------------------------------------------------------------------------------
1 | using System.Text;
2 | using System.Web;
3 |
4 | namespace MilkiBotFramework.Utils;
5 |
6 | public static class HttpHelperExtensions
7 | {
8 | [Obsolete]
9 | public static string ToUrlParamString(this IDictionary? args)
10 | {
11 | if (args == null || args.Count < 1)
12 | return "";
13 |
14 | var sb = new StringBuilder("?");
15 | foreach (var item in args)
16 | sb.Append(HttpUtility.UrlEncode(item.Key) + "=" + HttpUtility.UrlEncode(item.Value) + "&");
17 | sb.Remove(sb.Length - 1, 1);
18 |
19 | return sb.ToString();
20 | }
21 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework/Utils/Utilities.cs:
--------------------------------------------------------------------------------
1 | namespace MilkiBotFramework.Utils
2 | {
3 | public static class Utilities
4 | {
5 | public static string GetRelativePath(string path)
6 | {
7 | var uri = new Uri(Environment.CurrentDirectory + "/").MakeRelativeUri(new Uri(path));
8 | return $"./{uri}";
9 | }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/MilkiBotFramework/Utils/ValueListBuilder.cs:
--------------------------------------------------------------------------------
1 | using System.Buffers;
2 | using System.Diagnostics;
3 | using System.Runtime.CompilerServices;
4 |
5 | namespace MilkiBotFramework.Utils;
6 |
7 | public ref struct ValueListBuilder
8 | {
9 | private Span _span;
10 | private T[]? _arrayFromPool;
11 | private int _pos;
12 |
13 | public ValueListBuilder(Span initialSpan)
14 | {
15 | _span = initialSpan;
16 | _arrayFromPool = null;
17 | _pos = 0;
18 | }
19 |
20 | public int Length
21 | {
22 | get => _pos;
23 | set
24 | {
25 | Debug.Assert(value >= 0);
26 | Debug.Assert(value <= _span.Length);
27 | _pos = value;
28 | }
29 | }
30 |
31 | public ref T this[int index]
32 | {
33 | get
34 | {
35 | Debug.Assert(index < _pos);
36 | return ref _span[index];
37 | }
38 | }
39 |
40 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
41 | public void Append(T item)
42 | {
43 | int pos = _pos;
44 | if (pos >= _span.Length)
45 | Grow();
46 |
47 | _span[pos] = item;
48 | _pos = pos + 1;
49 | }
50 |
51 | public ReadOnlySpan AsSpan()
52 | {
53 | return _span.Slice(0, _pos);
54 | }
55 |
56 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
57 | public void Dispose()
58 | {
59 | T[]? toReturn = _arrayFromPool;
60 | if (toReturn != null)
61 | {
62 | _arrayFromPool = null;
63 | ArrayPool.Shared.Return(toReturn);
64 | }
65 | }
66 |
67 | private void Grow()
68 | {
69 | T[] array = ArrayPool.Shared.Rent(_span.Length * 2);
70 |
71 | bool success = _span.TryCopyTo(array);
72 | Debug.Assert(success);
73 |
74 | T[]? toReturn = _arrayFromPool;
75 | _span = _arrayFromPool = array;
76 | if (toReturn != null)
77 | {
78 | ArrayPool.Shared.Return(toReturn);
79 | }
80 | }
81 | }
--------------------------------------------------------------------------------
/src/MilkiBotFramework/Utils/WaitHandleExtensions.cs:
--------------------------------------------------------------------------------
1 | namespace MilkiBotFramework.Utils;
2 |
3 | public static class WaitHandleExtensions
4 | {
5 | //https://stackoverflow.com/questions/18756354/wrapping-manualresetevent-as-awaitable-task
6 | public static Task WaitOneAsync(this WaitHandle waitHandle, CancellationToken cancellationToken, int timeoutMilliseconds = Timeout.Infinite)
7 | {
8 | if (waitHandle == null)
9 | throw new ArgumentNullException(nameof(waitHandle));
10 |
11 | var tcs = new TaskCompletionSource();
12 | var tokenRegistration = cancellationToken.Register(() => tcs.TrySetCanceled());
13 | var timeout = timeoutMilliseconds > Timeout.Infinite
14 | ? TimeSpan.FromMilliseconds(timeoutMilliseconds)
15 | : Timeout.InfiniteTimeSpan;
16 |
17 | var registeredWaitHandle = ThreadPool.RegisterWaitForSingleObject(waitHandle,
18 | (_, timedOut) =>
19 | {
20 | if (timedOut)
21 | {
22 | tcs.TrySetCanceled();
23 | }
24 | else
25 | {
26 | tcs.TrySetResult(true);
27 | }
28 | },
29 | null, timeout, true);
30 |
31 | _ = tcs.Task.ContinueWith(_ =>
32 | {
33 | registeredWaitHandle.Unregister(null);
34 | return tokenRegistration.Unregister();
35 | }, CancellationToken.None);
36 |
37 | return tcs.Task;
38 | }
39 | }
--------------------------------------------------------------------------------
/src/Platforms/MilkiBotFramework.Platforms.GoCqHttp/Connecting/GoCqApiException.cs:
--------------------------------------------------------------------------------
1 | namespace MilkiBotFramework.Platforms.GoCqHttp.Connecting;
2 |
3 | public class GoCqApiException : Exception
4 | {
5 | public GoCqApiException(string error, string message) : base(error + ": " + message)
6 | {
7 | }
8 | }
--------------------------------------------------------------------------------
/src/Platforms/MilkiBotFramework.Platforms.GoCqHttp/Connecting/GoCqClient.cs:
--------------------------------------------------------------------------------
1 | using System.Diagnostics.CodeAnalysis;
2 | using Microsoft.Extensions.Logging;
3 | using MilkiBotFramework.Connecting;
4 | using MilkiBotFramework.Platforms.GoCqHttp.Connecting.ResponseModel;
5 |
6 | namespace MilkiBotFramework.Platforms.GoCqHttp.Connecting;
7 |
8 | public sealed class GoCqClient : WebSocketClientConnector, IGoCqConnector
9 | {
10 | public GoCqClient(ILogger logger) : base(logger)
11 | {
12 | }
13 |
14 | public Task> SendMessageAsync(string action, IDictionary? @params)
15 | {
16 | return SendMessageAsync