diff --git a/Assets/datetimemessagehelp.png b/Assets/datetimemessagehelp.png new file mode 100644 index 00000000..069e6ab0 Binary files /dev/null and b/Assets/datetimemessagehelp.png differ diff --git a/Assets/datetimemessagehelp.svg b/Assets/datetimemessagehelp.svg new file mode 100644 index 00000000..5dc576bc --- /dev/null +++ b/Assets/datetimemessagehelp.svg @@ -0,0 +1,702 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +1 week + -1 week + +1 day + -1 day + +1 hour + -1 hour + +1 min + -1 min + + + diff --git a/COMMANDS.md b/COMMANDS.md index 2b9d96bd..ef0f594b 100644 --- a/COMMANDS.md +++ b/COMMANDS.md @@ -1,4 +1,4 @@ -# List of commands (74 total) +# List of commands (77 total) ## Main (7) Stuff to do with the bot and other random stuff. @@ -124,6 +124,15 @@ For having a bit of fun. |`!wow games verbal-memory`|Try remember as many words as you can.| |`!wow games verbal-memory-leaderboard [optional:PAGE]`|Shows the leaderboard for the counting game.| +## Events (3) +Create and organise events for you and your friends. + +|Command|Summary| +|---|---| +|`!wow events new [optional:DESCRIPTION]`|Create a new event.| +|`!wow events delete`|Delete an upcoming event.| +|`!wow events set-announcements-channel [CHANNEL]`|Sets the channel that event notifications will be sent to.| + ## Developer (10) Boring stuff for developers. diff --git a/wow2.Bot/BotService.cs b/wow2.Bot/BotService.cs index 9b705b27..7a3cd0ea 100644 --- a/wow2.Bot/BotService.cs +++ b/wow2.Bot/BotService.cs @@ -14,6 +14,7 @@ using wow2.Bot.Data; using wow2.Bot.Extensions; using wow2.Bot.Modules; +using wow2.Bot.Modules.Events; using wow2.Bot.Modules.Games.Counting; using wow2.Bot.Modules.Games.VerbalMemory; using wow2.Bot.Modules.Keywords; @@ -207,7 +208,13 @@ public static async Task ReactionAddedAsync(Cacheable cache { if (!await ResponseMessage.ActOnReactionAddedAsync(reaction)) { - await QuestionMessage.ActOnReactionAsync(reaction); + if (!await QuestionMessage.ActOnReactionAsync(reaction)) + { + if (!await DateTimeSelectorMessage.ActOnReactionAsync(reaction)) + { + await EventMessage.ActOnReactionAddedAsync(reaction); + } + } } } } diff --git a/wow2.Bot/Data/GuildData.cs b/wow2.Bot/Data/GuildData.cs index 4eb273b3..9596e8a6 100644 --- a/wow2.Bot/Data/GuildData.cs +++ b/wow2.Bot/Data/GuildData.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Text.Json.Serialization; +using wow2.Bot.Modules.Events; using wow2.Bot.Modules.Games; using wow2.Bot.Modules.Keywords; using wow2.Bot.Modules.Main; @@ -26,6 +27,9 @@ public class GuildData [JsonIgnore] public List QuestionMessages { get; set; } = new(); + [JsonIgnore] + public List DateTimeSelectorMessages { get; set; } = new(); + public MainModuleConfig Main { get; set; } = new(); public KeywordsModuleConfig Keywords { get; set; } = new(); @@ -41,5 +45,7 @@ public class GuildData public YouTubeModuleConfig YouTube { get; set; } = new(); public TimersModuleConfig Timers { get; set; } = new(); + + public EventsModuleConfig Events { get; set; } = new(); } } \ No newline at end of file diff --git a/wow2.Bot/Modules/Dev/Tests.cs b/wow2.Bot/Modules/Dev/Tests.cs index a96c11e9..8265d460 100644 --- a/wow2.Bot/Modules/Dev/Tests.cs +++ b/wow2.Bot/Modules/Dev/Tests.cs @@ -6,6 +6,7 @@ using Discord; using Discord.Commands; using wow2.Bot.Data; +using wow2.Bot.Modules.Events; using wow2.Bot.Modules.Keywords; using wow2.Bot.Modules.Main; using wow2.Bot.Modules.Voice; @@ -73,6 +74,14 @@ public static async Task QuestionMessageTest(SocketCommandContext context) .SendAsync(context.Channel); } + [Test("date-time-message")] + public static async Task DateTimeMessageTeset(SocketCommandContext context) + { + await new DateTimeSelectorMessage( + (d) => context.Channel.SendMessageAsync($"DateTime: {d}")) + .SendAsync(context.Channel); + } + [Test("aliases")] public static async Task AliasesTest(SocketCommandContext context) { diff --git a/wow2.Bot/Modules/Events/Event.cs b/wow2.Bot/Modules/Events/Event.cs new file mode 100644 index 00000000..113b116a --- /dev/null +++ b/wow2.Bot/Modules/Events/Event.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; + +namespace wow2.Bot.Modules.Events +{ + public class Event + { + public string Description { get; set; } + + public DateTime ForDateTime { get; set; } + + public string CreatedByMention { get; set; } + + public List AttendeeMentions { get; set; } = new(); + } +} \ No newline at end of file diff --git a/wow2.Bot/Modules/Events/EventMessage.cs b/wow2.Bot/Modules/Events/EventMessage.cs new file mode 100644 index 00000000..208db122 --- /dev/null +++ b/wow2.Bot/Modules/Events/EventMessage.cs @@ -0,0 +1,95 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Discord; +using Discord.WebSocket; +using wow2.Bot.Data; +using wow2.Bot.Extensions; +using wow2.Bot.Verbose.Messages; + +namespace wow2.Bot.Modules.Events +{ + public class EventMessage : Message + { + public static readonly IEmote AttendEmote = new Emoji("👌"); + public static readonly IEmote EditEmote = new Emoji("✏"); + + public EventMessage() + { + } + + public EventMessage(Event @event) + { + Event = @event; + } + + public Event Event { get; set; } + + public static async Task ActOnReactionAddedAsync(SocketReaction reaction) + { + EventsModuleConfig config = DataManager.AllGuildData[reaction.Channel.GetGuild().Id].Events; + EventMessage message = config.EventMessages.Find(m => m.SentMessage.Id == reaction.MessageId); + + if (message == null) + return false; + + if (reaction.Emote.Name == AttendEmote.Name) + { + message.Event.AttendeeMentions.Add(reaction.User.Value.Mention); + await message.UpdateMessageAsync(); + } + else if (reaction.Emote.Name == EditEmote.Name) + { + IUserMessage sentMessage = await new DateTimeSelectorMessage( + async d => + { + message.Event.ForDateTime = d; + await message.UpdateMessageAsync(); + }, + "Select the new date and time for this event.") + .SendAsync(reaction.Channel); + + await message.SentMessage.RemoveReactionAsync(EditEmote, reaction.User.Value); + } + else + { + return false; + } + + return true; + } + + public async override Task SendAsync(IMessageChannel channel) + { + await UpdateMessageAsync(); + + IUserMessage message = await base.SendAsync(channel); + List eventMessages = DataManager.AllGuildData[message.GetGuild().Id].Events.EventMessages; + + eventMessages.Truncate(30); + eventMessages.Add(this); + + await message.AddReactionsAsync( + new IEmote[] { AttendEmote, EditEmote }); + + return message; + } + + public async Task UpdateMessageAsync() + { + EmbedBuilder = new EmbedBuilder() + { + Title = $"📋 {Event.Description}", + Description = $"{Event.AttendeeMentions.Count} people are attending on `{Event.ForDateTime}`", + Footer = new EmbedFooterBuilder() + { + Text = $"Want to attend this event? React with {AttendEmote.Name}", + }, + Color = Color.LightGrey, + }; + + if (SentMessage != null) + await SentMessage.ModifyAsync(m => m.Embed = Embed); + } + } +} \ No newline at end of file diff --git a/wow2.Bot/Modules/Events/EventsModule.cs b/wow2.Bot/Modules/Events/EventsModule.cs new file mode 100644 index 00000000..3119025f --- /dev/null +++ b/wow2.Bot/Modules/Events/EventsModule.cs @@ -0,0 +1,71 @@ +using System; +using System.Threading.Tasks; +using Discord; +using Discord.Commands; +using Discord.WebSocket; +using wow2.Bot.Data; +using wow2.Bot.Extensions; +using wow2.Bot.Verbose.Messages; + +namespace wow2.Bot.Modules.Events +{ + [Name("Events")] + [Group("events")] + [Alias("event")] + [Summary("Create and organise events for you and your friends.")] + public class EventsModule : Module + { + private EventsModuleConfig Config => DataManager.AllGuildData[Context.Guild.Id].Events; + + [Command("new")] + [Alias("start", "create", "add")] + [Summary("Create a new event.")] + public async Task NewAsync([Remainder] string description = "untitled event") + { + if (Config.AnnouncementsChannelId == default) + { + throw new CommandReturnException( + Context, + $"You can do this be using `{Context.Guild.GetCommandPrefix()} events set-announcements-channel`", + "Set an announcements channel first"); + } + + await new DateTimeSelectorMessage( + async d => + { + var @event = new Event() + { + Description = description, + ForDateTime = d, + CreatedByMention = Context.User.Mention, + }; + + Config.Events.Add(@event); + await new EventMessage(@event) + .SendAsync((IMessageChannel)BotService.Client.GetChannel(Config.AnnouncementsChannelId)); + }, + "Select a date and time for this event.") + .SendAsync(Context.Channel); + } + + [Command("delete")] + [Alias("remove", "stop")] + [Summary("Delete an upcoming event.")] + public async Task DeleteAsync() + { + throw new NotImplementedException(); + } + + [Command("set-announcements-channel")] + [Alias("set-channel", "announcements-channel", "channel")] + [Summary("Sets the channel that event notifications will be sent to.")] + public async Task SetAnnouncementsChannelAsync(SocketTextChannel channel) + { + Config.AnnouncementsChannelId = channel.Id; + await DataManager.SaveGuildDataToFileAsync(Context.Guild.Id); + + await new SuccessMessage($"You'll get notified about events in {channel.Mention}") + .SendAsync(Context.Channel); + } + } +} \ No newline at end of file diff --git a/wow2.Bot/Modules/Events/EventsModuleConfig.cs b/wow2.Bot/Modules/Events/EventsModuleConfig.cs new file mode 100644 index 00000000..4c96b3b2 --- /dev/null +++ b/wow2.Bot/Modules/Events/EventsModuleConfig.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; + +namespace wow2.Bot.Modules.Events +{ + public class EventsModuleConfig + { + public List Events { get; set; } = new(); + + public List EventMessages { get; set; } = new(); + + public ulong AnnouncementsChannelId { get; set; } + } +} \ No newline at end of file diff --git a/wow2.Bot/Verbose/Messages/DateTimeSelectorMessage.cs b/wow2.Bot/Verbose/Messages/DateTimeSelectorMessage.cs new file mode 100644 index 00000000..f0092f8f --- /dev/null +++ b/wow2.Bot/Verbose/Messages/DateTimeSelectorMessage.cs @@ -0,0 +1,101 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Discord; +using Discord.WebSocket; +using wow2.Bot.Data; +using wow2.Bot.Extensions; + +namespace wow2.Bot.Verbose.Messages +{ + public class DateTimeSelectorMessage : Message + { + public const string ConfirmChar = "✅"; + + public static readonly IReadOnlyDictionary DateTimeModifierEmotes = new Dictionary() + { + { new Emoji("⏫"), TimeSpan.FromDays(7) }, + { new Emoji("⏬"), TimeSpan.FromDays(-7) }, + { new Emoji("🔼"), TimeSpan.FromDays(1) }, + { new Emoji("🔽"), TimeSpan.FromDays(-1) }, + { new Emoji("⏩"), TimeSpan.FromHours(1) }, + { new Emoji("⏪"), TimeSpan.FromHours(-1) }, + { new Emoji("▶"), TimeSpan.FromMinutes(10) }, + { new Emoji("◀"), TimeSpan.FromMinutes(-10) }, + }; + + public DateTimeSelectorMessage(Func confirmFunc, string description = "Select a date and time.") + { + EmbedBuilder = new EmbedBuilder() + { + Description = "Give me a second to add reactions...", + Color = Color.LightGrey, + }; + Description = description; + ConfirmFunc = confirmFunc; + } + + public DateTime DateTime { get; set; } = DateTime.Now; + + private string Description { get; } + + private Func ConfirmFunc { get; } + + public static async Task ActOnReactionAsync(SocketReaction reaction) + { + GuildData guildData = DataManager.AllGuildData[reaction.Channel.GetGuild().Id]; + DateTimeSelectorMessage message = guildData.DateTimeSelectorMessages.Find(m => m.SentMessage.Id == reaction.MessageId); + + if (message == null) + return false; + + if (reaction.Emote.Name == ConfirmChar) + { + guildData.DateTimeSelectorMessages.Remove(message); + await message.SentMessage.RemoveAllReactionsAsync(); + await message.ConfirmFunc.Invoke(message.DateTime); + return true; + } + else if (DateTimeModifierEmotes.Any(p => p.Key.Name == reaction.Emote.Name)) + { + await message.SentMessage.RemoveReactionAsync(reaction.Emote, reaction.User.Value); + message.DateTime += DateTimeModifierEmotes[reaction.Emote]; + await message.UpdateMessageAsync(); + return true; + } + else + { + return false; + } + } + + public async override Task SendAsync(IMessageChannel channel) + { + IUserMessage message = await base.SendAsync(channel); + List dateTimeSelectorMessages = DataManager + .AllGuildData[message.GetGuild().Id].DateTimeSelectorMessages; + + await message.AddReactionsAsync( + DateTimeModifierEmotes.Keys.Append(new Emoji(ConfirmChar)).ToArray()); + + dateTimeSelectorMessages.Truncate(12); + dateTimeSelectorMessages.Add(this); + await UpdateMessageAsync(); + + return message; + } + + private async Task UpdateMessageAsync() + { + EmbedBuilder = new EmbedBuilder() + { + Description = $"{new Emoji($"<:wowquestion:{QuestionEmoteId}>")} {Description}\n`{DateTime}`", + ImageUrl = "https://github.com/rednir/wow2/blob/master/Assets/datetimemessagehelp.png", + Color = new Color(0x9b59b6), + }; + + await SentMessage?.ModifyAsync(m => m.Embed = Embed); + } + } +} \ No newline at end of file