Skip to content

Commit daabe0d

Browse files
committed
Add tcg-card slash command and refactor services
Introduced a new `tcg-card` slash command for fetching and displaying Genius Invokation card details. Added autocomplete support via `CardAutocompleteProvider`. Refactored `HoyolabHttpClientService` to improve modularity, including reusable query string helpers and type-safe method signatures. Added `GetActionSkillAsync` and renamed `GetActionsAsync` to `GetCardActionsAsync` for clarity. Added `HoyolabCardActionService` with caching for card action data. Updated dependency injection to include the new service. Simplified `HoyolabResponseBase<T>` and `Response` for consistency. Reorganized `using` directives and removed unused imports across files to improve code organization.
1 parent bc278b3 commit daabe0d

12 files changed

Lines changed: 353 additions & 36 deletions

File tree

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
using GitcgNetCord.MainApp.Infrastructure.HoyolabServices;
2+
using NetCord;
3+
using NetCord.Rest;
4+
using NetCord.Services.ApplicationCommands;
5+
6+
namespace GitcgNetCord.MainApp.Commands.Autocompletes;
7+
8+
public class CardAutocompleteProvider(
9+
HoyolabCardActionService cardActionService
10+
) : IAutocompleteProvider<AutocompleteInteractionContext>
11+
{
12+
public async ValueTask<
13+
IEnumerable<ApplicationCommandOptionChoiceProperties>?
14+
> GetChoicesAsync(
15+
ApplicationCommandInteractionDataOption option,
16+
AutocompleteInteractionContext context
17+
)
18+
{
19+
var keyword = option.Value!;
20+
21+
var cardActions = await cardActionService.GetCardActionsAsync("vi-vn");
22+
23+
var suggestions = cardActions.Actions
24+
.Where(x => StartsWith(x.Basic.Name, keyword) || Contains(x.Basic.Name, keyword))
25+
.OrderBy(x => StartsWith(x.Basic.Name, keyword) ? 0 : 1)
26+
.Select(x => new ApplicationCommandOptionChoiceProperties(x.Basic.Name, x.Basic.ItemId));
27+
28+
return suggestions.Take(25);
29+
30+
bool StartsWith(string x, string k)
31+
{
32+
return x.StartsWith(
33+
value: k,
34+
comparisonType: StringComparison.OrdinalIgnoreCase
35+
);
36+
}
37+
38+
bool Contains(string x, string k)
39+
{
40+
return x.Contains(
41+
value: k,
42+
comparisonType: StringComparison.OrdinalIgnoreCase
43+
);
44+
}
45+
}
46+
}

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

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,15 @@
1-
using System.Collections.Immutable;
2-
using GitcgNetCord.MainApp.Commands.Autocompletes;
1+
using GitcgNetCord.MainApp.Commands.Autocompletes;
32
using GitcgNetCord.MainApp.Commands.Interactions;
43
using GitcgNetCord.MainApp.Entities.Repositories;
54
using GitcgNetCord.MainApp.Enums;
65
using GitcgNetCord.MainApp.Extensions;
76
using GitcgNetCord.MainApp.Infrastructure.HoyolabServices;
87
using GitcgNetCord.MainApp.Models;
9-
using GitcgPainter.ImageCreators.Deck.Abstractions;
108
using HoyolabHttpClient;
119
using NetCord;
1210
using NetCord.Rest;
1311
using NetCord.Services.ApplicationCommands;
12+
using System.Collections.Immutable;
1413
using Color = System.Drawing.Color;
1514
using IDeckImageCreationService = GitcgSharp.Shared.ImageCreators.Deck.Abstractions.IDeckImageCreationService;
1615

@@ -63,7 +62,7 @@ await HoyolabAccountsSlashCommand
6362
}
6463

6564
await context.Interaction.SendResponseAsync(
66-
callback: InteractionCallback.DeferredMessage(MessageFlags.IsComponentsV2)
65+
callback: InteractionCallback.DeferredMessage()
6766
);
6867

