Skip to content

Commit

Permalink
Add osu! verification and automatic role assignment
Browse files Browse the repository at this point in the history
  • Loading branch information
Pasi4K5 committed Mar 19, 2024
1 parent aade393 commit c4f6379
Show file tree
Hide file tree
Showing 15 changed files with 454 additions and 24 deletions.
4 changes: 3 additions & 1 deletion Numerous.sln.DotSettings
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=PossibleNullReferenceException/@EntryIndexedValue">ERROR</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=RedundantBlankLines/@EntryIndexedValue">WARNING</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=RedundantLambdaSignatureParentheses/@EntryIndexedValue">WARNING</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=RedundantLinebreak/@EntryIndexedValue">WARNING</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=RedundantLinebreak/@EntryIndexedValue">DO_NOT_SHOW</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=RedundantSpace/@EntryIndexedValue">WARNING</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=RedundantStringInterpolation/@EntryIndexedValue">WARNING</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ReplaceSubstringWithRangeIndexer/@EntryIndexedValue">WARNING</s:String>
Expand All @@ -66,6 +66,8 @@
<s:Boolean x:Key="/Default/CodeInspection/Highlighting/RunLongAnalysisInSwa/@EntryValue">True</s:Boolean>
<s:String x:Key="/Default/CodeInspection/Highlighting/SweaWarningsMode/@EntryValue">ShowAndRun</s:String>
<s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/BLANK_LINES_BEFORE_CONTROL_TRANSFER_STATEMENTS/@EntryValue">1</s:Int64>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/KEEP_EXISTING_ATTRIBUTE_ARRANGEMENT/@EntryValue">True</s:Boolean>
<s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/MAX_ATTRIBUTE_LENGTH_FOR_SAME_LINE/@EntryValue">70</s:Int64>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_ACCESSOR_ATTRIBUTE_ON_SAME_LINE_EX/@EntryValue">ALWAYS</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_ACCESSORHOLDER_ATTRIBUTE_ON_SAME_LINE_EX/@EntryValue">NEVER</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_RECORD_FIELD_ATTRIBUTE_ON_SAME_LINE_EX/@EntryValue">ALWAYS</s:String>
Expand Down
29 changes: 29 additions & 0 deletions Numerous/ApiClients/Osu/Extensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Copyright (C) Pasi4K5 <https://www.github.com/Pasi4K5>
// This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
// This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
// You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.

using Numerous.ApiClients.Osu.Models;

namespace Numerous.ApiClients.Osu;

public static class Extensions
{
public static bool IsRankedMapper(this OsuUser user)
{
return user.RankedBeatmapsetCount > 0
|| user.GuestBeatmapsetCount > 0;
}

public static bool IsLovedMapper(this OsuUser user)
{
return user.LovedBeatmapsetCount > 0;
}

public static bool IsUnrankedMapper(this OsuUser user)
{
return
(user.GraveyardBeatmapsetCount > 0 || user.PendingBeatmapsetCount > 0)
&& !user.IsRankedMapper();
}
}
33 changes: 31 additions & 2 deletions Numerous/ApiClients/Osu/Models/OsuUser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

namespace Numerous.ApiClients.Osu.Models;

public record struct OsuUser
public record OsuUser
{
[JsonProperty("id")]
public uint Id { get; init; }
Expand All @@ -16,5 +16,34 @@ public record struct OsuUser
public bool IsBot { get; init; }

[JsonProperty("username")]
public string Username { get; init; }
public string Username { get; init; } = null!;

[JsonProperty("groups")]
public OsuUserGroup[]? Groups { get; set; } = Array.Empty<OsuUserGroup>();

[JsonProperty("graveyard_beatmapset_count")]
public uint GraveyardBeatmapsetCount { get; init; }

[JsonProperty("guest_beatmapset_count")]
public uint GuestBeatmapsetCount { get; init; }

[JsonProperty("loved_beatmapset_count")]
public uint LovedBeatmapsetCount { get; init; }

[JsonProperty("pending_beatmapset_count")]
public uint PendingBeatmapsetCount { get; init; }

[JsonProperty("ranked_beatmapset_count")]
public uint RankedBeatmapsetCount { get; init; }

public OsuUserGroup[] GetGroups()
{
return Groups ?? Array.Empty<OsuUserGroup>();
}
}

