From eca4a14cb626a186697060da458bc37b5edf7fca Mon Sep 17 00:00:00 2001 From: Kwoth Date: Fri, 26 Apr 2024 01:01:09 +0000 Subject: [PATCH] Removed a bunch of obsolete alises, command strings and commands --- CHANGELOG.md | 5 + src/Nadeko.Bot.Common/_common/OldCreds.cs | 1 - .../Models/NsfwBlacklistedTag.cs | 14 -- src/Nadeko.Bot.Db/Models/PlaylistSong.cs | 1 - src/Nadeko.Bot.Db/Models/Poll.cs | 17 --- src/Nadeko.Bot.Db/Models/PollVote.cs | 14 -- .../Administration.cs | 2 +- .../Games/Games.cs | 110 +------------- .../Games/Polls/PollCommands.cs | 102 ------------- .../Games/Polls/PollExtensions.cs | 36 ----- .../Games/Polls/PollRunner.cs | 63 -------- .../Games/Polls/PollService.cs | 140 ------------------ src/Nadeko.Bot.Modules.Music/Music.cs | 30 ---- .../Services/IMusicService.cs | 1 - .../Services/MusicService.cs | 18 --- .../Services/SoundCloudApiService.cs | 78 ---------- .../_common/ISoundcloudResolver.cs | 8 - .../_common/Impl/MusicPlatform.cs | 1 - .../_common/Resolvers/SoundcloudResolver.cs | 84 ----------- .../_common/Resolvers/TrackResolveProvider.cs | 7 - .../Patronage/PatronageCommands.cs | 1 - .../{TodoCommands.cs => Utility.cs} | 35 +---- src/NadekoBot/Db/NadekoContext.cs | 10 -- .../_common/ServiceCollectionExtensions.cs | 1 - src/NadekoBot/data/aliases.yml | 62 +------- .../data/strings/commands/commands.en-US.yml | 85 +---------- .../strings/responses/responses.en-US.json | 2 +- 27 files changed, 17 insertions(+), 911 deletions(-) delete mode 100644 src/Nadeko.Bot.Db/Models/NsfwBlacklistedTag.cs delete mode 100644 src/Nadeko.Bot.Db/Models/Poll.cs delete mode 100644 src/Nadeko.Bot.Db/Models/PollVote.cs delete mode 100644 src/Nadeko.Bot.Modules.Gambling/Games/Polls/PollCommands.cs delete mode 100644 src/Nadeko.Bot.Modules.Gambling/Games/Polls/PollExtensions.cs delete mode 100644 src/Nadeko.Bot.Modules.Gambling/Games/Polls/PollRunner.cs delete mode 100644 src/Nadeko.Bot.Modules.Gambling/Games/Polls/PollService.cs delete mode 100644 src/Nadeko.Bot.Modules.Music/Services/SoundCloudApiService.cs delete mode 100644 src/Nadeko.Bot.Modules.Music/_common/ISoundcloudResolver.cs delete mode 100644 src/Nadeko.Bot.Modules.Music/_common/Resolvers/SoundcloudResolver.cs rename src/Nadeko.Bot.Modules.Utility/{TodoCommands.cs => Utility.cs} (94%) diff --git a/CHANGELOG.md b/CHANGELOG.md index cb6de0722..9aa620c57 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -42,6 +42,11 @@ Experimental changelog. Mostly based on [keepachangelog](https://keepachangelog. - `.feed` should now correctly accept (and show) the message which can be passed as the third parameter +### Removed + +- `.poll` commands removed, discord added polls. +- `.scpl` and other music soundcloud commands have been removed as soundcloud isn't issuing new api tokens for years now + ## [4.3.22] - 23.04.2023 ### Added diff --git a/src/Nadeko.Bot.Common/_common/OldCreds.cs b/src/Nadeko.Bot.Common/_common/OldCreds.cs index ce94a6bdf..2a0dbeddf 100644 --- a/src/Nadeko.Bot.Common/_common/OldCreds.cs +++ b/src/Nadeko.Bot.Common/_common/OldCreds.cs @@ -9,7 +9,6 @@ public class OldCreds public string GoogleApiKey { get; set; } = string.Empty; public string MashapeKey { get; set; } = string.Empty; public string OsuApiKey { get; set; } = string.Empty; - public string SoundCloudClientId { get; set; } = string.Empty; public string CleverbotApiKey { get; set; } = string.Empty; public string CarbonKey { get; set; } = string.Empty; public int TotalShards { get; set; } = 1; diff --git a/src/Nadeko.Bot.Db/Models/NsfwBlacklistedTag.cs b/src/Nadeko.Bot.Db/Models/NsfwBlacklistedTag.cs deleted file mode 100644 index ae614ada9..000000000 --- a/src/Nadeko.Bot.Db/Models/NsfwBlacklistedTag.cs +++ /dev/null @@ -1,14 +0,0 @@ -#nullable disable -namespace Nadeko.Bot.Db.Models; - -public class NsfwBlacklistedTag : DbEntity -{ - public ulong GuildId { get; set; } - public string Tag { get; set; } - - public override int GetHashCode() - => Tag.GetHashCode(StringComparison.InvariantCulture); - - public override bool Equals(object obj) - => obj is NsfwBlacklistedTag x && x.Tag == Tag; -} \ No newline at end of file diff --git a/src/Nadeko.Bot.Db/Models/PlaylistSong.cs b/src/Nadeko.Bot.Db/Models/PlaylistSong.cs index 9c54e110c..94486a772 100644 --- a/src/Nadeko.Bot.Db/Models/PlaylistSong.cs +++ b/src/Nadeko.Bot.Db/Models/PlaylistSong.cs @@ -15,5 +15,4 @@ public enum MusicType Radio, YouTube, Local, - Soundcloud } \ No newline at end of file diff --git a/src/Nadeko.Bot.Db/Models/Poll.cs b/src/Nadeko.Bot.Db/Models/Poll.cs deleted file mode 100644 index 6d84a32c3..000000000 --- a/src/Nadeko.Bot.Db/Models/Poll.cs +++ /dev/null @@ -1,17 +0,0 @@ -#nullable disable -namespace Nadeko.Bot.Db.Models; - -public class Poll : DbEntity -{ - public ulong GuildId { get; set; } - public ulong ChannelId { get; set; } - public string Question { get; set; } - public IndexedCollection Answers { get; set; } - public HashSet Votes { get; set; } = new(); -} - -public class PollAnswer : DbEntity, IIndexed -{ - public int Index { get; set; } - public string Text { get; set; } -} \ No newline at end of file diff --git a/src/Nadeko.Bot.Db/Models/PollVote.cs b/src/Nadeko.Bot.Db/Models/PollVote.cs deleted file mode 100644 index 479a0d947..000000000 --- a/src/Nadeko.Bot.Db/Models/PollVote.cs +++ /dev/null @@ -1,14 +0,0 @@ -#nullable disable -namespace Nadeko.Bot.Db.Models; - -public class PollVote : DbEntity -{ - public ulong UserId { get; set; } - public int VoteIndex { get; set; } - - public override int GetHashCode() - => UserId.GetHashCode(); - - public override bool Equals(object obj) - => obj is PollVote p ? p.UserId == UserId : false; -} \ No newline at end of file diff --git a/src/Nadeko.Bot.Modules.Administration/Administration.cs b/src/Nadeko.Bot.Modules.Administration/Administration.cs index aa6918f13..1b9efbafb 100644 --- a/src/Nadeko.Bot.Modules.Administration/Administration.cs +++ b/src/Nadeko.Bot.Modules.Administration/Administration.cs @@ -248,7 +248,7 @@ public partial class Administration : NadekoModule [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.ManageChannels)] [BotPerm(GuildPerm.ManageChannels)] - public async Task NsfwToggle() + public async Task AgeRestrictToggle() { var channel = (ITextChannel)ctx.Channel; var isEnabled = channel.IsNsfw; diff --git a/src/Nadeko.Bot.Modules.Gambling/Games/Games.cs b/src/Nadeko.Bot.Modules.Gambling/Games/Games.cs index 49f94cd55..5ce9f42ce 100644 --- a/src/Nadeko.Bot.Modules.Gambling/Games/Games.cs +++ b/src/Nadeko.Bot.Modules.Gambling/Games/Games.cs @@ -40,111 +40,9 @@ public partial class Games : NadekoModule var res = _service.GetEightballResponse(ctx.User.Id, question); await ctx.Channel.EmbedAsync(_eb.Create() - .WithOkColor() - .WithDescription(ctx.User.ToString()) - .AddField("❓ " + GetText(strs.question), question) - .AddField("🎱 " + GetText(strs._8ball), res)); + .WithOkColor() + .WithDescription(ctx.User.ToString()) + .AddField("❓ " + GetText(strs.question), question) + .AddField("🎱 " + GetText(strs._8ball), res)); } - - [Cmd] - [RequireContext(ContextType.Guild)] - public async Task RateGirl([Leftover] IGuildUser usr) - { - var gr = _service.GirlRatings.GetOrAdd(usr.Id, GetGirl); - var originalStream = await gr.Stream; - - if (originalStream is null) - { - await ReplyErrorLocalizedAsync(strs.something_went_wrong); - return; - } - - await using var imgStream = new MemoryStream(); - lock (gr) - { - originalStream.Position = 0; - originalStream.CopyTo(imgStream); - } - - imgStream.Position = 0; - await ctx.Channel.SendFileAsync(imgStream, - $"rating.png", - Format.Bold($"{ctx.User.Mention} Girl Rating For {usr}"), - embed: _eb.Create() - .WithOkColor() - .AddField("Hot", gr.Hot.ToString("F2"), true) - .AddField("Crazy", gr.Crazy.ToString("F2"), true) - .AddField("Advice", gr.Advice) - .WithImageUrl($"attachment://rating.png") - .Build()); - } - - private double NextDouble(double x, double y) - => (_rng.NextDouble() * (y - x)) + x; - - private GirlRating GetGirl(ulong uid) - { - var rng = new NadekoRandom(); - - var roll = rng.Next(1, 1001); - - var ratings = _service.Ratings.GetAwaiter().GetResult(); - - double hot; - double crazy; - string advice; - if (roll < 500) - { - hot = NextDouble(0, 5); - crazy = NextDouble(4, 10); - advice = ratings.Nog; - } - else if (roll < 750) - { - hot = NextDouble(5, 8); - crazy = NextDouble(4, (.6 * hot) + 4); - advice = ratings.Fun; - } - else if (roll < 900) - { - hot = NextDouble(5, 10); - crazy = NextDouble((.61 * hot) + 4, 10); - advice = ratings.Dan; - } - else if (roll < 951) - { - hot = NextDouble(8, 10); - crazy = NextDouble(7, (.6 * hot) + 4); - advice = ratings.Dat; - } - else if (roll < 990) - { - hot = NextDouble(8, 10); - crazy = NextDouble(5, 7); - advice = ratings.Wif; - } - else if (roll < 999) - { - hot = NextDouble(8, 10); - crazy = NextDouble(2, 3.99d); - advice = ratings.Tra; - } - else - { - hot = NextDouble(8, 10); - crazy = NextDouble(4, 5); - advice = ratings.Uni; - } - - return new(_images, crazy, hot, roll, advice); - } - - [Cmd] - public async Task Linux(string guhnoo, string loonix) - => await SendConfirmAsync( - $@"I'd just like to interject for moment. What you're refering to as {loonix}, is in fact, {guhnoo}/{loonix}, or as I've recently taken to calling it, {guhnoo} plus {loonix}. {loonix} is not an operating system unto itself, but rather another free component of a fully functioning {guhnoo} system made useful by the {guhnoo} corelibs, shell utilities and vital system components comprising a full OS as defined by POSIX. - -Many computer users run a modified version of the {guhnoo} system every day, without realizing it. Through a peculiar turn of events, the version of {guhnoo} which is widely used today is often called {loonix}, and many of its users are not aware that it is basically the {guhnoo} system, developed by the {guhnoo} Project. - -There really is a {loonix}, and these people are using it, but it is just a part of the system they use. {loonix} is the kernel: the program in the system that allocates the machine's resources to the other programs that you run. The kernel is an essential part of an operating system, but useless by itself; it can only function in the context of a complete operating system. {loonix} is normally used in combination with the {guhnoo} operating system: the whole system is basically {guhnoo} with {loonix} added, or {guhnoo}/{loonix}. All the so-called {loonix} distributions are really distributions of {guhnoo}/{loonix}."); } \ No newline at end of file diff --git a/src/Nadeko.Bot.Modules.Gambling/Games/Polls/PollCommands.cs b/src/Nadeko.Bot.Modules.Gambling/Games/Polls/PollCommands.cs deleted file mode 100644 index 9ac9c1178..000000000 --- a/src/Nadeko.Bot.Modules.Gambling/Games/Polls/PollCommands.cs +++ /dev/null @@ -1,102 +0,0 @@ -#nullable disable -using NadekoBot.Modules.Games.Services; -using Nadeko.Bot.Db.Models; -using System.Text; - -namespace NadekoBot.Modules.Games; - -public partial class Games -{ - [Group] - public partial class PollCommands : NadekoModule - { - private readonly DiscordSocketClient _client; - - public PollCommands(DiscordSocketClient client) - => _client = client; - - [Cmd] - [UserPerm(GuildPerm.ManageMessages)] - [RequireContext(ContextType.Guild)] - public async Task Poll([Leftover] string arg) - { - if (string.IsNullOrWhiteSpace(arg)) - return; - - var poll = _service.CreatePoll(ctx.Guild.Id, ctx.Channel.Id, arg); - if (poll is null) - { - await ReplyErrorLocalizedAsync(strs.poll_invalid_input); - return; - } - - if (_service.StartPoll(poll)) - { - await ctx.Channel.EmbedAsync(_eb.Create() - .WithOkColor() - .WithTitle(GetText(strs.poll_created(ctx.User.ToString()))) - .WithDescription(Format.Bold(poll.Question) - + "\n\n" - + string.Join("\n", - poll.Answers.Select(x - => $"`{x.Index + 1}.` {Format.Bold(x.Text)}")))); - } - else - await ReplyErrorLocalizedAsync(strs.poll_already_running); - } - - [Cmd] - [UserPerm(GuildPerm.ManageMessages)] - [RequireContext(ContextType.Guild)] - public async Task PollStats() - { - if (!_service.ActivePolls.TryGetValue(ctx.Guild.Id, out var pr)) - return; - - await ctx.Channel.EmbedAsync(GetStats(pr.Poll, GetText(strs.current_poll_results))); - } - - [Cmd] - [UserPerm(GuildPerm.ManageMessages)] - [RequireContext(ContextType.Guild)] - public async Task Pollend() - { - Poll p; - if ((p = _service.StopPoll(ctx.Guild.Id)) is null) - return; - - var embed = GetStats(p, GetText(strs.poll_closed)); - await ctx.Channel.EmbedAsync(embed); - } - - public IEmbedBuilder GetStats(Poll poll, string title) - { - var results = poll.Votes.GroupBy(kvp => kvp.VoteIndex).ToDictionary(x => x.Key, x => x.Sum(_ => 1)); - - var totalVotesCast = results.Sum(x => x.Value); - - var eb = _eb.Create().WithTitle(title); - - var sb = new StringBuilder().AppendLine(Format.Bold(poll.Question)).AppendLine(); - - var stats = poll.Answers.Select(x => - { - results.TryGetValue(x.Index, out var votes); - - return (x.Index, votes, x.Text); - }) - .OrderByDescending(x => x.votes) - .ToArray(); - - for (var i = 0; i < stats.Length; i++) - { - var (index, votes, text) = stats[i]; - sb.AppendLine(GetText(strs.poll_result(index + 1, Format.Bold(text), Format.Bold(votes.ToString())))); - } - - return eb.WithDescription(sb.ToString()) - .WithFooter(GetText(strs.x_votes_cast(totalVotesCast))) - .WithOkColor(); - } - } -} \ No newline at end of file diff --git a/src/Nadeko.Bot.Modules.Gambling/Games/Polls/PollExtensions.cs b/src/Nadeko.Bot.Modules.Gambling/Games/Polls/PollExtensions.cs deleted file mode 100644 index ce8b7c414..000000000 --- a/src/Nadeko.Bot.Modules.Gambling/Games/Polls/PollExtensions.cs +++ /dev/null @@ -1,36 +0,0 @@ -#nullable disable -using Microsoft.EntityFrameworkCore; - -using Nadeko.Bot.Db.Models; - -namespace NadekoBot.Db; - -public static class PollExtensions -{ - public static IEnumerable GetAllPolls(this DbSet polls) - => polls.Include(x => x.Answers) - .Include(x => x.Votes) - .ToArray(); - - public static void RemovePoll(this DbContext ctx, int id) - { - var p = ctx.Set().Include(x => x.Answers).Include(x => x.Votes).FirstOrDefault(x => x.Id == id); - - if (p is null) - return; - - if (p.Votes is not null) - { - ctx.RemoveRange(p.Votes); - p.Votes.Clear(); - } - - if (p.Answers is not null) - { - ctx.RemoveRange(p.Answers); - p.Answers.Clear(); - } - - ctx.Set().Remove(p); - } -} \ No newline at end of file diff --git a/src/Nadeko.Bot.Modules.Gambling/Games/Polls/PollRunner.cs b/src/Nadeko.Bot.Modules.Gambling/Games/Polls/PollRunner.cs deleted file mode 100644 index 86efb234e..000000000 --- a/src/Nadeko.Bot.Modules.Gambling/Games/Polls/PollRunner.cs +++ /dev/null @@ -1,63 +0,0 @@ -#nullable disable -using Nadeko.Bot.Db.Models; - -namespace NadekoBot.Modules.Games.Common; - -public class PollRunner -{ - public event Func OnVoted; - public Poll Poll { get; } - private readonly DbService _db; - - private readonly SemaphoreSlim _locker = new(1, 1); - - public PollRunner(DbService db, Poll poll) - { - _db = db; - Poll = poll; - } - - public async Task TryVote(IUserMessage msg) - { - PollVote voteObj; - await _locker.WaitAsync(); - try - { - // has to be a user message - // channel must be the same the poll started in - if (msg is null || msg.Author.IsBot || msg.Channel.Id != Poll.ChannelId) - return false; - - // has to be an integer - if (!int.TryParse(msg.Content, out var vote)) - return false; - --vote; - if (vote < 0 || vote >= Poll.Answers.Count) - return false; - - var usr = msg.Author as IGuildUser; - if (usr is null) - return false; - - voteObj = new() - { - UserId = msg.Author.Id, - VoteIndex = vote - }; - if (!Poll.Votes.Add(voteObj)) - return false; - - _ = OnVoted?.Invoke(msg, usr); - } - finally { _locker.Release(); } - - await using var uow = _db.GetDbContext(); - var trackedPoll = uow.Set().FirstOrDefault(x => x.Id == Poll.Id); - trackedPoll.Votes.Add(voteObj); - uow.SaveChanges(); - return true; - } - - public void End() - => OnVoted = null; -} \ No newline at end of file diff --git a/src/Nadeko.Bot.Modules.Gambling/Games/Polls/PollService.cs b/src/Nadeko.Bot.Modules.Gambling/Games/Polls/PollService.cs deleted file mode 100644 index a7ccfa88e..000000000 --- a/src/Nadeko.Bot.Modules.Gambling/Games/Polls/PollService.cs +++ /dev/null @@ -1,140 +0,0 @@ -#nullable disable -using NadekoBot.Common.ModuleBehaviors; -using NadekoBot.Db; -using NadekoBot.Modules.Games.Common; -using Nadeko.Bot.Db.Models; - -namespace NadekoBot.Modules.Games.Services; - -public class PollService : IExecOnMessage -{ - public ConcurrentDictionary ActivePolls { get; } = new(); - - public int Priority - => 5; - - private readonly DbService _db; - private readonly IBotStrings _strs; - private readonly IEmbedBuilderService _eb; - - public PollService(DbService db, IBotStrings strs, IEmbedBuilderService eb) - { - _db = db; - _strs = strs; - _eb = eb; - - using var uow = db.GetDbContext(); - ActivePolls = uow.Set().GetAllPolls() - .ToDictionary(x => x.GuildId, - x => - { - var pr = new PollRunner(db, x); - pr.OnVoted += Pr_OnVoted; - return pr; - }) - .ToConcurrent(); - } - - public Poll CreatePoll(ulong guildId, ulong channelId, string input) - { - if (string.IsNullOrWhiteSpace(input) || !input.Contains(";")) - return null; - var data = input.Split(';'); - if (data.Length < 3) - return null; - - var col = new IndexedCollection(data.Skip(1) - .Select(x => new PollAnswer - { - Text = x - })); - - return new() - { - Answers = col, - Question = data[0], - ChannelId = channelId, - GuildId = guildId, - Votes = new() - }; - } - - public bool StartPoll(Poll p) - { - var pr = new PollRunner(_db, p); - if (ActivePolls.TryAdd(p.GuildId, pr)) - { - using (var uow = _db.GetDbContext()) - { - uow.Set().Add(p); - uow.SaveChanges(); - } - - pr.OnVoted += Pr_OnVoted; - return true; - } - - return false; - } - - public Poll StopPoll(ulong guildId) - { - if (ActivePolls.TryRemove(guildId, out var pr)) - { - pr.OnVoted -= Pr_OnVoted; - - using var uow = _db.GetDbContext(); - uow.RemovePoll(pr.Poll.Id); - uow.SaveChanges(); - - return pr.Poll; - } - - return null; - } - - private async Task Pr_OnVoted(IUserMessage msg, IGuildUser usr) - { - var toDelete = await msg.Channel.SendConfirmAsync(_eb, - _strs.GetText(strs.poll_voted(Format.Bold(usr.ToString())), usr.GuildId)); - toDelete.DeleteAfter(5); - try - { - await msg.DeleteAsync(); - } - catch - { - } - } - - public async Task ExecOnMessageAsync(IGuild guild, IUserMessage msg) - { - if (guild is null) - return false; - - if (!ActivePolls.TryGetValue(guild.Id, out var poll)) - return false; - - try - { - var voted = await poll.TryVote(msg); - - if (voted) - { - Log.Information("User {UserName} [{UserId}] voted in a poll on {GuildName} [{GuildId}] server", - msg.Author.ToString(), - msg.Author.Id, - guild.Name, - guild.Id); - } - - return voted; - } - catch (Exception ex) - { - Log.Warning(ex, "Error voting"); - } - - return false; - } -} \ No newline at end of file diff --git a/src/Nadeko.Bot.Modules.Music/Music.cs b/src/Nadeko.Bot.Modules.Music/Music.cs index eb43588c3..6dcb26212 100644 --- a/src/Nadeko.Bot.Modules.Music/Music.cs +++ b/src/Nadeko.Bot.Modules.Music/Music.cs @@ -589,36 +589,6 @@ public sealed partial class Music : NadekoModule await ctx.Channel.EmbedAsync(embed); } - [Cmd] - [RequireContext(ContextType.Guild)] - public Task SoundCloudQueue([Leftover] string query) - => QueueByQuery(query, false, MusicPlatform.SoundCloud); - - [Cmd] - [RequireContext(ContextType.Guild)] - public async Task SoundCloudPl([Leftover] string playlist) - { - if (string.IsNullOrWhiteSpace(playlist)) - return; - - var succ = await QueuePreconditionInternalAsync(); - if (!succ) - return; - - var mp = await _service.GetOrCreateMusicPlayerAsync((ITextChannel)ctx.Channel); - if (mp is null) - { - await ReplyErrorLocalizedAsync(strs.no_player); - return; - } - - _ = ctx.Channel.TriggerTypingAsync(); - - await _service.EnqueueSoundcloudPlaylistAsync(mp, playlist, ctx.User.ToString()); - - await ctx.OkAsync(); - } - [Cmd] [RequireContext(ContextType.Guild)] public async Task Playlist([Leftover] string playlistQuery) diff --git a/src/Nadeko.Bot.Modules.Music/Services/IMusicService.cs b/src/Nadeko.Bot.Modules.Music/Services/IMusicService.cs index 0ae296db5..e8abf08fd 100644 --- a/src/Nadeko.Bot.Modules.Music/Services/IMusicService.cs +++ b/src/Nadeko.Bot.Modules.Music/Services/IMusicService.cs @@ -22,7 +22,6 @@ public interface IMusicService : IPlaceholderProvider bool TryGetMusicPlayer(ulong guildId, [MaybeNullWhen(false)] out IMusicPlayer musicPlayer); Task EnqueueYoutubePlaylistAsync(IMusicPlayer mp, string playlistId, string queuer); Task EnqueueDirectoryAsync(IMusicPlayer mp, string dirPath, string queuer); - Task EnqueueSoundcloudPlaylistAsync(IMusicPlayer mp, string playlist, string queuer); Task SendToOutputAsync(ulong guildId, IEmbedBuilder embed); Task PlayAsync(ulong guildId, ulong voiceChannelId); Task> SearchVideosAsync(string query); diff --git a/src/Nadeko.Bot.Modules.Music/Services/MusicService.cs b/src/Nadeko.Bot.Modules.Music/Services/MusicService.cs index d7e5c1925..314df27cf 100644 --- a/src/Nadeko.Bot.Modules.Music/Services/MusicService.cs +++ b/src/Nadeko.Bot.Modules.Music/Services/MusicService.cs @@ -11,7 +11,6 @@ public sealed class MusicService : IMusicService private readonly DbService _db; private readonly IYoutubeResolver _ytResolver; private readonly ILocalTrackResolver _localResolver; - private readonly ISoundcloudResolver _scResolver; private readonly DiscordSocketClient _client; private readonly IBotStrings _strings; private readonly IGoogleApiService _googleApiService; @@ -28,7 +27,6 @@ public sealed class MusicService : IMusicService DbService db, IYoutubeResolver ytResolver, ILocalTrackResolver localResolver, - ISoundcloudResolver scResolver, DiscordSocketClient client, IBotStrings strings, IGoogleApiService googleApiService, @@ -40,7 +38,6 @@ public sealed class MusicService : IMusicService _db = db; _ytResolver = ytResolver; _localResolver = localResolver; - _scResolver = scResolver; _client = client; _strings = strings; _googleApiService = googleApiService; @@ -120,21 +117,6 @@ public sealed class MusicService : IMusicService } } - public async Task EnqueueSoundcloudPlaylistAsync(IMusicPlayer mp, string playlist, string queuer) - { - var i = 0; - await foreach (var track in _scResolver.ResolvePlaylistAsync(playlist)) - { - if (mp.IsKilled) - break; - - mp.EnqueueTrack(track, queuer); - ++i; - } - - return i; - } - private async Task CreateMusicPlayerInternalAsync(ulong guildId, ITextChannel defaultChannel) { var queue = new MusicQueue(); diff --git a/src/Nadeko.Bot.Modules.Music/Services/SoundCloudApiService.cs b/src/Nadeko.Bot.Modules.Music/Services/SoundCloudApiService.cs deleted file mode 100644 index 9bc250d26..000000000 --- a/src/Nadeko.Bot.Modules.Music/Services/SoundCloudApiService.cs +++ /dev/null @@ -1,78 +0,0 @@ -#nullable disable -using Newtonsoft.Json; - -namespace NadekoBot.Services; - -public class SoundCloudApiService : INService -{ - private readonly IHttpClientFactory _httpFactory; - - public SoundCloudApiService(IHttpClientFactory factory) - => _httpFactory = factory; - - public async Task ResolveVideoAsync(string url) - { - if (string.IsNullOrWhiteSpace(url)) - throw new ArgumentNullException(nameof(url)); - - var response = string.Empty; - - using (var http = _httpFactory.CreateClient()) - { - response = await http.GetStringAsync($"https://scapi.nadeko.bot/resolve?url={url}"); - } - - var responseObj = JsonConvert.DeserializeObject(response); - if (responseObj?.Kind != "track") - throw new InvalidOperationException("Url is either not a track, or it doesn't exist."); - - return responseObj; - } - - public async Task GetVideoByQueryAsync(string query) - { - if (string.IsNullOrWhiteSpace(query)) - throw new ArgumentNullException(nameof(query)); - - var response = string.Empty; - using (var http = _httpFactory.CreateClient()) - { - response = await http.GetStringAsync( - new Uri($"https://scapi.nadeko.bot/tracks?q={Uri.EscapeDataString(query)}")); - } - - var responseObj = JsonConvert.DeserializeObject(response) - .FirstOrDefault(s => s.Streamable is true); - - if (responseObj?.Kind != "track") - throw new InvalidOperationException("Query yielded no results."); - - return responseObj; - } -} - -public class SoundCloudVideo -{ - public string Kind { get; set; } = string.Empty; - public long Id { get; set; } = 0; - public SoundCloudUser User { get; set; } = new(); - public string Title { get; set; } = string.Empty; - - public string FullName - => User.Name + " - " + Title; - - public bool? Streamable { get; set; } = false; - public int Duration { get; set; } - - [JsonProperty("permalink_url")] - public string TrackLink { get; set; } = string.Empty; - - [JsonProperty("artwork_url")] - public string ArtworkUrl { get; set; } = string.Empty; -} - -public class SoundCloudUser -{ - [JsonProperty("username")] - public string Name { get; set; } -} \ No newline at end of file diff --git a/src/Nadeko.Bot.Modules.Music/_common/ISoundcloudResolver.cs b/src/Nadeko.Bot.Modules.Music/_common/ISoundcloudResolver.cs deleted file mode 100644 index 3669f9ebd..000000000 --- a/src/Nadeko.Bot.Modules.Music/_common/ISoundcloudResolver.cs +++ /dev/null @@ -1,8 +0,0 @@ -#nullable disable -namespace NadekoBot.Modules.Music; - -public interface ISoundcloudResolver : IPlatformQueryResolver -{ - bool IsSoundCloudLink(string url); - IAsyncEnumerable ResolvePlaylistAsync(string playlist); -} \ No newline at end of file diff --git a/src/Nadeko.Bot.Modules.Music/_common/Impl/MusicPlatform.cs b/src/Nadeko.Bot.Modules.Music/_common/Impl/MusicPlatform.cs index 55968086b..7eb207181 100644 --- a/src/Nadeko.Bot.Modules.Music/_common/Impl/MusicPlatform.cs +++ b/src/Nadeko.Bot.Modules.Music/_common/Impl/MusicPlatform.cs @@ -6,5 +6,4 @@ public enum MusicPlatform Radio, Youtube, Local, - SoundCloud } \ No newline at end of file diff --git a/src/Nadeko.Bot.Modules.Music/_common/Resolvers/SoundcloudResolver.cs b/src/Nadeko.Bot.Modules.Music/_common/Resolvers/SoundcloudResolver.cs deleted file mode 100644 index d4c0defd2..000000000 --- a/src/Nadeko.Bot.Modules.Music/_common/Resolvers/SoundcloudResolver.cs +++ /dev/null @@ -1,84 +0,0 @@ -using Newtonsoft.Json.Linq; -using System.Runtime.CompilerServices; -using System.Text.RegularExpressions; - -namespace NadekoBot.Modules.Music.Resolvers; - -public sealed class SoundcloudResolver : ISoundcloudResolver -{ - private readonly SoundCloudApiService _sc; - private readonly ITrackCacher _trackCacher; - private readonly IHttpClientFactory _httpFactory; - - public SoundcloudResolver(SoundCloudApiService sc, ITrackCacher trackCacher, IHttpClientFactory httpFactory) - { - _sc = sc; - _trackCacher = trackCacher; - _httpFactory = httpFactory; - } - - public bool IsSoundCloudLink(string url) - => Regex.IsMatch(url, "(.*)(soundcloud.com|snd.sc)(.*)"); - - public async IAsyncEnumerable ResolvePlaylistAsync(string playlist) - { - playlist = Uri.EscapeDataString(playlist); - - using var http = _httpFactory.CreateClient(); - var responseString = await http.GetStringAsync($"https://scapi.nadeko.bot/resolve?url={playlist}"); - var scvids = JObject.Parse(responseString)["tracks"]?.ToObject(); - if (scvids is null) - yield break; - - foreach (var videosChunk in scvids.Where(x => x.Streamable is true).Chunk(5)) - { - var cachableTracks = videosChunk.Select(VideoModelToCachedData).ToList(); - - await cachableTracks.Select(_trackCacher.CacheTrackDataAsync).WhenAll(); - foreach (var info in cachableTracks.Select(CachableDataToTrackInfo)) - yield return info; - } - } - - private ICachableTrackData VideoModelToCachedData(SoundCloudVideo svideo) - => new CachableTrackData - { - Title = svideo.FullName, - Url = svideo.TrackLink, - Thumbnail = svideo.ArtworkUrl, - TotalDurationMs = svideo.Duration, - Id = svideo.Id.ToString(), - Platform = MusicPlatform.SoundCloud - }; - - private ITrackInfo CachableDataToTrackInfo(ICachableTrackData trackData) - => new SimpleTrackInfo(trackData.Title, - trackData.Url, - trackData.Thumbnail, - trackData.Duration, - trackData.Platform, - GetStreamUrl(trackData.Id)); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private string GetStreamUrl(string trackId) - => $"https://api.soundcloud.com/tracks/{trackId}/stream?client_id=368b0c85751007cd588d869d3ae61ac0"; - - public async Task ResolveByQueryAsync(string query) - { - var cached = await _trackCacher.GetCachedDataByQueryAsync(query, MusicPlatform.SoundCloud); - if (cached is not null) - return CachableDataToTrackInfo(cached); - - var svideo = !IsSoundCloudLink(query) - ? await _sc.GetVideoByQueryAsync(query) - : await _sc.ResolveVideoAsync(query); - - if (svideo is null) - return null; - - var cachableData = VideoModelToCachedData(svideo); - await _trackCacher.CacheTrackDataByQueryAsync(query, cachableData); - - return CachableDataToTrackInfo(cachableData); - } -} \ No newline at end of file diff --git a/src/Nadeko.Bot.Modules.Music/_common/Resolvers/TrackResolveProvider.cs b/src/Nadeko.Bot.Modules.Music/_common/Resolvers/TrackResolveProvider.cs index d45c87678..54b85fa5d 100644 --- a/src/Nadeko.Bot.Modules.Music/_common/Resolvers/TrackResolveProvider.cs +++ b/src/Nadeko.Bot.Modules.Music/_common/Resolvers/TrackResolveProvider.cs @@ -4,18 +4,15 @@ public sealed class TrackResolveProvider : ITrackResolveProvider { private readonly IYoutubeResolver _ytResolver; private readonly ILocalTrackResolver _localResolver; - private readonly ISoundcloudResolver _soundcloudResolver; private readonly IRadioResolver _radioResolver; public TrackResolveProvider( IYoutubeResolver ytResolver, ILocalTrackResolver localResolver, - ISoundcloudResolver soundcloudResolver, IRadioResolver radioResolver) { _ytResolver = ytResolver; _localResolver = localResolver; - _soundcloudResolver = soundcloudResolver; _radioResolver = radioResolver; } @@ -29,14 +26,10 @@ public sealed class TrackResolveProvider : ITrackResolveProvider return _ytResolver.ResolveByQueryAsync(query); case MusicPlatform.Local: return _localResolver.ResolveByQueryAsync(query); - case MusicPlatform.SoundCloud: - return _soundcloudResolver.ResolveByQueryAsync(query); case null: var match = _ytResolver.YtVideoIdRegex.Match(query); if (match.Success) return _ytResolver.ResolveByIdAsync(match.Groups["id"].Value); - else if (_soundcloudResolver.IsSoundCloudLink(query)) - return _soundcloudResolver.ResolveByQueryAsync(query); else if (Uri.TryCreate(query, UriKind.Absolute, out var uri) && uri.IsFile) return _localResolver.ResolveByQueryAsync(uri.AbsolutePath); else if (IsRadioLink(query)) diff --git a/src/Nadeko.Bot.Modules.Patronage/Patronage/PatronageCommands.cs b/src/Nadeko.Bot.Modules.Patronage/Patronage/PatronageCommands.cs index 9fd961929..ebbf76396 100644 --- a/src/Nadeko.Bot.Modules.Patronage/Patronage/PatronageCommands.cs +++ b/src/Nadeko.Bot.Modules.Patronage/Patronage/PatronageCommands.cs @@ -37,7 +37,6 @@ public partial class Patronage : NadekoModule Format.Bold(result.Failed.ToString()))); } - // [Cmd] // [OwnerOnly] // public async Task PatronGift(IUser user, int amount) // { diff --git a/src/Nadeko.Bot.Modules.Utility/TodoCommands.cs b/src/Nadeko.Bot.Modules.Utility/Utility.cs similarity index 94% rename from src/Nadeko.Bot.Modules.Utility/TodoCommands.cs rename to src/Nadeko.Bot.Modules.Utility/Utility.cs index 7489009db..cb7565b9d 100644 --- a/src/Nadeko.Bot.Modules.Utility/TodoCommands.cs +++ b/src/Nadeko.Bot.Modules.Utility/Utility.cs @@ -306,8 +306,7 @@ public partial class Utility : NadekoModule } [Cmd] - public async Task - Showemojis([Leftover] string _) // need to have the parameter so that the message.tags gets populated + public async Task Showemojis([Leftover] string _) { var tags = ctx.Message.Tags.Where(t => t.Type == TagType.Emoji).Select(t => (Emote)t.Value); @@ -429,38 +428,6 @@ public partial class Utility : NadekoModule using var http = _httpFactory.CreateClient(); stream = await http.GetStreamAsync(ss.GetStickerUrl()); } - // else if (ctx.Message.Attachments.FirstOrDefault() is { } attachment) - // { - // var url = attachment?.Url; - // - // if (url is null) - // return; - // - // if (name is null) - // { - // await ReplyErrorLocalizedAsync(strs.sticker_missing_name); - // return; - // } - // - // format = Path.GetExtension(attachment.Filename); - // - // if (attachment is not { Width: 300, Height: 300 }) - // { - // await ReplyErrorLocalizedAsync(strs.sticker_invalid_size); - // return; - // } - // - // using var http = _httpFactory.CreateClient(); - // - // using var res = await http.GetAsync(url, HttpCompletionOption.ResponseHeadersRead); - // if (res.GetContentLength() > 512.Kilobytes().Bytes) - // { - // await ReplyErrorLocalizedAsync(strs.invalid_emoji_link); - // return; - // } - // - // stream = await res.Content.ReadAsStreamAsync(); - // } else { await ReplyErrorLocalizedAsync(strs.sticker_error); diff --git a/src/NadekoBot/Db/NadekoContext.cs b/src/NadekoBot/Db/NadekoContext.cs index 7f5185a0a..bba7276a6 100644 --- a/src/NadekoBot/Db/NadekoContext.cs +++ b/src/NadekoBot/Db/NadekoContext.cs @@ -42,10 +42,8 @@ public abstract class NadekoContext : DbContext public DbSet DiscordUser { get; set; } public DbSet MusicPlayerSettings { get; set; } public DbSet Repeaters { get; set; } - public DbSet Poll { get; set; } public DbSet WaifuInfo { get; set; } public DbSet ImageOnlyChannels { get; set; } - public DbSet NsfwBlacklistedTags { get; set; } public DbSet AutoTranslateChannels { get; set; } public DbSet AutoTranslateUsers { get; set; } @@ -285,12 +283,6 @@ public abstract class NadekoContext : DbContext #endregion - #region Polls - - modelBuilder.Entity().HasIndex(x => x.GuildId).IsUnique(); - - #endregion - #region CurrencyTransactions modelBuilder.Entity(e => @@ -402,8 +394,6 @@ public abstract class NadekoContext : DbContext modelBuilder.Entity(ioc => ioc.HasIndex(x => x.ChannelId).IsUnique()); - modelBuilder.Entity(nbt => nbt.HasIndex(x => x.GuildId).IsUnique(false)); - var atch = modelBuilder.Entity(); atch.HasIndex(x => x.GuildId).IsUnique(false); diff --git a/src/NadekoBot/_common/ServiceCollectionExtensions.cs b/src/NadekoBot/_common/ServiceCollectionExtensions.cs index 72f69542f..a30307987 100644 --- a/src/NadekoBot/_common/ServiceCollectionExtensions.cs +++ b/src/NadekoBot/_common/ServiceCollectionExtensions.cs @@ -59,7 +59,6 @@ public static class ServiceCollectionExtensions kernel.Bind().To().InSingletonScope(); kernel.Bind().To().InSingletonScope(); - kernel.Bind().To().InSingletonScope(); kernel.Bind().To().InSingletonScope(); kernel.Bind().To().InSingletonScope(); kernel.Bind().To().InSingletonScope(); diff --git a/src/NadekoBot/data/aliases.yml b/src/NadekoBot/data/aliases.yml index 6f8799189..bafe3535d 100644 --- a/src/NadekoBot/data/aliases.yml +++ b/src/NadekoBot/data/aliases.yml @@ -418,8 +418,6 @@ typestop: - typestop typeadd: - typeadd -pollend: - - pollend pick: - pick plant: @@ -434,8 +432,6 @@ choose: - choose rps: - rps -linux: - - linux next: - next - n @@ -463,9 +459,6 @@ queuesearch: - queuesearch - qs - yqs -soundcloudqueue: - - soundcloudqueue - - sq listqueue: - listqueue - lq @@ -479,9 +472,6 @@ volume: playlist: - playlist - pl -soundcloudpl: - - soundcloudpl - - scpl localplaylist: - localplaylist - lopl @@ -651,8 +641,6 @@ chucknorris: magicitem: - magicitem - mi -safebooru: - - safebooru wiki: - wiki - wikipedia @@ -662,25 +650,6 @@ color: avatar: - avatar - av -hentai: - - hentai -danbooru: - - danbooru -derpibooru: - - derpibooru - - derpi -gelbooru: - - gelbooru -rule34: - - rule34 -e621: - - e621 -boobs: - - boobs -butts: - - butts - - ass - - butt translate: - translate - trans @@ -761,10 +730,6 @@ chatmute: - chatmute voicemute: - voicemute -konachan: - - konachan -sankaku: - - sankaku muterole: - muterole - setmuterole @@ -782,9 +747,6 @@ placelist: - placelist place: - place -poll: - - poll - - ppoll autotranslang: - autotranslang - atl @@ -801,8 +763,6 @@ typelist: - typelist listservers: - listservers -hentaibomb: - - hentaibomb cleverbot: - cleverbot - chatgpt @@ -811,8 +771,6 @@ shorten: wikia: - wikia - fandom -yandere: - - yandere magicthegathering: - magicthegathering - mtg @@ -837,8 +795,6 @@ define: - def activity: - activity -autohentai: - - autohentai setstatus: - setstatus invitecreate: @@ -852,8 +808,6 @@ invitedelete: - invitedelete - invrm - invdel -pollstats: - - pollstats antilist: - antilist - antilst @@ -923,8 +877,6 @@ languageset: languageslist: - languageslist - langli -rategirl: - - rategirl aliaslist: - aliaslist - cmdmaplist @@ -1059,9 +1011,6 @@ configreload: - creload - confreload - crel -nsfwtagblacklist: - - nsfwtagbl - - nsfwtbl experience: - experience - xp @@ -1141,10 +1090,6 @@ clubleaderboard: - clubs clubadmin: - clubadmin -autoboobs: - - autoboobs -autobutts: - - autobutts eightball: - eightball - 8ball @@ -1253,10 +1198,9 @@ delete: roleid: - roleid - rid -nsfwtoggle: +agerestricttoggle: - nsfwtoggle - - nsfw - - nsfwtgl + - artoggle economy: - economy purgeuser: @@ -1410,7 +1354,7 @@ giveawaylist: - list # todos todoadd: - - add + - add - a todolist: - list diff --git a/src/NadekoBot/data/strings/commands/commands.en-US.yml b/src/NadekoBot/data/strings/commands/commands.en-US.yml index de81ac5d7..f2f0be14d 100644 --- a/src/NadekoBot/data/strings/commands/commands.en-US.yml +++ b/src/NadekoBot/data/strings/commands/commands.en-US.yml @@ -800,10 +800,6 @@ rps: args: - "r 100" - "scissors" -linux: - desc: "Prints a customizable Linux interjection" - args: - - "Spyware Windows" next: desc: "Goes to the next song in the queue. You have to be in the same voice channel as the bot" args: @@ -838,10 +834,6 @@ queuesearch: desc: "Search for top 5 youtube song result using keywords, and type the index of the song to play that song. Bot will join your voice channel. **You must be in a voice channel**." args: - "Dream Of Venice" -soundcloudqueue: - desc: "Queue a soundcloud song using keywords. Bot will join your voice channel. **You must be in a voice channel**." - args: - - "Dream Of Venice" listqueue: desc: "Lists 10 currently queued songs per page. Default page is 1." args: @@ -859,11 +851,6 @@ playlist: desc: "Queues up to 500 songs from a youtube playlist specified by a link, or keywords." args: - "" -soundcloudpl: - desc: "Queue a Soundcloud playlist using a link." - args: - - "https://soundcloud.com/classical-music-playlist/sets/classical-music-essential-collection" - - "" localplaylist: desc: "Queues all songs from a directory." args: @@ -1117,59 +1104,6 @@ avatar: desc: "Shows a mentioned person's avatar." args: - "@Someone" -hentai: - desc: "Shows a hentai image from a random website (gelbooru, danbooru, konachan or yandere) with a given tag. Tag(s) are optional but preferred. Maximum is usually 2 tags. Only 1 tag allowed." - args: - - "yuri" -autohentai: - desc: "Posts a hentai every X seconds with a random tag from the provided tags. Use `|` to separate tag groups. Random group will be chosen every time the image is sent. Max 2 tags per group. 20 seconds minimum. Provide no parameters to disable." - args: - - "30 yuri kissing|tail long_hair" - - "" -hentaibomb: - desc: "Shows a total 5 images (from gelbooru, danbooru, konachan and yandere). Tag(s) are optional but preferred. Maximum is usually 2 tags." - args: - - "yuri" -yandere: - desc: "Shows a random image from yandere with a given tag. Tag(s) are optional but preferred. Maximum is usually 2 tags." - args: - - "yuri kissing" -danbooru: - desc: "Shows a random hentai image from danbooru with a given tag. Tag(s) are optional but preferred. Maximum is usually 2 tags." - args: - - "yuri kissing" -derpibooru: - desc: "Shows a random image from derpibooru with a given tag. Tag(s) are optional but preferred. Maximum is usually 2 tags." - args: - - "yuri kissing" -gelbooru: - desc: "Shows a random hentai image from gelbooru with a given tag. Tag(s) are optional but preferred. Maximum is usually 2 tags." - args: - - "yuri kissing" -sankaku: - desc: "Shows a random hentai image from chan.sankakucomplex.com with a given tag. Tag(s) are optional but preferred. Maximum is usually 2 tags." - args: - - "yuri kiss" -rule34: - desc: "Shows a random image from rule34.xx with a given tag. Tag(s) are optional but preferred. Maximum is usually 2 tags." - args: - - "yuri kissing" -e621: - desc: "Shows a random hentai image from e621.net with a given tag. Tag(s) are optional but preferred. Maximum is usually 2 tags." - args: - - "yuri kissing" -safebooru: - desc: "Shows a random image from safebooru with a given tag. Tag(s) are optional but preferred. Maximum is usually 2 tags." - args: - - "yuri kissing" -boobs: - desc: "Real adult content." - args: - - "" -butts: - desc: "Real adult content." - args: - - "" translate: desc: "Translates text from the given language to the destination language." args: @@ -1581,10 +1515,6 @@ languageslist: desc: "List of languages for which translation (or part of it) exist atm." args: - "" -rategirl: - desc: "Use the universal hot-crazy wife zone matrix to determine the girl's worth. It is everything young men need to know about women. At any moment in time, any woman you have previously located on this chart can vanish from that location and appear anywhere else on the chart." - args: - - "@SomeGurl" exprtoggleglobal: desc: "Toggles whether global expressions are usable on this server." args: @@ -1640,7 +1570,6 @@ alias: desc: "Create a custom alias for a certain Nadeko command. Provide no alias to remove the existing one." args: - "allin {0}bf all h" - - "\"linux thingy\" >loonix Spyware Windows" warnlog: desc: "See a list of warnings of a certain user." args: @@ -1986,16 +1915,6 @@ clubadmin: desc: "Assigns (or unassigns) staff role to the member of the club. Admins can ban, kick and accept applications." args: - "@Someone" -autoboobs: - desc: "Posts a boobs every X seconds. 20 seconds minimum. Provide no parameters to disable." - args: - - "30" - - "" -autobutts: - desc: "Posts a butt every X seconds. 20 seconds minimum. Provide no parameters to disable." - args: - - "30" - - "" eightball: desc: "Ask the 8ball a yes/no question." args: @@ -2232,8 +2151,8 @@ roleid: desc: "Shows the id of the specified role." args: - "Some Role" -nsfwtoggle: - desc: "Toggles the NSFW parameter of the current text channel." +agerestricttoggle: + desc: "Toggles whether the current channel is age-restricted." args: - "" economy: diff --git a/src/NadekoBot/data/strings/responses/responses.en-US.json b/src/NadekoBot/data/strings/responses/responses.en-US.json index 50526799e..f0303446e 100644 --- a/src/NadekoBot/data/strings/responses/responses.en-US.json +++ b/src/NadekoBot/data/strings/responses/responses.en-US.json @@ -986,7 +986,7 @@ "module_description_gambling": "Bet on dice rolls, blackjack, slots, coinflips and others", "module_description_games": "Play trivia, nunchi, hangman, connect4 and other games", "module_description_nsfw": "NSFW commands.", - "module_description_music": "Play music from youtube, local files soundcloud and radio streams", + "module_description_music": "Play music from youtube, local files and radio streams", "module_description_utility": "Manage custom quotes, repeating messages and check facts about the server", "module_description_administration": "Moderation, punish users, setup self assignable roles and greet messages", "module_description_expressions": "Setup custom bot responses to certain words or phrases",