Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Filter by loader type #174

Merged
merged 4 commits into from
Nov 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading