From cfcd84eef92b47d8a73f12c6169b8da812f4cda4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Vargovsk=C3=BD?= Date: Sat, 13 Jan 2018 20:43:17 +0100 Subject: [PATCH 1/2] Inline reaction --- .../InlineReaction/InlineReactionCallback.cs | 66 +++++++++++++++++++ .../InlineReaction/ReactionCallbackData.cs | 32 +++++++++ .../InlineReaction/ReactionCallbackItem.cs | 18 +++++ Discord.Addons.Interactive/InteractiveBase.cs | 3 + .../InteractiveService.cs | 15 ++++- SampleApp/Modules/SampleModule.cs | 35 +++++++++- SampleApp/SampleApp.csproj | 4 +- 7 files changed, 166 insertions(+), 7 deletions(-) create mode 100644 Discord.Addons.Interactive/InlineReaction/InlineReactionCallback.cs create mode 100644 Discord.Addons.Interactive/InlineReaction/ReactionCallbackData.cs create mode 100644 Discord.Addons.Interactive/InlineReaction/ReactionCallbackItem.cs diff --git a/Discord.Addons.Interactive/InlineReaction/InlineReactionCallback.cs b/Discord.Addons.Interactive/InlineReaction/InlineReactionCallback.cs new file mode 100644 index 0000000..a7d3fe8 --- /dev/null +++ b/Discord.Addons.Interactive/InlineReaction/InlineReactionCallback.cs @@ -0,0 +1,66 @@ +using Discord.Commands; +using Discord.WebSocket; +using System; +using System.Linq; +using System.Threading.Tasks; + +namespace Discord.Addons.Interactive +{ + public class InlineReactionCallback : IReactionCallback + { + public RunMode RunMode => RunMode.Sync; + + public ICriterion Criterion { get; } + + public TimeSpan? Timeout { get; } + + public SocketCommandContext Context { get; } + + public IUserMessage Message { get; private set; } + + readonly InteractiveService interactive; + readonly ReactionCallbackData data; + + public InlineReactionCallback( + InteractiveService interactive, + SocketCommandContext context, + ReactionCallbackData data, + ICriterion criterion = null) + { + this.interactive = interactive; + Context = context; + this.data = data; + Criterion = criterion ?? new EmptyCriterion(); + Timeout = data.Timeout ?? TimeSpan.FromSeconds(30); + } + + public async Task DisplayAsync() + { + var message = await Context.Channel.SendMessageAsync(data.Text, embed: data.Embed).ConfigureAwait(false); + Message = message; + interactive.AddReactionCallback(message, this); + + _ = Task.Run(async () => + { + foreach (var item in data.Callbacks) + await message.AddReactionAsync(item.Reaction); + }); + + if (Timeout.HasValue) + { + _ = Task.Delay(Timeout.Value) + .ContinueWith(_ => interactive.RemoveReactionCallback(message)); + } + } + + public async Task HandleCallbackAsync(SocketReaction reaction) + { + var reactionCallbackItem = data.Callbacks.FirstOrDefault(t => t.Reaction.Equals(reaction.Emote)); + if (reactionCallbackItem == null) + return false; + + await reactionCallbackItem.Callback(Context); + return true; + } + } +} diff --git a/Discord.Addons.Interactive/InlineReaction/ReactionCallbackData.cs b/Discord.Addons.Interactive/InlineReaction/ReactionCallbackData.cs new file mode 100644 index 0000000..0c0cb98 --- /dev/null +++ b/Discord.Addons.Interactive/InlineReaction/ReactionCallbackData.cs @@ -0,0 +1,32 @@ +using Discord.Commands; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Discord.Addons.Interactive +{ + public class ReactionCallbackData + { + private readonly ICollection items; + + public string Text { get; } + public Embed Embed { get; } + public TimeSpan? Timeout { get; } + public IEnumerable Callbacks => items; + + public ReactionCallbackData(string text, Embed embed = null, TimeSpan? timeout = null) + { + Text = text; + Embed = embed; + Timeout = timeout; + items = new List(); + } + + public ReactionCallbackData WithCallback(IEmote reaction, Func callback) + { + var item = new ReactionCallbackItem(reaction, callback); + items.Add(item); + return this; + } + } +} diff --git a/Discord.Addons.Interactive/InlineReaction/ReactionCallbackItem.cs b/Discord.Addons.Interactive/InlineReaction/ReactionCallbackItem.cs new file mode 100644 index 0000000..4c224fe --- /dev/null +++ b/Discord.Addons.Interactive/InlineReaction/ReactionCallbackItem.cs @@ -0,0 +1,18 @@ +using Discord.Commands; +using System; +using System.Threading.Tasks; + +namespace Discord.Addons.Interactive +{ + public class ReactionCallbackItem + { + public IEmote Reaction { get; } + public Func Callback { get; } + + public ReactionCallbackItem(IEmote reaction, Func callback) + { + Reaction = reaction; + Callback = callback; + } + } +} diff --git a/Discord.Addons.Interactive/InteractiveBase.cs b/Discord.Addons.Interactive/InteractiveBase.cs index c934f85..9349384 100644 --- a/Discord.Addons.Interactive/InteractiveBase.cs +++ b/Discord.Addons.Interactive/InteractiveBase.cs @@ -41,6 +41,9 @@ public Task PagedReplyAsync(PaginatedMessage pager, bool fromSourc public Task PagedReplyAsync(PaginatedMessage pager, ICriterion criterion) => Interactive.SendPaginatedMessageAsync(Context, pager, criterion); + public Task InlineReactionReplyAsync(ReactionCallbackData data, bool fromSourceUser = true) + => Interactive.SendMessageWithReactionCallbacksAsync(Context, data, fromSourceUser); + public RuntimeResult Ok(string reason = null) => new OkResult(reason); } } diff --git a/Discord.Addons.Interactive/InteractiveService.cs b/Discord.Addons.Interactive/InteractiveService.cs index 11977f0..abb644a 100644 --- a/Discord.Addons.Interactive/InteractiveService.cs +++ b/Discord.Addons.Interactive/InteractiveService.cs @@ -3,7 +3,6 @@ using Discord.Commands; using Discord.WebSocket; using System.Collections.Generic; -using System.Linq; namespace Discord.Addons.Interactive { @@ -46,7 +45,7 @@ async Task Handler(SocketMessage message) } context.Client.MessageReceived += Handler; - + var trigger = eventTrigger.Task; var delay = Task.Delay(timeout.Value); var task = await Task.WhenAny(trigger, delay).ConfigureAwait(false); @@ -76,6 +75,16 @@ public async Task SendPaginatedMessageAsync(SocketCommandContext c return callback.Message; } + public async Task SendMessageWithReactionCallbacksAsync(SocketCommandContext context, ReactionCallbackData callbacks, bool fromSourceUser = true) + { + var criterion = new Criteria(); + if(fromSourceUser) + criterion.AddCriterion(new EnsureReactionFromSourceUserCriterion()); + var callback = new InlineReactionCallback(this, context, callbacks, criterion); + await callback.DisplayAsync().ConfigureAwait(false); + return callback.Message; + } + public void AddReactionCallback(IMessage message, IReactionCallback callback) => _callbacks[message.Id] = callback; public void RemoveReactionCallback(IMessage message) @@ -84,7 +93,7 @@ public void RemoveReactionCallback(ulong id) => _callbacks.Remove(id); public void ClearReactionCallbacks() => _callbacks.Clear(); - + private async Task HandleReactionAsync(Cacheable message, ISocketMessageChannel channel, SocketReaction reaction) { if (reaction.UserId == Discord.CurrentUser.Id) return; diff --git a/SampleApp/Modules/SampleModule.cs b/SampleApp/Modules/SampleModule.cs index 4436e5b..a6db863 100644 --- a/SampleApp/Modules/SampleModule.cs +++ b/SampleApp/Modules/SampleModule.cs @@ -1,7 +1,8 @@ -using System; -using System.Threading.Tasks; +using Discord; using Discord.Addons.Interactive; using Discord.Commands; +using System; +using System.Threading.Tasks; namespace SampleApp.Modules { @@ -40,5 +41,35 @@ public async Task Test_Paginator() var pages = new[] { "Page 1", "Page 2", "Page 3", "aaaaaa", "Page 5" }; await PagedReplyAsync(pages); } + + // InlineReactionReplyAsync will send a message and adds reactions on it. + // Once an user adds a reaction, the callback is fired. + // If callback was successfull next callback is not handled (message is unsubscribed). + // Unsuccessful callback is a reaction that did not have a callback. + [Command("reaction")] + public async Task Test_ReactionReply() + { + await InlineReactionReplyAsync(new ReactionCallbackData("text") + .WithCallback(new Emoji("👍"), c => c.Channel.SendMessageAsync("You've replied with 👍")) + .WithCallback(new Emoji("👎"), c => c.Channel.SendMessageAsync("You've replied with 👎")) + ); + } + [Command("embedreaction")] + public async Task Test_EmedReactionReply() + { + var one = new Emoji("1⃣"); + var two = new Emoji("2⃣"); + + var embed = new EmbedBuilder() + .WithTitle("Choose one") + .AddInlineField(one.Name, "Beer") + .AddInlineField(two.Name, "Drink") + .Build(); + + await InlineReactionReplyAsync(new ReactionCallbackData("text", embed) + .WithCallback(one, c => c.Channel.SendMessageAsync("Here you go :beer:")) + .WithCallback(two, c => c.Channel.SendMessageAsync("Here you go :tropical_drink:")) + ); + } } } diff --git a/SampleApp/SampleApp.csproj b/SampleApp/SampleApp.csproj index 208d140..c86d83a 100644 --- a/SampleApp/SampleApp.csproj +++ b/SampleApp/SampleApp.csproj @@ -6,8 +6,8 @@ - - + + From c689d38e6c2cc0060fb009a1475b0009a53274e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Vargovsk=C3=BD?= Date: Sun, 12 Aug 2018 12:02:55 +0200 Subject: [PATCH 2/2] Fixed code style. --- .../InlineReaction/InlineReactionCallback.cs | 18 +++++++++--------- .../InlineReaction/ReactionCallbackData.cs | 8 ++++---- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/Discord.Addons.Interactive/InlineReaction/InlineReactionCallback.cs b/Discord.Addons.Interactive/InlineReaction/InlineReactionCallback.cs index a7d3fe8..d446061 100644 --- a/Discord.Addons.Interactive/InlineReaction/InlineReactionCallback.cs +++ b/Discord.Addons.Interactive/InlineReaction/InlineReactionCallback.cs @@ -18,8 +18,8 @@ public class InlineReactionCallback : IReactionCallback public IUserMessage Message { get; private set; } - readonly InteractiveService interactive; - readonly ReactionCallbackData data; + private readonly InteractiveService _interactive; + private readonly ReactionCallbackData _data; public InlineReactionCallback( InteractiveService interactive, @@ -27,35 +27,35 @@ public InlineReactionCallback( ReactionCallbackData data, ICriterion criterion = null) { - this.interactive = interactive; + _interactive = interactive; Context = context; - this.data = data; + _data = data; Criterion = criterion ?? new EmptyCriterion(); Timeout = data.Timeout ?? TimeSpan.FromSeconds(30); } public async Task DisplayAsync() { - var message = await Context.Channel.SendMessageAsync(data.Text, embed: data.Embed).ConfigureAwait(false); + var message = await Context.Channel.SendMessageAsync(_data.Text, embed: _data.Embed).ConfigureAwait(false); Message = message; - interactive.AddReactionCallback(message, this); + _interactive.AddReactionCallback(message, this); _ = Task.Run(async () => { - foreach (var item in data.Callbacks) + foreach (var item in _data.Callbacks) await message.AddReactionAsync(item.Reaction); }); if (Timeout.HasValue) { _ = Task.Delay(Timeout.Value) - .ContinueWith(_ => interactive.RemoveReactionCallback(message)); + .ContinueWith(_ => _interactive.RemoveReactionCallback(message)); } } public async Task HandleCallbackAsync(SocketReaction reaction) { - var reactionCallbackItem = data.Callbacks.FirstOrDefault(t => t.Reaction.Equals(reaction.Emote)); + var reactionCallbackItem = _data.Callbacks.FirstOrDefault(t => t.Reaction.Equals(reaction.Emote)); if (reactionCallbackItem == null) return false; diff --git a/Discord.Addons.Interactive/InlineReaction/ReactionCallbackData.cs b/Discord.Addons.Interactive/InlineReaction/ReactionCallbackData.cs index 0c0cb98..77dcf84 100644 --- a/Discord.Addons.Interactive/InlineReaction/ReactionCallbackData.cs +++ b/Discord.Addons.Interactive/InlineReaction/ReactionCallbackData.cs @@ -7,25 +7,25 @@ namespace Discord.Addons.Interactive { public class ReactionCallbackData { - private readonly ICollection items; + private readonly ICollection _items; public string Text { get; } public Embed Embed { get; } public TimeSpan? Timeout { get; } - public IEnumerable Callbacks => items; + public IEnumerable Callbacks => _items; public ReactionCallbackData(string text, Embed embed = null, TimeSpan? timeout = null) { Text = text; Embed = embed; Timeout = timeout; - items = new List(); + _items = new List(); } public ReactionCallbackData WithCallback(IEmote reaction, Func callback) { var item = new ReactionCallbackItem(reaction, callback); - items.Add(item); + _items.Add(item); return this; } }