diff --git a/CHANGELOG.md b/CHANGELOG.md index cbca9df..a561485 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,21 @@ *a,c,f,r,o* +## [6.1.2] - 03.04.2025 + +### Fixed +- Fixed `.feed` not adding new feeds to the database + +## [6.1.1] - 03.04.2025 + +### Added +- Added some config options for .conf fish + +### Fixed +- Fixed a typo in fish shop +- .fishlb will now compare unique fish caught, instead of total catches +- hangman category now appears in .hangman output + ## [6.1.0] - 30.03.2025 ### Added diff --git a/src/EllieBot/EllieBot.csproj b/src/EllieBot/EllieBot.csproj index e623461..4024227 100644 --- a/src/EllieBot/EllieBot.csproj +++ b/src/EllieBot/EllieBot.csproj @@ -4,7 +4,7 @@ <Nullable>enable</Nullable> <ImplicitUsings>true</ImplicitUsings> <SatelliteResourceLanguages>en</SatelliteResourceLanguages> - <Version>6.1.0</Version> + <Version>6.1.2</Version> <!-- Output/build --> <RunWorkingDirectory>$(MSBuildProjectDirectory)</RunWorkingDirectory> diff --git a/src/EllieBot/Modules/Games/Fish/FishConfigService.cs b/src/EllieBot/Modules/Games/Fish/FishConfigService.cs index efabc52..8383aa7 100644 --- a/src/EllieBot/Modules/Games/Fish/FishConfigService.cs +++ b/src/EllieBot/Modules/Games/Fish/FishConfigService.cs @@ -15,17 +15,91 @@ public sealed class FishConfigService : ConfigServiceBase<FishConfig> IPubSub pubSub) : base(FILE_PATH, serializer, pubSub, _changeKey) { + AddParsedProp("captcha", + static (conf) => conf.RequireCaptcha, + bool.TryParse, + ConfigPrinters.ToString); + + AddParsedProp("chance.nothing", + static (conf) => conf.Chance.Nothing, + int.TryParse, + ConfigPrinters.ToString); + + AddParsedProp("chance.fish", + static (conf) => conf.Chance.Fish, + int.TryParse, + ConfigPrinters.ToString); + + AddParsedProp("chance.trash", + static (conf) => conf.Chance.Trash, + int.TryParse, + ConfigPrinters.ToString); + Migrate(); } private void Migrate() { - if (data.Version < 2) + if (data.Version < 11) { ModifyConfig(c => { - c.Version = 2; - c.RequireCaptcha = true; + c.Version = 11; + if (c.Items is { Count: > 0 }) + return; + c.Items = + [ + new FishItem + { + Id = 1, + ItemType = FishItemType.Pole, + Name = "Wooden Rod", + Description = "Better than catching it with bare hands.", + Price = 1000, + FishMultiplier = 1.2 + }, + new FishItem + { + Id = 11, + ItemType = FishItemType.Pole, + Name = "Magnet on a Stick", + Description = "Attracts all trash, not just metal.", + Price = 3000, + FishMultiplier = 0.9, + TrashMultiplier = 2 + }, + new FishItem + { + Id = 21, + ItemType = FishItemType.Bait, + Name = "Corn", + Description = "Just some cooked corn.", + Price = 100, + Uses = 100, + RareMultiplier = 1.1 + }, + new FishItem + { + Id = 31, + ItemType = FishItemType.Potion, + Name = "A Cup of Tea", + Description = "Helps you focus.", + Price = 12000, + DurationMinutes = 30, + MaxStarMultiplier = 1.1, + FishingSpeedMultiplier = 1.01 + }, + new FishItem + { + Id = 41, + ItemType = FishItemType.Boat, + Name = "Canoe", + Description = "Lets you fish a little faster.", + Price = 3000, + FishingSpeedMultiplier = 1.201, + MaxStarMultiplier = 1.1 + } + ]; }); } } diff --git a/src/EllieBot/Modules/Games/Hangman/DefaultHangmanSource.cs b/src/EllieBot/Modules/Games/Hangman/DefaultHangmanSource.cs index 333e8f0..8509e4b 100644 --- a/src/EllieBot/Modules/Games/Hangman/DefaultHangmanSource.cs +++ b/src/EllieBot/Modules/Games/Hangman/DefaultHangmanSource.cs @@ -44,7 +44,7 @@ public sealed class DefaultHangmanSource : IHangmanSource public IReadOnlyCollection<string> GetCategories() => termsDict.Keys.ToList(); - public bool GetTerm(string? category, [NotNullWhen(true)] out HangmanTerm? term) + public bool GetTerm(string? category, [NotNullWhen(true)] out (HangmanTerm Term, string Category)? term) { if (category is null) { @@ -54,7 +54,7 @@ public sealed class DefaultHangmanSource : IHangmanSource if (termsDict.TryGetValue(category, out var terms)) { - term = terms[_rng.Next(0, terms.Length)]; + term = (terms[_rng.Next(0, terms.Length)], category); return true; } diff --git a/src/EllieBot/Modules/Games/Hangman/HangmanCommands.cs b/src/EllieBot/Modules/Games/Hangman/HangmanCommands.cs index 12e29d2..e85fa55 100644 --- a/src/EllieBot/Modules/Games/Hangman/HangmanCommands.cs +++ b/src/EllieBot/Modules/Games/Hangman/HangmanCommands.cs @@ -10,7 +10,8 @@ public partial class Games [Cmd] [RequireContext(ContextType.Guild)] public async Task Hangmanlist() - => await Response().Confirm(GetText(strs.hangman_types(prefix)), _service.GetHangmanTypes().Join('\n')) + => await Response() + .Confirm(GetText(strs.hangman_types(prefix)), _service.GetHangmanTypes().Join('\n')) .SendAsync(); private static string Draw(HangmanGame.State state) @@ -35,29 +36,25 @@ public partial class Games public static EmbedBuilder GetEmbed(IMessageSenderService sender, HangmanGame.State state) { + var eb = sender.CreateEmbed() + .WithOkColor() + .AddField("Hangman", Draw(state)) + .AddField("Guess", Format.Code(state.Word)); + if (state.Phase == HangmanGame.Phase.Running) { - return sender.CreateEmbed() - .WithOkColor() - .AddField("Hangman", Draw(state)) - .AddField("Guess", Format.Code(state.Word)) - .WithFooter(state.MissedLetters.Join(' ')); + return eb + .WithFooter(state.MissedLetters.Join(' ')) + .WithAuthor(state.Category); } if (state.Phase == HangmanGame.Phase.Ended && state.Failed) { - return sender.CreateEmbed() - .WithErrorColor() - .AddField("Hangman", Draw(state)) - .AddField("Guess", Format.Code(state.Word)) + return eb .WithFooter(state.MissedLetters.Join(' ')); } - return sender.CreateEmbed() - .WithOkColor() - .AddField("Hangman", Draw(state)) - .AddField("Guess", Format.Code(state.Word)) - .WithFooter(state.MissedLetters.Join(' ')); + return eb.WithFooter(state.MissedLetters.Join(' ')); } [Cmd] diff --git a/src/EllieBot/Modules/Games/Hangman/HangmanGame.cs b/src/EllieBot/Modules/Games/Hangman/HangmanGame.cs index 58e779b..3b139d3 100644 --- a/src/EllieBot/Modules/Games/Hangman/HangmanGame.cs +++ b/src/EllieBot/Modules/Games/Hangman/HangmanGame.cs @@ -4,9 +4,20 @@ namespace EllieBot.Modules.Games.Hangman; public sealed class HangmanGame { - public enum GuessResult { NoAction, AlreadyTried, Incorrect, Guess, Win } + public enum GuessResult + { + NoAction, + AlreadyTried, + Incorrect, + Guess, + Win + } - public enum Phase { Running, Ended } + public enum Phase + { + Running, + Ended + } private Phase CurrentPhase { get; set; } @@ -17,16 +28,21 @@ public sealed class HangmanGame private readonly string _word; private readonly string _imageUrl; - public HangmanGame(HangmanTerm term) + public string Category { get; } + + public HangmanGame(HangmanTerm term, string cat) { _word = term.Word; _imageUrl = term.ImageUrl; + Category = cat; _remaining = _word.ToLowerInvariant().Where(x => char.IsLetter(x)).Select(char.ToLowerInvariant).ToHashSet(); } public State GetState(GuessResult guessResult = GuessResult.NoAction) - => new(_incorrect.Count, + => new( + Category, + _incorrect.Count, CurrentPhase, CurrentPhase == Phase.Ended ? _word : GetScrambledWord(), guessResult, @@ -99,6 +115,7 @@ public sealed class HangmanGame } public record State( + string Category, int Errors, Phase Phase, string Word, diff --git a/src/EllieBot/Modules/Games/Hangman/HangmanService.cs b/src/EllieBot/Modules/Games/Hangman/HangmanService.cs index c483027..dc7db68 100644 --- a/src/EllieBot/Modules/Games/Hangman/HangmanService.cs +++ b/src/EllieBot/Modules/Games/Hangman/HangmanService.cs @@ -36,10 +36,10 @@ public sealed class HangmanService : IHangmanService, IExecNoCommand public bool StartHangman(ulong channelId, string? category, [NotNullWhen(true)] out HangmanGame.State? state) { state = null; - if (!_source.GetTerm(category, out var term)) + if (!_source.GetTerm(category, out var termData)) return false; - var game = new HangmanGame(term); + var game = new HangmanGame(termData.Value.Term, termData.Value.Category); lock (_locker) { var hc = _hangmanGames.GetOrAdd(channelId, game); diff --git a/src/EllieBot/Modules/Games/Hangman/HangmanTerm.cs b/src/EllieBot/Modules/Games/Hangman/HangmanTerm.cs index 22e5144..edb88f7 100644 --- a/src/EllieBot/Modules/Games/Hangman/HangmanTerm.cs +++ b/src/EllieBot/Modules/Games/Hangman/HangmanTerm.cs @@ -1,4 +1,6 @@ #nullable disable +using YamlDotNet.Serialization; + namespace EllieBot.Modules.Games.Hangman; public sealed class HangmanTerm diff --git a/src/EllieBot/Modules/Games/Hangman/IHangmanSource.cs b/src/EllieBot/Modules/Games/Hangman/IHangmanSource.cs index d28199b..04b3c03 100644 --- a/src/EllieBot/Modules/Games/Hangman/IHangmanSource.cs +++ b/src/EllieBot/Modules/Games/Hangman/IHangmanSource.cs @@ -6,5 +6,5 @@ public interface IHangmanSource : IEService { public IReadOnlyCollection<string> GetCategories(); public void Reload(); - public bool GetTerm(string? category, [NotNullWhen(true)] out HangmanTerm? term); + public bool GetTerm(string? category, [NotNullWhen(true)] out (HangmanTerm Term, string Category)? term); } \ No newline at end of file diff --git a/src/EllieBot/Modules/Searches/Feeds/FeedCommands.cs b/src/EllieBot/Modules/Searches/Feeds/FeedCommands.cs index 95e83aa..4984ed4 100644 --- a/src/EllieBot/Modules/Searches/Feeds/FeedCommands.cs +++ b/src/EllieBot/Modules/Searches/Feeds/FeedCommands.cs @@ -53,6 +53,8 @@ public partial class Searches [Priority(1)] public async Task Feed(string url, ITextChannel? channel = null, [Leftover] string? message = null) { + await ctx.Channel.TriggerTypingAsync(); + if (!Uri.TryCreate(url, UriKind.Absolute, out var uri) || (uri.Scheme != Uri.UriSchemeHttp && uri.Scheme != Uri.UriSchemeHttps)) { @@ -61,7 +63,7 @@ public partial class Searches } channel ??= (ITextChannel)ctx.Channel; - + if (!((IGuildUser)ctx.User).GetPermissions(channel).MentionEveryone) message = message?.SanitizeAllMentions(); @@ -79,7 +81,7 @@ public partial class Searches if (ctx.User is not IGuildUser gu || !gu.GuildPermissions.Administrator) message = message?.SanitizeMentions(true); - var result = _service.AddFeed(ctx.Guild.Id, channel.Id, url, message); + var result = await _service.AddFeedAsync(ctx.Guild.Id, channel.Id, url, message); if (result == FeedAddResult.Success) { await Response().Confirm(strs.feed_added).SendAsync(); @@ -117,32 +119,32 @@ public partial class Searches { if (--page < 0) return; - + var feeds = _service.GetFeeds(ctx.Guild.Id); if (!feeds.Any()) { await Response() - .Embed(CreateEmbed().WithOkColor().WithDescription(GetText(strs.feed_no_feed))) - .SendAsync(); + .Embed(CreateEmbed().WithOkColor().WithDescription(GetText(strs.feed_no_feed))) + .SendAsync(); return; } await Response() - .Paginated() - .Items(feeds) - .PageSize(10) - .CurrentPage(page) - .Page((items, cur) => - { - var embed = CreateEmbed().WithOkColor(); - var i = 0; - var fs = string.Join("\n", - items.Select(x => $"`{(cur * 10) + ++i}.` <#{x.ChannelId}> {x.Url}")); + .Paginated() + .Items(feeds) + .PageSize(10) + .CurrentPage(page) + .Page((items, cur) => + { + var embed = CreateEmbed().WithOkColor(); + var i = 0; + var fs = string.Join("\n", + items.Select(x => $"`{(cur * 10) + ++i}.` <#{x.ChannelId}> {x.Url}")); - return embed.WithDescription(fs); - }) - .SendAsync(); + return embed.WithDescription(fs); + }) + .SendAsync(); } } } \ No newline at end of file diff --git a/src/EllieBot/Modules/Searches/Feeds/FeedsService.cs b/src/EllieBot/Modules/Searches/Feeds/FeedsService.cs index a3d9db2..3240ec4 100644 --- a/src/EllieBot/Modules/Searches/Feeds/FeedsService.cs +++ b/src/EllieBot/Modules/Searches/Feeds/FeedsService.cs @@ -40,13 +40,13 @@ public class FeedsService : IEService, IReadyExecutor await using (var uow = _db.GetDbContext()) { var subs = await uow.Set<FeedSub>() - .AsQueryable() - .Where(x => Queries.GuildOnShard(x.GuildId, _shardData.TotalShards, _shardData.ShardId)) - .ToListAsyncLinqToDB(); + .AsQueryable() + .Where(x => Queries.GuildOnShard(x.GuildId, _shardData.TotalShards, _shardData.ShardId)) + .ToListAsyncLinqToDB(); _subs = subs - .GroupBy(x => x.Url.ToLower()) - .ToDictionary(x => x.Key, x => x.ToList()) - .ToConcurrent(); + .GroupBy(x => x.Url.ToLower()) + .ToDictionary(x => x.Key, x => x.ToList()) + .ToConcurrent(); } await TrackFeeds(); @@ -66,7 +66,7 @@ public class FeedsService : IEService, IReadyExecutor // remove from db await using var ctx = _db.GetDbContext(); await ctx.GetTable<FeedSub>() - .DeleteAsync(x => ids.Contains(x.Id)); + .DeleteAsync(x => ids.Contains(x.Id)); // remove from the local cache _subs.TryRemove(url, out _); @@ -163,12 +163,12 @@ public class FeedsService : IEService, IReadyExecutor if (!gotImage && feedItem.SpecificItem is AtomFeedItem afi) { var previewElement = afi.Element.Elements() - .FirstOrDefault(x => x.Name.LocalName == "preview"); + .FirstOrDefault(x => x.Name.LocalName == "preview"); if (previewElement is null) { previewElement = afi.Element.Elements() - .FirstOrDefault(x => x.Name.LocalName == "thumbnail"); + .FirstOrDefault(x => x.Name.LocalName == "thumbnail"); } if (previewElement is not null) @@ -201,11 +201,11 @@ public class FeedsService : IEService, IReadyExecutor continue; var sendTask = _sender.Response(ch) - .Embed(embed) - .Text(string.IsNullOrWhiteSpace(val.Message) - ? string.Empty - : val.Message) - .SendAsync(); + .Embed(embed) + .Text(string.IsNullOrWhiteSpace(val.Message) + ? string.Empty + : val.Message) + .SendAsync(); tasks.Add(sendTask); } @@ -236,12 +236,14 @@ public class FeedsService : IEService, IReadyExecutor using var uow = _db.GetDbContext(); return uow.GetTable<FeedSub>() - .Where(x => x.GuildId == guildId) - .OrderBy(x => x.Id) - .ToList(); + .Where(x => x.GuildId == guildId) + .OrderBy(x => x.Id) + .ToList(); } - public FeedAddResult AddFeed( + private const int MAX_FEEDS = 10; + + public async Task<FeedAddResult> AddFeedAsync( ulong guildId, ulong channelId, string rssFeed, @@ -249,25 +251,24 @@ public class FeedsService : IEService, IReadyExecutor { ArgumentNullException.ThrowIfNull(rssFeed, nameof(rssFeed)); - var fs = new FeedSub - { - ChannelId = channelId, - Url = rssFeed.Trim(), - Message = message - }; - - using var uow = _db.GetDbContext(); - var feeds = uow.GetTable<FeedSub>() - .Where(x => x.GuildId == guildId) - .ToArray(); - - if (feeds.Any(x => x.Url.ToLower() == fs.Url.ToLower())) + await using var uow = _db.GetDbContext(); + var feedUrl = rssFeed.Trim(); + if (await uow.GetTable<FeedSub>().AnyAsyncLinqToDB(x => x.GuildId == guildId && + x.Url.ToLower() == feedUrl.ToLower())) return FeedAddResult.Duplicate; - if (feeds.Length >= 10) + + var count = await uow.GetTable<FeedSub>().CountAsyncLinqToDB(x => x.GuildId == guildId); + if (count >= MAX_FEEDS) return FeedAddResult.LimitReached; - uow.Add(fs); - uow.SaveChanges(); + var fs = await uow.GetTable<FeedSub>() + .InsertWithOutputAsync(() => new FeedSub + { + GuildId = guildId, + ChannelId = channelId, + Url = feedUrl, + Message = message, + }); _subs.AddOrUpdate(fs.Url.ToLower(), [fs], @@ -293,10 +294,7 @@ public class FeedsService : IEService, IReadyExecutor var toRemove = items[index]; _subs.AddOrUpdate(toRemove.Url.ToLower(), [], - (_, old) => - { - return old.Where(x => x.Id != toRemove.Id).ToList(); - }); + (_, old) => { return old.Where(x => x.Id != toRemove.Id).ToList(); }); uow.Remove(toRemove); uow.SaveChanges(); diff --git a/src/EllieBot/data/fish.yml b/src/EllieBot/data/fish.yml index e04be6a..b2504bc 100644 --- a/src/EllieBot/data/fish.yml +++ b/src/EllieBot/data/fish.yml @@ -1,5 +1,5 @@ # DO NOT CHANGE -version: 2 +version: 11 weatherSeed: w%29';^eGE)9oWHM(aI9I;%1[.r^z2ZS7ShV,l')o(e%#"hVzb>oxQq^`.&/7srh requireCaptcha: true starEmojis: @@ -45,40 +45,66 @@ trash: items: - id: 1 itemType: Pole - name: "Wooden Rod" - description: "Better than catching it with bare hands." + name: Wooden Rod + emoji: '' + description: Better than catching it with bare hands. price: 1000 + uses: + durationMinutes: fishMultiplier: 1.2 - + trashMultiplier: + maxStarMultiplier: + rareMultiplier: + fishingSpeedMultiplier: - id: 11 itemType: Pole name: Magnet on a Stick - description: "Attracts all trash, not just metal." + emoji: '' + description: Attracts all trash, not just metal. price: 3000 + uses: + durationMinutes: fishMultiplier: 0.9 trashMultiplier: 2 - + maxStarMultiplier: + rareMultiplier: + fishingSpeedMultiplier: - id: 21 itemType: Bait - name: "Corn" - description: "Just some cooked corn." + name: Corn + emoji: '' + description: Just some cooked corn. price: 100 uses: 100 + durationMinutes: + fishMultiplier: + trashMultiplier: + maxStarMultiplier: rareMultiplier: 1.1 - + fishingSpeedMultiplier: - id: 31 itemType: Potion - name: "A Cup of Tea" - description: "Helps you focus." + name: A Cup of Tea + emoji: '' + description: Helps you focus. price: 12000 + uses: durationMinutes: 30 + fishMultiplier: + trashMultiplier: maxStarMultiplier: 1.1 + rareMultiplier: fishingSpeedMultiplier: 1.01 - - id: 41 itemType: Boat - name: "Canoe" - description: "Lets you fish a little faster." + name: Canoe + emoji: '' + description: Lets you fish a little faster. price: 3000 + uses: + durationMinutes: + fishMultiplier: + trashMultiplier: + maxStarMultiplier: 1.1 + rareMultiplier: fishingSpeedMultiplier: 1.201 - maxStarMultiplier: 1.1 \ No newline at end of file