6968
var decodeResult = await decoder.DecodeAsync(
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
using GitcgNetCord.MainApp.Commands.Autocompletes;
2+
using GitcgNetCord.MainApp.Extensions;
3+
using GitcgNetCord.MainApp.Infrastructure.HoyolabServices;
4+
using HoyolabHttpClient;
5+
using NetCord;
6+
using NetCord.Rest;
7+
using NetCord.Services.ApplicationCommands;
8+
using System.Collections.Immutable;
9+
using System.Text.RegularExpressions;
10+
using Color = System.Drawing.Color;
11+
12+
namespace GitcgNetCord.MainApp.Commands.Slash;
13+
14+
public partial class TcgCardSlashCommand
15+
{
16+
public static async Task ExecuteAsync(
17+
HoyolabHttpClientService hoyolab,
18+
IServiceProvider serviceProvider,
19+
ApplicationCommandContext context,
20+
[
21+
SlashCommandParameter(
22+
Description = "Card ID.",
23+
AutocompleteProviderType = typeof(CardAutocompleteProvider)
24+
)
25+
]
26+
int cardId,
27+
[
28+
SlashCommandParameter(
29+
Description = "Language. Default: `en-us`.",
30+
AutocompleteProviderType = typeof(LanguageAutocompleteProvider)
31+
)
32+
]
33+
string lang = "en-us"
34+
)
35+
{
36+
var cardActionService = serviceProvider
37+
.GetRequiredService<HoyolabCardActionService>();
38+
39+
await context.Interaction.SendResponseAsync(
40+
callback: InteractionCallback.DeferredMessage()
41+
);
42+
43+
HoyolabHttpClient.Responses.ActionSkill.Data data;
44+
45+
try
46+
{
47+
data = await hoyolab.GetActionSkillAsync(cardId, lang);
48+
}
49+
catch (Exception e)
50+
{
51+
await context.Interaction.ModifyResponseAsync(message =>
52+
{
53+
//message.WithFlags(MessageFlags.IsComponentsV2);
54+
message.AddEmbeds(new EmbedProperties()
55+
.WithTitle("Error")
56+
.WithDescription(e.Message)
57+
.WithColor(Color.Red.ToNetCordColor())
58+
);
59+
});
60+
return;
61+
}
62+
63+
var appEmojis = await context.Client.Rest
64+
.GetApplicationEmojisAsync(context.Client.Id);
65+
var emojis = appEmojis.ToImmutableDictionary(x => x.Name);
66+
67+
var cardActions = await cardActionService
68+
.GetCardActionsAsync(lang);
69+
var action = cardActions.Actions
70+
.First(x => x.Basic.ItemId == cardId);
71+
72+
await context.Interaction.ModifyResponseAsync(message =>
73+
{
74+
message.WithFlags(MessageFlags.IsComponentsV2);
75+
message.AddComponents([new ComponentContainerProperties()
76+
.WithAccentColor(Color.Purple.ToNetCordColor())
77+
.AddComponents(
78+
new TextDisplayProperties(
79+
$"""
80+
## {GetName(cardId)}
81+
"""
82+
),
83+
new MediaGalleryProperties().AddItems(
84+
new MediaGalleryItemProperties(
85+
new ComponentMediaProperties(action.Basic.Icon))
86+
),
87+
new TextDisplayProperties(
88+
$"{GetDesc(data.Desc)}"
89+
)
90+
)
91+
]);
92+
});
93+
94+
return;
95+
96+
string GetName(int cardId)
97+
{
98+
return GetDesc(
99+
$"{data.Cost1Raw}" +
100+
$"<img src='{data.Cost2TypeIcon}' /> - " +
101+
$"{action.Basic.Name}"
102+
);
103+
}
104+
105+
string GetDesc(string desc)
106+
{
107+
var result = desc;
108+
109+
result = result.Replace("\\n", "\n");
110+
result = result.Replace(
111+
"<img src='https://act-webstatic.hoyoverse.com/hk4e/e20200928calculate/item_icon/67c7f71f/e25f04745615df9e779831a1c1354e38.png' />",
112+
emojis["food"].ToString()
113+
);
114+
result = result.Replace(
115+
"<img src='https://act-webstatic.hoyoverse.com/hk4e/e20200928calculate/item_icon/67c7f71f/d92127a21b942663e6ed0717cef1086e.png' />",
116+
emojis["location"].ToString()
117+
);
118+
result = result.Replace(
119+
"<img src='https://act-webstatic.hoyoverse.com/hk4e/e20200928calculate/item_icon/67c7f71f/377c4198e3072e9c68066736be5b790c.png' />",
120+
emojis["item"].ToString()
121+
);
122+
result = result.Replace(
123+
"<img src='https://act-webstatic.hoyoverse.com/hk4e/e20200928calculate/item_icon/67c7f71f/1643452964b58e2e69e64e2b5d3b5878.png' />",
124+
emojis["catalyst"].ToString()
125+
);
126+
result = result.Replace(
127+
"<img src='https://act-webstatic.hoyoverse.com/hk4e/e20200928calculate/item_icon/67c7f71f/49acb071b0d634ded17cb788d9f520ed.png' />",
128+
emojis["weapon"].ToString()
129+
);
130+
result = result.Replace(
131+
"<img src='https://act-webstatic.hoyoverse.com/hk4e/e20200928calculate/item_icon/67c7f719/e1430a8775eb864ed0617b7ef516e608.png' />",
132+
emojis["physical_dmg"].ToString()
133+
);
134+
result = result.Replace(
135+
"<img src='https://act-webstatic.hoyoverse.com/hk4e/e20200928calculate/item_icon/67c7f719/8401f5013a7c381edb6cd088b207bb9f.png' />",
136+
emojis["omni"].ToString()
137+
);
138+
result = result.Replace(
139+
"<img src='https://act-webstatic.hoyoverse.com/hk4e/e20200928calculate/' />",
140+
emojis["aligned"].ToString()
141+
);
142+
result = result.Replace(
143+
"<img src='https://act-webstatic.hoyoverse.com/hk4e/e20200928calculate/item_icon/67c7f719/39de35b178b080a090ac95622bbbf0df.png' />",
144+
emojis["unaligned"].ToString()
145+
);
146+
147+
while (ColorTagRegex().IsMatch(result))
148+
{
149+
result = ColorTagRegex().Replace(result, "**$1**");
150+
}
151+
152+
return result;
153+
}
154+
}
155+
156+
[
157+
GeneratedRegex(
158+
pattern: @"<color\b[^>]*>(.*?)</color>",
159+
options: RegexOptions.IgnoreCase | RegexOptions.Singleline
160+
)
161+
]
162+
private static partial Regex ColorTagRegex();
163+
}

src/GitcgNetCord.MainApp/Extensions/Extensions.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ this IServiceCollection services
6464
{
6565
services.AddHoyolabHttpClient();
6666
services.AddSingleton<HoyolabCardRoleService>();
67+
services.AddSingleton<HoyolabCardActionService>();
6768
services.AddSingleton<HoyolabDecoder>();
6869
services.AddSingleton<HoyolabDeckAccountService>();
6970
services.AddSingleton<HoyolabGcgBasicInfoService>();
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
using HoyolabHttpClient;
2+
using HoyolabHttpClient.Responses.CardActions;
3+
using Microsoft.Extensions.Caching.Hybrid;
4+
5+
namespace GitcgNetCord.MainApp.Infrastructure.HoyolabServices;
6+
7+
public class HoyolabCardActionService(
8+
HoyolabCardRoleService cardRoleService,
9+
HoyolabHttpClientService hoyolab,
10+
HybridCache cache
11+
)
12+
{
13+
public async Task<Data>
14+
GetCardActionsAsync(string lang = "en-us")
15+
{
16+
var cacheKey = $"card-action:{lang}";
17+
var cached = await cache
18+
.GetOrCreateAsync(
19+
key: cacheKey,
20+
factory: FetchAsync
21+
);
22+
23+
return cached;
24+
25+
async ValueTask<Data> FetchAsync(
26+
CancellationToken cancellationToken = default
27+
)
28+
{
29+
var roleData = await cardRoleService.GetCardRolesAsync(lang);
30+
var roleIds = roleData.Roles
31+
.Select(x => x.Basic.ItemId);
32+
33+
var cardActions = await hoyolab
34+
.GetCardActionsAsync(roleIds, lang);
35+
return cardActions;
36+
}
37+
}
38+
}

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,5 +31,17 @@ public static void AddHoyolabGcgModule(this IHost host)
3131
InteractionContextType.DMChannel
3232
]
3333
);
34+
35+
host.AddSlashCommand(
36+
name: "tcg-card",
37+
description: "Information about a specific Genius Invokation card.",
38+
handler: TcgCardSlashCommand.ExecuteAsync,
39+
contexts:
40+
[
41+
InteractionContextType.Guild,
42+
InteractionContextType.BotDMChannel,
43+
InteractionContextType.DMChannel
44+
]
45+
);
3446
}
3547
}

src/HoyolabHttpClient/Extensions/HoyolabServiceExtension.cs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
1-
using System.Threading.Tasks;
2-
using HoyolabHttpClient.Models;
3-
using HoyolabHttpClient.Responses.Skills;
1+
using HoyolabHttpClient.Models;
2+
using System.Threading.Tasks;
43

54
namespace HoyolabHttpClient.Extensions;
65

76
public static class HoyolabServiceExtension
87
{
9-
public static Task<Data>
8+
public static Task<Responses.Skills.Data>
109
GetRoleSkillAsync(
1110
this HoyolabHttpClientService hoyolabService,
1211
Role role,

0 commit comments

Comments
 (0)