remove DS1

This commit is contained in:
DismissedLight
2022-09-03 13:26:25 +08:00
parent c4eab0be70
commit e8a9542b32
30 changed files with 211 additions and 754 deletions

View File

@@ -41,7 +41,7 @@ internal struct ValueStopwatch
/// </summary>
/// <returns>经过的时间</returns>
/// <exception cref="InvalidOperationException">当前的停表未合理的初始化</exception>
public TimeSpan GetElapsedTime()
public long GetElapsedTimestamp()
{
// Start timestamp can't be zero in an initialized ValueStopwatch.
// It would have to be literally the first thing executed when the machine boots to be 0.
@@ -52,6 +52,16 @@ internal struct ValueStopwatch
long timestampDelta = end - startTimestamp;
long ticks = (long)(TimestampToTicks * timestampDelta);
return new TimeSpan(ticks);
return ticks;
}
/// <summary>
/// 获取经过的时间
/// </summary>
/// <returns>经过的时间</returns>
/// <exception cref="InvalidOperationException">当前的停表未合理的初始化</exception>
public TimeSpan GetElapsedTime()
{
return new TimeSpan(GetElapsedTimestamp());
}
}

View File

@@ -11,8 +11,7 @@ namespace Snap.Hutao.Core.Logging;
internal sealed partial class DatebaseLogger : ILogger
{
private readonly string name;
private readonly LogDbContext logDbContext;
private readonly object logDbContextLock;
private readonly LogEntryQueue logEntryQueue;
/// <summary>
/// Initializes a new instance of the <see cref="DatebaseLogger"/> class.
@@ -20,11 +19,10 @@ internal sealed partial class DatebaseLogger : ILogger
/// <param name="name">The name of the logger.</param>
/// <param name="logDbContext">应用程序数据库上下文</param>
/// <param name="logDbContextLock">上下文锁</param>
public DatebaseLogger(string name, LogDbContext logDbContext, object logDbContextLock)
public DatebaseLogger(string name, LogEntryQueue logEntryQueue)
{
this.name = name;
this.logDbContext = logDbContext;
this.logDbContextLock = logDbContextLock;
this.logEntryQueue = logEntryQueue;
}
/// <inheritdoc />
@@ -47,7 +45,7 @@ internal sealed partial class DatebaseLogger : ILogger
return;
}
string message = Must.NotNull(formatter)(state, exception);
string message = formatter(state, exception);
if (string.IsNullOrEmpty(message))
{
@@ -63,12 +61,7 @@ internal sealed partial class DatebaseLogger : ILogger
Exception = exception?.ToString(),
};
// DbContext is not a thread safe class, so we have to lock the wirte procedure
lock (logDbContextLock)
{
logDbContext.Logs.Add(entry);
logDbContext.SaveChanges();
}
logEntryQueue.Enqueue(entry);
}
/// <summary>

View File

@@ -1,12 +1,6 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.EntityFrameworkCore;
using Snap.Hutao.Context.Database;
using Snap.Hutao.Context.FileSystem;
using System.Diagnostics;
using System.Linq;
namespace Snap.Hutao.Core.Logging;
/// <summary>
@@ -15,49 +9,17 @@ namespace Snap.Hutao.Core.Logging;
[ProviderAlias("Database")]
public sealed class DatebaseLoggerProvider : ILoggerProvider
{
private static readonly object LogDbContextLock = new();
// the provider is created per logger, we don't want to create too much
private static volatile LogDbContext? logDbContext;
private static LogDbContext LogDbContext
{
get
{
if (logDbContext == null)
{
lock (LogDbContextLock)
{
// prevent re-entry call
if (logDbContext == null)
{
HutaoContext myDocument = new(new());
logDbContext = LogDbContext.Create($"Data Source={myDocument.Locate("Log.db")}");
if (logDbContext.Database.GetPendingMigrations().Any())
{
Debug.WriteLine("[Debug] Performing LogDbContext Migrations");
logDbContext.Database.Migrate();
}
logDbContext.Logs.RemoveRange(logDbContext.Logs);
logDbContext.SaveChanges();
}
}
}
return logDbContext;
}
}
private readonly LogEntryQueue logEntryQueue = new();
/// <inheritdoc/>
public ILogger CreateLogger(string name)
{
return new DatebaseLogger(name, LogDbContext, LogDbContextLock);
return new DatebaseLogger(name, logEntryQueue);
}
/// <inheritdoc/>
public void Dispose()
{
LogDbContext.Dispose();
logEntryQueue.Dispose();
}
}
}

View File

