#nullable disable using LinqToDB; using EllieBot.Modules.Music.Services; using EllieBot.Db.Models; namespace EllieBot.Modules.Music; public sealed partial class Music { [Group] public sealed partial class PlaylistCommands : EllieModule<IMusicService> { private static readonly SemaphoreSlim _playlistLock = new(1, 1); private readonly DbService _db; private readonly IBotCreds _creds; public PlaylistCommands(DbService db, IBotCreds creds) { _db = db; _creds = creds; } private async Task EnsureBotInVoiceChannelAsync(ulong voiceChannelId, IGuildUser botUser = null) { botUser ??= await ctx.Guild.GetCurrentUserAsync(); await _voiceChannelLock.WaitAsync(); try { if (botUser.VoiceChannel?.Id is null || !_service.TryGetMusicPlayer(ctx.Guild.Id, out _)) await _service.JoinVoiceChannelAsync(ctx.Guild.Id, voiceChannelId); } finally { _voiceChannelLock.Release(); } } [Cmd] [RequireContext(ContextType.Guild)] public async Task Playlists([Leftover] int num = 1) { if (num <= 0) return; List<MusicPlaylist> playlists; await using (var uow = _db.GetDbContext()) { playlists = uow.Set<MusicPlaylist>().GetPlaylistsOnPage(num); } var embed = CreateEmbed() .WithAuthor(GetText(strs.playlists_page(num)), MUSIC_ICON_URL) .WithDescription(string.Join("\n", playlists.Select(r => GetText(strs.playlists(r.Id, r.Name, r.Author, r.Songs.Count))))) .WithOkColor(); await Response().Embed(embed).SendAsync(); } [Cmd] [RequireContext(ContextType.Guild)] public async Task DeletePlaylist([Leftover] int id) { var success = false; try { await using var uow = _db.GetDbContext(); var pl = uow.Set<MusicPlaylist>().FirstOrDefault(x => x.Id == id); if (pl is not null) { if (_creds.IsOwner(ctx.User) || pl.AuthorId == ctx.User.Id) { uow.Set<MusicPlaylist>().Remove(pl); await uow.SaveChangesAsync(); success = true; } } } catch (Exception ex) { Log.Warning(ex, "Error deleting playlist"); } if (!success) await Response().Error(strs.playlist_delete_fail).SendAsync(); else await Response().Confirm(strs.playlist_deleted).SendAsync(); } [Cmd] [RequireContext(ContextType.Guild)] public async Task PlaylistShow(int id, int page = 1) { if (page-- < 1) return; MusicPlaylist mpl; await using (var uow = _db.GetDbContext()) { mpl = uow.Set<MusicPlaylist>().GetWithSongs(id); } await Response() .Paginated() .Items(mpl.Songs) .PageSize(20) .CurrentPage(page) .Page((items, _) => { var i = 0; var str = string.Join("\n", items .Select(x => $"`{++i}.` [{x.Title.TrimTo(45)}]({x.Query}) `{x.Provider}`")); return CreateEmbed().WithTitle($"\"{mpl.Name}\" by {mpl.Author}") .WithOkColor() .WithDescription(str); }) .SendAsync(); } [Cmd] [RequireContext(ContextType.Guild)] public async Task Save([Leftover] string name) { if (!_service.TryGetMusicPlayer(ctx.Guild.Id, out var mp)) { await Response().Error(strs.no_player).SendAsync(); return; } var songs = mp.GetQueuedTracks() .Select(s => new PlaylistSong { Provider = s.Platform.ToString(), ProviderType = (MusicType)s.Platform, Title = s.Title, Query = s.Url }) .ToList(); MusicPlaylist playlist; await using (var uow = _db.GetDbContext()) { playlist = new() { Name = name, Author = ctx.User.Username, AuthorId = ctx.User.Id, Songs = songs.ToList() }; uow.Set<MusicPlaylist>().Add(playlist); await uow.SaveChangesAsync(); } await Response() .Embed(CreateEmbed() .WithOkColor() .WithTitle(GetText(strs.playlist_saved)) .AddField(GetText(strs.name), name) .AddField(GetText(strs.id), playlist.Id.ToString())) .SendAsync(); } [Cmd] [RequireContext(ContextType.Guild)] public async Task Load([Leftover] int id) { // expensive action, 1 at a time await _playlistLock.WaitAsync(); try { var user = (IGuildUser)ctx.User; var voiceChannelId = user.VoiceChannel?.Id; if (voiceChannelId is null) { await Response().Error(strs.must_be_in_voice).SendAsync(); return; } _ = ctx.Channel.TriggerTypingAsync(); var botUser = await ctx.Guild.GetCurrentUserAsync(); await EnsureBotInVoiceChannelAsync(voiceChannelId!.Value, botUser); if (botUser.VoiceChannel?.Id != voiceChannelId) { await Response().Error(strs.not_with_bot_in_voice).SendAsync(); return; } var mp = await _service.GetOrCreateMusicPlayerAsync((ITextChannel)ctx.Channel); if (mp is null) { await Response().Error(strs.no_player).SendAsync(); return; } MusicPlaylist mpl; await using (var uow = _db.GetDbContext()) { mpl = uow.Set<MusicPlaylist>().GetWithSongs(id); } if (mpl is null) { await Response().Error(strs.playlist_id_not_found).SendAsync(); return; } IUserMessage msg = null; try { msg = await Response() .Pending(strs.attempting_to_queue(Format.Bold(mpl.Songs.Count.ToString()))) .SendAsync(); } catch (Exception) { } await mp.EnqueueManyAsync(mpl.Songs.Select(x => (x.Query, (MusicPlatform)x.ProviderType)), ctx.User.ToString()); if (msg is not null) await msg.ModifyAsync(m => m.Content = GetText(strs.playlist_queue_complete)); } finally { _playlistLock.Release(); } } [Cmd] [OwnerOnly] public async Task DeletePlaylists() { await using var uow = _db.GetDbContext(); await uow.Set<MusicPlaylist>().DeleteAsync(); await uow.SaveChangesAsync(); } } }