- Re-added .qap / .queueautoplay

- Several strings and commands related to music have been changed
  - Changed .ms / .movesong to .tm / .trackmove but kept old aliases
  - Changed ~~song~~ -> 	rack throughout music module strings
- Updated CHANGELOG.md
This commit is contained in:
Kwoth
2022-02-13 15:49:48 +01:00
parent 6895c8a2a4
commit 7ed1b13e85
20 changed files with 3054 additions and 181 deletions

View File

@@ -5,8 +5,6 @@ Experimental changelog. Mostly based on [keepachangelog](https://keepachangelog.
## Unreleased
## Changes
### Added
- Added `.deleteemptyservers` command
- Added `.curtr <id>` which lets you see full information about one of your own transactions with the specified id
@@ -18,16 +16,7 @@ Experimental changelog. Mostly based on [keepachangelog](https://keepachangelog.
- waifu.decay.minPrice - Unclaimed waifus with price lower than the one specified here will not be affected by the decay
- Added `currency.transactionsLifetime` to `data/gambling.yml` Any transaction older than the number of days specified will be automatically deleted
- Added `.stock` command to check stock prices and charts
### Fixed
- Fixed an extra whitespace in usage part of command help if the command has no arguments
- Possible small fix for `.prune` ratelimiting
- `.gvc` should now properly trigger when a user is already in a gvc and changes his activity
- `.gvc` should now properly detect multiple activities
- Fixed reference to non-existent command in bot.yml
- Comment indentation in .yml files should now make more sense
- Fixed `.warn` punishments not being applied properly when using weighted warnings
- Fixed embed color when disabling `.antialt`
- Re-added `.qap / .queueautoplay`
### Changed
- CustomReactions module (and customreactions db table) has been renamed to Expressions.
@@ -41,6 +30,9 @@ Experimental changelog. Mostly based on [keepachangelog](https://keepachangelog.
- Currency output format improvement (will use guild locale now for some commands)
- `.crypto` will now also show CoinMarketCap rank
- Waifus can now be claimed for much higher prices (int -> long)
- Several strings and commands related to music have been changed
- Changed `.ms / .movesong` to `.tm / .trackmove` but kept old aliases
- Changed ~~song~~ -> `track` throughout music module strings
- Improved .curtrs (It will now have a lot more useful data in the database, show Tx ids, and be partially localized)
- [dev] Reason renamed to Note
- [dev] Added Type, Extra, OtherId fields to the database
@@ -50,20 +42,30 @@ Experimental changelog. Mostly based on [keepachangelog](https://keepachangelog.
- [dev] Permissionv2 db table renamed to Permissions
- [dev] Moved FilterWordsChannelId to a separate table
### Fixed
- Fixed an extra whitespace in usage part of command help if the command has no arguments
- Possible small fix for `.prune` ratelimiting
- `.gvc` should now properly trigger when a user is already in a gvc and changes his activity
- `.gvc` should now properly detect multiple activities
- Fixed reference to non-existent command in bot.yml
- Comment indentation in .yml files should now make more sense
- Fixed `.warn` punishments not being applied properly when using weighted warnings
- Fixed embed color when disabling `.antialt`
### Removed
- Removed `.bce` - use `.config` or `.config bot` specifically for bot config
- Removed obsolete placeholders: %users% %servers% %userfull% %username% %userdiscrim% %useravatar% %id% %uid% %chname% %cid% %sid% %members% %server_time% %shardid% %time% %mention%
- Removed some obsolete commands and strings
- Removed code which migrated 2.x to v3 credentials, settings, etc...
## [3.0.13] - 14.04.2021
## [3.0.13] - 14.01.2022
### Fixed
- Fixed `.greetdm` causing ratelimits during raids
- Fixed `.gelbooru`
## [3.0.12] - 06.01.2021
## [3.0.12] - 06.01.2022
### Fixed
- `.smch` Fixed

View File

@@ -38,6 +38,11 @@ public class MusicPlayerSettings
/// Selected quality preset for the music player
/// </summary>
public QualityPreset QualityPreset { get; set; }
/// <summary>
/// Whether the bot will automatically queue related songs
/// </summary>
public bool AutoPlay { get; set; }
}
public enum QualityPreset

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,69 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace NadekoBot.Migrations
{
public partial class musicautoplay : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
// these 2 settings weren't being used for a long time
migrationBuilder.DropColumn(
name: "AutoDeleteByeMessages",
table: "GuildConfigs");
migrationBuilder.DropColumn(
name: "AutoDeleteGreetMessages",
table: "GuildConfigs");
migrationBuilder.AlterColumn<long>(
name: "Weight",
table: "Warnings",
type: "INTEGER",
nullable: false,
defaultValue: 1L,
oldClrType: typeof(int),
oldType: "INTEGER",
oldDefaultValue: 1);
migrationBuilder.AddColumn<bool>(
name: "AutoPlay",
table: "MusicPlayerSettings",
type: "INTEGER",
nullable: false,
defaultValue: false);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "AutoPlay",
table: "MusicPlayerSettings");
migrationBuilder.AlterColumn<int>(
name: "Weight",
table: "Warnings",
type: "INTEGER",
nullable: false,
defaultValue: 1,
oldClrType: typeof(long),
oldType: "INTEGER",
oldDefaultValue: 1L);
migrationBuilder.AddColumn<bool>(
name: "AutoDeleteByeMessages",
table: "GuildConfigs",
type: "INTEGER",
nullable: false,
defaultValue: false);
migrationBuilder.AddColumn<bool>(
name: "AutoDeleteGreetMessages",
table: "GuildConfigs",
type: "INTEGER",
nullable: false,
defaultValue: false);
}
}
}

View File

