Skip to content
Open
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
37 changes: 36 additions & 1 deletion BaseBotService/Commands/UserModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,15 @@ public class UserModule : BaseModule
private readonly ITranslationService _translationService;
private readonly IEngagementService _engagementService;
private readonly IMemberRepository _memberRepository;
private readonly IGuildMemberRepository _guildMemberRepository;

public UserModule(ILogger logger, ITranslationService translationService, IEngagementService engagementService, IMemberRepository memberRepository)
public UserModule(ILogger logger, ITranslationService translationService, IEngagementService engagementService, IMemberRepository memberRepository, IGuildMemberRepository guildMemberRepository)
{
Logger = logger.ForContext<UserModule>();
_translationService = translationService;
_engagementService = engagementService;
_memberRepository = memberRepository;
_guildMemberRepository = guildMemberRepository;
}

[UserCommand("User Profile")]
Expand Down Expand Up @@ -247,6 +249,39 @@ await ModifyOriginalResponseAsync(x =>
});
}

[SlashCommand("leaderboard", "Show the most active users on this server.")]
public async Task LeaderboardAsync(
[Summary(description: "Number of users to display (1-100)")] int amount = 10)
{
amount = Math.Clamp(amount, 1, 100);
var top = _guildMemberRepository.GetTopUsers(Context.Guild.Id, amount)
.ToList();

StringBuilder description = new();
for (int i = 0; i < top.Count; i++)
{
var member = top[i];
var user = Context.Guild.GetUser(member.MemberId);
string name = user?.Username ?? member.MemberId.ToString();
description.AppendLine(
_translationService.GetString(
"leaderboard-entry",
TranslationHelper.Arguments(
"rank", i + 1,
"user", name,
"points", member.ActivityPoints)));
}

EmbedBuilder embed = GetEmbedBuilder()
.WithTitle(
_translationService.GetString(
"leaderboard-title",
TranslationHelper.Arguments("amount", amount)))
.WithDescription(description.ToString());

await RespondOrFollowupAsync(embed: embed.Build());
}

internal double GetActivityScore(IGuildUser user, IGuildUser bot)
{
const double averageOnlineHours = 4;
Expand Down
1 change: 1 addition & 0 deletions BaseBotService/Core/DiscordEventListener.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ public async Task StartAsync()
_client.JoinedGuild += (guild) => _mediator.Publish(new JoinedGuildNotification(guild), _cancellationToken);
_client.LeftGuild += (guild) => _mediator.Publish(new LeftGuildNotification(guild), _cancellationToken);
_client.UserJoined += (user) => _mediator.Publish(new UserJoinedNotification(user), _cancellationToken);
_client.UserLeft += (guild, user) => _mediator.Publish(new UserLeftNotification(guild, user), _cancellationToken);
_handler.Log += (msg) => _mediator.Publish(new LogNotification(msg), _cancellationToken);

_ = await _handler.AddModulesAsync(Assembly.GetEntryAssembly(), _services);
Expand Down
14 changes: 14 additions & 0 deletions BaseBotService/Core/Messages/UserLeftNotification.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using Discord.WebSocket;

Comment on lines +1 to +2
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add missing using statement for MediatR.

The INotification interface requires a using statement for MediatR.

Apply this diff to add the missing using statement:

 using Discord.WebSocket;
+using MediatR;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
using Discord.WebSocket;
using Discord.WebSocket;
using MediatR;
🤖 Prompt for AI Agents
In BaseBotService/Core/Messages/UserLeftNotification.cs at the top of the file
around lines 1 to 2, add the missing using statement for MediatR by including
"using MediatR;". This is necessary because the INotification interface used in
the file is defined in the MediatR namespace.

namespace BaseBotService.Core.Messages;
public class UserLeftNotification : INotification
{
public SocketGuild Guild { get; }
public SocketUser User { get; }

public UserLeftNotification(SocketGuild guild, SocketUser user)
{
Guild = guild;
User = user;
}
}
8 changes: 8 additions & 0 deletions BaseBotService/Data/Interfaces/IGuildMemberRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,12 @@ public interface IGuildMemberRepository
/// <returns>True if the delete was successful, otherwise false.</returns>
bool DeleteUser(ulong guildId, ulong userId);
int DeleteGuild(ulong guildId);

/// <summary>
/// Gets the top users of a guild ordered by their activity points.
/// </summary>
/// <param name="guildId">The unique identifier of the guild.</param>
/// <param name="limit">The maximum number of users to return.</param>
/// <returns>An enumerable of <see cref="GuildMemberHC"/> ordered by activity points.</returns>
IEnumerable<GuildMemberHC> GetTopUsers(ulong guildId, int limit);
}
6 changes: 6 additions & 0 deletions BaseBotService/Data/Repositories/GuildMemberRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,10 @@ public int DeleteGuild(ulong guildId)
}
return 0;
}