public sealed record OsuUserExtended : OsuUser
{
[JsonProperty("discord")]
public string DiscordUsername { get; init; } = null!;
}
8 changes: 8 additions & 0 deletions Numerous/ApiClients/Osu/OsuApi.Repository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,14 @@ public partial class OsuApi
return await RequestRefAsync<JObject>($"users/{user}", ("key", "id"));
}

/// <summary>
/// Prioritizes the user ID over the username.
/// </summary>
public async Task<OsuUserExtended?> GetUserAsync(string user)
{
return await RequestRefAsync<OsuUserExtended>($"users/{user}");
}

public async Task<Beatmapset?> GetBeatmapsetAsync(uint id)
{
return await RequestValAsync<Beatmapset>($"beatmapsets/{id}");
Expand Down
26 changes: 26 additions & 0 deletions Numerous/ApiClients/Osu/OsuUserGroup.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Copyright (C) Pasi4K5 <https://www.github.com/Pasi4K5>
// This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
// This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
// You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.

using Discord.Interactions;

namespace Numerous.ApiClients.Osu;

public enum OsuUserGroup
{
[ChoiceDisplay("Unranked Mapper")] UnrankedMapper = -1,
[ChoiceDisplay("Ranked Mapper")] RankedMapper = -2,
[ChoiceDisplay("Loved Mapper")] LovedMapper = -3,

Check failure on line 14 in Numerous/ApiClients/Osu/OsuUserGroup.cs

View workflow job for this annotation

GitHub Actions / Build and Inspect

Enum member 'LovedMapper' is never used in Numerous\ApiClients\Osu\OsuUserGroup.cs on line 14
[ChoiceDisplay("GMT")] GlobalModerationTeam = 4,

Check failure on line 15 in Numerous/ApiClients/Osu/OsuUserGroup.cs

View workflow job for this annotation

GitHub Actions / Build and Inspect

Enum member 'GlobalModerationTeam' is never used in Numerous\ApiClients\Osu\OsuUserGroup.cs on line 15
[ChoiceDisplay("NAT")] NominationAssessmentTeam = 7,

Check failure on line 16 in Numerous/ApiClients/Osu/OsuUserGroup.cs

View workflow job for this annotation

GitHub Actions / Build and Inspect

Enum member 'NominationAssessmentTeam' is never used in Numerous\ApiClients\Osu\OsuUserGroup.cs on line 16
[ChoiceDisplay("Developers")] Developers = 11,

Check failure on line 17 in Numerous/ApiClients/Osu/OsuUserGroup.cs

View workflow job for this annotation

GitHub Actions / Build and Inspect

Enum member 'Developers' is never used in Numerous\ApiClients\Osu\OsuUserGroup.cs on line 17
[ChoiceDisplay("Community Contributors")] OsuAlumin = 16,

Check failure on line 18 in Numerous/ApiClients/Osu/OsuUserGroup.cs

View workflow job for this annotation

GitHub Actions / Build and Inspect

Enum member 'OsuAlumin' is never used in Numerous\ApiClients\Osu\OsuUserGroup.cs on line 18
[ChoiceDisplay("Technical Support Team")] TechnicalSupportTeam = 17,

Check failure on line 19 in Numerous/ApiClients/Osu/OsuUserGroup.cs

View workflow job for this annotation

GitHub Actions / Build and Inspect

Enum member 'TechnicalSupportTeam' is never used in Numerous\ApiClients\Osu\OsuUserGroup.cs on line 19
[ChoiceDisplay("Beatmap Nominators")] BeatmapNominators = 28,

Check failure on line 20 in Numerous/ApiClients/Osu/OsuUserGroup.cs

View workflow job for this annotation

GitHub Actions / Build and Inspect

Enum member 'BeatmapNominators' is never used in Numerous\ApiClients\Osu\OsuUserGroup.cs on line 20
[ChoiceDisplay("Project Loved")] ProjectLoved = 31,
[ChoiceDisplay("Beatmap Nominators (Probationary)")] BeatmapNominatorsProbationary = 32,

Check failure on line 22 in Numerous/ApiClients/Osu/OsuUserGroup.cs

View workflow job for this annotation

GitHub Actions / Build and Inspect

Enum member 'BeatmapNominatorsProbationary' is never used in Numerous\ApiClients\Osu\OsuUserGroup.cs on line 22
[ChoiceDisplay("ppy")] Ppy = 33,

Check failure on line 23 in Numerous/ApiClients/Osu/OsuUserGroup.cs

View workflow job for this annotation

GitHub Actions / Build and Inspect

Enum member 'Ppy' is never used in Numerous\ApiClients\Osu\OsuUserGroup.cs on line 23
[ChoiceDisplay("Featured Artists")] FeaturedArtists = 35,

Check failure on line 24 in Numerous/ApiClients/Osu/OsuUserGroup.cs

View workflow job for this annotation

GitHub Actions / Build and Inspect

Enum member 'FeaturedArtists' is never used in Numerous\ApiClients\Osu\OsuUserGroup.cs on line 24
[ChoiceDisplay("Beatmap Spotlight Curators")] BeatmapSpotlightCurators = 48,
}
21 changes: 14 additions & 7 deletions Numerous/Database/DbManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,20 +43,27 @@ public DbManager(ConfigManager configManager)
}
}

