Skip to content

Commit 37003cb

Browse files
authored
Merge pull request #105 from Zechiax/v3base
V3 Upgrade
2 parents d1b3842 + fd6ccbf commit 37003cb

28 files changed

+1005
-438
lines changed

.github/workflows/dotnet.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ jobs:
1616
- name: Setup .NET
1717
uses: actions/setup-dotnet@v2
1818
with:
19-
dotnet-version: 7.0.x
19+
dotnet-version: 8.0.x
2020
- name: Restore dependencies
2121
run: dotnet restore
2222
- name: Build

Asterion.Test/SplitExtensionTests.cs

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
using Asterion.Extensions;
2+
3+
namespace Asterion.Test;
4+
5+
[TestFixture]
6+
public class SplitExtensionTests
7+
{
8+
[Test]
9+
public void TestSliceOnEmptyArray()
10+
{
11+
var array = Array.Empty<int>();
12+
13+
var result = array.Split(10).ToArray();
14+
15+
Assert.That(result, Is.Empty);
16+
}
17+
18+
[Test]
19+
public void TestSplitEvenArray()
20+
{
21+
int[] numbers = {1, 2, 3, 4, 5, 6};
22+
var segments = numbers.Split(2).ToArray();
23+
24+
Assert.Multiple(() =>
25+
{
26+
Assert.That(segments, Has.Length.EqualTo(3));
27+
Assert.That(segments[0], Has.Count.EqualTo(2));
28+
Assert.That(segments[0].Array, Is.Not.Null);
29+
Assert.That(segments[0].Array![segments[0].Offset], Is.EqualTo(1));
30+
Assert.That(segments[0].Array![segments[0].Offset + 1], Is.EqualTo(2));
31+
});
32+
}
33+
34+
[Test]
35+
public void TestSplitOddArray()
36+
{
37+
int[] numbers = {1, 2, 3, 4, 5, 6, 7};
38+
var segments = numbers.Split(3).ToArray();
39+
40+
Assert.Multiple(() =>
41+
{
42+
Assert.That(segments, Has.Length.EqualTo(3));
43+
Assert.That(segments[0], Has.Count.EqualTo(3));
44+
Assert.That(segments[0].Array, Is.Not.Null);
45+
Assert.That(segments[0].Array![segments[0].Offset], Is.EqualTo(1));
46+
Assert.That(segments[0].Array![segments[0].Offset + 1], Is.EqualTo(2));
47+
Assert.That(segments[0].Array![segments[0].Offset + 2], Is.EqualTo(3));
48+
49+
// Last segment should have 1 element
50+
Assert.That(segments[2], Has.Count.EqualTo(1));
51+
});
52+
}
53+
}

Asterion/Asterion.cs

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
using Microsoft.Extensions.DependencyInjection;
1212
using Microsoft.Extensions.Logging;
1313
using Modrinth;
14+
using Quartz;
1415
using Serilog;
1516
using RunMode = Discord.Commands.RunMode;
1617