public IEnumerable<GuildMemberHC> GetTopUsers(ulong guildId, int limit)
=> _guildMembers
.Find(a => a.GuildId == guildId)
.OrderByDescending(u => u.ActivityPoints)
.Take(limit);
Comment on lines +42 to +46
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add input validation for the limit parameter.

The implementation is correct but should validate the limit parameter to prevent potential issues with negative values.

 public IEnumerable<GuildMemberHC> GetTopUsers(ulong guildId, int limit)
-    => _guildMembers
+{
+    if (limit < 0)
+        throw new ArgumentOutOfRangeException(nameof(limit), "Limit must be non-negative.");
+        
+    return _guildMembers
         .Find(a => a.GuildId == guildId)
         .OrderByDescending(u => u.ActivityPoints)
         .Take(limit);
+}

Note: For very large guilds, consider implementing database-level ordering and limiting to improve performance, though the current approach should be sufficient for typical Discord server sizes.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public IEnumerable<GuildMemberHC> GetTopUsers(ulong guildId, int limit)
=> _guildMembers
.Find(a => a.GuildId == guildId)
.OrderByDescending(u => u.ActivityPoints)
.Take(limit);
public IEnumerable<GuildMemberHC> GetTopUsers(ulong guildId, int limit)
{
if (limit < 0)
throw new ArgumentOutOfRangeException(nameof(limit), "Limit must be non-negative.");
return _guildMembers
.Find(a => a.GuildId == guildId)
.OrderByDescending(u => u.ActivityPoints)
.Take(limit);
}
🤖 Prompt for AI Agents
In BaseBotService/Data/Repositories/GuildMemberRepository.cs around lines 42 to
46, the GetTopUsers method lacks validation for the limit parameter, which could
lead to issues if a negative value is passed. Add input validation to check if
limit is less than or equal to zero and handle it appropriately, such as
returning an empty collection or throwing an ArgumentException. This ensures the
method behaves predictably and prevents potential runtime errors.

}
17 changes: 16 additions & 1 deletion BaseBotService/Interactions/EntityLifecycleHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
using BaseBotService.Data.Models;

namespace BaseBotService.Interactions;
public class EntityLifecycleHandler : INotificationHandler<JoinedGuildNotification>, INotificationHandler<UserJoinedNotification>, INotificationHandler<LeftGuildNotification>
public class EntityLifecycleHandler : INotificationHandler<JoinedGuildNotification>, INotificationHandler<UserJoinedNotification>, INotificationHandler<LeftGuildNotification>, INotificationHandler<UserLeftNotification>
{
private readonly ILogger _logger;
private readonly IMemberRepository _memberRepository;
Expand Down Expand Up @@ -54,4 +54,19 @@ public Task Handle(JoinedGuildNotification notification, CancellationToken cance

return Task.CompletedTask;
}

public Task Handle(UserLeftNotification notification, CancellationToken cancellationToken)
{
_logger.Debug($"{nameof(EntityLifecycleHandler)} received {nameof(UserLeftNotification)}");

GuildMemberHC? member = _guildMemberRepository.GetUser(notification.Guild.Id, notification.User.Id);
if (member != null)
{
member.ActivityPoints = 0;
_guildMemberRepository.UpdateUser(member);
_logger.Information($"User {notification.User.Id} left {notification.Guild.Id}, reset ActivityPoints.");
}

return Task.CompletedTask;
}
}
3 changes: 3 additions & 0 deletions BaseBotService/Locales/de.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,9 @@ profile = { $username } @ { $guildname }
*[other] Invalid activity score
}

leaderboard-title = Top { $amount } active users
leaderboard-entry = { $rank }. { $user } - { $points } pts
Comment on lines +137 to +138
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Translate strings to German for proper localization.

The leaderboard strings are in English within the German localization file. For proper internationalization, these should be translated to German.

-leaderboard-title = Top { $amount } active users
-leaderboard-entry = { $rank }. { $user } - { $points } pts
+leaderboard-title = Top { $amount } aktive Benutzer
+leaderboard-entry = { $rank }. { $user } - { $points } Punkte
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
leaderboard-title = Top { $amount } active users
leaderboard-entry = { $rank }. { $user } - { $points } pts
leaderboard-title = Top { $amount } aktive Benutzer
leaderboard-entry = { $rank }. { $user } - { $points } Punkte
🤖 Prompt for AI Agents
In BaseBotService/Locales/de.ftl around lines 137 to 138, the leaderboard
strings are still in English, which is incorrect for the German localization
file. Translate the strings "Top { $amount } active users" and "{ $rank }. {
$user } - { $points } pts" into proper German equivalents to ensure correct
localization.





Expand Down
3 changes: 3 additions & 0 deletions BaseBotService/Locales/en.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,9 @@ profile = { $username } @ { $guildname }
*[other] Invalid activity score
}

leaderboard-title = Top { $amount } active users
leaderboard-entry = { $rank }. { $user } - { $points } pts



