Skip to content

Commit

Permalink
Merge pull request #174 from Zechiax/feature/filterByLoader
Browse files Browse the repository at this point in the history
Filter by loader type
  • Loading branch information
Zechiax authored Nov 25, 2024
2 parents 7209e82 + 094f143 commit a177295
Show file tree
Hide file tree
Showing 8 changed files with 194 additions and 24 deletions.
10 changes: 8 additions & 2 deletions Asterion/Database/DataContext.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Asterion.Database.Models;
using System.Text.Json;
using Asterion.Database.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.Extensions.Logging;
Expand Down Expand Up @@ -52,9 +53,14 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
modelBuilder.Entity<GuildSettings>().Property(p => p.MessageStyle).HasDefaultValue(MessageStyle.Full);
modelBuilder.Entity<GuildSettings>().Property(p => p.ChangelogStyle).HasDefaultValue(ChangelogStyle.PlainText);
modelBuilder.Entity<GuildSettings>().Property(p => p.ChangeLogMaxLength).HasDefaultValue(2000);

modelBuilder.Entity<ModrinthEntry>()
.Property(e => e.ReleaseFilter)
.HasDefaultValue(ReleaseType.Alpha | ReleaseType.Beta | ReleaseType.Release);

// Ignore the LoaderFilter property
modelBuilder.Entity<ModrinthEntry>()
.Ignore(e => e.LoaderFilter);
}

}
15 changes: 15 additions & 0 deletions Asterion/Database/Models/ModrinthEntry.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Text.Json;

namespace Asterion.Database.Models;

Expand All @@ -26,8 +27,22 @@ public class ModrinthEntry
[Required] public DateTime Created { get; set; }

[Required] public ReleaseType ReleaseFilter { get; set; } = ReleaseType.Alpha | ReleaseType.Beta | ReleaseType.Release;

[NotMapped]
public string[]? LoaderFilter
{
get => SerializedLoaderFilter == null
? null
: JsonSerializer.Deserialize<string[]>(SerializedLoaderFilter);
set => SerializedLoaderFilter = value == null
? null
: JsonSerializer.Serialize(value);
}

public string? SerializedLoaderFilter { get; private set; }
}


[Flags]
public enum ReleaseType
{
Expand Down
1 change: 1 addition & 0 deletions Asterion/Interfaces/IDataService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -136,4 +136,5 @@ public Task<bool> UpdateModrinthProjectAsync(string projectId, string? newVersio
public Task<bool> SetPingRoleAsync(ulong guildId, ulong? roleId, string? projectId = null);
public Task<ulong?> GetPingRoleIdAsync(ulong guildId, string? projectId = null);
public Task<bool> SetReleaseFilterAsync(ulong entryId, ReleaseType releaseType);
public Task<bool> SetLoaderFilterAsync(ulong entryEntryId, string[]? newLoaderFilter);
}
28 changes: 28 additions & 0 deletions Asterion/Migrations/20241123122729_addloadertype_entry.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using Microsoft.EntityFrameworkCore.Migrations;

#nullable disable

namespace Asterion.Migrations
{
/// <inheritdoc />
public partial class addloadertype_entry : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "SerializedLoaderFilter",
table: "ModrinthEntries",
type: "TEXT",
nullable: true);
}

/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "SerializedLoaderFilter",
table: "ModrinthEntries");
}
}
}
5 changes: 4 additions & 1 deletion Asterion/Migrations/DataContextModelSnapshot.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ partial class DataContextModelSnapshot : ModelSnapshot
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "8.0.8");
modelBuilder.HasAnnotation("ProductVersion", "9.0.0");