@@ -19,9 +20,12 @@ namespace Asterion;
1920
public class Asterion
2021
{
2122
private readonly IConfiguration _config;
23+
private int _shardId;
2224

23-
public Asterion()
25+
public Asterion(int shardId)
2426
{
27+
_shardId = shardId;
28+
2529
_config = new ConfigurationBuilder()
2630
.SetBasePath(AppContext.BaseDirectory)
2731
.AddJsonFile("config.json", false, true)
@@ -78,12 +82,25 @@ public async Task MainAsync()
7882
await client.LoginAsync(TokenType.Bot, _config.GetValue<string>("token"));
7983
await client.StartAsync();
8084

85+
// We start the stats service after the client has been logged in
86+
// so that we can get the correct guild count
87+
services.GetRequiredService<IBotStatsService>().Initialize();
88+
89+
// We start the scheduler after the client has been logged in
90+
// so that we can get the correct guild count
91+
var scheduler = await services.GetRequiredService<ISchedulerFactory>().GetScheduler();
92+
await scheduler.Start();
93+
8194
// Disconnect from Discord when pressing Ctrl+C
8295
Console.CancelKeyPress += (_, args) =>
8396
{
8497
args.Cancel = true;
98+
8599
logger.LogInformation("{Key} pressed, exiting bot", args.SpecialKey);
86100

101+
logger.LogInformation("Stopping the scheduler");
102+
scheduler.Shutdown(true).Wait();
103+
87104
logger.LogInformation("Logging out from Discord");
88105
client.LogoutAsync().Wait();
89106
logger.LogInformation("Stopping the client");
@@ -94,11 +111,7 @@ public async Task MainAsync()
94111

95112
args.Cancel = false;
96113
};
97-
98-
// We start the stats service after the client has been logged in
99-
// so that we can get the correct guild count
100-
services.GetRequiredService<IBotStatsService>().Initialize();
101-
114+
102115
await Task.Delay(Timeout.Infinite);
103116
}
104117

@@ -140,9 +153,24 @@ private ServiceProvider ConfigureServices()
140153
.AddHttpClient()
141154
.AddDbContext<DataContext>()
142155
.AddSingleton<IBotStatsService, BotStatsService>()
156+
.AddSingleton<ILocalizationService, LocalizationService>()
143157
.AddMemoryCache()
144158
.AddLogging(configure => configure.AddSerilog(dispose: true));
145159

160+
services.AddQuartz(q =>
161+
{
162+
q.UseInMemoryStore();
163+
});
164+
services.AddQuartzHostedService(options =>
165+
{
166+
options.WaitForJobsToComplete = true;
167+
});
168+
169+
services.AddLocalization(options =>
170+
{
171+
options.ResourcesPath = "Resources";
172+
});
173+
146174
if (IsDebug())
147175
services.Configure<LoggerFilterOptions>(options => options.MinLevel = LogLevel.Debug);
148176
else

Asterion/Asterion.csproj

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
<PackageReference Include="Discord.Net" Version="3.13.0" />
1717
<PackageReference Include="Discord.Net.Core" Version="3.13.0" />
1818
<PackageReference Include="Fergun.Interactive" Version="1.7.3" />
19+
<PackageReference Include="Figgle" Version="0.5.1" />
1920
<PackageReference Include="Html2Markdown" Version="6.2.0.3" />
2021
<PackageReference Include="Humanizer.Core" Version="2.14.1" />
2122
<PackageReference Include="LiveChartsCore.SkiaSharpView" Version="2.0.0-rc2" />
@@ -32,6 +33,9 @@
3233
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="8.0.0" />
3334
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="8.0.0" />
3435
<PackageReference Include="Modrinth.Net" Version="3.4.0" />
36+
<PackageReference Include="Quartz" Version="3.8.0" />
37+
<PackageReference Include="Quartz.AspNetCore" Version="3.8.0" />
38+
<PackageReference Include="Quartz.Extensions.DependencyInjection" Version="3.8.0" />
3539
<PackageReference Include="Serilog" Version="3.1.1" />
3640
<PackageReference Include="Serilog.AspNetCore" Version="8.0.0" />
3741
<PackageReference Include="Serilog.Sinks.Console" Version="5.0.1" />
@@ -40,4 +44,19 @@
4044
<PackageReference Include="System.Data.SQLite" Version="1.0.118" />
4145
</ItemGroup>
4246

47+
<ItemGroup>
48+
<EmbeddedResource Update="Resources\Responses\responses.en-US.resx">
49+
<Generator>ResXFileCodeGenerator</Generator>
50+
<LastGenOutput>responses.en-US.Designer.cs</LastGenOutput>
51+
</EmbeddedResource>
52+
</ItemGroup>
53+
54+
<ItemGroup>
55+
<Compile Update="Resources\Responses\responses.en-US.Designer.cs">
56+
<DesignTime>True</DesignTime>
57+
<AutoGen>True</AutoGen>
58+
<DependentUpon>responses.en-US.resx</DependentUpon>
59+
</Compile>
60+
</ItemGroup>
61+
4362
</Project>
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
using System.Globalization;
2+
using Asterion.EmbedBuilders;
3+
using Asterion.Interfaces;
4+
using Discord;
5+
using Discord.Interactions;
6+
using Color = Discord.Color;
7+
8+
namespace Asterion.Common;
9+
10+
public class AsterionInteractionModuleBase : InteractionModuleBase<SocketInteractionContext>
11+
{
12+
protected CultureInfo? CommandCultureInfo { get; set; }
13+
protected ILocalizationService LocalizationService { get; }
14+
public AsterionInteractionModuleBase(ILocalizationService localizationService)
15+
{
16+
LocalizationService = localizationService;
17+
}
18+
19+
protected async Task FollowupWithSearchResultErrorAsync<T>(Services.Modrinth.SearchResult<T> status)
20+
{
21+
if (status.Success)
22+
{
23+
throw new ArgumentException("SearchResult was successful, but was expected to be an error");
24+
}
25+
26+
string title = LocalizationService.Get("Modrinth_Search_Unsuccessful", CommandCultureInfo);
27+
28+
string? description;
29+
switch (status.SearchStatus)
30+
{
31+
case Services.Modrinth.SearchStatus.ApiDown:
32+
description = LocalizationService.Get("Error_ModrinthApiUnavailable", CommandCultureInfo);
33+
title += ". " + LocalizationService.Get("Error_TryAgainLater", CommandCultureInfo);
34+
break;
35+
case Services.Modrinth.SearchStatus.NoResult:
36+
description = LocalizationService.Get("Modrinth_Search_NoResult_WithQuery", CommandCultureInfo, new object[] {status.Query});
37+
break;
38+
case Services.Modrinth.SearchStatus.UnknownError:
39+
description = LocalizationService.Get("Error_Unknown", CommandCultureInfo);
40+
title += ". " + LocalizationService.Get("Error_TryAgainLater", CommandCultureInfo);
41+
break;
42+
default:
43+
throw new ArgumentOutOfRangeException();
44+
}
45+
46+
var embed = CommonEmbedBuilder.GetErrorEmbedBuilder(title, description).Build();
47+
48+
await FollowupAsync(embeds: new[] {embed});
49+
}
50+
51+
public override void BeforeExecute(ICommandInfo cmd)
52+
{
53+
// We currently set US culture for all commands
54+
CommandCultureInfo = CultureInfo.GetCultureInfo("en-US");
55+
56+
base.BeforeExecute(cmd);
57+
}
58+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
using Discord;
2+
using Color = Discord.Color;
3+
4+
namespace Asterion.EmbedBuilders;
5+
6+
public static class CommonEmbedBuilder
7+
{
8+
private static Color _errorColor = Color.Red;
9+
private static Color _warningColor = Color.Orange;
10+
private static Color _infoColor = Color.Blue;
11+
private static Color _successColor = Color.Green;
12+
13+
public static EmbedBuilder GetSuccessEmbedBuilder(string title, string description)
14+
{
15+
return new EmbedBuilder()
16+
.WithTitle(title)
17+
.WithDescription(description)
18+
.WithColor(_successColor)
19+
.WithCurrentTimestamp();
20+
}
21+
22+
public static EmbedBuilder GetErrorEmbedBuilder(string title, string description)
23+
{
24+
return new EmbedBuilder()
25+
.WithTitle(title)
26+
.WithDescription(description)
27+
.WithColor(_errorColor)
28+
.WithCurrentTimestamp();
29+
}
30+
31+
public static EmbedBuilder GetInfoEmbedBuilder(string title, string description)
32+
{
33+
return new EmbedBuilder()
34+
.WithTitle(title)
35+
.WithDescription(description)
36+
.WithColor(_infoColor)
37+
.WithCurrentTimestamp();
38+
}
39+
40+
public static EmbedBuilder GetWarningEmbedBuilder(string title, string description)
41+
{
42+
return new EmbedBuilder()
43+
.WithTitle(title)
44+
.WithDescription(description)
45+
.WithColor(_warningColor)
46+
.WithCurrentTimestamp();
47+
}
48+
}

Asterion/EmbedBuilders/ListEmbedBuilder.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ public static List<Embed> CreateListEmbed(IList<ModrinthEntry> entries)
1616

1717
var currentEntry = 0;
1818

19-
var numberOfEmbeds = entries.Count / 25 + 1;
19+
var numberOfEmbeds = entries.Count / 25 + (entries.Count % 25 > 0 ? 1 : 0);
2020

2121
for (var i = 0; i < numberOfEmbeds; i++)
2222
{
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
namespace Asterion.Extensions;
2+
3+
public static class ArrayExtensions
4+
{
5+
public static IEnumerable<ArraySegment<T>> Split<T>(this T[] array, int blockSize)
6+
{
7+
var offset = 0;
8+
while (offset < array.Length)
9+
{
10+
var remaining = array.Length - offset;
11+
var blockSizeToUse = Math.Min(remaining, blockSize);
12+
yield return new ArraySegment<T>(array, offset, blockSizeToUse);
13+
offset += blockSizeToUse;
14+
}
15+
}
16+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
using System.Globalization;
2+
using Discord.Interactions;
3+
using Microsoft.Extensions.Localization;
4+
5+
namespace Asterion.Interfaces;
6+
7+
public interface ILocalizationService
8+
{
9+
LocalizedString Get(string key);
10+
LocalizedString Get(string key, CultureInfo? cultureInfo);
11+
LocalizedString Get(string key, params object[] parameters);
12+
LocalizedString Get(string key, CultureInfo? cultureInfo, params object[] parameters);
13+
}

Asterion/Modules/BotCommands.cs

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,20 @@
1-
using Discord.Interactions;
1+
using Asterion.Common;
2+
using Asterion.Interfaces;
3+
using Discord.Interactions;
4+
using Discord.WebSocket;
25

36
namespace Asterion.Modules;
47

5-
public class BotCommands : InteractionModuleBase<SocketInteractionContext>
8+
public class BotCommands : AsterionInteractionModuleBase
69
{
10+
#if DEBUG
711
[SlashCommand("ping", "Pings the bot", runMode: RunMode.Async)]
812
public async Task Ping()
913
{
10-
await RespondAsync($"Pong :ping_pong: It took me {Context.Client.Latency}ms to respond to you",
11-
ephemeral: true);
14+
await RespondAsync($"Pong! Latency: {Context.Client.Latency}ms");
15+
}
16+
#endif
17+
public BotCommands(ILocalizationService localizationService) : base(localizationService)
18+
{
1219
}
1320
}

Asterion/Modules/BotManagement.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
11
using Asterion.AutocompleteHandlers;
2+
using Asterion.Common;
23
using Asterion.Interfaces;
34
using Discord.Interactions;
45
using Microsoft.Extensions.DependencyInjection;
56

67
namespace Asterion.Modules;
78

89
[RequireOwner]
9-
public class BotManagement : InteractionModuleBase<SocketInteractionContext>
10+
public class BotManagement : AsterionInteractionModuleBase
1011
{
1112
private readonly IDataService _dataService;
1213

13-
public BotManagement(IServiceProvider serviceProvider)
14+
public BotManagement(IServiceProvider serviceProvider, ILocalizationService localizationService) : base(localizationService)
1415
{
1516
_dataService = serviceProvider.GetRequiredService<IDataService>();
1617
}

0 commit comments

Comments
 (0)