####################################
Expand Down
3 changes: 3 additions & 0 deletions BaseBotService/Locales/es.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,9 @@ profile = { $username } @ { $guildname }
*[other] Invalid activity score
}

leaderboard-title = Top { $amount } active users
leaderboard-entry = { $rank }. { $user } - { $points } pts
Comment on lines +137 to +138
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Translate strings to Spanish for proper localization.

The leaderboard strings are in English within the Spanish localization file. For proper internationalization, these should be translated to Spanish.

-leaderboard-title = Top { $amount } active users
-leaderboard-entry = { $rank }. { $user } - { $points } pts
+leaderboard-title = Top { $amount } usuarios activos
+leaderboard-entry = { $rank }. { $user } - { $points } pts

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In BaseBotService/Locales/es.ftl around lines 137 to 138, the leaderboard
strings are still in English within the Spanish localization file. Translate
"Top { $amount } active users" and "{ $rank }. { $user } - { $points } pts" into
Spanish to ensure proper localization and internationalization.





Expand Down
3 changes: 3 additions & 0 deletions BaseBotService/Locales/fr.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,9 @@ profile = { $username } @ { $guildname }
*[other] Invalid activity score
}

leaderboard-title = Top { $amount } active users
leaderboard-entry = { $rank }. { $user } - { $points } pts
Comment on lines +137 to +138
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Translate strings to French for proper localization.

The leaderboard strings are in English within the French localization file. For proper internationalization, these should be translated to French.

-leaderboard-title = Top { $amount } active users
-leaderboard-entry = { $rank }. { $user } - { $points } pts
+leaderboard-title = Top { $amount } utilisateurs actifs
+leaderboard-entry = { $rank }. { $user } - { $points } pts
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
leaderboard-title = Top { $amount } active users
leaderboard-entry = { $rank }. { $user } - { $points } pts
leaderboard-title = Top { $amount } utilisateurs actifs
leaderboard-entry = { $rank }. { $user } - { $points } pts
🤖 Prompt for AI Agents
In BaseBotService/Locales/fr.ftl at lines 137 to 138, the leaderboard strings
are still in English. Translate "Top { $amount } active users" and "{ $rank }. {
$user } - { $points } pts" into French to ensure proper localization. Replace
the English text with accurate French equivalents while keeping the variable
placeholders intact.





Expand Down
4 changes: 3 additions & 1 deletion BaseBotServiceTests/Commands/UserModuleTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ public class UserModuleTests
private ITranslationService _translationService;
private IEngagementService _engagementService;
private IMemberRepository _memberRepository;
private IGuildMemberRepository _guildMemberRepository;
private ILogger _logger;
private UserModule _userModule;
private readonly Faker _faker = new();
Expand All @@ -23,9 +24,10 @@ public void SetUp()
_translationService = Substitute.For<ITranslationService>();
_engagementService = Substitute.For<IEngagementService>();
_memberRepository = Substitute.For<IMemberRepository>();
_guildMemberRepository = Substitute.For<IGuildMemberRepository>();
_logger = Substitute.For<ILogger>();

_userModule = new UserModule(_logger, _translationService, _engagementService, _memberRepository);
_userModule = new UserModule(_logger, _translationService, _engagementService, _memberRepository, _guildMemberRepository);
}

[Test]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,4 +91,28 @@ public void DeleteUser_ShouldDeleteExistingUser()
var deletedUser = _guildMembers.FindOne(u => u.MemberId == existingUser.MemberId && u.GuildId == existingUser.GuildId);
deletedUser.ShouldBeNull();
}

[Test]
public void GetTopUsers_ShouldReturnOrderedLimitedList()
{
// Arrange
var guild = FakeDataHelper.GuildFaker.Generate();
_guilds.Insert(guild);
foreach (var member in guild.Members)
{
_guildMembers.Insert(member);
}
int limit = Math.Min(3, guild.Members.Count);

// Act
var result = _repository.GetTopUsers(guild.GuildId, limit).ToList();

// Assert
result.Count.ShouldBe(limit);
var expected = guild.Members
.OrderByDescending(m => m.ActivityPoints)
.Take(limit)
.Select(m => m.MemberId);
result.Select(r => r.MemberId).ShouldBe(expected);
}
}
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ Project Honeycomb is a Discord bot designed to provide artists with some useful
- **Commission Request Form:** Users can request a commission by filling out a form with necessary details.
- **OC Reference Management:** Users can add references with descriptions for their OCs to easily access them when needed.
- **Loyalty Points:** Artists can reward their loyal customers by awarding loyalty points, which can be redeemed for future commissions.
- **Activity Leaderboard:** See which members are most active in your server.
- **Announcement of New Art:** Artists can announce their new artwork with references to different platforms like Twitter, DeviantArt, FurAffinity, Patreon, and more.
- **Progress Tracking of Commissions:** Artists can track the progress of their commissions using the bot.
- **Raffles:** Organize raffles on Discord or Twitter and randomly draw the winners.
Expand Down
Loading