diff --git a/CHANGELOG.md b/CHANGELOG.md index 531e5c5..0f2ad9b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ Mostly based on [keepachangelog](https://keepachangelog.com/en/1.1.0/) except da - `.btr list` to list all button roles on the server - `.btr rm` to remove a button role from the specified message - `.btr rma` to remove all button roles on the specified message + - `.btr excl` to toggle exclusive button roles (only 1 role per message or any number) - Use `.h` on any of the above for more info - Added `.wrongsong` which will delete the last queued song. - Useful in case you made a mistake, or the bot queued a wrong song diff --git a/src/EllieBot/Db/Models/GroupName.cs b/src/EllieBot/Db/Models/SarGroup.cs similarity index 100% rename from src/EllieBot/Db/Models/GroupName.cs rename to src/EllieBot/Db/Models/SarGroup.cs diff --git a/src/EllieBot/Db/Models/btnrole/ButtonRole.cs b/src/EllieBot/Db/Models/btnrole/ButtonRole.cs index 64c3ef8..053ea2d 100644 --- a/src/EllieBot/Db/Models/btnrole/ButtonRole.cs +++ b/src/EllieBot/Db/Models/btnrole/ButtonRole.cs @@ -22,4 +22,6 @@ public sealed class ButtonRole [MaxLength(50)] public string Label { get; set; } = string.Empty; + + public bool Exclusive { get; set; } } diff --git a/src/EllieBot/Migrations/PostgreSql/20241127120348_guildcolors.Designer.cs b/src/EllieBot/Migrations/PostgreSql/20241126033634_btnroles_guildcolors.Designer.cs similarity index 99% rename from src/EllieBot/Migrations/PostgreSql/20241127120348_guildcolors.Designer.cs rename to src/EllieBot/Migrations/PostgreSql/20241126033634_btnroles_guildcolors.Designer.cs index 478a5a8..0dd7bfb 100644 --- a/src/EllieBot/Migrations/PostgreSql/20241127120348_guildcolors.Designer.cs +++ b/src/EllieBot/Migrations/PostgreSql/20241126033634_btnroles_guildcolors.Designer.cs @@ -12,8 +12,8 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; namespace EllieBot.Migrations.PostgreSql { [DbContext(typeof(PostgreSqlContext))] - [Migration("20241127120348_guildcolors")] - partial class guildcolors + [Migration("20241126033634_btnroles_guildcolors")] + partial class btnroles_guildcolors { /// protected override void BuildTargetModel(ModelBuilder modelBuilder) @@ -479,6 +479,10 @@ namespace EllieBot.Migrations.PostgreSql .HasColumnType("character varying(100)") .HasColumnName("emote"); + b.Property("Exclusive") + .HasColumnType("boolean") + .HasColumnName("exclusive"); + b.Property("GuildId") .HasColumnType("numeric(20,0)") .HasColumnName("guildid"); diff --git a/src/EllieBot/Migrations/PostgreSql/20241127120348_guildcolors.cs b/src/EllieBot/Migrations/PostgreSql/20241126033634_btnroles_guildcolors.cs similarity index 94% rename from src/EllieBot/Migrations/PostgreSql/20241127120348_guildcolors.cs rename to src/EllieBot/Migrations/PostgreSql/20241126033634_btnroles_guildcolors.cs index 66c373b..60829d1 100644 --- a/src/EllieBot/Migrations/PostgreSql/20241127120348_guildcolors.cs +++ b/src/EllieBot/Migrations/PostgreSql/20241126033634_btnroles_guildcolors.cs @@ -6,7 +6,7 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; namespace EllieBot.Migrations.PostgreSql { /// - public partial class guildcolors : Migration + public partial class btnroles_guildcolors : Migration { /// protected override void Up(MigrationBuilder migrationBuilder) @@ -24,7 +24,8 @@ namespace EllieBot.Migrations.PostgreSql position = table.Column(type: "integer", nullable: false), roleid = table.Column(type: "numeric(20,0)", nullable: false), emote = table.Column(type: "character varying(100)", maxLength: 100, nullable: false), - label = table.Column(type: "character varying(50)", maxLength: 50, nullable: false) + label = table.Column(type: "character varying(50)", maxLength: 50, nullable: false), + exclusive = table.Column(type: "boolean", nullable: false) }, constraints: table => { diff --git a/src/EllieBot/Migrations/PostgreSql/PostgreSqlContextModelSnapshot.cs b/src/EllieBot/Migrations/PostgreSql/PostgreSqlContextModelSnapshot.cs index 4053ed2..b340473 100644 --- a/src/EllieBot/Migrations/PostgreSql/PostgreSqlContextModelSnapshot.cs +++ b/src/EllieBot/Migrations/PostgreSql/PostgreSqlContextModelSnapshot.cs @@ -476,6 +476,10 @@ namespace EllieBot.Migrations.PostgreSql .HasColumnType("character varying(100)") .HasColumnName("emote"); + b.Property("Exclusive") + .HasColumnType("boolean") + .HasColumnName("exclusive"); + b.Property("GuildId") .HasColumnType("numeric(20,0)") .HasColumnName("guildid"); diff --git a/src/EllieBot/Migrations/Sqlite/20241127120303_guildcolors.Designer.cs b/src/EllieBot/Migrations/Sqlite/20241126033626_btnroles_guildcolors.Designer.cs similarity index 99% rename from src/EllieBot/Migrations/Sqlite/20241127120303_guildcolors.Designer.cs rename to src/EllieBot/Migrations/Sqlite/20241126033626_btnroles_guildcolors.Designer.cs index d2e42c9..a6badf9 100644 --- a/src/EllieBot/Migrations/Sqlite/20241127120303_guildcolors.Designer.cs +++ b/src/EllieBot/Migrations/Sqlite/20241126033626_btnroles_guildcolors.Designer.cs @@ -11,8 +11,8 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion; namespace EllieBot.Migrations { [DbContext(typeof(SqliteContext))] - [Migration("20241127120303_guildcolors")] - partial class guildcolors + [Migration("20241126033626_btnroles_guildcolors")] + partial class btnroles_guildcolors { /// protected override void BuildTargetModel(ModelBuilder modelBuilder) @@ -357,6 +357,9 @@ namespace EllieBot.Migrations .HasMaxLength(100) .HasColumnType("TEXT"); + b.Property("Exclusive") + .HasColumnType("INTEGER"); + b.Property("GuildId") .HasColumnType("INTEGER"); diff --git a/src/EllieBot/Migrations/Sqlite/20241127120303_guildcolors.cs b/src/EllieBot/Migrations/Sqlite/20241126033626_btnroles_guildcolors.cs similarity index 94% rename from src/EllieBot/Migrations/Sqlite/20241127120303_guildcolors.cs rename to src/EllieBot/Migrations/Sqlite/20241126033626_btnroles_guildcolors.cs index 9f7ac5d..285bd6b 100644 --- a/src/EllieBot/Migrations/Sqlite/20241127120303_guildcolors.cs +++ b/src/EllieBot/Migrations/Sqlite/20241126033626_btnroles_guildcolors.cs @@ -5,7 +5,7 @@ namespace EllieBot.Migrations { /// - public partial class guildcolors : Migration + public partial class btnroles_guildcolors : Migration { /// protected override void Up(MigrationBuilder migrationBuilder) @@ -23,7 +23,8 @@ namespace EllieBot.Migrations Position = table.Column(type: "INTEGER", nullable: false), RoleId = table.Column(type: "INTEGER", nullable: false), Emote = table.Column(type: "TEXT", maxLength: 100, nullable: false), - Label = table.Column(type: "TEXT", maxLength: 50, nullable: false) + Label = table.Column(type: "TEXT", maxLength: 50, nullable: false), + Exclusive = table.Column(type: "INTEGER", nullable: false) }, constraints: table => { diff --git a/src/EllieBot/Migrations/Sqlite/EllieSqliteContextModelSnapshot.cs b/src/EllieBot/Migrations/Sqlite/EllieSqliteContextModelSnapshot.cs index 981f2b7..d8edf97 100644 --- a/src/EllieBot/Migrations/Sqlite/EllieSqliteContextModelSnapshot.cs +++ b/src/EllieBot/Migrations/Sqlite/EllieSqliteContextModelSnapshot.cs @@ -354,6 +354,9 @@ namespace EllieBot.Migrations .HasMaxLength(100) .HasColumnType("TEXT"); + b.Property("Exclusive") + .HasColumnType("INTEGER"); + b.Property("GuildId") .HasColumnType("INTEGER"); diff --git a/src/EllieBot/Modules/Administration/Role/ButtonRolesCommands.cs b/src/EllieBot/Modules/Administration/Role/ButtonRolesCommands.cs index 626f556..4896c0a 100644 --- a/src/EllieBot/Modules/Administration/Role/ButtonRolesCommands.cs +++ b/src/EllieBot/Modules/Administration/Role/ButtonRolesCommands.cs @@ -1,4 +1,5 @@ -using EllieBot.Db.Models; +using EllieBot.Common.TypeReaders.Models; +using EllieBot.Db.Models; using EllieBot.Modules.Administration.Services; using System.Text; using ContextType = Discord.Commands.ContextType; @@ -7,6 +8,7 @@ namespace EllieBot.Modules.Administration; public partial class Administration { + [Group("btr")] public partial class ButtonRoleCommands : EllieModule { private List GetActionRows(IReadOnlyList roles) @@ -265,5 +267,30 @@ public partial class Administration }) .SendAsync(); } + + [Cmd] + [RequireContext(ContextType.Guild)] + [BotPerm(GuildPerm.ManageRoles)] + [RequireUserPermission(GuildPerm.ManageRoles)] + public Task BtnRoleExclusive(MessageLink link, PermissionAction exclusive) + => BtnRoleExclusive(link.Message.Id, exclusive); + + [Cmd] + [RequireContext(ContextType.Guild)] + [BotPerm(GuildPerm.ManageRoles)] + [RequireUserPermission(GuildPerm.ManageRoles)] + public async Task BtnRoleExclusive(ulong messageId, PermissionAction exclusive) + { + var res = await _service.SetExclusiveButtonRoles(ctx.Guild.Id, messageId, exclusive.Value); + + if (res) + { + await Response().Confirm(strs.btnrole_exclusive).SendAsync(); + } + else + { + await Response().Confirm(strs.btnrole_multiple).SendAsync(); + } + } } } \ No newline at end of file diff --git a/src/EllieBot/Modules/Administration/Role/ButtonRolesService.cs b/src/EllieBot/Modules/Administration/Role/ButtonRolesService.cs index 6369d72..3dc076b 100644 --- a/src/EllieBot/Modules/Administration/Role/ButtonRolesService.cs +++ b/src/EllieBot/Modules/Administration/Role/ButtonRolesService.cs @@ -42,32 +42,49 @@ public sealed class ButtonRolesService : IEService, IReadyExecutor _ = Task.Run(async () => { - await using var uow = _db.GetDbContext(); - var buttonRole = await uow.GetTable() - .Where(x => x.ButtonId == smc.Data.CustomId && x.MessageId == smc.Message.Id) - .FirstOrDefaultAsyncLinqToDB(); - - if (buttonRole is null) - return; - - var guild = _client.GetGuild(buttonRole.GuildId); - if (guild is null) - return; - - var role = guild.GetRole(buttonRole.RoleId); - if (role is null) - return; - - if (smc.User is not IGuildUser user) - return; - - if (user.GetRoles().Any(x => x.Id == role.Id)) + try { - await user.RemoveRoleAsync(role.Id); - return; - } + await using var uow = _db.GetDbContext(); + var buttonRole = await uow.GetTable() + .Where(x => x.ButtonId == smc.Data.CustomId && x.MessageId == smc.Message.Id) + .FirstOrDefaultAsyncLinqToDB(); - await user.AddRoleAsync(role.Id); + if (buttonRole is null) + return; + + var guild = _client.GetGuild(buttonRole.GuildId); + if (guild is null) + return; + + var role = guild.GetRole(buttonRole.RoleId); + if (role is null) + return; + + if (smc.User is not IGuildUser user) + return; + + if (user.GetRoles().Any(x => x.Id == role.Id)) + { + await user.RemoveRoleAsync(role.Id); + return; + } + + if (buttonRole.Exclusive) + { + var otherRoles = await uow.GetTable() + .Where(x => x.GuildId == smc.GuildId && x.MessageId == smc.Message.Id) + .Select(x => x.RoleId) + .ToListAsyncLinqToDB(); + + await user.RemoveRolesAsync(otherRoles); + } + + await user.AddRoleAsync(role.Id); + } + catch (Exception ex) + { + Log.Warning(ex, "Unable to handle button role interaction for user {UserId}", inter.User.Id); + } }); } @@ -108,7 +125,8 @@ public sealed class ButtonRolesService : IEService, IReadyExecutor : 1, Emote = emoteStr, Label = string.Empty, - ButtonId = $"{BTN_PREFIX}:{guildId}:{guid}" + ButtonId = $"{BTN_PREFIX}:{guildId}:{guid}", + Exclusive = (uow.GetTable().Where(x => x.GuildId == guildId && x.MessageId == messageId).All(x => x.Exclusive)) }, _ => new() { @@ -151,4 +169,16 @@ public sealed class ButtonRolesService : IEService, IReadyExecutor .OrderBy(x => x.Id) .ToListAsyncLinqToDB(); } -} + + public async Task SetExclusiveButtonRoles(ulong guildId, ulong messageId, bool exclusive) + { + await using var uow = _db.GetDbContext(); + return await uow.GetTable() + .Where(x => x.GuildId == guildId && x.MessageId == messageId) + .UpdateAsync((_) => new() + { + Exclusive = exclusive + }) + > 0; + } +} \ No newline at end of file diff --git a/src/EllieBot/Modules/Gambling/Draw/DrawCommands.cs b/src/EllieBot/Modules/Gambling/Draw/DrawCommands.cs index cbb9de2..ff0c94b 100644 --- a/src/EllieBot/Modules/Gambling/Draw/DrawCommands.cs +++ b/src/EllieBot/Modules/Gambling/Draw/DrawCommands.cs @@ -57,7 +57,7 @@ public partial class Gambling i.Dispose(); var eb = CreateEmbed() - .WithOkColor(); + .WithOkColor(); var toSend = string.Empty; if (cardObjects.Count == 5) @@ -172,13 +172,14 @@ public partial class Gambling } var eb = CreateEmbed() - .WithOkColor() - .WithAuthor(ctx.User) - .WithDescription(result.Card.GetEmoji()) - .AddField(GetText(strs.guess), GetGuessInfo(val, col), true) - .AddField(GetText(strs.card), GetCardInfo(result.Card), true) - .AddField(GetText(strs.won), N((long)result.Won), false) - .WithImageUrl("attachment://card.png"); + .WithOkColor() + .WithAuthor(ctx.User) + .WithDescription(result.Card.GetEmoji()) + .AddField(GetText(strs.guess), GetGuessInfo(val, col), true) + .AddField(GetText(strs.card), GetCardInfo(result.Card), false) + .AddField(GetText(strs.bet), N(amount), true) + .AddField(GetText(strs.won), N((long)result.Won), true) + .WithImageUrl("attachment://card.png"); using var img = await GetCardImageAsync(result.Card); await using var imgStream = await img.ToStreamAsync(); diff --git a/src/EllieBot/Modules/Gambling/FlipCoin/FlipCoinCommands.cs b/src/EllieBot/Modules/Gambling/FlipCoin/FlipCoinCommands.cs index f297d4f..7003bcd 100644 --- a/src/EllieBot/Modules/Gambling/FlipCoin/FlipCoinCommands.cs +++ b/src/EllieBot/Modules/Gambling/FlipCoin/FlipCoinCommands.cs @@ -85,10 +85,10 @@ public partial class Gambling : Format.Bold(GetText(strs.tails)))); var eb = CreateEmbed() - .WithOkColor() - .WithAuthor(ctx.User) - .WithDescription(msg) - .WithImageUrl($"attachment://{imgName}"); + .WithOkColor() + .WithAuthor(ctx.User) + .WithDescription(msg) + .WithImageUrl($"attachment://{imgName}"); await ctx.Channel.SendFileAsync(stream, imgName, @@ -123,18 +123,22 @@ public partial class Gambling var won = (long)result.Won; if (won > 0) { - str = Format.Bold(GetText(strs.flip_guess(N(won)))); + str = Format.Bold(GetText(strs.betflip_guess)); } else { str = Format.Bold(GetText(strs.better_luck)); } - await Response().Embed(CreateEmbed() - .WithAuthor(ctx.User) - .WithDescription(str) - .WithOkColor() - .WithImageUrl(imageToSend.ToString())).SendAsync(); + await Response() + .Embed(CreateEmbed() + .WithAuthor(ctx.User) + .WithDescription(str) + .AddField(GetText(strs.bet), N(amount), true) + .AddField(GetText(strs.won), N((long)result.Won), true) + .WithOkColor() + .WithImageUrl(imageToSend.ToString())) + .SendAsync(); } } } \ No newline at end of file diff --git a/src/EllieBot/Modules/Gambling/Gambling.cs b/src/EllieBot/Modules/Gambling/Gambling.cs index 3815137..249060f 100644 --- a/src/EllieBot/Modules/Gambling/Gambling.cs +++ b/src/EllieBot/Modules/Gambling/Gambling.cs @@ -718,7 +718,7 @@ public partial class Gambling : GamblingModule string str; if (win > 0) { - str = GetText(strs.br_win(N(win), result.Threshold + (result.Roll == 100 ? " 👑" : ""))); + str = GetText(strs.betroll_win(result.Threshold + (result.Roll == 100 ? " 👑" : ""))); } else { @@ -728,7 +728,9 @@ public partial class Gambling : GamblingModule var eb = CreateEmbed() .WithAuthor(ctx.User) .WithDescription(Format.Bold(str)) - .AddField(GetText(strs.roll2), result.Roll.ToString(CultureInfo.InvariantCulture)) + .AddField(GetText(strs.roll2), result.Roll.ToString(CultureInfo.InvariantCulture), true) + .AddField(GetText(strs.bet), N(amount), true) + .AddField(GetText(strs.won), N((long)result.Won), true) .WithOkColor(); await Response().Embed(eb).SendAsync(); @@ -922,8 +924,8 @@ public partial class Gambling : GamblingModule var eb = CreateEmbed() .WithOkColor() .WithDescription(sb.ToString()) - .AddField(GetText(strs.multiplier), $"{result.Multiplier:0.##}x", true) - .AddField(GetText(strs.won), $"{(long)result.Won}", true) + .AddField(GetText(strs.bet), N(amount), true) + .AddField(GetText(strs.won), $"{N((long)result.Won)}", true) .WithAuthor(ctx.User); diff --git a/src/EllieBot/Modules/Gambling/Slot/SlotCommands.cs b/src/EllieBot/Modules/Gambling/Slot/SlotCommands.cs index 6cda98d..c38f646 100644 --- a/src/EllieBot/Modules/Gambling/Slot/SlotCommands.cs +++ b/src/EllieBot/Modules/Gambling/Slot/SlotCommands.cs @@ -66,10 +66,10 @@ public partial class Gambling var eb = CreateEmbed() - .WithAuthor(ctx.User) - .WithDescription(Format.Bold(text)) - .WithImageUrl($"attachment://result.png") - .WithOkColor(); + .WithAuthor(ctx.User) + .WithDescription(Format.Bold(text)) + .WithImageUrl($"attachment://result.png") + .WithOkColor(); var bb = new ButtonBuilder(emote: Emoji.Parse("🔁"), customId: "slot:again", label: "Pull Again"); var inter = _inter.Create(ctx.User.Id, @@ -168,7 +168,8 @@ public partial class Gambling await using (var uow = _db.GetDbContext()) { ownedAmount = uow.Set() - .FirstOrDefault(x => x.UserId == ctx.User.Id)?.CurrencyAmount + .FirstOrDefault(x => x.UserId == ctx.User.Id) + ?.CurrencyAmount ?? 0; } diff --git a/src/EllieBot/data/aliases.yml b/src/EllieBot/data/aliases.yml index f2c4a72..fd20da5 100644 --- a/src/EllieBot/data/aliases.yml +++ b/src/EllieBot/data/aliases.yml @@ -1499,18 +1499,22 @@ snipe: - snipe - sn btnroleadd: - - btnradd - - btnra + - add + - a btnroleremove: - - btnrrem - - btnrr - - btnrrm + - rem + - r + - rm btnroleremoveall: - - btnrremall - - btnrra + - remall + - rma btnrolelist: - - btnrlist - - btnrl + - list + - l + - ls +btnroleexclusive: + - excl + - e wrongsong: - wrongsong - wrongtrack diff --git a/src/EllieBot/data/strings/commands/commands.en-US.yml b/src/EllieBot/data/strings/commands/commands.en-US.yml index 8fd6ea9..8539adf 100644 --- a/src/EllieBot/data/strings/commands/commands.en-US.yml +++ b/src/EllieBot/data/strings/commands/commands.en-US.yml @@ -4769,6 +4769,19 @@ btnrolelist: - '' params: - { } +btnroleexclusive: + desc: |- + Toggles whether button roles are exclusive or not. + If enabled, users can only pick one role from the buttons per message. + If disabled, users can pick any number of roles. + ex: + - '123123123 enable' + - '123123123 disable' + params: + - message: + desc: "A message link or id of the message" + enable: + desc: "Whether to enable or disable exclusive button roles" wrongsong: desc: |- Removes the last queued song. diff --git a/src/EllieBot/data/strings/responses/responses.en-US.json b/src/EllieBot/data/strings/responses/responses.en-US.json index 0d99314..e7b8ae8 100644 --- a/src/EllieBot/data/strings/responses/responses.en-US.json +++ b/src/EllieBot/data/strings/responses/responses.en-US.json @@ -238,10 +238,10 @@ "sb_user": "User soft-banned", "awarded": "{2} has awarded {0} to {1}", "better_luck": "Better luck next time ^_^", - "br_win": "Congratulations! You won {0} for rolling above {1}", + "betroll_win": "Congratulations! You rolled above {0}", "deck_reshuffled": "Deck reshuffled.", "flipped": "Flipped {0}", - "flip_guess": "You guessed it! You won {0}", + "betflip_guess": "You guessed it!", "flip_invalid": "Invalid number specified. You can flip 1 to {0} coins.", "flip_results": "Flipped {0} coins. {1} heads, {2} tails.", "cards_left": "{0} cards left in the deck.", @@ -266,7 +266,8 @@ "available_tests": "Available Tests", "test_results_for": "Test results for {0}", "won": "Won", - "multiplier": "Multiplier", + "bet": "Bet", + "multi": "Multi", "tails": "Tail", "take": "successfully took {0} from {1}", "take_fail": "was unable to take {0} from {1} because the user doesn't have that much {2}!", @@ -1129,6 +1130,8 @@ "btnrole_none": "There are no button roles on this page.", "btnrole_removeall_not_found": "Button role successfully removed but message wasn't found.", "btnrole_removed": "Button role removed.", + "btnrole_exclusive": "Users can now pick only one of the roles from that message.", + "btnrole_multiple": "Users can now pick any number of button roles from that message.", "no_last_queued_found": "No last queued track found.", "wrongsong_success": "Oops! Wrong song removed: {0}", "server_not_found": "Server not found.",