public async Task EnsureUserExistsAsync(ulong id)
{
await GetUserAsync(id);
}

public async Task<DbUser> GetUserAsync(ulong id)
{
var user = await Users.Find(x => x.Id == id).FirstOrDefaultAsync();

if (user is null)
if (user is not null)
{
user = new DbUser
{
Id = id,
};

await Users.InsertOneAsync(user);
return user;
}

user = new DbUser
{
Id = id,
};

await Users.InsertOneAsync(user);

return user;
}
}
1 change: 1 addition & 0 deletions Numerous/Database/Entities/DbUser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@ public sealed record DbUser : DbEntity<ulong>
// For future osu!-related features
// public DbOsuUser? OsuUser { get; init; }

public uint? OsuId { get; set; }

Check notice on line 13 in Numerous/Database/Entities/DbUser.cs

View workflow job for this annotation

GitHub Actions / Build and Inspect

Auto-property accessor 'OsuId.set' is never used in Numerous\Database\Entities\DbUser.cs on line 13
public string? TimeZone { get; set; }
}
9 changes: 9 additions & 0 deletions Numerous/Database/Entities/GuildOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
// This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
// You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.

using Numerous.ApiClients.Osu;

namespace Numerous.Database.Entities;

// TODO: Remove comment
Expand All @@ -11,6 +13,7 @@ public sealed record GuildOptions : DbEntity<ulong>
{
public bool TrackMessages { get; init; }
public bool TrackMemberCount { get; init; }
public ICollection<OsuRole> OsuRoles { get; init; } = [];

public TrackingOptions[] PlayerTrackingOptions { get; init; } = Array.Empty<TrackingOptions>();

Expand All @@ -19,4 +22,10 @@ public record struct TrackingOptions
public ulong DiscordId { get; init; }
public bool TrackPlayer { get; init; }
}

public record struct OsuRole
{
public OsuUserGroup Group { get; init; }
public ulong RoleId { get; init; }
}
}
3 changes: 2 additions & 1 deletion Numerous/Discord/Commands/CommandModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,10 @@ protected async Task RespondWithEmbedAsync(string message, ResponseType type = R
);
}