modelBuilder.Entity("Asterion.Database.Models.Array", b =>
{
Expand Down Expand Up @@ -149,6 +149,9 @@ protected override void BuildModel(ModelBuilder modelBuilder)
.HasColumnType("INTEGER")
.HasDefaultValue(7);

b.Property<string>("SerializedLoaderFilter")
.HasColumnType("TEXT");

b.HasKey("EntryId");

b.HasIndex("ArrayId");
Expand Down
127 changes: 106 additions & 21 deletions Asterion/Modules/EntryInteractionModule.cs
Original file line number Diff line number Diff line change
@@ -1,17 +1,23 @@
using Asterion.Common;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using Asterion.Common;
using Asterion.Interfaces;
using Discord;
using Discord.Interactions;
using System.Threading.Tasks;
using Asterion.Attributes;
using Asterion.AutocompleteHandlers;
using Asterion.Database.Models;
using Modrinth;

namespace Asterion.Modules
{
[RequireUserPermission(GuildPermission.Administrator, Group = "ManageSubs")]
[DoManageSubsRoleCheck(Group = "ManageSubs")]
public class EntryInteractionModule(ILocalizationService localizationService, IDataService dataService)
public class EntryInteractionModule(
ILocalizationService localizationService,
IDataService dataService,
IModrinthClient modrinthClient)
: AsterionInteractionModuleBase(localizationService)
{
public static Embed CreateModrinthEntryEmbed(ModrinthEntry entry, ReleaseType releaseFilter)
Expand All @@ -20,20 +26,24 @@ public static Embed CreateModrinthEntryEmbed(ModrinthEntry entry, ReleaseType re
.WithTitle("Modrinth Entry Information")
.WithDescription("Here is the information about the Modrinth entry:")
.AddField("📂 Project ID", $"`{entry.ProjectId}`", false)
.AddField("📢 Custom Update Channel",
entry.CustomUpdateChannel.HasValue ? MentionUtils.MentionChannel(entry.CustomUpdateChannel.Value) : "None",
.AddField("📢 Custom Update Channel",
entry.CustomUpdateChannel.HasValue
? MentionUtils.MentionChannel(entry.CustomUpdateChannel.Value)
: "None",
true)
.AddField("📜 Custom Ping Role",
entry.CustomPingRole.HasValue ? MentionUtils.MentionRole(entry.CustomPingRole.Value) : "None",
.AddField("📜 Custom Ping Role",
entry.CustomPingRole.HasValue ? MentionUtils.MentionRole(entry.CustomPingRole.Value) : "None",
false)
.AddField("🔖 Release Filter", releaseFilter.ToString(), true)
.AddField("🕒 Created", TimestampTag.FromDateTime(entry.Created).ToString(), true)
.AddField("🔍 Loader Filter",
entry.LoaderFilter == null ? "None" : string.Join(", ", entry.LoaderFilter), true)
.AddField("🕒 Created", TimestampTag.FromDateTime(entry.Created).ToString(), false)
.WithColor(Color.Blue)
.WithCurrentTimestamp()
.Build();
}
private static MessageComponent CreateReleaseFilterComponent(string projectId, ReleaseType releaseFilter)

private static SelectMenuBuilder CreateReleaseFilterComponent(string projectId, ReleaseType releaseFilter)
{
var selectMenu = new SelectMenuBuilder()
.WithCustomId($"release_filter:{projectId}")
Expand All @@ -47,17 +57,36 @@ private static MessageComponent CreateReleaseFilterComponent(string projectId, R
.AddOption("Release", ((int)ReleaseType.Release).ToString(), "Include Release",
isDefault: releaseFilter.HasFlag(ReleaseType.Release));

return new ComponentBuilder()
.WithSelectMenu(selectMenu)
.Build();
return selectMenu;
}


private static SelectMenuBuilder CreateLoaderFilterComponent(string projectId, string[] supportedLoaders,
string[]? currentLoaderFilter)
{
var selectMenu = new SelectMenuBuilder()
.WithCustomId($"loader_filter:{projectId}")
.WithPlaceholder("Select loader(s) to filter - currently ALL")
.WithMinValues(0)
.WithMaxValues(supportedLoaders.Length);

// Add options for each supported loader
foreach (var loader in supportedLoaders)
{
selectMenu.AddOption(loader, loader, $"Include {loader} loader",
isDefault: currentLoaderFilter != null && currentLoaderFilter.Contains(loader));
}

return selectMenu;
}

// Interaction command to display the entry info and provide a dropdown
[SlashCommand("entry", "Displays entry information and allows selection of release type filters.")]
public async Task ShowEntryAsync([Summary("project_id")] [Autocomplete(typeof(SubscribedIdAutocompletionHandler))] string projectId)
public async Task ShowEntryAsync(
[Summary("project_id")] [Autocomplete(typeof(SubscribedIdAutocompletionHandler))]
string projectId)
{
await DeferAsync();

// Retrieve entry data using your custom connector
var entry = await dataService.GetModrinthEntryAsync(Context.Guild.Id, projectId);
if (entry == null)
Expand All @@ -69,12 +98,19 @@ public async Task ShowEntryAsync([Summary("project_id")] [Autocomplete(typeof(Su
// Create embed with entry details
var embed = CreateModrinthEntryEmbed(entry, entry.ReleaseFilter);


var components = new ComponentBuilder();
// Create a SelectMenu for ReleaseType filter
var component = CreateReleaseFilterComponent(projectId, entry.ReleaseFilter);
var releaseFilterComponent = CreateReleaseFilterComponent(projectId, entry.ReleaseFilter);
components.WithSelectMenu(releaseFilterComponent, 0);

// Create a SelectMenu for Loader filter
var project = await modrinthClient.Project.GetAsync(projectId);
var supportedLoaders = project.Loaders;
var loaderFilterComponent = CreateLoaderFilterComponent(projectId, supportedLoaders, entry.LoaderFilter);
components.WithSelectMenu(loaderFilterComponent, 1);

// Send the embed with the dropdown menu
await FollowupAsync(embed: embed, components: component);
await FollowupAsync(embed: embed, components: components.Build());
}

[ComponentInteraction("release_filter:*")]
Expand Down Expand Up @@ -113,16 +149,65 @@ public async Task HandleReleaseFilterSelectionAsync(string projectId, string[] s
// Use the static method to generate the updated embed
var updatedEmbed = CreateModrinthEntryEmbed(entry, newReleaseFilter);

var supportedLoaders = (await modrinthClient.Project.GetAsync(projectId)).Loaders;

// Update the original message with the new embed
await ModifyOriginalResponseAsync(msg => {
await ModifyOriginalResponseAsync(msg =>
{
msg.Embed = updatedEmbed;
msg.Components = CreateReleaseFilterComponent(projectId, newReleaseFilter);
msg.Components = new ComponentBuilder()
.WithSelectMenu(CreateReleaseFilterComponent(projectId, newReleaseFilter))
.WithSelectMenu(CreateLoaderFilterComponent(projectId, supportedLoaders, entry.LoaderFilter))
.Build();
});

// Optionally, acknowledge the selection change in a temporary message
await FollowupAsync("Release filter updated successfully!", ephemeral: true);
}

[ComponentInteraction("loader_filter:*")]
public async Task HandleLoaderFilterSelectionAsync(string projectId, string[] selectedValues)
{
// Defer the interaction to avoid timeout issues
await DeferAsync(ephemeral: true);

// Retrieve the entry using projectId
var entry = await dataService.GetModrinthEntryAsync(Context.Guild.Id, projectId);
if (entry == null)
{
await FollowupAsync("Entry not found.", ephemeral: true);
return;
}

// Convert selectedValues back into the Loader filter array
string[]? newLoaderFilter = null;
if (selectedValues.Length > 0)
{
newLoaderFilter = selectedValues;
}

// Update the loader filter in the database
await dataService.SetLoaderFilterAsync(entry.EntryId, newLoaderFilter);
entry.LoaderFilter = newLoaderFilter;

// Use the static method to generate the updated embed
var updatedEmbed = CreateModrinthEntryEmbed(entry, entry.ReleaseFilter);

// TODO: Maybe parse the supported loaders from the selected project instead of fetching it again
var supportedLoaders = (await modrinthClient.Project.GetAsync(projectId)).Loaders;

// Update the original message with the new embed
await ModifyOriginalResponseAsync(msg =>
{
msg.Embed = updatedEmbed;
msg.Components = new ComponentBuilder()
.WithSelectMenu(CreateReleaseFilterComponent(projectId, entry.ReleaseFilter))
.WithSelectMenu(CreateLoaderFilterComponent(projectId, supportedLoaders, entry.LoaderFilter))
.Build();
});

// Optionally, acknowledge the selection change in a temporary message
await FollowupAsync("Loader filter updated successfully!", ephemeral: true);
}
}
}
}
16 changes: 16 additions & 0 deletions Asterion/Services/DataService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -427,6 +427,22 @@ public async Task<bool> SetReleaseFilterAsync(ulong entryId, ReleaseType release
return true;
}

public async Task<bool> SetLoaderFilterAsync(ulong entryEntryId, string[]? newLoaderFilter)
{
using var scope = _services.CreateScope();
var db = scope.ServiceProvider.GetRequiredService<DataContext>();

var entry = db.ModrinthEntries.FirstOrDefault(x => x.EntryId == entryEntryId);

if (entry is null) return false;

entry.LoaderFilter = newLoaderFilter;

await db.SaveChangesAsync();

return true;
}

private async Task JoinGuild(SocketGuild guild)
{
await AddGuildAsync(guild.Id);
Expand Down
16 changes: 16 additions & 0 deletions Asterion/Services/Modrinth/SendDiscordNotificationJob.cs
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,22 @@ private async Task SendNotifications(Project project, Version[] versions)
_logger.LogDebug("Version {VersionId} of project {ProjectId} passed release filter", version.Id, project.Id);
break;
}

// Now we check if there is a loader filter
if (entry.LoaderFilter is not null && entry.LoaderFilter.Length > 0)
{
var versionLoaders = version.Loaders;

// Now we check, if any of the loaders are in the filter
if (!versionLoaders.Any(x => entry.LoaderFilter.Contains(x)))
{
_logger.LogDebug("Skipping version {VersionId} of project {ProjectId} due to loader filter", version.Id, project.Id);
continue;

}

_logger.LogDebug("Version {VersionId} of project {ProjectId} passed loader filter", version.Id, project.Id);
}

_logger.LogDebug("Sending notification for version {VersionId} of project {ProjectId} to guild {GuildId}", version.Id, project.Id, guild.GuildId);
var embed = ModrinthEmbedBuilder.VersionUpdateEmbed(guild.GuildSettings, project, version, team).Build();
Expand Down

0 comments on commit a177295

Please sign in to comment.