Skip to content

Commit b0fda8a

Browse files
authored
Merge pull request #22 from pk9r/dev
feat select decks
2 parents c702e03 + d534be6 commit b0fda8a

File tree

14 files changed

+345
-109
lines changed

14 files changed

+345
-109
lines changed

.aspire/settings.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"appHostPath": "../src/AppHost/AppHost.csproj"
3+
}

.vscode/settings.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"dotnet.automaticallyCreateSolutionInWorkspace": false
3+
}

GitcgNetCord.sln.DotSettings.user

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,15 @@
33
<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>
44
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AGuildThreadMetadata_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003F72dab619544a61db6fcfa35f1ceeed540e7b87a5f79477e29f7a6c73a485a_003FGuildThreadMetadata_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
55
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AINamedChannel_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003Fc6b34531972538dcb2125236898e9a73bfc3bfb939b67fb2d4f6057bd3498f5_003FINamedChannel_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
6+
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AInteractionCallbackType_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FSourcesCache_003Fc7eff0a055a09ff3f738916309048d5181ed9996c802249d023b1d076c57158_003FInteractionCallbackType_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
7+
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AInteractionCallback_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FSourcesCache_003F569fede2c7ad6896753819e615d3e26a2db7cf15ab5a2eab17c6f4d2c614481_003FInteractionCallback_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
68
<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>
9+
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AOrderBy_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FSourcesCache_003F9aabb2337f87aee16a5178682a1b236f89b636acc2abaa5be5eadd13ec46109f_003FOrderBy_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
710
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003APrivateGuildThread_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003F79a2d5b719ab826f4d81b95fc1963dc92e6dd8f43065b729ed53ae1f797_003FPrivateGuildThread_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
811
<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>
912
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ARestClient_002EChannel_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003F3e62ebbf24ad7ee71524db17f814b1703f198645cce7517bc17122fb38f8f81_003FRestClient_002EChannel_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
1013
<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>
1114
<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>
15+
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ATextDisplayProperties_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FSourcesCache_003F303ada71171edeaa385be4ef807061b6eaa4377f119e6fef55ad713b54b496e_003FTextDisplayProperties_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
1216
<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>
1317
<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>

src/GitcgNetCord.MainApp/Commands/Autocompletes/SharingCodeAutocompleteProvider.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,13 @@ async ValueTask AddAccountDecks()
139139
return;
140140
}
141141

