Skip to content

Commit 8aa14a4

Browse files
authored
Merge pull request #8 from pk9r/dev
release v1.2.0
2 parents 650386f + 26a958c commit 8aa14a4

File tree

12 files changed

+439
-24
lines changed

12 files changed

+439
-24
lines changed

.github/FUNDING.yml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# These are supported funding model platforms
2+
3+
github: pk9r
4+
patreon: # Replace with a single Patreon username
5+
open_collective: # Replace with a single Open Collective username
6+
ko_fi: # Replace with a single Ko-fi username
7+
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8+
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9+
liberapay: # Replace with a single Liberapay username
10+
issuehunt: # Replace with a single IssueHunt username
11+
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
12+
polar: # Replace with a single Polar username
13+
buy_me_a_coffee: # Replace with a single Buy Me a Coffee username
14+
thanks_dev: # Replace with a single thanks.dev username
15+
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']

GitcgNetCord.sln.DotSettings.user

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
2+
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AApplicationCommand_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003Fba38522c788bae84bc932ef7a29d71d27152622c27dced65c633f40401360_003FApplicationCommand_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
23
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AEmojiProperties_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003F6ab3be37dec913ef7d649338adbdc7ba6ecf9e17ad69af5d293b71d60e4be5a_003FEmojiProperties_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
4+
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ANetCord_002ERest_002EEmbedProperties_002Eg_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003F2d74c9ef991b267cbaf535bf08019b8966eb55a_003FNetCord_002ERest_002EEmbedProperties_002Eg_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
35
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AReactionEmojiProperties_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003Ffa944ea7b9c605f0445098be8ca0ce31dd212e446a97d32b344db96e36aa95_003FReactionEmojiProperties_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
46
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ASlashCommandChoiceAttribute_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003F70977d8e8e415d5c5286c84b973d39d27dd1744ed6edc99adee5b82899ff31_003FSlashCommandChoiceAttribute_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
7+
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AString_002EManipulation_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003Fe75a5575ba872c8ea754c015cb363850e6c661f39569712d5b74aaca67263c_003FString_002EManipulation_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
58
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AValidateOptionsResultBuilder_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003Ff7c3fb89ef359986fa6049c62f921cb5a184ae6b30dcddaffc3d5ab7fcd77_003FValidateOptionsResultBuilder_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
69
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AValidateOptionsResult_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003F728062cbf4f62ed8eda2cc948368227a2c54cae9679f89fb68c439f71b7576_003FValidateOptionsResult_002Ecs/@EntryIndexedValue">ForceIncluded</s:String></wpf:ResourceDictionary>
Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
1+
using System.Collections.Immutable;
2+
using System.Drawing;
3+
using System.Text;
4+
using GitcgNetCord.MainApp.Entities.Repositories;
5+
using HoyolabHttpClient;
6+
using NetCord.Rest;
7+
using NetCord.Services.ApplicationCommands;
8+
9+
namespace GitcgNetCord.MainApp.Commands.Slash;
10+
11+
public static class CardSlashCommand
12+
{
13+
public static async Task ExecuteAsync(
14+
HoyolabHttpClientService hoyolab,
15+
IServiceProvider serviceProvider,
16+
ApplicationCommandContext context,
17+
[
18+
SlashCommandParameter(
19+
Description = "Minimum use count for a card to be considered. Default: 10."
20+
)
21+
]
22+
int minUseCount = 10,
23+
[
24+
SlashCommandParameter(
25+
Description = "Number of top win rates to display. Default: 5."
26+
)
27+
]
28+
int topWinRateCount = 5,
29+
[
30+
SlashCommandParameter(
31+
Description = "Number of top use counts to display. Default: 10."
32+
)
33+
]
34+
int topUseCount = 10
35+
)
36+
{
37+
var activeHoyolabAccountService = serviceProvider
38+
.GetRequiredService<ActiveHoyolabAccountService>();
39+
40+
await context.Interaction.SendResponseAsync(
41+
InteractionCallback.DeferredMessage()
42+
);
43+
44+
var hoyolabAccount = await activeHoyolabAccountService
45+
.GetActiveHoyolabAccountAsync(context.User.Id);
46+
47+
if (hoyolabAccount == null)
48+
{
49+
var commands = await context.Client.Rest
50+
.GetGlobalApplicationCommandsAsync(context.Client.Id);
51+
52+
var accountCommand = commands
53+
.First(x => x.Name == "hoyolab-accounts");
54+
55+
await context.Interaction.ModifyResponseAsync(message =>
56+
{
57+
message.AddEmbeds(new EmbedProperties()
58+
.WithTitle("Error")
59+
.WithDescription(
60+
$"""
61+
You don't have an active Hoyolab account.
62+
Please use the command {accountCommand} to set one up.
63+
"""
64+
)
65+
.WithColor(new NetCord.Color(Color.Red.ToArgb()))
66+
);
67+
});
68+
69+
return;
70+
}
71+
72+
var authorize = new HoyolabAuthorize(
73+
HoyolabUserId: hoyolabAccount.HoyolabUserId,
74+
Token: hoyolabAccount.Token
75+
);
76+
77+
var uid = hoyolabAccount.GameRoleId;
78+
var server = hoyolabAccount.Region;
79+
80+
HoyolabHttpClient.Responses.GcgCardList.Data data;
81+
try
82+
{
83+
data = await hoyolab.GetGcgCardListAsync(
84+
server: server,
85+
roleId: uid,
86+
authorize: authorize
87+
);
88+
}
89+
catch (Exception e)
90+
{
91+
await context.Interaction.ModifyResponseAsync(message =>
92+
{
93+
message.AddEmbeds(new EmbedProperties()
94+
.WithTitle("Error")
95+
.WithDescription(e.Message)
96+
.WithColor(new NetCord.Color(Color.Red.ToArgb()))
97+
);
98+
});
99+
return;
100+
}
101+
102+
var appEmojis = await context.Client.Rest
103+
.GetApplicationEmojisAsync(context.Client.Id);
104+
var emojis = appEmojis.ToImmutableDictionary(x => x.Name);
105+
106+
var winRate = data.CardList
107+
.Where(x => x is
108+
{
109+
CardType: "CardTypeCharacter",
110+
UseCount: > 0
111+
}
112+
&& x.UseCount > minUseCount
113+
)
114+
.Select(x => new
115+
{
116+
x.Id, x.Name, x.Proficiency, x.UseCount,
117+
WinRate = (float)x.Proficiency / x.UseCount
118+
})
119+
.OrderByDescending(x => x.WinRate)
120+
.ToImmutableArray();
121+
122+
var bestWinRate = winRate
123+
.Take(topWinRateCount)
124+
.ToImmutableArray();
125+
126+
var lowestWinRate = winRate
127+
.TakeLast(topWinRateCount)
128+
.Reverse()
129+
.ToImmutableArray();
130+
131+
var topCharacterUseCounts = data.CardList
132+
.Where(x => x.CardType == "CardTypeCharacter")
133+
.Select(x => new
134+
{
135+
x.Id, x.Name, x.UseCount
136+
})
137+
.OrderByDescending(x => x.UseCount)
138+
.Take(topUseCount)
139+
.ToImmutableArray();
140+
141+
var topActionUseCounts = data.CardList
142+
.Where(x => x.CardType != "CardTypeCharacter")
143+
.Select(x => new
144+
{
145+
x.Id, x.Name, x.UseCount
146+
})
147+
.OrderByDescending(x => x.UseCount)
148+
.Take(topUseCount)
149+
.ToImmutableArray();
150+
151+
var bestWinRateStringBuilder = new StringBuilder();
152+
153+
for (var i = 0; i < bestWinRate.Length; i++)
154+
{
155+
var x = bestWinRate[i];
156+
157+
bestWinRateStringBuilder.AppendLine(
158+
$"{i + 1}. {emojis[x.Id.ToString()]} {x.Name} - " +
159+
$"{x.WinRate:P2} " +
160+
$"({x.Proficiency}/{x.UseCount})"
161+
);
162+
}
163+
164+
var lowestWinRateStringBuilder = new StringBuilder();
165+
for (var i = 0; i < lowestWinRate.Length; i++)
166+
{
167+
var x = lowestWinRate[i];
168+
169+
lowestWinRateStringBuilder.AppendLine(
170+
$"{i + 1}. {emojis[x.Id.ToString()]} {x.Name} - " +
171+
$"{x.WinRate:P2} " +
172+
$"({x.Proficiency}/{x.UseCount})"
173+
);
174+
}
175+
176+
var topUseCountsStringBuilder = new StringBuilder();
177+
for (var i = 0; i < topCharacterUseCounts.Length; i++)
178+
{
179+
var card = topCharacterUseCounts[i];
180+
181+
topUseCountsStringBuilder.AppendLine(
182+
$"{i + 1}. {card.Name} - {card.UseCount}"
183+
);
184+
}
185+
186+
var topActionUseCountsStringBuilder = new StringBuilder();
187+
for (var i = 0; i < topActionUseCounts.Length; i++)
188+
{
189+
var card = topActionUseCounts[i];
190+
191+
topActionUseCountsStringBuilder.AppendLine(
192+
$"{i + 1}. {card.Name} - {card.UseCount}"
193+
);
194+
}
195+
196+
await context.Interaction.ModifyResponseAsync(message =>
197+
{
198+
message.AddEmbeds(
199+
new EmbedProperties()
200+
.WithColor(new NetCord.Color(Color.Purple.ToArgb()))
201+
.WithTitle($"{data.Stats.Nickname}")
202+
.AddFields(
203+
new EmbedFieldProperties()
204+
.WithName("Best win rate")
205+
.WithValue(bestWinRateStringBuilder.ToString())
206+
.WithInline(),
207+
new EmbedFieldProperties()
208+
.WithName("Lowest win rate")
209+
.WithValue(lowestWinRateStringBuilder.ToString())
210+
.WithInline()
211+
)
212+
)
213+
.AddEmbeds(
214+
new EmbedProperties()
215+
.WithColor(new NetCord.Color(Color.Purple.ToArgb()))
216+
.AddFields(
217+
new EmbedFieldProperties()
218+
.WithName("Top character use counts")
219+
.WithValue(topUseCountsStringBuilder.ToString())
220+
.WithInline(),
221+
new EmbedFieldProperties()
222+
.WithName("Top action use counts")
223+
.WithValue(topActionUseCountsStringBuilder.ToString())
224+
.WithInline()
225+
)
226+
);
227+
});
228+
}
229+
}

