Skip to content

Commit

Permalink
Update front end to batch responses (#55)
Browse files Browse the repository at this point in the history
  • Loading branch information
kwunkiuma authored Nov 20, 2020
1 parent 41f7d48 commit 91fff2b
Show file tree
Hide file tree
Showing 3 changed files with 63 additions and 73 deletions.
2 changes: 1 addition & 1 deletion DiscordBot/Commands/EventCommands.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ internal class EventCommands : BaseCommandModule
public const string ClearReaction = ":no_entry_sign:";
public const string RefreshReaction = ":arrows_counterclockwise:";

private const string SignupChannelName = "events";
public const string SignupChannelName = "events";

private static readonly string[] EventOperations =
{
Expand Down
44 changes: 33 additions & 11 deletions DiscordBot/Commands/ReactionBuffer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
using System.Threading.Tasks;
using DiscordBot.DataAccess;
using DiscordBot.DataAccess.Models;
using DSharpPlus;
using DSharpPlus.EventArgs;
using static DiscordBot.Commands.EventHelper;

namespace DiscordBot.Commands
{
Expand All @@ -19,30 +20,28 @@ internal class ReactionBuffer
{
private const int bufferDelayMilliseconds = 10 * 1000;

private readonly BlockingCollection<ResponseReaction> buffer;
private readonly ConcurrentQueue<MessageReactionAddEventArgs> buffer;
private readonly SemaphoreSlim bufferStateLock;
private ReactionBufferState state;

private readonly IEventsSheetsService eventsSheetsService;
private readonly DiscordClient client;

public ReactionBuffer(IEventsSheetsService eventsSheetsService, DiscordClient client)
public ReactionBuffer(IEventsSheetsService eventsSheetsService)
{
buffer = new BlockingCollection<ResponseReaction>();
buffer = new ConcurrentQueue<MessageReactionAddEventArgs>();
bufferStateLock = new SemaphoreSlim(1);
state = ReactionBufferState.Ready;

this.eventsSheetsService = eventsSheetsService;
this.client = client;
}

public async Task AddReaction(ResponseReaction reaction)
public async Task AddReaction(MessageReactionAddEventArgs eventArguments)
{
// Invariants: you may only read/write from the state and buffer variables if you hold
// the bufferStateLock semaphore

await bufferStateLock.WaitAsync();
buffer.Add(reaction);
buffer.Enqueue(eventArguments);

// If another thread is collecting reactions to process, we don't need to
if (state == ReactionBufferState.Collecting)
Expand All @@ -58,15 +57,26 @@ public async Task AddReaction(ResponseReaction reaction)

// Lock the list, get and empty its contents to be processed
await bufferStateLock.WaitAsync();
var reactions = buffer.GetConsumingEnumerable().ToList();
var reactions = buffer.ToList();
buffer.Clear();

state = ReactionBufferState.Ready;
bufferStateLock.Release();

await ProcessReactions(reactions);
}

private async Task ProcessReactions(IEnumerable<ResponseReaction> reactions)
private async Task ProcessReactions(IEnumerable<MessageReactionAddEventArgs> reactionArguments)
{
var reactionArgumentsList = reactionArguments.ToList();
var reactions = reactionArgumentsList.Select(eventArguments =>
new ResponseReaction(
eventArguments.Message.Id,
eventArguments.User.Id,
eventArguments.Emoji.GetDiscordName()
)
);

var distinctReactions = reactions.Distinct().ToList();

var addReactions = distinctReactions.Where(reaction => reaction.Emoji != EventCommands.ClearReaction);
Expand All @@ -75,7 +85,19 @@ private async Task ProcessReactions(IEnumerable<ResponseReaction> reactions)
var clearReactions = distinctReactions.Where(reaction => reaction.Emoji == EventCommands.ClearReaction);
await eventsSheetsService.ClearResponseBatchAsync(clearReactions);

// Send some DMs... TODO by Frank
// Update each message that had a reaction processed
var messages = reactionArgumentsList
.Select(eventArguments => eventArguments.Message)
.Distinct();
foreach (var message in messages)
{
var discordEvent = await eventsSheetsService.GetEventFromMessageIdAsync(message.Id);
var signupsByResponse = await eventsSheetsService.GetSignupsByResponseAsync(discordEvent.Key);
await message.ModifyAsync(
message.Content,
GetSignupEmbed(discordEvent, signupsByResponse).Build()
);
}
}
}
}
90 changes: 29 additions & 61 deletions DiscordBot/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,8 @@
using DiscordBot.Commands;
using DiscordBot.DataAccess;
using DiscordBot.DataAccess.Exceptions;
using DiscordBot.DataAccess.Models;
using DSharpPlus;
using DSharpPlus.CommandsNext;
using DSharpPlus.Entities;
using DSharpPlus.EventArgs;
using DSharpPlus.Interactivity;
using DSharpPlus.Interactivity.Extensions;
Expand Down Expand Up @@ -47,102 +45,72 @@ private static async Task MainAsync()

commands.RegisterCommands<EventCommands>();

var reactionBuffer = new ReactionBuffer(eventsSheetsService);

discord.UseInteractivity(new InteractivityConfiguration());
discord.MessageReactionAdded += async (client, eventArguments) =>
discord.MessageReactionAdded += (client, eventArguments) =>
{
await OnMessageReactionAdded(client, eventArguments, eventsSheetsService);
OnMessageReactionAdded(client, eventArguments, eventsSheetsService, reactionBuffer);
return Task.CompletedTask;
};

await discord.ConnectAsync();
await Task.Delay(-1);
}

private static async Task OnMessageReactionAdded(
private static void OnMessageReactionAdded(
DiscordClient client,
MessageReactionAddEventArgs eventArguments,
IEventsSheetsService eventsSheetsService)
IEventsSheetsService eventsSheetsService,
ReactionBuffer reactionBuffer)
{
// Skip if event was triggered by the bot
if (eventArguments.User == client.CurrentUser)
{
return;
}

var discordEvent = await GetEventFromMessageIdOrDefaultAsync(eventArguments.Message.Id, eventsSheetsService);
if (discordEvent == null)
// Skip if reaction is not in the signup channel
if (eventArguments.Channel.Name != EventCommands.SignupChannelName)
{
return;
}

await ProcessReaction(client, eventArguments, discordEvent, eventsSheetsService);
_ = Task.Run(async () =>
await ProcessReaction(eventArguments, eventsSheetsService, reactionBuffer)
);
}

private static async Task ProcessReaction(
DiscordClient client,
MessageReactionAddEventArgs eventArguments,
DiscordEvent discordEvent,
IEventsSheetsService eventsSheetsService)
IEventsSheetsService eventsSheetsService,
ReactionBuffer reactionBuffer)
{
var dmChannel = await eventArguments.Guild
.GetMemberAsync(eventArguments.User.Id).Result
.CreateDmChannelAsync();

switch (eventArguments.Emoji.GetDiscordName())
{
case (EventCommands.RefreshReaction):
await UpdateSignupMessage(eventArguments, discordEvent, eventsSheetsService);
break;
case (EventCommands.ClearReaction):
await eventsSheetsService.ClearResponsesForUserAsync(discordEvent.Key, eventArguments.User.Id);
await client.SendMessageAsync(
dmChannel,
$"You've signed off {discordEvent.Name}."
);
break;
default:
await AddResponse(client, eventArguments, discordEvent, dmChannel, eventsSheetsService);
break;
}

await eventArguments.Message.DeleteReactionAsync(eventArguments.Emoji, eventArguments.User);
}

