mirror of
https://gitee.com/fudiwei/DotNetCore.SKIT.FlurlHttpClient.Wechat.git
synced 2026-02-11 10:16:20 +08:00
feat(openai): 升级公共组件
This commit is contained in:
@@ -4,7 +4,7 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI.Events
|
|||||||
/// <para>表示 userEnter 事件的数据。</para>
|
/// <para>表示 userEnter 事件的数据。</para>
|
||||||
/// <para>REF: https://developers.weixin.qq.com/doc/aispeech/confapi/thirdkefu/recivemsg.html </para>
|
/// <para>REF: https://developers.weixin.qq.com/doc/aispeech/confapi/thirdkefu/recivemsg.html </para>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class UserEnterEvent : WechatOpenAIEvent, WechatOpenAIEvent.Serialization.IXmlSerializable
|
public class UserEnterEvent : WechatOpenAIEvent
|
||||||
{
|
{
|
||||||
public static class Types
|
public static class Types
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,24 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace SKIT.FlurlHttpClient.Wechat.OpenAI.Exceptions
|
|
||||||
{
|
|
||||||
public class WechatOpenAIEventSerializationException : WechatOpenAIException
|
|
||||||
{
|
|
||||||
/// <inheritdoc/>
|
|
||||||
internal WechatOpenAIEventSerializationException()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
internal WechatOpenAIEventSerializationException(string message)
|
|
||||||
: base(message)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
internal WechatOpenAIEventSerializationException(string message, Exception innerException)
|
|
||||||
: base(message, innerException)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace SKIT.FlurlHttpClient.Wechat.OpenAI.Exceptions
|
|
||||||
{
|
|
||||||
public class WechatOpenAIRequestTimeoutException : WechatOpenAIException
|
|
||||||
{
|
|
||||||
/// <inheritdoc/>
|
|
||||||
internal WechatOpenAIRequestTimeoutException()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
internal WechatOpenAIRequestTimeoutException(string message)
|
|
||||||
: base(message)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
internal WechatOpenAIRequestTimeoutException(string message, Exception innerException)
|
|
||||||
: base(message, innerException)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -7,19 +7,19 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static class WechatOpenAIClientEventExtensions
|
public static class WechatOpenAIClientEventExtensions
|
||||||
{
|
{
|
||||||
private static TEvent InnerDeserializeEventFromXml<TEvent>(this WechatOpenAIClient client, string callbackXml)
|
private static TEvent InnerDeserializeEventFromXml<TEvent>(this WechatOpenAIClient client, string webhookXml)
|
||||||
where TEvent : WechatOpenAIEvent
|
where TEvent : WechatOpenAIEvent
|
||||||
{
|
{
|
||||||
if (client == null) throw new ArgumentNullException(nameof(client));
|
if (client is null) throw new ArgumentNullException(nameof(client));
|
||||||
if (callbackXml == null) throw new ArgumentNullException(callbackXml);
|
if (webhookXml is null) throw new ArgumentNullException(webhookXml);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (!Utilities.WxMsgCryptor.TryParseXml(callbackXml, out string? encryptedXml))
|
if (!Utilities.WxMsgCryptor.TryParseXml(webhookXml, out string? encryptedXml))
|
||||||
throw new Exceptions.WechatOpenAIEventSerializationException("Failed to encrypt event data, because of empty encrypted data.");
|
throw new WechatOpenAIException("Failed to decrypt event data, because of the encrypted data is empty.");
|
||||||
|
|
||||||
callbackXml = Utilities.WxMsgCryptor.AESDecrypt(cipherText: encryptedXml!, encodingAESKey: client.Credentials.EncodingAESKey!, out _);
|
webhookXml = Utilities.WxMsgCryptor.AESDecrypt(cipherText: encryptedXml!, encodingAESKey: client.Credentials.EncodingAESKey!, out _);
|
||||||
return Utilities.XmlUtility.Deserialize<TEvent>(callbackXml);
|
return Utilities.XmlHelper.Deserialize<TEvent>(webhookXml);
|
||||||
}
|
}
|
||||||
catch (WechatOpenAIException)
|
catch (WechatOpenAIException)
|
||||||
{
|
{
|
||||||
@@ -27,7 +27,7 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
throw new Exceptions.WechatOpenAIEventSerializationException("Failed to deserialize event data. Please see the inner exception for more details.", ex);
|
throw new WechatOpenAIException("Failed to deserialize event data. Please see the inner exception for more details.", ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,23 +36,23 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="TEvent"></typeparam>
|
/// <typeparam name="TEvent"></typeparam>
|
||||||
/// <param name="client"></param>
|
/// <param name="client"></param>
|
||||||
/// <param name="callbackXml"></param>
|
/// <param name="webhookXml"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public static TEvent DeserializeEventFromXml<TEvent>(this WechatOpenAIClient client, string callbackXml)
|
public static TEvent DeserializeEventFromXml<TEvent>(this WechatOpenAIClient client, string webhookXml)
|
||||||
where TEvent : WechatOpenAIEvent, WechatOpenAIEvent.Serialization.IXmlSerializable, new()
|
where TEvent : WechatOpenAIEvent, new()
|
||||||
{
|
{
|
||||||
return InnerDeserializeEventFromXml<TEvent>(client, callbackXml);
|
return InnerDeserializeEventFromXml<TEvent>(client, webhookXml);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// <para>从 XML 反序列化得到 <see cref="WechatOpenAIEvent"/> 对象。</para>
|
/// <para>从 XML 反序列化得到 <see cref="WechatOpenAIEvent"/> 对象。</para>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="client"></param>
|
/// <param name="client"></param>
|
||||||
/// <param name="callbackXml"></param>
|
/// <param name="webhookXml"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public static WechatOpenAIEvent DeserializeEventFromXml(this WechatOpenAIClient client, string callbackXml)
|
public static WechatOpenAIEvent DeserializeEventFromXml(this WechatOpenAIClient client, string webhookXml)
|
||||||
{
|
{
|
||||||
return InnerDeserializeEventFromXml<WechatOpenAIEvent>(client, callbackXml);
|
return InnerDeserializeEventFromXml<WechatOpenAIEvent>(client, webhookXml);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -60,26 +60,26 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="TEvent"></typeparam>
|
/// <typeparam name="TEvent"></typeparam>
|
||||||
/// <param name="client"></param>
|
/// <param name="client"></param>
|
||||||
/// <param name="callbackModel"></param>
|
/// <param name="webhookEvent"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public static string SerializeEventToXml<TEvent>(this WechatOpenAIClient client, TEvent callbackModel)
|
public static string SerializeEventToXml<TEvent>(this WechatOpenAIClient client, TEvent webhookEvent)
|
||||||
where TEvent : WechatOpenAIEvent, WechatOpenAIEvent.Serialization.IXmlSerializable, new()
|
where TEvent : WechatOpenAIEvent, new()
|
||||||
{
|
{
|
||||||
string xml;
|
string xml;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
xml = Utilities.XmlUtility.Serialize(callbackModel);
|
xml = Utilities.XmlHelper.Serialize(webhookEvent);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
throw new Exceptions.WechatOpenAIEventSerializationException("Failed to serialize event data. Please see the inner exception for more details.", ex);
|
throw new WechatOpenAIException("Failed to serialize event data. Please see the inner exception for more details.", ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(client.Credentials.EncodingAESKey))
|
if (string.IsNullOrEmpty(client.Credentials.EncodingAESKey))
|
||||||
throw new Exceptions.WechatOpenAIEventSerializationException("Failed to encrypt event data, because there is no encoding AES key.");
|
throw new WechatOpenAIException("Failed to encrypt event data, because the push encoding AES key is not set.");
|
||||||
if (string.IsNullOrEmpty(client.Credentials.Token))
|
if (string.IsNullOrEmpty(client.Credentials.Token))
|
||||||
throw new Exceptions.WechatOpenAIEventSerializationException("Failed to encrypt event data, because there is no token.");
|
throw new WechatOpenAIException("Failed to encrypt event data, because the push token is not set.");
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -93,7 +93,7 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
throw new Exceptions.WechatOpenAIEventSerializationException("Failed to encrypt event data. Please see the inner exception for more details.", ex);
|
throw new WechatOpenAIException("Failed to encrypt event data. Please see the inner exception for more details.", ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
return xml;
|
return xml;
|
||||||
|
|||||||
@@ -22,9 +22,9 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI
|
|||||||
if (request is null) throw new ArgumentNullException(nameof(request));
|
if (request is null) throw new ArgumentNullException(nameof(request));
|
||||||
|
|
||||||
IFlurlRequest flurlReq = client
|
IFlurlRequest flurlReq = client
|
||||||
.CreateRequest(request, HttpMethod.Post, "batchimportskill", client.Credentials.Token!);
|
.CreateFlurlRequest(request, HttpMethod.Post, "batchimportskill", client.Credentials.Token!);
|
||||||
|
|
||||||
return await client.SendRequestWithJsonAsync<Models.BatchImportSkillResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
|
return await client.SendFlurlRequestAsJsonAsync<Models.BatchImportSkillResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -41,9 +41,9 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI
|
|||||||
if (request is null) throw new ArgumentNullException(nameof(request));
|
if (request is null) throw new ArgumentNullException(nameof(request));
|
||||||
|
|
||||||
IFlurlRequest flurlReq = client
|
IFlurlRequest flurlReq = client
|
||||||
.CreateRequest(request, HttpMethod.Post, "publish", client.Credentials.Token!);
|
.CreateFlurlRequest(request, HttpMethod.Post, "publish", client.Credentials.Token!);
|
||||||
|
|
||||||
return await client.SendRequestWithJsonAsync<Models.PublishResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
|
return await client.SendFlurlRequestAsJsonAsync<Models.PublishResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -60,9 +60,9 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI
|
|||||||
if (request is null) throw new ArgumentNullException(nameof(request));
|
if (request is null) throw new ArgumentNullException(nameof(request));
|
||||||
|
|
||||||
IFlurlRequest flurlReq = client
|
IFlurlRequest flurlReq = client
|
||||||
.CreateRequest(request, HttpMethod.Post, "publish_progress", client.Credentials.Token!);
|
.CreateFlurlRequest(request, HttpMethod.Post, "publish_progress", client.Credentials.Token!);
|
||||||
|
|
||||||
return await client.SendRequestWithJsonAsync<Models.PublishProgressResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
|
return await client.SendFlurlRequestAsJsonAsync<Models.PublishProgressResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -79,9 +79,9 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI
|
|||||||
if (request is null) throw new ArgumentNullException(nameof(request));
|
if (request is null) throw new ArgumentNullException(nameof(request));
|
||||||
|
|
||||||
IFlurlRequest flurlReq = client
|
IFlurlRequest flurlReq = client
|
||||||
.CreateRequest(request, HttpMethod.Post, "setautoreply", client.Credentials.Token!);
|
.CreateFlurlRequest(request, HttpMethod.Post, "setautoreply", client.Credentials.Token!);
|
||||||
|
|
||||||
return await client.SendRequestWithJsonAsync<Models.SetAutoReplyResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
|
return await client.SendFlurlRequestAsJsonAsync<Models.SetAutoReplyResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -98,9 +98,9 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI
|
|||||||
if (request is null) throw new ArgumentNullException(nameof(request));
|
if (request is null) throw new ArgumentNullException(nameof(request));
|
||||||
|
|
||||||
IFlurlRequest flurlReq = client
|
IFlurlRequest flurlReq = client
|
||||||
.CreateRequest(request, HttpMethod.Post, "label", "batchset", client.Credentials.Token!);
|
.CreateFlurlRequest(request, HttpMethod.Post, "label", "batchset", client.Credentials.Token!);
|
||||||
|
|
||||||
return await client.SendRequestWithJsonAsync<Models.LabelBatchSetResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
|
return await client.SendFlurlRequestAsJsonAsync<Models.LabelBatchSetResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,9 +22,9 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI
|
|||||||
if (request is null) throw new ArgumentNullException(nameof(request));
|
if (request is null) throw new ArgumentNullException(nameof(request));
|
||||||
|
|
||||||
IFlurlRequest flurlReq = client
|
IFlurlRequest flurlReq = client
|
||||||
.CreateRequest(request, HttpMethod.Post, "sign", client.Credentials.Token!);
|
.CreateFlurlRequest(request, HttpMethod.Post, "sign", client.Credentials.Token!);
|
||||||
|
|
||||||
return await client.SendRequestWithJsonAsync<Models.SignResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
|
return await client.SendFlurlRequestAsJsonAsync<Models.SignResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -41,9 +41,9 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI
|
|||||||
if (request is null) throw new ArgumentNullException(nameof(request));
|
if (request is null) throw new ArgumentNullException(nameof(request));
|
||||||
|
|
||||||
IFlurlRequest flurlReq = client
|
IFlurlRequest flurlReq = client
|
||||||
.CreateRequest(request, HttpMethod.Post, "aibot", client.Credentials.Token!);
|
.CreateFlurlRequest(request, HttpMethod.Post, "aibot", client.Credentials.Token!);
|
||||||
|
|
||||||
return await client.SendRequestWithJsonAsync<Models.AIBotResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
|
return await client.SendFlurlRequestAsJsonAsync<Models.AIBotResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -60,9 +60,9 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI
|
|||||||
if (request is null) throw new ArgumentNullException(nameof(request));
|
if (request is null) throw new ArgumentNullException(nameof(request));
|
||||||
|
|
||||||
IFlurlRequest flurlReq = client
|
IFlurlRequest flurlReq = client
|
||||||
.CreateRequest(request, HttpMethod.Post, "feedback", client.Credentials.Token!);
|
.CreateFlurlRequest(request, HttpMethod.Post, "feedback", client.Credentials.Token!);
|
||||||
|
|
||||||
return await client.SendRequestWithJsonAsync<Models.FeedbackResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
|
return await client.SendFlurlRequestAsJsonAsync<Models.FeedbackResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -79,9 +79,9 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI
|
|||||||
if (request is null) throw new ArgumentNullException(nameof(request));
|
if (request is null) throw new ArgumentNullException(nameof(request));
|
||||||
|
|
||||||
IFlurlRequest flurlReq = client
|
IFlurlRequest flurlReq = client
|
||||||
.CreateRequest(request, HttpMethod.Post, "gethotquerylist", client.Credentials.Token!);
|
.CreateFlurlRequest(request, HttpMethod.Post, "gethotquerylist", client.Credentials.Token!);
|
||||||
|
|
||||||
return await client.SendRequestWithJsonAsync<Models.GetHotQueryListResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
|
return await client.SendFlurlRequestAsJsonAsync<Models.GetHotQueryListResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -98,9 +98,9 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI
|
|||||||
if (request is null) throw new ArgumentNullException(nameof(request));
|
if (request is null) throw new ArgumentNullException(nameof(request));
|
||||||
|
|
||||||
IFlurlRequest flurlReq = client
|
IFlurlRequest flurlReq = client
|
||||||
.CreateRequest(request, HttpMethod.Post, "gethotquerydetail", client.Credentials.Token!);
|
.CreateFlurlRequest(request, HttpMethod.Post, "gethotquerydetail", client.Credentials.Token!);
|
||||||
|
|
||||||
return await client.SendRequestWithJsonAsync<Models.GetHotQueryDetailResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
|
return await client.SendFlurlRequestAsJsonAsync<Models.GetHotQueryDetailResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,9 +25,9 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI
|
|||||||
if (request is null) throw new ArgumentNullException(nameof(request));
|
if (request is null) throw new ArgumentNullException(nameof(request));
|
||||||
|
|
||||||
IFlurlRequest flurlReq = client
|
IFlurlRequest flurlReq = client
|
||||||
.CreateRequest(request, HttpMethod.Post, "getbindlink", client.Credentials.Token!);
|
.CreateFlurlRequest(request, HttpMethod.Post, "getbindlink", client.Credentials.Token!);
|
||||||
|
|
||||||
return await client.SendRequestWithJsonAsync<Models.GetBindLinkResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
|
return await client.SendFlurlRequestAsJsonAsync<Models.GetBindLinkResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -44,9 +44,9 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI
|
|||||||
if (request is null) throw new ArgumentNullException(nameof(request));
|
if (request is null) throw new ArgumentNullException(nameof(request));
|
||||||
|
|
||||||
IFlurlRequest flurlReq = client
|
IFlurlRequest flurlReq = client
|
||||||
.CreateRequest(request, HttpMethod.Post, "getbindlist", client.Credentials.Token!);
|
.CreateFlurlRequest(request, HttpMethod.Post, "getbindlist", client.Credentials.Token!);
|
||||||
|
|
||||||
return await client.SendRequestWithJsonAsync<Models.GetBindListResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
|
return await client.SendFlurlRequestAsJsonAsync<Models.GetBindListResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -63,9 +63,9 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI
|
|||||||
if (request is null) throw new ArgumentNullException(nameof(request));
|
if (request is null) throw new ArgumentNullException(nameof(request));
|
||||||
|
|
||||||
IFlurlRequest flurlReq = client
|
IFlurlRequest flurlReq = client
|
||||||
.CreateRequest(request, HttpMethod.Post, "unbindmp", client.Credentials.Token!);
|
.CreateFlurlRequest(request, HttpMethod.Post, "unbindmp", client.Credentials.Token!);
|
||||||
|
|
||||||
return await client.SendRequestWithJsonAsync<Models.UnbindMpResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
|
return await client.SendFlurlRequestAsJsonAsync<Models.UnbindMpResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -82,9 +82,9 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI
|
|||||||
if (request is null) throw new ArgumentNullException(nameof(request));
|
if (request is null) throw new ArgumentNullException(nameof(request));
|
||||||
|
|
||||||
IFlurlRequest flurlReq = client
|
IFlurlRequest flurlReq = client
|
||||||
.CreateRequest(request, HttpMethod.Post, "geth5link", client.Credentials.Token!);
|
.CreateFlurlRequest(request, HttpMethod.Post, "geth5link", client.Credentials.Token!);
|
||||||
|
|
||||||
return await client.SendRequestWithJsonAsync<Models.GetH5LinkResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
|
return await client.SendFlurlRequestAsJsonAsync<Models.GetH5LinkResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -100,19 +100,19 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI
|
|||||||
if (client is null) throw new ArgumentNullException(nameof(client));
|
if (client is null) throw new ArgumentNullException(nameof(client));
|
||||||
if (request is null) throw new ArgumentNullException(nameof(request));
|
if (request is null) throw new ArgumentNullException(nameof(request));
|
||||||
|
|
||||||
if (request.FileName == null)
|
if (request.FileName is null)
|
||||||
request.FileName = Guid.NewGuid().ToString("N").ToLower() + ".jpg";
|
request.FileName = Guid.NewGuid().ToString("N").ToLower() + ".jpg";
|
||||||
|
|
||||||
if (request.FileContentType == null)
|
if (request.FileContentType is null)
|
||||||
request.FileContentType = "image/jpeg";
|
request.FileContentType = "image/jpeg";
|
||||||
|
|
||||||
IFlurlRequest flurlReq = client
|
IFlurlRequest flurlReq = client
|
||||||
.CreateRequest(request, HttpMethod.Post, "assetsupload", client.Credentials.Token!);
|
.CreateFlurlRequest(request, HttpMethod.Post, "assetsupload", client.Credentials.Token!);
|
||||||
|
|
||||||
using var fileContent = new ByteArrayContent(request.FileBytes ?? Array.Empty<byte>());
|
using var fileContent = new ByteArrayContent(request.FileBytes ?? Array.Empty<byte>());
|
||||||
using var paramContent = new StringContent(
|
using var paramContent = new StringContent(
|
||||||
Utilities.WxMsgCryptor.AESEncrypt(
|
Utilities.WxMsgCryptor.AESEncrypt(
|
||||||
plainText: Utilities.XmlUtility.ConvertFromJson(client.JsonSerializer.Serialize(request)),
|
plainText: Utilities.XmlHelper.ConvertFromJson(client.JsonSerializer.Serialize(request)),
|
||||||
encodingAESKey: client.Credentials.EncodingAESKey!,
|
encodingAESKey: client.Credentials.EncodingAESKey!,
|
||||||
appId: client.Credentials.AppId!
|
appId: client.Credentials.AppId!
|
||||||
),
|
),
|
||||||
@@ -124,7 +124,7 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI
|
|||||||
fileContent.Headers.ContentType = MediaTypeHeaderValue.Parse(request.FileContentType);
|
fileContent.Headers.ContentType = MediaTypeHeaderValue.Parse(request.FileContentType);
|
||||||
fileContent.Headers.ContentLength = request.FileBytes?.Length;
|
fileContent.Headers.ContentLength = request.FileBytes?.Length;
|
||||||
|
|
||||||
return await client.SendRequestAsync<Models.AssetsUploadResponse>(flurlReq, httpContent: httpContent, cancellationToken: cancellationToken);
|
return await client.SendFlurlRequestAsync<Models.AssetsUploadResponse>(flurlReq, httpContent: httpContent, cancellationToken: cancellationToken);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,13 +21,13 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI
|
|||||||
if (client is null) throw new ArgumentNullException(nameof(client));
|
if (client is null) throw new ArgumentNullException(nameof(client));
|
||||||
if (request is null) throw new ArgumentNullException(nameof(request));
|
if (request is null) throw new ArgumentNullException(nameof(request));
|
||||||
|
|
||||||
if (request.AppId == null)
|
if (request.AppId is null)
|
||||||
request.AppId = client.Credentials.AppId;
|
request.AppId = client.Credentials.AppId;
|
||||||
|
|
||||||
IFlurlRequest flurlReq = client
|
IFlurlRequest flurlReq = client
|
||||||
.CreateRequest(request, HttpMethod.Post, "sendmsg", client.Credentials.Token!);
|
.CreateFlurlRequest(request, HttpMethod.Post, "sendmsg", client.Credentials.Token!);
|
||||||
|
|
||||||
return await client.SendRequestWithJsonAsync<Models.SendMessageResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
|
return await client.SendFlurlRequestAsJsonAsync<Models.SendMessageResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -43,13 +43,13 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI
|
|||||||
if (client is null) throw new ArgumentNullException(nameof(client));
|
if (client is null) throw new ArgumentNullException(nameof(client));
|
||||||
if (request is null) throw new ArgumentNullException(nameof(request));
|
if (request is null) throw new ArgumentNullException(nameof(request));
|
||||||
|
|
||||||
if (request.AppId == null)
|
if (request.AppId is null)
|
||||||
request.AppId = client.Credentials.AppId;
|
request.AppId = client.Credentials.AppId;
|
||||||
|
|
||||||
IFlurlRequest flurlReq = client
|
IFlurlRequest flurlReq = client
|
||||||
.CreateRequest(request, HttpMethod.Post, "kefustate", "get", client.Credentials.Token!);
|
.CreateFlurlRequest(request, HttpMethod.Post, "kefustate", "get", client.Credentials.Token!);
|
||||||
|
|
||||||
return await client.SendRequestWithJsonAsync<Models.KefuStateGetResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
|
return await client.SendFlurlRequestAsJsonAsync<Models.KefuStateGetResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -65,13 +65,13 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI
|
|||||||
if (client is null) throw new ArgumentNullException(nameof(client));
|
if (client is null) throw new ArgumentNullException(nameof(client));
|
||||||
if (request is null) throw new ArgumentNullException(nameof(request));
|
if (request is null) throw new ArgumentNullException(nameof(request));
|
||||||
|
|
||||||
if (request.AppId == null)
|
if (request.AppId is null)
|
||||||
request.AppId = client.Credentials.AppId;
|
request.AppId = client.Credentials.AppId;
|
||||||
|
|
||||||
IFlurlRequest flurlReq = client
|
IFlurlRequest flurlReq = client
|
||||||
.CreateRequest(request, HttpMethod.Post, "kefustate", "change", client.Credentials.Token!);
|
.CreateFlurlRequest(request, HttpMethod.Post, "kefustate", "change", client.Credentials.Token!);
|
||||||
|
|
||||||
return await client.SendRequestWithJsonAsync<Models.KefuStateChangeResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
|
return await client.SendFlurlRequestAsJsonAsync<Models.KefuStateChangeResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI.Models
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
[Newtonsoft.Json.JsonProperty("confidence")]
|
[Newtonsoft.Json.JsonProperty("confidence")]
|
||||||
[System.Text.Json.Serialization.JsonPropertyName("confidence")]
|
[System.Text.Json.Serialization.JsonPropertyName("confidence")]
|
||||||
public float Confidence { get; set; } = default!;
|
public decimal Confidence { get; set; } = default!;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 获取或设置消息类型。
|
/// 获取或设置消息类型。
|
||||||
@@ -156,7 +156,7 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI.Models
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
[Newtonsoft.Json.JsonProperty("confidence")]
|
[Newtonsoft.Json.JsonProperty("confidence")]
|
||||||
[System.Text.Json.Serialization.JsonPropertyName("confidence")]
|
[System.Text.Json.Serialization.JsonPropertyName("confidence")]
|
||||||
public double Confidence { get; set; }
|
public decimal Confidence { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Slot
|
public class Slot
|
||||||
@@ -274,7 +274,7 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI.Models
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
[Newtonsoft.Json.JsonProperty("confidence")]
|
[Newtonsoft.Json.JsonProperty("confidence")]
|
||||||
[System.Text.Json.Serialization.JsonPropertyName("confidence")]
|
[System.Text.Json.Serialization.JsonPropertyName("confidence")]
|
||||||
public double Confidence { get; set; }
|
public decimal Confidence { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 获取或设置发起查询的用户 ID。
|
/// 获取或设置发起查询的用户 ID。
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI.Models
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
[Newtonsoft.Json.JsonProperty("score")]
|
[Newtonsoft.Json.JsonProperty("score")]
|
||||||
[System.Text.Json.Serialization.JsonPropertyName("score")]
|
[System.Text.Json.Serialization.JsonPropertyName("score")]
|
||||||
public double Score { get; set; }
|
public decimal Score { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI.Models
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
[Newtonsoft.Json.JsonProperty("score")]
|
[Newtonsoft.Json.JsonProperty("score")]
|
||||||
[System.Text.Json.Serialization.JsonPropertyName("score")]
|
[System.Text.Json.Serialization.JsonPropertyName("score")]
|
||||||
public double Score { get; set; }
|
public decimal Score { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -46,45 +46,45 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI.Models
|
|||||||
/// 获取或设置是否开通微信门店功能。
|
/// 获取或设置是否开通微信门店功能。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Newtonsoft.Json.JsonProperty("open_store")]
|
[Newtonsoft.Json.JsonProperty("open_store")]
|
||||||
[Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.NumericalBooleanConverter))]
|
[Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.Common.NumericalBooleanConverter))]
|
||||||
[System.Text.Json.Serialization.JsonPropertyName("open_store")]
|
[System.Text.Json.Serialization.JsonPropertyName("open_store")]
|
||||||
[System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Converters.NumericalBooleanConverter))]
|
[System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Serialization.Common.NumericalBooleanConverter))]
|
||||||
public bool IsOpenStore { get; set; }
|
public bool IsOpenStore { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 获取或设置是否开通微信扫商品功能。
|
/// 获取或设置是否开通微信扫商品功能。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Newtonsoft.Json.JsonProperty("open_scan")]
|
[Newtonsoft.Json.JsonProperty("open_scan")]
|
||||||
[Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.NumericalBooleanConverter))]
|
[Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.Common.NumericalBooleanConverter))]
|
||||||
[System.Text.Json.Serialization.JsonPropertyName("open_scan")]
|
[System.Text.Json.Serialization.JsonPropertyName("open_scan")]
|
||||||
[System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Converters.NumericalBooleanConverter))]
|
[System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Serialization.Common.NumericalBooleanConverter))]
|
||||||
public bool IsOpenScan { get; set; }
|
public bool IsOpenScan { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 获取或设置是否开通微信支付功能。
|
/// 获取或设置是否开通微信支付功能。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Newtonsoft.Json.JsonProperty("open_pay")]
|
[Newtonsoft.Json.JsonProperty("open_pay")]
|
||||||
[Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.NumericalBooleanConverter))]
|
[Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.Common.NumericalBooleanConverter))]
|
||||||
[System.Text.Json.Serialization.JsonPropertyName("open_pay")]
|
[System.Text.Json.Serialization.JsonPropertyName("open_pay")]
|
||||||
[System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Converters.NumericalBooleanConverter))]
|
[System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Serialization.Common.NumericalBooleanConverter))]
|
||||||
public bool IsOpenPay { get; set; }
|
public bool IsOpenPay { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 获取或设置是否开通微信卡券功能。
|
/// 获取或设置是否开通微信卡券功能。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Newtonsoft.Json.JsonProperty("open_card")]
|
[Newtonsoft.Json.JsonProperty("open_card")]
|
||||||
[Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.NumericalBooleanConverter))]
|
[Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.Common.NumericalBooleanConverter))]
|
||||||
[System.Text.Json.Serialization.JsonPropertyName("open_card")]
|
[System.Text.Json.Serialization.JsonPropertyName("open_card")]
|
||||||
[System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Converters.NumericalBooleanConverter))]
|
[System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Serialization.Common.NumericalBooleanConverter))]
|
||||||
public bool IsOpenCard { get; set; }
|
public bool IsOpenCard { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 获取或设置是否开通微信摇一摇功能。
|
/// 获取或设置是否开通微信摇一摇功能。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Newtonsoft.Json.JsonProperty("open_shake")]
|
[Newtonsoft.Json.JsonProperty("open_shake")]
|
||||||
[Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.NumericalBooleanConverter))]
|
[Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.Common.NumericalBooleanConverter))]
|
||||||
[System.Text.Json.Serialization.JsonPropertyName("open_shake")]
|
[System.Text.Json.Serialization.JsonPropertyName("open_shake")]
|
||||||
[System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Converters.NumericalBooleanConverter))]
|
[System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Serialization.Common.NumericalBooleanConverter))]
|
||||||
public bool IsOpenShake { get; set; }
|
public bool IsOpenShake { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFrameworks>net461; netstandard2.0; net6.0</TargetFrameworks>
|
<TargetFrameworks>net461; netstandard2.0; net6.0</TargetFrameworks>
|
||||||
<LangVersion>8.0</LangVersion>
|
<LangVersion>10.0</LangVersion>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<NullableReferenceTypes>true</NullableReferenceTypes>
|
<NullableReferenceTypes>true</NullableReferenceTypes>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||||
<PackageProjectUrl>https://github.com/fudiwei/DotNetCore.SKIT.FlurlHttpClient.Wechat</PackageProjectUrl>
|
<PackageProjectUrl>https://github.com/fudiwei/DotNetCore.SKIT.FlurlHttpClient.Wechat</PackageProjectUrl>
|
||||||
<PackageTags>Flurl.Http Wechat Weixin MicroMessage WechatAI WechatOpenAI WexinAI WeixinOpenAI 微信 微信智能对话 微信对话开放平台 微信智能对话开放平台 智能对话平台</PackageTags>
|
<PackageTags>Flurl.Http Wechat Weixin MicroMessage WechatAI WechatOpenAI WexinAI WeixinOpenAI 微信 微信智能对话 微信对话开放平台 微信智能对话开放平台 智能对话平台</PackageTags>
|
||||||
<Version>2.0.1</Version>
|
<Version>3.0.0-preview.1</Version>
|
||||||
<Description>基于 Flurl.Http 的微信对话开放平台(微信智能对话) API 客户端,支持智能对话接口、第三方客服接入、机器人配置、公众号/小程序/H5 绑定等功能。</Description>
|
<Description>基于 Flurl.Http 的微信对话开放平台(微信智能对话) API 客户端,支持智能对话接口、第三方客服接入、机器人配置、公众号/小程序/H5 绑定等功能。</Description>
|
||||||
<Authors>Fu Diwei</Authors>
|
<Authors>Fu Diwei</Authors>
|
||||||
<RepositoryType>git</RepositoryType>
|
<RepositoryType>git</RepositoryType>
|
||||||
@@ -40,7 +40,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="SKIT.FlurlHttpClient.Common" Version="2.6.0" />
|
<PackageReference Include="SKIT.FlurlHttpClient.Common" Version="3.0.0-preview.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI.Settings
|
|||||||
|
|
||||||
internal Credentials(WechatOpenAIClientOptions options)
|
internal Credentials(WechatOpenAIClientOptions options)
|
||||||
{
|
{
|
||||||
if (options == null) throw new ArgumentNullException(nameof(options));
|
if (options is null) throw new ArgumentNullException(nameof(options));
|
||||||
|
|
||||||
AppId = options.AppId;
|
AppId = options.AppId;
|
||||||
Token = options.Token;
|
Token = options.Token;
|
||||||
|
|||||||
@@ -1,84 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections;
|
|
||||||
using System.IO;
|
|
||||||
using System.Text;
|
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
using System.Xml;
|
|
||||||
using System.Xml.Serialization;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
|
|
||||||
namespace SKIT.FlurlHttpClient.Wechat.OpenAI.Utilities
|
|
||||||
{
|
|
||||||
internal static class XmlUtility
|
|
||||||
{
|
|
||||||
// REF: https://docs.microsoft.com/zh-cn/dotnet/api/system.xml.serialization.xmlserializer#dynamically-generated-assemblies
|
|
||||||
private static readonly Hashtable _xmlSerializers = new Hashtable();
|
|
||||||
private static readonly XmlRootAttribute _xmlRoot = new XmlRootAttribute("xml");
|
|
||||||
|
|
||||||
private static XmlSerializer GetTypedSerializer(Type type)
|
|
||||||
{
|
|
||||||
if (type == null) throw new ArgumentNullException(nameof(type));
|
|
||||||
|
|
||||||
string skey = type.AssemblyQualifiedName ?? type.GetHashCode().ToString();
|
|
||||||
XmlSerializer? xmlSerializer = (XmlSerializer?)_xmlSerializers[skey];
|
|
||||||
if (xmlSerializer == null)
|
|
||||||
{
|
|
||||||
xmlSerializer = new XmlSerializer(type, _xmlRoot);
|
|
||||||
_xmlSerializers[skey] = xmlSerializer;
|
|
||||||
}
|
|
||||||
|
|
||||||
return xmlSerializer;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string Serialize(Type type, object obj)
|
|
||||||
{
|
|
||||||
string xml;
|
|
||||||
|
|
||||||
var settings = new XmlWriterSettings();
|
|
||||||
settings.Encoding = Encoding.UTF8;
|
|
||||||
settings.Indent = false;
|
|
||||||
settings.OmitXmlDeclaration = true;
|
|
||||||
settings.WriteEndDocumentOnClose = false;
|
|
||||||
settings.NamespaceHandling = NamespaceHandling.OmitDuplicates;
|
|
||||||
|
|
||||||
using var stream = new MemoryStream();
|
|
||||||
using var writer = XmlWriter.Create(stream, settings);
|
|
||||||
XmlSerializer serializer = GetTypedSerializer(type);
|
|
||||||
XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
|
|
||||||
ns.Add(string.Empty, string.Empty);
|
|
||||||
serializer.Serialize(writer, obj, ns);
|
|
||||||
writer.Flush();
|
|
||||||
xml = Encoding.UTF8.GetString(stream.ToArray());
|
|
||||||
xml = Regex.Replace(xml, "\\s*<\\w+ ([a-zA-Z0-9]+):nil=\"true\"[^>]*/>", string.Empty, RegexOptions.IgnoreCase);
|
|
||||||
xml = Regex.Replace(xml, "<\\?xml[^>]*\\?>", string.Empty, RegexOptions.IgnoreCase);
|
|
||||||
|
|
||||||
return xml;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string Serialize<T>(T obj)
|
|
||||||
where T : class
|
|
||||||
{
|
|
||||||
return Serialize(typeof(T), obj);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static object Deserialize(Type type, string xml)
|
|
||||||
{
|
|
||||||
using var reader = new StringReader(xml);
|
|
||||||
XmlSerializer serializer = GetTypedSerializer(type);
|
|
||||||
return serializer.Deserialize(reader)!;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static T Deserialize<T>(string xml)
|
|
||||||
where T : class
|
|
||||||
{
|
|
||||||
return (T)Deserialize(typeof(T), xml);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string ConvertFromJson(string json)
|
|
||||||
{
|
|
||||||
XmlDocument xmlDocument = JsonConvert.DeserializeXmlNode(json, "xml")!;
|
|
||||||
string xml = xmlDocument.InnerXml;
|
|
||||||
return xml;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -16,7 +16,7 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI.Utilities
|
|||||||
/// <returns>信息摘要字节数组。</returns>
|
/// <returns>信息摘要字节数组。</returns>
|
||||||
public static byte[] Hash(byte[] bytes)
|
public static byte[] Hash(byte[] bytes)
|
||||||
{
|
{
|
||||||
if (bytes == null) throw new ArgumentNullException(nameof(bytes));
|
if (bytes is null) throw new ArgumentNullException(nameof(bytes));
|
||||||
|
|
||||||
using SHA1 sha = SHA1.Create();
|
using SHA1 sha = SHA1.Create();
|
||||||
return sha.ComputeHash(bytes);
|
return sha.ComputeHash(bytes);
|
||||||
@@ -29,7 +29,7 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI.Utilities
|
|||||||
/// <returns>信息摘要。</returns>
|
/// <returns>信息摘要。</returns>
|
||||||
public static string Hash(string message)
|
public static string Hash(string message)
|
||||||
{
|
{
|
||||||
if (message == null) throw new ArgumentNullException(nameof(message));
|
if (message is null) throw new ArgumentNullException(nameof(message));
|
||||||
|
|
||||||
byte[] msgBytes = Encoding.UTF8.GetBytes(message);
|
byte[] msgBytes = Encoding.UTF8.GetBytes(message);
|
||||||
byte[] hashBytes = Hash(msgBytes);
|
byte[] hashBytes = Hash(msgBytes);
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI.Utilities
|
|||||||
|
|
||||||
private static byte[] Decode2(byte[] decryptedBytes)
|
private static byte[] Decode2(byte[] decryptedBytes)
|
||||||
{
|
{
|
||||||
if (decryptedBytes == null) throw new ArgumentNullException(nameof(decryptedBytes));
|
if (decryptedBytes is null) throw new ArgumentNullException(nameof(decryptedBytes));
|
||||||
|
|
||||||
int pad = (int)decryptedBytes[decryptedBytes.Length - 1];
|
int pad = (int)decryptedBytes[decryptedBytes.Length - 1];
|
||||||
if (pad < 1 || pad > 32)
|
if (pad < 1 || pad > 32)
|
||||||
@@ -75,9 +75,9 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI.Utilities
|
|||||||
|
|
||||||
private static byte[] AESDecrypt(byte[] keyBytes, byte[] ivBytes, byte[] ciperBytes)
|
private static byte[] AESDecrypt(byte[] keyBytes, byte[] ivBytes, byte[] ciperBytes)
|
||||||
{
|
{
|
||||||
if (keyBytes == null) throw new ArgumentNullException(nameof(keyBytes));
|
if (keyBytes is null) throw new ArgumentNullException(nameof(keyBytes));
|
||||||
if (ivBytes == null) throw new ArgumentNullException(nameof(ivBytes));
|
if (ivBytes is null) throw new ArgumentNullException(nameof(ivBytes));
|
||||||
if (ciperBytes == null) throw new ArgumentNullException(nameof(ciperBytes));
|
if (ciperBytes is null) throw new ArgumentNullException(nameof(ciperBytes));
|
||||||
|
|
||||||
using var aes = Aes.Create();
|
using var aes = Aes.Create();
|
||||||
aes.KeySize = 256;
|
aes.KeySize = 256;
|
||||||
@@ -103,9 +103,9 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI.Utilities
|
|||||||
|
|
||||||
private static string AESEncrypt(byte[] keyBytes, byte[] ivBytes, byte[] plainBytes)
|
private static string AESEncrypt(byte[] keyBytes, byte[] ivBytes, byte[] plainBytes)
|
||||||
{
|
{
|
||||||
if (keyBytes == null) throw new ArgumentNullException(nameof(keyBytes));
|
if (keyBytes is null) throw new ArgumentNullException(nameof(keyBytes));
|
||||||
if (ivBytes == null) throw new ArgumentNullException(nameof(ivBytes));
|
if (ivBytes is null) throw new ArgumentNullException(nameof(ivBytes));
|
||||||
if (plainBytes == null) throw new ArgumentNullException(nameof(plainBytes));
|
if (plainBytes is null) throw new ArgumentNullException(nameof(plainBytes));
|
||||||
|
|
||||||
using var aes = Aes.Create();
|
using var aes = Aes.Create();
|
||||||
aes.KeySize = AES_KEY_SIZE;
|
aes.KeySize = AES_KEY_SIZE;
|
||||||
@@ -140,8 +140,8 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI.Utilities
|
|||||||
/// <returns>解密后的文本内容。</returns>
|
/// <returns>解密后的文本内容。</returns>
|
||||||
public static string AESDecrypt(string cipherText, string encodingAESKey, out string appId)
|
public static string AESDecrypt(string cipherText, string encodingAESKey, out string appId)
|
||||||
{
|
{
|
||||||
if (cipherText == null) throw new ArgumentNullException(nameof(cipherText));
|
if (cipherText is null) throw new ArgumentNullException(nameof(cipherText));
|
||||||
if (encodingAESKey == null) throw new ArgumentNullException(nameof(encodingAESKey));
|
if (encodingAESKey is null) throw new ArgumentNullException(nameof(encodingAESKey));
|
||||||
|
|
||||||
byte[] cipherBytes = Convert.FromBase64String(cipherText);
|
byte[] cipherBytes = Convert.FromBase64String(cipherText);
|
||||||
byte[] keyBytes = Convert.FromBase64String(encodingAESKey + "=");
|
byte[] keyBytes = Convert.FromBase64String(encodingAESKey + "=");
|
||||||
@@ -170,9 +170,9 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI.Utilities
|
|||||||
/// <returns>加密后的文本内容。</returns>
|
/// <returns>加密后的文本内容。</returns>
|
||||||
public static string AESEncrypt(string plainText, string encodingAESKey, string appId)
|
public static string AESEncrypt(string plainText, string encodingAESKey, string appId)
|
||||||
{
|
{
|
||||||
if (plainText == null) throw new ArgumentNullException(nameof(plainText));
|
if (plainText is null) throw new ArgumentNullException(nameof(plainText));
|
||||||
if (encodingAESKey == null) throw new ArgumentNullException(nameof(encodingAESKey));
|
if (encodingAESKey is null) throw new ArgumentNullException(nameof(encodingAESKey));
|
||||||
if (appId == null) throw new ArgumentNullException(nameof(appId));
|
if (appId is null) throw new ArgumentNullException(nameof(appId));
|
||||||
|
|
||||||
byte[] keyBytes = Convert.FromBase64String(encodingAESKey + "=");
|
byte[] keyBytes = Convert.FromBase64String(encodingAESKey + "=");
|
||||||
byte[] ivBytes = new byte[16];
|
byte[] ivBytes = new byte[16];
|
||||||
@@ -204,11 +204,11 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI.Utilities
|
|||||||
/// <returns>验证结果。</returns>
|
/// <returns>验证结果。</returns>
|
||||||
public static bool VerifySignature(string sToken, string sTimestamp, string sNonce, string sMsgEncrypt, string sMsgSign)
|
public static bool VerifySignature(string sToken, string sTimestamp, string sNonce, string sMsgEncrypt, string sMsgSign)
|
||||||
{
|
{
|
||||||
if (sToken == null) throw new ArgumentNullException(nameof(sToken));
|
if (sToken is null) throw new ArgumentNullException(nameof(sToken));
|
||||||
if (sTimestamp == null) throw new ArgumentNullException(nameof(sTimestamp));
|
if (sTimestamp is null) throw new ArgumentNullException(nameof(sTimestamp));
|
||||||
if (sNonce == null) throw new ArgumentNullException(nameof(sNonce));
|
if (sNonce is null) throw new ArgumentNullException(nameof(sNonce));
|
||||||
if (sMsgEncrypt == null) throw new ArgumentNullException(nameof(sMsgEncrypt));
|
if (sMsgEncrypt is null) throw new ArgumentNullException(nameof(sMsgEncrypt));
|
||||||
if (sMsgSign == null) throw new ArgumentNullException(nameof(sMsgSign));
|
if (sMsgSign is null) throw new ArgumentNullException(nameof(sMsgSign));
|
||||||
|
|
||||||
string expectedSign = GenerateSignature(sToken, sTimestamp, sNonce, sMsgEncrypt);
|
string expectedSign = GenerateSignature(sToken, sTimestamp, sNonce, sMsgEncrypt);
|
||||||
return string.Equals(expectedSign, sMsgSign, StringComparison.OrdinalIgnoreCase);
|
return string.Equals(expectedSign, sMsgSign, StringComparison.OrdinalIgnoreCase);
|
||||||
@@ -224,10 +224,10 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI.Utilities
|
|||||||
/// <returns>签名。</returns>
|
/// <returns>签名。</returns>
|
||||||
public static string GenerateSignature(string sToken, string sTimestamp, string sNonce, string sMsgEncrypt)
|
public static string GenerateSignature(string sToken, string sTimestamp, string sNonce, string sMsgEncrypt)
|
||||||
{
|
{
|
||||||
if (sToken == null) throw new ArgumentNullException(nameof(sToken));
|
if (sToken is null) throw new ArgumentNullException(nameof(sToken));
|
||||||
if (sTimestamp == null) throw new ArgumentNullException(nameof(sTimestamp));
|
if (sTimestamp is null) throw new ArgumentNullException(nameof(sTimestamp));
|
||||||
if (sNonce == null) throw new ArgumentNullException(nameof(sNonce));
|
if (sNonce is null) throw new ArgumentNullException(nameof(sNonce));
|
||||||
if (sMsgEncrypt == null) throw new ArgumentNullException(nameof(sMsgEncrypt));
|
if (sMsgEncrypt is null) throw new ArgumentNullException(nameof(sMsgEncrypt));
|
||||||
|
|
||||||
List<string> tmp = new List<string>(capacity: 4) { sToken, sTimestamp, sNonce, sMsgEncrypt };
|
List<string> tmp = new List<string>(capacity: 4) { sToken, sTimestamp, sNonce, sMsgEncrypt };
|
||||||
tmp.Sort(StringComparer.Ordinal);
|
tmp.Sort(StringComparer.Ordinal);
|
||||||
@@ -257,7 +257,7 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI.Utilities
|
|||||||
/// <returns>指示是否是有效的 XML 内容。</returns>
|
/// <returns>指示是否是有效的 XML 内容。</returns>
|
||||||
public static bool TryParseXml(string xml, out string? encryptedMsg, out string? toUserName)
|
public static bool TryParseXml(string xml, out string? encryptedMsg, out string? toUserName)
|
||||||
{
|
{
|
||||||
if (xml == null) throw new ArgumentNullException(nameof(xml));
|
if (xml is null) throw new ArgumentNullException(nameof(xml));
|
||||||
|
|
||||||
encryptedMsg = null;
|
encryptedMsg = null;
|
||||||
toUserName = null;
|
toUserName = null;
|
||||||
@@ -269,7 +269,7 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI.Utilities
|
|||||||
xmlDoc.LoadXml(xml);
|
xmlDoc.LoadXml(xml);
|
||||||
|
|
||||||
XmlNode? xmlRoot = xmlDoc.FirstChild;
|
XmlNode? xmlRoot = xmlDoc.FirstChild;
|
||||||
if (xmlRoot == null)
|
if (xmlRoot is null)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
encryptedMsg = xmlRoot["Encrypt"]?.InnerText?.ToString();
|
encryptedMsg = xmlRoot["Encrypt"]?.InnerText?.ToString();
|
||||||
@@ -291,8 +291,8 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI.Utilities
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public static string WrapXml(string sToken, string sMsgEncrypt)
|
public static string WrapXml(string sToken, string sMsgEncrypt)
|
||||||
{
|
{
|
||||||
if (sToken == null) throw new ArgumentNullException(nameof(sToken));
|
if (sToken is null) throw new ArgumentNullException(nameof(sToken));
|
||||||
if (sMsgEncrypt == null) throw new ArgumentNullException(nameof(sMsgEncrypt));
|
if (sMsgEncrypt is null) throw new ArgumentNullException(nameof(sMsgEncrypt));
|
||||||
|
|
||||||
string sTimestamp = DateTimeOffset.Now.ToLocalTime().ToUnixTimeSeconds().ToString();
|
string sTimestamp = DateTimeOffset.Now.ToLocalTime().ToUnixTimeSeconds().ToString();
|
||||||
string sNonce = DateTimeOffset.Now.Ticks.ToString("x");
|
string sNonce = DateTimeOffset.Now.Ticks.ToString("x");
|
||||||
@@ -310,10 +310,10 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI.Utilities
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public static string WrapXml(string sTimestamp, string sNonce, string sMsgEncrypt, string sMsgSign)
|
public static string WrapXml(string sTimestamp, string sNonce, string sMsgEncrypt, string sMsgSign)
|
||||||
{
|
{
|
||||||
if (sTimestamp == null) throw new ArgumentNullException(nameof(sTimestamp));
|
if (sTimestamp is null) throw new ArgumentNullException(nameof(sTimestamp));
|
||||||
if (sNonce == null) throw new ArgumentNullException(nameof(sNonce));
|
if (sNonce is null) throw new ArgumentNullException(nameof(sNonce));
|
||||||
if (sMsgEncrypt == null) throw new ArgumentNullException(nameof(sMsgEncrypt));
|
if (sMsgEncrypt is null) throw new ArgumentNullException(nameof(sMsgEncrypt));
|
||||||
if (sMsgSign == null) throw new ArgumentNullException(nameof(sMsgSign));
|
if (sMsgSign is null) throw new ArgumentNullException(nameof(sMsgSign));
|
||||||
|
|
||||||
StringBuilder builder = new StringBuilder();
|
StringBuilder builder = new StringBuilder();
|
||||||
builder.AppendFormat("<xml>");
|
builder.AppendFormat("<xml>");
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
using System;
|
||||||
|
using System.Xml;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace SKIT.FlurlHttpClient.Wechat.OpenAI.Utilities
|
||||||
|
{
|
||||||
|
using SKIT.FlurlHttpClient.Internal;
|
||||||
|
|
||||||
|
internal static class XmlHelper
|
||||||
|
{
|
||||||
|
public static string Serialize(object obj, Type type)
|
||||||
|
{
|
||||||
|
return _XmlSimpleSerializer.Serialize(obj, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string Serialize<T>(T obj)
|
||||||
|
where T : class
|
||||||
|
{
|
||||||
|
return Serialize(obj, typeof(T));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static object Deserialize(string xml, Type type)
|
||||||
|
{
|
||||||
|
return _XmlSimpleSerializer.Deserialize(xml, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static T Deserialize<T>(string xml)
|
||||||
|
where T : class
|
||||||
|
{
|
||||||
|
return (T)Deserialize(xml, typeof(T));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string ConvertFromJson(string json)
|
||||||
|
{
|
||||||
|
XmlDocument xmlDocument = JsonConvert.DeserializeXmlNode(json, "xml")!;
|
||||||
|
string xml = xmlDocument.InnerXml;
|
||||||
|
return xml;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -21,42 +21,37 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="options">配置项。</param>
|
/// <param name="options">配置项。</param>
|
||||||
public WechatOpenAIClient(WechatOpenAIClientOptions options)
|
public WechatOpenAIClient(WechatOpenAIClientOptions options)
|
||||||
: base()
|
: this(options, null)
|
||||||
{
|
{
|
||||||
if (options == null) throw new ArgumentNullException(nameof(options));
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="options"></param>
|
||||||
|
/// <param name="httpClient"></param>
|
||||||
|
/// <param name="disposeClient"></param>
|
||||||
|
internal protected WechatOpenAIClient(WechatOpenAIClientOptions options, HttpClient? httpClient, bool disposeClient = true)
|
||||||
|
: base(httpClient, disposeClient)
|
||||||
|
{
|
||||||
|
if (options is null) throw new ArgumentNullException(nameof(options));
|
||||||
|
|
||||||
Credentials = new Settings.Credentials(options);
|
Credentials = new Settings.Credentials(options);
|
||||||
|
|
||||||
FlurlClient.BaseUrl = options.Endpoint ?? WechatOpenAIEndpoints.DEFAULT;
|
FlurlClient.BaseUrl = options.Endpoint ?? WechatOpenAIEndpoints.DEFAULT;
|
||||||
FlurlClient.WithTimeout(TimeSpan.FromMilliseconds(options.Timeout));
|
FlurlClient.WithTimeout(options.Timeout <= 0 ? Timeout.InfiniteTimeSpan : TimeSpan.FromMilliseconds(options.Timeout));
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 用指定的微信智能对话 AppId、Token、EncodingAESKey 初始化 <see cref="WechatOpenAIThirdPartyClient"/> 类的新实例。
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="appId">微信智能对话 AppId。</param>
|
|
||||||
/// <param name="token">微信智能对话 Token。</param>
|
|
||||||
/// <param name="encodingAESKey">微信智能对话 EncodingAESKey。</param>
|
|
||||||
public WechatOpenAIClient(string appId, string token, string encodingAESKey)
|
|
||||||
: this(new WechatOpenAIClientOptions() { AppId = appId, Token = token, EncodingAESKey = encodingAESKey })
|
|
||||||
{
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 使用当前客户端生成一个新的 <see cref="IFlurlRequest"/> 对象。
|
/// 使用当前客户端生成一个新的 <see cref="IFlurlRequest"/> 对象。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="request"></param>
|
/// <param name="request"></param>
|
||||||
/// <param name="method"></param>
|
/// <param name="httpMethod"></param>
|
||||||
/// <param name="urlSegments"></param>
|
/// <param name="urlSegments"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public IFlurlRequest CreateRequest(WechatOpenAIRequest request, HttpMethod method, params object[] urlSegments)
|
public IFlurlRequest CreateFlurlRequest(WechatOpenAIRequest request, HttpMethod httpMethod, params object[] urlSegments)
|
||||||
{
|
{
|
||||||
IFlurlRequest flurlRequest = FlurlClient.Request(urlSegments).WithVerb(method);
|
IFlurlRequest flurlRequest = base.CreateFlurlRequest(request, httpMethod, urlSegments);
|
||||||
|
|
||||||
if (request.Timeout != null)
|
|
||||||
{
|
|
||||||
flurlRequest.WithTimeout(TimeSpan.FromMilliseconds(request.Timeout.Value));
|
|
||||||
}
|
|
||||||
|
|
||||||
return flurlRequest;
|
return flurlRequest;
|
||||||
}
|
}
|
||||||
@@ -69,20 +64,13 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI
|
|||||||
/// <param name="httpContent"></param>
|
/// <param name="httpContent"></param>
|
||||||
/// <param name="cancellationToken"></param>
|
/// <param name="cancellationToken"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public async Task<T> SendRequestAsync<T>(IFlurlRequest flurlRequest, HttpContent? httpContent = null, CancellationToken cancellationToken = default)
|
public async Task<T> SendFlurlRequestAsync<T>(IFlurlRequest flurlRequest, HttpContent? httpContent = null, CancellationToken cancellationToken = default)
|
||||||
where T : WechatOpenAIResponse, new()
|
where T : WechatOpenAIResponse, new()
|
||||||
{
|
{
|
||||||
if (flurlRequest == null) throw new ArgumentNullException(nameof(flurlRequest));
|
if (flurlRequest is null) throw new ArgumentNullException(nameof(flurlRequest));
|
||||||
|
|
||||||
try
|
using IFlurlResponse flurlResponse = await base.SendFlurlRequestAsync(flurlRequest, httpContent, cancellationToken);
|
||||||
{
|
return await WrapFlurlResponseAsJsonAsync<T>(flurlResponse, cancellationToken);
|
||||||
using IFlurlResponse flurlResponse = await base.SendRequestAsync(flurlRequest, httpContent, cancellationToken);
|
|
||||||
return await WrapResponseWithJsonAsync<T>(flurlResponse, cancellationToken);
|
|
||||||
}
|
|
||||||
catch (FlurlHttpException ex)
|
|
||||||
{
|
|
||||||
throw new WechatOpenAIException(ex.Message, ex);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -93,37 +81,26 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI
|
|||||||
/// <param name="data"></param>
|
/// <param name="data"></param>
|
||||||
/// <param name="cancellationToken"></param>
|
/// <param name="cancellationToken"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public async Task<T> SendRequestWithJsonAsync<T>(IFlurlRequest flurlRequest, object? data = null, CancellationToken cancellationToken = default)
|
public async Task<T> SendFlurlRequestAsJsonAsync<T>(IFlurlRequest flurlRequest, object? data = null, CancellationToken cancellationToken = default)
|
||||||
where T : WechatOpenAIResponse, new()
|
where T : WechatOpenAIResponse, new()
|
||||||
{
|
{
|
||||||
if (flurlRequest == null) throw new ArgumentNullException(nameof(flurlRequest));
|
if (flurlRequest is null) throw new ArgumentNullException(nameof(flurlRequest));
|
||||||
|
|
||||||
try
|
if (data is WechatOpenAIRequest.Serialization.IEncryptedXmlable)
|
||||||
{
|
{
|
||||||
if (data is WechatOpenAIRequest.Serialization.IEncryptedXmlable)
|
string plainXml = Utilities.XmlHelper.ConvertFromJson(JsonSerializer.Serialize(data));
|
||||||
{
|
string encryptedXml = Utilities.WxMsgCryptor.AESEncrypt(plainText: plainXml, encodingAESKey: Credentials.EncodingAESKey!, appId: Credentials.AppId!);
|
||||||
string plainXml = Utilities.XmlUtility.ConvertFromJson(JsonSerializer.Serialize(data));
|
data = new { encrypt = encryptedXml };
|
||||||
string encryptedXml = Utilities.WxMsgCryptor.AESEncrypt(plainText: plainXml, encodingAESKey: Credentials.EncodingAESKey!, appId: Credentials.AppId!);
|
}
|
||||||
data = new { encrypt = encryptedXml };
|
|
||||||
}
|
|
||||||
|
|
||||||
bool isSimpleRequest = data == null ||
|
bool isSimpleRequest = data is null ||
|
||||||
flurlRequest.Verb == HttpMethod.Get ||
|
flurlRequest.Verb == HttpMethod.Get ||
|
||||||
flurlRequest.Verb == HttpMethod.Head ||
|
flurlRequest.Verb == HttpMethod.Head ||
|
||||||
flurlRequest.Verb == HttpMethod.Options;
|
flurlRequest.Verb == HttpMethod.Options;
|
||||||
using IFlurlResponse flurlResponse = isSimpleRequest ?
|
using IFlurlResponse flurlResponse = isSimpleRequest ?
|
||||||
await base.SendRequestAsync(flurlRequest, null, cancellationToken) :
|
await base.SendFlurlRequestAsync(flurlRequest, null, cancellationToken) :
|
||||||
await base.SendRequestWithJsonAsync(flurlRequest, data, cancellationToken);
|
await base.SendFlurlRequestAsJsonAsync(flurlRequest, data, cancellationToken);
|
||||||
return await WrapResponseWithJsonAsync<T>(flurlResponse, cancellationToken);
|
return await WrapFlurlResponseAsJsonAsync<T>(flurlResponse, cancellationToken);
|
||||||
}
|
|
||||||
catch (FlurlHttpTimeoutException ex)
|
|
||||||
{
|
|
||||||
throw new Exceptions.WechatOpenAIRequestTimeoutException(ex.Message, ex);
|
|
||||||
}
|
|
||||||
catch (FlurlHttpException ex)
|
|
||||||
{
|
|
||||||
throw new WechatOpenAIException(ex.Message, ex);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,38 +1,27 @@
|
|||||||
using System;
|
|
||||||
using System.Xml.Serialization;
|
|
||||||
|
|
||||||
namespace SKIT.FlurlHttpClient.Wechat.OpenAI
|
namespace SKIT.FlurlHttpClient.Wechat.OpenAI
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 表示微信智能对话 API 回调通知事件的基类。
|
/// 表示微信智能对话 API 回调通知事件的基类。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Serializable]
|
[System.Xml.Serialization.XmlRoot("xml")]
|
||||||
public class WechatOpenAIEvent
|
public class WechatOpenAIEvent : ICommonWebhookEvent
|
||||||
{
|
{
|
||||||
public static class Serialization
|
|
||||||
{
|
|
||||||
[XmlRoot("xml")]
|
|
||||||
public interface IXmlSerializable
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 获取或设置 AppId。
|
/// 获取或设置 AppId。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[XmlElement("appid", IsNullable = true)]
|
[System.Xml.Serialization.XmlElement("appid", IsNullable = true)]
|
||||||
public string? AppId { get; set; }
|
public string? AppId { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 获取或设置事件类型。
|
/// 获取或设置事件类型。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[XmlElement("event", IsNullable = true)]
|
[System.Xml.Serialization.XmlElement("event", IsNullable = true)]
|
||||||
public string? Event { get; set; }
|
public string? Event { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 获取或设置消息创建时间戳。
|
/// 获取或设置消息创建时间戳。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[XmlElement("createtime", IsNullable = true)]
|
[System.Xml.Serialization.XmlElement("createtime", IsNullable = true)]
|
||||||
public long? CreateTimestamp { get; set; }
|
public long? CreateTimestamp { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace SKIT.FlurlHttpClient.Wechat.OpenAI
|
namespace SKIT.FlurlHttpClient.Wechat.OpenAI
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 当调用微信智能对话 API 出错时引发的异常。
|
/// 当调用微信智能对话 API 出错时引发的异常。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class WechatOpenAIException : CommonExceptionBase
|
public class WechatOpenAIException : CommonException
|
||||||
{
|
{
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public WechatOpenAIException()
|
public WechatOpenAIException()
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 表示微信智能对话 API 请求的基类。
|
/// 表示微信智能对话 API 请求的基类。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract class WechatOpenAIRequest : ICommonRequest
|
public abstract class WechatOpenAIRequest : CommonRequestBase, ICommonRequest
|
||||||
{
|
{
|
||||||
public static class Serialization
|
public static class Serialization
|
||||||
{
|
{
|
||||||
@@ -11,14 +11,5 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取或设置请求超时时间(单位:毫秒)。如果不指定将使用构造 <see cref="WechatOpenAIClient"/> 时的 <see cref="WechatOpenAIClientOptions.Timeout"/> 参数,这在需要指定特定耗时请求(比如上传或下载文件)的超时时间时很有用。
|
|
||||||
/// </summary>
|
|
||||||
[Newtonsoft.Json.JsonIgnore]
|
|
||||||
[System.Text.Json.Serialization.JsonIgnore]
|
|
||||||
[System.Xml.Serialization.XmlIgnore]
|
|
||||||
[System.Xml.Serialization.SoapIgnore]
|
|
||||||
public virtual int? Timeout { get; set; }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,60 +1,10 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace SKIT.FlurlHttpClient.Wechat.OpenAI
|
namespace SKIT.FlurlHttpClient.Wechat.OpenAI
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 表示微信智能对话 API 响应的基类。
|
/// 表示微信智能对话 API 响应的基类。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract class WechatOpenAIResponse : ICommonResponse
|
public abstract class WechatOpenAIResponse : CommonResponseBase, ICommonResponse
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
int ICommonResponse.RawStatus { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
IDictionary<string, string> ICommonResponse.RawHeaders { get; set; } = default!;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
byte[] ICommonResponse.RawBytes { get; set; } = default!;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取原始的 HTTP 响应状态码。
|
|
||||||
/// </summary>
|
|
||||||
[Newtonsoft.Json.JsonIgnore]
|
|
||||||
[System.Text.Json.Serialization.JsonIgnore]
|
|
||||||
public int RawStatus
|
|
||||||
{
|
|
||||||
get { return ((ICommonResponse)this).RawStatus; }
|
|
||||||
internal set { ((ICommonResponse)this).RawStatus = value; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取原始的 HTTP 响应表头集合。
|
|
||||||
/// </summary>
|
|
||||||
[Newtonsoft.Json.JsonIgnore]
|
|
||||||
[System.Text.Json.Serialization.JsonIgnore]
|
|
||||||
public IDictionary<string, string> RawHeaders
|
|
||||||
{
|
|
||||||
get { return ((ICommonResponse)this).RawHeaders; }
|
|
||||||
internal set { ((ICommonResponse)this).RawHeaders = value; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取原始的 HTTP 响应正文。
|
|
||||||
/// </summary>
|
|
||||||
[Newtonsoft.Json.JsonIgnore]
|
|
||||||
[System.Text.Json.Serialization.JsonIgnore]
|
|
||||||
public byte[] RawBytes
|
|
||||||
{
|
|
||||||
get { return ((ICommonResponse)this).RawBytes; }
|
|
||||||
internal set { ((ICommonResponse)this).RawBytes = value; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 获取微信智能对话 API 返回的错误码。
|
/// 获取微信智能对话 API 返回的错误码。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -84,12 +34,15 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI
|
|||||||
public virtual string? ReturnError { get; set; }
|
public virtual string? ReturnError { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 获取一个值,该值指示调用微信 API 是否成功(即 HTTP 状态码为 200、且 errcode/ret 值都为 0)。
|
/// 获取一个值,该值指示调用微信 API 是否成功。
|
||||||
|
/// <para>
|
||||||
|
/// (即 HTTP 状态码为 200,且 <see cref="ErrorCode"/>、<see cref="ReturnCode"/> 值都为 0)
|
||||||
|
/// </para>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public virtual bool IsSuccessful()
|
public override bool IsSuccessful()
|
||||||
{
|
{
|
||||||
return RawStatus == 200 && ErrorCode.GetValueOrDefault() == 0 && ReturnCode.GetValueOrDefault() == 0 && string.IsNullOrEmpty(ReturnError);
|
return GetRawStatus() == 200 && ErrorCode.GetValueOrDefault() == 0 && ReturnCode.GetValueOrDefault() == 0 && string.IsNullOrEmpty(ReturnError);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Reflection;
|
||||||
|
using SKIT.FlurlHttpClient.Tools.CodeAnalyzer;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace SKIT.FlurlHttpClient.Wechat.OpenAI.UnitTests
|
||||||
|
{
|
||||||
|
public class CodeAnalyzeTests
|
||||||
|
{
|
||||||
|
[Fact(DisplayName = "代码质量分析")]
|
||||||
|
public void CodeAnalyze()
|
||||||
|
{
|
||||||
|
Assert.Null(Record.Exception(() =>
|
||||||
|
{
|
||||||
|
var options = new TypeDeclarationAnalyzerOptions()
|
||||||
|
{
|
||||||
|
SdkAssembly = Assembly.GetAssembly(typeof(WechatOpenAIClient))!,
|
||||||
|
SdkRequestModelDeclarationNamespace = "SKIT.FlurlHttpClient.Wechat.OpenAI.Models",
|
||||||
|
SdkResponseModelDeclarationNamespace = "SKIT.FlurlHttpClient.Wechat.OpenAI.Models",
|
||||||
|
SdkExecutingExtensionDeclarationNamespace = "SKIT.FlurlHttpClient.Wechat.OpenAI",
|
||||||
|
SdkWebhookEventDeclarationNamespace = "SKIT.FlurlHttpClient.Wechat.OpenAI.Events",
|
||||||
|
ThrowOnNotFoundRequestModelTypes = true,
|
||||||
|
ThrowOnNotFoundResponseModelTypes = true,
|
||||||
|
ThrowOnNotFoundExecutingExtensionTypes = true,
|
||||||
|
ThrowOnNotFoundWebhookEventTypes = true
|
||||||
|
};
|
||||||
|
new TypeDeclarationAnalyzer(options).AssertNoIssues();
|
||||||
|
}));
|
||||||
|
|
||||||
|
Assert.Null(Record.Exception(() =>
|
||||||
|
{
|
||||||
|
string workdir = Environment.CurrentDirectory;
|
||||||
|
string projdir = Path.Combine(workdir, "../../../../../");
|
||||||
|
|
||||||
|
var options = new SourceFileAnalyzerOptions()
|
||||||
|
{
|
||||||
|
SdkAssembly = Assembly.GetAssembly(typeof(WechatOpenAIClient))!,
|
||||||
|
SdkRequestModelDeclarationNamespace = "SKIT.FlurlHttpClient.Wechat.OpenAI.Models",
|
||||||
|
SdkResponseModelDeclarationNamespace = "SKIT.FlurlHttpClient.Wechat.OpenAI.Models",
|
||||||
|
SdkWebhookEventDeclarationNamespace = "SKIT.FlurlHttpClient.Wechat.OpenAI.Events",
|
||||||
|
ProjectSourceRootDirectory = Path.Combine(projdir, "./src/SKIT.FlurlHttpClient.Wechat.OpenAI/"),
|
||||||
|
ProjectTestRootDirectory = Path.Combine(projdir, "./test/SKIT.FlurlHttpClient.Wechat.OpenAI.UnitTests/"),
|
||||||
|
ThrowOnNotFoundRequestModelClassCodeFiles = true,
|
||||||
|
ThrowOnNotFoundResponseModelClassCodeFiles = true,
|
||||||
|
ThrowOnNotFoundExecutingExtensionClassCodeFiles = true,
|
||||||
|
ThrowOnNotFoundWebhookEventClassCodeFiles = true,
|
||||||
|
ThrowOnNotFoundRequestModelSerializationSampleFiles = true,
|
||||||
|
ThrowOnNotFoundResponseModelSerializationSampleFiles = true,
|
||||||
|
ThrowOnNotFoundWebhookEventSerializationSampleFiles = true
|
||||||
|
};
|
||||||
|
new SourceFileAnalyzer(options).AssertNoIssues();
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,23 +10,23 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Remove=".gitignore" />
|
<None Remove=".gitignore" />
|
||||||
<Content Include="appsettings.json">
|
<Content Include="appsettings.json">
|
||||||
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
<CopyToPublishDirectory>Never</CopyToPublishDirectory>
|
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||||
</Content>
|
</Content>
|
||||||
<Content Include="appsettings.*.json" Condition="'$(Configuration)' == 'Debug'">
|
<Content Include="appsettings.*.json" Condition="'$(Configuration)' == 'Debug'">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
<CopyToPublishDirectory>Always</CopyToPublishDirectory>
|
||||||
</Content>
|
</Content>
|
||||||
<Content Include="ModelSamples/**/*.json" />
|
<Content Include="ModelSamples/**/*.json" />
|
||||||
<Content Include="EventSamples/**/*.xml" />
|
<Content Include="EventSamples/**/*.xml" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
|
||||||
<PackageReference Include="SKIT.FlurlHttpClient.Tools.CodeAnalyzer" Version="0.1.0-alpha.1" />
|
<PackageReference Include="SKIT.FlurlHttpClient.Tools.CodeAnalyzer" Version="0.3.0-preview.1" />
|
||||||
<PackageReference Include="xunit" Version="2.4.2" />
|
<PackageReference Include="xunit" Version="2.6.6" />
|
||||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
|
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.6">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
|
|||||||
@@ -1,28 +0,0 @@
|
|||||||
using SKIT.FlurlHttpClient.Tools.CodeAnalyzer;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace SKIT.FlurlHttpClient.Wechat.OpenAI.UnitTests
|
|
||||||
{
|
|
||||||
public class TestCase_CodeReview
|
|
||||||
{
|
|
||||||
[Fact(DisplayName = "测试用例:代码质量分析")]
|
|
||||||
public void TestCodeAnalyzer()
|
|
||||||
{
|
|
||||||
Assert.Null(Record.Exception(() =>
|
|
||||||
{
|
|
||||||
CodeAnalyzerOptions options = new CodeAnalyzerOptions()
|
|
||||||
{
|
|
||||||
AssemblyName = "SKIT.FlurlHttpClient.Wechat.OpenAI",
|
|
||||||
WorkDirectoryForSourceCode = TestConfigs.WorkDirectoryForSdk,
|
|
||||||
WorkDirectoryForTestSample = TestConfigs.WorkDirectoryForTest,
|
|
||||||
AllowNotFoundEventTypes = true,
|
|
||||||
AllowNotFoundEventSamples = true
|
|
||||||
};
|
|
||||||
CodeAnalyzer analyzer = new CodeAnalyzer(options);
|
|
||||||
analyzer.Start();
|
|
||||||
analyzer.Assert();
|
|
||||||
analyzer.Flush();
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -17,14 +17,11 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI.UnitTests
|
|||||||
using var stream = File.OpenRead("appsettings.local.json");
|
using var stream = File.OpenRead("appsettings.local.json");
|
||||||
using var jdoc = JsonDocument.Parse(stream);
|
using var jdoc = JsonDocument.Parse(stream);
|
||||||
|
|
||||||
var config = jdoc.RootElement.GetProperty("TestConfig");
|
var config = jdoc.RootElement.GetProperty("TestConfigs");
|
||||||
WechatAppId = config.GetProperty("AppId").GetString()!;
|
WechatAppId = config.GetProperty("AppId").GetString()!;
|
||||||
WechatToken = config.GetProperty("Token").GetString()!;
|
WechatToken = config.GetProperty("Token").GetString()!;
|
||||||
WechatEncodingAESKey = config.GetProperty("EncodingAESKey").GetString()!;
|
WechatEncodingAESKey = config.GetProperty("EncodingAESKey").GetString()!;
|
||||||
WechatAccessToken = config.GetProperty("AccessToken").GetString()!;
|
WechatAccessToken = config.GetProperty("AccessToken").GetString()!;
|
||||||
|
|
||||||
WorkDirectoryForSdk = jdoc.RootElement.GetProperty("WorkDirectoryForSdk").GetString()!;
|
|
||||||
WorkDirectoryForTest = jdoc.RootElement.GetProperty("WorkDirectoryForTest").GetString()!;
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -36,8 +33,5 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI.UnitTests
|
|||||||
public static readonly string WechatToken;
|
public static readonly string WechatToken;
|
||||||
public static readonly string WechatEncodingAESKey;
|
public static readonly string WechatEncodingAESKey;
|
||||||
public static readonly string WechatAccessToken;
|
public static readonly string WechatAccessToken;
|
||||||
|
|
||||||
public static readonly string WorkDirectoryForSdk;
|
|
||||||
public static readonly string WorkDirectoryForTest;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
{
|
{
|
||||||
"TestConfig": {
|
"TestConfigs": {
|
||||||
"AppId": "请在此填写用于测试的微信智能对话 AppId",
|
"AppId": "请在此填写用于测试的微信智能对话 AppId",
|
||||||
"Token": "请在此填写用于测试的微信智能对话 Token",
|
"Token": "请在此填写用于测试的微信智能对话 Token",
|
||||||
"EncodingAESKey": "请在此填写用于测试的微信智能对话 EncodingAESKey",
|
"EncodingAESKey": "请在此填写用于测试的微信智能对话 EncodingAESKey",
|
||||||
"AccessToken": "请在此填写用于测试的微信智能对话 AccessToken"
|
"AccessToken": "请在此填写用于测试的微信智能对话 AccessToken"
|
||||||
},
|
}
|
||||||
"WorkDirectoryForSdk": "请输入当前 SDK 项目所在的目录完整路径,如 C:\\Project\\src\\SKIT.FlurlHttpClient.Wechat.OpenAI\\",
|
|
||||||
"WorkDirectoryForTest": "请输入当前测试项目所在的目录完整路径,如 C:\\Project\\test\\SKIT.FlurlHttpClient.Wechat.OpenAI.UnitTests\\"
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user