├── .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(action, @params); 17 | } 18 | 19 | public Task> SendMessageAsync(string action, IDictionary? @params) 20 | { 21 | return GoCqWebSocketHelper.SendMessageAsync(this, action, @params); 22 | } 23 | 24 | protected override bool TryGetStateByMessage(string msg, [NotNullWhen(true)] out string? state) 25 | { 26 | return GoCqWebSocketHelper.TryGetStateByMessage(this, msg, out state); 27 | } 28 | } -------------------------------------------------------------------------------- /src/Platforms/MilkiBotFramework.Platforms.GoCqHttp/Connecting/GoCqKestrelConnector.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | using Microsoft.AspNetCore.Builder; 3 | using Microsoft.Extensions.Logging; 4 | using MilkiBotFramework.Aspnetcore; 5 | using MilkiBotFramework.Connecting; 6 | using MilkiBotFramework.Platforms.GoCqHttp.Connecting.ResponseModel; 7 | 8 | namespace MilkiBotFramework.Platforms.GoCqHttp.Connecting; 9 | 10 | public sealed class GoCqKestrelConnector : AspnetcoreConnector, IGoCqConnector 11 | { 12 | private readonly LightHttpClient _lightHttpClient; 13 | 14 | public Task> SendMessageAsync(string action, IDictionary? @params) 15 | { 16 | return SendMessageAsync(action, @params); 17 | } 18 | 19 | public async Task> SendMessageAsync(string action, IDictionary? @params) 20 | { 21 | if (ConnectionType == ConnectionType.ReverseWebSocket) 22 | return await GoCqWebSocketHelper.SendMessageAsync(this, action, @params); 23 | if (WebSocketConnector == null) 24 | return await _lightHttpClient.HttpPost>(TargetUri + "/" + action, @params); 25 | if (WebSocketConnector is IGoCqConnector goCqConnector) 26 | return await goCqConnector.SendMessageAsync(action, @params); 27 | throw new ArgumentException(null, nameof(WebSocketConnector)); 28 | } 29 | 30 | protected override bool TryGetStateByMessage(string msg, [NotNullWhen(true)] out string? state) 31 | { 32 | return GoCqWebSocketHelper.TryGetStateByMessage(this, msg, out state); 33 | } 34 | 35 | public GoCqKestrelConnector(IWebSocketConnector? webSocketConnector, 36 | ILogger logger, 37 | LightHttpClient lightHttpClient, 38 | WebApplication webApplication) 39 | : base(webSocketConnector, logger, webApplication) 40 | { 41 | _lightHttpClient = lightHttpClient; 42 | } 43 | } -------------------------------------------------------------------------------- /src/Platforms/MilkiBotFramework.Platforms.GoCqHttp/Connecting/GoCqServer.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 GoCqServer : WebSocketServerConnector, IGoCqConnector 9 | { 10 | public GoCqServer(ILogger logger) : base(logger) 11 | { 12 | } 13 | 14 | public Task> SendMessageAsync(string action, IDictionary? @params) 15 | { 16 | return SendMessageAsync(action, @params); 17 | } 18 | 19 | public Task> SendMessageAsync(string action, IDictionary? @params) 20 | { 21 | return GoCqWebSocketHelper.SendMessageAsync(this, action, @params); 22 | } 23 | 24 | protected override bool TryGetStateByMessage(string msg, [NotNullWhen(true)] out string? state) 25 | { 26 | return GoCqWebSocketHelper.TryGetStateByMessage(this, msg, out state); 27 | } 28 | } -------------------------------------------------------------------------------- /src/Platforms/MilkiBotFramework.Platforms.GoCqHttp/Connecting/GoCqWebsocketHelper.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | using System.Text.Json; 3 | using MilkiBotFramework.Connecting; 4 | using MilkiBotFramework.Platforms.GoCqHttp.Connecting.RequestModel; 5 | using MilkiBotFramework.Platforms.GoCqHttp.Connecting.ResponseModel; 6 | 7 | namespace MilkiBotFramework.Platforms.GoCqHttp.Connecting; 8 | 9 | internal static class GoCqWebSocketHelper 10 | { 11 | public static async Task> SendMessageAsync(IConnector connector, 12 | string action, 13 | IDictionary? @params) 14 | { 15 | var state = Guid.NewGuid().ToString("B"); 16 | var req = new GoCqRequest 17 | { 18 | Action = action, 19 | Params = @params, 20 | State = state 21 | }; 22 | var reqJson = JsonSerializer.Serialize(req); 23 | var str = await connector.SendMessageAsync(reqJson, state); 24 | return JsonSerializer.Deserialize>(str)!; 25 | } 26 | 27 | public static bool TryGetStateByMessage(IConnector connector, 28 | string msg, 29 | [NotNullWhen(true)] out string? state) 30 | { 31 | var jDoc = JsonDocument.Parse(msg); 32 | var hasProperty = jDoc.RootElement.TryGetProperty("echo", out var echoElement); 33 | if (!hasProperty) 34 | { 35 | state = null; 36 | return false; 37 | } 38 | 39 | state = echoElement.GetString(); 40 | return !string.IsNullOrEmpty(state); 41 | } 42 | } -------------------------------------------------------------------------------- /src/Platforms/MilkiBotFramework.Platforms.GoCqHttp/Connecting/IGoCqConnector.cs: -------------------------------------------------------------------------------- 1 | using MilkiBotFramework.Platforms.GoCqHttp.Connecting.ResponseModel; 2 | 3 | namespace MilkiBotFramework.Platforms.GoCqHttp.Connecting; 4 | 5 | public interface IGoCqConnector 6 | { 7 | Task> SendMessageAsync(string action, IDictionary? @params); 8 | Task> SendMessageAsync(string action, IDictionary? @params); 9 | } -------------------------------------------------------------------------------- /src/Platforms/MilkiBotFramework.Platforms.GoCqHttp/Connecting/RequestModel/FriendAddRequest.cs: -------------------------------------------------------------------------------- 1 | #nullable disable 2 | 3 | using System.Text.Json.Serialization; 4 | 5 | namespace MilkiBotFramework.Platforms.GoCqHttp.Connecting.RequestModel 6 | { 7 | public class FriendAddRequest 8 | { 9 | [JsonPropertyName("flag")] 10 | public string Flag { get; set; } 11 | 12 | [JsonPropertyName("approve")] 13 | public bool Approve { get; set; } = true; 14 | 15 | [JsonPropertyName("remark")] 16 | public string Remark { get; set; } 17 | 18 | public static FriendAddRequest GetRefuse(string flag) 19 | { 20 | return new FriendAddRequest { Approve = false, Flag = flag }; 21 | } 22 | 23 | public static FriendAddRequest GetApprove(string flag, string remark = null) 24 | { 25 | return new FriendAddRequest { Approve = true, Flag = flag, Remark = remark }; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Platforms/MilkiBotFramework.Platforms.GoCqHttp/Connecting/RequestModel/GoCqRequest.cs: -------------------------------------------------------------------------------- 1 | #nullable disable 2 | 3 | using System.Text.Json.Serialization; 4 | 5 | namespace MilkiBotFramework.Platforms.GoCqHttp.Connecting.RequestModel; 6 | 7 | public sealed class GoCqRequest 8 | { 9 | [JsonPropertyName("echo")] 10 | public string State { get; set; } 11 | 12 | [JsonPropertyName("action")] 13 | public string Action { get; set; } 14 | 15 | [JsonPropertyName("params")] 16 | public IDictionary Params { get; set; } 17 | } -------------------------------------------------------------------------------- /src/Platforms/MilkiBotFramework.Platforms.GoCqHttp/Connecting/RequestModel/GroupAddRequest.cs: -------------------------------------------------------------------------------- 1 | #nullable disable 2 | 3 | using System.Text.Json.Serialization; 4 | 5 | namespace MilkiBotFramework.Platforms.GoCqHttp.Connecting.RequestModel 6 | { 7 | public class GroupAddRequest 8 | { 9 | [JsonPropertyName("flag")] 10 | public string Flag { get; set; } 11 | 12 | [JsonPropertyName("type")] 13 | public string Type { get; set; } 14 | 15 | [JsonPropertyName("sub_type")] 16 | public string SubType { get; set; } 17 | 18 | [JsonPropertyName("approve")] 19 | public bool Approve { get; set; } = true; 20 | 21 | [JsonPropertyName("reason")] 22 | public string Reason { get; set; } 23 | 24 | public static GroupAddRequest GetInviteRefuse(string flag, string reason = null) 25 | { 26 | return new GroupAddRequest { Approve = false, Flag = flag, SubType = "invite", Type = "invite", Reason = reason }; 27 | } 28 | 29 | public static GroupAddRequest GetInviteApprove(string flag) 30 | { 31 | return new GroupAddRequest { Approve = true, Flag = flag, SubType = "invite", Type = "invite" }; 32 | } 33 | 34 | public static GroupAddRequest GetRefuseAsAdmin(string flag, string reason = null) 35 | { 36 | return new GroupAddRequest { Approve = false, Flag = flag, SubType = "add", Type = "add", Reason = reason }; 37 | } 38 | 39 | public static GroupAddRequest GetApproveAsAdmin(string flag) 40 | { 41 | return new GroupAddRequest { Approve = true, Flag = flag, SubType = "add", Type = "add" }; 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /src/Platforms/MilkiBotFramework.Platforms.GoCqHttp/Connecting/RequestModel/GroupMsgResponse.cs: -------------------------------------------------------------------------------- 1 | #nullable disable 2 | 3 | using System.Text.Json.Serialization; 4 | 5 | namespace MilkiBotFramework.Platforms.GoCqHttp.Connecting.RequestModel 6 | { 7 | public class GroupMsgResponse 8 | { 9 | [JsonPropertyName("reply")] 10 | public string Reply { get; set; } 11 | 12 | [JsonPropertyName("auto_escape")] 13 | public bool AutoEscape { get; set; } 14 | 15 | [JsonPropertyName("at_sender")] 16 | public bool AtSender { get; set; } 17 | 18 | [JsonPropertyName("delete")] 19 | public bool Delete { get; set; } 20 | 21 | [JsonPropertyName("kick")] 22 | public bool Kick { get; set; } 23 | 24 | [JsonPropertyName("ban")] 25 | public bool Ban { get; set; } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Platforms/MilkiBotFramework.Platforms.GoCqHttp/Connecting/RequestModel/PrivateMsgResponse.cs: -------------------------------------------------------------------------------- 1 | #nullable disable 2 | 3 | using System.Text.Json.Serialization; 4 | 5 | namespace MilkiBotFramework.Platforms.GoCqHttp.Connecting.RequestModel 6 | { 7 | public class PrivateMsgResponse 8 | { 9 | [JsonPropertyName("reply")] 10 | public string Reply { get; set; } 11 | 12 | [JsonPropertyName("auto_escape")] 13 | public bool AutoEscape { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/Platforms/MilkiBotFramework.Platforms.GoCqHttp/Connecting/RequestModel/SendDiscussMsg.cs: -------------------------------------------------------------------------------- 1 | #nullable disable 2 | 3 | using System.Text.Json.Serialization; 4 | 5 | namespace MilkiBotFramework.Platforms.GoCqHttp.Connecting.RequestModel 6 | { 7 | public class SendDiscussMsg 8 | { 9 | [JsonPropertyName("discuss_id")] 10 | public long DiscussId { get; set; } 11 | 12 | [JsonPropertyName("message")] 13 | public string Message { get; set; } 14 | 15 | [JsonPropertyName("auto_escape")] 16 | public bool AutoEscape { get; set; } 17 | 18 | public SendDiscussMsg(string discussId, string message, bool autoEscape = false) 19 | { 20 | DiscussId = long.Parse(discussId); 21 | Message = message; 22 | AutoEscape = autoEscape; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Platforms/MilkiBotFramework.Platforms.GoCqHttp/Connecting/RequestModel/SendGroupMsg.cs: -------------------------------------------------------------------------------- 1 | #nullable disable 2 | 3 | using System.Text.Json.Serialization; 4 | 5 | namespace MilkiBotFramework.Platforms.GoCqHttp.Connecting.RequestModel 6 | { 7 | public class SendGroupMsg 8 | { 9 | [JsonPropertyName("group_id")] 10 | public long GroupId { get; set; } 11 | 12 | [JsonPropertyName("message")] 13 | public string Message { get; set; } 14 | 15 | [JsonPropertyName("auto_escape")] 16 | public bool AutoEscape { get; set; } 17 | 18 | public SendGroupMsg(string groupId, string message, bool autoEscape = false) 19 | { 20 | GroupId = long.Parse(groupId); 21 | Message = message; 22 | AutoEscape = autoEscape; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Platforms/MilkiBotFramework.Platforms.GoCqHttp/Connecting/RequestModel/SendPrivateMsg.cs: -------------------------------------------------------------------------------- 1 | #nullable disable 2 | 3 | using System.Text.Json.Serialization; 4 | 5 | namespace MilkiBotFramework.Platforms.GoCqHttp.Connecting.RequestModel 6 | { 7 | public class SendPrivateMsg 8 | { 9 | [JsonPropertyName("user_id")] 10 | public long UserId { get; set; } 11 | 12 | [JsonPropertyName("message")] 13 | public string Message { get; set; } 14 | 15 | [JsonPropertyName("auto_escape")] 16 | public bool AutoEscape { get; set; } 17 | 18 | public SendPrivateMsg(string userId, string message, bool autoEscape = false) 19 | { 20 | UserId = long.Parse(userId); 21 | Message = message; 22 | AutoEscape = autoEscape; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Platforms/MilkiBotFramework.Platforms.GoCqHttp/Connecting/ResponseModel/ChannelType.cs: -------------------------------------------------------------------------------- 1 | namespace MilkiBotFramework.Platforms.GoCqHttp.Connecting.ResponseModel; 2 | 3 | public enum ChannelType 4 | { 5 | Text = 1, Voice = 2, Broadcast = 5 6 | } -------------------------------------------------------------------------------- /src/Platforms/MilkiBotFramework.Platforms.GoCqHttp/Connecting/ResponseModel/FriendInfo.cs: -------------------------------------------------------------------------------- 1 | #nullable disable 2 | 3 | using System.Text.Json.Serialization; 4 | using MilkiBotFramework.Platforms.GoCqHttp.Internal; 5 | 6 | namespace MilkiBotFramework.Platforms.GoCqHttp.Connecting.ResponseModel 7 | { 8 | public class FriendInfo 9 | { 10 | [JsonPropertyName("user_id")] 11 | [JsonConverter(typeof(Int64ToStringConverter))] 12 | public string UserId { get; set; } 13 | 14 | [JsonPropertyName("nickname")] 15 | public string Nickname { get; set; } 16 | 17 | [JsonPropertyName("remark")] 18 | public string Remark { get; set; } 19 | } 20 | } -------------------------------------------------------------------------------- /src/Platforms/MilkiBotFramework.Platforms.GoCqHttp/Connecting/ResponseModel/GetMsgResponse.cs: -------------------------------------------------------------------------------- 1 | #nullable disable 2 | 3 | using System.Text.Json.Serialization; 4 | using MilkiBotFramework.Platforms.GoCqHttp.Internal; 5 | 6 | namespace MilkiBotFramework.Platforms.GoCqHttp.Connecting.ResponseModel 7 | { 8 | // ReSharper disable once InconsistentNaming 9 | public class GetMsgResponse 10 | { 11 | [JsonPropertyName("group")] 12 | public bool Group { get; set; } 13 | 14 | [JsonPropertyName("group_id")] 15 | public long GroupId { get; set; } 16 | 17 | [JsonPropertyName("message")] 18 | public string Message { get; set; } 19 | 20 | [JsonPropertyName("message_id")] 21 | public string MessageId { get; set; } 22 | 23 | [JsonPropertyName("message_seq")] 24 | public long MessageSeq { get; set; } 25 | 26 | [JsonPropertyName("message_type")] 27 | public string MessageType { get; set; } 28 | 29 | [JsonPropertyName("raw_message")] 30 | public string RawMessage { get; set; } 31 | 32 | [JsonPropertyName("real_id")] 33 | public long RealId { get; set; } 34 | 35 | [JsonPropertyName("sender")] 36 | public Sender Sender { get; set; } 37 | 38 | [JsonPropertyName("time")] 39 | [JsonConverter(typeof(UnixDateTimeConverter))] 40 | public DateTimeOffset Time { get; set; } 41 | } 42 | 43 | public class Sender 44 | { 45 | [JsonPropertyName("nickname")] 46 | public string Nickname { get; set; } 47 | 48 | [JsonPropertyName("user_id")] 49 | public long UserId { get; set; } 50 | } 51 | } -------------------------------------------------------------------------------- /src/Platforms/MilkiBotFramework.Platforms.GoCqHttp/Connecting/ResponseModel/GoCqApiResponse.cs: -------------------------------------------------------------------------------- 1 | #nullable disable 2 | 3 | using System.Text.Json.Serialization; 4 | 5 | namespace MilkiBotFramework.Platforms.GoCqHttp.Connecting.ResponseModel; 6 | 7 | public sealed class GoCqApiResponse 8 | { 9 | [JsonPropertyName("echo")] 10 | public string State { get; set; } 11 | 12 | [JsonPropertyName("retcode")] 13 | public int RetCode { get; set; } 14 | 15 | [JsonPropertyName("status")] 16 | public string Status { get; set; } 17 | 18 | [JsonPropertyName("data")] 19 | public T Data { get; set; } 20 | 21 | [JsonPropertyName("msg")] 22 | public string Msg { get; set; } 23 | 24 | [JsonPropertyName("wording")] 25 | public string Wording { get; set; } 26 | } -------------------------------------------------------------------------------- /src/Platforms/MilkiBotFramework.Platforms.GoCqHttp/Connecting/ResponseModel/GroupInfo.cs: -------------------------------------------------------------------------------- 1 | #nullable disable 2 | 3 | using System.Text.Json.Serialization; 4 | using MilkiBotFramework.Platforms.GoCqHttp.Internal; 5 | 6 | namespace MilkiBotFramework.Platforms.GoCqHttp.Connecting.ResponseModel 7 | { 8 | public class GroupInfo 9 | { 10 | [JsonPropertyName("group_id")] 11 | [JsonConverter(typeof(Int64ToStringConverter))] 12 | public string GroupId { get; set; } 13 | 14 | [JsonPropertyName("group_name")] 15 | public string GroupName { get; set; } 16 | 17 | [JsonPropertyName("group_memo")] 18 | public string GroupMemo { get; set; } 19 | 20 | [JsonPropertyName("group_create_time")] 21 | [JsonConverter(typeof(UnixDateTimeConverter))] 22 | public DateTimeOffset GroupCreateTime { get; set; } 23 | 24 | [JsonPropertyName("group_level")] 25 | public int GroupLevel { get; set; } 26 | 27 | [JsonPropertyName("member_count")] 28 | public int MemberCount { get; set; } 29 | 30 | [JsonPropertyName("max_member_count")] 31 | public int MaxMemberCount { get; set; } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Platforms/MilkiBotFramework.Platforms.GoCqHttp/Connecting/ResponseModel/GroupMemberInfo.cs: -------------------------------------------------------------------------------- 1 | #nullable disable 2 | 3 | using System.Text.Json.Serialization; 4 | using MilkiBotFramework.Platforms.GoCqHttp.Internal; 5 | 6 | namespace MilkiBotFramework.Platforms.GoCqHttp.Connecting.ResponseModel 7 | { 8 | public class GroupMember 9 | { 10 | [JsonPropertyName("group_id")] 11 | [JsonConverter(typeof(Int64ToStringConverter))] 12 | public string GroupId { get; set; } 13 | 14 | [JsonPropertyName("user_id")] 15 | [JsonConverter(typeof(Int64ToStringConverter))] 16 | public string UserId { get; set; } 17 | 18 | [JsonPropertyName("nickname")] 19 | public string Nickname { get; set; } 20 | 21 | [JsonPropertyName("card")] 22 | public string Card { get; set; } 23 | 24 | [JsonPropertyName("sex")] 25 | public string Sex { get; set; } 26 | 27 | [JsonPropertyName("age")] 28 | public int Age { get; set; } 29 | 30 | [JsonPropertyName("area")] 31 | public string Area { get; set; } 32 | 33 | [JsonPropertyName("join_time")] 34 | [JsonConverter(typeof(UnixDateTimeConverter))] 35 | public DateTimeOffset JoinTime { get; set; } 36 | 37 | [JsonPropertyName("last_sent_time")] 38 | [JsonConverter(typeof(UnixDateTimeConverter))] 39 | public DateTimeOffset LastSentTime { get; set; } 40 | 41 | [JsonPropertyName("level")] 42 | public string Level { get; set; } 43 | 44 | [JsonPropertyName("role")] 45 | public string Role { get; set; } 46 | 47 | [JsonPropertyName("unfriendly")] 48 | public bool Unfriendly { get; set; } 49 | 50 | [JsonPropertyName("title")] 51 | public string Title { get; set; } 52 | 53 | [JsonPropertyName("title_expire_time")] 54 | [JsonConverter(typeof(UnixDateTimeConverter))] 55 | public DateTimeOffset TitleExpireTime { get; set; } 56 | 57 | [JsonPropertyName("card_changeable")] 58 | public bool CardChangeable { get; set; } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/Platforms/MilkiBotFramework.Platforms.GoCqHttp/Connecting/ResponseModel/Guild/GetGuildMembersResponse.cs: -------------------------------------------------------------------------------- 1 | #nullable disable 2 | 3 | using System.Text.Json.Serialization; 4 | 5 | namespace MilkiBotFramework.Platforms.GoCqHttp.Connecting.ResponseModel.Guild; 6 | 7 | // ReSharper disable once InconsistentNaming 8 | public class GetGuildMembersResponse 9 | { 10 | [JsonPropertyName("admins")] 11 | public List Admins { get; set; } 12 | 13 | [JsonPropertyName("bots")] 14 | public List Bots { get; set; } 15 | 16 | [JsonPropertyName("members")] 17 | public List Members { get; set; } 18 | } 19 | 20 | public class GuildServiceProfile 21 | { 22 | [JsonPropertyName("nickname")] 23 | public string Nickname { get; set; } 24 | 25 | [JsonPropertyName("tiny_id")] 26 | public long TinyId { get; set; } 27 | 28 | [JsonPropertyName("avatar_url")] 29 | public string AvatarUrl { get; set; } 30 | } -------------------------------------------------------------------------------- /src/Platforms/MilkiBotFramework.Platforms.GoCqHttp/Connecting/ResponseModel/Guild/GuildBrief.cs: -------------------------------------------------------------------------------- 1 | #nullable disable 2 | 3 | using System.Text.Json.Serialization; 4 | 5 | namespace MilkiBotFramework.Platforms.GoCqHttp.Connecting.ResponseModel.Guild; 6 | 7 | public class GuildBrief 8 | { 9 | [JsonPropertyName("guild_display_id")] 10 | public long GuildDisplayId { get; set; } 11 | 12 | [JsonPropertyName("guild_id")] 13 | public long GuildId { get; set; } 14 | 15 | [JsonPropertyName("guild_name")] 16 | public string GuildName { get; set; } 17 | } -------------------------------------------------------------------------------- /src/Platforms/MilkiBotFramework.Platforms.GoCqHttp/Connecting/ResponseModel/Guild/GuildInfo.cs: -------------------------------------------------------------------------------- 1 | #nullable disable 2 | 3 | using System.Text.Json.Serialization; 4 | using MilkiBotFramework.Platforms.GoCqHttp.Internal; 5 | 6 | namespace MilkiBotFramework.Platforms.GoCqHttp.Connecting.ResponseModel.Guild; 7 | 8 | public class GuildInfo 9 | { 10 | [JsonPropertyName("create_time")] 11 | [JsonConverter(typeof(UnixDateTimeConverter))] 12 | public DateTimeOffset CreateTime { get; set; } 13 | 14 | //[JsonPropertyName("guild_id")] 15 | //public long GuildId { get; set; } 16 | 17 | [JsonPropertyName("guild_name")] 18 | public string GuildName { get; set; } 19 | 20 | [JsonPropertyName("guild_profile")] 21 | public string GuildProfile { get; set; } 22 | 23 | [JsonPropertyName("max_admin_count")] 24 | public int MaxAdminCount { get; set; } 25 | 26 | [JsonPropertyName("max_member_count")] 27 | public int MaxMemberCount { get; set; } 28 | 29 | [JsonPropertyName("max_robot_count")] 30 | public int MaxRobotCount { get; set; } 31 | 32 | [JsonPropertyName("member_count")] 33 | public int MemberCount { get; set; } 34 | 35 | [JsonPropertyName("owner_id")] 36 | public long OwnerId { get; set; } 37 | } -------------------------------------------------------------------------------- /src/Platforms/MilkiBotFramework.Platforms.GoCqHttp/Connecting/ResponseModel/Guild/GuildMember.cs: -------------------------------------------------------------------------------- 1 | #nullable disable 2 | 3 | using System.Text.Json.Serialization; 4 | 5 | namespace MilkiBotFramework.Platforms.GoCqHttp.Connecting.ResponseModel.Guild; 6 | 7 | public class GuildMember 8 | { 9 | [JsonPropertyName("nickname")] 10 | public string Nickname { get; set; } 11 | 12 | [JsonPropertyName("role")] 13 | public int Role { get; set; } 14 | 15 | [JsonPropertyName("tiny_id")] 16 | public long TinyId { get; set; } 17 | 18 | [JsonPropertyName("title")] 19 | public string Title { get; set; } 20 | } -------------------------------------------------------------------------------- /src/Platforms/MilkiBotFramework.Platforms.GoCqHttp/Connecting/ResponseModel/Guild/SlowMode.cs: -------------------------------------------------------------------------------- 1 | #nullable disable 2 | 3 | using System.Text.Json.Serialization; 4 | 5 | namespace MilkiBotFramework.Platforms.GoCqHttp.Connecting.ResponseModel.Guild; 6 | 7 | public class SlowMode 8 | { 9 | [JsonPropertyName("slow_mode_circle")] 10 | public long SlowModeCircle { get; set; } 11 | 12 | [JsonPropertyName("slow_mode_key")] 13 | public long SlowModeKey { get; set; } 14 | 15 | [JsonPropertyName("slow_mode_text")] 16 | public string SlowModeText { get; set; } 17 | 18 | [JsonPropertyName("speak_frequency")] 19 | public long SpeakFrequency { get; set; } 20 | } -------------------------------------------------------------------------------- /src/Platforms/MilkiBotFramework.Platforms.GoCqHttp/Connecting/ResponseModel/Guild/SubChannelInfo.cs: -------------------------------------------------------------------------------- 1 | #nullable disable 2 | 3 | using System.Text.Json.Serialization; 4 | using MilkiBotFramework.Platforms.GoCqHttp.Internal; 5 | 6 | namespace MilkiBotFramework.Platforms.GoCqHttp.Connecting.ResponseModel.Guild; 7 | 8 | public class SubChannelInfo 9 | { 10 | [JsonPropertyName("channel_id")] 11 | public long ChannelId { get; set; } 12 | 13 | [JsonPropertyName("channel_name")] 14 | public string ChannelName { get; set; } 15 | 16 | [JsonPropertyName("channel_type")] 17 | public ChannelType ChannelType { get; set; } 18 | 19 | [JsonPropertyName("create_time")] 20 | [JsonConverter(typeof(UnixDateTimeConverter))] 21 | public DateTimeOffset CreateTime { get; set; } 22 | 23 | [JsonPropertyName("creator_id")] 24 | public long CreatorId { get; set; } 25 | 26 | [JsonPropertyName("creator_tiny_id")] 27 | public long CreatorTinyId { get; set; } 28 | 29 | [JsonPropertyName("current_slow_mode")] 30 | public int CurrentSlowMode { get; set; } 31 | 32 | [JsonPropertyName("owner_guild_id")] 33 | public long OwnerGuildId { get; set; } 34 | 35 | [JsonPropertyName("slow_modes")] 36 | public List SlowModes { get; set; } 37 | 38 | [JsonPropertyName("talk_permission")] 39 | public int TalkPermission { get; set; } 40 | 41 | [JsonPropertyName("visible_type")] 42 | public int VisibleType { get; set; } 43 | } -------------------------------------------------------------------------------- /src/Platforms/MilkiBotFramework.Platforms.GoCqHttp/Connecting/ResponseModel/LoginInfo.cs: -------------------------------------------------------------------------------- 1 | #nullable disable 2 | 3 | using System.Text.Json.Serialization; 4 | 5 | namespace MilkiBotFramework.Platforms.GoCqHttp.Connecting.ResponseModel 6 | { 7 | public class LoginInfo 8 | { 9 | [JsonPropertyName("user_id")] 10 | public long UserId { get; set; } 11 | 12 | [JsonPropertyName("nickname")] 13 | public string Nickname { get; set; } 14 | } 15 | } -------------------------------------------------------------------------------- /src/Platforms/MilkiBotFramework.Platforms.GoCqHttp/Connecting/ResponseModel/MsgResponse.cs: -------------------------------------------------------------------------------- 1 | #nullable disable 2 | 3 | using System.Text.Json.Serialization; 4 | using MilkiBotFramework.Platforms.GoCqHttp.Internal; 5 | 6 | namespace MilkiBotFramework.Platforms.GoCqHttp.Connecting.ResponseModel 7 | { 8 | public class MsgResponse 9 | { 10 | [JsonPropertyName("message_id")] 11 | [JsonConverter(typeof(Int64ToStringConverter))] 12 | public string MessageId { get; set; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/Platforms/MilkiBotFramework.Platforms.GoCqHttp/Connecting/ResponseModel/StrangerInfo.cs: -------------------------------------------------------------------------------- 1 | #nullable disable 2 | 3 | using System.Text.Json.Serialization; 4 | 5 | namespace MilkiBotFramework.Platforms.GoCqHttp.Connecting.ResponseModel 6 | { 7 | public class StrangerInfo 8 | { 9 | [JsonPropertyName("user_id")] 10 | public long UserId { get; set; } 11 | 12 | [JsonPropertyName("nickname")] 13 | public string Nickname { get; set; } 14 | 15 | [JsonPropertyName("sex")] 16 | public string Sex { get; set; } 17 | 18 | [JsonPropertyName("age")] 19 | public int Age { get; set; } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Platforms/MilkiBotFramework.Platforms.GoCqHttp/GoCqBotOptions.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | 3 | namespace MilkiBotFramework.Platforms.GoCqHttp 4 | { 5 | public class GoCqBotOptions : BotOptions 6 | { 7 | [Description("go-cqhttp连接设置")] 8 | public GoCqConnection Connection { get; set; } = new(); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/Platforms/MilkiBotFramework.Platforms.GoCqHttp/GoCqConnection.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using MilkiBotFramework.Connecting; 3 | 4 | namespace MilkiBotFramework.Platforms.GoCqHttp; 5 | 6 | public class GoCqConnection 7 | { 8 | public GoCqConnection() 9 | { 10 | } 11 | 12 | public GoCqConnection(ConnectionType connectionType, string? targetUri, string? serverBindUrl, string? serverBindPath) 13 | { 14 | ConnectionType = connectionType; 15 | TargetUri = targetUri; 16 | ServerBindPath = serverBindPath; 17 | ServerBindUrl = serverBindUrl; 18 | } 19 | 20 | [Description("连接方式,支持: Http, WebSocket, ReverseWebSocket")] 21 | public ConnectionType ConnectionType { get; set; } = ConnectionType.WebSocket; 22 | 23 | [Description("目标接口链接。当 ConnectionType 为 Http, WebSocket 时生效")] 24 | public string? TargetUri { get; set; } = "ws://127.0.0.1:5700"; 25 | 26 | [Description("服务器绑定Url。当 ConnectionType 为 Http, ReverseWebSocket 时生效")] 27 | public string? ServerBindUrl { get; set; } = "http://0.0.0.0:2333"; 28 | 29 | [Description("服务器绑定的具体路由。当 ConnectionType 为 Http, ReverseWebSocket 时生效")] 30 | public string? ServerBindPath { get; set; } = "/endpoint"; 31 | 32 | public static GoCqConnection WebSocket(string targetUri) 33 | { 34 | return new GoCqConnection(ConnectionType.WebSocket, targetUri, null, null); 35 | } 36 | 37 | public static GoCqConnection ReverseWebSocket(string serverBindUrl, string serverBindPath) 38 | { 39 | return new GoCqConnection(ConnectionType.ReverseWebSocket, null, serverBindUrl, serverBindPath); 40 | } 41 | 42 | public static GoCqConnection Http(string targetUri, string serverBindUrl, string serverBindPath) 43 | { 44 | return new GoCqConnection(ConnectionType.Http, targetUri, serverBindUrl, serverBindPath); 45 | } 46 | } -------------------------------------------------------------------------------- /src/Platforms/MilkiBotFramework.Platforms.GoCqHttp/GoCqParameterConverter.cs: -------------------------------------------------------------------------------- 1 | using MilkiBotFramework.Messaging.RichMessages; 2 | using MilkiBotFramework.Plugining.Loading; 3 | 4 | namespace MilkiBotFramework.Platforms.GoCqHttp; 5 | 6 | public class GoCqParameterConverter : DefaultParameterConverter 7 | { 8 | public override object Convert(Type targetType, ReadOnlyMemory source) 9 | { 10 | if (targetType != typeof(LinkImage)) 11 | return base.Convert(targetType, source); 12 | 13 | var span = source.Span; 14 | var index = span.IndexOf(",url=", StringComparison.Ordinal); 15 | 16 | if (index == -1) 17 | return base.Convert(targetType, source); 18 | 19 | var subSpan = span[index..]; 20 | var endIndex = subSpan.IndexOf(']'); 21 | if (endIndex == -1) 22 | return base.Convert(targetType, source); 23 | 24 | return new LinkImage(subSpan.Slice(5, endIndex - 5).ToString()); 25 | } 26 | } -------------------------------------------------------------------------------- /src/Platforms/MilkiBotFramework.Platforms.GoCqHttp/Internal/Int64ToStringConverter.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace MilkiBotFramework.Platforms.GoCqHttp.Internal 5 | { 6 | internal class Int64ToStringConverter : JsonConverter 7 | { 8 | public override string? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) 9 | { 10 | if (reader.TokenType == JsonTokenType.String) 11 | return reader.GetString(); 12 | var l = reader.GetInt64(); 13 | return l.ToString(); 14 | } 15 | 16 | public override void Write(Utf8JsonWriter writer, string value, JsonSerializerOptions options) 17 | { 18 | writer.WriteNumberValue(long.Parse(value)); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Platforms/MilkiBotFramework.Platforms.GoCqHttp/Internal/UnixDateTimeConverter.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace MilkiBotFramework.Platforms.GoCqHttp.Internal 5 | { 6 | internal class UnixDateTimeConverter : JsonConverter 7 | { 8 | private static readonly DateTime UnixEpoch = new(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); 9 | private static readonly Type ObjectType = typeof(DateTimeOffset); 10 | 11 | public override DateTimeOffset Read(ref Utf8JsonReader reader, Type typeToConvert, 12 | JsonSerializerOptions options) 13 | { 14 | long result; 15 | if (reader.TokenType == JsonTokenType.Number) 16 | { 17 | result = reader.GetInt64(); 18 | } 19 | else 20 | { 21 | if (reader.TokenType != JsonTokenType.String) 22 | throw new JsonException( 23 | $"Unexpected token parsing date. Expected Integer or String, got {reader.TokenType}."); 24 | 25 | var str = reader.GetString(); 26 | if (!long.TryParse(str, out result)) 27 | throw new JsonException($"Cannot convert invalid value to {ObjectType}."); 28 | } 29 | 30 | DateTime dateTime = result >= 0L 31 | ? UnixEpoch.AddSeconds(result) 32 | : throw new JsonException( 33 | $"Cannot convert value that is before Unix epoch of 00:00:00 UTC on 1 January 1970 to {ObjectType}."); 34 | return new DateTimeOffset(dateTime, TimeSpan.Zero); 35 | } 36 | 37 | public override void Write(Utf8JsonWriter writer, DateTimeOffset value, JsonSerializerOptions options) 38 | { 39 | var totalSeconds = (long)(value.ToUniversalTime() - UnixEpoch).TotalSeconds; 40 | 41 | if (totalSeconds < 0L) 42 | throw new JsonException("Cannot convert date value that is before Unix epoch of 00:00:00 UTC on 1 January 1970."); 43 | writer.WriteNumberValue(totalSeconds); 44 | } 45 | 46 | // ReSharper disable once UnusedMember.Local 47 | private static bool IsNullable(Type t) 48 | { 49 | return !t.IsValueType || IsNullableType(t); 50 | } 51 | 52 | private static bool IsNullableType(Type t) 53 | { 54 | return t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Nullable<>); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/Platforms/MilkiBotFramework.Platforms.GoCqHttp/Messaging/CqCodes/CQAt.cs: -------------------------------------------------------------------------------- 1 | using MilkiBotFramework.Messaging.RichMessages; 2 | 3 | namespace MilkiBotFramework.Platforms.GoCqHttp.Messaging.CqCodes 4 | { 5 | /// 6 | /// @某人 7 | /// 8 | // ReSharper disable once InconsistentNaming 9 | public class CQAt : At 10 | { 11 | ///// 12 | ///// 被@的群成员QQ。 13 | ///// 14 | //public string UserId { get; } 15 | 16 | /// 17 | /// @某人。 18 | /// 19 | /// 为被@的群成员QQ。若该参数为all,则@全体成员(次数用尽或权限不足则会转换为文本)。 20 | public CQAt(string userId) : base(userId) 21 | { 22 | UserId = userId; 23 | } 24 | 25 | internal static CQAt Parse(ReadOnlyMemory content) 26 | { 27 | const int flagLen = 2; 28 | var s = content.Slice(5 + flagLen, content.Length - 6 - flagLen).ToString(); 29 | var dictionary = CQCodeHelper.GetParameters(s); 30 | if (!dictionary.TryGetValue("qq", out var qq)) 31 | throw new InvalidOperationException(nameof(CQAt) + "至少需要qq参数"); 32 | 33 | var userId = string.Equals(qq, "all", StringComparison.OrdinalIgnoreCase) ? "-1" : qq; 34 | var cq = new CQAt(userId); 35 | return cq; 36 | } 37 | 38 | public override string ToString() => "@" + (UserId == "-1" ? "<全体成员>" : UserId); 39 | public override ValueTask EncodeAsync() => ValueTask.FromResult($"[CQ:at,qq={UserId}]"); 40 | } 41 | } -------------------------------------------------------------------------------- /src/Platforms/MilkiBotFramework.Platforms.GoCqHttp/Messaging/CqCodes/CQCodeHelper.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | 3 | namespace MilkiBotFramework.Platforms.GoCqHttp.Messaging.CqCodes 4 | { 5 | /// 6 | /// CQ码 7 | /// 8 | // ReSharper disable once InconsistentNaming 9 | public static class CQCodeHelper 10 | { 11 | public static string Escape(string text) 12 | { 13 | var sb = new StringBuilder(text); 14 | return sb.Replace("&", "&") 15 | .Replace("[", "[") 16 | .Replace("]", "]") 17 | .Replace(",", ",") 18 | .ToString(); 19 | } 20 | 21 | public static string AntiEscape(string text) 22 | { 23 | var sb = new StringBuilder(text); 24 | return sb.Replace("[", "[") 25 | .Replace("]", "]") 26 | .Replace(",", ",") 27 | .Replace("&", "&") 28 | .ToString(); 29 | } 30 | 31 | /// 32 | /// 判断文本是否为数字。 33 | /// 34 | /// 要判断的文本。 35 | /// 36 | public static bool IsNum(string str) => 37 | str.All(char.IsDigit); 38 | 39 | /// 40 | /// 判断文本是否在某一范围(包含界限) 41 | /// 42 | /// 要判断的文本。 43 | /// 界限的下界。 44 | /// 界限的上界。 45 | /// 46 | public static bool InRange(string str, int lBound, int uBound) => 47 | int.Parse(str) >= lBound && int.Parse(str) <= uBound; 48 | 49 | public static Dictionary GetParameters(string content) 50 | { 51 | return content 52 | .Split(',') 53 | .Select(k => 54 | { 55 | var i = k.IndexOf('='); 56 | return new KeyValuePair(AntiEscape(k[..i]), AntiEscape(k[(i + 1)..])); 57 | }) 58 | .ToDictionary(k => k.Key, k => k.Value); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/Platforms/MilkiBotFramework.Platforms.GoCqHttp/Messaging/CqCodes/CQDice.cs: -------------------------------------------------------------------------------- 1 | using MilkiBotFramework.Messaging.RichMessages; 2 | 3 | namespace MilkiBotFramework.Platforms.GoCqHttp.Messaging.CqCodes 4 | { 5 | /// 6 | /// 掷骰子魔法表情 7 | /// 8 | // ReSharper disable once InconsistentNaming 9 | public class CQDice : IRichMessage 10 | { 11 | private CQDice() 12 | { 13 | } 14 | 15 | public static CQDice Instance { get; } = new(); 16 | public override string ToString() => "[掷色子]"; 17 | public ValueTask EncodeAsync() => ValueTask.FromResult("[CQ:dice]"); 18 | } 19 | } -------------------------------------------------------------------------------- /src/Platforms/MilkiBotFramework.Platforms.GoCqHttp/Messaging/CqCodes/CQImageType.cs: -------------------------------------------------------------------------------- 1 | namespace MilkiBotFramework.Platforms.GoCqHttp.Messaging.CqCodes 2 | { 3 | // ReSharper disable once InconsistentNaming 4 | public enum CQImageType 5 | { 6 | Normal, 表情包, 热图, 斗图, 智图, 贴图 = 7, 自拍, 贴图广告, 有待测试, 热搜图 = 13 7 | } 8 | } -------------------------------------------------------------------------------- /src/Platforms/MilkiBotFramework.Platforms.GoCqHttp/Messaging/CqCodes/CQMusic.cs: -------------------------------------------------------------------------------- 1 | using MilkiBotFramework.Messaging.RichMessages; 2 | 3 | namespace MilkiBotFramework.Platforms.GoCqHttp.Messaging.CqCodes 4 | { 5 | /// 6 | /// 发送音乐自定义分享 7 | /// 8 | // ReSharper disable once InconsistentNaming 9 | public class CQMusic : IRichMessage 10 | { 11 | /// 12 | /// 分享链接。 13 | /// 14 | public string LinkUrl { get; } 15 | 16 | /// 17 | /// 音频链接。 18 | /// 19 | public string AudioUrl { get; } 20 | 21 | /// 22 | /// 音乐的标题。 23 | /// 24 | public string Title { get; } 25 | 26 | /// 27 | /// 音乐的简介。 28 | /// 29 | public string? Content { get; } 30 | 31 | /// 32 | /// 音乐的封面图片链接。 33 | /// 34 | public string? ImageUrl { get; } 35 | 36 | /// 37 | /// 发送音乐自定义分享。注意:音乐自定义分享只能作为单独的一条消息发送。 38 | /// 39 | /// 为分享链接,即点击分享后进入的音乐页面(如歌曲介绍页)。 40 | /// 为音频链接(如mp3链接)。 41 | /// 为音乐的标题,建议12字以内。 42 | /// 为音乐的简介,建议30字以内。该参数可被忽略。 43 | /// 为音乐的封面图片链接。若参数为空或被忽略,则显示默认图片。 44 | public CQMusic(string linkUrl, string audioUrl, string title, string? content = null, string? imageUrl = null) 45 | { 46 | LinkUrl = CQCodeHelper.Escape(linkUrl); 47 | AudioUrl = CQCodeHelper.Escape(audioUrl); 48 | Title = CQCodeHelper.Escape(title); 49 | Content = content == null ? null : CQCodeHelper.Escape(content); 50 | ImageUrl = imageUrl == null ? null : CQCodeHelper.Escape(imageUrl); 51 | } 52 | 53 | public override string ToString() => "[音乐自定义分享]"; 54 | 55 | public ValueTask EncodeAsync() => ValueTask.FromResult(string.Format( 56 | "[CQ:music,type=custom,url={0},audio={1},title={2},content={3},image={4}]", LinkUrl, AudioUrl, Title, 57 | Content, ImageUrl)); 58 | } 59 | } -------------------------------------------------------------------------------- /src/Platforms/MilkiBotFramework.Platforms.GoCqHttp/Messaging/CqCodes/CQReply.cs: -------------------------------------------------------------------------------- 1 | using MilkiBotFramework.Messaging.RichMessages; 2 | 3 | namespace MilkiBotFramework.Platforms.GoCqHttp.Messaging.CqCodes 4 | { 5 | /// 6 | /// 回复 7 | /// 8 | // ReSharper disable once InconsistentNaming 9 | public class CQReply : IRichMessage 10 | { 11 | /// 12 | /// 被@回复时所引用的消息id, 必须为本群消息。 13 | /// 14 | public string MessageId { get; } 15 | 16 | /// 17 | /// 回复。 18 | /// 19 | /// 被@回复时所引用的消息id, 必须为本群消息。 20 | public CQReply(string messageId) 21 | { 22 | //Contract.Requires(IsNum(messageId) || messageId.ToLower() == "all"); 23 | MessageId = messageId; 24 | } 25 | 26 | internal static CQReply Parse(ReadOnlyMemory content) 27 | { 28 | const int flagLen = 5; 29 | var s = content.Slice(5 + flagLen, content.Length - 6 - flagLen).ToString(); 30 | var dictionary = CQCodeHelper.GetParameters(s); 31 | if (!dictionary.TryGetValue("id", out var id)) 32 | throw new InvalidOperationException(nameof(CQReply) + "至少需要id参数"); 33 | 34 | //var messageId = int.Parse(id); 35 | var cq = new CQReply(id); 36 | return cq; 37 | } 38 | 39 | public override string ToString() => $"[回复]"; 40 | public ValueTask EncodeAsync() => ValueTask.FromResult($"[CQ:reply,id={MessageId}]"); 41 | } 42 | } -------------------------------------------------------------------------------- /src/Platforms/MilkiBotFramework.Platforms.GoCqHttp/Messaging/CqCodes/CQRps.cs: -------------------------------------------------------------------------------- 1 | using MilkiBotFramework.Messaging.RichMessages; 2 | 3 | namespace MilkiBotFramework.Platforms.GoCqHttp.Messaging.CqCodes 4 | { 5 | /// 6 | /// 猜拳魔法表情 7 | /// 8 | // ReSharper disable once InconsistentNaming 9 | public class CQRps : IRichMessage 10 | { 11 | private CQRps() 12 | { 13 | } 14 | 15 | public static CQRps Instance { get; } = new(); 16 | public override string ToString() => "[猜拳]"; 17 | public ValueTask EncodeAsync() => ValueTask.FromResult("[CQ:rps]"); 18 | } 19 | } -------------------------------------------------------------------------------- /src/Platforms/MilkiBotFramework.Platforms.GoCqHttp/Messaging/CqCodes/CQShare.cs: -------------------------------------------------------------------------------- 1 | using MilkiBotFramework.Messaging.RichMessages; 2 | 3 | namespace MilkiBotFramework.Platforms.GoCqHttp.Messaging.CqCodes 4 | { 5 | /// 6 | /// 发送链接分享 7 | /// 8 | // ReSharper disable once InconsistentNaming 9 | public class CQShare : IRichMessage 10 | { 11 | /// 12 | /// 分享链接。 13 | /// 14 | public string LinkUrl { get; } 15 | 16 | /// 17 | /// 分享的标题。 18 | /// 19 | public string Title { get; } 20 | 21 | /// 22 | /// 分享的简介。 23 | /// 24 | public string? Content { get; } 25 | 26 | /// 27 | /// 分享的图片链接。 28 | /// 29 | public string? ImageUrl { get; } 30 | 31 | /// 32 | /// 发送链接分享。注意:链接分享只能作为单独的一条消息发送。 33 | /// 34 | /// 为分享链接。 35 | /// 为分享的标题,建议12字以内。 36 | /// 为分享的简介,建议30字以内。该参数可被忽略。 37 | /// 为分享的图片链接。若参数为空或被忽略,则显示默认图片。 38 | public CQShare(string linkUrl, string title, string? content = null, string? imageUrl = null) 39 | { 40 | LinkUrl = CQCodeHelper.Escape(linkUrl); 41 | Title = CQCodeHelper.Escape(title); 42 | Content = content == null ? null : CQCodeHelper.Escape(content); 43 | ImageUrl = imageUrl == null ? null : CQCodeHelper.Escape(imageUrl); 44 | } 45 | 46 | public override string ToString() => "[链接分享]"; 47 | 48 | public ValueTask EncodeAsync() => ValueTask.FromResult( 49 | string.Format("[CQ:share,url={0},title={1},content={2},image={3}]", LinkUrl, Title, Content, ImageUrl)); 50 | } 51 | } -------------------------------------------------------------------------------- /src/Platforms/MilkiBotFramework.Platforms.GoCqHttp/Messaging/CqCodes/CQUnknown.cs: -------------------------------------------------------------------------------- 1 | using MilkiBotFramework.Messaging.RichMessages; 2 | 3 | namespace MilkiBotFramework.Platforms.GoCqHttp.Messaging.CqCodes 4 | { 5 | // ReSharper disable once InconsistentNaming 6 | public class CQUnknown : IRichMessage 7 | { 8 | public string Type { get; } 9 | public string Content { get; } 10 | 11 | public CQUnknown(string type, string content) 12 | { 13 | Type = type; 14 | Content = content; 15 | } 16 | 17 | public override string ToString() => $"[不支持的消息:{Type}]"; 18 | public ValueTask EncodeAsync() => ValueTask.FromResult(Content); 19 | } 20 | } -------------------------------------------------------------------------------- /src/Platforms/MilkiBotFramework.Platforms.GoCqHttp/Messaging/Events/EventBase.cs: -------------------------------------------------------------------------------- 1 | #nullable disable 2 | 3 | using System.Text.Json.Serialization; 4 | using MilkiBotFramework.Platforms.GoCqHttp.Internal; 5 | 6 | namespace MilkiBotFramework.Platforms.GoCqHttp.Messaging.Events 7 | { 8 | /// 9 | /// 上报消息中含有的字段。 10 | /// 11 | public abstract class EventBase 12 | { 13 | /// 14 | /// 上报类型,分别为message、notice、request。 15 | /// 16 | [JsonPropertyName("post_type")] 17 | public string PostType { get; set; } 18 | 19 | /// 20 | /// 事件发生的时间戳。 21 | /// 22 | [JsonPropertyName("time")] 23 | [JsonConverter(typeof(UnixDateTimeConverter))] 24 | public DateTimeOffset Time { get; set; } 25 | 26 | /// 27 | /// 收到消息的机器人 QQ 号。 28 | /// 29 | [JsonPropertyName("self_id")] 30 | public long SelfId { get; set; } 31 | 32 | //[JsonPropertyName( "target_id")] 33 | //public string TargetId { get; set; } 34 | } 35 | } -------------------------------------------------------------------------------- /src/Platforms/MilkiBotFramework.Platforms.GoCqHttp/Messaging/Events/FriendAdd.cs: -------------------------------------------------------------------------------- 1 | #nullable disable 2 | 3 | using System.Text.Json.Serialization; 4 | 5 | namespace MilkiBotFramework.Platforms.GoCqHttp.Messaging.Events 6 | { 7 | /// 8 | /// 好友添加。 9 | /// 10 | public class FriendAdd : EventBase 11 | { 12 | /// 13 | /// 事件名。 14 | /// 15 | [JsonPropertyName("request_type")] 16 | public string RequestType { get; set; } 17 | 18 | /// 19 | /// 发送请求的 QQ 号。 20 | /// 21 | [JsonPropertyName("user_id")] 22 | public long UserId { get; set; } 23 | 24 | /// 25 | /// 验证信息。 26 | /// 27 | [JsonPropertyName("comment")] 28 | public string Comment { get; set; } 29 | 30 | /// 31 | /// 请求 flag, 在调用处理请求的 API 时需要传入。 32 | /// 33 | [JsonPropertyName("flag")] 34 | public string Flag { get; set; } 35 | } 36 | } -------------------------------------------------------------------------------- /src/Platforms/MilkiBotFramework.Platforms.GoCqHttp/Messaging/Events/FriendRequest.cs: -------------------------------------------------------------------------------- 1 | #nullable disable 2 | 3 | using System.Text.Json.Serialization; 4 | 5 | namespace MilkiBotFramework.Platforms.GoCqHttp.Messaging.Events 6 | { 7 | /// 8 | /// 加好友请求。 9 | /// 10 | public class FriendRequest 11 | { 12 | /// 13 | /// 请求类型。 14 | /// 15 | [JsonPropertyName("request_type")] 16 | public string RequestType { get; set; } 17 | 18 | /// 19 | /// 发送请求的 QQ 号。 20 | /// 21 | [JsonPropertyName("user_id")] 22 | public long UserId { get; set; } 23 | 24 | /// 25 | /// 验证信息。 26 | /// 27 | [JsonPropertyName("comment")] 28 | public string Comment { get; set; } 29 | 30 | /// 31 | /// 请求 flag,在调用处理请求的 API 时需要传入。 32 | /// 33 | [JsonPropertyName("flag")] 34 | public long Flag { get; set; } 35 | } 36 | 37 | /// 38 | /// 加好友请求的响应。 39 | /// 40 | public class FriendRequestResp 41 | { 42 | /// 43 | /// 是否同意请求。 44 | /// 45 | [JsonPropertyName("approve")] 46 | public bool Approve { get; set; } 47 | 48 | /// 49 | /// 添加后的好友备注(仅在同意时有效)。 50 | /// 51 | [JsonPropertyName("remark")] 52 | public string Remark { get; set; } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Platforms/MilkiBotFramework.Platforms.GoCqHttp/Messaging/Events/GroupAdminChange.cs: -------------------------------------------------------------------------------- 1 | #nullable disable 2 | 3 | using System.Text.Json.Serialization; 4 | 5 | namespace MilkiBotFramework.Platforms.GoCqHttp.Messaging.Events 6 | { 7 | /// 8 | /// 群管理员变动。 9 | /// 10 | public class GroupAdminChange : EventBase 11 | { 12 | /// 13 | /// 事件名。 14 | /// 15 | [JsonPropertyName("notice_type")] 16 | public string NoticeType { get; set; } 17 | 18 | /// 19 | /// 事件子类型,分别表示设置和取消管理员。 20 | /// 21 | [JsonPropertyName("sub_type")] 22 | public string SubType { get; set; } 23 | 24 | /// 25 | /// 群号。 26 | /// 27 | [JsonPropertyName("group_id")] 28 | public long GroupId { get; set; } 29 | 30 | /// 31 | /// 管理员 QQ 号。 32 | /// 33 | [JsonPropertyName("user_id")] 34 | public long UserId { get; set; } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Platforms/MilkiBotFramework.Platforms.GoCqHttp/Messaging/Events/GroupFileUpload.cs: -------------------------------------------------------------------------------- 1 | #nullable disable 2 | 3 | using System.Text.Json.Serialization; 4 | 5 | namespace MilkiBotFramework.Platforms.GoCqHttp.Messaging.Events 6 | { 7 | /// 8 | /// 群文件上传。 9 | /// 10 | public class GroupFileUpload : EventBase 11 | { 12 | /// 13 | /// 事件名。 14 | /// 15 | [JsonPropertyName("notice_type")] 16 | public string NoticeType { get; set; } 17 | 18 | /// 19 | /// 群号。 20 | /// 21 | [JsonPropertyName("group_id")] 22 | public long GroupId { get; set; } 23 | 24 | /// 25 | /// 发送者 QQ 号。 26 | /// 27 | [JsonPropertyName("user_id")] 28 | public long UserId { get; set; } 29 | 30 | /// 31 | /// 文件信息。 32 | /// 33 | [JsonPropertyName("file")] 34 | public FileObj File { get; set; } 35 | 36 | /// 37 | /// 文件信息。 38 | /// 39 | public class FileObj 40 | { 41 | /// 42 | /// 文件 ID。 43 | /// 44 | [JsonPropertyName("id")] 45 | public string Id { get; set; } 46 | 47 | /// 48 | /// 文件名。 49 | /// 50 | [JsonPropertyName("name")] 51 | public string Name { get; set; } 52 | 53 | /// 54 | /// 文件大小(字节数)。 55 | /// 56 | [JsonPropertyName("size")] 57 | public long Size { get; set; } 58 | 59 | /// 60 | /// busid(目前不清楚有什么作用)。 61 | /// 62 | [JsonPropertyName("busid")] 63 | public int BusId { get; set; } 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/Platforms/MilkiBotFramework.Platforms.GoCqHttp/Messaging/Events/GroupInvite.cs: -------------------------------------------------------------------------------- 1 | #nullable disable 2 | 3 | using System.Text.Json.Serialization; 4 | 5 | namespace MilkiBotFramework.Platforms.GoCqHttp.Messaging.Events 6 | { 7 | /// 8 | /// 加群请求/邀请。 9 | /// 10 | public class GroupInvite : EventBase 11 | { 12 | /// 13 | /// 请求类型。 14 | /// 15 | [JsonPropertyName("request_type")] 16 | public string RequestType { get; set; } 17 | 18 | /// 19 | /// 请求子类型,add、invite分别表示加群请求、邀请登录号入群。 20 | /// 21 | [JsonPropertyName("sub_type")] 22 | public string SubType { get; set; } 23 | 24 | /// 25 | /// 群号。 26 | /// 27 | [JsonPropertyName("group_id")] 28 | public long GroupId { get; set; } 29 | 30 | /// 31 | /// 发送请求的 QQ 号。 32 | /// 33 | [JsonPropertyName("user_id")] 34 | public long UserId { get; set; } 35 | 36 | /// 37 | /// 验证信息。 38 | /// 39 | [JsonPropertyName("comment")] 40 | public string Comment { get; set; } 41 | 42 | /// 43 | /// 请求 flag,在调用处理请求的 API 时需要传入。 44 | /// 45 | [JsonPropertyName("flag")] 46 | public string Flag { get; set; } 47 | } 48 | 49 | /// 50 | /// 加群请求/邀请的响应。 51 | /// 52 | public class GroupInviteResp 53 | { 54 | /// 55 | /// 是否同意请求/邀请。 56 | /// 57 | [JsonPropertyName("approve")] 58 | public bool Approve { get; set; } 59 | 60 | /// 61 | /// 拒绝理由(仅在拒绝时有效)。 62 | /// 63 | [JsonPropertyName("reason")] 64 | public string Reason { get; set; } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/Platforms/MilkiBotFramework.Platforms.GoCqHttp/Messaging/Events/GroupMemberChange.cs: -------------------------------------------------------------------------------- 1 | #nullable disable 2 | 3 | using System.Text.Json.Serialization; 4 | 5 | namespace MilkiBotFramework.Platforms.GoCqHttp.Messaging.Events 6 | { 7 | /// 8 | /// 群成员增加/减少。 9 | /// 10 | public class GroupMemberChange : EventBase 11 | { 12 | /// 13 | /// 事件名。 14 | /// 15 | [JsonPropertyName("notice_type")] 16 | public string NoticeType { get; set; } 17 | 18 | /// 19 | /// 事件子类型,当增加时approve、invite分别表示管理员已同意入群、管理员邀请入群。 20 | /// 当减少时leave、kick、kick_me分别表示主动退群、成员被踢、登录号被踢。 21 | /// 22 | [JsonPropertyName("sub_type")] 23 | public string SubType { get; set; } 24 | 25 | /// 26 | /// 群号。 27 | /// 28 | [JsonPropertyName("group_id")] 29 | public long GroupId { get; set; } 30 | 31 | /// 32 | /// 操作者 QQ 号。 33 | /// 34 | [JsonPropertyName("operator_id")] 35 | public long OperatorId { get; set; } 36 | 37 | /// 38 | /// 加入/离开者 QQ 号。 39 | /// 40 | [JsonPropertyName("user_id")] 41 | public long UserId { get; set; } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Platforms/MilkiBotFramework.Platforms.GoCqHttp/Messaging/Events/GuildMessage.cs: -------------------------------------------------------------------------------- 1 | #nullable disable 2 | 3 | using System.Text.Json.Serialization; 4 | using MilkiBotFramework.Platforms.GoCqHttp.Internal; 5 | 6 | namespace MilkiBotFramework.Platforms.GoCqHttp.Messaging.Events 7 | { 8 | public class GuildMessage : MessageBase, IDetailedSenderMessage 9 | { 10 | /// 11 | /// 频道号。 12 | /// 13 | [JsonPropertyName("guild_id")] 14 | [JsonConverter(typeof(Int64ToStringConverter))] 15 | public string GuildId { get; set; } 16 | 17 | /// 18 | /// 子频道号。 19 | /// 20 | [JsonPropertyName("channel_id")] 21 | [JsonConverter(typeof(Int64ToStringConverter))] 22 | public string ChannelId { get; set; } 23 | 24 | [JsonPropertyName("self_tiny_id")] 25 | [JsonConverter(typeof(Int64ToStringConverter))] 26 | public string SelfTinyId { get; set; } 27 | 28 | /// 29 | /// 发送人信息。 30 | /// 31 | [JsonPropertyName("sender")] 32 | public SenderBase Sender { get; set; } 33 | 34 | /// 35 | SenderBase IDetailedSenderMessage.Sender { get => Sender; set => Sender = value; } 36 | } 37 | } -------------------------------------------------------------------------------- /src/Platforms/MilkiBotFramework.Platforms.GoCqHttp/Messaging/Events/IDetailedSenderMessage.cs: -------------------------------------------------------------------------------- 1 | #nullable disable 2 | 3 | namespace MilkiBotFramework.Platforms.GoCqHttp.Messaging.Events 4 | { 5 | public interface IDetailedSenderMessage 6 | { 7 | /// 8 | /// 发送人信息。 9 | /// 10 | public SenderBase Sender { get; set; } 11 | } 12 | } -------------------------------------------------------------------------------- /src/Platforms/MilkiBotFramework.Platforms.GoCqHttp/Messaging/Events/MessageBase.cs: -------------------------------------------------------------------------------- 1 | #nullable disable 2 | 3 | using System.Text.Json.Serialization; 4 | using MilkiBotFramework.Platforms.GoCqHttp.Internal; 5 | 6 | namespace MilkiBotFramework.Platforms.GoCqHttp.Messaging.Events 7 | { 8 | /// 9 | /// 私聊消息。 10 | /// 11 | public abstract class MessageBase : EventBase 12 | { 13 | /// 14 | /// 消息类型。 15 | /// 16 | [JsonPropertyName("message_type")] 17 | public string MessageType { get; set; } 18 | 19 | /// 20 | /// 消息子类型,如果是好友则是 friend,是群临时会话则是 group。 21 | /// 22 | [JsonPropertyName("sub_type")] 23 | public string SubType { get; set; } 24 | 25 | /// 26 | /// 消息 ID。 27 | /// 28 | [JsonPropertyName("message_id")] 29 | [JsonConverter(typeof(Int64ToStringConverter))] 30 | public string MessageId { get; set; } 31 | 32 | /// 33 | /// 发送者 QQ 号。 34 | /// 35 | [JsonPropertyName("user_id")] 36 | [JsonConverter(typeof(Int64ToStringConverter))] 37 | public string UserId { get; set; } 38 | 39 | /// 40 | /// 消息内容。 41 | /// 42 | [JsonPropertyName("message")] 43 | public string Message { get; set; } 44 | 45 | /// 46 | /// 原始消息内容。 47 | /// 48 | [JsonPropertyName("raw_message")] 49 | public string RawMessage { get; set; } 50 | 51 | /// 52 | /// 字体。 53 | /// 54 | [JsonPropertyName("font")] 55 | public long Font { get; set; } 56 | } 57 | } -------------------------------------------------------------------------------- /src/Platforms/MilkiBotFramework.Platforms.GoCqHttp/Messaging/Events/PrivateMessage.cs: -------------------------------------------------------------------------------- 1 | #nullable disable 2 | 3 | using System.Text.Json.Serialization; 4 | 5 | namespace MilkiBotFramework.Platforms.GoCqHttp.Messaging.Events 6 | { 7 | // seealso: https://ishkong.github.io/go-cqhttp-docs/event/#私聊消息 8 | public class PrivateMessage : MessageBase, IDetailedSenderMessage 9 | { 10 | /// 11 | /// 发送人信息 12 | /// 13 | [JsonPropertyName("sender")] 14 | public PrivateSender Sender { get; set; } 15 | 16 | /// 17 | SenderBase IDetailedSenderMessage.Sender { get => Sender; set => Sender = (PrivateSender)value; } 18 | 19 | public class PrivateSender : SenderBase 20 | { 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /src/Platforms/MilkiBotFramework.Platforms.GoCqHttp/Messaging/Events/SenderBase.cs: -------------------------------------------------------------------------------- 1 | #nullable disable 2 | 3 | using System.Text.Json.Serialization; 4 | 5 | namespace MilkiBotFramework.Platforms.GoCqHttp.Messaging.Events 6 | { 7 | public class SenderBase 8 | { 9 | /// 10 | /// 发送者 QQ 号 11 | /// 12 | [JsonPropertyName("user_id")] 13 | public long UserId { get; set; } 14 | 15 | /// 16 | /// 昵称 17 | /// 18 | [JsonPropertyName("nickname")] 19 | public string NickName { get; set; } 20 | 21 | /// 22 | /// 性别, male 或 female 或 unknown 23 | /// 24 | [JsonPropertyName("sex")] 25 | public string Sex { get; set; } 26 | 27 | /// 28 | /// 年龄 29 | /// 30 | [JsonPropertyName("age")] 31 | public int Age { get; set; } 32 | } 33 | } -------------------------------------------------------------------------------- /src/Platforms/MilkiBotFramework.Platforms.GoCqHttp/Messaging/GoCqMessageContext.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | using MilkiBotFramework.Messaging; 3 | using MilkiBotFramework.Platforms.GoCqHttp.Messaging.Events; 4 | 5 | namespace MilkiBotFramework.Platforms.GoCqHttp.Messaging; 6 | 7 | public class GoCqMessageContext : MessageContext 8 | { 9 | public JsonDocument RawJsonDocument { get; internal set; } = null!; 10 | public MessageBase RawMessage { get; internal set; } = null!; 11 | 12 | public GoCqMessageContext(IRichMessageConverter richMessageConverter) : base(richMessageConverter) 13 | { 14 | } 15 | } -------------------------------------------------------------------------------- /src/Platforms/MilkiBotFramework.Platforms.GoCqHttp/Messaging/RangeComparer.cs: -------------------------------------------------------------------------------- 1 | namespace MilkiBotFramework.Platforms.GoCqHttp.Messaging; 2 | 3 | public class RangeComparer : IComparer<(int index, int count, bool isRaw)> 4 | { 5 | private RangeComparer() 6 | { 7 | } 8 | 9 | public static IComparer<(int index, int count, bool isRaw)> Instance { get; } = new RangeComparer(); 10 | 11 | public int Compare((int index, int count, bool isRaw) x, (int index, int count, bool isRaw) y) 12 | { 13 | return Comparer.Default.Compare(x.Item1, y.Item1); 14 | } 15 | } -------------------------------------------------------------------------------- /src/Platforms/MilkiBotFramework.Platforms.GoCqHttp/MilkiBotFramework.Platforms.GoCqHttp.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | all 17 | runtime; build; native; contentfiles; analyzers; buildtransitive 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/Platforms/MilkiBotFramework.Platforms.GoCqHttp/Utils/EncodingHelper.cs: -------------------------------------------------------------------------------- 1 | namespace MilkiBotFramework.Platforms.GoCqHttp.Utils; 2 | 3 | public static class EncodingHelper 4 | { 5 | public static string EncodeFileToBase64(Stream stream) 6 | { 7 | if (stream.Position != 0) stream.Position = 0; 8 | 9 | var length = stream.Length; 10 | Span span = length <= FrameworkConstants.MaxStackArrayLength 11 | ? stackalloc byte[(int)length] 12 | : new byte[length]; 13 | _ = stream.Read(span); 14 | return Convert.ToBase64String(span); 15 | } 16 | 17 | public static string EncodeFileToBase64(string path) 18 | { 19 | using var fileStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Write); 20 | return EncodeFileToBase64(fileStream); 21 | } 22 | 23 | public static byte[] DecodeBase64ToBytes(string base64) 24 | { 25 | return Convert.FromBase64String(base64); 26 | } 27 | } -------------------------------------------------------------------------------- /src/Platforms/MilkiBotFramework.Platforms.QQ/BotBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using MilkiBotFramework.Platforms.QQ.Connecting; 3 | using MilkiBotFramework.Platforms.QQ.ContactsManaging; 4 | using MilkiBotFramework.Platforms.QQ.Dispatching; 5 | using MilkiBotFramework.Platforms.QQ.Messaging; 6 | using MilkiBotFramework.Plugining.CommandLine; 7 | using MilkiBotFramework.Plugining.Loading; 8 | 9 | namespace MilkiBotFramework.Platforms.QQ; 10 | 11 | public static class BotBuilderExtensions 12 | { 13 | public static TBuilder UseQQ(this BotBuilderBase builder, 14 | QConnection? connection = null) 15 | where TBot : Bot where TBuilder : BotBuilderBase 16 | { 17 | builder 18 | .ConfigureServices(k => 19 | { 20 | k.AddScoped(typeof(QMessageContext)); 21 | k.AddSingleton(); 22 | }) 23 | .UseCommandLineAnalyzer(new DefaultParameterConverter()) 24 | .UseContactsManager() 25 | .UseDispatcher() 26 | .UseMessageApi() 27 | .UseOptions(null) 28 | .UseRichMessageConverter(); 29 | 30 | connection ??= ((QQBotOptions)builder.GetOptionInstance()).Connection; 31 | builder.UseConnector(k => k.Connection = connection); 32 | 33 | return (TBuilder)builder; 34 | } 35 | } -------------------------------------------------------------------------------- /src/Platforms/MilkiBotFramework.Platforms.QQ/Connecting/Intents.cs: -------------------------------------------------------------------------------- 1 | namespace MilkiBotFramework.Platforms.QQ.Connecting; 2 | 3 | /// 4 | /// 5 | /// 6 | [Flags] 7 | public enum Intents 8 | { 9 | Guilds = 1 << 0, 10 | GuildMembers = 1 << 1, 11 | GuildMessages = 1 << 9, 12 | GuildMessageReactions = 1 << 10, 13 | DirectMessage = 1 << 12, 14 | GroupAndC2CEvent = 1 << 25, 15 | Interaction = 1 << 26, 16 | MessageAudit = 1 << 27, 17 | ForumsEvent = 1 << 28, 18 | AudioAction = 1 << 29, 19 | PublicGuildMessages = 1 << 30, 20 | } -------------------------------------------------------------------------------- /src/Platforms/MilkiBotFramework.Platforms.QQ/Connecting/OpCode.cs: -------------------------------------------------------------------------------- 1 | namespace MilkiBotFramework.Platforms.QQ.Connecting; 2 | 3 | // ReSharper disable InconsistentNaming 4 | internal enum OpCode 5 | { 6 | Dispatch = 0, 7 | Heartbeat = 1, 8 | Identify = 2, 9 | Resume = 6, 10 | Reconnect = 7, 11 | InvalidSession = 9, 12 | Hello = 10, 13 | HeartbeatACK = 11, 14 | HTTPCallbackACK = 12 15 | } -------------------------------------------------------------------------------- /src/Platforms/MilkiBotFramework.Platforms.QQ/Connecting/QApiException.cs: -------------------------------------------------------------------------------- 1 | namespace MilkiBotFramework.Platforms.QQ.Connecting; 2 | 3 | public class QApiException : Exception 4 | { 5 | public QApiException(string error, string? message) : base(message == null ? error : $"{error}: {message}") 6 | { 7 | } 8 | } -------------------------------------------------------------------------------- /src/Platforms/MilkiBotFramework.Platforms.QQ/Messaging/QMessageContext.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | using MilkiBotFramework.Messaging; 3 | 4 | namespace MilkiBotFramework.Platforms.QQ.Messaging; 5 | 6 | public class QMessageContext : MessageContext 7 | { 8 | public JsonDocument RawJsonDocument { get; internal set; } = null!; 9 | public string RawMessage { get; internal set; } = null!; 10 | 11 | public QMessageContext(IRichMessageConverter richMessageConverter) : base(richMessageConverter) 12 | { 13 | } 14 | } -------------------------------------------------------------------------------- /src/Platforms/MilkiBotFramework.Platforms.QQ/Messaging/QMessageConverter.cs: -------------------------------------------------------------------------------- 1 | using MilkiBotFramework.Messaging; 2 | using MilkiBotFramework.Messaging.RichMessages; 3 | 4 | namespace MilkiBotFramework.Platforms.QQ.Messaging; 5 | 6 | public class QMessageConverter : IRichMessageConverter 7 | { 8 | public async ValueTask EncodeAsync(IRichMessage message) 9 | { 10 | return await message.EncodeAsync(); 11 | } 12 | 13 | public RichMessage Decode(ReadOnlyMemory message) 14 | { 15 | return new RichMessage(new Text(message.ToString())); 16 | } 17 | } -------------------------------------------------------------------------------- /src/Platforms/MilkiBotFramework.Platforms.QQ/Messaging/RichMessages/ImagePlaceholder.cs: -------------------------------------------------------------------------------- 1 | using MilkiBotFramework.Messaging.RichMessages; 2 | 3 | namespace MilkiBotFramework.Platforms.QQ.Messaging.RichMessages; 4 | 5 | internal class ImagePlaceholder(int i) : IRichMessage 6 | { 7 | public int Index { get; } = i; 8 | 9 | public ValueTask EncodeAsync() 10 | { 11 | return ValueTask.FromResult($"(见图{Index})"); 12 | } 13 | } -------------------------------------------------------------------------------- /src/Platforms/MilkiBotFramework.Platforms.QQ/MilkiBotFramework.Platforms.QQ.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/Platforms/MilkiBotFramework.Platforms.QQ/MinIOOptions.cs: -------------------------------------------------------------------------------- 1 | namespace MilkiBotFramework.Platforms.QQ; 2 | 3 | // ReSharper disable once InconsistentNaming 4 | public class MinIOOptions 5 | { 6 | public string Endpoint { get; set; } = "min.io"; 7 | public string AccessKey { get; set; } = ""; 8 | public string SecretKey { get; set; } = ""; 9 | 10 | // ReSharper disable once InconsistentNaming 11 | public bool UseSSL { get; set; } = true; 12 | public string BucketName { get; set; } = "milkibotframework-qq"; 13 | } -------------------------------------------------------------------------------- /src/Platforms/MilkiBotFramework.Platforms.QQ/QConnection.cs: -------------------------------------------------------------------------------- 1 | namespace MilkiBotFramework.Platforms.QQ; 2 | 3 | public class QConnection 4 | { 5 | public bool IsDevelopment { get; set; } 6 | public string? AppId { get; set; } 7 | public string? ClientSecret { get; set; } 8 | } -------------------------------------------------------------------------------- /src/Platforms/MilkiBotFramework.Platforms.QQ/QParameterConverter.cs: -------------------------------------------------------------------------------- 1 | using MilkiBotFramework.Messaging.RichMessages; 2 | using MilkiBotFramework.Plugining.Loading; 3 | 4 | namespace MilkiBotFramework.Platforms.QQ; 5 | 6 | public class QParameterConverter : DefaultParameterConverter 7 | { 8 | public override object Convert(Type targetType, ReadOnlyMemory source) 9 | { 10 | if (targetType != typeof(LinkImage)) 11 | return base.Convert(targetType, source); 12 | 13 | var span = source.Span; 14 | var index = span.IndexOf(",url=", StringComparison.Ordinal); 15 | 16 | if (index == -1) 17 | return base.Convert(targetType, source); 18 | 19 | var subSpan = span[index..]; 20 | var endIndex = subSpan.IndexOf(']'); 21 | if (endIndex == -1) 22 | return base.Convert(targetType, source); 23 | 24 | return new LinkImage(subSpan.Slice(5, endIndex - 5).ToString()); 25 | } 26 | } -------------------------------------------------------------------------------- /src/Platforms/MilkiBotFramework.Platforms.QQ/QQBotOptions.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | 3 | namespace MilkiBotFramework.Platforms.QQ; 4 | 5 | // ReSharper disable once InconsistentNaming 6 | public class QQBotOptions : BotOptions 7 | { 8 | [Description("QQ API设置")] 9 | public QConnection Connection { get; set; } = new(); 10 | 11 | [Description("MinIO 设置")] 12 | // ReSharper disable once InconsistentNaming 13 | public MinIOOptions MinIOOptions { get; set; } = new(); 14 | } -------------------------------------------------------------------------------- /src/Samples/DemoBot/DemoBot.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net8.0 6 | enable 7 | enable 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | PreserveNewest 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/Samples/DemoBot/DemoPlugin2.cs: -------------------------------------------------------------------------------- 1 | // ReSharper disable All 2 | #pragma warning disable CS1998 3 | #nullable disable 4 | 5 | using Microsoft.Extensions.Logging; 6 | using MilkiBotFramework.Plugining; 7 | using MilkiBotFramework.Plugining.Attributes; 8 | using MilkiBotFramework.Plugining.Configuration; 9 | 10 | namespace DemoBot; 11 | 12 | [PluginIdentifier("487ade0a-3afb-4451-8543-78e63bd2c668")] 13 | public class DemoPlugin2 : BasicPlugin 14 | { 15 | private readonly ILogger _logger; 16 | 17 | public DemoPlugin2(ILogger logger, IConfiguration configuration) 18 | { 19 | _logger = logger; 20 | //var config = configuration.Instance; 21 | //config.Key2++; 22 | //config.SaveAsync().Wait(); 23 | } 24 | 25 | //protected override async Task OnInitialized() 26 | //{ 27 | // _logger.LogDebug(nameof(OnInitialized)); 28 | //} 29 | 30 | //protected override async Task OnUninitialized() 31 | //{ 32 | // _logger.LogDebug(nameof(OnUninitialized)); 33 | //} 34 | 35 | //protected override async Task OnExecuting() 36 | //{ 37 | // _logger.LogDebug(nameof(OnExecuting)); 38 | //} 39 | 40 | //protected override async Task OnExecuted() 41 | //{ 42 | // _logger.LogDebug(nameof(OnExecuted)); 43 | //} 44 | } -------------------------------------------------------------------------------- /src/Samples/DemoBot/DemoServicePlugin.cs: -------------------------------------------------------------------------------- 1 | // ReSharper disable All 2 | #pragma warning disable CS1998 3 | #nullable disable 4 | 5 | using System.ComponentModel; 6 | using System.Text.Json; 7 | using Microsoft.Extensions.Logging; 8 | using MilkiBotFramework.Connecting; 9 | using MilkiBotFramework.Plugining; 10 | using MilkiBotFramework.Plugining.Attributes; 11 | 12 | namespace DemoBot; 13 | 14 | [PluginIdentifier("850cea3e-b448-4f45-a2c7-c6bc708ccb3f", Authors = "Milkitic")] 15 | [Description("Demo service plugin description here!")] 16 | public class DemoServicePlugin : ServicePlugin 17 | { 18 | private readonly ILogger _logger; 19 | private readonly IMessageApi _messageApi; 20 | private readonly DemoPlugin _demoPlugin; 21 | private readonly PluginManager _pluginManager; 22 | private readonly MyPluginDbContext _myPluginDbContext; 23 | 24 | public DemoServicePlugin(ILogger logger, 25 | IMessageApi messageApi, 26 | DemoPlugin demoPlugin, 27 | PluginManager pluginManager, 28 | MyPluginDbContext myPluginDbContext) 29 | { 30 | _logger = logger; 31 | _messageApi = messageApi; 32 | _demoPlugin = demoPlugin; 33 | _pluginManager = pluginManager; 34 | _myPluginDbContext = myPluginDbContext; 35 | } 36 | 37 | protected override async Task OnInitialized() 38 | { 39 | _logger.LogInformation(JsonSerializer.Serialize(Metadata)); 40 | } 41 | } -------------------------------------------------------------------------------- /src/Samples/DemoBot/MyPluginDbContext.cs: -------------------------------------------------------------------------------- 1 | using MilkiBotFramework.Plugining.Database; 2 | 3 | namespace DemoBot 4 | { 5 | public class MyPluginDbContext : PluginDbContext 6 | { 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/Samples/DemoBot/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | using MilkiBotFramework.Aspnetcore; 3 | using MilkiBotFramework.Platforms.QQ; 4 | 5 | var bot = new AspnetcoreBotBuilder() 6 | //.UseGoCqHttp() 7 | .UseQQ() 8 | .ConfigureLogger(builder => 9 | { 10 | builder 11 | .AddSimpleConsole(options => 12 | { 13 | options.IncludeScopes = true; 14 | //options.SingleLine = true; 15 | options.TimestampFormat = "hh:mm:ss.ffzz "; 16 | }); 17 | builder.AddFilter((ns, level) => 18 | { 19 | #if !DEBUG 20 | if (ns?.StartsWith("Microsoft") == true && level < LogLevel.Warning) 21 | return false; 22 | if (level < LogLevel.Information) 23 | return false; 24 | return true; 25 | #else 26 | if (ns?.StartsWith("Microsoft") == true && level < LogLevel.Information) 27 | return false; 28 | return true; 29 | #endif 30 | }); 31 | }) 32 | .Build(); 33 | await bot.RunAsync(); -------------------------------------------------------------------------------- /src/Samples/DemoBot/TestConfiguration.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using MilkiBotFramework.Plugining.Configuration; 3 | 4 | namespace DemoBot; 5 | 6 | public class TestConfiguration : ConfigurationBase 7 | { 8 | [Description("Testing for comment in config file!")] 9 | public string Key1 { get; set; } = ""; 10 | 11 | [Description("这是一条注释,这是个数字!")] 12 | public int Key2 { get; set; } 13 | } -------------------------------------------------------------------------------- /src/Samples/DemoBot/WeatherForecast.cs: -------------------------------------------------------------------------------- 1 | namespace DemoBot 2 | { 3 | public class WeatherForecast 4 | { 5 | public DateTime Date { get; set; } 6 | 7 | public int TemperatureC { get; set; } 8 | 9 | public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); 10 | 11 | public string? Summary { get; set; } 12 | } 13 | } -------------------------------------------------------------------------------- /src/Samples/DemoBot/WeatherForecastController.cs: -------------------------------------------------------------------------------- 1 | // ReSharper disable All 2 | #pragma warning disable CS1998 3 | #nullable disable 4 | 5 | using Microsoft.AspNetCore.Mvc; 6 | using Microsoft.Extensions.Logging; 7 | using MilkiBotFramework.Plugining; 8 | 9 | namespace DemoBot 10 | { 11 | //[ApiController] 12 | //[Route("[controller]")] 13 | //public class SbController : HomeController 14 | //{ 15 | //} 16 | 17 | [ApiController] 18 | [Route("[controller]")] 19 | public class WeatherForecastController : ControllerBase 20 | { 21 | private static readonly string[] Summaries = new[] 22 | { 23 | "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" 24 | }; 25 | 26 | private readonly ILogger _logger; 27 | private readonly PluginManager _pluginManager; 28 | 29 | public WeatherForecastController(ILogger logger, PluginManager pluginManager) 30 | { 31 | _logger = logger; 32 | _pluginManager = pluginManager; 33 | } 34 | 35 | [HttpGet(Name = "GetWeatherForecast")] 36 | public IEnumerable Get() 37 | { 38 | return Enumerable.Range(1, 5).Select(index => new WeatherForecast 39 | { 40 | Date = DateTime.Now.AddDays(index), 41 | TemperatureC = Random.Shared.Next(-20, 55), 42 | Summary = Summaries[Random.Shared.Next(Summaries.Length)] 43 | }) 44 | .ToArray(); 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /src/Samples/DemoPlugin/AnotherServicePlugin.cs: -------------------------------------------------------------------------------- 1 | // ReSharper disable All 2 | #pragma warning disable CS1998 3 | #nullable disable 4 | 5 | using System.ComponentModel; 6 | using System.Text.Json; 7 | using Microsoft.Extensions.Logging; 8 | using MilkiBotFramework.Connecting; 9 | using MilkiBotFramework.Platforms.GoCqHttp.Connecting; 10 | using MilkiBotFramework.Plugining; 11 | using MilkiBotFramework.Plugining.Attributes; 12 | 13 | namespace DemoPlugin; 14 | 15 | [PluginIdentifier("e5b9df0a-3954-49e3-a119-aace9af22cfa", Authors = "test")] 16 | [Description("asdfasfdsfasdf")] 17 | public class AnotherServicePlugin : ServicePlugin 18 | { 19 | private readonly ILogger _logger; 20 | private readonly IMessageApi _messageApi; 21 | private readonly GoCqApi _goCqApi; 22 | private readonly PluginManager _pluginManager; 23 | 24 | public AnotherServicePlugin(ILogger logger, 25 | IMessageApi messageApi, 26 | GoCqApi goCqApi, 27 | PluginManager pluginManager) 28 | { 29 | _logger = logger; 30 | _messageApi = messageApi; 31 | _goCqApi = goCqApi; 32 | _pluginManager = pluginManager; 33 | } 34 | 35 | protected override async Task OnInitialized() 36 | { 37 | _logger.LogInformation(JsonSerializer.Serialize(Metadata)); 38 | } 39 | } -------------------------------------------------------------------------------- /src/Samples/DemoPlugin/DemoPlugin.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/Tests/AvaHeadlessTest/App.axaml: -------------------------------------------------------------------------------- 1 | 6 | 7 | Light 8 | 9 | 10 | 11 | 12 | 13 | 14 | 17 | 18 | -------------------------------------------------------------------------------- /src/Tests/AvaHeadlessTest/App.axaml.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using Avalonia; 3 | using Avalonia.Controls; 4 | using Avalonia.Controls.ApplicationLifetimes; 5 | using Avalonia.Markup.Xaml; 6 | using Avalonia.Threading; 7 | 8 | namespace AvaHeadlessTest; 9 | 10 | // ReSharper disable once PartialTypeWithSinglePart 11 | public partial class App : Application 12 | { 13 | private readonly TaskCompletionSource _setupFinished; 14 | 15 | public App() 16 | { 17 | _setupFinished = new TaskCompletionSource(); 18 | } 19 | 20 | public App(TaskCompletionSource setupFinished) 21 | { 22 | _setupFinished = setupFinished; 23 | } 24 | 25 | public override void Initialize() 26 | { 27 | AvaloniaXamlLoader.Load(this); 28 | } 29 | 30 | public override void OnFrameworkInitializationCompleted() 31 | { 32 | if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) 33 | { 34 | desktop.ShutdownMode = ShutdownMode.OnExplicitShutdown; 35 | desktop.Startup += Desktop_Startup; 36 | Dispatcher.UIThread.UnhandledException += UIThread_UnhandledException; 37 | } 38 | 39 | base.OnFrameworkInitializationCompleted(); 40 | } 41 | 42 | private void Desktop_Startup(object? sender, ControlledApplicationLifetimeStartupEventArgs e) 43 | { 44 | _setupFinished.SetResult(); 45 | } 46 | 47 | private void UIThread_UnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e) 48 | { 49 | Debug.Fail("Exception on UI thread!", e.Exception.ToString()); 50 | e.Handled = true; 51 | } 52 | } -------------------------------------------------------------------------------- /src/Tests/AvaHeadlessTest/Assets/Fonts/readme.md: -------------------------------------------------------------------------------- 1 | 1. Save fonts in this diretory 2 | 3 | 2. Insert the code below: 4 | ```xml 5 | 6 | avares://AvaHeadlessTest/Assets/Fonts#Arial 7 | 8 | ``` 9 | 10 | 3. Set `FontFamily` in xaml: 11 | 12 | ```xml 13 | 14 | ... 15 | 16 | ``` -------------------------------------------------------------------------------- /src/Tests/AvaHeadlessTest/AvaHeadlessTest.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net8.0 6 | enable 7 | enable 8 | 9 | 10 | 11 | True 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | App.axaml 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /src/Tests/AvaHeadlessTest/AvaTestControl.axaml.cs: -------------------------------------------------------------------------------- 1 | using Avalonia.Threading; 2 | using MilkiBotFramework.Data; 3 | using MilkiBotFramework.Imaging.Avalonia; 4 | 5 | namespace AvaHeadlessTest; 6 | 7 | public class AvaTestViewModel : ViewModelBase 8 | { 9 | public string Text { get; } = "Hello MilkiBotFramework!"; 10 | } 11 | 12 | public partial class AvaTestControl : AvaRenderingControl 13 | { 14 | public AvaTestControl() 15 | { 16 | InitializeComponent(); 17 | NavigationView.SelectedItem = NavigationView.MenuItems[0]; 18 | //Button.Focus(NavigationMethod.Tab); 19 | Loaded += AvaTestControl_Loaded; 20 | } 21 | 22 | private async void AvaTestControl_Loaded(object? sender, Avalonia.Interactivity.RoutedEventArgs e) 23 | { 24 | //var rand = new Random(); 25 | for (int i = 0; i < 10000; i++) 26 | { 27 | Dispatcher.UIThread.InvokeAsync(() => { InvalidateVisual(); }); 28 | //ColorPicker.Color = new Color2((byte)rand.Next(256), (byte)rand.Next(256), (byte)rand.Next(256)); 29 | await Task.Delay(1); 30 | } 31 | 32 | //await Task.Delay(200); 33 | //if (Design.IsDesignMode) return; 34 | //var topLevel = TopLevel.GetTopLevel(this); 35 | //topLevel!.MouseDown(new Point(251, 165), MouseButton.Left); 36 | ////topLevel.MouseDown(new Point(251, 247), MouseButton.Left); 37 | ////topLevel.MouseUp(new Point(251, 247), MouseButton.Left); 38 | ////topLevel.MouseMove(new Point(271, 165)); 39 | //await Task.Delay(200); 40 | await FinishRender(); 41 | } 42 | } -------------------------------------------------------------------------------- /src/Tests/AvaHeadlessTest/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "AvaHeadlessTest": { 4 | "commandName": "Project" 5 | }, 6 | "WSL": { 7 | "commandName": "WSL2", 8 | "distributionName": "" 9 | } 10 | } 11 | } -------------------------------------------------------------------------------- /src/Tests/MinioTest/MinioTest.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net8.0 6 | enable 7 | enable 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/Tests/UnitTests/UnitTests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net8.0 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | runtime; build; native; contentfiles; analyzers; buildtransitive 14 | all 15 | 16 | 17 | runtime; build; native; contentfiles; analyzers; buildtransitive 18 | all 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | PreserveNewest 29 | 30 | 31 | 32 | 33 | --------------------------------------------------------------------------------