@@ -15,7 +15,7 @@ namespace NadekoBot.Migrations
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "6.0.1");
modelBuilder.HasAnnotation("ProductVersion", "6.0.2");
modelBuilder.Entity("NadekoBot.Db.Models.ClubApplicants", b =>
{
@@ -29,7 +29,7 @@ namespace NadekoBot.Migrations
b.HasIndex("UserId");
b.ToTable("ClubApplicants", (string)null);
b.ToTable("ClubApplicants");
});
modelBuilder.Entity("NadekoBot.Db.Models.ClubBans", b =>
@@ -44,7 +44,7 @@ namespace NadekoBot.Migrations
b.HasIndex("UserId");
b.ToTable("ClubBans", (string)null);
b.ToTable("ClubBans");
});
modelBuilder.Entity("NadekoBot.Db.Models.ClubInfo", b =>
@@ -86,7 +86,7 @@ namespace NadekoBot.Migrations
b.HasIndex("OwnerId")
.IsUnique();
b.ToTable("Clubs", (string)null);
b.ToTable("Clubs");
});
modelBuilder.Entity("NadekoBot.Db.Models.DiscordUser", b =>
@@ -155,7 +155,7 @@ namespace NadekoBot.Migrations
b.HasIndex("UserId");
b.ToTable("DiscordUser", (string)null);
b.ToTable("DiscordUser");
});
modelBuilder.Entity("NadekoBot.Db.Models.FollowedStream", b =>
@@ -189,7 +189,7 @@ namespace NadekoBot.Migrations
b.HasIndex("GuildConfigId");
b.ToTable("FollowedStream", (string)null);
b.ToTable("FollowedStream");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiAltSetting", b =>
@@ -218,7 +218,7 @@ namespace NadekoBot.Migrations
b.HasIndex("GuildConfigId")
.IsUnique();
b.ToTable("AntiAltSetting", (string)null);
b.ToTable("AntiAltSetting");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiRaidSetting", b =>
@@ -250,7 +250,7 @@ namespace NadekoBot.Migrations
b.HasIndex("GuildConfigId")
.IsUnique();
b.ToTable("AntiRaidSetting", (string)null);
b.ToTable("AntiRaidSetting");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiSpamIgnore", b =>
@@ -272,7 +272,7 @@ namespace NadekoBot.Migrations
b.HasIndex("AntiSpamSettingId");
b.ToTable("AntiSpamIgnore", (string)null);
b.ToTable("AntiSpamIgnore");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiSpamSetting", b =>
@@ -304,7 +304,7 @@ namespace NadekoBot.Migrations
b.HasIndex("GuildConfigId")
.IsUnique();
b.ToTable("AntiSpamSetting", (string)null);
b.ToTable("AntiSpamSetting");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.AutoCommand", b =>
@@ -342,7 +342,7 @@ namespace NadekoBot.Migrations
b.HasKey("Id");
b.ToTable("AutoCommands", (string)null);
b.ToTable("AutoCommands");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.AutoTranslateChannel", b =>
@@ -370,7 +370,7 @@ namespace NadekoBot.Migrations
b.HasIndex("GuildId");
b.ToTable("AutoTranslateChannels", (string)null);
b.ToTable("AutoTranslateChannels");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.AutoTranslateUser", b =>
@@ -398,7 +398,7 @@ namespace NadekoBot.Migrations
b.HasAlternateKey("ChannelId", "UserId");
b.ToTable("AutoTranslateUsers", (string)null);
b.ToTable("AutoTranslateUsers");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.BanTemplate", b =>
@@ -421,7 +421,7 @@ namespace NadekoBot.Migrations
b.HasIndex("GuildId")
.IsUnique();
b.ToTable("BanTemplates", (string)null);
b.ToTable("BanTemplates");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.BlacklistEntry", b =>
@@ -441,7 +441,7 @@ namespace NadekoBot.Migrations
b.HasKey("Id");
b.ToTable("Blacklist", (string)null);
b.ToTable("Blacklist");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandAlias", b =>
@@ -466,7 +466,7 @@ namespace NadekoBot.Migrations
b.HasIndex("GuildConfigId");
b.ToTable("CommandAlias", (string)null);
b.ToTable("CommandAlias");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandCooldown", b =>
@@ -491,7 +491,7 @@ namespace NadekoBot.Migrations
b.HasIndex("GuildConfigId");
b.ToTable("CommandCooldown", (string)null);
b.ToTable("CommandCooldown");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.CurrencyTransaction", b =>
@@ -529,7 +529,7 @@ namespace NadekoBot.Migrations
b.HasIndex("UserId");
b.ToTable("CurrencyTransactions", (string)null);
b.ToTable("CurrencyTransactions");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.DelMsgOnCmdChannel", b =>
@@ -554,7 +554,7 @@ namespace NadekoBot.Migrations
b.HasIndex("GuildConfigId");
b.ToTable("DelMsgOnCmdChannel", (string)null);
b.ToTable("DelMsgOnCmdChannel");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.DiscordPermOverride", b =>
@@ -580,7 +580,7 @@ namespace NadekoBot.Migrations
b.HasIndex("GuildId", "Command")
.IsUnique();
b.ToTable("DiscordPermOverrides", (string)null);
b.ToTable("DiscordPermOverrides");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.ExcludedItem", b =>
@@ -605,7 +605,7 @@ namespace NadekoBot.Migrations
b.HasIndex("XpSettingsId");
b.ToTable("ExcludedItem", (string)null);
b.ToTable("ExcludedItem");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.FeedSub", b =>
@@ -631,7 +631,7 @@ namespace NadekoBot.Migrations
b.HasAlternateKey("GuildConfigId", "Url");
b.ToTable("FeedSub", (string)null);
b.ToTable("FeedSub");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.FilterChannelId", b =>
@@ -653,7 +653,7 @@ namespace NadekoBot.Migrations
b.HasIndex("GuildConfigId");
b.ToTable("FilterChannelId", (string)null);
b.ToTable("FilterChannelId");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.FilteredWord", b =>
@@ -675,7 +675,7 @@ namespace NadekoBot.Migrations
b.HasIndex("GuildConfigId");
b.ToTable("FilteredWord", (string)null);
b.ToTable("FilteredWord");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.FilterLinksChannelId", b =>
@@ -697,7 +697,7 @@ namespace NadekoBot.Migrations
b.HasIndex("GuildConfigId");
b.ToTable("FilterLinksChannelId", (string)null);
b.ToTable("FilterLinksChannelId");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.FilterWordsChannelId", b =>
@@ -719,7 +719,7 @@ namespace NadekoBot.Migrations
b.HasIndex("GuildConfigId");
b.ToTable("FilterWordsChannelId", (string)null);
b.ToTable("FilterWordsChannelId");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.GCChannelId", b =>
@@ -741,7 +741,7 @@ namespace NadekoBot.Migrations
b.HasIndex("GuildConfigId");
b.ToTable("GCChannelId", (string)null);
b.ToTable("GCChannelId");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.GroupName", b =>
@@ -767,7 +767,7 @@ namespace NadekoBot.Migrations
b.HasIndex("GuildConfigId", "Number")
.IsUnique();
b.ToTable("GroupName", (string)null);
b.ToTable("GroupName");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.GuildConfig", b =>
@@ -779,15 +779,9 @@ namespace NadekoBot.Migrations
b.Property<string>("AutoAssignRoleIds")
.HasColumnType("TEXT");
b.Property<bool>("AutoDeleteByeMessages")
.HasColumnType("INTEGER");
b.Property<int>("AutoDeleteByeMessagesTimer")
.HasColumnType("INTEGER");
b.Property<bool>("AutoDeleteGreetMessages")
.HasColumnType("INTEGER");
b.Property<int>("AutoDeleteGreetMessagesTimer")
.HasColumnType("INTEGER");
@@ -897,7 +891,7 @@ namespace NadekoBot.Migrations
b.HasIndex("WarnExpireHours");
b.ToTable("GuildConfigs", (string)null);
b.ToTable("GuildConfigs");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredLogItem", b =>
@@ -923,7 +917,7 @@ namespace NadekoBot.Migrations
b.HasIndex("LogSettingId", "LogItemId", "ItemType")
.IsUnique();
b.ToTable("IgnoredLogChannels", (string)null);
b.ToTable("IgnoredLogChannels");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredVoicePresenceChannel", b =>
@@ -945,7 +939,7 @@ namespace NadekoBot.Migrations
b.HasIndex("LogSettingId");
b.ToTable("IgnoredVoicePresenceCHannels", (string)null);
b.ToTable("IgnoredVoicePresenceCHannels");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.ImageOnlyChannel", b =>
@@ -968,7 +962,7 @@ namespace NadekoBot.Migrations
b.HasIndex("ChannelId")
.IsUnique();
b.ToTable("ImageOnlyChannels", (string)null);
b.ToTable("ImageOnlyChannels");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.LogSetting", b =>
@@ -1033,7 +1027,7 @@ namespace NadekoBot.Migrations
b.HasIndex("GuildId")
.IsUnique();
b.ToTable("LogSettings", (string)null);
b.ToTable("LogSettings");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.MusicPlayerSettings", b =>
@@ -1045,6 +1039,9 @@ namespace NadekoBot.Migrations
b.Property<bool>("AutoDisconnect")
.HasColumnType("INTEGER");
b.Property<bool>("AutoPlay")
.HasColumnType("INTEGER");
b.Property<ulong>("GuildId")
.HasColumnType("INTEGER");
@@ -1067,7 +1064,7 @@ namespace NadekoBot.Migrations
b.HasIndex("GuildId")
.IsUnique();
b.ToTable("MusicPlayerSettings", (string)null);
b.ToTable("MusicPlayerSettings");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.MusicPlaylist", b =>
@@ -1090,7 +1087,7 @@ namespace NadekoBot.Migrations
b.HasKey("Id");
b.ToTable("MusicPlaylists", (string)null);
b.ToTable("MusicPlaylists");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.MutedUserId", b =>
@@ -1112,7 +1109,7 @@ namespace NadekoBot.Migrations
b.HasIndex("GuildConfigId");
b.ToTable("MutedUserId", (string)null);
b.ToTable("MutedUserId");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.NadekoExpression", b =>
@@ -1150,7 +1147,7 @@ namespace NadekoBot.Migrations
b.HasKey("Id");
b.ToTable("Expressions", (string)null);
b.ToTable("Expressions");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.NsfwBlacklistedTag", b =>
@@ -1172,7 +1169,7 @@ namespace NadekoBot.Migrations
b.HasIndex("GuildId");
b.ToTable("NsfwBlacklistedTags", (string)null);
b.ToTable("NsfwBlacklistedTags");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.Permissionv2", b =>
@@ -1212,7 +1209,7 @@ namespace NadekoBot.Migrations
b.HasIndex("GuildConfigId");
b.ToTable("Permissions", (string)null);
b.ToTable("Permissions");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.PlantedCurrency", b =>
@@ -1249,7 +1246,7 @@ namespace NadekoBot.Migrations
b.HasIndex("MessageId")
.IsUnique();
b.ToTable("PlantedCurrency", (string)null);
b.ToTable("PlantedCurrency");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.PlaylistSong", b =>
@@ -1283,7 +1280,7 @@ namespace NadekoBot.Migrations
b.HasIndex("MusicPlaylistId");
b.ToTable("PlaylistSong", (string)null);
b.ToTable("PlaylistSong");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.Poll", b =>
@@ -1309,7 +1306,7 @@ namespace NadekoBot.Migrations
b.HasIndex("GuildId")
.IsUnique();
b.ToTable("Poll", (string)null);
b.ToTable("Poll");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.PollAnswer", b =>
@@ -1334,7 +1331,7 @@ namespace NadekoBot.Migrations
b.HasIndex("PollId");
b.ToTable("PollAnswer", (string)null);
b.ToTable("PollAnswer");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.PollVote", b =>
@@ -1359,7 +1356,7 @@ namespace NadekoBot.Migrations
b.HasIndex("PollId");
b.ToTable("PollVote", (string)null);
b.ToTable("PollVote");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.Quote", b =>
@@ -1395,7 +1392,7 @@ namespace NadekoBot.Migrations
b.HasIndex("Keyword");
b.ToTable("Quotes", (string)null);
b.ToTable("Quotes");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.ReactionRole", b =>
@@ -1420,7 +1417,7 @@ namespace NadekoBot.Migrations
b.HasIndex("ReactionRoleMessageId");
b.ToTable("ReactionRole", (string)null);
b.ToTable("ReactionRole");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.ReactionRoleMessage", b =>
@@ -1451,7 +1448,7 @@ namespace NadekoBot.Migrations
b.HasIndex("GuildConfigId");
b.ToTable("ReactionRoleMessage", (string)null);
b.ToTable("ReactionRoleMessage");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.Reminder", b =>
@@ -1485,7 +1482,7 @@ namespace NadekoBot.Migrations
b.HasIndex("When");
b.ToTable("Reminders", (string)null);
b.ToTable("Reminders");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.Repeater", b =>
@@ -1520,7 +1517,7 @@ namespace NadekoBot.Migrations
b.HasKey("Id");
b.ToTable("Repeaters", (string)null);
b.ToTable("Repeaters");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.RewardedUser", b =>
@@ -1549,7 +1546,7 @@ namespace NadekoBot.Migrations
b.HasIndex("PatreonUserId")
.IsUnique();
b.ToTable("RewardedUsers", (string)null);
b.ToTable("RewardedUsers");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.RotatingPlayingStatus", b =>
@@ -1569,7 +1566,7 @@ namespace NadekoBot.Migrations
b.HasKey("Id");
b.ToTable("RotatingStatus", (string)null);
b.ToTable("RotatingStatus");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.SelfAssignedRole", b =>
@@ -1600,7 +1597,7 @@ namespace NadekoBot.Migrations
b.HasIndex("GuildId", "RoleId")
.IsUnique();
b.ToTable("SelfAssignableRoles", (string)null);
b.ToTable("SelfAssignableRoles");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.ShopEntry", b =>
@@ -1640,7 +1637,7 @@ namespace NadekoBot.Migrations
b.HasIndex("GuildConfigId");
b.ToTable("ShopEntry", (string)null);
b.ToTable("ShopEntry");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.ShopEntryItem", b =>
@@ -1662,7 +1659,7 @@ namespace NadekoBot.Migrations
b.HasIndex("ShopEntryId");
b.ToTable("ShopEntryItem", (string)null);
b.ToTable("ShopEntryItem");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.SlowmodeIgnoredRole", b =>
@@ -1684,7 +1681,7 @@ namespace NadekoBot.Migrations
b.HasIndex("GuildConfigId");
b.ToTable("SlowmodeIgnoredRole", (string)null);
b.ToTable("SlowmodeIgnoredRole");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.SlowmodeIgnoredUser", b =>
@@ -1706,7 +1703,7 @@ namespace NadekoBot.Migrations
b.HasIndex("GuildConfigId");
b.ToTable("SlowmodeIgnoredUser", (string)null);
b.ToTable("SlowmodeIgnoredUser");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleBlacklistedUser", b =>
@@ -1731,7 +1728,7 @@ namespace NadekoBot.Migrations
b.HasIndex("StreamRoleSettingsId");
b.ToTable("StreamRoleBlacklistedUser", (string)null);
b.ToTable("StreamRoleBlacklistedUser");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleSettings", b =>
@@ -1763,7 +1760,7 @@ namespace NadekoBot.Migrations
b.HasIndex("GuildConfigId")
.IsUnique();
b.ToTable("StreamRoleSettings", (string)null);
b.ToTable("StreamRoleSettings");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.StreamRoleWhitelistedUser", b =>
@@ -1788,7 +1785,7 @@ namespace NadekoBot.Migrations
b.HasIndex("StreamRoleSettingsId");
b.ToTable("StreamRoleWhitelistedUser", (string)null);
b.ToTable("StreamRoleWhitelistedUser");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.UnbanTimer", b =>
@@ -1813,7 +1810,7 @@ namespace NadekoBot.Migrations
b.HasIndex("GuildConfigId");
b.ToTable("UnbanTimer", (string)null);
b.ToTable("UnbanTimer");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.UnmuteTimer", b =>
@@ -1838,7 +1835,7 @@ namespace NadekoBot.Migrations
b.HasIndex("GuildConfigId");
b.ToTable("UnmuteTimer", (string)null);
b.ToTable("UnmuteTimer");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.UnroleTimer", b =>
@@ -1866,7 +1863,7 @@ namespace NadekoBot.Migrations
b.HasIndex("GuildConfigId");
b.ToTable("UnroleTimer", (string)null);
b.ToTable("UnroleTimer");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.UserXpStats", b =>
@@ -1911,7 +1908,7 @@ namespace NadekoBot.Migrations
b.HasIndex("UserId", "GuildId")
.IsUnique();
b.ToTable("UserXpStats", (string)null);
b.ToTable("UserXpStats");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.VcRoleInfo", b =>
@@ -1936,7 +1933,7 @@ namespace NadekoBot.Migrations
b.HasIndex("GuildConfigId");
b.ToTable("VcRoleInfo", (string)null);
b.ToTable("VcRoleInfo");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuInfo", b =>
@@ -1954,7 +1951,7 @@ namespace NadekoBot.Migrations
b.Property<DateTime?>("DateAdded")
.HasColumnType("TEXT");
b.Property<int>("Price")
b.Property<long>("Price")
.HasColumnType("INTEGER");
b.Property<int>("WaifuId")
@@ -1971,7 +1968,7 @@ namespace NadekoBot.Migrations
b.HasIndex("WaifuId")
.IsUnique();
b.ToTable("WaifuInfo", (string)null);
b.ToTable("WaifuInfo");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuItem", b =>
@@ -1996,7 +1993,7 @@ namespace NadekoBot.Migrations
b.HasIndex("WaifuInfoId");
b.ToTable("WaifuItem", (string)null);
b.ToTable("WaifuItem");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.WaifuUpdate", b =>
@@ -2028,7 +2025,7 @@ namespace NadekoBot.Migrations
b.HasIndex("UserId");
b.ToTable("WaifuUpdates", (string)null);
b.ToTable("WaifuUpdates");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.Warning", b =>
@@ -2058,10 +2055,10 @@ namespace NadekoBot.Migrations
b.Property<ulong>("UserId")
.HasColumnType("INTEGER");
b.Property<int>("Weight")
b.Property<long>("Weight")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasDefaultValue(1);
.HasDefaultValue(1L);
b.HasKey("Id");
@@ -2071,7 +2068,7 @@ namespace NadekoBot.Migrations
b.HasIndex("UserId");
b.ToTable("Warnings", (string)null);
b.ToTable("Warnings");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.WarningPunishment", b =>
@@ -2102,7 +2099,7 @@ namespace NadekoBot.Migrations
b.HasIndex("GuildConfigId");
b.ToTable("WarningPunishment", (string)null);
b.ToTable("WarningPunishment");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.XpCurrencyReward", b =>
@@ -2127,7 +2124,7 @@ namespace NadekoBot.Migrations
b.HasIndex("XpSettingsId");
b.ToTable("XpCurrencyReward", (string)null);
b.ToTable("XpCurrencyReward");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.XpRoleReward", b =>
@@ -2156,7 +2153,7 @@ namespace NadekoBot.Migrations
b.HasIndex("XpSettingsId", "Level")
.IsUnique();
b.ToTable("XpRoleReward", (string)null);
b.ToTable("XpRoleReward");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.XpSettings", b =>
@@ -2179,7 +2176,7 @@ namespace NadekoBot.Migrations
b.HasIndex("GuildConfigId")
.IsUnique();
b.ToTable("XpSettings", (string)null);
b.ToTable("XpSettings");
});
modelBuilder.Entity("NadekoBot.Db.Models.ClubApplicants", b =>

View File

@@ -103,7 +103,7 @@ public sealed partial class Music : NadekoModule<IMusicService>
var (trackInfo, index) = await mp.TryEnqueueTrackAsync(query, ctx.User.ToString(), asNext, forcePlatform);
if (trackInfo is null)
{
await ReplyErrorLocalizedAsync(strs.song_not_found);
await ReplyErrorLocalizedAsync(strs.track_not_found);
return;
}
@@ -111,7 +111,7 @@ public sealed partial class Music : NadekoModule<IMusicService>
{
var embed = _eb.Create()
.WithOkColor()
.WithAuthor(GetText(strs.queued_song) + " #" + (index + 1), MUSIC_ICON_URL)
.WithAuthor(GetText(strs.queued_track) + " #" + (index + 1), MUSIC_ICON_URL)
.WithDescription($"{trackInfo.PrettyName()}\n{GetText(strs.queue)} ")
.WithFooter(trackInfo.Platform.ToString());
@@ -248,7 +248,7 @@ public sealed partial class Music : NadekoModule<IMusicService>
[RequireContext(ContextType.Guild)]
public async partial Task ListQueue()
{
// show page with the current song
// show page with the current track
if (!_service.TryGetMusicPlayer(ctx.Guild.Id, out var mp))
{
await ReplyErrorLocalizedAsync(strs.no_player);
@@ -291,8 +291,8 @@ public sealed partial class Music : NadekoModule<IMusicService>
add += "🔂 " + GetText(strs.repeating_track) + "\n";
else
{
// if (mp.Autoplay)
// add += "↪ " + GetText(strs.autoplaying) + "\n";
if (mp.AutoPlay)
add += "↪ " + GetText(strs.autoplaying) + "\n";
// if (mp.FairPlay && !mp.Autoplay)
// add += " " + GetText(strs.fairplay) + "\n";
if (repeatType == PlayerRepeatType.Queue)
@@ -339,7 +339,7 @@ public sealed partial class Music : NadekoModule<IMusicService>
if (videos.Count == 0)
{
await ReplyErrorLocalizedAsync(strs.song_not_found);
await ReplyErrorLocalizedAsync(strs.track_not_found);
return;
}
@@ -388,7 +388,7 @@ public sealed partial class Music : NadekoModule<IMusicService>
{
if (index < 1)
{
await ReplyErrorLocalizedAsync(strs.removed_song_error);
await ReplyErrorLocalizedAsync(strs.removed_track_error);
return;
}
@@ -402,16 +402,16 @@ public sealed partial class Music : NadekoModule<IMusicService>
return;
}
if (!mp.TryRemoveTrackAt(index - 1, out var song))
if (!mp.TryRemoveTrackAt(index - 1, out var track))
{
await ReplyErrorLocalizedAsync(strs.removed_song_error);
await ReplyErrorLocalizedAsync(strs.removed_track_error);
return;
}
var embed = _eb.Create()
.WithAuthor(GetText(strs.removed_song) + " #" + index, MUSIC_ICON_URL)
.WithDescription(song.PrettyName())
.WithFooter(song.PrettyInfo())
.WithAuthor(GetText(strs.removed_track) + " #" + index, MUSIC_ICON_URL)
.WithDescription(track.PrettyName())
.WithFooter(track.PrettyInfo())
.WithErrorColor();
await _service.SendToOutputAsync(ctx.Guild.Id, embed);
@@ -550,7 +550,7 @@ public sealed partial class Music : NadekoModule<IMusicService>
[Cmd]
[RequireContext(ContextType.Guild)]
public async partial Task MoveSong(int from, int to)
public async partial Task TrackMove(int from, int to)
{
if (--from < 0 || --to < 0 || from == to)
{
@@ -578,7 +578,7 @@ public sealed partial class Music : NadekoModule<IMusicService>
var embed = _eb.Create()
.WithTitle(track.Title.TrimTo(65))
.WithAuthor(GetText(strs.song_moved), MUSIC_ICON_URL)
.WithAuthor(GetText(strs.track_moved), MUSIC_ICON_URL)
.AddField(GetText(strs.from_position), $"#{from + 1}", true)
.AddField(GetText(strs.to_position), $"#{to + 1}", true)
.WithOkColor();
@@ -744,4 +744,15 @@ public sealed partial class Music : NadekoModule<IMusicService>
await _service.SetMusicQualityAsync(ctx.Guild.Id, preset);
await ReplyConfirmLocalizedAsync(strs.music_quality_set(Format.Bold(preset.ToString())));
}
[Cmd]
[RequireContext(ContextType.Guild)]
public async partial Task QueueAutoPlay()
{
var newValue = await _service.ToggleQueueAutoPlayAsync(ctx.Guild.Id);
if (newValue)
await ReplyConfirmLocalizedAsync(strs.music_autoplay_on);
else
await ReplyConfirmLocalizedAsync(strs.music_autoplay_off);
}
}

View File

@@ -32,4 +32,5 @@ public interface IMusicService : IPlaceholderProvider
Task<bool> ToggleAutoDisconnectAsync(ulong guildId);
Task<QualityPreset> GetMusicQualityAsync(ulong guildId);
Task SetMusicQualityAsync(ulong guildId, QualityPreset preset);
Task<bool> ToggleQueueAutoPlayAsync(ulong guildId);
}

View File

@@ -156,7 +156,12 @@ public sealed class MusicService : IMusicService
_outputChannels[guildId] = (defaultChannel, overrideChannel);
var mp = new MusicPlayer(queue, resolver, proxy, settings.QualityPreset);
var mp = new MusicPlayer(queue,
resolver,
proxy,
_googleApiService,
settings.QualityPreset,
settings.AutoPlay);
mp.SetRepeat(settings.PlayerRepeat);
@@ -191,7 +196,7 @@ public sealed class MusicService : IMusicService
_ = lastFinishedMessage?.DeleteAsync();
var embed = _eb.Create()
.WithOkColor()
.WithAuthor(GetText(guildId, strs.finished_song), Music.MUSIC_ICON_URL)
.WithAuthor(GetText(guildId, strs.finished_track), Music.MUSIC_ICON_URL)
.WithDescription(trackInfo.PrettyName())
.WithFooter(trackInfo.PrettyTotalTime());
@@ -207,7 +212,7 @@ public sealed class MusicService : IMusicService
_ = lastPlayingMessage?.DeleteAsync();
var embed = _eb.Create()
.WithOkColor()
.WithAuthor(GetText(guildId, strs.playing_song(index + 1)), Music.MUSIC_ICON_URL)
.WithAuthor(GetText(guildId, strs.playing_track(index + 1)), Music.MUSIC_ICON_URL)
.WithDescription(trackInfo.PrettyName())
.WithFooter($"{mp.PrettyVolume()} | {trackInfo.PrettyInfo()}");
@@ -288,7 +293,7 @@ public sealed class MusicService : IMusicService
public IEnumerable<(string Name, Func<string> Func)> GetPlaceholders()
{
// random song that's playing
// random track that's playing
yield return ("%music.playing%", () =>
{
var randomPlayingTrack = _players.Select(x => x.Value.GetCurrentTrack(out _))
@@ -438,5 +443,17 @@ public sealed class MusicService : IMusicService
},
preset);
public async Task<bool> ToggleQueueAutoPlayAsync(ulong guildId)
{
var newValue = false;
await ModifySettingsInternalAsync(guildId,
(settings, _) => newValue = settings.AutoPlay = !settings.AutoPlay,
false);
if (TryGetMusicPlayer(guildId, out var mp))
mp.AutoPlay = newValue;
return newValue;
}
#endregion
}

View File

@@ -10,6 +10,7 @@ public interface IMusicPlayer : IDisposable
bool IsKilled { get; }
int CurrentIndex { get; }
public PlayerRepeatType Repeat { get; }
bool AutoPlay { get; set; }
void Stop();
void Clear();

View File

@@ -2,6 +2,7 @@
public interface ITrackInfo
{
public string Id => string.Empty;
public string Title { get; }
public string Url { get; }
public string Thumbnail { get; }

View File

@@ -28,6 +28,7 @@ public sealed class MusicPlayer : IMusicPlayer
private readonly IMusicQueue _queue;
private readonly ITrackResolveProvider _trackResolveProvider;
private readonly IVoiceProxy _proxy;
private readonly IGoogleApiService _googleApiService;
private readonly ISongBuffer _songBuffer;
private bool skipped;
@@ -35,15 +36,21 @@ public sealed class MusicPlayer : IMusicPlayer
private readonly Thread _thread;
private readonly Random _rng;
public bool AutoPlay { get; set; }
public MusicPlayer(
IMusicQueue queue,
ITrackResolveProvider trackResolveProvider,
IVoiceProxy proxy,
QualityPreset qualityPreset)
IGoogleApiService googleApiService,
QualityPreset qualityPreset,
bool autoPlay)
{
_queue = queue;
_trackResolveProvider = trackResolveProvider;
_proxy = proxy;
_googleApiService = googleApiService;
AutoPlay = autoPlay;
_rng = new NadekoRandom();
_vc = GetVoiceClient(qualityPreset);
@@ -265,8 +272,30 @@ public sealed class MusicPlayer : IMusicPlayer
{
cancellationTokenSource.Cancel();
// turn off green in vc
_ = OnCompleted?.Invoke(this, track);
// todo update when settings are changed
if (AutoPlay && track.Platform == MusicPlatform.Youtube)
{
try
{
var relatedSongs = await _googleApiService.GetRelatedVideosAsync(track.TrackInfo.Id, 5);
var related = relatedSongs.Shuffle().FirstOrDefault();
if (related is not null)
{
var relatedTrack = await _trackResolveProvider.QuerySongAsync(related, MusicPlatform.Youtube);
if (relatedTrack is not null)
EnqueueTrack(relatedTrack, "Autoplay");
}
}
catch (Exception ex)
{
Log.Warning(ex, "Failed queueing a related song via autoplay");
}
}
HandleQueuePostTrack();
skipped = false;

View File

@@ -1,30 +1,15 @@
namespace NadekoBot.Modules.Music;
public sealed class RemoteTrackInfo : ITrackInfo
public sealed record RemoteTrackInfo(
string Id,
string Title,
string Url,
string Thumbnail,
TimeSpan Duration,
MusicPlatform Platform,
Func<Task<string?>> _streamFactory) : ITrackInfo
{
public string Title { get; }
public string Url { get; }
public string Thumbnail { get; }
public TimeSpan Duration { get; }
public MusicPlatform Platform { get; }
private readonly Func<Task<string?>> _streamFactory;
public RemoteTrackInfo(
string title,
string url,
string thumbnail,
TimeSpan duration,
MusicPlatform platform,
Func<Task<string?>> streamFactory)
{
_streamFactory = streamFactory;
Title = title;
Url = url;
Thumbnail = thumbnail;
Duration = duration;
Platform = platform;
}
private readonly Func<Task<string?>> _streamFactory = _streamFactory;
public async ValueTask<string?> GetStreamUrl()
=> await _streamFactory();

View File

@@ -94,7 +94,9 @@ public sealed class YtdlYoutubeResolver : IYoutubeResolver
}
private ITrackInfo DataToInfo(in YtTrackData trackData)
=> new RemoteTrackInfo(trackData.Title,
=> new RemoteTrackInfo(
trackData.Id,
trackData.Title,
$"https://youtube.com/watch?v={trackData.Id}",
trackData.Thumbnail,
trackData.Duration,

View File

@@ -4,8 +4,6 @@ using System.Globalization;
namespace NadekoBot.Modules.Searches;
// todo weighted warnings fix
// todo autoplay/fairplay
public partial class Searches
{
public partial class FinanceCommands : NadekoSubmodule<CryptoService>

View File

@@ -1,4 +1,5 @@
#nullable disable
// #nullable disable
// using NadekoBot.Db.Models;
// using System;
// using System.Collections.Generic;
// using System.Linq;
@@ -10,6 +11,7 @@
// using NadekoBot.Services.Database.Models;
// using NadekoBot.Extensions;
// using Serilog;
// using TwitchLib.Api;
// using JsonSerializer = System.Text.Json.JsonSerializer;
//
// namespace NadekoBot.Modules.Searches.Common.StreamNotifications.Providers
@@ -25,32 +27,16 @@
// public override FollowedStream.FType Platform => FollowedStream.FType.Twitch;
//
// private (string Token, DateTime Expiry) _token = default;
// private readonly TwitchAPI _api;
//
// public TwitchHelixProvider(IHttpClientFactory httpClientFactory)
// {
// _httpClientFactory = httpClientFactory;
// _api = new TwitchAPI();
// }
//
// private async Task EnsureTokenValidAsync()
// {
// if (_token != default && (DateTime.UtcNow - _token.Expiry) > TimeSpan.FromHours(1))
// return;
//
// const string clientId = string.Empty;
// const string clientSecret = string.Empty;
//
// var client = _httpClientFactory.CreateClient();
// var res = await client.PostAsync("https://id.twitch.tv/oauth2/token" +
// $"?client_id={clientId}" +
// $"&client_secret={clientSecret}" +
// "&grant_type=client_credentials", new StringContent(""));
//
// var data = JsonDocument.Parse(await res.Content.ReadAsStringAsync()).RootElement;
//
// _token = (data.GetProperty("access_token").GetString(),
// DateTime.UtcNow + TimeSpan.FromSeconds(data.GetProperty("expires_in").GetInt32()));
//
// }
// => await _api.Auth.GetAccessTokenAsync();
//
// public override Task<bool> IsValidUrl(string url)
// {

View File

@@ -10,7 +10,7 @@ public interface IGoogleApiService
Task<IEnumerable<string>> GetVideoLinksByKeywordAsync(string keywords, int count = 1);
Task<IEnumerable<(string Name, string Id, string Url)>> GetVideoInfosByKeywordAsync(string keywords, int count = 1);
Task<IEnumerable<string>> GetPlaylistIdsByKeywordsAsync(string keywords, int count = 1);
Task<IEnumerable<string>> GetRelatedVideosAsync(string url, int count = 1);
Task<IEnumerable<string>> GetRelatedVideosAsync(string id, int count = 1, string user = null);
Task<IEnumerable<string>> GetPlaylistTracksAsync(string playlistId, int count = 50);
Task<IReadOnlyDictionary<string, TimeSpan>> GetVideoDurationsAsync(IEnumerable<string> videoIds);
Task<ImageResult> GetImageAsync(string query);

View File

@@ -194,8 +194,7 @@ public class GoogleApiService : IGoogleApiService, INService
return (await query.ExecuteAsync()).Items.Select(i => i.Id.PlaylistId);
}
// todo future add quota users
public async Task<IEnumerable<string>> GetRelatedVideosAsync(string id, int count = 1)
public async Task<IEnumerable<string>> GetRelatedVideosAsync(string id, int count = 1, string user = null)
{
if (string.IsNullOrWhiteSpace(id))
throw new ArgumentNullException(nameof(id));
@@ -207,6 +206,7 @@ public class GoogleApiService : IGoogleApiService, INService
query.MaxResults = count;
query.RelatedToVideoId = id;
query.Type = "video";
query.QuotaUser = user;
return (await query.ExecuteAsync()).Items.Select(i => "http://www.youtube.com/watch?v=" + i.Id.VideoId);
}

View File

@@ -472,13 +472,19 @@ trackremove:
- songremove
- srm
- trackremove
movesong:
- trm
trackmove:
- trackmove
- tm
- movesong
- ms
queuerepeat:
- queuerepeat
- qrp
- rpl
queueautoplay:
- queueautoplay
- qap
save:
- save
streamrole:

View File

@@ -849,7 +849,7 @@ trackremove:
- "5"
- "all"
- ""
movesong:
trackmove:
desc: "Moves a song from one position to another."
args:
- "5 3"
@@ -888,7 +888,7 @@ deleteplaylist:
desc: "Deletes a saved playlist using its id. Works only if you made it or if you are the bot owner."
args:
- "5"
autoplay:
queueautoplay:
desc: "Toggles autoplay - When the song is finished, automatically queue a related Youtube song. (Works only for Youtube songs and when queue is empty)"
args:
- ""

View File

@@ -354,10 +354,10 @@
"ttt_time_expired": "Time expired!",
"ttt_users_move": "{0}'s move",
"vs": "{0} vs {1}",
"attempting_to_queue": "Attempting to queue {0} songs...",
"attempting_to_queue": "Attempting to queue {0} tracks...",
"dir_queue_complete": "Directory queue complete.",
"fairplay": "Fairplay",
"finished_song": "Finished song",
"finished_track": "Track Finished",
"fp_disabled": "Fair play disabled.",
"fp_enabled": "Fair play enabled.",
"from_position": "From position",
@@ -366,8 +366,8 @@
"no_player": "No active music player.",
"no_search_results": "No search results.",
"player_queue": "Player queue - Page {0}/{1}",
"playing_song": "Playing song #{0}",
"playlists": "`#{0}` - **{1}** by *{2}* ({3} songs)",
"playing_track": "Playing track #{0}",
"playlists": "`#{0}` - **{1}** by *{2}* ({3} tracks)",
"playlists_page": "Page {0} of saved playlists",
"playlist_deleted": "Playlist deleted.",
"playlist_delete_fail": "Failed to delete that playlist. It either doesn't exist, or you are not its author.",
@@ -375,17 +375,20 @@
"playlist_queue_complete": "Playlist queue complete.",
"playlist_saved": "Playlist saved",
"queue": "Queue",
"queued_song": "Queued song",
"queued_track": "Queued track",
"queue_cleared": "Music queue cleared.",
"removed_song": "Removed song",
"removed_track": "Removed track",
"repeating_none": "Player will stop once the end of queue is reached.",
"repeating_queue": "Player will repeat the queue.",
"repeating_track": "Player will keep repeating the same track.",
"set_music_channel": "I will now output playing, finished, paused and removed songs in this channel.",
"unset_music_channel": "I will now output playing, finished, paused and removed songs of the newly created music players in the channel music was started from.",
"autoplaying": "Automatically adds related tracks.",
"set_music_channel": "I will now output playing, finished, paused and removed tracks in this channel.",
"unset_music_channel": "I will now output playing, finished, paused and removed tracks of the newly created music players in the channel music was started from.",
"current_music_quality": "Currently set music quality is: {0}",
"music_quality_set": "Music quality has been set to {0}. You will have to destroy and restart music player for the change to take effect.",
"song_moved": "Song moved",
"music_autoplay_on": "Music autoplay enabled. I will automatically queue related tracks after each track finishes playing.",
"music_autoplay_off": "Music autoplay disabled.",
"track_moved": "Track moved",
"to_position": "To position",
"volume_input_invalid": "Volume must be between 0 and 100",
"volume_set": "Volume set to {0}%",
@@ -731,7 +734,7 @@
"timezone_not_found": "Timezone not found. Use \"timezones\" command to see the list of available timezones.",
"timezones_available": "Available Timezones",
"perm_override": "Users will need {0} permission in order to run {1} command.",
"song_not_found": "No song found.",
"track_not_found": "No track found.",
"define_unknown": "Can't find the definition for that term.",
"verbose_errors_enabled": "Incorrectly used commands will now show errors.",
"verbose_errors_disabled": "Incorrectly used commands will no longer show errors.",
@@ -741,7 +744,7 @@
"module": "Module: {0}",
"hangman_stopped": "Hangman game stopped.",
"queue_stopped": "Player is stopped. Use {0} command to start playing.",
"removed_song_error": "Song on that index doesn't exist",
"removed_track_error": "Track on that index doesn't exist",
"queue_shuffled": "Music queue has been shuffled.",
"warnings_list": "List of all warned users on the server",
"redacted_too_long": "Redacted because it's too long.",
@@ -873,8 +876,8 @@
"rafflecur_joined": "User {0} joined the raffle",
"rafflecur_already_joined": "You have already joined this raffle or the value you used is not valid.",
"rafflecur_ended": "{0} raffle ended. {1} won {2}!",
"autodc_enable": "I will disconnect from the voice channel when there are no more songs to play.",
"autodc_disable": "I will no longer disconnect from the voice channel when there are no more songs to play.",
"autodc_enable": "I will disconnect from the voice channel when there are no more tracks to play.",
"autodc_disable": "I will no longer disconnect from the voice channel when there are no more tracks to play.",
"timely_none": "Bot owner didn't specify a timely reward.",
"timely_already_claimed": "You've already claimed your timely reward. You can get it again in {0}.",
"timely": "You've claimed your {0}. You can claim again in {1}h",