From ae338db294c8ffbe9bf57758b47ca40c0a8d9a37 Mon Sep 17 00:00:00 2001
From: Toastie <toastie@toastiet0ast.com>
Date: Thu, 28 Nov 2024 19:12:37 +1300
Subject: [PATCH] gambling commands now show amount bet. Slightly changed the
 layout. Updated some gambling strings added .btr excl

---
 CHANGELOG.md                                  |  1 +
 .../Db/Models/{GroupName.cs => SarGroup.cs}   |  0
 src/EllieBot/Db/Models/btnrole/ButtonRole.cs  |  2 +
 ...26033634_btnroles_guildcolors.Designer.cs} |  8 +-
 ...=> 20241126033634_btnroles_guildcolors.cs} |  5 +-
 .../PostgreSqlContextModelSnapshot.cs         |  4 +
 ...26033626_btnroles_guildcolors.Designer.cs} |  7 +-
 ...=> 20241126033626_btnroles_guildcolors.cs} |  5 +-
 .../Sqlite/EllieSqliteContextModelSnapshot.cs |  3 +
 .../Role/ButtonRolesCommands.cs               | 29 ++++++-
 .../Administration/Role/ButtonRolesService.cs | 82 +++++++++++++------
 .../Modules/Gambling/Draw/DrawCommands.cs     | 17 ++--
 .../Gambling/FlipCoin/FlipCoinCommands.cs     | 24 +++---
 src/EllieBot/Modules/Gambling/Gambling.cs     | 10 ++-
 .../Modules/Gambling/Slot/SlotCommands.cs     | 11 +--
 src/EllieBot/data/aliases.yml                 | 22 +++--
 .../data/strings/commands/commands.en-US.yml  | 13 +++
 .../strings/responses/responses.en-US.json    |  9 +-
 18 files changed, 178 insertions(+), 74 deletions(-)
 rename src/EllieBot/Db/Models/{GroupName.cs => SarGroup.cs} (100%)
 rename src/EllieBot/Migrations/PostgreSql/{20241127120348_guildcolors.Designer.cs => 20241126033634_btnroles_guildcolors.Designer.cs} (99%)
 rename src/EllieBot/Migrations/PostgreSql/{20241127120348_guildcolors.cs => 20241126033634_btnroles_guildcolors.cs} (94%)
 rename src/EllieBot/Migrations/Sqlite/{20241127120303_guildcolors.Designer.cs => 20241126033626_btnroles_guildcolors.Designer.cs} (99%)
 rename src/EllieBot/Migrations/Sqlite/{20241127120303_guildcolors.cs => 20241126033626_btnroles_guildcolors.cs} (94%)

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
     {
         /// <inheritdoc />
         protected override void BuildTargetModel(ModelBuilder modelBuilder)
@@ -479,6 +479,10 @@ namespace EllieBot.Migrations.PostgreSql
                         .HasColumnType("character varying(100)")
                         .HasColumnName("emote");
 
+                    b.Property<bool>("Exclusive")
+                        .HasColumnType("boolean")
+                        .HasColumnName("exclusive");
+
                     b.Property<decimal>("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
 {
     /// <inheritdoc />
-    public partial class guildcolors : Migration
+    public partial class btnroles_guildcolors : Migration
     {
         /// <inheritdoc />
         protected override void Up(MigrationBuilder migrationBuilder)
@@ -24,7 +24,8 @@ namespace EllieBot.Migrations.PostgreSql
                     position = table.Column<int>(type: "integer", nullable: false),
                     roleid = table.Column<decimal>(type: "numeric(20,0)", nullable: false),
                     emote = table.Column<string>(type: "character varying(100)", maxLength: 100, nullable: false),
-                    label = table.Column<string>(type: "character varying(50)", maxLength: 50, nullable: false)
+                    label = table.Column<string>(type: "character varying(50)", maxLength: 50, nullable: false),
+                    exclusive = table.Column<bool>(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<bool>("Exclusive")
+                        .HasColumnType("boolean")
+                        .HasColumnName("exclusive");
+
                     b.Property<decimal>("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
     {
         /// <inheritdoc />
         protected override void BuildTargetModel(ModelBuilder modelBuilder)
@@ -357,6 +357,9 @@ namespace EllieBot.Migrations
                         .HasMaxLength(100)
                         .HasColumnType("TEXT");
 
+                    b.Property<bool>("Exclusive")
+                        .HasColumnType("INTEGER");
+
                     b.Property<ulong>("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
 {
     /// <inheritdoc />
-    public partial class guildcolors : Migration
+    public partial class btnroles_guildcolors : Migration
     {
         /// <inheritdoc />
         protected override void Up(MigrationBuilder migrationBuilder)
@@ -23,7 +23,8 @@ namespace EllieBot.Migrations
                     Position = table.Column<int>(type: "INTEGER", nullable: false),
                     RoleId = table.Column<ulong>(type: "INTEGER", nullable: false),
                     Emote = table.Column<string>(type: "TEXT", maxLength: 100, nullable: false),
-                    Label = table.Column<string>(type: "TEXT", maxLength: 50, nullable: false)
+                    Label = table.Column<string>(type: "TEXT", maxLength: 50, nullable: false),
+                    Exclusive = table.Column<bool>(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<bool>("Exclusive")
+                        .HasColumnType("INTEGER");
+
                     b.Property<ulong>("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<ButtonRolesService>
     {
         private List<ActionRowBuilder> GetActionRows(IReadOnlyList<ButtonRole> 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<ButtonRole>()
-                                      .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<ButtonRole>()
+                                          .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<ButtonRole>()
+                                              .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<ButtonRole>().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<bool> SetExclusiveButtonRoles(ulong guildId, ulong messageId, bool exclusive)
+    {
+        await using var uow = _db.GetDbContext();
+        return await uow.GetTable<ButtonRole>()
+                        .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<GamblingService>
         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<GamblingService>
         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<GamblingService>
         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<DiscordUser>()
-                                 .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.",