@@ -0,0 +1,119 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.EntityFrameworkCore;
using Snap.Hutao.Context.Database;
using Snap.Hutao.Context.FileSystem;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Linq;
namespace Snap.Hutao.Core.Logging;
/// <summary>
/// 日志队列
/// </summary>
public sealed class LogEntryQueue : IDisposable
{
private static readonly object LogDbContextCreationLock = new();
private readonly ConcurrentQueue<LogEntry> entryQueue = new();
private readonly CancellationTokenSource disposeCancellationTokenSource = new();
private readonly TaskCompletionSource writeDbTaskCompletionSource = new();
// the provider is created per logger, we don't want to create too much
private volatile LogDbContext? logDbContext;
/// <summary>
/// 构造一个新的日志队列
/// </summary>
public LogEntryQueue()
{
Execute();
}
private LogDbContext LogDbContext
{
get
{
if (logDbContext == null)
{
lock (LogDbContextCreationLock)
{
// prevent re-entry call
if (logDbContext == null)
{
HutaoContext myDocument = new(new());
logDbContext = LogDbContext.Create($"Data Source={myDocument.Locate("Log.db")}");
if (logDbContext.Database.GetPendingMigrations().Any())
{
Debug.WriteLine("[Debug] Performing LogDbContext Migrations");
logDbContext.Database.Migrate();
}
logDbContext.Logs.RemoveRange(logDbContext.Logs);
logDbContext.SaveChanges();
}
}
}
return logDbContext;
}
}
/// <summary>
/// 将日志消息存入队列
/// </summary>
/// <param name="logEntry">日志</param>
public void Enqueue(LogEntry logEntry)
{
entryQueue.Enqueue(logEntry);
}
/// <inheritdoc/>
[SuppressMessage("", "VSTHRD002")]
public void Dispose()
{
disposeCancellationTokenSource.Cancel();
writeDbTaskCompletionSource.Task.GetAwaiter().GetResult();
LogDbContext.Dispose();
}
[SuppressMessage("", "VSTHRD100")]
private async void Execute()
{
await Task.Run(async () => await ExecuteCoreAsync(disposeCancellationTokenSource.Token));
}
private async Task ExecuteCoreAsync(CancellationToken token)
{
bool hasAdded = false;
while (true)
{
if (entryQueue.TryDequeue(out LogEntry? logEntry))
{
LogDbContext.Logs.Add(logEntry);
hasAdded = true;
}
else
{
if (hasAdded)
{
LogDbContext.SaveChanges();
hasAdded = false;
}
if (token.IsCancellationRequested)
{
writeDbTaskCompletionSource.TrySetResult();
break;
}
await Task
.Delay(1000, CancellationToken.None)
.ConfigureAwait(false);
}
}
}
}

View File

@@ -1,6 +1,7 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using System.Diagnostics;
using Windows.Win32.Foundation;
using Windows.Win32.UI.Shell;
using static Windows.Win32.PInvoke;

View File

@@ -42,9 +42,5 @@ public sealed partial class MainWindow : Window
messenger.Send(new MainWindowClosedMessage());
windowManager?.Dispose();
// save userdata datebase
int changes = appDbContext.SaveChanges();
Verify.Operation(changes == 0, "存在未经处理的数据库记录更改");
}
}

View File

@@ -39,11 +39,13 @@ public static class Program
XamlCheckProcessRequirements();
ComWrappersSupport.InitializeComWrappers();
InitializeDependencyInjection();
// In a Desktop app this runs a message pump internally,
// and does not return until the application shuts down.
Application.Start(InitializeApp);
// by adding the using statement, we can dispose the injected services when we closing
using (InitializeDependencyInjection())
{
// In a Desktop app this runs a message pump internally,
// and does not return until the application shuts down.
Application.Start(InitializeApp);
}
}
private static void InitializeApp(ApplicationInitializationCallbackParams param)
@@ -55,9 +57,13 @@ public static class Program
_ = Ioc.Default.GetRequiredService<App>();
}
private static void InitializeDependencyInjection()
/// <summary>
/// 初始化依赖注入
/// </summary>
/// <returns>The ServiceProvider, so that we can dispose it.</returns>
private static ServiceProvider InitializeDependencyInjection()
{
IServiceProvider services = new ServiceCollection()
ServiceProvider services = new ServiceCollection()
// Microsoft extension
.AddLogging(builder => builder
@@ -78,5 +84,6 @@ public static class Program
.BuildServiceProvider();
Ioc.Default.ConfigureServices(services);
return services;
}
}

View File

