500 lines
17 KiB
C#
500 lines
17 KiB
C#
|
using DSharpPlus.Entities;
|
|||
|
using System;
|
|||
|
using System.Collections.Generic;
|
|||
|
using System.Text;
|
|||
|
using System.Threading.Tasks;
|
|||
|
using System.Linq;
|
|||
|
using Microsoft.Extensions.Logging;
|
|||
|
using TomatenMusic.Prompt.Option;
|
|||
|
using TomatenMusic.Util;
|
|||
|
using DSharpPlus.Exceptions;
|
|||
|
using Microsoft.Extensions.DependencyInjection;
|
|||
|
using DSharpPlus;
|
|||
|
|
|||
|
namespace TomatenMusic.Prompt.Model
|
|||
|
{
|
|||
|
abstract class DiscordPromptBase
|
|||
|
{
|
|||
|
public static List<DiscordPromptBase> ActivePrompts { get; } = new List<DiscordPromptBase>();
|
|||
|
|
|||
|
public PromptState State { get; protected set; }
|
|||
|
public DiscordMessage Message { get; private set; }
|
|||
|
public DiscordInteraction Interaction { get; private set; }
|
|||
|
public List<IPromptOption> Options { get; protected set; } = new List<IPromptOption>();
|
|||
|
public DiscordClient _client { get; set; }
|
|||
|
public DiscordPromptBase LastPrompt { get; protected set; }
|
|||
|
public System.Timers.Timer TimeoutTimer { get; set; }
|
|||
|
|
|||
|
protected ILogger<DiscordPromptBase> _logger { get; set; }
|
|||
|
|
|||
|
protected EventId eventId = new EventId(16, "Prompts");
|
|||
|
|
|||
|
protected DiscordPromptBase(DiscordPromptBase lastPrompt)
|
|||
|
{
|
|||
|
LastPrompt = lastPrompt;
|
|||
|
Options = new List<IPromptOption>();
|
|||
|
IServiceProvider serviceProvider = TomatenMusicBot.ServiceProvider;
|
|||
|
|
|||
|
_logger = serviceProvider.GetRequiredService<ILogger<DiscordPromptBase>>();
|
|||
|
|
|||
|
|
|||
|
if (lastPrompt != null)
|
|||
|
{
|
|||
|
Options.Add(new ButtonPromptOption
|
|||
|
{
|
|||
|
Style = DSharpPlus.ButtonStyle.Danger,
|
|||
|
Row = 5,
|
|||
|
Emoji = new DiscordComponentEmoji("↩️"),
|
|||
|
Run = async (args, sender, option) =>
|
|||
|
{
|
|||
|
_ = BackAsync();
|
|||
|
}
|
|||
|
});
|
|||
|
}
|
|||
|
|
|||
|
Options.Add(new ButtonPromptOption
|
|||
|
{
|
|||
|
Style = DSharpPlus.ButtonStyle.Danger,
|
|||
|
Row = 5,
|
|||
|
Emoji = new DiscordComponentEmoji("❌"),
|
|||
|
Run = async (args, sender, option) =>
|
|||
|
{
|
|||
|
_ = InvalidateAsync();
|
|||
|
}
|
|||
|
});
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
public async Task InvalidateAsync(bool withEdit = true, bool destroyHistory = false)
|
|||
|
{
|
|||
|
foreach (var option in Options)
|
|||
|
option.UpdateMethod = (prompt) =>
|
|||
|
{
|
|||
|
prompt.Disabled = true;
|
|||
|
return Task.FromResult<IPromptOption>(prompt);
|
|||
|
};
|
|||
|
|
|||
|
if (withEdit)
|
|||
|
await EditMessageAsync(new DiscordWebhookBuilder().WithContent("This Prompt is invalid!"));
|
|||
|
ActivePrompts.Remove(this);
|
|||
|
if (destroyHistory)
|
|||
|
{
|
|||
|
if (LastPrompt != null)
|
|||
|
await LastPrompt.InvalidateAsync(false);
|
|||
|
await EditMessageAsync(new DiscordWebhookBuilder().WithContent("This Prompt is invalid!"));
|
|||
|
}
|
|||
|
|
|||
|
if (State == PromptState.INVALID)
|
|||
|
return;
|
|||
|
State = PromptState.INVALID;
|
|||
|
|
|||
|
|
|||
|
_client.ComponentInteractionCreated -= Discord_ComponentInteractionCreated;
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
public async Task SendAsync(DiscordChannel channel)
|
|||
|
{
|
|||
|
if (State == PromptState.INVALID)
|
|||
|
return;
|
|||
|
|
|||
|
IServiceProvider serviceProvider = TomatenMusicBot.ServiceProvider;
|
|||
|
var client = serviceProvider.GetRequiredService<DiscordShardedClient>();
|
|||
|
_client = client.GetShard( (ulong) channel.GuildId);
|
|||
|
|
|||
|
_client.ComponentInteractionCreated += Discord_ComponentInteractionCreated;
|
|||
|
ActivePrompts.Add(this);
|
|||
|
AddGuids();
|
|||
|
DiscordMessageBuilder builder = await GetMessageAsync();
|
|||
|
builder = await AddComponentsAsync(builder);
|
|||
|
|
|||
|
|
|||
|
Message = await builder.SendAsync(channel);
|
|||
|
State = PromptState.OPEN;
|
|||
|
}
|
|||
|
|
|||
|
public async Task SendAsync(DiscordInteraction interaction, bool ephemeral = false)
|
|||
|
{
|
|||
|
if (State == PromptState.INVALID)
|
|||
|
return;
|
|||
|
|
|||
|
IServiceProvider serviceProvider = TomatenMusicBot.ServiceProvider;
|
|||
|
var client = serviceProvider.GetRequiredService<DiscordShardedClient>();
|
|||
|
_client = client.GetShard((ulong)interaction.GuildId);
|
|||
|
|
|||
|
_client.ComponentInteractionCreated += Discord_ComponentInteractionCreated;
|
|||
|
ActivePrompts.Add(this);
|
|||
|
|
|||
|
AddGuids();
|
|||
|
DiscordFollowupMessageBuilder builder = await GetFollowupMessageAsync();
|
|||
|
builder = await AddComponentsAsync(builder);
|
|||
|
builder.AsEphemeral(ephemeral);
|
|||
|
|
|||
|
Interaction = interaction;
|
|||
|
Message = await interaction.CreateFollowupMessageAsync(builder);
|
|||
|
State = PromptState.OPEN;
|
|||
|
|
|||
|
long timeoutTime = (Interaction.CreationTimestamp.ToUnixTimeMilliseconds() + 900000) - DateTimeOffset.Now.ToUnixTimeMilliseconds();
|
|||
|
|
|||
|
if (TimeoutTimer != null)
|
|||
|
TimeoutTimer.Close();
|
|||
|
|
|||
|
TimeoutTimer = new System.Timers.Timer(timeoutTime);
|
|||
|
TimeoutTimer.Elapsed += OnTimeout;
|
|||
|
TimeoutTimer.AutoReset = false;
|
|||
|
TimeoutTimer.Start();
|
|||
|
}
|
|||
|
|
|||
|
public async Task UseAsync(DiscordMessage message)
|
|||
|
{
|
|||
|
if (State == PromptState.INVALID)
|
|||
|
return;
|
|||
|
|
|||
|
IServiceProvider serviceProvider = TomatenMusicBot.ServiceProvider;
|
|||
|
var client = serviceProvider.GetRequiredService<DiscordShardedClient>();
|
|||
|
_client = client.GetShard((ulong)message.Channel.GuildId);
|
|||
|
|
|||
|
_client.ComponentInteractionCreated += Discord_ComponentInteractionCreated;
|
|||
|
ActivePrompts.Add(this);
|
|||
|
|
|||
|
AddGuids();
|
|||
|
DiscordWebhookBuilder builder = await GetWebhookMessageAsync();
|
|||
|
|
|||
|
await EditMessageAsync(builder);
|
|||
|
State = PromptState.OPEN;
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
public async Task UseAsync(DiscordInteraction interaction, DiscordMessage message)
|
|||
|
{
|
|||
|
if (State == PromptState.INVALID)
|
|||
|
return;
|
|||
|
|
|||
|
IServiceProvider serviceProvider = TomatenMusicBot.ServiceProvider;
|
|||
|
var client = serviceProvider.GetRequiredService<DiscordShardedClient>();
|
|||
|
_client = client.GetShard((ulong)interaction.GuildId);
|
|||
|
|
|||
|
_client.ComponentInteractionCreated += Discord_ComponentInteractionCreated;
|
|||
|
|
|||
|
ActivePrompts.Add(this);
|
|||
|
AddGuids();
|
|||
|
DiscordWebhookBuilder builder = await GetWebhookMessageAsync();
|
|||
|
Interaction = interaction;
|
|||
|
Message = message;
|
|||
|
await EditMessageAsync(builder);
|
|||
|
State = PromptState.OPEN;
|
|||
|
|
|||
|
long timeoutTime = (Interaction.CreationTimestamp.ToUnixTimeMilliseconds() + 900000) - DateTimeOffset.Now.ToUnixTimeMilliseconds();
|
|||
|
|
|||
|
if (TimeoutTimer != null)
|
|||
|
TimeoutTimer.Close();
|
|||
|
|
|||
|
TimeoutTimer = new System.Timers.Timer(timeoutTime);
|
|||
|
TimeoutTimer.Elapsed += OnTimeout;
|
|||
|
TimeoutTimer.AutoReset = false;
|
|||
|
TimeoutTimer.Start();
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
private void OnTimeout(object? sender, System.Timers.ElapsedEventArgs e)
|
|||
|
{
|
|||
|
_ = InvalidateAsync();
|
|||
|
}
|
|||
|
|
|||
|
private void AddGuids()
|
|||
|
{
|
|||
|
foreach (var item in Options)
|
|||
|
{
|
|||
|
item.CustomID = RandomUtil.GenerateGuid();
|
|||
|
if (item is SelectMenuPromptOption)
|
|||
|
{
|
|||
|
SelectMenuPromptOption menuItem = (SelectMenuPromptOption)item;
|
|||
|
foreach (var option in menuItem.Options)
|
|||
|
{
|
|||
|
option.CustomID = RandomUtil.GenerateGuid();
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
}
|
|||
|
//this.Options = options;
|
|||
|
}
|
|||
|
|
|||
|
protected abstract Task<DiscordComponent> GetComponentAsync(IPromptOption option);
|
|||
|
|
|||
|
protected abstract Task<DiscordMessageBuilder> GetMessageAsync();
|
|||
|
|
|||
|
private async Task<DiscordFollowupMessageBuilder> GetFollowupMessageAsync()
|
|||
|
{
|
|||
|
DiscordMessageBuilder oldBuilder = await GetMessageAsync();
|
|||
|
|
|||
|
return new DiscordFollowupMessageBuilder()
|
|||
|
.WithContent(oldBuilder.Content)
|
|||
|
.AddEmbeds(oldBuilder.Embeds);
|
|||
|
|
|||
|
}
|
|||
|
private async Task<DiscordWebhookBuilder> GetWebhookMessageAsync()
|
|||
|
{
|
|||
|
DiscordMessageBuilder oldBuilder = await GetMessageAsync();
|
|||
|
|
|||
|
return new DiscordWebhookBuilder()
|
|||
|
.WithContent(oldBuilder.Content)
|
|||
|
.AddEmbeds(oldBuilder.Embeds);
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
public async Task UpdateAsync()
|
|||
|
{
|
|||
|
if (State == PromptState.INVALID)
|
|||
|
return;
|
|||
|
await EditMessageAsync(await GetWebhookMessageAsync());
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
private async Task UpdateOptionsAsync()
|
|||
|
{
|
|||
|
List<IPromptOption> options = new List<IPromptOption>();
|
|||
|
foreach (var option in this.Options)
|
|||
|
options.Add(await option.UpdateMethod.Invoke(option));
|
|||
|
this.Options = options;
|
|||
|
}
|
|||
|
|
|||
|
protected async Task Discord_ComponentInteractionCreated(DSharpPlus.DiscordClient sender, DSharpPlus.EventArgs.ComponentInteractionCreateEventArgs e)
|
|||
|
{
|
|||
|
if (State == PromptState.INVALID)
|
|||
|
return;
|
|||
|
|
|||
|
foreach (var option in Options)
|
|||
|
{
|
|||
|
if (option.CustomID == e.Id)
|
|||
|
{
|
|||
|
|
|||
|
await e.Interaction.CreateResponseAsync(DSharpPlus.InteractionResponseType.DeferredMessageUpdate);
|
|||
|
_ = option.Run.Invoke(e, sender, option);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
public async Task EditMessageAsync(DiscordWebhookBuilder builder)
|
|||
|
{
|
|||
|
try
|
|||
|
{
|
|||
|
if (Interaction != null)
|
|||
|
{
|
|||
|
await AddComponentsAsync(builder);
|
|||
|
try
|
|||
|
{
|
|||
|
Message = await Interaction.EditFollowupMessageAsync(Message.Id, builder);
|
|||
|
}catch (Exception e)
|
|||
|
{
|
|||
|
Message = await Interaction.EditOriginalResponseAsync(builder);
|
|||
|
}
|
|||
|
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
DiscordMessageBuilder msgbuilder = new DiscordMessageBuilder()
|
|||
|
.AddEmbeds(builder.Embeds)
|
|||
|
.WithContent(builder.Content);
|
|||
|
await AddComponentsAsync(msgbuilder);
|
|||
|
Message = await Message.ModifyAsync(msgbuilder);
|
|||
|
}
|
|||
|
}catch (BadRequestException e)
|
|||
|
{
|
|||
|
_logger.LogError(e.Errors);
|
|||
|
}
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
protected async Task<DiscordMessageBuilder> AddComponentsAsync(DiscordMessageBuilder builder)
|
|||
|
{
|
|||
|
await UpdateOptionsAsync();
|
|||
|
builder.ClearComponents();
|
|||
|
|
|||
|
List<DiscordComponent> row1 = new List<DiscordComponent>(5);
|
|||
|
List<DiscordComponent> row2 = new List<DiscordComponent>(5);
|
|||
|
List<DiscordComponent> row3 = new List<DiscordComponent>(5);
|
|||
|
List<DiscordComponent> row4 = new List<DiscordComponent>(5);
|
|||
|
List<DiscordComponent> row5 = new List<DiscordComponent>(5);
|
|||
|
|
|||
|
foreach (var option in Options)
|
|||
|
{
|
|||
|
switch (option.Row)
|
|||
|
{
|
|||
|
case 1:
|
|||
|
row1.Add(await GetComponentAsync(option));
|
|||
|
break;
|
|||
|
case 2:
|
|||
|
row2.Add(await GetComponentAsync(option));
|
|||
|
break;
|
|||
|
case 3:
|
|||
|
row3.Add(await GetComponentAsync(option));
|
|||
|
break;
|
|||
|
case 4:
|
|||
|
row4.Add(await GetComponentAsync(option));
|
|||
|
break;
|
|||
|
case 5:
|
|||
|
row5.Add(await GetComponentAsync(option));
|
|||
|
break;
|
|||
|
default:
|
|||
|
throw new ArgumentException("Invalid Row! Must be between 1 and 5", "Row");
|
|||
|
}
|
|||
|
}
|
|||
|
if (row1.Count != 0)
|
|||
|
{
|
|||
|
builder.AddComponents(row1);
|
|||
|
}
|
|||
|
if (row2.Count != 0)
|
|||
|
{
|
|||
|
builder.AddComponents(row2);
|
|||
|
}
|
|||
|
if (row3.Count != 0)
|
|||
|
{
|
|||
|
builder.AddComponents(row3);
|
|||
|
}
|
|||
|
if (row4.Count != 0)
|
|||
|
{
|
|||
|
builder.AddComponents(row4);
|
|||
|
}
|
|||
|
if (row5.Count != 0)
|
|||
|
{
|
|||
|
builder.AddComponents(row5);
|
|||
|
}
|
|||
|
return builder;
|
|||
|
}
|
|||
|
|
|||
|
protected async Task<DiscordFollowupMessageBuilder> AddComponentsAsync(DiscordFollowupMessageBuilder builder)
|
|||
|
{
|
|||
|
await UpdateOptionsAsync();
|
|||
|
builder.ClearComponents();
|
|||
|
|
|||
|
List<DiscordComponent> row1 = new List<DiscordComponent>(5);
|
|||
|
List<DiscordComponent> row2 = new List<DiscordComponent>(5);
|
|||
|
List<DiscordComponent> row3 = new List<DiscordComponent>(5);
|
|||
|
List<DiscordComponent> row4 = new List<DiscordComponent>(5);
|
|||
|
List<DiscordComponent> row5 = new List<DiscordComponent>(5);
|
|||
|
|
|||
|
foreach (var option in Options)
|
|||
|
{
|
|||
|
switch (option.Row)
|
|||
|
{
|
|||
|
case 1:
|
|||
|
row1.Add(await GetComponentAsync(option));
|
|||
|
break;
|
|||
|
case 2:
|
|||
|
row2.Add(await GetComponentAsync(option));
|
|||
|
break;
|
|||
|
case 3:
|
|||
|
row3.Add(await GetComponentAsync(option));
|
|||
|
break;
|
|||
|
case 4:
|
|||
|
row4.Add(await GetComponentAsync(option));
|
|||
|
break;
|
|||
|
case 5:
|
|||
|
row5.Add(await GetComponentAsync(option));
|
|||
|
break;
|
|||
|
default:
|
|||
|
throw new ArgumentException("Invalid Row! Must be between 1 and 5", "Row");
|
|||
|
}
|
|||
|
}
|
|||
|
if (row1.Count != 0)
|
|||
|
{
|
|||
|
builder.AddComponents(row1);
|
|||
|
}
|
|||
|
if (row2.Count != 0)
|
|||
|
{
|
|||
|
builder.AddComponents(row2);
|
|||
|
}
|
|||
|
if (row3.Count != 0)
|
|||
|
{
|
|||
|
builder.AddComponents(row3);
|
|||
|
}
|
|||
|
if (row4.Count != 0)
|
|||
|
{
|
|||
|
builder.AddComponents(row4);
|
|||
|
}
|
|||
|
if (row5.Count != 0)
|
|||
|
{
|
|||
|
builder.AddComponents(row5);
|
|||
|
}
|
|||
|
return builder;
|
|||
|
}
|
|||
|
|
|||
|
protected async Task<DiscordWebhookBuilder> AddComponentsAsync(DiscordWebhookBuilder builder)
|
|||
|
{
|
|||
|
await UpdateOptionsAsync();
|
|||
|
builder.ClearComponents();
|
|||
|
|
|||
|
List<DiscordComponent> row1 = new List<DiscordComponent>(5);
|
|||
|
List<DiscordComponent> row2 = new List<DiscordComponent>(5);
|
|||
|
List<DiscordComponent> row3 = new List<DiscordComponent>(5);
|
|||
|
List<DiscordComponent> row4 = new List<DiscordComponent>(5);
|
|||
|
List<DiscordComponent> row5 = new List<DiscordComponent>(5);
|
|||
|
|
|||
|
foreach (var option in Options)
|
|||
|
{
|
|||
|
switch (option.Row)
|
|||
|
{
|
|||
|
case 1:
|
|||
|
row1.Add(await GetComponentAsync(option));
|
|||
|
break;
|
|||
|
case 2:
|
|||
|
row2.Add(await GetComponentAsync(option));
|
|||
|
break;
|
|||
|
case 3:
|
|||
|
row3.Add(await GetComponentAsync(option));
|
|||
|
break;
|
|||
|
case 4:
|
|||
|
row4.Add(await GetComponentAsync(option));
|
|||
|
break;
|
|||
|
case 5:
|
|||
|
row5.Add(await GetComponentAsync(option));
|
|||
|
break;
|
|||
|
default:
|
|||
|
throw new ArgumentException("Invalid Row! Must be between 1 and 5", "Row");
|
|||
|
}
|
|||
|
}
|
|||
|
if (row1.Count != 0)
|
|||
|
{
|
|||
|
builder.AddComponents(row1);
|
|||
|
}
|
|||
|
if (row2.Count != 0)
|
|||
|
{
|
|||
|
builder.AddComponents(row2);
|
|||
|
}
|
|||
|
if (row3.Count != 0)
|
|||
|
{
|
|||
|
builder.AddComponents(row3);
|
|||
|
}
|
|||
|
if (row4.Count != 0)
|
|||
|
{
|
|||
|
builder.AddComponents(row4);
|
|||
|
}
|
|||
|
if (row5.Count != 0)
|
|||
|
{
|
|||
|
builder.AddComponents(row5);
|
|||
|
}
|
|||
|
return builder;
|
|||
|
}
|
|||
|
|
|||
|
public async Task BackAsync()
|
|||
|
{
|
|||
|
|
|||
|
if (LastPrompt == null)
|
|||
|
return;
|
|||
|
_client.ComponentInteractionCreated -= LastPrompt.Discord_ComponentInteractionCreated;
|
|||
|
|
|||
|
await InvalidateAsync(false);
|
|||
|
if (Interaction == null)
|
|||
|
await LastPrompt.UseAsync(Message);
|
|||
|
else
|
|||
|
await LastPrompt.UseAsync(Interaction, Message);
|
|||
|
}
|
|||
|
|
|||
|
public void AddOption(IPromptOption option)
|
|||
|
{
|
|||
|
Options.Add(option);
|
|||
|
}
|
|||
|
|
|||
|
}
|
|||
|
}
|