fix: xplb and xpglb pagination fixed, closes #430

fix: Page number when there is an unknown number of items while paginating is now correct
fix: .stm and .stma fixed and can now mention everyone as long as the user executing the command also can
dev: Cleaned up/improved some code
This commit is contained in:
Kwoth
2024-05-15 13:44:20 +00:00
parent 803fe5db2f
commit a52a246982
13 changed files with 183 additions and 134 deletions

View File

@@ -2,6 +2,15 @@
Mostly based on [keepachangelog](https://keepachangelog.com/en/1.0.0/) except date format. a-c-f-r-o
## [5.0.7] - 15.05.2024
### Fixed
- `.streammessage` will once again be able to mention anyone (as long as the user setting the message has the permission to mention everyone)
- `.streammsgall` fixed
- `.xplb` and `.xpglb` pagination fixed
- Fixed page number when the total number of elements is unknown
## [5.0.6] - 14.05.2024
### Changed

View File

@@ -20,48 +20,48 @@ public static class DiscordUserExtensions
string discrim,
string avatarId)
=> ctx.GetTable<DiscordUser>()
.InsertOrUpdate(
() => new()
{
UserId = userId,
Username = username,
Discriminator = discrim,
AvatarId = avatarId,
TotalXp = 0,
CurrencyAmount = 0
},
old => new()
{
Username = username,
Discriminator = discrim,
AvatarId = avatarId
},
() => new()
{
UserId = userId
});
.InsertOrUpdate(
() => new()
{
UserId = userId,
Username = username,
Discriminator = discrim,
AvatarId = avatarId,
TotalXp = 0,
CurrencyAmount = 0
},
old => new()
{
Username = username,
Discriminator = discrim,
AvatarId = avatarId
},
() => new()
{
UserId = userId
});
public static Task EnsureUserCreatedAsync(
this DbContext ctx,
ulong userId)
=> ctx.GetTable<DiscordUser>()
.InsertOrUpdateAsync(
() => new()
{
UserId = userId,
Username = "Unknown",
Discriminator = "????",
AvatarId = string.Empty,
TotalXp = 0,
CurrencyAmount = 0
},
old => new()
{
},
() => new()
{
UserId = userId
});
.InsertOrUpdateAsync(
() => new()
{
UserId = userId,
Username = "Unknown",
Discriminator = "????",
AvatarId = string.Empty,
TotalXp = 0,
CurrencyAmount = 0
},
old => new()
{
},
() => new()
{
UserId = userId
});
//temp is only used in updatecurrencystate, so that i don't overwrite real usernames/discrims with Unknown
public static DiscordUser GetOrCreateUser(
@@ -83,25 +83,29 @@ public static class DiscordUserExtensions
public static int GetUserGlobalRank(this DbSet<DiscordUser> users, ulong id)
=> users.AsQueryable()
.Where(x => x.TotalXp
> users.AsQueryable().Where(y => y.UserId == id).Select(y => y.TotalXp).FirstOrDefault())
.Count()
.Where(x => x.TotalXp
> users.AsQueryable().Where(y => y.UserId == id).Select(y => y.TotalXp).FirstOrDefault())
.Count()
+ 1;
public static DiscordUser[] GetUsersXpLeaderboardFor(this DbSet<DiscordUser> users, int page, int perPage)
=> users.AsQueryable().OrderByDescending(x => x.TotalXp).Skip(page * perPage).Take(perPage).AsEnumerable()
.ToArray();
public static async Task<IReadOnlyCollection<DiscordUser>> GetUsersXpLeaderboardFor(this DbSet<DiscordUser> users, int page, int perPage)
=> await users.ToLinqToDBTable()
.OrderByDescending(x => x.TotalXp)
.Skip(page * perPage)
.Take(perPage)
.ToArrayAsyncLinqToDB();
public static Task<List<DiscordUser>> GetTopRichest(
this DbSet<DiscordUser> users,
ulong botId,
int page = 0, int perPage = 9)
int page = 0,
int perPage = 9)
=> users.AsQueryable()
.Where(c => c.CurrencyAmount > 0 && botId != c.UserId)
.OrderByDescending(c => c.CurrencyAmount)
.Skip(page * perPage)
.Take(perPage)
.ToListAsyncLinqToDB();
.Where(c => c.CurrencyAmount > 0 && botId != c.UserId)
.OrderByDescending(c => c.CurrencyAmount)
.Skip(page * perPage)
.Take(perPage)
.ToListAsyncLinqToDB();
public static async Task<long> GetUserCurrencyAsync(this DbSet<DiscordUser> users, ulong userId)
=> (await users.FirstOrDefaultAsyncLinqToDB(x => x.UserId == userId))?.CurrencyAmount ?? 0;
@@ -118,8 +122,8 @@ public static class DiscordUserExtensions
public static decimal GetTopOnePercentCurrency(this DbSet<DiscordUser> users, ulong botId)
=> users.AsQueryable()
.Where(x => x.UserId != botId)
.OrderByDescending(x => x.CurrencyAmount)
.Take(users.Count() / 100 == 0 ? 1 : users.Count() / 100)
.Sum(x => x.CurrencyAmount);
.Where(x => x.UserId != botId)
.OrderByDescending(x => x.CurrencyAmount)
.Take(users.Count() / 100 == 0 ? 1 : users.Count() / 100)
.Sum(x => x.CurrencyAmount);
}