private static async Task AddResponse(
DiscordClient client,
MessageReactionAddEventArgs eventArguments,
DiscordEvent discordEvent,
DiscordChannel dmChannel,
IEventsSheetsService eventsSheetsService)
{
try
if (eventArguments.Emoji.GetDiscordName() == EventCommands.RefreshReaction)
{
await eventsSheetsService.AddResponseForUserAsync(
discordEvent.Key,
eventArguments.User.Id,
eventArguments.Emoji.GetDiscordName()
);
await UpdateSignupMessage(eventArguments, eventsSheetsService);
}
catch (ResponseNotFoundException)
{
return;
else
{
await reactionBuffer.AddReaction(eventArguments);
}

await client.SendMessageAsync(
dmChannel,
$"You've responded to {discordEvent.Name} as {eventArguments.Emoji.Name}."
);
}

private static async Task UpdateSignupMessage(
MessageReactionAddEventArgs eventArguments,
DiscordEvent discordEvent,
IEventsSheetsService eventsSheetsService)
{
var signupsByResponse = await eventsSheetsService.GetSignupsByResponseAsync(discordEvent.Key);
await eventArguments.Message.ModifyAsync(
eventArguments.Message.Content,
GetSignupEmbed(discordEvent, signupsByResponse).Build()
);
var discordEvent = await GetEventFromMessageIdOrDefaultAsync(eventArguments.Message.Id, eventsSheetsService);
if (discordEvent != null)
{
var signupsByResponse = await eventsSheetsService.GetSignupsByResponseAsync(discordEvent.Key);
await eventArguments.Message.ModifyAsync(
eventArguments.Message.Content,
GetSignupEmbed(discordEvent, signupsByResponse).Build()
);
}
}
}
}

0 comments on commit 91fff2b

Please sign in to comment.