mirror of
https://github.com/HolographicHat/Yae.git
synced 2025-12-06 22:52:49 +08:00
spectre console
This commit is contained in:
@@ -21,15 +21,19 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.3.106">
|
||||
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.3.183">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Google.Protobuf" Version="3.29.0"/>
|
||||
<PackageReference Include="Grpc.Tools" Version="2.67.0">
|
||||
<PackageReference Include="Google.Protobuf" Version="3.30.2" />
|
||||
<PackageReference Include="Grpc.Tools" Version="2.71.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Spectre.Console" Version="0.49.1"/>
|
||||
<PackageReference Include="Spectre.Console" Version="0.50.1-preview.0.3" />
|
||||
<PackageReference Include="Spectre.Console.Analyzer" Version="1.0.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -55,6 +59,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<CETCompat>false</CETCompat>
|
||||
<!-- <TrimmerSingleWarn>false</TrimmerSingleWarn>-->
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
121
YaeAchievement/res/App.Designer.cs
generated
121
YaeAchievement/res/App.Designer.cs
generated
@@ -86,6 +86,24 @@ namespace YaeAchievement.res {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to No.
|
||||
/// </summary>
|
||||
internal static string CommonNo {
|
||||
get {
|
||||
return ResourceManager.GetString("CommonNo", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Yes.
|
||||
/// </summary>
|
||||
internal static string CommonYes {
|
||||
get {
|
||||
return ResourceManager.GetString("CommonYes", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to You need to login genshin impact before exporting..
|
||||
/// </summary>
|
||||
@@ -114,16 +132,7 @@ namespace YaeAchievement.res {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Export to:
|
||||
///[0] Cocogoat (https://cocogoat.work/achievement, Default)
|
||||
///[1] Snap.HuTao
|
||||
///[2] Paimon.moe
|
||||
///[3] Seelie.me
|
||||
///[4] Csv file
|
||||
///[5] Xunkong
|
||||
///[7] Teyvat Guide
|
||||
///[8] UIAF JSON File
|
||||
///Input a number (0-8): .
|
||||
/// Looks up a localized string similar to Export to:.
|
||||
/// </summary>
|
||||
internal static string ExportChoose {
|
||||
get {
|
||||
@@ -131,6 +140,87 @@ namespace YaeAchievement.res {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Cocogoat (https://cocogoat.work/achievement).
|
||||
/// </summary>
|
||||
internal static string ExportTargetCocogoat {
|
||||
get {
|
||||
return ResourceManager.GetString("ExportTargetCocogoat", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Csv file.
|
||||
/// </summary>
|
||||
internal static string ExportTargetCsv {
|
||||
get {
|
||||
return ResourceManager.GetString("ExportTargetCsv", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Snap.HuTao.
|
||||
/// </summary>
|
||||
internal static string ExportTargetHuTao {
|
||||
get {
|
||||
return ResourceManager.GetString("ExportTargetHuTao", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Paimon.moe.
|
||||
/// </summary>
|
||||
internal static string ExportTargetPaimon {
|
||||
get {
|
||||
return ResourceManager.GetString("ExportTargetPaimon", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Seelie.me.
|
||||
/// </summary>
|
||||
internal static string ExportTargetSeelie {
|
||||
get {
|
||||
return ResourceManager.GetString("ExportTargetSeelie", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Teyvat Guide.
|
||||
/// </summary>
|
||||
internal static string ExportTargetTeyvatGuide {
|
||||
get {
|
||||
return ResourceManager.GetString("ExportTargetTeyvatGuide", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to UIAF JSON File.
|
||||
/// </summary>
|
||||
internal static string ExportTargetUIAFJson {
|
||||
get {
|
||||
return ResourceManager.GetString("ExportTargetUIAFJson", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to .
|
||||
/// </summary>
|
||||
internal static string ExportTargetWxApp1 {
|
||||
get {
|
||||
return ResourceManager.GetString("ExportTargetWxApp1", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Xunkong.
|
||||
/// </summary>
|
||||
internal static string ExportTargetXunkong {
|
||||
get {
|
||||
return ResourceManager.GetString("ExportTargetXunkong", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Fail, please contact developer to get help information.
|
||||
/// </summary>
|
||||
@@ -320,6 +410,15 @@ namespace YaeAchievement.res {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Checking update....
|
||||
/// </summary>
|
||||
internal static string UpdateChecking {
|
||||
get {
|
||||
return ResourceManager.GetString("UpdateChecking", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Description:
|
||||
///{0}.
|
||||
@@ -358,7 +457,7 @@ namespace YaeAchievement.res {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Use previous fetched data? (yes|no).
|
||||
/// Looks up a localized string similar to Use previous fetched data?.
|
||||
/// </summary>
|
||||
internal static string UsePreviousData {
|
||||
get {
|
||||
|
||||
@@ -25,16 +25,7 @@
|
||||
<value>all achievement</value>
|
||||
</data>
|
||||
<data name="ExportChoose" xml:space="preserve">
|
||||
<value>Export to:
|
||||
[0] Cocogoat (https://cocogoat.work/achievement, Default)
|
||||
[1] Snap.HuTao
|
||||
[2] Paimon.moe
|
||||
[3] Seelie.me
|
||||
[4] Csv file
|
||||
[5] Xunkong
|
||||
[7] Teyvat Guide
|
||||
[8] UIAF JSON File
|
||||
Input a number (0-8): </value>
|
||||
<value>Export to:</value>
|
||||
</data>
|
||||
<data name="ExportToCocogoatSuccess" xml:space="preserve">
|
||||
<value>Successfully exported to cocogoat.</value>
|
||||
@@ -107,7 +98,7 @@ Input a number (0-8): </value>
|
||||
<value>YaeAchievement ({0})</value>
|
||||
</data>
|
||||
<data name="UsePreviousData" xml:space="preserve">
|
||||
<value>Use previous fetched data? (yes|no)</value>
|
||||
<value>Use previous fetched data?</value>
|
||||
</data>
|
||||
<data name="NetworkError" xml:space="preserve">
|
||||
<value>Network error:</value>
|
||||
@@ -121,7 +112,6 @@ Input a number (0-8): </value>
|
||||
<data name="ExceptionNetwork" xml:space="preserve">
|
||||
<value>Network error ({0}: {1})</value>
|
||||
</data>
|
||||
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
|
||||
<data name="GenshinHashError" xml:space="preserve">
|
||||
<value>Please update genshin and retry.</value>
|
||||
</data>
|
||||
@@ -137,4 +127,40 @@ Input a number (0-8): </value>
|
||||
<data name="WaitMetadataUpdate" xml:space="preserve">
|
||||
<value>Please update game and retry.</value>
|
||||
</data>
|
||||
<data name="UpdateChecking" xml:space="preserve">
|
||||
<value>Checking update...</value>
|
||||
</data>
|
||||
<data name="CommonYes" xml:space="preserve">
|
||||
<value>Yes</value>
|
||||
</data>
|
||||
<data name="CommonNo" xml:space="preserve">
|
||||
<value>No</value>
|
||||
</data>
|
||||
<data name="ExportTargetCocogoat" xml:space="preserve">
|
||||
<value>Cocogoat (https://cocogoat.work/achievement)</value>
|
||||
</data>
|
||||
<data name="ExportTargetHuTao" xml:space="preserve">
|
||||
<value>Snap.HuTao</value>
|
||||
</data>
|
||||
<data name="ExportTargetPaimon" xml:space="preserve">
|
||||
<value>Paimon.moe</value>
|
||||
</data>
|
||||
<data name="ExportTargetSeelie" xml:space="preserve">
|
||||
<value>Seelie.me</value>
|
||||
</data>
|
||||
<data name="ExportTargetCsv" xml:space="preserve">
|
||||
<value>Csv file</value>
|
||||
</data>
|
||||
<data name="ExportTargetXunkong" xml:space="preserve">
|
||||
<value>Xunkong</value>
|
||||
</data>
|
||||
<data name="ExportTargetTeyvatGuide" xml:space="preserve">
|
||||
<value>Teyvat Guide</value>
|
||||
</data>
|
||||
<data name="ExportTargetUIAFJson" xml:space="preserve">
|
||||
<value>UIAF JSON File</value>
|
||||
</data>
|
||||
<data name="ExportTargetWxApp1" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
</root>
|
||||
@@ -18,17 +18,7 @@
|
||||
<value>全部成就</value>
|
||||
</data>
|
||||
<data name="ExportChoose" xml:space="preserve">
|
||||
<value>导出至:
|
||||
[0] 椰羊 (https://cocogoat.work/achievement, 默认)
|
||||
[1] Snap Hutao
|
||||
[2] Paimon.moe
|
||||
[3] Seelie.me
|
||||
[4] 表格文件
|
||||
[5] 寻空
|
||||
[6] 原魔工具箱
|
||||
[7] Teyvat Guide
|
||||
[8] UIAF JSON 文件
|
||||
输入一个数字 (0-8): </value>
|
||||
<value>导出至:</value>
|
||||
</data>
|
||||
<data name="ExportToCocogoatSuccess" xml:space="preserve">
|
||||
<value>在浏览器内进行下一步操作</value>
|
||||
@@ -101,7 +91,7 @@
|
||||
<value>YaeAchievement - 原神成就导出工具 ({0})</value>
|
||||
</data>
|
||||
<data name="UsePreviousData" xml:space="preserve">
|
||||
<value>要使用上一次获取到的成就数据吗? (yes|no)</value>
|
||||
<value>要使用上一次获取到的成就数据吗? </value>
|
||||
</data>
|
||||
<data name="NetworkError" xml:space="preserve">
|
||||
<value>网络错误: {0}</value>
|
||||
@@ -130,4 +120,40 @@
|
||||
<data name="WaitMetadataUpdate" xml:space="preserve">
|
||||
<value>当前元数据版本不匹配,请更新原神至最新版本或等待元数据更新后重试。</value>
|
||||
</data>
|
||||
<data name="UpdateChecking" xml:space="preserve">
|
||||
<value>正在检查更新...</value>
|
||||
</data>
|
||||
<data name="CommonYes" xml:space="preserve">
|
||||
<value>是</value>
|
||||
</data>
|
||||
<data name="CommonNo" xml:space="preserve">
|
||||
<value>否</value>
|
||||
</data>
|
||||
<data name="ExportTargetCocogoat" xml:space="preserve">
|
||||
<value>椰羊 (https://cocogoat.work/achievement)</value>
|
||||
</data>
|
||||
<data name="ExportTargetHuTao" xml:space="preserve">
|
||||
<value>Snap Hutao</value>
|
||||
</data>
|
||||
<data name="ExportTargetPaimon" xml:space="preserve">
|
||||
<value>Paimon.moe</value>
|
||||
</data>
|
||||
<data name="ExportTargetSeelie" xml:space="preserve">
|
||||
<value>Seelie.me</value>
|
||||
</data>
|
||||
<data name="ExportTargetCsv" xml:space="preserve">
|
||||
<value>表格文件</value>
|
||||
</data>
|
||||
<data name="ExportTargetXunkong" xml:space="preserve">
|
||||
<value>寻空</value>
|
||||
</data>
|
||||
<data name="ExportTargetUIAFJson" xml:space="preserve">
|
||||
<value>UIAF JSON 文件</value>
|
||||
</data>
|
||||
<data name="ExportTargetTeyvatGuide" xml:space="preserve">
|
||||
<value>Teyvat Guide</value>
|
||||
</data>
|
||||
<data name="ExportTargetWxApp1" xml:space="preserve">
|
||||
<value>原魔工具箱</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -5,37 +5,40 @@ using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Microsoft.Win32;
|
||||
using Spectre.Console;
|
||||
using YaeAchievement.Outputs;
|
||||
using YaeAchievement.Parsers;
|
||||
using YaeAchievement.res;
|
||||
|
||||
// ReSharper disable UnusedMember.Local
|
||||
|
||||
namespace YaeAchievement;
|
||||
|
||||
public static class Export {
|
||||
|
||||
public static uint ExportTo { get; set; } = uint.MaxValue;
|
||||
public static int ExportTo { get; set; } = 114514;
|
||||
|
||||
public static void Choose(AchievementAllDataNotify data) {
|
||||
if (ExportTo == uint.MaxValue) {
|
||||
Console.Write(App.ExportChoose);
|
||||
while (Console.KeyAvailable) {
|
||||
Console.ReadKey(false);
|
||||
}
|
||||
if (!uint.TryParse(Console.ReadLine(), out var num)) num = 0;
|
||||
ExportTo = num;
|
||||
var targets = new Dictionary<string, Action<AchievementAllDataNotify>> {
|
||||
{ App.ExportTargetCocogoat, ToCocogoat },
|
||||
{ App.ExportTargetHuTao, ToHuTao },
|
||||
{ App.ExportTargetPaimon, ToPaimon },
|
||||
{ App.ExportTargetSeelie, ToSeelie },
|
||||
{ App.ExportTargetCsv, ToCSV },
|
||||
{ App.ExportTargetXunkong, ToXunkong },
|
||||
// { App.ExportTargetWxApp1, ToWxApp1 },
|
||||
{ App.ExportTargetTeyvatGuide, ToTeyvatGuide },
|
||||
{ App.ExportTargetUIAFJson, ToUIAFJson },
|
||||
// { "", ToRawJson }
|
||||
};
|
||||
Action<AchievementAllDataNotify> action;
|
||||
if (ExportTo == 114514) {
|
||||
var prompt = new SelectionPrompt<string>().Title(App.ExportChoose).AddChoices(targets.Keys);
|
||||
action = targets[AnsiConsole.Prompt(prompt)];
|
||||
} else {
|
||||
action = targets.ElementAtOrDefault(ExportTo).Value ?? ToCocogoat;
|
||||
}
|
||||
((Action<AchievementAllDataNotify>) (ExportTo switch {
|
||||
1 => ToHuTao,
|
||||
2 => ToPaimon,
|
||||
3 => ToSeelie,
|
||||
4 => ToCSV,
|
||||
5 => ToXunkong,
|
||||
6 => ToWxApp1,
|
||||
7 => ToTeyvatGuide,
|
||||
8 => ToUIAFJson,
|
||||
9 => ToRawJson,
|
||||
_ => ToCocogoat
|
||||
})).Invoke(data);
|
||||
action(data);
|
||||
}
|
||||
|
||||
private static void ToCocogoat(AchievementAllDataNotify data) {
|
||||
@@ -46,17 +49,17 @@ public static class Export {
|
||||
request.Content = new StringContent(result, Encoding.UTF8, "application/json");
|
||||
using var response = Utils.CHttpClient.Send(request);
|
||||
if (response.StatusCode != HttpStatusCode.Created) {
|
||||
Console.WriteLine(App.ExportToCocogoatFail);
|
||||
AnsiConsole.WriteLine(App.ExportToCocogoatFail);
|
||||
return;
|
||||
}
|
||||
var responseText = response.Content.ReadAsStringAsync().GetAwaiter().GetResult();
|
||||
var responseJson = JsonSerializer.Deserialize(responseText, CocogoatResponseContext.Default.CocogoatResponse)!;
|
||||
var cocogoatUrl = $"https://cocogoat.work/achievement?memo={responseJson.Key}";
|
||||
Utils.SetQuickEditMode(true);
|
||||
Console.WriteLine(cocogoatUrl);
|
||||
AnsiConsole.MarkupLineInterpolated($"[link]{cocogoatUrl}[/]");
|
||||
if (Utils.ShellOpen(cocogoatUrl))
|
||||
{
|
||||
Console.WriteLine(App.ExportToCocogoatSuccess);
|
||||
AnsiConsole.WriteLine(App.ExportToCocogoatSuccess);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,16 +71,16 @@ public static class Export {
|
||||
request.RequestUri = new Uri("https://api.qyinter.com/achievementRedis");
|
||||
request.Content = new StringContent(result, Encoding.UTF8, "application/json");
|
||||
using var response = Utils.CHttpClient.Send(request);
|
||||
Console.WriteLine(App.ExportToWxApp1Success, id);
|
||||
AnsiConsole.WriteLine(App.ExportToWxApp1Success, id);
|
||||
}
|
||||
|
||||
private static void ToHuTao(AchievementAllDataNotify data) {
|
||||
if (CheckWinUIAppScheme("hutao")) {
|
||||
Utils.CopyToClipboard(UIAFSerializer.Serialize(data));
|
||||
Utils.ShellOpen("hutao://achievement/import");
|
||||
Console.WriteLine(App.ExportToSnapGenshinSuccess);
|
||||
AnsiConsole.WriteLine(App.ExportToSnapGenshinSuccess);
|
||||
} else {
|
||||
Console.WriteLine(App.ExportToSnapGenshinNeedUpdate);
|
||||
AnsiConsole.WriteLine(App.ExportToSnapGenshinNeedUpdate);
|
||||
Utils.ShellOpen("ms-windows-store://pdp/?productid=9PH4NXJ2JN52");
|
||||
}
|
||||
}
|
||||
@@ -86,9 +89,9 @@ public static class Export {
|
||||
if (CheckWinUIAppScheme("xunkong")) {
|
||||
Utils.CopyToClipboard(UIAFSerializer.Serialize(data));
|
||||
Utils.ShellOpen("xunkong://import-achievement?caller=YaeAchievement&from=clipboard");
|
||||
Console.WriteLine(App.ExportToXunkongSuccess);
|
||||
AnsiConsole.WriteLine(App.ExportToXunkongSuccess);
|
||||
} else {
|
||||
Console.WriteLine(App.ExportToXunkongNeedUpdate);
|
||||
AnsiConsole.WriteLine(App.ExportToXunkongNeedUpdate);
|
||||
Utils.ShellOpen("ms-windows-store://pdp/?productid=9N2SVG0JMT12");
|
||||
}
|
||||
}
|
||||
@@ -97,9 +100,9 @@ public static class Export {
|
||||
if (Process.GetProcessesByName("TeyvatGuide").Length != 0) {
|
||||
Utils.CopyToClipboard(UIAFSerializer.Serialize(data));
|
||||
Utils.ShellOpen("teyvatguide://import_uiaf?app=Yae");
|
||||
Console.WriteLine(App.ExportToTauriSuccess);
|
||||
AnsiConsole.WriteLine(App.ExportToTauriSuccess);
|
||||
} else {
|
||||
Console.WriteLine(App.ExportToTauriFail);
|
||||
AnsiConsole.WriteLine(App.ExportToTauriFail);
|
||||
Utils.ShellOpen("ms-windows-store://pdp/?productid=9NLBNNNBNSJN");
|
||||
}
|
||||
}
|
||||
@@ -108,21 +111,21 @@ public static class Export {
|
||||
private static void ToUIAFJson(AchievementAllDataNotify data) {
|
||||
var path = Path.GetFullPath($"uiaf-{DateTime.Now:yyyyMMddHHmmss}.json");
|
||||
if (TryWriteToFile(path, UIAFSerializer.Serialize(data))) {
|
||||
Console.WriteLine(App.ExportToFileSuccess, path);
|
||||
AnsiConsole.WriteLine(App.ExportToFileSuccess, path);
|
||||
}
|
||||
}
|
||||
|
||||
private static void ToPaimon(AchievementAllDataNotify data) {
|
||||
var path = Path.GetFullPath($"export-{DateTime.Now:yyyyMMddHHmmss}-paimon.json");
|
||||
if (TryWriteToFile(path, PaimonSerializer.Serialize(data))) {
|
||||
Console.WriteLine(App.ExportToFileSuccess, path);
|
||||
AnsiConsole.WriteLine(App.ExportToFileSuccess, path);
|
||||
}
|
||||
}
|
||||
|
||||
private static void ToSeelie(AchievementAllDataNotify data) {
|
||||
var path = Path.GetFullPath($"export-{DateTime.Now:yyyyMMddHHmmss}-seelie.json");
|
||||
if (TryWriteToFile(path, SeelieSerializer.Serialize(data))) {
|
||||
Console.WriteLine(App.ExportToFileSuccess, path);
|
||||
AnsiConsole.WriteLine(App.ExportToFileSuccess, path);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,7 +136,7 @@ public static class Export {
|
||||
foreach (var ach in data.AchievementList.OrderBy(a => a.Id)) {
|
||||
if (UnusedAchievement.Contains(ach.Id)) continue;
|
||||
if (!info.Items.TryGetValue(ach.Id, out var achInfo) || achInfo == null) {
|
||||
Console.WriteLine($@"Unable to find {ach.Id} in metadata.");
|
||||
AnsiConsole.WriteLine($@"Unable to find {ach.Id} in metadata.");
|
||||
continue;
|
||||
}
|
||||
var finishAt = "";
|
||||
@@ -156,7 +159,7 @@ public static class Export {
|
||||
}));
|
||||
var path = Path.GetFullPath($"achievement-{DateTime.Now:yyyyMMddHHmmss}.csv");
|
||||
if (TryWriteToFile(path, $"\uFEFF{string.Join("\n", output)}")) {
|
||||
Console.WriteLine(App.ExportToFileSuccess, path);
|
||||
AnsiConsole.WriteLine(App.ExportToFileSuccess, path);
|
||||
Process.Start("explorer.exe", $"{Path.GetDirectoryName(path)}");
|
||||
}
|
||||
}
|
||||
@@ -165,7 +168,7 @@ public static class Export {
|
||||
var path = Path.GetFullPath($"export-{DateTime.Now:yyyyMMddHHmmss}-raw.json");
|
||||
var text = AchievementRawDataSerializer.Serialize(data);
|
||||
if (TryWriteToFile(path, text)) {
|
||||
Console.WriteLine(App.ExportToFileSuccess, path);
|
||||
AnsiConsole.WriteLine(App.ExportToFileSuccess, path);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -192,7 +195,7 @@ public static class Export {
|
||||
|
||||
public static int PrintMsgAndReturnErrCode(this Win32Exception ex, string msg) {
|
||||
// ReSharper disable once LocalizableElement
|
||||
Console.WriteLine($"{msg}: {ex.Message}");
|
||||
AnsiConsole.WriteLine($"{msg}: {ex.Message}");
|
||||
return ex.NativeErrorCode;
|
||||
}
|
||||
|
||||
@@ -201,7 +204,7 @@ public static class Export {
|
||||
File.WriteAllText(path, contents);
|
||||
return true;
|
||||
} catch (UnauthorizedAccessException) {
|
||||
Console.WriteLine(App.NoWritePermission, path);
|
||||
AnsiConsole.WriteLine(App.NoWritePermission, path);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,11 +4,6 @@ using Proto;
|
||||
|
||||
namespace YaeAchievement;
|
||||
|
||||
// ReSharper disable InconsistentNaming
|
||||
// ReSharper disable ConvertToConstant.Global
|
||||
// ReSharper disable FieldCanBeMadeReadOnly.Global
|
||||
// ReSharper disable once MemberCanBePrivate.Global
|
||||
|
||||
public static class GlobalVars {
|
||||
|
||||
public static bool PauseOnExit { get; set; } = true;
|
||||
@@ -24,8 +19,6 @@ public static class GlobalVars {
|
||||
public const string AppVersionName = "5.3";
|
||||
|
||||
public const string PipeName = "YaeAchievementPipe";
|
||||
public const string RinBucketHost = "https://rin.holohat.work";
|
||||
public const string SakuraBucketHost = "https://cn-cd-1259389942.file.myqcloud.com";
|
||||
|
||||
[field:MaybeNull]
|
||||
public static AchievementInfo AchievementInfo =>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Google.Protobuf;
|
||||
using Spectre.Console;
|
||||
using YaeAchievement.res;
|
||||
using YaeAchievement.Utilities;
|
||||
|
||||
@@ -73,7 +74,7 @@ public class AchievementAllDataNotify {
|
||||
}
|
||||
} catch (InvalidProtocolBufferException) {
|
||||
// ReSharper disable once LocalizableElement
|
||||
Console.WriteLine("Parse failed");
|
||||
AnsiConsole.WriteLine("Parse failed");
|
||||
File.WriteAllBytes("achievement_raw_data.bin", bytes);
|
||||
Environment.Exit(0);
|
||||
}
|
||||
@@ -98,7 +99,7 @@ public class AchievementAllDataNotify {
|
||||
.FieldIds;
|
||||
#if DEBUG
|
||||
// ReSharper disable once LocalizableElement
|
||||
Console.WriteLine($"Id={iId}, Status={sId}, Total={totalId}, Current={currentId}, Timestamp={tId}");
|
||||
AnsiConsole.WriteLine($"Id={iId}, Status={sId}, Total={totalId}, Current={currentId}, Timestamp={tId}");
|
||||
#endif
|
||||
} else {
|
||||
var info = GlobalVars.AchievementInfo.PbInfo; // ...
|
||||
@@ -108,7 +109,7 @@ public class AchievementAllDataNotify {
|
||||
totalId = info.TotalProgress;
|
||||
currentId = info.CurrentProgress;
|
||||
if (data.Any(dict => !dict.ContainsKey(iId) || !dict.ContainsKey(sId) || !dict.ContainsKey(totalId))) {
|
||||
Console.WriteLine(App.WaitMetadataUpdate);
|
||||
AnsiConsole.WriteLine(App.WaitMetadataUpdate);
|
||||
Environment.Exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Google.Protobuf;
|
||||
using Proto;
|
||||
using Spectre.Console;
|
||||
|
||||
// ReSharper disable MemberCanBePrivate.Global
|
||||
// ReSharper disable CollectionNeverQueried.Global
|
||||
@@ -51,7 +52,7 @@ public class PlayerStoreNotify {
|
||||
}
|
||||
} catch (InvalidProtocolBufferException) {
|
||||
// ReSharper disable once LocalizableElement
|
||||
Console.WriteLine("Parse failed");
|
||||
AnsiConsole.WriteLine("Parse failed");
|
||||
File.WriteAllBytes("store_raw_data.bin", bytes);
|
||||
Environment.Exit(0);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using Spectre.Console;
|
||||
using YaeAchievement.Parsers;
|
||||
using YaeAchievement.res;
|
||||
using YaeAchievement.Utilities;
|
||||
@@ -11,8 +12,13 @@ internal static class Program {
|
||||
|
||||
public static async Task Main(string[] args) {
|
||||
|
||||
AnsiConsole.WriteLine(@"----------------------------------------------------");
|
||||
AnsiConsole.WriteLine(App.AppBanner, GlobalVars.AppVersionName);
|
||||
AnsiConsole.WriteLine(@"https://github.com/HolographicHat/YaeAchievement");
|
||||
AnsiConsole.WriteLine(@"----------------------------------------------------");
|
||||
|
||||
if (!new Mutex(true, @"Global\YaeMiku~uwu").WaitOne(0, false)) {
|
||||
Console.WriteLine(App.AnotherInstance);
|
||||
AnsiConsole.WriteLine(App.AnotherInstance);
|
||||
Environment.Exit(302);
|
||||
}
|
||||
|
||||
@@ -21,15 +27,10 @@ internal static class Program {
|
||||
|
||||
CheckGenshinIsRunning();
|
||||
|
||||
Console.WriteLine(@"----------------------------------------------------");
|
||||
Console.WriteLine(App.AppBanner, GlobalVars.AppVersionName);
|
||||
Console.WriteLine(@"https://github.com/HolographicHat/YaeAchievement");
|
||||
Console.WriteLine(@"----------------------------------------------------");
|
||||
|
||||
AppConfig.Load(args.GetOrNull(0) ?? "auto");
|
||||
Export.ExportTo = ToUIntOrNull(args.GetOrNull(1)) ?? uint.MaxValue;
|
||||
Export.ExportTo = ToIntOrDefault(args.GetOrNull(1), 114514);
|
||||
|
||||
await CheckUpdate(ToBooleanOrFalse(args.GetOrNull(2)));
|
||||
await CheckUpdate(ToBooleanOrDefault(args.GetOrNull(2)));
|
||||
|
||||
AchievementAllDataNotify? data = null;
|
||||
try {
|
||||
@@ -39,8 +40,10 @@ internal static class Program {
|
||||
} catch (Exception) { /* ignored */ }
|
||||
|
||||
if (CacheFile.GetLastWriteTime("achievement_data").AddMinutes(60) > DateTime.UtcNow && data != null) {
|
||||
Console.WriteLine(App.UsePreviousData);
|
||||
if (Console.ReadLine()?.ToUpper() is "Y" or "YES") {
|
||||
var prompt = new SelectionPrompt<string>()
|
||||
.Title(App.UsePreviousData)
|
||||
.AddChoices(App.CommonYes, App.CommonNo);
|
||||
if (AnsiConsole.Prompt(prompt) == App.CommonYes) {
|
||||
Export.Choose(data);
|
||||
return;
|
||||
}
|
||||
@@ -51,7 +54,7 @@ internal static class Program {
|
||||
{ 2, PlayerStoreNotify.OnReceive },
|
||||
{ 100, PlayerPropNotify.OnReceive },
|
||||
}, () => {
|
||||
#if DEBUG
|
||||
#if DEBUG_EX
|
||||
PlayerPropNotify.OnFinish();
|
||||
File.WriteAllText("store_data.json", JsonSerializer.Serialize(PlayerStoreNotify.Instance, new JsonSerializerOptions {
|
||||
WriteIndented = true,
|
||||
|
||||
@@ -9,6 +9,7 @@ using Windows.Win32;
|
||||
using Windows.Win32.Foundation;
|
||||
using Windows.Win32.System.Console;
|
||||
using Proto;
|
||||
using Spectre.Console;
|
||||
using YaeAchievement.res;
|
||||
using YaeAchievement.Utilities;
|
||||
|
||||
@@ -28,31 +29,36 @@ public static class Utils {
|
||||
|
||||
public static async Task<byte[]> GetBucketFile(string path, bool useCache = true) {
|
||||
try {
|
||||
return await await Task.WhenAny(GetFile(GlobalVars.RinBucketHost), GetFile(GlobalVars.SakuraBucketHost));
|
||||
} catch (Exception e) when(e is SocketException or TaskCanceledException) {
|
||||
Console.WriteLine(App.NetworkError, e.Message);
|
||||
Environment.Exit(-1);
|
||||
return null!;
|
||||
return await GetFile("https://api.qhy04.com/hutaocdn/download?filename={0}", path, useCache);
|
||||
} catch (Exception e) when (e is SocketException or TaskCanceledException or HttpRequestException) {
|
||||
}
|
||||
async Task<byte[]> GetFile(string host) {
|
||||
using var msg = new HttpRequestMessage();
|
||||
msg.Method = HttpMethod.Get;
|
||||
msg.RequestUri = new Uri($"{host}/{path}");
|
||||
try {
|
||||
return await Task.WhenAny(
|
||||
GetFile("https://rin.holohat.work/{0}", path, useCache),
|
||||
GetFile("https://cn-cd-1259389942.file.myqcloud.com/{0}", path, useCache)
|
||||
).Unwrap();
|
||||
} catch (Exception ex) when (ex is SocketException or TaskCanceledException) {
|
||||
AnsiConsole.WriteLine(App.NetworkError, ex.Message);
|
||||
Environment.Exit(-1);
|
||||
}
|
||||
return null!;
|
||||
static async Task<byte[]> GetFile(string baseUrl, string objectKey, bool useCache) {
|
||||
using var reqwest = new HttpRequestMessage(HttpMethod.Get, string.Format(baseUrl, objectKey));
|
||||
CacheItem? cache = null;
|
||||
if (useCache && CacheFile.TryRead(path, out cache)) {
|
||||
msg.Headers.TryAddWithoutValidation("If-None-Match", $"{cache.Etag}");
|
||||
if (useCache && CacheFile.TryRead(objectKey, out cache)) {
|
||||
reqwest.Headers.TryAddWithoutValidation("If-None-Match", $"{cache.Etag}");
|
||||
}
|
||||
using var response = await CHttpClient.SendAsync(msg);
|
||||
using var response = await CHttpClient.SendAsync(reqwest);
|
||||
if (cache != null && response.StatusCode == HttpStatusCode.NotModified) {
|
||||
return cache.Content.ToByteArray();
|
||||
}
|
||||
response.EnsureSuccessStatusCode();
|
||||
var responseBytes = await response.Content.ReadAsByteArrayAsync();
|
||||
var bytes = await response.Content.ReadAsByteArrayAsync();
|
||||
if (useCache) {
|
||||
var etag = response.Headers.ETag!.Tag;
|
||||
CacheFile.Write(path, responseBytes, etag);
|
||||
CacheFile.Write(objectKey, bytes, etag);
|
||||
}
|
||||
return responseBytes;
|
||||
return bytes;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,12 +66,12 @@ public static class Utils {
|
||||
return array.Length > index ? array[index] : null;
|
||||
}
|
||||
|
||||
public static uint? ToUIntOrNull(string? value) {
|
||||
return value != null ? uint.TryParse(value, out var result) ? result : null : null;
|
||||
public static int ToIntOrDefault(string? value, int defaultValue = 0) {
|
||||
return value != null && int.TryParse(value, out var result) ? result : defaultValue;
|
||||
}
|
||||
|
||||
public static bool ToBooleanOrFalse(string? value) {
|
||||
return value != null && bool.TryParse(value, out var result) && result;
|
||||
public static bool ToBooleanOrDefault(string? value, bool defaultValue = false) {
|
||||
return value != null && bool.TryParse(value, out var result) ? result : defaultValue;
|
||||
}
|
||||
|
||||
public static unsafe void CopyToClipboard(string text) {
|
||||
@@ -86,35 +92,44 @@ public static class Utils {
|
||||
// ReSharper disable once NotAccessedField.Local
|
||||
private static UpdateInfo _updateInfo = null!;
|
||||
|
||||
public static Task StartSpinnerAsync(string status, Func<StatusContext, Task> func) {
|
||||
return AnsiConsole.Status().Spinner(Spinner.Known.SimpleDotsScrolling).StartAsync(status, func);
|
||||
}
|
||||
|
||||
public static Task<T> StartSpinnerAsync<T>(string status, Func<StatusContext, Task<T>> func) {
|
||||
return AnsiConsole.Status().Spinner(Spinner.Known.SimpleDotsScrolling).StartAsync(status, func);
|
||||
}
|
||||
|
||||
public static async Task CheckUpdate(bool useLocalLib) {
|
||||
var info = UpdateInfo.Parser.ParseFrom(await GetBucketFile("schicksal/version"))!;
|
||||
if (GlobalVars.AppVersionCode < info.VersionCode) {
|
||||
Console.WriteLine(App.UpdateNewVersion, GlobalVars.AppVersionName, info.VersionName);
|
||||
Console.WriteLine(App.UpdateDescription, info.Description);
|
||||
if (info.EnableAutoUpdate) {
|
||||
Console.WriteLine(App.UpdateDownloading);
|
||||
var versionData = await StartSpinnerAsync(App.UpdateChecking, _ => GetBucketFile("schicksal/version"));
|
||||
var versionInfo = UpdateInfo.Parser.ParseFrom(versionData)!;
|
||||
if (GlobalVars.AppVersionCode < versionInfo.VersionCode) {
|
||||
AnsiConsole.WriteLine(App.UpdateNewVersion, GlobalVars.AppVersionName, versionInfo.VersionName);
|
||||
AnsiConsole.WriteLine(App.UpdateDescription, versionInfo.Description);
|
||||
if (versionInfo.EnableAutoUpdate) {
|
||||
var newBin = await StartSpinnerAsync(App.UpdateDownloading, _ => GetBucketFile(versionInfo.PackageLink));
|
||||
var tmpPath = Path.GetTempFileName();
|
||||
await File.WriteAllBytesAsync(tmpPath, await GetBucketFile(info.PackageLink));
|
||||
var updaterPath = Path.Combine(GlobalVars.DataPath, "update.exe");
|
||||
await using (var dstStream = File.Open($"{GlobalVars.DataPath}/update.exe", FileMode.Create)) {
|
||||
await using var srcStream = typeof(Program).Assembly.GetManifestResourceStream("updater")!;
|
||||
await srcStream.CopyToAsync(dstStream);
|
||||
}
|
||||
await File.WriteAllBytesAsync(tmpPath, newBin);
|
||||
ShellOpen(updaterPath, $"{Environment.ProcessId} \"{tmpPath}\"");
|
||||
await Task.Delay(1919810);
|
||||
await StartSpinnerAsync(App.UpdateChecking, _ => Task.Delay(1919810));
|
||||
GlobalVars.PauseOnExit = false;
|
||||
Environment.Exit(0);
|
||||
}
|
||||
Console.WriteLine(App.DownloadLink, info.PackageLink);
|
||||
if (info.ForceUpdate) {
|
||||
AnsiConsole.MarkupLine($"[link]{App.DownloadLink}[/]", versionInfo.PackageLink);
|
||||
if (versionInfo.ForceUpdate) {
|
||||
Environment.Exit(0);
|
||||
}
|
||||
}
|
||||
if (info.EnableLibDownload && !useLocalLib) {
|
||||
if (versionInfo.EnableLibDownload && !useLocalLib) {
|
||||
var data = await GetBucketFile("schicksal/lic.dll");
|
||||
await File.WriteAllBytesAsync(GlobalVars.LibFilePath, data);
|
||||
}
|
||||
_updateInfo = info;
|
||||
_updateInfo = versionInfo;
|
||||
}
|
||||
|
||||
// ReSharper disable once UnusedMethodReturnValue.Global
|
||||
@@ -142,7 +157,7 @@ public static class Utils {
|
||||
try {
|
||||
using var handle = File.OpenHandle($"{path}/output_log.txt", share: FileShare.None);
|
||||
} catch (IOException) {
|
||||
Console.WriteLine(App.GenshinIsRunning, 0);
|
||||
AnsiConsole.WriteLine(App.GenshinIsRunning, 0);
|
||||
Environment.Exit(301);
|
||||
}
|
||||
}
|
||||
@@ -154,7 +169,7 @@ public static class Utils {
|
||||
AppDomain.CurrentDomain.ProcessExit += (_, _) => {
|
||||
_proc?.Terminate(0);
|
||||
if (GlobalVars.PauseOnExit) {
|
||||
Console.WriteLine(App.PressKeyToExit);
|
||||
AnsiConsole.WriteLine(App.PressKeyToExit);
|
||||
Console.ReadKey();
|
||||
}
|
||||
};
|
||||
@@ -165,16 +180,16 @@ public static class Utils {
|
||||
var ex = e.ExceptionObject;
|
||||
switch (ex) {
|
||||
case ApplicationException ex1:
|
||||
Console.WriteLine(ex1.Message);
|
||||
AnsiConsole.WriteLine(ex1.Message);
|
||||
break;
|
||||
case SocketException ex2:
|
||||
Console.WriteLine(App.ExceptionNetwork, nameof(SocketException), ex2.Message);
|
||||
AnsiConsole.WriteLine(App.ExceptionNetwork, nameof(SocketException), ex2.Message);
|
||||
break;
|
||||
case HttpRequestException ex3:
|
||||
Console.WriteLine(App.ExceptionNetwork, nameof(HttpRequestException), ex3.Message);
|
||||
AnsiConsole.WriteLine(App.ExceptionNetwork, nameof(HttpRequestException), ex3.Message);
|
||||
break;
|
||||
default:
|
||||
Console.WriteLine(ex.ToString());
|
||||
AnsiConsole.WriteLine(ex.ToString()!);
|
||||
break;
|
||||
}
|
||||
Environment.Exit(-1);
|
||||
@@ -189,13 +204,13 @@ public static class Utils {
|
||||
_proc.OnExit += () => {
|
||||
if (_isUnexpectedExit) {
|
||||
_proc = null;
|
||||
Console.WriteLine(App.GameProcessExit);
|
||||
AnsiConsole.WriteLine(App.GameProcessExit);
|
||||
Environment.Exit(114514);
|
||||
}
|
||||
};
|
||||
_proc.LoadLibrary(GlobalVars.LibFilePath);
|
||||
_proc.ResumeMainThread();
|
||||
Console.WriteLine(App.GameLoading, _proc.Id);
|
||||
AnsiConsole.WriteLine(App.GameLoading, _proc.Id);
|
||||
Task.Run(() => {
|
||||
using var stream = new NamedPipeServerStream(GlobalVars.PipeName);
|
||||
using var reader = new BinaryReader(stream);
|
||||
|
||||
Reference in New Issue
Block a user