add: Added .quteedit command

add: Added an edit button to .quoteshow and .exprshow commands that opens a modal which lets you edit the quote or expr in question
This commit is contained in:
Kwoth
2024-05-21 00:33:40 +00:00
parent b51ce34190
commit 06321380ee
17 changed files with 313 additions and 78 deletions

View File

@@ -1,5 +1,7 @@
#nullable disable #nullable disable
using NadekoBot.Db.Models;
namespace NadekoBot.Modules.NadekoExpressions; namespace NadekoBot.Modules.NadekoExpressions;
[Name("Expressions")] [Name("Expressions")]
@@ -34,12 +36,12 @@ public partial class NadekoExpressions : NadekoModule<NadekoExpressionsService>
await Response() await Response()
.Embed(_sender.CreateEmbed() .Embed(_sender.CreateEmbed()
.WithOkColor() .WithOkColor()
.WithTitle(GetText(strs.expr_new)) .WithTitle(GetText(strs.expr_new))
.WithDescription($"#{new kwum(ex.Id)}") .WithDescription($"#{new kwum(ex.Id)}")
.AddField(GetText(strs.trigger), key) .AddField(GetText(strs.trigger), key)
.AddField(GetText(strs.response), .AddField(GetText(strs.response),
message.Length > 1024 ? GetText(strs.redacted_too_long) : message)) message.Length > 1024 ? GetText(strs.redacted_too_long) : message))
.SendAsync(); .SendAsync();
} }
@@ -93,8 +95,7 @@ public partial class NadekoExpressions : NadekoModule<NadekoExpressionsService>
return; return;
} }
if ((channel is null && !_creds.IsOwner(ctx.User)) if (!IsValidExprEditor())
|| (channel is not null && !((IGuildUser)ctx.User).GuildPermissions.Administrator))
{ {
await Response().Error(strs.expr_insuff_perms).SendAsync(); await Response().Error(strs.expr_insuff_perms).SendAsync();
return; return;
@@ -105,12 +106,12 @@ public partial class NadekoExpressions : NadekoModule<NadekoExpressionsService>
{ {
await Response() await Response()
.Embed(_sender.CreateEmbed() .Embed(_sender.CreateEmbed()
.WithOkColor() .WithOkColor()
.WithTitle(GetText(strs.expr_edited)) .WithTitle(GetText(strs.expr_edited))
.WithDescription($"#{id}") .WithDescription($"#{id}")
.AddField(GetText(strs.trigger), ex.Trigger) .AddField(GetText(strs.trigger), ex.Trigger)
.AddField(GetText(strs.response), .AddField(GetText(strs.response),
message.Length > 1024 ? GetText(strs.redacted_too_long) : message)) message.Length > 1024 ? GetText(strs.redacted_too_long) : message))
.SendAsync(); .SendAsync();
} }
else else
@@ -119,6 +120,10 @@ public partial class NadekoExpressions : NadekoModule<NadekoExpressionsService>
} }
} }
private bool IsValidExprEditor()
=> (ctx.Guild is not null && ((IGuildUser)ctx.User).GuildPermissions.Administrator)
|| (ctx.Guild is null && _creds.IsOwner(ctx.User));
[Cmd] [Cmd]
[Priority(1)] [Priority(1)]
public async Task ExprList(int page = 1) public async Task ExprList(int page = 1)
@@ -132,7 +137,7 @@ public partial class NadekoExpressions : NadekoModule<NadekoExpressionsService>
.OrderBy(x => x.Trigger) .OrderBy(x => x.Trigger)
.ToArray(); .ToArray();
if (allExpressions is null || !allExpressions.Any()) if (!allExpressions.Any())
{ {
await Response().Error(strs.expr_no_found).SendAsync(); await Response().Error(strs.expr_no_found).SendAsync();
return; return;
@@ -171,16 +176,48 @@ public partial class NadekoExpressions : NadekoModule<NadekoExpressionsService>
return; return;
} }
var inter = CreateEditInteraction(id, found);
await Response() await Response()
.Interaction(IsValidExprEditor() ? inter : null)
.Embed(_sender.CreateEmbed() .Embed(_sender.CreateEmbed()
.WithOkColor() .WithOkColor()
.WithDescription($"#{id}") .WithDescription($"#{id}")
.AddField(GetText(strs.trigger), found.Trigger.TrimTo(1024)) .AddField(GetText(strs.trigger), found.Trigger.TrimTo(1024))
.AddField(GetText(strs.response), .AddField(GetText(strs.response),
found.Response.TrimTo(1000).Replace("](", "]\\("))) found.Response.TrimTo(1000).Replace("](", "]\\(")))
.SendAsync(); .SendAsync();
} }
private NadekoInteractionBase CreateEditInteraction(kwum id, NadekoExpression found)
{
var modal = new ModalBuilder()
.WithCustomId("expr:edit_modal")
.WithTitle($"Edit expression {id}")
.AddTextInput(new TextInputBuilder()
.WithLabel(GetText(strs.response))
.WithValue(found.Response)
.WithMinLength(1)
.WithCustomId("expr:edit_modal:response")
.WithStyle(TextInputStyle.Paragraph));
var inter = _inter.Create(ctx.User.Id,
new ButtonBuilder()
.WithEmote(Emoji.Parse("📝"))
.WithLabel("Edit")
.WithStyle(ButtonStyle.Primary)
.WithCustomId("test"),
modal,
async (sm) =>
{
var msg = sm.Data.Components.FirstOrDefault()?.Value;
await ExprEdit(id, msg);
}
);
return inter;
}
public async Task ExprDeleteInternalAsync(kwum id) public async Task ExprDeleteInternalAsync(kwum id)
{ {
var ex = await _service.DeleteAsync(ctx.Guild?.Id, id); var ex = await _service.DeleteAsync(ctx.Guild?.Id, id);
@@ -189,11 +226,11 @@ public partial class NadekoExpressions : NadekoModule<NadekoExpressionsService>
{ {
await Response() await Response()
.Embed(_sender.CreateEmbed() .Embed(_sender.CreateEmbed()
.WithOkColor() .WithOkColor()
.WithTitle(GetText(strs.expr_deleted)) .WithTitle(GetText(strs.expr_deleted))
.WithDescription($"#{id}") .WithDescription($"#{id}")
.AddField(GetText(strs.trigger), ex.Trigger.TrimTo(1024)) .AddField(GetText(strs.trigger), ex.Trigger.TrimTo(1024))
.AddField(GetText(strs.response), ex.Response.TrimTo(1024))) .AddField(GetText(strs.response), ex.Response.TrimTo(1024)))
.SendAsync(); .SendAsync();
} }
else else
@@ -340,8 +377,8 @@ public partial class NadekoExpressions : NadekoModule<NadekoExpressionsService>
public async Task ExprClear() public async Task ExprClear()
{ {
if (await PromptUserConfirmAsync(_sender.CreateEmbed() if (await PromptUserConfirmAsync(_sender.CreateEmbed()
.WithTitle("Expression clear") .WithTitle("Expression clear")
.WithDescription("This will delete all expressions on this server."))) .WithDescription("This will delete all expressions on this server.")))
{ {
var count = _service.DeleteAllExpressions(ctx.Guild.Id); var count = _service.DeleteAllExpressions(ctx.Guild.Id);
await Response().Confirm(strs.exprs_cleared(count)).SendAsync(); await Response().Confirm(strs.exprs_cleared(count)).SendAsync();

View File

@@ -154,7 +154,7 @@ public partial class Gambling : GamblingModule<GamblingService>
await smc.RespondConfirmAsync(_sender, GetText(strs.remind_timely(tt)), ephemeral: true); await smc.RespondConfirmAsync(_sender, GetText(strs.remind_timely(tt)), ephemeral: true);
} }
private NadekoInteraction CreateRemindMeInteraction(int period) private NadekoInteractionBase CreateRemindMeInteraction(int period)
=> _inter => _inter
.Create(ctx.User.Id, .Create(ctx.User.Id,
new ButtonBuilder( new ButtonBuilder(
@@ -415,7 +415,7 @@ public partial class Gambling : GamblingModule<GamblingService>
.Pipe(text => smc.RespondConfirmAsync(_sender, text, ephemeral: true)); .Pipe(text => smc.RespondConfirmAsync(_sender, text, ephemeral: true));
} }
private NadekoInteraction CreateCashInteraction() private NadekoInteractionBase CreateCashInteraction()
=> _inter.Create(ctx.User.Id, => _inter.Create(ctx.User.Id,
new ButtonBuilder( new ButtonBuilder(
customId: "cash:bank_show_balance", customId: "cash:bank_show_balance",

View File

@@ -1,4 +1,6 @@
#nullable disable warnings #nullable disable warnings
using LinqToDB;
using LinqToDB.EntityFrameworkCore;
using NadekoBot.Common.Yml; using NadekoBot.Common.Yml;
using NadekoBot.Db; using NadekoBot.Db;
using NadekoBot.Db.Models; using NadekoBot.Db.Models;
@@ -133,23 +135,54 @@ public partial class Utility
await ShowQuoteData(quote); await ShowQuoteData(quote);
} }
private async Task ShowQuoteData(Quote data) private NadekoInteractionBase CreateEditInteraction(kwum id, Quote found)
{ {
var modal = new ModalBuilder()
.WithCustomId("quote:edit_modal")
.WithTitle($"Edit expression {id}")
.AddTextInput(new TextInputBuilder()
.WithLabel(GetText(strs.response))
.WithValue(found.Text)
.WithMinLength(1)
.WithCustomId("quote:edit_modal:response")
.WithStyle(TextInputStyle.Paragraph));
var inter = _inter.Create(ctx.User.Id,
new ButtonBuilder()
.WithEmote(Emoji.Parse("📝"))
.WithLabel("Edit")
.WithStyle(ButtonStyle.Primary)
.WithCustomId("test"),
modal,
async (sm) =>
{
var msg = sm.Data.Components.FirstOrDefault()?.Value;
if(!string.IsNullOrWhiteSpace(msg))
await QuoteEdit(id, msg);
}
);
return inter;
}
private async Task ShowQuoteData(Quote quote)
{
var inter = CreateEditInteraction(quote.Id, quote);
var eb = _sender.CreateEmbed() var eb = _sender.CreateEmbed()
.WithOkColor() .WithOkColor()
.WithTitle($"{GetText(strs.quote_id($"`{new kwum(data.Id)}"))}`") .WithTitle($"{GetText(strs.quote_id($"`{new kwum(quote.Id)}"))}`")
.WithDescription(Format.Sanitize(data.Text).Replace("](", "]\\(").TrimTo(4096)) .WithDescription(Format.Sanitize(quote.Text).Replace("](", "]\\(").TrimTo(4096))
.AddField(GetText(strs.trigger), data.Keyword) .AddField(GetText(strs.trigger), quote.Keyword)
.WithFooter( .WithFooter(
GetText(strs.created_by($"{data.AuthorName} ({data.AuthorId})"))); GetText(strs.created_by($"{quote.AuthorName} ({quote.AuthorId})")));
if (!(data.Text.Length > 4096)) if (!(quote.Text.Length > 4096))
{ {
await Response().Embed(eb).SendAsync(); await Response().Embed(eb).Interaction(quote.AuthorId == ctx.User.Id ? inter : null).SendAsync();
return; return;
} }
await using var textStream = await data.Text.ToStream(); await using var textStream = await quote.Text.ToStream();
await Response() await Response()
.Embed(eb) .Embed(eb)
@@ -255,6 +288,47 @@ public partial class Utility
await Response().Confirm(strs.quote_added_new(Format.Code(new kwum(q.Id).ToString()))).SendAsync(); await Response().Confirm(strs.quote_added_new(Format.Code(new kwum(q.Id).ToString()))).SendAsync();
} }
[Cmd]
[RequireContext(ContextType.Guild)]
public async Task QuoteEdit(kwum quoteId, [Leftover] string text)
{
if (string.IsNullOrWhiteSpace(text))
{
return;
}
Quote q;
await using (var uow = _db.GetDbContext())
{
var intId = (int)quoteId;
var result = await uow.GetTable<Quote>()
.Where(x => x.Id == intId && x.AuthorId == ctx.User.Id)
.Set(x => x.Text, text)
.UpdateWithOutputAsync((del, ins) => ins);
q = result.FirstOrDefault();
await uow.SaveChangesAsync();
}
if (q is not null)
{
await Response()
.Embed(_sender.CreateEmbed()
.WithOkColor()
.WithTitle(GetText(strs.quote_edited))
.WithDescription($"#{quoteId}")
.AddField(GetText(strs.trigger), q.Keyword)
.AddField(GetText(strs.response),
text.Length > 1024 ? GetText(strs.redacted_too_long) : text))
.SendAsync();
}
else
{
await Response().Error(strs.expr_no_found_id).SendAsync();
}
}
[Cmd] [Cmd]
[RequireContext(ContextType.Guild)] [RequireContext(ContextType.Guild)]
public async Task QuoteDelete(kwum quoteId) public async Task QuoteDelete(kwum quoteId)

View File

@@ -506,7 +506,7 @@ public partial class Xp : NadekoModule<XpService>
{ {
var result = await _service.BuyShopItemAsync(ctx.User.Id, (XpShopItemType)type, key); var result = await _service.BuyShopItemAsync(ctx.User.Id, (XpShopItemType)type, key);
NadekoInteraction GetUseInteraction() NadekoInteractionBase GetUseInteraction()
{ {
return _inter.Create(ctx.User.Id, return _inter.Create(ctx.User.Id,
new(label: "Use", customId: "xpshop:use_item", emote: Emoji.Parse("👐")), new(label: "Use", customId: "xpshop:use_item", emote: Emoji.Parse("👐")),

View File

@@ -1,5 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net8.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
@@ -31,9 +30,9 @@
<PackageReference Include="CodeHollow.FeedReader" Version="1.2.6"/> <PackageReference Include="CodeHollow.FeedReader" Version="1.2.6"/>
<PackageReference Include="CommandLineParser" Version="2.9.1"/> <PackageReference Include="CommandLineParser" Version="2.9.1"/>
<PackageReference Include="Discord.Net" Version="3.204.0"/> <PackageReference Include="Discord.Net" Version="3.204.0"/>
<PackageReference Include="CoreCLR-NCalc" Version="3.1.246" /> <PackageReference Include="CoreCLR-NCalc" Version="3.1.246"/>
<PackageReference Include="Google.Apis.Urlshortener.v1" Version="1.41.1.138"/> <PackageReference Include="Google.Apis.Urlshortener.v1" Version="1.41.1.138"/>
<PackageReference Include="Google.Apis.YouTube.v3" Version="1.68.0.3414" /> <PackageReference Include="Google.Apis.YouTube.v3" Version="1.68.0.3414"/>
<PackageReference Include="Google.Apis.Customsearch.v1" Version="1.49.0.2084"/> <PackageReference Include="Google.Apis.Customsearch.v1" Version="1.49.0.2084"/>
<!-- <PackageReference Include="Grpc.AspNetCore" Version="2.62.0" />--> <!-- <PackageReference Include="Grpc.AspNetCore" Version="2.62.0" />-->
<PackageReference Include="Google.Protobuf" Version="3.26.1"/> <PackageReference Include="Google.Protobuf" Version="3.26.1"/>
@@ -52,11 +51,11 @@
<PackageReference Include="NetEscapades.Configuration.Yaml" Version="3.1.0"/> <PackageReference Include="NetEscapades.Configuration.Yaml" Version="3.1.0"/>
<!-- DI --> <!-- DI -->
<!-- <PackageReference Include="Ninject" Version="3.3.6"/>--> <!-- <PackageReference Include="Ninject" Version="3.3.6"/>-->
<!-- <PackageReference Include="Ninject.Extensions.Conventions" Version="3.3.0"/>--> <!-- <PackageReference Include="Ninject.Extensions.Conventions" Version="3.3.0"/>-->
<!-- <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />--> <!-- <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />-->
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.1"/> <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.1"/>
<PackageReference Include="DryIoc.dll" Version="5.4.3" /> <PackageReference Include="DryIoc.dll" Version="5.4.3"/>
<!-- <PackageReference Include="Scrutor" Version="4.2.0" />--> <!-- <PackageReference Include="Scrutor" Version="4.2.0" />-->
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0"/> <PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0"/>
@@ -68,14 +67,14 @@
<PackageReference Include="OneOf" Version="3.0.263"/> <PackageReference Include="OneOf" Version="3.0.263"/>
<PackageReference Include="OneOf.SourceGenerator" Version="3.0.263"/> <PackageReference Include="OneOf.SourceGenerator" Version="3.0.263"/>
<PackageReference Include="Serilog.Sinks.Console" Version="5.0.1"/> <PackageReference Include="Serilog.Sinks.Console" Version="5.0.1"/>
<PackageReference Include="Serilog.Sinks.Seq" Version="7.0.1" /> <PackageReference Include="Serilog.Sinks.Seq" Version="7.0.1"/>
<PackageReference Include="SixLabors.Fonts" Version="1.0.0-beta17"/> <PackageReference Include="SixLabors.Fonts" Version="1.0.0-beta17"/>
<PackageReference Include="SixLabors.ImageSharp" Version="2.1.8"/> <PackageReference Include="SixLabors.ImageSharp" Version="2.1.8"/>
<PackageReference Include="SixLabors.ImageSharp.Drawing" Version="1.0.0-beta14"/> <PackageReference Include="SixLabors.ImageSharp.Drawing" Version="1.0.0-beta14"/>
<PackageReference Include="SixLabors.Shapes" Version="1.0.0-beta0009"/> <PackageReference Include="SixLabors.Shapes" Version="1.0.0-beta0009"/>
<PackageReference Include="StackExchange.Redis" Version="2.7.33"/> <PackageReference Include="StackExchange.Redis" Version="2.7.33"/>
<PackageReference Include="YamlDotNet" Version="15.1.4" /> <PackageReference Include="YamlDotNet" Version="15.1.4"/>
<PackageReference Include="SharpToken" Version="2.0.2"/> <PackageReference Include="SharpToken" Version="2.0.2"/>
<PackageReference Include="JetBrains.Annotations" Version="2023.3.0"/> <PackageReference Include="JetBrains.Annotations" Version="2023.3.0"/>
@@ -100,7 +99,7 @@
<PackageReference Include="TwitchLib.Api" Version="3.4.1"/> <PackageReference Include="TwitchLib.Api" Version="3.4.1"/>
<!-- sqlselectcsv and stock --> <!-- sqlselectcsv and stock -->
<PackageReference Include="CsvHelper" Version="32.0.3" /> <PackageReference Include="CsvHelper" Version="32.0.3"/>
</ItemGroup> </ItemGroup>
@@ -109,6 +108,8 @@
<ProjectReference Include="..\NadekoBot.Voice\NadekoBot.Voice.csproj"/> <ProjectReference Include="..\NadekoBot.Voice\NadekoBot.Voice.csproj"/>
<ProjectReference Include="..\NadekoBot.Generators\NadekoBot.Generators.csproj" OutputItemType="Analyzer"/> <ProjectReference Include="..\NadekoBot.Generators\NadekoBot.Generators.csproj" OutputItemType="Analyzer"/>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<AdditionalFiles Include="data\strings\responses\responses.en-US.json"/> <AdditionalFiles Include="data\strings\responses\responses.en-US.json"/>
</ItemGroup> </ItemGroup>
@@ -131,10 +132,6 @@
</None> </None>
</ItemGroup> </ItemGroup>
<ItemGroup>
<Folder Include="Modules\Utility\GuildColors\" />
</ItemGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'GlobalNadeko' "> <PropertyGroup Condition=" '$(Configuration)' == 'GlobalNadeko' ">
<!-- Define trace doesn't seem to affect the build at all so I had to remove $(DefineConstants)--> <!-- Define trace doesn't seem to affect the build at all so I had to remove $(DefineConstants)-->
<DefineTrace>false</DefineTrace> <DefineTrace>false</DefineTrace>

View File

@@ -2,22 +2,30 @@
public interface INadekoInteractionService public interface INadekoInteractionService
{ {
public NadekoInteraction Create( public NadekoInteractionBase Create(
ulong userId, ulong userId,
ButtonBuilder button, ButtonBuilder button,
Func<SocketMessageComponent, Task> onTrigger, Func<SocketMessageComponent, Task> onTrigger,
bool singleUse = true); bool singleUse = true);
public NadekoInteraction Create<T>( public NadekoInteractionBase Create<T>(
ulong userId, ulong userId,
ButtonBuilder button, ButtonBuilder button,
Func<SocketMessageComponent, T, Task> onTrigger, Func<SocketMessageComponent, T, Task> onTrigger,
in T state, in T state,
bool singleUse = true); bool singleUse = true);
NadekoInteraction Create( NadekoInteractionBase Create(
ulong userId, ulong userId,
SelectMenuBuilder menu, SelectMenuBuilder menu,
Func<SocketMessageComponent, Task> onTrigger, Func<SocketMessageComponent, Task> onTrigger,
bool singleUse = true); bool singleUse = true);
NadekoInteractionBase Create(
ulong userId,
ButtonBuilder button,
ModalBuilder modal,
Func<SocketModal, Task> onTrigger,
bool singleUse = true);
} }

View File

@@ -1,8 +1,8 @@
namespace NadekoBot; namespace NadekoBot;
public sealed class NadekoButtonInteraction : NadekoInteraction public sealed class NadekoButtonInteractionHandler : NadekoInteractionBase
{ {
public NadekoButtonInteraction( public NadekoButtonInteractionHandler(
DiscordSocketClient client, DiscordSocketClient client,
ulong authorId, ulong authorId,
ButtonBuilder button, ButtonBuilder button,

View File

@@ -3,12 +3,12 @@
public static class NadekoInteractionExtensions public static class NadekoInteractionExtensions
{ {
public static MessageComponent CreateComponent( public static MessageComponent CreateComponent(
this NadekoInteraction nadekoInteraction this NadekoInteractionBase nadekoInteractionBase
) )
{ {
var cb = new ComponentBuilder(); var cb = new ComponentBuilder();
nadekoInteraction.AddTo(cb); nadekoInteractionBase.AddTo(cb);
return cb.Build(); return cb.Build();
} }

View File

@@ -1,8 +1,8 @@
namespace NadekoBot; namespace NadekoBot;
public sealed class NadekoSelectInteraction : NadekoInteraction public sealed class NadekoButtonSelectInteractionHandler : NadekoInteractionBase
{ {
public NadekoSelectInteraction( public NadekoButtonSelectInteractionHandler(
DiscordSocketClient client, DiscordSocketClient client,
ulong authorId, ulong authorId,
SelectMenuBuilder menu, SelectMenuBuilder menu,

View File

@@ -1,6 +1,6 @@
namespace NadekoBot; namespace NadekoBot;
public abstract class NadekoInteraction public abstract class NadekoInteractionBase
{ {
private readonly ulong _authorId; private readonly ulong _authorId;
private readonly Func<SocketMessageComponent, Task> _onAction; private readonly Func<SocketMessageComponent, Task> _onAction;
@@ -13,7 +13,7 @@ public abstract class NadekoInteraction
private readonly string _customId; private readonly string _customId;
private readonly bool _singleUse; private readonly bool _singleUse;
public NadekoInteraction( public NadekoInteractionBase(
DiscordSocketClient client, DiscordSocketClient client,
ulong authorId, ulong authorId,
string customId, string customId,
@@ -85,4 +85,80 @@ public abstract class NadekoInteraction
public Task ExecuteOnActionAsync(SocketMessageComponent smc) public Task ExecuteOnActionAsync(SocketMessageComponent smc)
=> _onAction(smc); => _onAction(smc);
}
public sealed class NadekoModalSubmitHandler
{
private readonly ulong _authorId;
private readonly Func<SocketModal, Task> _onAction;
private readonly bool _onlyAuthor;
public DiscordSocketClient Client { get; }
private readonly TaskCompletionSource<bool> _interactionCompletedSource;
private IUserMessage message = null!;
private readonly string _customId;
public NadekoModalSubmitHandler(
DiscordSocketClient client,
ulong authorId,
string customId,
Func<SocketModal, Task> onAction,
bool onlyAuthor)
{
_authorId = authorId;
_customId = customId;
_onAction = onAction;
_onlyAuthor = onlyAuthor;
_interactionCompletedSource = new(TaskCreationOptions.RunContinuationsAsynchronously);
Client = client;
}
public async Task RunAsync(IUserMessage msg)
{
message = msg;
Client.ModalSubmitted += OnInteraction;
await Task.WhenAny(Task.Delay(300_000), _interactionCompletedSource.Task);
Client.ModalSubmitted -= OnInteraction;
await msg.ModifyAsync(m => m.Components = new ComponentBuilder().Build());
}
private Task OnInteraction(SocketModal sm)
{
if (sm.Message.Id != message.Id)
return Task.CompletedTask;
if (_onlyAuthor && sm.User.Id != _authorId)
return Task.CompletedTask;
if (sm.Data.CustomId != _customId)
return Task.CompletedTask;
_ = Task.Run(async () =>
{
try
{
_interactionCompletedSource.TrySetResult(true);
await ExecuteOnActionAsync(sm);
if (!sm.HasResponded)
{
await sm.DeferAsync();
}
}
catch (Exception ex)
{
Log.Warning(ex, "An exception occured while handling a: {Message}", ex.Message);
}
});
return Task.CompletedTask;
}
public Task ExecuteOnActionAsync(SocketModal smd)
=> _onAction(smd);
} }

View File

@@ -9,19 +9,19 @@ public class NadekoInteractionService : INadekoInteractionService, INService
_client = client; _client = client;
} }
public NadekoInteraction Create( public NadekoInteractionBase Create(
ulong userId, ulong userId,
ButtonBuilder button, ButtonBuilder button,
Func<SocketMessageComponent, Task> onTrigger, Func<SocketMessageComponent, Task> onTrigger,
bool singleUse = true) bool singleUse = true)
=> new NadekoButtonInteraction(_client, => new NadekoButtonInteractionHandler(_client,
userId, userId,
button, button,
onTrigger, onTrigger,
onlyAuthor: true, onlyAuthor: true,
singleUse: singleUse); singleUse: singleUse);
public NadekoInteraction Create<T>( public NadekoInteractionBase Create<T>(
ulong userId, ulong userId,
ButtonBuilder button, ButtonBuilder button,
Func<SocketMessageComponent, T, Task> onTrigger, Func<SocketMessageComponent, T, Task> onTrigger,
@@ -32,16 +32,46 @@ public class NadekoInteractionService : INadekoInteractionService, INService
((Func<T, Func<SocketMessageComponent, Task>>)((data) ((Func<T, Func<SocketMessageComponent, Task>>)((data)
=> smc => onTrigger(smc, data)))(state), => smc => onTrigger(smc, data)))(state),
singleUse); singleUse);
public NadekoInteraction Create( public NadekoInteractionBase Create(
ulong userId, ulong userId,
SelectMenuBuilder menu, SelectMenuBuilder menu,
Func<SocketMessageComponent, Task> onTrigger, Func<SocketMessageComponent, Task> onTrigger,
bool singleUse = true) bool singleUse = true)
=> new NadekoSelectInteraction(_client, => new NadekoButtonSelectInteractionHandler(_client,
userId, userId,
menu, menu,
onTrigger, onTrigger,
onlyAuthor: true, onlyAuthor: true,
singleUse: singleUse); singleUse: singleUse);
/// <summary>
/// Create an interaction which opens a modal
/// </summary>
/// <param name="userId">Id of the author</param>
/// <param name="button">Button builder for the button that will open the modal</param>
/// <param name="modal">Modal</param>
/// <param name="onTrigger">The function that will be called when the modal is submitted</param>
/// <param name="singleUse">Whether the button is single use</param>
/// <returns></returns>
public NadekoInteractionBase Create(
ulong userId,
ButtonBuilder button,
ModalBuilder modal,
Func<SocketModal, Task> onTrigger,
bool singleUse = true)
=> Create(userId,
button,
async (smc) =>
{
await smc.RespondWithModalAsync(modal.Build());
var modalHandler = new NadekoModalSubmitHandler(_client,
userId,
modal.CustomId,
onTrigger,
true);
await modalHandler.RunAsync(smc.Message);
},
singleUse: singleUse);
} }

View File

@@ -34,11 +34,11 @@ public partial class ResponseBuilder
if (_paginationBuilder.AddPaginatedFooter) if (_paginationBuilder.AddPaginatedFooter)
embed.AddPaginatedFooter(currentPage, lastPage); embed.AddPaginatedFooter(currentPage, lastPage);
NadekoInteraction? maybeInter = null; NadekoInteractionBase? maybeInter = null;
var model = await _builder.BuildAsync(ephemeral); var model = await _builder.BuildAsync(ephemeral);
async Task<(NadekoButtonInteraction left, NadekoInteraction? extra, NadekoButtonInteraction right)> async Task<(NadekoButtonInteractionHandler left, NadekoInteractionBase? extra, NadekoButtonInteractionHandler right)>
GetInteractions() GetInteractions()
{ {
var leftButton = new ButtonBuilder() var leftButton = new ButtonBuilder()
@@ -47,7 +47,7 @@ public partial class ResponseBuilder
.WithEmote(InteractionHelpers.ArrowLeft) .WithEmote(InteractionHelpers.ArrowLeft)
.WithDisabled(lastPage == 0 || currentPage <= 0); .WithDisabled(lastPage == 0 || currentPage <= 0);
var leftBtnInter = new NadekoButtonInteraction(_client, var leftBtnInter = new NadekoButtonInteractionHandler(_client,
model.User?.Id ?? 0, model.User?.Id ?? 0,
leftButton, leftButton,
(smc) => (smc) =>
@@ -80,7 +80,7 @@ public partial class ResponseBuilder
.WithEmote(InteractionHelpers.ArrowRight) .WithEmote(InteractionHelpers.ArrowRight)
.WithDisabled(lastPage == 0 || currentPage >= lastPage); .WithDisabled(lastPage == 0 || currentPage >= lastPage);
var rightBtnInter = new NadekoButtonInteraction(_client, var rightBtnInter = new NadekoButtonInteractionHandler(_client,
model.User?.Id ?? 0, model.User?.Id ?? 0,
rightButton, rightButton,
(smc) => (smc) =>

View File

@@ -19,7 +19,7 @@ public sealed partial class ResponseBuilder
private readonly IBotStrings _bs; private readonly IBotStrings _bs;
private readonly BotConfigService _bcs; private readonly BotConfigService _bcs;
private EmbedBuilder? embedBuilder; private EmbedBuilder? embedBuilder;
private NadekoInteraction? inter; private NadekoInteractionBase? inter;
private Stream? fileStream; private Stream? fileStream;
private string? fileName; private string? fileName;
private EmbedColor color = EmbedColor.Ok; private EmbedColor color = EmbedColor.Ok;
@@ -340,7 +340,7 @@ public sealed partial class ResponseBuilder
return this; return this;
} }
public ResponseBuilder Interaction(NadekoInteraction? interaction) public ResponseBuilder Interaction(NadekoInteractionBase? interaction)
{ {
inter = interaction; inter = interaction;
return this; return this;
@@ -395,7 +395,7 @@ public sealed class SourcedPaginatedResponseBuilder<T> : PaginatedResponseBuilde
return Task.FromResult<IReadOnlyCollection<T>>(ReadOnlyCollection<T>.Empty); return Task.FromResult<IReadOnlyCollection<T>>(ReadOnlyCollection<T>.Empty);
}; };
public Func<int, Task<NadekoInteraction>>? InteractionFunc { get; private set; } public Func<int, Task<NadekoInteractionBase>>? InteractionFunc { get; private set; }
public int? Elems { get; private set; } = 1; public int? Elems { get; private set; } = 1;
public int ItemsPerPage { get; private set; } = 9; public int ItemsPerPage { get; private set; } = 9;
@@ -478,13 +478,13 @@ public sealed class SourcedPaginatedResponseBuilder<T> : PaginatedResponseBuilde
return paginationSender.SendAsync(IsEphemeral); return paginationSender.SendAsync(IsEphemeral);
} }
public SourcedPaginatedResponseBuilder<T> Interaction(Func<int, Task<NadekoInteraction>> func) public SourcedPaginatedResponseBuilder<T> Interaction(Func<int, Task<NadekoInteractionBase>> func)
{ {
InteractionFunc = func; //async (i) => await func(i); InteractionFunc = func; //async (i) => await func(i);
return this; return this;
} }
public SourcedPaginatedResponseBuilder<T> Interaction(NadekoInteraction inter) public SourcedPaginatedResponseBuilder<T> Interaction(NadekoInteractionBase inter)
{ {
InteractionFunc = _ => Task.FromResult(inter); InteractionFunc = _ => Task.FromResult(inter);
return this; return this;

View File

@@ -8,5 +8,5 @@
public required AllowedMentions SanitizeMentions { get; set; } public required AllowedMentions SanitizeMentions { get; set; }
public IUser? User { get; set; } public IUser? User { get; set; }
public bool Ephemeral { get; set; } public bool Ephemeral { get; set; }
public NadekoInteraction? Interaction { get; set; } public NadekoInteractionBase? Interaction { get; set; }
} }

View File

@@ -341,6 +341,9 @@ allcmdcooldowns:
quoteadd: quoteadd:
- quoteadd - quoteadd
- . - .
quoteedit:
- quoteedit
- qedit
quoteprint: quoteprint:
- quoteprint - quoteprint
- .. - ..

View File

@@ -1245,6 +1245,15 @@ quoteadd:
desc: "The name of the quote used to retrieve the quote." desc: "The name of the quote used to retrieve the quote."
text: text:
desc: "The message of the quote." desc: "The message of the quote."
quoteedit:
desc: Edits a quote with the specified ID.
ex:
- 55 This is the new response.
params:
- quoteId:
desc: "The ID of the quote being edited."
text:
desc: "The new message of the quote."
quoteprint: quoteprint:
desc: Prints a random quote with a specified name. desc: Prints a random quote with a specified name.
ex: ex:

View File

@@ -617,6 +617,7 @@
"quotes_remove_none": "No quotes found which you can remove.", "quotes_remove_none": "No quotes found which you can remove.",
"quote_added_new": "Quote #{0} added.", "quote_added_new": "Quote #{0} added.",
"quote_deleted": "Quote #{0} deleted.", "quote_deleted": "Quote #{0} deleted.",
"quote_edited": "Quote Edited",
"region": "Region", "region": "Region",
"remind": "I will remind {0} to {1} in {2} `({3:d.M.yyyy.} at {4:HH:mm})`", "remind": "I will remind {0} to {1} in {2} `({3:d.M.yyyy.} at {4:HH:mm})`",
"remind_timely": "I will remind you about your timely reward {0}", "remind_timely": "I will remind you about your timely reward {0}",