@@ -2,7 +2,7 @@
"profiles": {
"Snap.Hutao (Package)": {
"commandName": "MsixPackage",
"nativeDebugging": true
"nativeDebugging": false
},
"Snap.Hutao (Unpackaged)": {
"commandName": "Project"

View File

@@ -1,18 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Service.Sign;
/// <summary>
/// 签到服务
/// </summary>
[Obsolete("米游社官方限制了签到功能")]
public interface ISignService
{
/// <summary>
/// 异步全部签到
/// </summary>
/// <param name="token">取消令牌</param>
/// <returns>任务</returns>
Task<SignResult> SignForAllAsync(CancellationToken token);
}

View File

@@ -1,37 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Service.Sign;
/// <summary>
/// 签到操作结果
/// </summary>
public struct SignResult
{
/// <summary>
/// 构造一个新的签到操作结果
/// </summary>
/// <param name="totalCount">总次数</param>
/// <param name="retryCount">重试次数</param>
public SignResult(int totalCount, int retryCount, TimeSpan time)
{
TotalCount = totalCount;
RetryCount = retryCount;
Time = time;
}
/// <summary>
/// 总次数
/// </summary>
public int TotalCount { get; set; }
/// <summary>
/// 重试次数
/// </summary>
public int RetryCount { get; set; }
/// <summary>
/// 用时
/// </summary>
public TimeSpan Time { get; set; }
}

View File

@@ -1,117 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Core.Diagnostics;
using Snap.Hutao.Service.Abstraction;
using Snap.Hutao.Web.Hoyolab.Takumi.Binding;
using Snap.Hutao.Web.Hoyolab.Takumi.Event.BbsSignReward;
using Snap.Hutao.Web.Response;
namespace Snap.Hutao.Service.Sign;
/// <summary>
/// 签到服务
/// </summary>
[Obsolete("米游社官方限制了签到功能")]
[Injection(InjectAs.Transient, typeof(ISignService))]
internal class SignService : ISignService
{
private readonly IUserService userService;
private readonly IInfoBarService infoBarService;
private readonly SignClient signClient;
/// <summary>
/// 构造一个新的签到服务
/// </summary>
/// <param name="userService">用户服务</param>
/// <param name="infoBarService">信息条服务</param>
/// <param name="signClient">签到客户端</param>
public SignService(IUserService userService, IInfoBarService infoBarService, SignClient signClient)
{
this.userService = userService;
this.infoBarService = infoBarService;
this.signClient = signClient;
}
/// <inheritdoc/>
public async Task<SignResult> SignForAllAsync(CancellationToken token)
{
IEnumerable<Model.Binding.User>? users = await userService
.GetUserCollectionAsync()
.ConfigureAwait(false);
Queue<UserRole> userRolesQueue = GetSignQueue(users);
int totalCount = 0;
int retryCount = 0;
ValueStopwatch stopwatch = ValueStopwatch.StartNew();
while (userRolesQueue.TryDequeue(out UserRole current))
{
totalCount++;
Response<SignInResult>? resp = await signClient
.SignAsync(current, token)
.ConfigureAwait(false);
Must.NotNull(resp!);
if (resp.Data != null)
{
Must.Argument(resp.ReturnCode == 0, "返回代码应为 0");
// Geetest applied
if (resp.Data.Success != 0)
{
userRolesQueue.Enqueue(current);
retryCount++;
}
else
{
infoBarService.Information($"[{current.Role}] 签到成功");
}
}
else
{
switch ((KnownReturnCode)resp.ReturnCode)
{
case KnownReturnCode.OK:
infoBarService.Information($"[{current.Role}] 签到成功");
break;
case KnownReturnCode.NotLoggedIn:
case KnownReturnCode.AlreadySignedIn:
infoBarService.Information($"[{current.Role}] {resp.Message}");
break;
case KnownReturnCode.InvalidRequest:
infoBarService.Information("米游社SALT过期请更新胡桃");
break;
default:
throw Must.NeverHappen();
}
}
if (userRolesQueue.Count > 0)
{
int seconds = Random.Shared.Next(5, 15);
await Task
.Delay(TimeSpan.FromSeconds(seconds), token)
.ConfigureAwait(false);
}
}
return new(totalCount, retryCount, stopwatch.GetElapsedTime());
}
private static Queue<UserRole> GetSignQueue(IEnumerable<Model.Binding.User> users)
{
Queue<UserRole> queue = new();
foreach (Model.Binding.User user in users)
{
foreach (UserGameRole role in user.UserGameRoles)
{
queue.Enqueue(new(user, role));
}
}
return queue;
}
}

View File

@@ -193,4 +193,7 @@
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Folder Include="Web\Hoyolab\Takumi\Event\" />
</ItemGroup>
</Project>

View File

@@ -176,7 +176,7 @@ internal class UserViewModel : ObservableObject
DataPackage content = new();
content.SetText(Must.NotNull(user.Cookie!));
Clipboard.SetContent(content);
Clipboard.Flush();
infoBarService.Success($"{user.UserInfo!.Nickname} 的 Cookie 复制成功");
}
catch (Exception e)

View File

@@ -38,7 +38,6 @@ internal class UserClient
public async Task<UserInfo?> GetUserFullInfoAsync(Model.Binding.User user, CancellationToken token = default)
{
Response<UserFullInfoWrapper>? resp = await httpClient
.UsingDynamicSecret()
.SetUser(user)
.GetFromJsonAsync<Response<UserFullInfoWrapper>>(ApiEndpoints.UserFullInfo, jsonSerializerOptions, token)
.ConfigureAwait(false);
@@ -56,7 +55,6 @@ internal class UserClient
public async Task<UserInfo?> GetUserFullInfoAsync(Model.Binding.User user, string uid, CancellationToken token = default)
{
Response<UserFullInfoWrapper>? resp = await httpClient
.UsingDynamicSecret()
.SetUser(user)
.GetFromJsonAsync<Response<UserFullInfoWrapper>>(ApiEndpoints.UserFullInfoQuery(uid), jsonSerializerOptions, token)
.ConfigureAwait(false);

View File

@@ -1,44 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Core.Convert;
using System.Text;
namespace Snap.Hutao.Web.Hoyolab.DynamicSecret;
/// <summary>
/// 为MiHoYo接口请求器 <see cref="Requester"/> 提供动态密钥
/// </summary>
internal abstract class DynamicSecretProvider : Md5Convert
{
private const string RandomRange = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
/// <summary>
/// 创建动态密钥
/// </summary>
/// <returns>密钥</returns>
public static string Create()
{
// unix timestamp
long t = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
string r = GetRandomString();
string check = ToHexString($"salt={Core.CoreEnvironment.DynamicSecret1Salt}&t={t}&r={r}").ToLowerInvariant();
return $"{t},{r},{check}";
}
private static string GetRandomString()
{
StringBuilder sb = new(6);
for (int i = 0; i < 6; i++)
{
int pos = Random.Shared.Next(0, RandomRange.Length);
sb.Append(RandomRange[pos]);
}
return sb.ToString();
}
}

View File

@@ -9,7 +9,7 @@ namespace Snap.Hutao.Web.Hoyolab.DynamicSecret;
/// <summary>
/// 为MiHoYo接口请求器 <see cref="Requester"/> 提供2代动态密钥
/// </summary>
internal abstract class DynamicSecretProvider2 : Md5Convert
internal abstract class DynamicSecretProvider : Md5Convert
{
/// <summary>
/// 创建动态密钥
@@ -24,7 +24,7 @@ internal abstract class DynamicSecretProvider2 : Md5Convert
long t = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
// random
string r = GetRandomString();
int r = GetRandom();
// body
string b = postBody is null ? string.Empty : JsonSerializer.Serialize(postBody, options);
@@ -38,7 +38,7 @@ internal abstract class DynamicSecretProvider2 : Md5Convert
return $"{t},{r},{check}";
}
private static string GetRandomString()
private static int GetRandom()
{
// 原汁原味
// v16 = time(0LL);
@@ -49,11 +49,6 @@ internal abstract class DynamicSecretProvider2 : Md5Convert
// else
// v18 = v17 + 542367;
int rand = Random.Shared.Next(100000, 200000);
if (rand == 100000)
{
rand = 642367;
}
return rand.ToString();
return rand == 100000 ? 642367 : rand;
}
}

View File

@@ -10,7 +10,7 @@ namespace Snap.Hutao.Web.Hoyolab.DynamicSecret.Http;
/// 使用动态密钥2的Http客户端默认实现
/// </summary>
/// <typeparam name="TValue">请求提交的数据的的格式</typeparam>
internal class DynamicSecret2HttpClient : IDynamicSecret2HttpClient
internal class DynamicSecretHttpClient : IDynamicSecretHttpClient
{
private readonly HttpClient httpClient;
private readonly JsonSerializerOptions options;
@@ -23,13 +23,13 @@ internal class DynamicSecret2HttpClient : IDynamicSecret2HttpClient
/// <param name="options">Json序列化选项</param>
/// <param name="url">url</param>
/// <param name="data">请求的数据</param>
public DynamicSecret2HttpClient(HttpClient httpClient, JsonSerializerOptions options, string url)
public DynamicSecretHttpClient(HttpClient httpClient, JsonSerializerOptions options, string url)
{
this.httpClient = httpClient;
this.options = options;
this.url = url;
httpClient.DefaultRequestHeaders.Set("DS", DynamicSecretProvider2.Create(options, url, null));
httpClient.DefaultRequestHeaders.Set("DS", DynamicSecretProvider.Create(options, url, null));
}
/// <inheritdoc/>
@@ -44,7 +44,7 @@ internal class DynamicSecret2HttpClient : IDynamicSecret2HttpClient
/// </summary>
/// <typeparam name="TValue">请求提交的数据的的格式</typeparam>
[SuppressMessage("", "SA1402")]
internal class DynamicSecret2HttpClient<TValue> : IDynamicSecret2HttpClient<TValue>
internal class DynamicSecretHttpClient<TValue> : IDynamicSecretHttpClient<TValue>
where TValue : class
{
private readonly HttpClient httpClient;
@@ -59,14 +59,14 @@ internal class DynamicSecret2HttpClient<TValue> : IDynamicSecret2HttpClient<TVal
/// <param name="options">Json序列化选项</param>
/// <param name="url">url</param>
/// <param name="data">请求的数据</param>
public DynamicSecret2HttpClient(HttpClient httpClient, JsonSerializerOptions options, string url, TValue? data)
public DynamicSecretHttpClient(HttpClient httpClient, JsonSerializerOptions options, string url, TValue? data)
{
this.httpClient = httpClient;
this.options = options;
this.url = url;
this.data = data;
httpClient.DefaultRequestHeaders.Set("DS", DynamicSecretProvider2.Create(options, url, data));
httpClient.DefaultRequestHeaders.Set("DS", DynamicSecretProvider.Create(options, url, data));
}
/// <inheritdoc/>

View File

@@ -8,7 +8,7 @@ namespace Snap.Hutao.Web.Hoyolab.DynamicSecret.Http;
/// <summary>
/// 使用动态密钥2的Http客户端抽象
/// </summary>
internal interface IDynamicSecret2HttpClient
internal interface IDynamicSecretHttpClient
{
/// <summary>
/// Sends a GET request to the specified Uri and returns the value that results from deserializing the response body as JSON in an asynchronous operation.
@@ -23,7 +23,7 @@ internal interface IDynamicSecret2HttpClient
/// 使用动态密钥2的Http客户端抽象
/// </summary>
/// <typeparam name="TValue">请求数据的类型</typeparam>
internal interface IDynamicSecret2HttpClient<TValue>
internal interface IDynamicSecretHttpClient<TValue>
where TValue : class
{
/// <summary>

View File

@@ -11,17 +11,6 @@ namespace Snap.Hutao.Web.Hoyolab.DynamicSecret;
/// </summary>
internal static class HttpClientDynamicSecretExtensions
{
/// <summary>
/// 使用动态密钥执行 GET 操作
/// </summary>
/// <param name="httpClient">请求器</param>
/// <returns>响应</returns>
public static HttpClient UsingDynamicSecret(this HttpClient httpClient)
{
httpClient.DefaultRequestHeaders.Set("DS", DynamicSecretProvider.Create());
return httpClient;
}
/// <summary>
/// 使用二代动态密钥执行 GET 操作
/// </summary>
@@ -29,9 +18,9 @@ internal static class HttpClientDynamicSecretExtensions
/// <param name="options">选项</param>
/// <param name="url">地址</param>
/// <returns>响应</returns>
public static IDynamicSecret2HttpClient UsingDynamicSecret2(this HttpClient httpClient, JsonSerializerOptions options, string url)
public static IDynamicSecretHttpClient UsingDynamicSecret(this HttpClient httpClient, JsonSerializerOptions options, string url)
{
return new DynamicSecret2HttpClient(httpClient, options, url);
return new DynamicSecretHttpClient(httpClient, options, url);
}
/// <summary>
@@ -43,9 +32,9 @@ internal static class HttpClientDynamicSecretExtensions
/// <param name="url">地址</param>
/// <param name="data">post数据</param>
/// <returns>响应</returns>
public static IDynamicSecret2HttpClient<TValue> UsingDynamicSecret2<TValue>(this HttpClient httpClient, JsonSerializerOptions options, string url, TValue data)
public static IDynamicSecretHttpClient<TValue> UsingDynamicSecret<TValue>(this HttpClient httpClient, JsonSerializerOptions options, string url, TValue data)
where TValue : class
{
return new DynamicSecret2HttpClient<TValue>(httpClient, options, url, data);
return new DynamicSecretHttpClient<TValue>(httpClient, options, url, data);
}
}

View File

@@ -1,28 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Web.Hoyolab.Takumi.Event.BbsSignReward;
/// <summary>
/// 奖励物品
/// </summary>
public class Award
{
/// <summary>
/// 图标
/// </summary>
[JsonPropertyName("icon")]
public string Icon { get; set; } = default!;
/// <summary>
/// 名称
/// </summary>
[JsonPropertyName("name")]
public string Name { get; set; } = default!;
/// <summary>
/// 个数
/// </summary>
[JsonPropertyName("cnt")]
public string Count { get; set; } = default!;
}

View File

@@ -1,28 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Web.Hoyolab.Takumi.Event.BbsSignReward;
/// <summary>
/// 奖励
/// </summary>
public class Reward
{
/// <summary>
/// 月份
/// </summary>
[JsonPropertyName("month")]
public string? Month { get; set; }
/// <summary>
/// 奖励列表
/// </summary>
[JsonPropertyName("awards")]
public List<Award>? Awards { get; set; }
/// <summary>
/// 支持补签
/// </summary>
[JsonPropertyName("resign")]
public bool Resign { get; set; }
}

View File

@@ -1,151 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient;
using Snap.Hutao.Model.Binding;
using Snap.Hutao.Web.Hoyolab.DynamicSecret;
using Snap.Hutao.Web.Hoyolab.Takumi.Binding;
using Snap.Hutao.Web.Response;
using System.Net.Http;
using System.Net.Http.Json;
namespace Snap.Hutao.Web.Hoyolab.Takumi.Event.BbsSignReward;
/// <summary>
/// 签到客户端
/// </summary>
[HttpClient(HttpClientConfigration.XRpc)]
internal class SignClient
{
private readonly HttpClient httpClient;
private readonly JsonSerializerOptions options;
/// <summary>
/// 构造一个新的签到客户端
/// </summary>
/// <param name="httpClient">http客户端</param>
/// <param name="options">选项</param>
public SignClient(HttpClient httpClient, JsonSerializerOptions options)
{
this.httpClient = httpClient;
this.options = options;
}
/// <summary>
/// 异步获取签到信息
/// </summary>
/// <param name="user">用户</param>
/// <param name="role">角色</param>
/// <param name="token">取消令牌</param>
/// <returns>签到信息</returns>
public async Task<SignInRewardInfo?> GetInfoAsync(User user, UserGameRole role, CancellationToken token = default)
{
Response<SignInRewardInfo>? resp = await httpClient
.SetUser(user)
.UsingDynamicSecret()
.GetFromJsonAsync<Response<SignInRewardInfo>>(ApiEndpoints.SignInRewardInfo((PlayerUid)role), options, token)
.ConfigureAwait(false);
return resp?.Data;
}
/// <summary>
/// 异步获取签到信息
/// </summary>
/// <param name="user">用户</param>
/// <param name="role">角色</param>
/// <param name="token">取消令牌</param>
/// <returns>签到信息</returns>
public async Task<SignInRewardReSignInfo?> GetResignInfoAsync(User user, UserGameRole role, CancellationToken token = default)
{
Response<SignInRewardReSignInfo>? resp = await httpClient
.SetUser(user)
.UsingDynamicSecret()
.GetFromJsonAsync<Response<SignInRewardReSignInfo>>(ApiEndpoints.SignInRewardResignInfo((PlayerUid)role), options, token)
.ConfigureAwait(false);
return resp?.Data;
}
/// <summary>
/// 获取签到奖励
/// </summary>
/// <param name="user">用户</param>
/// <param name="token">取消令牌</param>
/// <returns>奖励信息</returns>
public async Task<Reward?> GetRewardAsync(User user, CancellationToken token = default)
{
Response<Reward>? resp = await httpClient
.SetUser(user)
.GetFromJsonAsync<Response<Reward>>(ApiEndpoints.SignInRewardHome, options, token)
.ConfigureAwait(false);
return resp?.Data;
}
/// <summary>
/// 补签
/// </summary>
/// <param name="userRole">用户角色</param>
/// <param name="token">取消令牌</param>
/// <returns>签到结果</returns>
public Task<Response<SignInResult>?> ReSignAsync(UserRole userRole, CancellationToken token = default)
{
return ReSignAsync(userRole.User, userRole.Role, token);
}
/// <summary>
/// 补签
/// </summary>
/// <param name="user">用户</param>
/// <param name="role">角色</param>
/// <param name="token">取消令牌</param>
/// <returns>签到消息</returns>
public async Task<Response<SignInResult>?> ReSignAsync(User user, UserGameRole role, CancellationToken token = default)
{
SignInData data = new((PlayerUid)role);
HttpResponseMessage response = await httpClient
.SetUser(user)
.UsingDynamicSecret()
.PostAsJsonAsync(ApiEndpoints.SignInRewardReSign, data, options, token)
.ConfigureAwait(false);
Response<SignInResult>? resp = await response.Content
.ReadFromJsonAsync<Response<SignInResult>>(options, token)
.ConfigureAwait(false);
return resp;
}
/// <summary>
/// 签到
/// </summary>
/// <param name="userRole">用户角色</param>
/// <param name="token">取消令牌</param>
/// <returns>签到结果</returns>
public Task<Response<SignInResult>?> SignAsync(UserRole userRole, CancellationToken token = default)
{
return SignAsync(userRole.User, userRole.Role, token);
}
/// <summary>
/// 签到
/// </summary>
/// <param name="user">用户</param>
/// <param name="role">角色</param>
/// <param name="token">取消令牌</param>
/// <returns>签到结果</returns>
public async Task<Response<SignInResult>?> SignAsync(User user, UserGameRole role, CancellationToken token = default)
{
HttpResponseMessage response = await httpClient
.SetUser(user)
.UsingDynamicSecret()
.PostAsJsonAsync(ApiEndpoints.SignInRewardSign, new SignInData((PlayerUid)role), options, token)
.ConfigureAwait(false);
Response<SignInResult>? resp = await response.Content
.ReadFromJsonAsync<Response<SignInResult>>(options, token)
.ConfigureAwait(false);
return resp;
}
}

View File

@@ -1,38 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Web.Hoyolab.Takumi.Event.BbsSignReward;
/// <summary>
/// 签到提交数据
/// </summary>
public class SignInData
{
/// <summary>
/// 构造一个新的签到提交数据
/// </summary>
/// <param name="uid">uid</param>
public SignInData(PlayerUid uid)
{
Region = uid.Region;
Uid = uid.Value;
}
/// <summary>
/// 活动Id
/// </summary>
[JsonPropertyName("act_id")]
public string ActivityId { get; } = ApiEndpoints.SignInRewardActivityId;
/// <summary>
/// 地区代码
/// </summary>
[JsonPropertyName("region")]
public string Region { get; }
/// <summary>
/// Uid
/// </summary>
[JsonPropertyName("uid")]
public string Uid { get; }
}

View File

@@ -1,41 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Web.Hoyolab.Takumi.Event.BbsSignReward;
/// <summary>
/// 签到结果
/// https://docs.geetest.com/sensebot/apirefer/api/server
/// </summary>
public class SignInResult
{
/// <summary>
///
/// </summary>
[JsonPropertyName("code")]
public string Code { get; set; } = default!;
/// <summary>
/// 风控码 375
/// </summary>
[JsonPropertyName("risk_code")]
public int RiskCode { get; set; }
/// <summary>
/// geetest appid
/// </summary>
[JsonPropertyName("gt")]
public string Gt { get; set; } = default!;
/// <summary>
/// geetest challenge id
/// </summary>
[JsonPropertyName("challenge")]
public string Challenge { get; set; } = default!;
/// <summary>
/// geetest 服务状态
/// </summary>
[JsonPropertyName("success")]
public int Success { get; set; }
}

View File

@@ -1,52 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Web.Hoyolab.Takumi.Event.BbsSignReward;
/// <summary>
/// 签到信息
/// </summary>
public class SignInRewardInfo
{
/// <summary>
/// 累积签到天数
/// </summary>
[JsonPropertyName("total_sign_day")]
public int TotalSignDay { get; set; }
/// <summary>
/// yyyy-MM-dd
/// </summary>
[JsonPropertyName("today")]
public string? Today { get; set; }
/// <summary>
/// 今日是否已签到
/// </summary>
[JsonPropertyName("is_sign")]
public bool IsSign { get; set; }
/// <summary>
///
/// </summary>
[JsonPropertyName("is_sub")]
public bool IsSub { get; set; }
/// <summary>
/// 是否首次绑定
/// </summary>
[JsonPropertyName("first_bind")]
public bool FirstBind { get; set; }
/// <summary>
/// 是否为当月第一次
/// </summary>
[JsonPropertyName("month_first")]
public bool MonthFirst { get; set; }
/// <summary>
/// 漏签天数
/// </summary>
[JsonPropertyName("sign_cnt_missed")]
public bool SignCountMissed { get; set; }
}

View File

@@ -1,58 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Web.Hoyolab.Takumi.Event.BbsSignReward;
/// <summary>
/// 补签说明
/// </summary>
public class SignInRewardReSignInfo
{
/// <summary>
/// 当日补签次数
/// </summary>
[JsonPropertyName("resign_cnt_daily")]
public bool ResignCountDaily { get; set; }
/// <summary>
/// 当月补签次数
/// </summary>
[JsonPropertyName("resign_cnt_monthly")]
public bool ResignCountMonthly { get; set; }
/// <summary>
/// 当日补签次数限制
/// </summary>
[JsonPropertyName("resign_limit_daily")]
public bool ResignLimitDaily { get; set; }
/// <summary>
/// 当月补签次数限制
/// </summary>
[JsonPropertyName("resign_limit_monthly")]
public bool ResignLimitMonthly { get; set; }
/// <summary>
/// 漏签次数
/// </summary>
[JsonPropertyName("sign_cnt_missed")]
public bool SignCountMissed { get; set; }
/// <summary>
/// 米游币个数
/// </summary>
[JsonPropertyName("coin_cnt")]
public bool CoinCount { get; set; }
/// <summary>
/// 补签需要的米游币个数
/// </summary>
[JsonPropertyName("coin_cost")]
public bool CoinCost { get; set; }
/// <summary>
/// 规则
/// </summary>
[JsonPropertyName("rule")]
public string Rule { get; set; } = default!;
}

View File

@@ -1,34 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Model.Binding;
using Snap.Hutao.Web.Hoyolab.Takumi.Binding;
namespace Snap.Hutao.Web.Hoyolab.Takumi.Event.BbsSignReward;
/// <summary>
/// 将用户与角色捆绑在一起
/// </summary>
public struct UserRole
{
/// <summary>
/// 构造一个新的用户角色
/// </summary>
/// <param name="user">用户</param>
/// <param name="role">角色</param>
public UserRole(User user, UserGameRole role)
{
User = user;
Role = role;
}
/// <summary>
/// 用户
/// </summary>
public User User { get; set; }
/// <summary>
/// 角色
/// </summary>
public UserGameRole Role { get; set; }
}

View File

@@ -56,7 +56,7 @@ internal class GameRecordClient
{
Response<PlayerInfo>? resp = await httpClient
.SetUser(user)
.UsingDynamicSecret2(jsonSerializerOptions, ApiEndpoints.GameRecordIndex(uid.Value, uid.Region))
.UsingDynamicSecret(jsonSerializerOptions, ApiEndpoints.GameRecordIndex(uid.Value, uid.Region))
.GetFromJsonAsync<Response<PlayerInfo>>(token)
.ConfigureAwait(false);
@@ -88,7 +88,7 @@ internal class GameRecordClient
{
Response<SpiralAbyss.SpiralAbyss>? resp = await httpClient
.SetUser(user)
.UsingDynamicSecret2(jsonSerializerOptions, ApiEndpoints.GameRecordSpiralAbyss(schedule, uid))
.UsingDynamicSecret(jsonSerializerOptions, ApiEndpoints.GameRecordSpiralAbyss(schedule, uid))
.GetFromJsonAsync<Response<SpiralAbyss.SpiralAbyss>>(token)
.ConfigureAwait(false);
@@ -122,7 +122,7 @@ internal class GameRecordClient
HttpResponseMessage? response = await httpClient
.SetUser(user)
.UsingDynamicSecret2(jsonSerializerOptions, ApiEndpoints.GameRecordCharacter, data)
.UsingDynamicSecret(jsonSerializerOptions, ApiEndpoints.GameRecordCharacter, data)
.PostAsJsonAsync(token)
.ConfigureAwait(false);

View File

@@ -18,10 +18,30 @@ public enum KnownReturnCode : int
/// </summary>
AlreadySignedIn = -5003,
/// <summary>
/// 需要风险验证
/// </summary>
RET_NEED_RISK_VERIFY = -3235,
/// <summary>
/// 禁止
/// </summary>
RET_AIGIS_FAILED = -3202,
/// <summary>
/// Ban
/// </summary>
RET_BAN = -3201,
/// <summary>
/// 需要地理验证
/// </summary>
RET_NEED_AIGIS = -3101,
/// <summary>
/// 尚未登录
/// </summary>
NotLoggedIn = -100,
RET_TOKEN_INVALID = -100,
/// <summary>
/// 验证密钥过期
@@ -31,7 +51,7 @@ public enum KnownReturnCode : int
/// <summary>
/// Ok
/// </summary>
OK = 0,
RET_SUCCESS = 0,
/// <summary>
/// 未定义

View File

@@ -88,4 +88,14 @@ public class Response<TData> : Response
/// </summary>
[JsonPropertyName("data")]
public TData? Data { get; set; }
/// <inheritdoc/>
public override int GetHashCode()
{
int j = ReturnCode.GetHashCode();
int k = Message == null ? 0 : Message.GetHashCode();
int i = Data == null ? 0 : Data.GetHashCode();
return (((j * 31) + k) * 31) + i;
}
}