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.",