diff --git a/Directory.Build.props b/Directory.Build.props
index 93cdb16..8464280 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -9,10 +9,10 @@
Library
true
- 2.1.7.0
- 2.1.8.0
- 2.1.8.0
- 2.1.8
+ 2.1.9.0
+ 2.1.9.0
+ 2.1.9.0
+ 2.1.9
enable
preview
Executor
diff --git a/Mirai-CSharp.HttpApi/Builder/MiraiHttpFrameworkBuilder.cs b/Mirai-CSharp.HttpApi/Builder/MiraiHttpFrameworkBuilder.cs
index aed5324..1687cc5 100644
--- a/Mirai-CSharp.HttpApi/Builder/MiraiHttpFrameworkBuilder.cs
+++ b/Mirai-CSharp.HttpApi/Builder/MiraiHttpFrameworkBuilder.cs
@@ -166,7 +166,6 @@ public static class MiraiHttpFrameworkBuilderExtensions
public static MiraiHttpFrameworkBuilder AddDefaultServices(this MiraiHttpFrameworkBuilder builder)
{
builder.Services.TryAddSingleton();
- builder.Services.TryAddSingleton();
builder.AddInvoker();
builder.AddDefaultParsers();
builder.AddDefaultChatParsers();
diff --git a/Mirai-CSharp.HttpApi/Exceptions/MissingVoiceCoderException.cs b/Mirai-CSharp.HttpApi/Exceptions/MissingVoiceCoderException.cs
deleted file mode 100644
index 806c386..0000000
--- a/Mirai-CSharp.HttpApi/Exceptions/MissingVoiceCoderException.cs
+++ /dev/null
@@ -1,18 +0,0 @@
-using System;
-
-namespace Mirai.CSharp.HttpApi.Exceptions
-{
- ///
- /// 尚未注册用于处理音频格式所需的编码器时引发的异常
- ///
- public sealed class MissingVoiceCoderException : Exception
- {
- private const string DefaultMessage = "尚未注册用于处理此音频格式的编码器。";
-
- public MissingVoiceCoderException() : this(DefaultMessage) { }
-
- public MissingVoiceCoderException(string? message) : base(message) { }
-
- public MissingVoiceCoderException(string? message, Exception? innerException) : base(message, innerException) { }
- }
-}
diff --git a/Mirai-CSharp.HttpApi/Extensions/ApiResponseExtensions.cs b/Mirai-CSharp.HttpApi/Extensions/ApiResponseExtensions.cs
index c18994a..d547360 100644
--- a/Mirai-CSharp.HttpApi/Extensions/ApiResponseExtensions.cs
+++ b/Mirai-CSharp.HttpApi/Extensions/ApiResponseExtensions.cs
@@ -79,7 +79,7 @@ public static void EnsureApiRespCode(this JsonElement root)
public static async Task AsApiRespAsync(this Task responseTask, CancellationToken token = default)
{
- using var j = await responseTask.GetJsonAsync(token);
+ using var j = await responseTask.GetJsonAsync(token).ConfigureAwait(false);
var root = j.RootElement;
if (!root.CheckApiRespCode(out var code))
{
@@ -89,28 +89,46 @@ public static async Task AsApiRespAsync(this Task responseT
public static Task AsApiRespAsync(this Task responseTask, CancellationToken token = default)
{
- return responseTask.AsApiRespAsync(token);
+ return responseTask.AsApiRespAsync(null, token);
+ }
+
+ public static Task AsApiRespAsync(this Task responseTask, JsonSerializerOptions? options, CancellationToken token = default)
+ {
+ return responseTask.AsApiRespAsync(options, token);
+ }
+
+ public static Task AsApiRespAsync(this Task responseTask, CancellationToken token = default) where TImpl : TResult
+ {
+ return responseTask.AsApiRespAsync(null, token);
}
- public static async Task AsApiRespAsync(this Task responseTask, CancellationToken token = default) where TImpl : TResult
+ public static async Task AsApiRespAsync(this Task responseTask, JsonSerializerOptions? options, CancellationToken token = default) where TImpl : TResult
{
- //using var j = await responseTask.GetJsonAsync(token);
- string json = await responseTask.GetStringAsync(token);
- using var j = JsonDocument.Parse(json);
+ using var j = await responseTask.GetJsonAsync(token);
var root = j.RootElement;
if (root.CheckApiRespCode(out var code))
{
- return root.Deserialize()!;
+ return root.Deserialize(options)!;
}
throw GetCommonException(code!.Value, in root);
}
public static Task AsApiRespV2Async(this Task responseTask, CancellationToken token = default)
{
- return responseTask.AsApiRespV2Async(token);
+ return responseTask.AsApiRespV2Async(null, token);
+ }
+
+ public static Task AsApiRespV2Async(this Task responseTask, JsonSerializerOptions? options = null, CancellationToken token = default)
+ {
+ return responseTask.AsApiRespV2Async(options, token);
+ }
+
+ public static Task AsApiRespV2Async(this Task responseTask, CancellationToken token = default) where TImpl : TResult
+ {
+ return responseTask.AsApiRespV2Async(null, token);
}
- public static async Task AsApiRespV2Async(this Task responseTask, CancellationToken token = default) where TImpl : TResult
+ public static async Task AsApiRespV2Async(this Task responseTask, JsonSerializerOptions? options, CancellationToken token = default) where TImpl : TResult
{
using var j = await responseTask.GetJsonAsync(token);
var root = j.RootElement;
@@ -118,9 +136,9 @@ public static async Task AsApiRespV2Async(this Task()!;
+ return data.Deserialize(options)!;
}
- return root.Deserialize()!; // 向前兼容, 不确定 mirai-api-http v1.x 的行为如何
+ return root.Deserialize(options)!; // 向前兼容, 不确定 mirai-api-http v1.x 的行为如何
}
throw GetCommonException(code!.Value, in root);
}
diff --git a/Mirai-CSharp.HttpApi/Extensions/HttpClientExtensions.cs b/Mirai-CSharp.HttpApi/Extensions/HttpClientExtensions.cs
index 0c935eb..fc14f16 100644
--- a/Mirai-CSharp.HttpApi/Extensions/HttpClientExtensions.cs
+++ b/Mirai-CSharp.HttpApi/Extensions/HttpClientExtensions.cs
@@ -39,7 +39,7 @@ public static async Task SendAsync(this HttpClient client,
using HttpRequestMessage request = new HttpRequestMessage(method, uri);
request.Content = content;
request.Version = DefaultHttpVersion;
- return await client.SendAsync(request, token);
+ return await client.SendAsync(request, token).ConfigureAwait(false);
}
#if NET5_0_OR_GREATER
@@ -52,7 +52,7 @@ public static async Task SendAsync(this HttpClient client,
public static async Task GetBytesAsync(this Task responseTask, CancellationToken token = default)
{
using HttpResponseMessage response = await responseTask.ConfigureAwait(false);
- return await response.Content.ReadAsByteArrayAsync(token);
+ return await response.Content.ReadAsByteArrayAsync(token).ConfigureAwait(false);
}
///
@@ -64,7 +64,7 @@ public static async Task GetBytesAsync(this Task re
public static async Task GetStringAsync(this Task responseTask, CancellationToken token = default)
{
using HttpResponseMessage response = await responseTask.ConfigureAwait(false);
- return await response.Content.ReadAsStringAsync(token);
+ return await response.Content.ReadAsStringAsync(token).ConfigureAwait(false);
}
#else
#pragma warning disable IDE0079 // Remove unnecessary suppression
@@ -78,7 +78,7 @@ public static async Task GetStringAsync(this Task r
public static async Task GetBytesAsync(this Task responseTask, CancellationToken token = default)
{
using HttpResponseMessage response = await responseTask.ConfigureAwait(false);
- return await response.Content.ReadAsByteArrayAsync();
+ return await response.Content.ReadAsByteArrayAsync().ConfigureAwait(false);
}
///
@@ -90,7 +90,7 @@ public static async Task GetBytesAsync(this Task re
public static async Task GetStringAsync(this Task responseTask, CancellationToken token = default)
{
using HttpResponseMessage response = await responseTask.ConfigureAwait(false);
- return await response.Content.ReadAsStringAsync();
+ return await response.Content.ReadAsStringAsync().ConfigureAwait(false);
}
#pragma warning restore IDE0060
#pragma warning restore IDE0079
@@ -136,7 +136,7 @@ public static async Task GetStringAsync(this Task r
public static async Task GetObjectAsync(this Task responseTask, JsonSerializerOptions? options, CancellationToken token = default)
{
using HttpResponseMessage response = await responseTask.ConfigureAwait(false);
- return await response.Content.ReadFromJsonAsync(options, token);
+ return await response.Content.ReadFromJsonAsync(options, token).ConfigureAwait(false);
}
///
@@ -151,7 +151,7 @@ public static async Task GetStringAsync(this Task r
public static async Task
private unsafe Task InternalUploadVoiceAsync(InternalSessionInfo session, UploadTarget type, byte[] voice, CancellationToken token)
{
- if (voice.Length <= 10)
- {
- throw new ArgumentException("输入的音频数据无效", nameof(voice));
- }
- Span silkSpan = voice.AsSpan();
- if (silkSpan.Slice(0, 9).SequenceEqual(_silkHeader.Slice(1)))
- {
- byte[] copied = new byte[voice.Length + 1];
- copied[0] = 2;
-#if NET5_0_OR_GREATER
- Unsafe.CopyBlock(ref Unsafe.AddByteOffset(ref MemoryMarshal.GetArrayDataReference(copied), new IntPtr(1)), ref MemoryMarshal.GetReference(silkSpan), (uint)voice.Length);
-#else
- Unsafe.CopyBlock(ref copied[1], ref MemoryMarshal.GetReference(silkSpan), (uint)voice.Length);
-#endif
- voice = copied;
- }
- else if (!silkSpan.SequenceEqual(_silkHeader))
+ IVoiceConverter? converter = _services.GetService();
+ if (converter != null)
{
- if (_coder == null)
+ if (!converter.TryConvert(voice, out byte[]? converted))
{
- throw new MissingVoiceCoderException();
+ throw new ArgumentException("输入的音频数据无法被已注册的 IVoiceConverter 转换", nameof(voice));
}
- if (_coder.TryEncodeMp3ToSilk(silkSpan, out voice!) != 0)
+ if (converted != null)
{
- throw new ArgumentException("输入的音频格式不为mp3, 因此无法将其转为silk格式进行发送", nameof(voice));
+ voice = converted;
}
}
MultipartFormDataContent payload = new MultipartFormDataContent(HttpClientExtensions.DefaultBoundary);
payload.Add(new StringContent(session.SessionKey), "sessionKey");
payload.Add(new StringContent(type.ToString().ToLower()), "type");
- payload.Add(new StringContent(type.ToString().ToLower()), "type");
payload.Add(new ByteArrayContent(voice), "voice", $"{Guid.NewGuid():n}.silk");
CreateLinkedUserSessionToken(session.Token, token, out CancellationTokenSource? cts, out token);
return _client.PostAsync($"{_options.BaseUrl}/uploadVoice", payload, token)
diff --git a/Mirai-CSharp.HttpApi/Session/MiraiHttpSession.cs b/Mirai-CSharp.HttpApi/Session/MiraiHttpSession.cs
index 1ea0719..3333b3c 100644
--- a/Mirai-CSharp.HttpApi/Session/MiraiHttpSession.cs
+++ b/Mirai-CSharp.HttpApi/Session/MiraiHttpSession.cs
@@ -76,8 +76,6 @@ public void Dispose()
protected readonly JsonSerializerOptions _chatMessageSerializingOptions; // 缓存一份
- protected readonly ISilkLameCoder? _coder;
-
protected CancellationTokenSource? _instanceCts;
protected InternalSessionInfo? _currentSession;
@@ -85,8 +83,8 @@ public void Dispose()
///
/// 初始化 类的新实例
///
- public MiraiHttpSession(IServiceProvider services, IOptions options, IMiraiHttpMessageHandlerInvoker invoker, ChatMessageJsonConverter jsonConverter, HttpClient? client = null, ISilkLameCoder? coder = null)
- : this(services, options.Value, invoker, jsonConverter, client ?? new HttpClient(), coder)
+ public MiraiHttpSession(IServiceProvider services, IOptions options, IMiraiHttpMessageHandlerInvoker invoker, ChatMessageJsonConverter jsonConverter, HttpClient? client = null)
+ : this(services, options.Value, invoker, jsonConverter, client ?? new HttpClient())
{
}
@@ -94,13 +92,12 @@ public MiraiHttpSession(IServiceProvider services, IOptions
/// 初始化 类的新实例
///
- protected MiraiHttpSession(IServiceProvider services, MiraiHttpSessionOptions options, IMiraiHttpMessageHandlerInvoker invoker, ChatMessageJsonConverter jsonConverter, HttpClient client, ISilkLameCoder? coder = null)
+ protected MiraiHttpSession(IServiceProvider services, MiraiHttpSessionOptions options, IMiraiHttpMessageHandlerInvoker invoker, ChatMessageJsonConverter jsonConverter, HttpClient client)
{
_services = services;
_options = options;
_client = client;
_invoker = invoker;
- _coder = coder;
JsonSerializerOptions chatMessageSerializingOptions = JsonSerializeOptionsFactory.IgnoreNulls;
chatMessageSerializingOptions.Converters.Add(jsonConverter);
_chatMessageSerializingOptions = chatMessageSerializingOptions;
diff --git a/Mirai-CSharp.ImageConverter/DependencyInjection/SkiasharpImageConverterDIExtensions.cs b/Mirai-CSharp.ImageConverter/DependencyInjection/SkiasharpImageConverterDIExtensions.cs
new file mode 100644
index 0000000..faaf4c6
--- /dev/null
+++ b/Mirai-CSharp.ImageConverter/DependencyInjection/SkiasharpImageConverterDIExtensions.cs
@@ -0,0 +1,15 @@
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.DependencyInjection.Extensions;
+using Mirai.CSharp.Services;
+
+namespace Mirai_CSharp.ImageConverter.DependencyInjection
+{
+ public static class SkiasharpImageConverterDIExtensions
+ {
+ public static IServiceCollection AddSkiasharpImageConverter(this IServiceCollection services)
+ {
+ services.TryAddSingleton();
+ return services;
+ }
+ }
+}
diff --git a/Mirai-CSharp.ImageConverter/Mirai-CSharp.ImageConverter.csproj b/Mirai-CSharp.ImageConverter/Mirai-CSharp.ImageConverter.csproj
new file mode 100644
index 0000000..30131f4
--- /dev/null
+++ b/Mirai-CSharp.ImageConverter/Mirai-CSharp.ImageConverter.csproj
@@ -0,0 +1,16 @@
+
+
+
+ Mirai_CSharp.ImageConverter
+ Provides default IImageConverter implementation.
+ mirai
+
+
+
+
+
+
+
+
+
+
diff --git a/Mirai-CSharp.ImageConverter/SkiasharpImageConverter.cs b/Mirai-CSharp.ImageConverter/SkiasharpImageConverter.cs
new file mode 100644
index 0000000..06ace93
--- /dev/null
+++ b/Mirai-CSharp.ImageConverter/SkiasharpImageConverter.cs
@@ -0,0 +1,89 @@
+using System;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+using Mirai.CSharp.Models;
+using Mirai.CSharp.Services;
+using SkiaSharp;
+
+namespace Mirai_CSharp.ImageConverter
+{
+ public class SkiasharpImageConverter : IImageConverter
+ {
+ public unsafe bool TryConvert(ReadOnlySpan inputSpan, ImageFormat convertTo, out ImageFormat outputFormat, out byte[]? output)
+ {
+ fixed (byte* inputPtr = &inputSpan.GetPinnableReference())
+ {
+ using SKData inputData = SKData.Create((nint)inputPtr, inputSpan.Length);
+ using SKCodec codec = SKCodec.Create(inputData);
+ var skformat = codec.EncodedFormat;
+ ImageFormat inputFormat = skformat switch
+ {
+ SKEncodedImageFormat.Png => ImageFormat.Png,
+ SKEncodedImageFormat.Jpeg => ImageFormat.Jpeg,
+ SKEncodedImageFormat.Gif => ImageFormat.Gif,
+ _ => ImageFormat.Unknown
+ };
+ inputFormat &= convertTo;
+ if ((inputFormat & convertTo) != ImageFormat.Unknown) // 如果输入的图片是请求输出格式的其中一种就原样返回
+ {
+ outputFormat = inputFormat;
+ output = null;
+ return true;
+ }
+ convertTo = (ImageFormat)((int)convertTo & -(int)convertTo); // 保留最右侧的位,其余位全部置0
+ if (convertTo == ImageFormat.Unknown)
+ {
+ outputFormat = ImageFormat.Unknown;
+ output = null;
+ return false;
+ }
+ // 1 => 4
+ // 2 => 3
+ // 4 => 1
+ SKEncodedImageFormat skFormat = (SKEncodedImageFormat)(5 - (int)convertTo); // 看上边的对照表
+ using SKBitmap bitmap = SKBitmap.Decode(codec);
+ using SKData encoded = bitmap.Encode(skFormat, 100);
+ outputFormat = convertTo;
+ output = encoded.ToArray();
+ return true;
+ }
+ }
+
+ public async Task<(ImageFormat outputFormat, byte[] output)> ConvertAsync(Stream inputStream, ImageFormat convertTo, CancellationToken token = default)
+ {
+ byte[] buffer;
+ int length;
+ if (inputStream is not MemoryStream ms) // 对于非 MemoryStream 的, 一律假定其读取行为是阻塞的
+ {
+ ms = new MemoryStream(8192);
+ await inputStream.CopyToAsync(ms, 81920, token);
+ buffer = ms.GetBuffer();
+ length = (int)ms.Length;
+ }
+ else
+ {
+ length = (int)(ms.Length - ms.Position);
+ buffer = new byte[length];
+ ms.Read(buffer, 0, length);
+ }
+ if (!TryConvert(buffer, convertTo, out ImageFormat outputFormat, out byte[]? output))
+ {
+ throw new ArgumentOutOfRangeException(nameof(convertTo), convertTo, "无效的输出格式");
+ }
+#if !NETSTANDARD2_0
+ return (outputFormat, output ?? (length == buffer.Length ? buffer : buffer[..length]));
+#else
+ if (output != null)
+ {
+ return (outputFormat, output);
+ }
+ if (length != buffer.Length)
+ {
+ Array.Resize(ref buffer, length);
+ }
+ return (outputFormat, buffer);
+#endif
+ }
+ }
+}
diff --git a/Mirai-CSharp.NativeAssets.LinuxArm/Mirai-CSharp.NativeAssets.LinuxArm.nuspec b/Mirai-CSharp.NativeAssets.LinuxArm/Mirai-CSharp.NativeAssets.LinuxArm.nuspec
new file mode 100644
index 0000000..84063ae
--- /dev/null
+++ b/Mirai-CSharp.NativeAssets.LinuxArm/Mirai-CSharp.NativeAssets.LinuxArm.nuspec
@@ -0,0 +1,34 @@
+
+
+
+ Mirai-CSharp.NativeAssets.LinuxArm
+ Mirai-CSharp - Native Assets for LinuxArm
+ 1.0.2
+ 提供silk和lame的相互转换
+ 提供silk和lame的相互转换
+ Executor
+ Copyright © Executor 2021
+ https://github.com/Executor-Cheng/Mirai-CSharp
+ true
+ AGPL-3.0-or-later
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Mirai-CSharp.NativeAssets.LinuxArm/Mirai-CSharp.NativeAssets.LinuxArm.props b/Mirai-CSharp.NativeAssets.LinuxArm/Mirai-CSharp.NativeAssets.LinuxArm.props
new file mode 100644
index 0000000..0dbbd6d
--- /dev/null
+++ b/Mirai-CSharp.NativeAssets.LinuxArm/Mirai-CSharp.NativeAssets.LinuxArm.props
@@ -0,0 +1,12 @@
+
+
+
+ True
+
+
+
+ <_NativeSilkLameFile Include="$(MSBuildThisFileDirectory)..\..\runtimes\linux-arm\native\libsilklame*.dll" Dir="x86\"/>
+ <_NativeSilkLameFile Include="$(MSBuildThisFileDirectory)..\..\runtimes\linux-arm64\native\libsilklame*.dll" Dir="x64\"/>
+
+
+
diff --git a/Mirai-CSharp.NativeAssets.LinuxArm/_._ b/Mirai-CSharp.NativeAssets.LinuxArm/_._
new file mode 100644
index 0000000..e69de29
diff --git a/Mirai-CSharp.NativeAssets.LinuxArm/runtimes/linux-arm/native/libsilklame.so b/Mirai-CSharp.NativeAssets.LinuxArm/runtimes/linux-arm/native/libsilklame.so
new file mode 100644
index 0000000..e93e4c7
Binary files /dev/null and b/Mirai-CSharp.NativeAssets.LinuxArm/runtimes/linux-arm/native/libsilklame.so differ
diff --git a/Mirai-CSharp.NativeAssets.LinuxArm/runtimes/linux-arm64/native/libsilklame.so b/Mirai-CSharp.NativeAssets.LinuxArm/runtimes/linux-arm64/native/libsilklame.so
new file mode 100644
index 0000000..2927248
Binary files /dev/null and b/Mirai-CSharp.NativeAssets.LinuxArm/runtimes/linux-arm64/native/libsilklame.so differ
diff --git a/Mirai-CSharp.VoiceConverter/DependencyInjection/SilkLameVoiceConverterDIExtensions.cs b/Mirai-CSharp.VoiceConverter/DependencyInjection/SilkLameVoiceConverterDIExtensions.cs
new file mode 100644
index 0000000..13140b0
--- /dev/null
+++ b/Mirai-CSharp.VoiceConverter/DependencyInjection/SilkLameVoiceConverterDIExtensions.cs
@@ -0,0 +1,17 @@
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.DependencyInjection.Extensions;
+using Mirai.CSharp.Services;
+using Mirai.CSharp.VoiceConverter;
+
+namespace Mirai_CSharp.VoiceConverter.DependencyInjection
+{
+ public static class SilkLameVoiceConverterDIExtensions
+ {
+ public static IServiceCollection AddSilkLameVoiceConverter(this IServiceCollection services)
+ {
+ services.TryAddSingleton();
+ services.TryAddSingleton();
+ return services;
+ }
+ }
+}
diff --git a/Mirai-CSharp.HttpApi/Models/LameOutputChannelMode.cs b/Mirai-CSharp.VoiceConverter/LameOutputChannelMode.cs
similarity index 76%
rename from Mirai-CSharp.HttpApi/Models/LameOutputChannelMode.cs
rename to Mirai-CSharp.VoiceConverter/LameOutputChannelMode.cs
index d676630..e280455 100644
--- a/Mirai-CSharp.HttpApi/Models/LameOutputChannelMode.cs
+++ b/Mirai-CSharp.VoiceConverter/LameOutputChannelMode.cs
@@ -1,4 +1,4 @@
-namespace Mirai.CSharp.HttpApi.Models
+namespace Mirai.CSharp.VoiceConverter
{
public enum LameOutputChannelMode
{
diff --git a/Mirai-CSharp.VoiceConverter/Mirai-CSharp.VoiceConverter.csproj b/Mirai-CSharp.VoiceConverter/Mirai-CSharp.VoiceConverter.csproj
new file mode 100644
index 0000000..da4a38a
--- /dev/null
+++ b/Mirai-CSharp.VoiceConverter/Mirai-CSharp.VoiceConverter.csproj
@@ -0,0 +1,13 @@
+
+
+
+ Mirai_CSharp.VoiceConverter
+ Provides default IVoiceConverter implementation.
+ mirai
+
+
+
+
+
+
+
diff --git a/Mirai-CSharp.HttpApi/Models/Mp3Metadata.cs b/Mirai-CSharp.VoiceConverter/Mp3Metadata.cs
similarity index 90%
rename from Mirai-CSharp.HttpApi/Models/Mp3Metadata.cs
rename to Mirai-CSharp.VoiceConverter/Mp3Metadata.cs
index 9d48ded..88528ab 100644
--- a/Mirai-CSharp.HttpApi/Models/Mp3Metadata.cs
+++ b/Mirai-CSharp.VoiceConverter/Mp3Metadata.cs
@@ -1,4 +1,4 @@
-namespace Mirai.CSharp.HttpApi.Models
+namespace Mirai.CSharp.VoiceConverter
{
public struct Mp3Metadata
{
diff --git a/Mirai-CSharp.HttpApi/Services/SilkLameCoder.NativeHelper.cs b/Mirai-CSharp.VoiceConverter/SilkLameCoder.NativeHelper.cs
similarity index 98%
rename from Mirai-CSharp.HttpApi/Services/SilkLameCoder.NativeHelper.cs
rename to Mirai-CSharp.VoiceConverter/SilkLameCoder.NativeHelper.cs
index 0b0edd0..aa7dd6c 100644
--- a/Mirai-CSharp.HttpApi/Services/SilkLameCoder.NativeHelper.cs
+++ b/Mirai-CSharp.VoiceConverter/SilkLameCoder.NativeHelper.cs
@@ -1,8 +1,7 @@
using System.Runtime.InteropServices;
-using Mirai.CSharp.HttpApi.Models;
#pragma warning disable CA1401 // P/Invokes should not be visible
-namespace Mirai.CSharp.HttpApi.Utility
+namespace Mirai.CSharp.VoiceConverter
{
public unsafe partial class SilkLameCoder
{
diff --git a/Mirai-CSharp.HttpApi/Services/SilkLameCoder.cs b/Mirai-CSharp.VoiceConverter/SilkLameCoder.cs
similarity index 97%
rename from Mirai-CSharp.HttpApi/Services/SilkLameCoder.cs
rename to Mirai-CSharp.VoiceConverter/SilkLameCoder.cs
index a47908e..cfab626 100644
--- a/Mirai-CSharp.HttpApi/Services/SilkLameCoder.cs
+++ b/Mirai-CSharp.VoiceConverter/SilkLameCoder.cs
@@ -1,20 +1,14 @@
using System;
using System.IO;
using System.Runtime.CompilerServices;
-using Mirai.CSharp.HttpApi.Models;
#if NET5_0_OR_GREATER
using System.Runtime.InteropServices;
#endif
-namespace Mirai.CSharp.HttpApi.Utility
+namespace Mirai.CSharp.VoiceConverter
{
public unsafe interface ISilkLameCoder : IDisposable
{
- ///
- ///
- ///
- ///
- ///
byte[] EncodeMp3ToSilk(ReadOnlySpan buffer);
byte[] DecodeSilkToMp3(ReadOnlySpan buffer, int samplerate);
@@ -41,7 +35,6 @@ public SilkLameCoder()
#if NETSTANDARD2_0
_loadedSilkLame = LoadSilklame();
#endif
- NativeHelper.InitializeLameDecoder();
void* glf = NativeHelper.CreateDefaultLameFlag();
if (glf == null)
{
diff --git a/Mirai-CSharp.VoiceConverter/SilkLameVoiceConverter.cs b/Mirai-CSharp.VoiceConverter/SilkLameVoiceConverter.cs
new file mode 100644
index 0000000..742bd96
--- /dev/null
+++ b/Mirai-CSharp.VoiceConverter/SilkLameVoiceConverter.cs
@@ -0,0 +1,86 @@
+using System;
+using System.IO;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Threading;
+using System.Threading.Tasks;
+using Mirai.CSharp.Services;
+
+namespace Mirai.CSharp.VoiceConverter
+{
+ public class SilkLameVoiceConverter : IVoiceConverter
+ {
+ private static ReadOnlySpan _silkHeader => new byte[10] { 2, 35, 33, 83, 73, 76, 75, 95, 86, 51 };
+
+ private readonly ISilkLameCoder _coder;
+
+ public SilkLameVoiceConverter(ISilkLameCoder coder)
+ {
+ _coder = coder;
+ }
+
+ public bool TryConvert(ReadOnlySpan inputSpan, out byte[]? output)
+ {
+ if (inputSpan.Length <= 10)
+ {
+ output = null;
+ return false;
+ }
+ if (inputSpan.Slice(0, _silkHeader.Length).SequenceEqual(_silkHeader))
+ {
+ output = null;
+ return true;
+ }
+ if (inputSpan.Slice(0, 9).SequenceEqual(_silkHeader.Slice(1)))
+ {
+ byte[] copied = new byte[inputSpan.Length + 1];
+ copied[0] = 2;
+#if NET5_0_OR_GREATER
+ Unsafe.CopyBlock(ref Unsafe.AddByteOffset(ref MemoryMarshal.GetArrayDataReference(copied), new IntPtr(1)), ref MemoryMarshal.GetReference(inputSpan), (uint)inputSpan.Length);
+#else
+ Unsafe.CopyBlock(ref copied[1], ref MemoryMarshal.GetReference(inputSpan), (uint)inputSpan.Length);
+#endif
+ output = copied;
+ return true;
+ }
+ return _coder.TryEncodeMp3ToSilk(inputSpan, out output) == 0;
+ }
+
+ public async Task ConvertAsync(Stream inputStream, CancellationToken token = default)
+ {
+ byte[] buffer;
+ int length;
+ if (inputStream is not MemoryStream ms) // 对于非 MemoryStream 的, 一律假定其读取行为是阻塞的
+ {
+ ms = new MemoryStream(8192);
+ await inputStream.CopyToAsync(ms, 81920, token);
+ buffer = ms.GetBuffer();
+ length = (int)ms.Length;
+ }
+ else
+ {
+ length = (int)(ms.Length - ms.Position);
+ buffer = new byte[length];
+ ms.Read(buffer, 0, length);
+ }
+ if (!TryConvert(buffer, out byte[]? output))
+ {
+ throw new ArgumentException("输入的音频格式不为mp3, 因此无法将其转为silk格式进行发送", nameof(inputStream));
+ }
+#if !NETSTANDARD2_0
+ return output ?? (length == buffer.Length ? buffer : buffer[..length]);
+#else
+ if (output != null)
+ {
+ return output;
+ }
+ if (length != buffer.Length)
+ {
+ Array.Resize(ref buffer, length);
+ }
+ return buffer;
+#endif
+ }
+ }
+}
diff --git a/Mirai-CSharp.sln b/Mirai-CSharp.sln
index 65b06fc..29f5fce 100644
--- a/Mirai-CSharp.sln
+++ b/Mirai-CSharp.sln
@@ -27,7 +27,11 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Mirai-CSharp.NativeAssets.L
Mirai-CSharp.NativeAssets.Linux\Mirai-CSharp.NativeAssets.Linux.props = Mirai-CSharp.NativeAssets.Linux\Mirai-CSharp.NativeAssets.Linux.props
EndProjectSection
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Mirai-CSharp.Example.Hosting", "Mirai-CSharp.Example.Hosting\Mirai-CSharp.Example.Hosting.csproj", "{5679DB58-8369-408C-A607-8C7BC1A76D69}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mirai-CSharp.Example.Hosting", "Mirai-CSharp.Example.Hosting\Mirai-CSharp.Example.Hosting.csproj", "{5679DB58-8369-408C-A607-8C7BC1A76D69}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Mirai-CSharp.ImageConverter", "Mirai-CSharp.ImageConverter\Mirai-CSharp.ImageConverter.csproj", "{7079EFD7-355A-4F77-BAC1-21B6AC135B93}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Mirai-CSharp.VoiceConverter", "Mirai-CSharp.VoiceConverter\Mirai-CSharp.VoiceConverter.csproj", "{869CD8E1-4D45-4742-B40B-8930C5F7ECA4}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -51,6 +55,14 @@ Global
{5679DB58-8369-408C-A607-8C7BC1A76D69}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5679DB58-8369-408C-A607-8C7BC1A76D69}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5679DB58-8369-408C-A607-8C7BC1A76D69}.Release|Any CPU.Build.0 = Release|Any CPU
+ {7079EFD7-355A-4F77-BAC1-21B6AC135B93}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {7079EFD7-355A-4F77-BAC1-21B6AC135B93}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {7079EFD7-355A-4F77-BAC1-21B6AC135B93}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {7079EFD7-355A-4F77-BAC1-21B6AC135B93}.Release|Any CPU.Build.0 = Release|Any CPU
+ {869CD8E1-4D45-4742-B40B-8930C5F7ECA4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {869CD8E1-4D45-4742-B40B-8930C5F7ECA4}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {869CD8E1-4D45-4742-B40B-8930C5F7ECA4}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {869CD8E1-4D45-4742-B40B-8930C5F7ECA4}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/Mirai-CSharp/Framework/Invoking/MessageSubscription.cs b/Mirai-CSharp/Framework/Invoking/MessageSubscription.cs
index e753de2..52177b7 100644
--- a/Mirai-CSharp/Framework/Invoking/MessageSubscription.cs
+++ b/Mirai-CSharp/Framework/Invoking/MessageSubscription.cs
@@ -122,7 +122,7 @@ public virtual async Task HandleMessageAsync(TClient client, TMessage message)
{
foreach (IMessageHandler handler in this)
{
- await handler.HandleMessageAsync(client, message);
+ await handler.HandleMessageAsync(client, message).ConfigureAwait(false);
if (message.BlockRemainingHandlers)
{
break;
diff --git a/Mirai-CSharp/Mirai-CSharp.csproj b/Mirai-CSharp/Mirai-CSharp.csproj
index 4129a9b..7606acb 100644
--- a/Mirai-CSharp/Mirai-CSharp.csproj
+++ b/Mirai-CSharp/Mirai-CSharp.csproj
@@ -7,7 +7,7 @@
- F:\Projects\Mirai-CSharp\Mirai-CSharp\Mirai-CSharp.xml
+ Mirai-CSharp.xml
diff --git a/Mirai-CSharp/Models/EventArgs/Friend/IFriendSyncMessageEventArgs.cs b/Mirai-CSharp/Models/EventArgs/Friend/IFriendSyncMessageEventArgs.cs
new file mode 100644
index 0000000..06ed84e
--- /dev/null
+++ b/Mirai-CSharp/Models/EventArgs/Friend/IFriendSyncMessageEventArgs.cs
@@ -0,0 +1,27 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Mirai.CSharp.Models.EventArgs
+{
+ ///
+ /// 提供好友同步消息的相关信息接口。继承自
+ ///
+ public interface IFriendSyncMessageEventArgs : ICommonMessageEventArgs
+ {
+ ///
+ /// 目标好友信息
+ ///
+ IFriendInfo Subject { get; }
+ }
+
+ ///
+ /// 提供好友同步消息的相关信息接口。继承自 和
+ ///
+ public interface IFriendSyncMessageEventArgs : IFriendSyncMessageEventArgs, ICommonMessageEventArgs
+ {
+
+ }
+}
diff --git a/Mirai-CSharp/Models/EventArgs/Group/IGroupApplyEventArgs.cs b/Mirai-CSharp/Models/EventArgs/Group/IGroupApplyEventArgs.cs
index 04b71b8..3e1e354 100644
--- a/Mirai-CSharp/Models/EventArgs/Group/IGroupApplyEventArgs.cs
+++ b/Mirai-CSharp/Models/EventArgs/Group/IGroupApplyEventArgs.cs
@@ -5,7 +5,10 @@ namespace Mirai.CSharp.Models.EventArgs
///
public interface IGroupApplyEventArgs : ICommonGroupApplyEventArgs
{
-
+ ///
+ /// 邀请人QQ号
+ ///
+ long? InvitorId { get; }
}
///
diff --git a/Mirai-CSharp/Models/EventArgs/Group/IGroupDisbandEventArgs.cs b/Mirai-CSharp/Models/EventArgs/Group/IGroupDisbandEventArgs.cs
new file mode 100644
index 0000000..7ef3bda
--- /dev/null
+++ b/Mirai-CSharp/Models/EventArgs/Group/IGroupDisbandEventArgs.cs
@@ -0,0 +1,24 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Mirai.CSharp.Models.EventArgs
+{
+ ///
+ /// 提供群被群主解散后bot离开群相关信息的接口。继承自
+ ///
+ public interface IGroupDisbandEventArgs : IGroupOperatingEventArgs
+ {
+
+ }
+
+ ///
+ /// 提供群被群主解散后bot离开群相关信息的接口。继承自 和
+ ///
+ public interface IGroupDisbandEventArgs : IGroupDisbandEventArgs, IGroupOperatingEventArgs
+ {
+
+ }
+}
diff --git a/Mirai-CSharp/Models/EventArgs/Group/IGroupSyncMessageEventArgs.cs b/Mirai-CSharp/Models/EventArgs/Group/IGroupSyncMessageEventArgs.cs
new file mode 100644
index 0000000..7908301
--- /dev/null
+++ b/Mirai-CSharp/Models/EventArgs/Group/IGroupSyncMessageEventArgs.cs
@@ -0,0 +1,27 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Mirai.CSharp.Models.EventArgs
+{
+ ///
+ /// 提供群同步消息相关信息的接口。继承自
+ ///
+ public interface IGroupSyncMessageEventArgs : ICommonMessageEventArgs
+ {
+ ///
+ /// 目标群信息
+ ///
+ IGroupInfo Subject { get; }
+ }
+
+ ///
+ /// 提供群同步消息相关信息的接口。继承自 和
+ ///
+ public interface IGroupSyncMessageEventArgs : IGroupSyncMessageEventArgs, ICommonMessageEventArgs
+ {
+
+ }
+}
diff --git a/Mirai-CSharp/Models/EventArgs/Stranger/IStrangerMessageEventArgs.cs b/Mirai-CSharp/Models/EventArgs/Stranger/IStrangerMessageEventArgs.cs
index cc6b1f9..7e0cb94 100644
--- a/Mirai-CSharp/Models/EventArgs/Stranger/IStrangerMessageEventArgs.cs
+++ b/Mirai-CSharp/Models/EventArgs/Stranger/IStrangerMessageEventArgs.cs
@@ -1,10 +1,19 @@
namespace Mirai.CSharp.Models.EventArgs
{
+ ///
+ /// 提供陌生人消息的相关信息接口。继承自
+ ///
public interface IStrangerMessageEventArgs : ICommonMessageEventArgs
{
+ ///
+ /// 来源陌生人信息
+ ///
IStrangerInfo Sender { get; }
}
+ ///
+ /// 提供陌生人消息的相关信息接口。继承自 和
+ ///
public interface IStrangerMessageEventArgs : IStrangerMessageEventArgs, ICommonMessageEventArgs
{
diff --git a/Mirai-CSharp/Models/EventArgs/Stranger/IStrangerSyncMessageEventArgs.cs b/Mirai-CSharp/Models/EventArgs/Stranger/IStrangerSyncMessageEventArgs.cs
new file mode 100644
index 0000000..7748c2c
--- /dev/null
+++ b/Mirai-CSharp/Models/EventArgs/Stranger/IStrangerSyncMessageEventArgs.cs
@@ -0,0 +1,27 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Mirai.CSharp.Models.EventArgs
+{
+ ///
+ /// 提供陌生人同步消息的相关信息接口。继承自
+ ///
+ public interface IStrangerSyncMessageEventArgs : ICommonMessageEventArgs
+ {
+ ///
+ /// 目标陌生人信息
+ ///
+ IStrangerInfo Subject { get; }
+ }
+
+ ///
+ /// 提供陌生人同步消息的相关信息接口。继承自 和
+ ///
+ public interface IStrangerSyncMessageEventArgs : IStrangerSyncMessageEventArgs, ICommonMessageEventArgs
+ {
+
+ }
+}
diff --git a/Mirai-CSharp/Models/EventArgs/Temp/ITempSyncMessageEventArgs.cs b/Mirai-CSharp/Models/EventArgs/Temp/ITempSyncMessageEventArgs.cs
new file mode 100644
index 0000000..3674c26
--- /dev/null
+++ b/Mirai-CSharp/Models/EventArgs/Temp/ITempSyncMessageEventArgs.cs
@@ -0,0 +1,27 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Mirai.CSharp.Models.EventArgs
+{
+ ///
+ /// 提供临时同步消息的相关信息接口。继承自
+ ///
+ public interface ITempSyncMessageEventArgs : ICommonMessageEventArgs
+ {
+ ///
+ /// 目标群成员信息
+ ///
+ IGroupMemberInfo Subject { get; }
+ }
+
+ ///
+ /// 提供临时同步消息的相关信息接口。继承自 和
+ ///
+ public interface ITempSyncMessageEventArgs : ITempSyncMessageEventArgs, ICommonMessageEventArgs
+ {
+
+ }
+}
diff --git a/Mirai-CSharp/Models/ImageFormat.cs b/Mirai-CSharp/Models/ImageFormat.cs
new file mode 100644
index 0000000..bf4ae92
--- /dev/null
+++ b/Mirai-CSharp/Models/ImageFormat.cs
@@ -0,0 +1,16 @@
+using System;
+
+namespace Mirai.CSharp.Models
+{
+ [Flags]
+ public enum ImageFormat
+ {
+ Unknown = 0,
+
+ Png = 1,
+
+ Jpeg = 2,
+
+ Gif = 4
+ }
+}
diff --git a/Mirai-CSharp/Services/IImageConverter.cs b/Mirai-CSharp/Services/IImageConverter.cs
new file mode 100644
index 0000000..dc867bc
--- /dev/null
+++ b/Mirai-CSharp/Services/IImageConverter.cs
@@ -0,0 +1,15 @@
+using System;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+using Mirai.CSharp.Models;
+
+namespace Mirai.CSharp.Services
+{
+ public interface IImageConverter
+ {
+ bool TryConvert(ReadOnlySpan inputSpan, ImageFormat convertTo, out ImageFormat outputFormat, out byte[]? output);
+
+ Task<(ImageFormat outputFormat, byte[] output)> ConvertAsync(Stream inputStream, ImageFormat convertTo, CancellationToken token = default);
+ }
+}
diff --git a/Mirai-CSharp/Services/IVoiceConverter.cs b/Mirai-CSharp/Services/IVoiceConverter.cs
new file mode 100644
index 0000000..70792ce
--- /dev/null
+++ b/Mirai-CSharp/Services/IVoiceConverter.cs
@@ -0,0 +1,14 @@
+using System;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Mirai.CSharp.Services
+{
+ public interface IVoiceConverter
+ {
+ bool TryConvert(ReadOnlySpan inputSpan, out byte[]? output);
+
+ Task ConvertAsync(Stream inputStream, CancellationToken token = default);
+ }
+}
diff --git a/Mirai-CSharp/Session/IMiraiSession.GroupAnnouncement.cs b/Mirai-CSharp/Session/IMiraiSession.GroupAnnouncement.cs
index b10c3f6..6294a77 100644
--- a/Mirai-CSharp/Session/IMiraiSession.GroupAnnouncement.cs
+++ b/Mirai-CSharp/Session/IMiraiSession.GroupAnnouncement.cs
@@ -35,7 +35,7 @@ public partial interface IMiraiSession
/// 表示此异步操作的
Task DeleteGroupAnnouncementAsync(long groupNumber, string id, CancellationToken token = default);
- ///
+ ///
/// 群公告实例
Task DeleteGroupAnnouncementAsync(long groupNumber, IGroupAnnouncement announcement, CancellationToken token = default);
}
diff --git a/Mirai-CSharp/Session/IMiraiSession.Management.cs b/Mirai-CSharp/Session/IMiraiSession.Management.cs
index c03cd6f..7065138 100644
--- a/Mirai-CSharp/Session/IMiraiSession.Management.cs
+++ b/Mirai-CSharp/Session/IMiraiSession.Management.cs
@@ -1,8 +1,8 @@
-using Mirai.CSharp.Exceptions;
-using Mirai.CSharp.Models;
using System;
using System.Threading;
using System.Threading.Tasks;
+using Mirai.CSharp.Exceptions;
+using Mirai.CSharp.Models;
namespace Mirai.CSharp.Session
{
@@ -120,6 +120,15 @@ public partial interface IMiraiSession
/// 表示此异步操作的
Task SetEssenceMessageAsync(int id, CancellationToken token = default);
+ ///
+ /// 异步设置群精华消息
+ ///
+ /// 消息Id
+ /// 消息来源群号/好友QQ号
+ /// 用于取消此异步操作的
+ /// 表示此异步操作的
+ Task SetEssenceMessageAsync(int id, long target, CancellationToken token = default);
+
///
/// 异步修改群信息
///
diff --git a/Mirai-CSharp/Session/IMiraiSession.SendMessage.cs b/Mirai-CSharp/Session/IMiraiSession.SendMessage.cs
index 62b0c74..56129a5 100644
--- a/Mirai-CSharp/Session/IMiraiSession.SendMessage.cs
+++ b/Mirai-CSharp/Session/IMiraiSession.SendMessage.cs
@@ -3,7 +3,6 @@
using System.Threading.Tasks;
using Mirai.CSharp.Builders;
using Mirai.CSharp.Exceptions;
-using Mirai.CSharp.Models;
using Mirai.CSharp.Models.ChatMessages;
#pragma warning disable CS1573 // Parameter has no matching param tag in the XML comment (but other parameters do)
@@ -23,10 +22,29 @@ public partial interface IMiraiSession
/// - 的返回值
///
///
+ /// 用于取消此异步操作的
///
///
Task RevokeMessageAsync(int messageId, CancellationToken token = default);
+ ///
+ /// 异步撤回消息
+ ///
+ ///
+ /// 请提供以下之一
+ ///
+ ///
+ /// - 的返回值
+ /// - 的返回值
+ /// - 的返回值
+ ///
+ ///
+ /// 消息来源群号/好友QQ号
+ /// 用于取消此异步操作的
+ ///
+ ///
+ Task RevokeMessageAsync(int messageId, long target, CancellationToken token = default);
+
///
Task SendFriendMessageAsync(long qqNumber, params IChatMessage[] chain);
///
diff --git a/Mirai-CSharp/Session/MiraiSession.Management.cs b/Mirai-CSharp/Session/MiraiSession.Management.cs
index f43bf0f..d8de8ef 100644
--- a/Mirai-CSharp/Session/MiraiSession.Management.cs
+++ b/Mirai-CSharp/Session/MiraiSession.Management.cs
@@ -40,6 +40,9 @@ public abstract partial class MiraiSession
///
public abstract Task SetEssenceMessageAsync(int id, CancellationToken token = default);
+ ///
+ public abstract Task SetEssenceMessageAsync(int id, long target, CancellationToken token = default);
+
///
public abstract Task ChangeGroupConfigAsync(long groupNumber, IGroupConfig config, CancellationToken token = default);
diff --git a/Mirai-CSharp/Session/MiraiSession.SendMessage.cs b/Mirai-CSharp/Session/MiraiSession.SendMessage.cs
index 29915fa..2f971dd 100644
--- a/Mirai-CSharp/Session/MiraiSession.SendMessage.cs
+++ b/Mirai-CSharp/Session/MiraiSession.SendMessage.cs
@@ -9,6 +9,8 @@ public abstract partial class MiraiSession
{
public abstract Task RevokeMessageAsync(int messageId, CancellationToken token = default);
+ public abstract Task RevokeMessageAsync(int messageId, long target, CancellationToken token = default);
+
///
public virtual Task SendFriendMessageAsync(long qqNumber, params IChatMessage[] chain)
{