protected async Task FollowupWithEmbedAsync(string message, ResponseType type = ResponseType.Info)
protected async Task FollowupWithEmbedAsync(string title = "", string message = "", ResponseType type = ResponseType.Info)
{
await FollowupAsync(embed: new EmbedBuilder()
.WithTitle(title)
.WithColor(GetTypeColor(type))
.WithDescription(message)
.Build()
Expand Down
49 changes: 49 additions & 0 deletions Numerous/Discord/Commands/ConfigCommandModule.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// Copyright (C) Pasi4K5 <https://www.github.com/Pasi4K5>
// This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
// This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
// You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.

using Discord;
using Discord.Interactions;
using JetBrains.Annotations;
using Numerous.ApiClients.Osu;

namespace Numerous.Discord.Commands;

[Group("config", "Configuration commands")]
[DefaultMemberPermissions(GuildPermission.Administrator)]
public sealed class ConfigCommandModule : CommandModule
{
[Group("role", "Role configuration commands")]
public sealed class RoleCommandModule(OsuVerifier verifier) : CommandModule
{
[UsedImplicitly]
[SlashCommand("set", "Configures which role to assign to which users.")]
public async Task SetRole(
[Summary("group", "The group to set the role for.")] OsuUserGroup group,
[Summary("role", "The role to assign to users in the group.")] IRole role
)
{
await verifier.SetRoleAsync(Context.Guild, group, role);

await RespondWithEmbedAsync(
$"Set role for group {group} to {role.Mention}.",
ResponseType.Success
);
}

[UsedImplicitly]
[SlashCommand("remove", "Stops automatically assigning the given role.")]
public async Task RemoveRole(
[Summary("group", "The group to remove the role for.")] OsuUserGroup group
)
{
await verifier.RemoveRoleAsync(Context.Guild, group);

await RespondWithEmbedAsync(
$"Removed role for group {group}.",
ResponseType.Success
);
}
}
}
16 changes: 10 additions & 6 deletions Numerous/Discord/Commands/InteractionHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public sealed class InteractionHandler(
ConfigManager configManager
) : IHostedService
{
public async Task StartAsync(CancellationToken cancellationToken)
public Task StartAsync(CancellationToken cancellationToken)
{
var cfg = configManager.Get();

Expand All @@ -40,8 +40,11 @@ public async Task StartAsync(CancellationToken cancellationToken)
return Task.CompletedTask;
};

client.Ready += cfg.GuildMode
? async () =>
client.Ready += async () =>
{
await interactions.AddModulesAsync(Assembly.GetEntryAssembly(), services);

if (cfg.GuildMode)
{
foreach (var cmd in await client.GetGlobalApplicationCommandsAsync())
{
Expand All @@ -53,18 +56,19 @@ public async Task StartAsync(CancellationToken cancellationToken)
await interactions.RegisterCommandsToGuildAsync(guildId);
}
}
: async () =>
else
{
foreach (var guild in await client.Rest.GetGuildsAsync())
{
await guild.DeleteSlashCommandsAsync();
}

await interactions.RegisterCommandsGloballyAsync();
};
}
};
client.InteractionCreated += OnInteractionCreatedAsync;

await interactions.AddModulesAsync(Assembly.GetEntryAssembly(), services);
return Task.CompletedTask;
}

public Task StopAsync(CancellationToken cancellationToken)
Expand Down
12 changes: 6 additions & 6 deletions Numerous/Discord/Commands/ReminderCommandModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,8 @@ public async Task AtCommand(
if (timeZoneId is null)
{
await FollowupWithEmbedAsync(
"To use this command, please set your time zone with `/settimezone` first.",
ResponseType.Error
message: "To use this command, please set your time zone with `/settimezone` first.",
type: ResponseType.Error
);

return;
Expand All @@ -120,8 +120,8 @@ await FollowupWithEmbedAsync(
if (timestamp is null)
{
await FollowupWithEmbedAsync(
"The specified time must be at least 10 seconds in the future.",
ResponseType.Error
message: "The specified time must be at least 10 seconds in the future.",
type: ResponseType.Error
);

return;
Expand All @@ -143,7 +143,7 @@ await FollowupAsync(embed:
}
catch (ArgumentOutOfRangeException)
{
await FollowupWithEmbedAsync("The specified time is invalid.", ResponseType.Error);
await FollowupWithEmbedAsync("The specified time is invalid.", type: ResponseType.Error);
}
}

Expand Down Expand Up @@ -281,7 +281,7 @@ int index

if (index < 1 || index > reminders.Count)
{
await FollowupWithEmbedAsync("There is no reminder with that index.", ResponseType.Error);
await FollowupWithEmbedAsync("There is no reminder with that index.", type: ResponseType.Error);

return;
}
Expand Down
Loading

0 comments on commit c4f6379

Please sign in to comment.