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 GetObjectAsync(this Task responseTask, Type returnType, JsonSerializerOptions? options, CancellationToken token = default) { using HttpResponseMessage response = await responseTask.ConfigureAwait(false); - return await response.Content.ReadFromJsonAsync(returnType, options, token); + return await response.Content.ReadFromJsonAsync(returnType, options, token).ConfigureAwait(false); } /// @@ -178,7 +178,7 @@ public static async Task GetJsonAsync(this Task GetJsonAsync(this Task(stream, options, token); + return await JsonSerializer.DeserializeAsync(stream, options, token).ConfigureAwait(false); } /// @@ -214,8 +214,8 @@ public static async Task GetJsonAsync(this Task GetObjectAsync(this Task responseTask, Type returnType, JsonSerializerOptions? options, CancellationToken token = default) { using HttpResponseMessage response = await responseTask.ConfigureAwait(false); - using var stream = await response.Content.ReadAsStreamAsync(); - return await JsonSerializer.DeserializeAsync(stream, returnType, options, token); + using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); + return await JsonSerializer.DeserializeAsync(stream, returnType, options, token).ConfigureAwait(false); } /// @@ -237,8 +237,8 @@ public static Task GetJsonAsync(this Task res public static async Task GetJsonAsync(this Task responseTask, JsonDocumentOptions options, CancellationToken token = default) { using HttpResponseMessage response = await responseTask.ConfigureAwait(false); - using Stream stream = await response.Content.ReadAsStreamAsync(); - return await JsonDocument.ParseAsync(stream, options, token); + using Stream stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); + return await JsonDocument.ParseAsync(stream, options, token).ConfigureAwait(false); } #endif /// diff --git a/Mirai-CSharp.HttpApi/Mirai-CSharp.HttpApi.csproj b/Mirai-CSharp.HttpApi/Mirai-CSharp.HttpApi.csproj index eb758d5..bc51011 100644 --- a/Mirai-CSharp.HttpApi/Mirai-CSharp.HttpApi.csproj +++ b/Mirai-CSharp.HttpApi/Mirai-CSharp.HttpApi.csproj @@ -1,4 +1,4 @@ - + Mirai.CSharp.HttpApi @@ -19,13 +19,4 @@ - - - - - - - - - diff --git a/Mirai-CSharp.HttpApi/Models/EventArgs/Friend/FriendMessageEventArgs.cs b/Mirai-CSharp.HttpApi/Models/EventArgs/Friend/FriendMessageEventArgs.cs index 779a2b1..087e9e3 100644 --- a/Mirai-CSharp.HttpApi/Models/EventArgs/Friend/FriendMessageEventArgs.cs +++ b/Mirai-CSharp.HttpApi/Models/EventArgs/Friend/FriendMessageEventArgs.cs @@ -22,6 +22,8 @@ public interface IFriendMessageEventArgs : ISharedJsonFriendMessageEventArgs, IC new IFriendInfo Sender { get; } #if !NETSTANDARD2_0 + [JsonConverter(typeof(ChangeTypeJsonConverter))] + [JsonPropertyName("sender")] ISharedFriendInfo ISharedFriendMessageEventArgs.Sender => Sender; #endif } @@ -46,6 +48,8 @@ public override string ToString() => $"{Sender.Name}({Sender.Id}) -> {string.Join("", (IEnumerable)Chain)}"; #if NETSTANDARD2_0 + [JsonConverter(typeof(ChangeTypeJsonConverter))] + [JsonPropertyName("sender")] ISharedFriendInfo ISharedFriendMessageEventArgs.Sender => Sender; #endif } diff --git a/Mirai-CSharp.HttpApi/Models/EventArgs/Friend/FriendSyncMessageEventArgs.cs b/Mirai-CSharp.HttpApi/Models/EventArgs/Friend/FriendSyncMessageEventArgs.cs new file mode 100644 index 0000000..3fb980f --- /dev/null +++ b/Mirai-CSharp.HttpApi/Models/EventArgs/Friend/FriendSyncMessageEventArgs.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; +using System.Text.Json.Serialization; +using Mirai.CSharp.HttpApi.Models.ChatMessages; +using Mirai.CSharp.HttpApi.Parsers.Attributes; +using Mirai.CSharp.HttpApi.Utility.JsonConverters; +using ISharedFriendInfo = Mirai.CSharp.Models.IFriendInfo; +using ISharedFriendSyncMessageEventArgs = Mirai.CSharp.Models.EventArgs.IFriendSyncMessageEventArgs; +using ISharedJsonFriendMessageEventArgs = Mirai.CSharp.Models.EventArgs.IFriendSyncMessageEventArgs; + +#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. +namespace Mirai.CSharp.HttpApi.Models.EventArgs +{ + /// + /// 提供好友同步消息的相关信息接口。继承自 + /// + [MappableMiraiHttpMessageKey("FriendSyncMessage")] + public interface IFriendSyncMessageEventArgs : ISharedJsonFriendMessageEventArgs, ICommonMessageEventArgs + { + /// + [JsonConverter(typeof(ChangeTypeJsonConverter))] + [JsonPropertyName("subject")] + new IFriendInfo Subject { get; } + +#if !NETSTANDARD2_0 + [JsonConverter(typeof(ChangeTypeJsonConverter))] + [JsonPropertyName("subject")] + ISharedFriendInfo ISharedFriendSyncMessageEventArgs.Subject => Subject; +#endif + } + + public class FriendSyncMessageEventArgs : CommonMessageEventArgs, IFriendSyncMessageEventArgs + { + /// + [JsonConverter(typeof(ChangeTypeJsonConverter))] + [JsonPropertyName("subject")] + public IFriendInfo Subject { get; set; } + + [Obsolete("此类不应由用户主动创建实例。")] + public FriendSyncMessageEventArgs() { } + + [Obsolete("此类不应由用户主动创建实例。")] + public FriendSyncMessageEventArgs(IChatMessage[] chain, IFriendInfo subject) : base(chain) + { + Subject = subject; + } + + public override string ToString() + => $"{Subject.Name}({Subject.Id})[SYNC] <- {string.Join("", (IEnumerable)Chain)}"; + +#if NETSTANDARD2_0 + [JsonConverter(typeof(ChangeTypeJsonConverter))] + [JsonPropertyName("subject")] + ISharedFriendInfo ISharedFriendSyncMessageEventArgs.Subject => Subject; +#endif + } +} diff --git a/Mirai-CSharp.HttpApi/Models/EventArgs/Group/GroupApplyEventArgs.cs b/Mirai-CSharp.HttpApi/Models/EventArgs/Group/GroupApplyEventArgs.cs index 6b97a28..a105d89 100644 --- a/Mirai-CSharp.HttpApi/Models/EventArgs/Group/GroupApplyEventArgs.cs +++ b/Mirai-CSharp.HttpApi/Models/EventArgs/Group/GroupApplyEventArgs.cs @@ -15,6 +15,9 @@ public interface IGroupApplyEventArgs : ISharedGroupApplyEventArgs, ICommonGroup public class GroupApplyEventArgs : CommonGroupApplyEventArgs, IGroupApplyEventArgs { + /// + public long? InvitorId { get; } + [Obsolete("此类不应由用户主动创建实例。")] public GroupApplyEventArgs() { @@ -22,9 +25,9 @@ public GroupApplyEventArgs() } [Obsolete("此类不应由用户主动创建实例。")] - public GroupApplyEventArgs(string fromGroupName, long eventId, long fromGroup, long fromQQ, string nickName, string message) : base(fromGroupName, eventId, fromGroup, fromQQ, nickName, message) + public GroupApplyEventArgs(string fromGroupName, long eventId, long fromGroup, long fromQQ, string nickName, string message, long? invitorId) : base(fromGroupName, eventId, fromGroup, fromQQ, nickName, message) { - + InvitorId = invitorId; } } } diff --git a/Mirai-CSharp.HttpApi/Models/EventArgs/Group/GroupDisbandEventArgs.cs b/Mirai-CSharp.HttpApi/Models/EventArgs/Group/GroupDisbandEventArgs.cs new file mode 100644 index 0000000..e84dc1c --- /dev/null +++ b/Mirai-CSharp.HttpApi/Models/EventArgs/Group/GroupDisbandEventArgs.cs @@ -0,0 +1,30 @@ +using System; +using Mirai.CSharp.HttpApi.Parsers.Attributes; +using ISharedGroupDisbandEventArgs = Mirai.CSharp.Models.EventArgs.IGroupDisbandEventArgs; + +namespace Mirai.CSharp.HttpApi.Models.EventArgs.Group +{ + /// + /// 提供群被群主解散后bot离开群相关信息的接口。继承自 + /// + [MappableMiraiHttpMessageKey("BotLeaveEventDisband")] + public interface IGroupDisbandEventArgs : ISharedGroupDisbandEventArgs, IGroupOperatingEventArgs + { + + } + + public class GroupDisbandEventArgs : GroupOperatingEventArgs, IGroupDisbandEventArgs + { + [Obsolete("此类不应由用户主动创建实例。")] + public GroupDisbandEventArgs() + { + + } + + [Obsolete("此类不应由用户主动创建实例。")] + public GroupDisbandEventArgs(IGroupInfo group, IGroupMemberInfo @operator) : base(group, @operator) + { + + } + } +} diff --git a/Mirai-CSharp.HttpApi/Models/EventArgs/Group/GroupSyncMessageEventArgs.cs b/Mirai-CSharp.HttpApi/Models/EventArgs/Group/GroupSyncMessageEventArgs.cs new file mode 100644 index 0000000..fe36310 --- /dev/null +++ b/Mirai-CSharp.HttpApi/Models/EventArgs/Group/GroupSyncMessageEventArgs.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using System.Text.Json.Serialization; +using Mirai.CSharp.HttpApi.Models.ChatMessages; +using Mirai.CSharp.HttpApi.Parsers.Attributes; +using Mirai.CSharp.HttpApi.Utility.JsonConverters; +using ISharedGroupInfo = Mirai.CSharp.Models.IGroupInfo; +using ISharedGroupSyncMessageEventArgs = Mirai.CSharp.Models.EventArgs.IGroupSyncMessageEventArgs; +using ISharedJsonElementGroupSyncMessageEventArgs = Mirai.CSharp.Models.EventArgs.IGroupSyncMessageEventArgs; + +namespace Mirai.CSharp.HttpApi.Models.EventArgs +{ + /// + /// 提供群同步消息相关信息的接口。继承自 + /// + [MappableMiraiHttpMessageKey("GroupSyncMessage")] + public interface IGroupSyncMessageEventArgs : ISharedJsonElementGroupSyncMessageEventArgs, ICommonMessageEventArgs + { + /// + [JsonConverter(typeof(ChangeTypeJsonConverter))] + [JsonPropertyName("subject")] + new IGroupInfo Subject { get; } + +#if !NETSTANDARD2_0 + [JsonConverter(typeof(ChangeTypeJsonConverter))] + [JsonPropertyName("subject")] + ISharedGroupInfo ISharedGroupSyncMessageEventArgs.Subject => Subject; +#endif + } + + public class GroupSyncMessageEventArgs : CommonMessageEventArgs, IGroupSyncMessageEventArgs + { + /// + [JsonConverter(typeof(ChangeTypeJsonConverter))] + [JsonPropertyName("subject")] + public IGroupInfo Subject { get; set; } = null!; + + [Obsolete("此类不应由用户主动创建实例。")] + public GroupSyncMessageEventArgs() { } + + [Obsolete("此类不应由用户主动创建实例。")] + public GroupSyncMessageEventArgs(IChatMessage[] chain, IGroupInfo subject) : base(chain) + { + Subject = subject; + } + + public override string ToString() + => $"[{Subject.Name}({Subject.Id})][SYNC] <- {string.Join("", (IEnumerable)Chain)}"; + +#if NETSTANDARD2_0 + [JsonConverter(typeof(ChangeTypeJsonConverter))] + [JsonPropertyName("subject")] + ISharedGroupInfo ISharedGroupSyncMessageEventArgs.Subject => Subject; +#endif + } +} diff --git a/Mirai-CSharp.HttpApi/Models/EventArgs/OtherClient/OtherClientMessageEventArgs.cs b/Mirai-CSharp.HttpApi/Models/EventArgs/OtherClient/OtherClientMessageEventArgs.cs index 81abba1..f7c8f9b 100644 --- a/Mirai-CSharp.HttpApi/Models/EventArgs/OtherClient/OtherClientMessageEventArgs.cs +++ b/Mirai-CSharp.HttpApi/Models/EventArgs/OtherClient/OtherClientMessageEventArgs.cs @@ -1,6 +1,7 @@ using System.Text.Json.Serialization; using Mirai.CSharp.HttpApi.Models.ChatMessages; using Mirai.CSharp.HttpApi.Parsers.Attributes; +using Mirai.CSharp.HttpApi.Utility.JsonConverters; using ISharedJsonElementOtherClientMessageEventArgs = Mirai.CSharp.Models.EventArgs.IOtherClientMessageEventArgs; using ISharedOtherClientInfo = Mirai.CSharp.Models.IOtherClientInfo; using ISharedOtherClientMessageEventArgs = Mirai.CSharp.Models.EventArgs.IOtherClientMessageEventArgs; @@ -24,6 +25,7 @@ public interface IOtherClientMessageEventArgs : ISharedJsonElementOtherClientMes public class OtherClientMessageEventArgs : CommonMessageEventArgs, IOtherClientMessageEventArgs { + [JsonConverter(typeof(ChangeTypeJsonConverter))] [JsonPropertyName("sender")] public IOtherClientInfo Sender { get; set; } = null!; diff --git a/Mirai-CSharp.HttpApi/Models/EventArgs/Stranger/StrangerSyncMessageEventArgs.cs b/Mirai-CSharp.HttpApi/Models/EventArgs/Stranger/StrangerSyncMessageEventArgs.cs new file mode 100644 index 0000000..608ad9a --- /dev/null +++ b/Mirai-CSharp.HttpApi/Models/EventArgs/Stranger/StrangerSyncMessageEventArgs.cs @@ -0,0 +1,52 @@ +using System.Text.Json.Serialization; +using Mirai.CSharp.HttpApi.Models.ChatMessages; +using Mirai.CSharp.HttpApi.Parsers.Attributes; +using Mirai.CSharp.HttpApi.Utility.JsonConverters; +using ISharedJsonElementStrangerSyncMessageEventArgs = Mirai.CSharp.Models.EventArgs.IStrangerSyncMessageEventArgs; +using ISharedStrangerInfo = Mirai.CSharp.Models.IStrangerInfo; +using ISharedStrangerSyncMessageEventArgs = Mirai.CSharp.Models.EventArgs.IStrangerSyncMessageEventArgs; + +#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. +namespace Mirai.CSharp.HttpApi.Models.EventArgs +{ + /// + /// 提供陌生人同步消息的相关信息接口。继承自 + /// + [MappableMiraiHttpMessageKey("StrangerSyncMessage")] + public interface IStrangerSyncMessageEventArgs : ISharedJsonElementStrangerSyncMessageEventArgs, ICommonMessageEventArgs + { + /// + [JsonConverter(typeof(ChangeTypeJsonConverter))] + [JsonPropertyName("subject")] + new IStrangerInfo Subject { get; } + +#if !NETSTANDARD2_0 + [JsonConverter(typeof(ChangeTypeJsonConverter))] + [JsonPropertyName("subject")] + ISharedStrangerInfo ISharedStrangerSyncMessageEventArgs.Subject => Subject; +#endif + } + + public class StrangerSyncMessageEventArgs : CommonMessageEventArgs, IStrangerSyncMessageEventArgs + { + [JsonConverter(typeof(ChangeTypeJsonConverter))] + [JsonPropertyName("subject")] + public IStrangerInfo Subject { get; set; } + + public StrangerSyncMessageEventArgs() + { + + } + + public StrangerSyncMessageEventArgs(IChatMessage[] chain, IStrangerInfo subject) : base(chain) + { + Subject = subject; + } + +#if NETSTANDARD2_0 + [JsonConverter(typeof(ChangeTypeJsonConverter))] + [JsonPropertyName("subject")] + ISharedStrangerInfo ISharedStrangerSyncMessageEventArgs.Subject => Subject; +#endif + } +} diff --git a/Mirai-CSharp.HttpApi/Models/EventArgs/Temp/ITempMessageEventArgs.cs b/Mirai-CSharp.HttpApi/Models/EventArgs/Temp/TempMessageEventArgs.cs similarity index 100% rename from Mirai-CSharp.HttpApi/Models/EventArgs/Temp/ITempMessageEventArgs.cs rename to Mirai-CSharp.HttpApi/Models/EventArgs/Temp/TempMessageEventArgs.cs diff --git a/Mirai-CSharp.HttpApi/Models/EventArgs/Temp/TempSyncMessageEventArgs.cs b/Mirai-CSharp.HttpApi/Models/EventArgs/Temp/TempSyncMessageEventArgs.cs new file mode 100644 index 0000000..1d70bd1 --- /dev/null +++ b/Mirai-CSharp.HttpApi/Models/EventArgs/Temp/TempSyncMessageEventArgs.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using System.Text.Json.Serialization; +using Mirai.CSharp.HttpApi.Models.ChatMessages; +using Mirai.CSharp.HttpApi.Parsers.Attributes; +using Mirai.CSharp.HttpApi.Utility.JsonConverters; +using ISharedGroupMemberInfo = Mirai.CSharp.Models.IGroupMemberInfo; +using ISharedTempSyncMessageEventArgs = Mirai.CSharp.Models.EventArgs.ITempSyncMessageEventArgs; +using ISharedJsonElementTempSyncMessageEventArgs = Mirai.CSharp.Models.EventArgs.ITempSyncMessageEventArgs; + +namespace Mirai.CSharp.HttpApi.Models.EventArgs +{ + /// + /// 提供临时同步消息的相关信息接口。继承自 + /// + [MappableMiraiHttpMessageKey("TempSyncMessage")] + public interface ITempSyncMessageEventArgs : ISharedJsonElementTempSyncMessageEventArgs, ICommonMessageEventArgs + { + /// + [JsonConverter(typeof(ChangeTypeJsonConverter))] + [JsonPropertyName("subject")] + new IGroupMemberInfo Subject { get; } + +#if !NETSTANDARD2_0 + [JsonConverter(typeof(ChangeTypeJsonConverter))] + [JsonPropertyName("subject")] + ISharedGroupMemberInfo ISharedTempSyncMessageEventArgs.Subject => Subject; +#endif + } + + public class TempSyncMessageEventArgs : CommonMessageEventArgs, ITempSyncMessageEventArgs + { + /// + [JsonConverter(typeof(ChangeTypeJsonConverter))] + [JsonPropertyName("subject")] + public IGroupMemberInfo Subject { get; set; } = null!; + + [Obsolete("此类不应由用户主动创建实例。")] + public TempSyncMessageEventArgs() { } + + [Obsolete("此类不应由用户主动创建实例。")] + public TempSyncMessageEventArgs(IChatMessage[] chain, IGroupMemberInfo subject) : base(chain) + { + Subject = subject; + } + + public override string ToString() + => $"{Subject.Name}(Temp {Subject.Id})[SYNC] <- {string.Join("", (IEnumerable)Chain)}"; + +#if NETSTANDARD2_0 + [JsonConverter(typeof(ChangeTypeJsonConverter))] + [JsonPropertyName("subject")] + ISharedGroupMemberInfo ISharedTempSyncMessageEventArgs.Subject => Subject; +#endif + } +} diff --git a/Mirai-CSharp.HttpApi/Options/MiraiHttpSessionOptions.cs b/Mirai-CSharp.HttpApi/Options/MiraiHttpSessionOptions.cs index 2eddc77..ce85c80 100644 --- a/Mirai-CSharp.HttpApi/Options/MiraiHttpSessionOptions.cs +++ b/Mirai-CSharp.HttpApi/Options/MiraiHttpSessionOptions.cs @@ -24,11 +24,18 @@ public class MiraiHttpSessionOptions /// public bool SuppressAwaitMessageInvoker { get; set; } /// + /// 请求形式 + /// + /// + /// 如果本属性为 , 将使用http + /// + public string? Scheme { get; set; } + /// /// 内部使用。 /// internal string BaseUrl { - get => _baseUrl ??= $"http://{Host}:{Port}"; + get => _baseUrl ??= $"{Scheme ?? "http"}://{Host}:{Port}"; set => _baseUrl = value; } diff --git a/Mirai-CSharp.HttpApi/Session/IMiraiHttpSession.Authentication.cs b/Mirai-CSharp.HttpApi/Session/IMiraiHttpSession.Authentication.cs index eec968a..5a5d313 100644 --- a/Mirai-CSharp.HttpApi/Session/IMiraiHttpSession.Authentication.cs +++ b/Mirai-CSharp.HttpApi/Session/IMiraiHttpSession.Authentication.cs @@ -3,7 +3,6 @@ using System.Threading.Tasks; using Mirai.CSharp.Exceptions; using Mirai.CSharp.HttpApi.Exceptions; -using Mirai.CSharp.Session; namespace Mirai.CSharp.HttpApi.Session { diff --git a/Mirai-CSharp.HttpApi/Session/IMiraiHttpSession.Management.cs b/Mirai-CSharp.HttpApi/Session/IMiraiHttpSession.Management.cs new file mode 100644 index 0000000..2def568 --- /dev/null +++ b/Mirai-CSharp.HttpApi/Session/IMiraiHttpSession.Management.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace Mirai.CSharp.HttpApi.Session +{ + public partial interface IMiraiHttpSession + { + /// + /// 异步获取已登录的所有QQ号 + /// + /// + /// 本 api 自 mirai-api-http v2.6.0 起可用 + /// + /// 用于取消此异步操作的 + /// 表示此异步操作的 + Task GetBotListAsync(CancellationToken token = default); + } +} diff --git a/Mirai-CSharp.HttpApi/Session/IMiraiHttpSession.MessageCache.cs b/Mirai-CSharp.HttpApi/Session/IMiraiHttpSession.MessageCache.cs index 8b9b998..6d2857f 100644 --- a/Mirai-CSharp.HttpApi/Session/IMiraiHttpSession.MessageCache.cs +++ b/Mirai-CSharp.HttpApi/Session/IMiraiHttpSession.MessageCache.cs @@ -2,7 +2,7 @@ using System.Threading; using System.Threading.Tasks; using Mirai.CSharp.HttpApi.Models; -using Mirai.CSharp.HttpApi.Parsers; +using Mirai.CSharp.HttpApi.Models.ChatMessages; namespace Mirai.CSharp.HttpApi.Session { @@ -15,6 +15,31 @@ public partial interface IMiraiHttpSession /// 消息Id /// 用于取消此异步操作的 /// 表示此异步操作的 + [Obsolete("自mirai-api-http 2.6.0起, 要求传入消息所在的群号或好友QQ号, 请考虑调用RetriveMessageAsync(int, long, CancellationToken)方法")] Task RetriveMessageAsync(int messageId, CancellationToken token = default); + + /// + /// 异步获取缓存于 mirai-api-http 的消息 + /// + /// + /// 消息Id + /// 消息来源群号/好友QQ号 + /// 用于取消此异步操作的 + /// 表示此异步操作的 + Task RetriveMessageAsync(int messageId, long target, CancellationToken token = default); + + /// + /// 异步获取与给定好友的漫游聊天记录 + /// + /// + /// 返回的 [][] 表示以消息链 [] 为类型的一个数组 + /// 本 api 自 mirai-api-http v2.6.0 起可用 + /// + /// 好友QQ号 + /// 指示从何时起的聊天记录 + /// 指示至何时的聊天记录 + /// 用于取消此异步操作的 + /// 表示此异步操作的 + Task GetChatHistoryAsync(long target, DateTime? from, DateTime? to, CancellationToken token = default); } } diff --git a/Mirai-CSharp.HttpApi/Session/MiraiHttpSession.Authentication.cs b/Mirai-CSharp.HttpApi/Session/MiraiHttpSession.Authentication.cs index 54926af..158c3cd 100644 --- a/Mirai-CSharp.HttpApi/Session/MiraiHttpSession.Authentication.cs +++ b/Mirai-CSharp.HttpApi/Session/MiraiHttpSession.Authentication.cs @@ -8,6 +8,7 @@ using System.Threading; using System.Threading.Tasks; using System.Text.Json.Serialization; +using Mirai.CSharp.HttpApi.Models.ChatMessages; #if NET5_0_OR_GREATER using System.Net.Http.Json; #endif diff --git a/Mirai-CSharp.HttpApi/Session/MiraiHttpSession.Command.cs b/Mirai-CSharp.HttpApi/Session/MiraiHttpSession.Command.cs index 7e11cde..3fb08c6 100644 --- a/Mirai-CSharp.HttpApi/Session/MiraiHttpSession.Command.cs +++ b/Mirai-CSharp.HttpApi/Session/MiraiHttpSession.Command.cs @@ -113,7 +113,7 @@ public override Task ExecuteCommandAsync(string name, string[]? args, Cancellati [Obsolete("新版本的 mirai-console 中已经没有管理员概念了, 参考: https://github.com/project-mirai/mirai-api-http/pull/265#discussion_r598428011")] public static async Task GetManagersAsync(HttpClient client, MiraiHttpSessionOptions options, long qqNumber, CancellationToken token = default) { - string json = await client.GetAsync($"{options.BaseUrl}/managers?qq={qqNumber}", token).GetStringAsync(token); + string json = await client.GetAsync($"{options.BaseUrl}/managers?qq={qqNumber}", token).GetStringAsync(token).ConfigureAwait(false); if (!string.IsNullOrEmpty(json)) { return JsonSerializer.Deserialize(json)!; diff --git a/Mirai-CSharp.HttpApi/Session/MiraiHttpSession.Configuration.cs b/Mirai-CSharp.HttpApi/Session/MiraiHttpSession.Configuration.cs index 0efe5d9..8e17a1a 100644 --- a/Mirai-CSharp.HttpApi/Session/MiraiHttpSession.Configuration.cs +++ b/Mirai-CSharp.HttpApi/Session/MiraiHttpSession.Configuration.cs @@ -16,8 +16,8 @@ public partial class MiraiHttpSession { private Task GetConfigAsync(InternalSessionInfo session, CancellationToken token = default) { - return _client.GetAsync($"{_options.BaseUrl}/config?sessionKey={WebUtility.UrlEncode(session.SessionKey)}", session.Token) - .AsApiRespAsync(session.Token); + return _client.GetAsync($"{_options.BaseUrl}/config?sessionKey={WebUtility.UrlEncode(session.SessionKey)}", token) + .AsApiRespAsync(token); } private Task SetConfigAsync(InternalSessionInfo session, IMiraiSessionConfig config, CancellationToken token = default) @@ -28,7 +28,7 @@ private Task SetConfigAsync(InternalSessionInfo session, IMiraiSessionConfig con cacheSize = config.CacheSize, enableWebsocket = config.EnableWebSocket }; - return _client.PostAsJsonAsync($"{_options.BaseUrl}/config", payload, JsonSerializeOptionsFactory.IgnoreNulls, session.Token).AsApiRespAsync(session.Token); + return _client.PostAsJsonAsync($"{_options.BaseUrl}/config", payload, JsonSerializeOptionsFactory.IgnoreNulls, token).AsApiRespAsync(token); } /// diff --git a/Mirai-CSharp.HttpApi/Session/MiraiHttpSession.GroupFile.cs b/Mirai-CSharp.HttpApi/Session/MiraiHttpSession.GroupFile.cs index a284739..e5d6bf6 100644 --- a/Mirai-CSharp.HttpApi/Session/MiraiHttpSession.GroupFile.cs +++ b/Mirai-CSharp.HttpApi/Session/MiraiHttpSession.GroupFile.cs @@ -13,7 +13,6 @@ using System.Net.Http.Json; #endif -#pragma warning disable CA2016 // Forward the 'CancellationToken' parameter to methods namespace Mirai.CSharp.HttpApi.Session { public partial class MiraiHttpSession diff --git a/Mirai-CSharp.HttpApi/Session/MiraiHttpSession.Management.cs b/Mirai-CSharp.HttpApi/Session/MiraiHttpSession.Management.cs index efd9ba6..76c533d 100644 --- a/Mirai-CSharp.HttpApi/Session/MiraiHttpSession.Management.cs +++ b/Mirai-CSharp.HttpApi/Session/MiraiHttpSession.Management.cs @@ -15,6 +15,7 @@ using System.Net.Http.Json; #endif +#pragma warning disable CS0809 // Obsolete member overrides non-obsolete member namespace Mirai.CSharp.HttpApi.Session { public partial class MiraiHttpSession @@ -163,15 +164,51 @@ public override Task DeleteFriendAsync(long qqNumber, CancellationToken token = } /// + /// + [Obsolete("自mirai-api-http 2.6.0起, 要求传入消息所在的群号或好友QQ号, 请考虑调用SetEssenceMessageAsync(int, long, CancellationToken)方法")] public override Task SetEssenceMessageAsync(int id, CancellationToken token = default) { InternalSessionInfo session = SafeGetSession(); - CreateLinkedUserSessionToken(session.Token, token, out CancellationTokenSource? cts, out token); + if (session.ApiVersion >= new Version(2, 6, 0)) + { + throw new NotSupportedException("自mirai-api-http 2.6.0起, 要求传入消息所在的群号或好友QQ号, 请调用SetEssenceMessageAsync(int, long, CancellationToken)方法"); + } var payload = new { sessionKey = session.SessionKey, target = id, }; + return SetEssenceMessageAsync(session, payload, token); + } + + /// + public override Task SetEssenceMessageAsync(int id, long target, CancellationToken token = default) + { + InternalSessionInfo session = SafeGetSession(); + object payload; + if (session.ApiVersion >= new Version(2, 6, 0)) + { + payload = new + { + sessionKey = session.SessionKey, + messageId = id, + target + }; + } + else + { + payload = new + { + sessionKey = session.SessionKey, + target = id, + }; + } + return SetEssenceMessageAsync(session, payload, token); + } + + private Task SetEssenceMessageAsync(InternalSessionInfo session, object payload, CancellationToken token = default) + { + CreateLinkedUserSessionToken(session.Token, token, out CancellationTokenSource? cts, out token); return _client.PostAsJsonAsync($"{_options.BaseUrl}/setEssence", payload, token) .AsApiRespAsync(token) .DisposeWhenCompleted(cts); @@ -246,5 +283,15 @@ public override Task SetGroupAdminAsync(long memberId, long groupNumber, bool as .AsApiRespAsync(token) .DisposeWhenCompleted(cts); } + + /// + public virtual Task GetBotListAsync(CancellationToken token = default) + { + InternalSessionInfo session = SafeGetSession(); + CreateLinkedUserSessionToken(session.Token, token, out CancellationTokenSource? cts, out token); + return _client.GetAsync($"{_options.BaseUrl}/botList", token) + .AsApiRespAsync(token) + .DisposeWhenCompleted(cts); + } } } diff --git a/Mirai-CSharp.HttpApi/Session/MiraiHttpSession.MessageCache.cs b/Mirai-CSharp.HttpApi/Session/MiraiHttpSession.MessageCache.cs index 5eb41e2..c28140f 100644 --- a/Mirai-CSharp.HttpApi/Session/MiraiHttpSession.MessageCache.cs +++ b/Mirai-CSharp.HttpApi/Session/MiraiHttpSession.MessageCache.cs @@ -1,3 +1,4 @@ +using System; using System.Text.Json; using System.Threading; using System.Threading.Tasks; @@ -5,7 +6,12 @@ using Mirai.CSharp.Extensions; using Mirai.CSharp.HttpApi.Extensions; using Mirai.CSharp.HttpApi.Models; +using Mirai.CSharp.HttpApi.Models.ChatMessages; using Mirai.CSharp.HttpApi.Parsers; +using Mirai.CSharp.HttpApi.Utility; +#if NET5_0_OR_GREATER +using System.Net.Http.Json; +#endif namespace Mirai.CSharp.HttpApi.Session { @@ -15,14 +21,35 @@ public partial class MiraiHttpSession /// /// 当缓存失效, 或者未注册用于解析消息的 时, 本异步方法将返回 /// - public async Task RetriveMessageAsync(int messageId, CancellationToken token = default) + [Obsolete("自mirai-api-http 2.6.0起, 要求传入消息所在的群号或好友QQ号, 请考虑调用RetriveMessageAsync(int, long, CancellationToken)方法")] + public Task RetriveMessageAsync(int messageId, CancellationToken token = default) { InternalSessionInfo session = SafeGetSession(); + if (session.ApiVersion >= new Version(2, 6, 0)) + { + throw new NotSupportedException("自mirai-api-http 2.6.0起, 要求传入消息所在的群号或好友QQ号, 请考虑调用RetriveMessageAsync(int, long, CancellationToken)方法"); + } + return RetriveMessageAsync(session, $"{_options.BaseUrl}/messageFromId?sessionKey={session.SessionKey}&id={messageId}", token); + } + + /// + /// + /// 当缓存失效, 或者未注册用于解析消息的 时, 本异步方法将返回 + /// + public Task RetriveMessageAsync(int messageId, long target, CancellationToken token = default) + { + InternalSessionInfo session = SafeGetSession(); + return RetriveMessageAsync(session, $"{_options.BaseUrl}/messageFromId?sessionKey={session.SessionKey}&id={messageId}&target={target}", token); + } + + private async Task RetriveMessageAsync(InternalSessionInfo session, string url, CancellationToken token = default) + { IMiraiHttpMessageParserResolver resolver = _services.GetRequiredService(); CreateLinkedUserSessionToken(session.Token, token, out CancellationTokenSource? cts, out token); - JsonElement root = await _client.GetAsync($"{_options.BaseUrl}/messageFromId?sessionKey={session.SessionKey}&id={messageId}", token) + JsonElement root = await _client.GetAsync(url, token) .GetObjectAsync(token) - .DisposeWhenCompleted(cts); + .DisposeWhenCompleted(cts) + .ConfigureAwait(false); root.EnsureApiRespCode(); JsonElement data = root.GetProperty("data"); IMiraiHttpMessageParser? parser = resolver.ResolveParser(in data); @@ -32,5 +59,22 @@ public partial class MiraiHttpSession } return null; } + + /// + public virtual Task GetChatHistoryAsync(long target, DateTime? from, DateTime? to, CancellationToken token = default) + { + InternalSessionInfo session = SafeGetSession(); + CreateLinkedUserSessionToken(session.Token, token, out CancellationTokenSource? cts, out token); + var payload = new + { + sessionKey = session.SessionKey, + timeStart = from.HasValue ? Utils.DateTime2UnixTimeSeconds(from.Value) : 0, + timeEnd = to.HasValue ? Utils.DateTime2UnixTimeSeconds(to.Value) : 0, + target + }; + return _client.PostAsJsonAsync($"{_options.BaseUrl}/roamingMessages", payload, token) + .AsApiRespAsync(_chatMessageSerializingOptions) + .DisposeWhenCompleted(cts); + } } } diff --git a/Mirai-CSharp.HttpApi/Session/MiraiHttpSession.SendImage.cs b/Mirai-CSharp.HttpApi/Session/MiraiHttpSession.SendImage.cs index 3f51f14..de595f0 100644 --- a/Mirai-CSharp.HttpApi/Session/MiraiHttpSession.SendImage.cs +++ b/Mirai-CSharp.HttpApi/Session/MiraiHttpSession.SendImage.cs @@ -5,13 +5,14 @@ using System.Text.Json; using System.Threading; using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; using Mirai.CSharp.Extensions; using Mirai.CSharp.HttpApi.Extensions; +using Mirai.CSharp.HttpApi.Models.ChatMessages; using Mirai.CSharp.HttpApi.Utility; using Mirai.CSharp.Models; -using SkiaSharp; +using Mirai.CSharp.Services; using ISharedImageMessage = Mirai.CSharp.Models.ChatMessages.IImageMessage; -using Mirai.CSharp.HttpApi.Models.ChatMessages; #if NET5_0_OR_GREATER using System.Net.Http.Json; #endif @@ -62,7 +63,23 @@ public override Task SendImageToGroupAsync(long groupNumber, string[] /// private async Task InternalUploadPictureAsync(InternalSessionInfo session, UploadTarget type, Stream imgStream, bool disposeStream, CancellationToken token = default) { - // 使用 disposeStream 目的是不使上层 caller 创建多余的状态机 + IImageConverter? imageConverter = _services.GetService(); + string format; + if (imageConverter != null) + { + var (outputFormat, converted) = await imageConverter.ConvertAsync(imgStream, ImageFormat.Png | ImageFormat.Jpeg | ImageFormat.Gif, token).ConfigureAwait(false); + imgStream = new MemoryStream(converted); + format = outputFormat switch + { + ImageFormat.Png => "png", + ImageFormat.Jpeg => "jpeg", + _ => "gif", + }; + } + else + { + format = "jpeg"; + } if (session.ApiVersion <= new Version(1, 7, 0)) { Guid guid = Guid.NewGuid(); @@ -71,22 +88,6 @@ private async Task InternalUploadPictureAsync(InternalSessi ImageHttpListener.RegisterImage(guid, ms); return new ImageMessage(null, $"http://127.0.0.1:{ImageHttpListener.Port}/fetch?guid={guid:n}", null); } - Stream? internalStream = null; - bool internalCreated = false; - long pervious = 0; - if (!imgStream.CanSeek || imgStream.CanTimeout) // 对于 CanTimeOut 的 imgStream, 或者无法Seek的, 一律假定其读取行为是阻塞的 - // 为其创建一个内部缓存先行异步读取 - { - internalStream = new MemoryStream(8192); - internalCreated = true; - await imgStream.CopyToAsync(internalStream, 81920, token); - internalStream.Seek(0, SeekOrigin.Begin); - } - else // 否则不创建副本, 避免多余的堆分配 - { - internalStream = imgStream; - pervious = imgStream.Position; - } HttpContent sessionKeyContent = new StringContent(session.SessionKey); sessionKeyContent.Headers.ContentDisposition = new ContentDispositionHeaderValue("form-data") { @@ -97,48 +98,7 @@ private async Task InternalUploadPictureAsync(InternalSessi { Name = "type" }; - string format; - using SKManagedStream skstream = new SKManagedStream(internalStream, false); - using (SKCodec codec = SKCodec.Create(skstream)) // 已经把数据读到非托管内存里边了, 就不用管input的死活了 - { - var skformat = codec.EncodedFormat; - format = skformat.ToString().ToLower(); - switch (skformat) - { - case SKEncodedImageFormat.Gif: - case SKEncodedImageFormat.Jpeg: - case SKEncodedImageFormat.Png: - break; - default: - { - skstream.Seek(0); - using (SKBitmap bitmap = SKBitmap.Decode(skstream)) - { - if (!internalCreated) - { - internalStream = new MemoryStream(8192); - internalCreated = true; - } - else - { - internalStream.Seek(0, SeekOrigin.Begin); - } - bitmap.Encode(internalStream, SKEncodedImageFormat.Png, 100); - } - format = "png"; - break; - } - } - } - if (internalCreated) - { - internalStream.Seek(0, SeekOrigin.Begin); - } - else // internalStream == imgStream - { - internalStream.Seek(pervious - internalStream.Position, SeekOrigin.Current); - } - HttpContent imageContent = new StreamContent(internalStream); + HttpContent imageContent = new StreamContent(imgStream); imageContent.Headers.ContentDisposition = new ContentDispositionHeaderValue("form-data") { Name = "img", diff --git a/Mirai-CSharp.HttpApi/Session/MiraiHttpSession.SendMessage.cs b/Mirai-CSharp.HttpApi/Session/MiraiHttpSession.SendMessage.cs index 7061b78..df392b2 100644 --- a/Mirai-CSharp.HttpApi/Session/MiraiHttpSession.SendMessage.cs +++ b/Mirai-CSharp.HttpApi/Session/MiraiHttpSession.SendMessage.cs @@ -6,7 +6,6 @@ using System.Threading.Tasks; using Mirai.CSharp.Extensions; using Mirai.CSharp.HttpApi.Extensions; -using Mirai.CSharp.HttpApi.Models; using Mirai.CSharp.HttpApi.Models.ChatMessages; using IMessageChainBuilder = Mirai.CSharp.Builders.IMessageChainBuilder; using ISharedChatMessage = Mirai.CSharp.Models.ChatMessages.IChatMessage; @@ -14,6 +13,7 @@ using System.Net.Http.Json; #endif +#pragma warning disable CS0809 // Obsolete member overrides non-obsolete member namespace Mirai.CSharp.HttpApi.Session { public partial class MiraiHttpSession @@ -101,15 +101,50 @@ public override Task SendTempMessageAsync(long qqNumber, long groupNumber, } /// + [Obsolete("自mirai-api-http 2.6.0起, 要求传入消息所在的群号或好友QQ号, 请考虑调用RevokeMessageAsync(int, long, CancellationToken)方法")] public override Task RevokeMessageAsync(int messageId, CancellationToken token = default) { InternalSessionInfo session = SafeGetSession(); - CreateLinkedUserSessionToken(session.Token, token, out CancellationTokenSource? cts, out token); + if (session.ApiVersion >= new Version(2, 6, 0)) + { + throw new NotSupportedException("自mirai-api-http 2.6.0起, 要求传入消息所在的群号或好友QQ号, 请调用RevokeMessageAsync(int, long, CancellationToken)方法"); + } var payload = new { sessionKey = session.SessionKey, target = messageId }; + return RevokeMessageAsync(session, payload, token); + } + + /// + public override Task RevokeMessageAsync(int messageId, long target, CancellationToken token = default) + { + InternalSessionInfo session = SafeGetSession(); + object payload; + if (session.ApiVersion >= new Version(2, 6, 0)) + { + payload = new + { + sessionKey = session.SessionKey, + target, + messageId + }; + } + else + { + payload = new + { + sessionKey = session.SessionKey, + target = messageId + }; + } + return RevokeMessageAsync(session, payload, token); + } + + private Task RevokeMessageAsync(InternalSessionInfo session, object payload, CancellationToken token = default) + { + CreateLinkedUserSessionToken(session.Token, token, out CancellationTokenSource? cts, out token); return _client.PostAsJsonAsync($"{_options.BaseUrl}/recall", payload, token).AsApiRespAsync(token).DisposeWhenCompleted(cts); } } diff --git a/Mirai-CSharp.HttpApi/Session/MiraiHttpSession.SendVoice.cs b/Mirai-CSharp.HttpApi/Session/MiraiHttpSession.SendVoice.cs index dca7a76..74800bf 100644 --- a/Mirai-CSharp.HttpApi/Session/MiraiHttpSession.SendVoice.cs +++ b/Mirai-CSharp.HttpApi/Session/MiraiHttpSession.SendVoice.cs @@ -1,25 +1,20 @@ using System; using System.IO; -using System.Linq; using System.Net.Http; -using System.Net.Http.Headers; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; using Mirai.CSharp.Extensions; -using Mirai.CSharp.HttpApi.Exceptions; using Mirai.CSharp.HttpApi.Extensions; using Mirai.CSharp.HttpApi.Models.ChatMessages; using Mirai.CSharp.Models; +using Mirai.CSharp.Services; using ISharedVoiceMessage = Mirai.CSharp.Models.ChatMessages.IVoiceMessage; namespace Mirai.CSharp.HttpApi.Session { public partial class MiraiHttpSession { - private static ReadOnlySpan _silkHeader => new byte[10] { 2, 35, 33, 83, 73, 76, 75, 95, 86, 51 }; - /// /// 内部使用 /// @@ -53,37 +48,21 @@ async Task Await(InternalSessionInfo session, UploadTarget /// 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) {