View File

@@ -2,7 +2,6 @@
using LinqToDB;
using LinqToDB.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using NadekoBot.Db.Models;
namespace NadekoBot.Db;
@@ -27,33 +26,33 @@ public static class UserXpExtensions
return usr;
}
public static List<UserXpStats> GetUsersFor(this DbSet<UserXpStats> xps, ulong guildId, int page)
=> xps.AsQueryable()
.AsNoTracking()
.Where(x => x.GuildId == guildId)
.OrderByDescending(x => x.Xp + x.AwardedXp)
.Skip(page * 9)
.Take(9)
.ToList();
public static async Task<IReadOnlyCollection<UserXpStats>> GetUsersFor(
this DbSet<UserXpStats> xps,
ulong guildId,
int page)
=> await xps.ToLinqToDBTable()
.Where(x => x.GuildId == guildId)
.OrderByDescending(x => x.Xp + x.AwardedXp)
.Skip(page * 9)
.Take(9)
.ToArrayAsyncLinqToDB();
public static List<UserXpStats> GetTopUserXps(this DbSet<UserXpStats> xps, ulong guildId, int count)
=> xps.AsQueryable()
.AsNoTracking()
.Where(x => x.GuildId == guildId)
.OrderByDescending(x => x.Xp + x.AwardedXp)
.Take(count)
.ToList();
public static async Task<List<UserXpStats>> GetTopUserXps(this DbSet<UserXpStats> xps, ulong guildId, int count)
=> await xps.ToLinqToDBTable()
.Where(x => x.GuildId == guildId)
.OrderByDescending(x => x.Xp + x.AwardedXp)
.Take(count)
.ToListAsyncLinqToDB();
public static int GetUserGuildRanking(this DbSet<UserXpStats> xps, ulong userId, ulong guildId)
=> xps.AsQueryable()
.AsNoTracking()
.Where(x => x.GuildId == guildId
&& x.Xp + x.AwardedXp
> xps.AsQueryable()
.Where(y => y.UserId == userId && y.GuildId == guildId)
.Select(y => y.Xp + y.AwardedXp)
.FirstOrDefault())
.Count()
public static async Task<int> GetUserGuildRanking(this DbSet<UserXpStats> xps, ulong userId, ulong guildId)
=> await xps.ToLinqToDBTable()
.Where(x => x.GuildId == guildId
&& x.Xp + x.AwardedXp
> xps.AsQueryable()
.Where(y => y.UserId == userId && y.GuildId == guildId)
.Select(y => y.Xp + y.AwardedXp)
.FirstOrDefault())
.CountAsyncLinqToDB()
+ 1;
public static void ResetGuildUserXp(this DbSet<UserXpStats> xps, ulong userId, ulong guildId)
@@ -63,10 +62,9 @@ public static class UserXpExtensions
=> xps.Delete(x => x.GuildId == guildId);
public static async Task<LevelStats> GetLevelDataFor(this ITable<UserXpStats> userXp, ulong guildId, ulong userId)
=> await userXp
.Where(x => x.GuildId == guildId && x.UserId == userId)
.FirstOrDefaultAsyncLinqToDB() is UserXpStats uxs
? new(uxs.Xp + uxs.AwardedXp)
: new(0);
=> await userXp
.Where(x => x.GuildId == guildId && x.UserId == userId)
.FirstOrDefaultAsyncLinqToDB() is UserXpStats uxs
? new(uxs.Xp + uxs.AwardedXp)
: new(0);
}

