ProjectMakoto.Plugins.Music/Commands/Playlists/ModifyCommand.cs
2025-04-07 21:33:05 +02:00

413 lines
No EOL
23 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Project Makoto
// Copyright (C) 2024 Fortunevale
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY
using DisCatSharp.Interactivity.Extensions;
using ProjectMakoto.Entities.Users;
using Xorog.UniversalExtensions;
using Xorog.UniversalExtensions.Enums;
namespace ProjectMakoto.Plugins.Music;
internal sealed class ModifyCommand : BaseCommand
{
public override Task ExecuteCommand(SharedCommandContext ctx, Dictionary<string, object> arguments)
{
var CommandKey = ((Entities.Translations)MusicPlugin.Plugin!.Translations).Commands.Music;
return Task.Run(async () =>
{
if (await ctx.DbUser.Cooldown.WaitForModerate(ctx))
return;
var playlistId = (string)arguments["playlist"];
if (!MusicPlugin.Plugin.Users[ctx.User.Id].Playlists.Any(x => x.PlaylistId == playlistId))
{
_ = await this.RespondOrEdit(new DiscordEmbedBuilder
{
Description = this.GetString(CommandKey.Playlists.NoPlaylist, true),
}.AsError(ctx, this.GetString(CommandKey.Playlists.Title)));
return;
}
var SelectedPlaylist = MusicPlugin.Plugin.Users[ctx.User.Id].Playlists.First(x => x.PlaylistId == playlistId);
var embed = new DiscordEmbedBuilder().AsInfo(ctx);
var LastInt = 0;
int GetInt()
{
LastInt++;
return LastInt;
}
var CurrentPage = 0;
async Task UpdateMessage()
{
LastInt = CurrentPage * 10;
var CurrentTracks = SelectedPlaylist.List.Skip(CurrentPage * 10).Take(10);
DiscordButtonComponent NextPage = new(ButtonStyle.Primary, "NextPage", this.GetString(this.t.Common.NextPage), false, new DiscordComponentEmoji(DiscordEmoji.FromUnicode("▶")));
DiscordButtonComponent PreviousPage = new(ButtonStyle.Primary, "PreviousPage", this.GetString(this.t.Common.PreviousPage), false, new DiscordComponentEmoji(DiscordEmoji.FromUnicode("◀")));
DiscordButtonComponent PlaylistName = new(ButtonStyle.Success, "ChangePlaylistName", this.GetString(CommandKey.Playlists.Modify.ChangeName), false, new DiscordComponentEmoji(DiscordEmoji.FromUnicode("💬")));
DiscordButtonComponent ChangePlaylistColor = new(ButtonStyle.Secondary, "ChangeColor", this.GetString(CommandKey.Playlists.Modify.ChangeColor), false, new DiscordComponentEmoji(DiscordEmoji.FromUnicode("🎨")));
DiscordButtonComponent ChangePlaylistThumbnail = new(ButtonStyle.Secondary, "ChangeThumbnail", this.GetString(CommandKey.Playlists.Modify.ChangeThumbnail), false, new DiscordComponentEmoji(DiscordEmoji.FromUnicode("🖼")));
DiscordButtonComponent AddSong = new(ButtonStyle.Success, "AddSong", this.GetString(CommandKey.Playlists.Modify.AddTracks), false, new DiscordComponentEmoji(DiscordEmoji.FromUnicode("")));
DiscordButtonComponent RemoveSong = new(ButtonStyle.Danger, "DeleteSong", this.GetString(CommandKey.Playlists.Modify.RemoveTracks), false, new DiscordComponentEmoji(DiscordEmoji.FromUnicode("🗑")));
DiscordButtonComponent RemoveDuplicates = new(ButtonStyle.Secondary, "RemoveDuplicates", this.GetString(CommandKey.Playlists.Modify.RemoveDuplicates), false, new DiscordComponentEmoji(DiscordEmoji.FromUnicode("♻")));
var TotalTimespan = TimeSpan.Zero;
for (var i = 0; i < SelectedPlaylist.List.Length; i++)
{
TotalTimespan = TotalTimespan.Add(SelectedPlaylist.List[i].Length.Value);
}
var Description = $"**`{this.GetString(CommandKey.Playlists.Modify.CurrentTrackCount, new TVar("Count", SelectedPlaylist.List.Length), new TVar("Timespan", TotalTimespan.GetHumanReadable()))}`**\n\n";
Description += $"{string.Join("\n", CurrentTracks.Select(x => $"**{GetInt()}**. `{x.Length.Value.GetShortHumanReadable(TimeFormat.Hours)}` {this.GetString(CommandKey.Playlists.Modify.Track, new TVar("Track", $"**[`{x.Title}`]({x.Url})**"), new TVar("Timestamp", Formatter.Timestamp(x.AddedTime)))}"))}";
if (SelectedPlaylist.List.Length > 0)
Description += $"\n\n`{this.GetString(this.t.Common.Page)} {CurrentPage + 1}/{Math.Ceiling(SelectedPlaylist.List.Length / 10.0)}`";
if (CurrentPage <= 0)
PreviousPage = PreviousPage.Disable();
if ((CurrentPage * 10) + 10 >= SelectedPlaylist.List.Length)
NextPage = NextPage.Disable();
embed.Author.IconUrl = ctx.Guild.IconUrl;
embed.Color = (SelectedPlaylist.PlaylistColor is "#FFFFFF" or null or "" ? EmbedColors.Info : new DiscordColor(SelectedPlaylist.PlaylistColor.IsValidHexColor()));
embed.Title = $"{this.GetString(CommandKey.Playlists.Modify.ModifyingPlaylist)}: `{SelectedPlaylist.PlaylistName}`";
embed.Description = Description;
embed.Thumbnail = new DiscordEmbedBuilder.EmbedThumbnail { Url = (SelectedPlaylist.PlaylistThumbnail.IsNullOrWhiteSpace() ? "" : SelectedPlaylist.PlaylistThumbnail) };
_ = await this.RespondOrEdit(new DiscordMessageBuilder().AddEmbed(embed)
.AddComponents(new List<DiscordComponent> { PreviousPage, NextPage })
.AddComponents(new List<DiscordComponent> { AddSong, RemoveSong, RemoveDuplicates })
.AddComponents(new List<DiscordComponent> { PlaylistName, ChangePlaylistColor, ChangePlaylistThumbnail })
.AddComponents(MessageComponents.GetCancelButton(ctx.DbUser, ctx.Bot)));
return;
}
await UpdateMessage();
CancellationTokenSource tokenSource = new();
_ = Task.Delay(120000, tokenSource.Token).ContinueWith(x =>
{
if (x.IsCompletedSuccessfully)
{
ctx.Client.ComponentInteractionCreated -= RunInteraction;
this.ModifyToTimedOut();
}
});
ctx.Client.ComponentInteractionCreated += RunInteraction;
async Task RunInteraction(DiscordClient s, ComponentInteractionCreateEventArgs e)
{
_ = Task.Run(async () =>
{
if (e.Message?.Id == ctx.ResponseMessage.Id && e.User.Id == ctx.User.Id)
{
tokenSource.Cancel();
tokenSource = new();
_ = Task.Delay(120000, tokenSource.Token).ContinueWith(x =>
{
if (x.IsCompletedSuccessfully)
{
ctx.Client.ComponentInteractionCreated -= RunInteraction;
this.ModifyToTimedOut();
}
});
switch (e.GetCustomId())
{
case "AddSong":
{
if (SelectedPlaylist.List.Length >= 250)
{
embed.Description = this.GetString(CommandKey.Playlists.Modify.TrackLimit, true);
_ = embed.AsError(ctx, this.GetString(CommandKey.Playlists.Title));
_ = await this.RespondOrEdit(embed.Build());
_ = Task.Delay(5000).ContinueWith(async x =>
{
await UpdateMessage();
});
return;
}
var modal = new DiscordInteractionModalBuilder(this.GetString(CommandKey.Playlists.Modify.AddSong), Guid.NewGuid().ToString())
.AddTextComponent(new DiscordTextComponent(TextComponentStyle.Small, "query", this.GetString(CommandKey.Playlists.CreatePlaylist.SupportedAddType), "", 1, 100, true));
var ModalResult = await this.PromptModalWithRetry(e.Interaction, modal, false);
if (ModalResult.TimedOut)
{
this.ModifyToTimedOut(true);
return;
}
else if (ModalResult.Cancelled)
{
await UpdateMessage();
break;
}
else if (ModalResult.Errored)
{
throw ModalResult.Exception;
}
var (Tracks, oriResult, Continue) = await MusicModuleAbstractions.GetLoadResult(ctx, ModalResult.Result.Interaction.GetModalValueByCustomId("query"));
if (!Continue)
{
await UpdateMessage();
break;
}
if (SelectedPlaylist.List.Length >= 250)
{
embed.Description = this.GetString(CommandKey.Playlists.Modify.TrackLimit, true);
_ = embed.AsError(ctx, this.GetString(CommandKey.Playlists.Title));
_ = await this.RespondOrEdit(embed.Build());
_ = Task.Delay(5000).ContinueWith(async x =>
{
await UpdateMessage();
});
return;
}
SelectedPlaylist.List = SelectedPlaylist.List.AddRange(Tracks.Take(250 - SelectedPlaylist.List.Length).Select(x => new PlaylistEntry { Title = x.Info.Title, Url = x.Info.Uri.ToString(), Length = x.Info.Length }));
await UpdateMessage();
break;
}
case "ChangeThumbnail":
{
_ = e.Interaction.CreateResponseAsync(InteractionResponseType.DeferredMessageUpdate);
try
{
embed = new DiscordEmbedBuilder
{
Description = $"{this.GetString(CommandKey.Playlists.Modify.UploadThumbnail, true, new TVar("Command", $"{ctx.Prefix}upload"))}\n\n" +
$"⚠ {this.GetString(CommandKey.Playlists.ThumbnailModerationNote, true)}",
}.AsAwaitingInput(ctx, this.GetString(CommandKey.Playlists.Title));
_ = await this.RespondOrEdit(new DiscordMessageBuilder().AddEmbed(embed));
(Stream stream, int fileSize) stream;
try
{
stream = await this.PromptForFileUpload();
}
catch (AlreadyAppliedException)
{
return;
}
catch (ArgumentException)
{
this.ModifyToTimedOut();
return;
}
embed.Description = this.GetString(CommandKey.Playlists.Modify.ImportingThumbnail, true);
_ = embed.AsLoading(ctx, this.GetString(CommandKey.Playlists.Title));
_ = await this.RespondOrEdit(embed.Build());
if (stream.fileSize > ctx.Bot.status.SafeReadOnlyConfig.Discord.MaxUploadSize)
{
embed.Description = this.GetString(CommandKey.Playlists.Modify.ThumbnailSizeError, true, new TVar("Size", ctx.Bot.status.SafeReadOnlyConfig.Discord.MaxUploadSize.FileSizeToHumanReadable()));
_ = embed.AsError(ctx, this.GetString(CommandKey.Playlists.Title));
_ = await this.RespondOrEdit(embed.Build());
_ = Task.Delay(5000).ContinueWith(async x =>
{
await UpdateMessage();
});
return;
}
var asset = await (await ctx.Client.GetChannelAsync(ctx.Bot.status.SafeReadOnlyConfig.Channels.PlaylistAssets)).SendMessageAsync(new DiscordMessageBuilder().WithContent($"{ctx.User.Mention} `{ctx.User.GetUsernameWithIdentifier()} ({ctx.User.Id})`\n`{SelectedPlaylist.PlaylistName}`").WithFile($"{Guid.NewGuid()}.png", stream.stream));
SelectedPlaylist.PlaylistThumbnail = asset.Attachments[0].Url;
}
catch (Exception ex)
{
MusicPlugin.Plugin._logger.LogError(ex, "An exception occurred while trying to import thumbnail");
embed.Description = this.GetString(CommandKey.Playlists.Modify.ThumbnailError, true);
_ = embed.AsError(ctx, this.GetString(CommandKey.Playlists.Title));
_ = await this.RespondOrEdit(embed.Build());
_ = Task.Delay(5000).ContinueWith(async x =>
{
await UpdateMessage();
});
return;
}
await UpdateMessage();
break;
}
case "ChangeColor":
{
var modal = new DiscordInteractionModalBuilder(this.GetString(CommandKey.Playlists.Modify.NewPlaylistColor), Guid.NewGuid().ToString())
.AddTextComponent(new DiscordTextComponent(TextComponentStyle.Small, "color", this.GetString(CommandKey.Playlists.Modify.NewPlaylistColor), "#FF0000", 1, 100, true, SelectedPlaylist.PlaylistColor));
var ModalResult = await this.PromptModalWithRetry(e.Interaction, modal, new DiscordEmbedBuilder
{
Description = this.GetString(CommandKey.Playlists.Modify.NewPlaylistColorPrompt, true,
new TVar("Hex", "#FF0000"),
new TVar("HelpUrl", $"` [`{this.GetString(CommandKey.Playlists.Modify.HexHelp)}`](https://g.co/kgs/jDHPp6)")),
}.AsAwaitingInput(ctx, this.GetString(CommandKey.Playlists.Title)), false);
if (ModalResult.TimedOut)
{
this.ModifyToTimedOut(true);
return;
}
else if (ModalResult.Cancelled)
{
await UpdateMessage();
break;
}
else if (ModalResult.Errored)
{
throw ModalResult.Exception;
}
SelectedPlaylist.PlaylistColor = ModalResult.Result.Interaction.GetModalValueByCustomId("color");
await UpdateMessage();
break;
}
case "ChangePlaylistName":
{
var modal = new DiscordInteractionModalBuilder(this.GetString(CommandKey.Playlists.CreatePlaylist.SetPlaylistName), Guid.NewGuid().ToString())
.AddTextComponent(new DiscordTextComponent(TextComponentStyle.Small, "name", this.GetString(CommandKey.Playlists.CreatePlaylist.PlaylistName), "Playlist", 1, 100, true, SelectedPlaylist.PlaylistName));
var ModalResult = await this.PromptModalWithRetry(e.Interaction, modal, new DiscordEmbedBuilder
{
Description = $"⚠ {this.GetString(CommandKey.Playlists.NameModerationNote, true)}",
}.AsAwaitingInput(ctx, this.GetString(CommandKey.Playlists.Title)), false);
if (ModalResult.TimedOut)
{
this.ModifyToTimedOut(true);
return;
}
else if (ModalResult.Cancelled)
{
await UpdateMessage();
break;
}
else if (ModalResult.Errored)
{
throw ModalResult.Exception;
}
SelectedPlaylist.PlaylistName = ModalResult.Result.Interaction.GetModalValueByCustomId("name");
await UpdateMessage();
break;
}
case "RemoveDuplicates":
{
_ = e.Interaction.CreateResponseAsync(InteractionResponseType.DeferredMessageUpdate);
CurrentPage = 0;
SelectedPlaylist.List = SelectedPlaylist.List.GroupBy(x => x.Url).Select(y => y.FirstOrDefault()).ToArray();
await UpdateMessage();
break;
}
case "DeleteSong":
{
_ = e.Interaction.CreateResponseAsync(InteractionResponseType.DeferredMessageUpdate);
var TrackList = SelectedPlaylist.List.Skip(CurrentPage * 10).Take(10).Select(x => new DiscordStringSelectComponentOption($"{x.Title}", x.Url.MakeValidFileName(), $"Added {x.AddedTime.GetTimespanSince().GetHumanReadable()} ago")).ToList();
DiscordStringSelectComponent Tracks = new(this.GetString(CommandKey.Playlists.Modify.DeleteNote), TrackList, Guid.NewGuid().ToString(), 1, TrackList.Count);
_ = await this.RespondOrEdit(new DiscordMessageBuilder().AddEmbed(embed).AddComponents(Tracks));
var Response = await s.GetInteractivity().WaitForSelectAsync(ctx.ResponseMessage, x => x.User.Id == ctx.User.Id, ComponentType.StringSelect);
if (Response.TimedOut)
{
this.ModifyToTimedOut();
return;
}
_ = Response.Result.Interaction.CreateResponseAsync(InteractionResponseType.DeferredMessageUpdate);
foreach (var b in Response.Result.Values.Select(x => SelectedPlaylist.List.First(y => y.Url.MakeValidFileName() == x)))
{
SelectedPlaylist.List = SelectedPlaylist.List.Remove(x => x.Url, b);
}
if (SelectedPlaylist.List.Length <= 0)
{
_ = await this.RespondOrEdit(new DiscordMessageBuilder().AddEmbed(new DiscordEmbedBuilder
{
Description = this.GetString(CommandKey.Playlists.Delete.Deleted, true, new TVar("Name", SelectedPlaylist.PlaylistName)),
}.AsSuccess(ctx, this.GetString(CommandKey.Playlists.Title))));
MusicPlugin.Plugin.Users[ctx.User.Id].Playlists = MusicPlugin.Plugin.Users[ctx.User.Id].Playlists.Remove(x => x.PlaylistId, SelectedPlaylist);
await Task.Delay(5000);
return;
}
if (!SelectedPlaylist.List.Skip(CurrentPage * 10).Take(10).Any())
CurrentPage--;
await UpdateMessage();
break;
}
case "NextPage":
{
_ = e.Interaction.CreateResponseAsync(InteractionResponseType.DeferredMessageUpdate);
CurrentPage++;
await UpdateMessage();
break;
}
case "PreviousPage":
{
_ = e.Interaction.CreateResponseAsync(InteractionResponseType.DeferredMessageUpdate);
CurrentPage--;
await UpdateMessage();
break;
}
case "cancel":
{
_ = e.Interaction.CreateResponseAsync(InteractionResponseType.DeferredMessageUpdate);
ctx.Client.ComponentInteractionCreated -= RunInteraction;
if (!ctx.Transferred)
this.DeleteOrInvalidate();
else
_ = new ManageCommand().TransferCommand(ctx, null);
return;
}
}
}
}).Add(ctx.Bot, ctx);
}
return;
});
}
}