142+
suggestions.Add(
143+
new ApplicationCommandOptionChoiceProperties(
144+
name: "Select decks from your account",
145+
stringValue: "account-decks"
146+
)
147+
);
148+
142149
suggestions.AddRange(
143150
deckListResult.DeckList.Select(CreateSuggestion)
144151
.Where(x => StartsWith(x.Name, keyword) || Contains(x.Name, keyword))
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
using System.Collections.Immutable;
2+
using GitcgNetCord.MainApp.Infrastructure.HoyolabServices;
3+
using GitcgPainter.ImageCreators.Deck;
4+
using NetCord;
5+
using NetCord.Rest;
6+
using NetCord.Services.ComponentInteractions;
7+
using Color = System.Drawing.Color;
8+
9+
namespace GitcgNetCord.MainApp.Commands.Interactions;
10+
11+
public static class SelectDecksComponentInteraction
12+
{
13+
public const string CustomId = "select-decks";
14+
15+
public static async Task ExecuteAsync(
16+
IServiceProvider serviceProvider,
17+
StringMenuInteractionContext context
18+
)
19+
{
20+
await context.Interaction.SendResponseAsync(
21+
InteractionCallback.DeferredMessage()
22+
);
23+
24+
var decoder = serviceProvider
25+
.GetRequiredService<HoyolabDecoder>();
26+
var deckImageCreatorCollection = serviceProvider
27+
.GetRequiredService<DeckImageCreatorCollection>();
28+
29+
var deckImageCreator = deckImageCreatorCollection.GameBackground;
30+
31+
var appEmojis = await context.Client.Rest
32+
.GetApplicationEmojisAsync(context.Client.Id);
33+
var emojis = appEmojis.ToImmutableDictionary(x => x.Name);
34+
35+
var decks = await Task.WhenAll(
36+
context.SelectedValues.Select(async (s, i) =>
37+
{
38+
var result = await decoder.DecodeAsync(s);
39+
40+
var deckEmoji = result.Deck.RoleCards
41+
.Select(x => emojis[x.Basic.ItemId.ToString()]);
42+
43+
var image = await deckImageCreator.CreateImageAsync(result.Deck);
44+
45+
return new
46+
{
47+
RoleEmojis = string.Join(" ", deckEmoji),
48+
RoleNames = string.Join(
49+
separator: ", ",
50+
values: result.Deck.RoleCards.Select(x => x.Basic.Name)
51+
),
52+
SharingCode = s,
53+
FileName = $"deck{i + 1}.png",
54+
Image = image
55+
};
56+
})
57+
);
58+
59+
await context.Interaction.ModifyResponseAsync(message => message
60+
.WithFlags(MessageFlags.IsComponentsV2)
61+
.AddAttachments(
62+
decks.Select(x => new AttachmentProperties(x.FileName, x.Image))
63+
)
64+
.AddComponents(
65+
decks.Select(s => new ComponentContainerProperties()
66+
.WithAccentColor(new NetCord.Color(Color.Purple.ToArgb()))
67+
.AddComponents(
68+
new TextDisplayProperties(
69+
$"""
70+
## {s.RoleEmojis} - {s.RoleNames}
71+
"""
72+
),
73+
new MediaGalleryProperties().AddItems(
74+
new MediaGalleryItemProperties(
75+
new ComponentMediaProperties($"attachment://{s.FileName}"))
76+
),
77+
new TextDisplayProperties(
78+
$"`{s.SharingCode}`"
79+
),
80+
new ActionRowProperties().AddButtons(
81+
CopySharingCodeComponentInteraction
82+
.CreateCopySharingCodeButton(s.SharingCode)
83+
)
84+
)
85+
)
86+
)
87+
);
88+
89+
foreach (var deck in decks)
90+
await deck.Image.DisposeAsync();
91+
}
92+
}

src/GitcgNetCord.MainApp/Commands/Slash/CardSlashCommand.cs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -234,8 +234,7 @@ await context.Interaction.ModifyResponseAsync(message =>
234234
.WithName("Total wins")
235235
.WithValue($"{winCount} ({winRate:P2})")
236236
.WithInline()
237-
)
238-
,
237+
),
239238
new EmbedProperties()
240239
.WithColor(new NetCord.Color(Color.Purple.ToArgb()))
241240
.AddFields(
@@ -255,8 +254,7 @@ await context.Interaction.ModifyResponseAsync(message =>
255254
? $"with use count > {minUseCountValue} ({minUseCount} total games)."
256255
: $"with use count > {minUseCountValue}.")
257256
)
258-
)
259-
,
257+
),
260258
new EmbedProperties()
261259
.WithColor(new NetCord.Color(Color.Purple.ToArgb()))
262260
.AddFields(

src/GitcgNetCord.MainApp/Commands/Slash/DeckSlashCommand.cs

Lines changed: 115 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
using System.Collections.Immutable;
2+
using System.Text;
23
using GitcgNetCord.MainApp.Commands.Autocompletes;
34
using GitcgNetCord.MainApp.Commands.Interactions;
5+
using GitcgNetCord.MainApp.Entities.Repositories;
46
using GitcgNetCord.MainApp.Enums;
57
using GitcgNetCord.MainApp.Infrastructure.HoyolabServices;
68
using GitcgPainter.ImageCreators.Deck;
79
using GitcgPainter.ImageCreators.Deck.Abstractions;
10+
using HoyolabHttpClient;
811
using NetCord;
912
using NetCord.Rest;
1013
using NetCord.Services.ApplicationCommands;
@@ -47,16 +50,19 @@ public static async Task ExecuteAsync(
4750
string lang = "en-us"
4851
)
4952
{
50-
if (sharingCode == "hoyolab-accounts")
53+
switch (sharingCode)
5154
{
52-
await HoyolabAccountsSlashCommand
53-
.ExecuteAsync(serviceProvider, context);
54-
55-
return;
55+
case "hoyolab-accounts":
56+
await HoyolabAccountsSlashCommand
57+
.ExecuteAsync(serviceProvider, context);
58+
return;
59+
case "account-decks":
60+
await ExecuteAccountDecksAsync(serviceProvider, context);
61+
return;
5662
}
5763

5864
await context.Interaction.SendResponseAsync(
59-
callback: InteractionCallback.DeferredMessage()
65+
callback: InteractionCallback.DeferredMessage(MessageFlags.IsComponentsV2)
6066
);
6167

6268
var decodeResult = await decoder.DecodeAsync(
@@ -95,8 +101,6 @@ await context.Interaction.ModifyResponseAsync(message => message
95101
values: deck.RoleCards.Select(x => x.Basic.Name)
96102
);
97103

98-
var label = $"{deckEmojisString} - {roleCardsString}";
99-
100104
if (type.IsCreateImageType())
101105
{
102106
IDeckImageCreationService deckImageCreator = type switch
@@ -110,33 +114,41 @@ await context.Interaction.ModifyResponseAsync(message => message
110114
_ => throw new NotImplementedException()
111115
};
112116

113-
var pngBytes = await deckImageCreator.CreateImageAsync(deck);
117+
await using var stream = await deckImageCreator.CreateImageAsync(deck);
114118

115119
const string deckImageFileName = "deck.png";
116120
const string deckImageUrl = $"attachment://{deckImageFileName}";
117121

118-
using var memoryStream = new MemoryStream(pngBytes);
119-
120122
await context.Interaction.ModifyResponseAsync(message => message
121-
.AddAttachments(
122-
new AttachmentProperties(
123-
fileName: deckImageFileName,
124-
stream: memoryStream)
125-
)
126-
.AddEmbeds(new EmbedProperties()
127-
.WithTitle(label)
128-
.AddFields(
129-
new EmbedFieldProperties()
130-
.WithName("Sharing code")
131-
.WithValue(sharingCode)
132-
)
133-
.WithImage(new EmbedImageProperties(deckImageUrl))
134-
.WithColor(new NetCord.Color(Color.Purple.ToArgb()))
135-
)
136-
.AddComponents(new ActionRowProperties().AddButtons(
137-
CopySharingCodeComponentInteraction
138-
.CreateCopySharingCodeButton(sharingCode)
123+
.WithFlags(MessageFlags.IsComponentsV2)
124+
.AddAttachments(new AttachmentProperties(
125+
fileName: deckImageFileName,
126+
stream: stream
139127
))
128+
.AddComponents(
129+
[
130+
new ComponentContainerProperties()
131+
.WithAccentColor(new NetCord.Color(Color.Purple.ToArgb()))
132+
.AddComponents(
133+
new TextDisplayProperties(
134+
$"""
135+
## {deckEmojisString} - {roleCardsString}
136+
"""
137+
),
138+
new MediaGalleryProperties().AddItems(
139+
new MediaGalleryItemProperties(
140+
new ComponentMediaProperties(deckImageUrl))
141+
),
142+
new TextDisplayProperties(
143+
$"`{sharingCode}`"
144+
),
145+
new ActionRowProperties([
146+
CopySharingCodeComponentInteraction
147+
.CreateCopySharingCodeButton(sharingCode)
148+
])
149+
)
150+
]
151+
)
140152
);
141153

142154
return;
@@ -147,4 +159,77 @@ await context.Interaction.ModifyResponseAsync(message => message
147159
throw new NotImplementedException();
148160
}
149161
}
150-
}
162+
163+
private static async Task ExecuteAccountDecksAsync(
164+
IServiceProvider serviceProvider,
165+
ApplicationCommandContext context
166+
)
167+
{
168+
var hoyolab = serviceProvider
169+
.GetRequiredService<HoyolabHttpClientService>();
170+
var deckAccountService = serviceProvider
171+
.GetRequiredService<HoyolabDeckAccountService>();
172+
var activeHoyolabAccountService = serviceProvider
173+
.GetRequiredService<ActiveHoyolabAccountService>();
174+
175+
await context.Interaction.SendResponseAsync(
176+
callback: InteractionCallback.DeferredMessage(MessageFlags.Ephemeral)
177+
);
178+
179+
var hoyolabAccount = await activeHoyolabAccountService
180+
.GetActiveHoyolabAccountAsync(
181+
discordUserId: context.User.Id
182+
);
183+
184+
if (hoyolabAccount == null)
185+
return;
186+
187+
var authorize = new HoyolabAuthorize(
188+
HoyolabUserId: hoyolabAccount.HoyolabUserId,
189+
Token: hoyolabAccount.Token
190+
);
191+
192+
var deckListResult = await deckAccountService
193+
.GetDeckListAsync(
194+
authorize: authorize,
195+
hoyolabAccount.GameRoleId,
196+
hoyolabAccount.Region
197+
);
198+
199+
var appEmojis = await context.Client.Rest
200+
.GetApplicationEmojisAsync(context.Client.Id);
201+
var emojis = appEmojis.ToImmutableDictionary(x => x.Name);
202+
203+
await context.Interaction.ModifyResponseAsync(message => message
204+
.AddComponents(
205+
new StringMenuProperties(
206+
customId: SelectDecksComponentInteraction.CustomId
207+
).AddOptions(
208+
deckListResult.DeckList.Select((deck, index) =>
209+
{
210+
var rolesDisplay = string.Join(
211+
separator: ", ",
212+
deck.AvatarCards.Select(y => y.Name)
213+
);
214+
215+
var emoji = EmojiProperties.Custom(
216+
emojis[deck.AvatarCards.First().Id.ToString()].Id);
217+
218+
return new StringMenuSelectOptionProperties(
219+
label: LimitLength($"{index + 1}. {deck.Name}"),
220+
value: deck.ShareCode
221+
)
222+
.WithDescription(LimitLength(rolesDisplay))
223+
.WithEmoji(emoji);
224+
})
225+
)
226+
.WithMaxValues(Math.Min(deckListResult.DeckList.Count, 5))
227+
)
228+
);
229+
}
230+
231+
private static string LimitLength(ReadOnlySpan<char> name)
232+
{
233+
return name.Length <= 100 ? name.ToString() : $"{name[..97]}...";
234+
}
235+
}

0 commit comments

Comments
 (0)