View File

@@ -3,6 +3,7 @@ namespace NadekoBot.Db.Models;
public class GuildConfig : DbEntity
{
// public bool Keep { get; set; }
public ulong GuildId { get; set; }
public string Prefix { get; set; }

View File

@@ -766,7 +766,7 @@ public partial class Gambling : GamblingModule<GamblingService>
}
async Task<IEnumerable<DiscordUser>> GetTopRichest(int curPage)
async Task<IReadOnlyCollection<DiscordUser>> GetTopRichest(int curPage)
{
if (opts.Clean)
{

View File

@@ -143,6 +143,10 @@ public partial class Searches
if (--index < 0)
return;
var canMentionEveryone = (ctx.User as IGuildUser)?.GuildPermissions.MentionEveryone ?? true;
if (!canMentionEveryone)
message = message?.SanitizeAllMentions();
if (!_service.SetStreamMessage(ctx.Guild.Id, index, message, out var fs))
{
await Response().Confirm(strs.stream_not_following).SendAsync();
@@ -160,6 +164,10 @@ public partial class Searches
[UserPerm(GuildPerm.ManageMessages)]
public async Task StreamMessageAll([Leftover] string message)
{
var canMentionEveryone = (ctx.User as IGuildUser)?.GuildPermissions.MentionEveryone ?? true;
if (!canMentionEveryone)
message = message?.SanitizeAllMentions();
var count = _service.SetStreamMessageForAll(ctx.Guild.Id, message);
if (count == 0)

View File

@@ -294,6 +294,7 @@ public sealed class StreamNotificationService : INService, IReadyExecutor
var msg = await _sender.Response(textChannel)
.Embed(GetEmbed(fs.GuildId, stream, false))
.Text(message)
.Sanitize(false)
.SendAsync();
// only cache the ids of channel/message pairs
@@ -615,7 +616,9 @@ public sealed class StreamNotificationService : INService, IReadyExecutor
{
using var uow = _db.GetDbContext();
var all = uow.Set<FollowedStream>().ToList();
var all = uow.Set<FollowedStream>()
.Where(x => x.GuildId == guildId)
.ToList();
if (all.Count == 0)
return 0;
@@ -624,6 +627,19 @@ public sealed class StreamNotificationService : INService, IReadyExecutor
uow.SaveChanges();
lock (_shardLock)
{
foreach (var fs in all)
{
var streams = GetLocalGuildStreams(fs.CreateKey(), guildId);
// message doesn't participate in equality checking
// removing and adding = update
streams.Remove(fs);
streams.Add(fs);
}
}
return all.Count;
}

View File

@@ -1,7 +1,6 @@
#nullable disable warnings
using NadekoBot.Modules.Xp.Services;
using NadekoBot.Db.Models;
using NadekoBot.Db;
using NadekoBot.Modules.Patronage;
namespace NadekoBot.Modules.Xp;
@@ -191,21 +190,22 @@ public partial class Xp : NadekoModule<XpService>
await ctx.Channel.TriggerTypingAsync();
await _tracker.EnsureUsersDownloadedAsync(ctx.Guild);
allCleanUsers = _service.GetTopUserXps(ctx.Guild.Id, 1000)
.Where(user => socketGuild.GetUser(user.UserId) is not null)
.ToList();
allCleanUsers = (await _service.GetTopUserXps(ctx.Guild.Id, 1000))
.Where(user => socketGuild.GetUser(user.UserId) is not null)
.ToList();
}
await Response()
var res = opts.Clean
? Response()
.Paginated()
.PageItems<UserXpStats>(opts.Clean
? (curPage) => Task.FromResult<IEnumerable<UserXpStats>>(allCleanUsers.Skip(curPage * 9)
.Take(9)
.ToList())
: (curPage) => Task.FromResult<IEnumerable<UserXpStats>>(_service.GetUserXps(ctx.Guild.Id, curPage)))
.Items(allCleanUsers)
: Response()
.Paginated()
.PageItems((curPage) => _service.GetUserXps(ctx.Guild.Id, curPage));
await res
.PageSize(9)
.CurrentPage(page)
.AddFooter(false)
.Page((users, curPage) =>
{
var embed = _sender.CreateEmbed().WithTitle(GetText(strs.server_leaderboard)).WithOkColor();
@@ -241,23 +241,33 @@ public partial class Xp : NadekoModule<XpService>
{
if (--page < 0 || page > 99)
return;
var users = _service.GetUserXps(page);
var embed = _sender.CreateEmbed().WithTitle(GetText(strs.global_leaderboard)).WithOkColor();
await Response()
.Paginated()
.PageItems(async curPage => await _service.GetUserXps(curPage))
.PageSize(9)
.Page((users, curPage) =>
{
var embed = _sender.CreateEmbed()
.WithOkColor()
.WithTitle(GetText(strs.global_leaderboard));
if (!users.Any())
embed.WithDescription("-");
else
{
for (var i = 0; i < users.Length; i++)
{
var user = users[i];
embed.AddField($"#{i + 1 + (page * 9)} {user.ToString()}",
$"{GetText(strs.level_x(new LevelStats(users[i].TotalXp).Level))} - {users[i].TotalXp}xp");
}
}
if (!users.Any())
{
embed.WithDescription("-");
return embed;
}
await Response().Embed(embed).SendAsync();
for (var i = 0; i < users.Count; i++)
{
var user = users[i];
embed.AddField($"#{i + 1 + (curPage * 9)} {user}",
$"{GetText(strs.level_x(new LevelStats(users[i].TotalXp).Level))} - {users[i].TotalXp}xp");
}
return embed;
})
.SendAsync();
}
[Cmd]

View File

@@ -563,22 +563,23 @@ public class XpService : INService, IReadyExecutor, IExecNoCommand
uow.SaveChanges();
}
public List<UserXpStats> GetUserXps(ulong guildId, int page)
public async Task<IReadOnlyCollection<UserXpStats>> GetUserXps(ulong guildId, int page)
{
using var uow = _db.GetDbContext();
return uow.Set<UserXpStats>().GetUsersFor(guildId, page);
await using var uow = _db.GetDbContext();
return await uow.Set<UserXpStats>().GetUsersFor(guildId, page);
}
public List<UserXpStats> GetTopUserXps(ulong guildId, int count)
public async Task<IReadOnlyCollection<UserXpStats>> GetTopUserXps(ulong guildId, int count)
{
using var uow = _db.GetDbContext();
return uow.Set<UserXpStats>().GetTopUserXps(guildId, count);
await using var uow = _db.GetDbContext();
return await uow.Set<UserXpStats>().GetTopUserXps(guildId, count);
}
public DiscordUser[] GetUserXps(int page, int perPage = 9)
public Task<IReadOnlyCollection<DiscordUser>> GetUserXps(int page, int perPage = 9)
{
using var uow = _db.GetDbContext();
return uow.Set<DiscordUser>().GetUsersXpLeaderboardFor(page, perPage);
return uow.Set<DiscordUser>()
.GetUsersXpLeaderboardFor(page, perPage);
}
public async Task ChangeNotificationType(ulong userId, ulong guildId, XpNotificationLocation type)
@@ -884,7 +885,7 @@ public class XpService : INService, IReadyExecutor, IExecNoCommand
var du = uow.GetOrCreateUser(user, set => set.Include(x => x.Club));
var totalXp = du.TotalXp;
var globalRank = uow.Set<DiscordUser>().GetUserGlobalRank(user.Id);
var guildRank = uow.Set<UserXpStats>().GetUserGuildRanking(user.Id, user.GuildId);
var guildRank = await uow.Set<UserXpStats>().GetUserGuildRanking(user.Id, user.GuildId);
var stats = uow.GetOrCreateUserXpStats(user.GuildId, user.Id);
await uow.SaveChangesAsync();

View File

@@ -5,7 +5,7 @@
<Nullable>enable</Nullable>
<ImplicitUsings>true</ImplicitUsings>
<SatelliteResourceLanguages>en</SatelliteResourceLanguages>
<Version>5.0.6</Version>
<Version>5.0.7</Version>
<!-- Output/build -->
<RunWorkingDirectory>$(MSBuildProjectDirectory)</RunWorkingDirectory>

View File

@@ -58,7 +58,7 @@ public partial class ResponseBuilder
cb.WithButton(new ButtonBuilder()
.WithStyle(ButtonStyle.Primary)
.WithCustomId(BUTTON_RIGHT)
.WithDisabled(lastPage == 0 || currentPage >= lastPage)
.WithDisabled(lastPage is not null && (lastPage == 0 || currentPage >= lastPage))
.WithEmote(InteractionHelpers.ArrowRight));
return cb;

View File

@@ -1,5 +1,6 @@
using NadekoBot.Common.Configs;
using NadekoBot.Db.Models;
using System.Collections.ObjectModel;
namespace NadekoBot.Extensions;
@@ -360,7 +361,6 @@ public sealed partial class ResponseBuilder
public PaginatedResponseBuilder Paginated()
=> new(this);
}
public class PaginatedResponseBuilder
@@ -376,7 +376,7 @@ public class PaginatedResponseBuilder
=> new SourcedPaginatedResponseBuilder<T>(_builder)
.Items(items);
public SourcedPaginatedResponseBuilder<T> PageItems<T>(Func<int, Task<IEnumerable<T>>> items)
public SourcedPaginatedResponseBuilder<T> PageItems<T>(Func<int, Task<IReadOnlyCollection<T>>> items)
=> new SourcedPaginatedResponseBuilder<T>(_builder)
.PageItems(items);
}
@@ -390,14 +390,14 @@ public sealed class SourcedPaginatedResponseBuilder<T> : PaginatedResponseBuilde
return Task.FromResult<EmbedBuilder>(new());
};
public Func<int, Task<IEnumerable<T>>> ItemsFunc { get; set; } = static delegate
public Func<int, Task<IReadOnlyCollection<T>>> ItemsFunc { get; set; } = static delegate
{
return Task.FromResult(Enumerable.Empty<T>());
return Task.FromResult<IReadOnlyCollection<T>>(ReadOnlyCollection<T>.Empty);
};
public Func<int, Task<SimpleInteractionBase>>? InteractionFunc { get; private set; }
public int Elems { get; private set; } = 1;
public int? Elems { get; private set; } = 1;
public int ItemsPerPage { get; private set; } = 9;
public bool AddPaginatedFooter { get; private set; } = true;
public bool IsEphemeral { get; private set; }
@@ -413,7 +413,7 @@ public sealed class SourcedPaginatedResponseBuilder<T> : PaginatedResponseBuilde
{
items = col;
Elems = col.Count;
ItemsFunc = (i) => Task.FromResult(items.Skip(i * ItemsPerPage).Take(ItemsPerPage));
ItemsFunc = (i) => Task.FromResult(items.Skip(i * ItemsPerPage).Take(ItemsPerPage).ToArray() as IReadOnlyCollection<T>);
return this;
}
@@ -423,8 +423,9 @@ public sealed class SourcedPaginatedResponseBuilder<T> : PaginatedResponseBuilde
return this;
}
public SourcedPaginatedResponseBuilder<T> PageItems(Func<int, Task<IEnumerable<T>>> func)
public SourcedPaginatedResponseBuilder<T> PageItems(Func<int, Task<IReadOnlyCollection<T>>> func)
{
Elems = null;
ItemsFunc = func;
return this;
}

View File

@@ -167,7 +167,8 @@ public static class Extensions
{
if (lastPage is not null)
return embed.WithFooter($"{curPage + 1} / {lastPage + 1}");
return embed.WithFooter(curPage.ToString());
return embed.WithFooter((curPage + 1).ToString());
}
// public static EmbedBuilder WithOkColor(this EmbedBuilder eb)