src/GitcgNetCord.MainApp/Modules/Feats/HoyolabGcgModule.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,11 @@ public static void AddHoyolabGcgModule(this IHost host)
1212
description: "Show Genshin Impact TCG information.",
1313
handler: TcgSlashCommand.ExecuteAsync
1414
);
15+
16+
host.AddSlashCommand(
17+
name: "card",
18+
description: "Show Genshin Impact TCG card information.",
19+
handler: CardSlashCommand.ExecuteAsync
20+
);
1521
}
1622
}

src/HoyolabHttpClient/Configuration/HoyolabHttpClientOptions.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22

33
public class HoyolabHttpClientOptions
44
{
5-
public const string ConfigurationSectionName = nameof(HoyolabHttpClientOptions);
5+
public const string ConfigurationSectionName =
6+
nameof(HoyolabHttpClientOptions);
67

78
public HoyolabAuthorize? DefaultAuthorize { get; set; }
89
}

src/HoyolabHttpClient/Extensions/Extensions.cs

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -20,29 +20,9 @@ this IServiceCollection services
2020
.ConfigurePrimaryHttpMessageHandler(ConfigureHandler);
2121

2222
services.AddHttpClient<HoyolabHttpClientService>()
23-
.ConfigureHttpClient(ConfigureClient)
2423
.ConfigurePrimaryHttpMessageHandler(ConfigureHandler);
2524
}
2625

27-
private static void ConfigureClient(
28-
IServiceProvider serviceProvider,
29-
HttpClient client
30-
)
31-
{
32-
var options = serviceProvider
33-
.GetRequiredService<IOptions<HoyolabHttpClientOptions>>();
34-
var defaultAuthorize = options.Value.DefaultAuthorize;
35-
36-
if (defaultAuthorize == null)
37-
return;
38-
39-
HoyolabHttpClientService
40-
.ConfigAuthorizeClient(
41-
client: client,
42-
authorize: defaultAuthorize
43-
);
44-
}
45-
4626
private static HttpMessageHandler ConfigureHandler(
4727
IServiceProvider serviceProvider
4828
)

0 commit comments

Comments
 (0)