public static async Task AddReminder(CommandContext ctx, string args) { // Firstly get all the matches. MatchCollection regexMatches = DateRegex.Matches(args); BitArray regexCoverage = new BitArray(args.Length); var dto = ctx.Message.CreationTimestamp; List <ulong> mentions = new List <ulong>(); // String processing - find the message and get the reminder end date. // To find what's not a date, we simply look for the first character that isn't in the boundaries of a Regex match. // // Structure of a possible string: // // Date String | Message String // DATE, DATE, DATE, DATE | message // // Beyond the Date String we want to stop processing time information as people may reference time in the message string, so we don't // erronously want that data added to the date string. // Populate the regexCoverage... foreach (Match match in regexMatches) { for (int i = match.Index; i < match.Index + match.Length; i++) { regexCoverage[i] = true; } } // So I want to explain what I'm about to do here. Every value in regexCoverage[] indicates if that's part of the initial time // string. We want to use it to determine if something is a message or a time value, so if at any point, we run into something that // isn't a time string, we want to set every instance thereafter as false so we know it's part of a message. if (regexMatches.Count > 0) { bool value = regexCoverage[0]; for (int k = 1; k < regexCoverage.Count; k++) { if (!IsWhitespace(args[k])) { if (!regexCoverage[k] && value) { value = false; } if (!value) { regexCoverage[k] = value; } } } } // We need to figure out where the date string ends. string messageString = String.Empty; int dateEndIndex = 0; bool messageFound = false; while (dateEndIndex < regexCoverage.Length && !messageFound) { char stringChar = args[dateEndIndex]; bool inRegexBoundaries = regexCoverage[dateEndIndex]; // This checks to see if the character is non-white-space and outside of any RegEx boundaries. messageFound = !IsWhitespace(stringChar) && !inRegexBoundaries; // If not found, continue; otherwise, keep incrementing. if (!messageFound) { dateEndIndex++; } } // If we aren't going out of bounds, let's set the string to this. if (dateEndIndex < regexCoverage.Length) { messageString = args.Substring(dateEndIndex); } // Get date information foreach (Match match in regexMatches) { // Only try to exclude Message String date information if a message string was found. if (!messageFound || (regexCoverage[match.Index] && regexCoverage[match.Index + match.Length - 1])) { InterpretTime(match.Groups[1].Value, match.Groups[2].Value, ref dto); } } // Get mentions foreach (DiscordUser user in ctx.Message.MentionedUsers) { ulong id = user.Id; if (!user.IsBot && !mentions.Contains(id)) { mentions.Add(id); } } // At this point, now we have the DateTimeOffset describing when this reminder needs to be set off, and we have a message string if // any. So now we just need to make sure it's within reasonable boundaries, set the reminder, and notify the user. DateTimeOffset maxtime = new DateTimeOffset(ctx.Message.CreationTimestamp.UtcDateTime).AddMonths(Program.Settings.MaxReminderTimeMonths); DiscordEmbedBuilder embed; bool sendErrorEmbed = false; if (dto.UtcTicks == ctx.Message.CreationTimestamp.UtcTicks) { // No time was added. embed = Generics.GenericEmbedTemplate( color: Generics.NegativeColor, description: Generics.NegativeDirectResponseTemplate( mention: ctx.Member.Mention, body: @"I was unable able to add the reminder you gave me. You didn't supply me a valid time..."), title: @"Unable to add reminder", thumbnail: Generics.URL_REMINDER_GENERIC ); sendErrorEmbed = true; } else if (dto.UtcTicks > maxtime.UtcTicks) { // More than our allowed time away. int maxMonths = Program.Settings.MaxReminderTimeMonths; embed = Generics.GenericEmbedTemplate( color: Generics.NegativeColor, description: Generics.NegativeDirectResponseTemplate( mention: ctx.Member.Mention, body: $"I was unable able to add the reminder you gave me. That's more than {maxMonths} month{(maxMonths > 0 ? @"s" : String.Empty)} away..."), title: @"Unable to add reminder", thumbnail: Generics.URL_REMINDER_GENERIC ); sendErrorEmbed = true; } else { // Everything is good in the world... except that the world is burning, but that's not something we're worried about here, for // now... embed = Generics.GenericEmbedTemplate( color: Generics.PositiveColor, description: Generics.PositiveDirectResponseTemplate( mention: ctx.Member.Mention, body: @"I added the reminder you gave me!"), title: @"Add reminder", thumbnail: Generics.URL_REMINDER_GENERIC ); Reminder reminder = new Reminder( originalMessageId: ctx.Message.Id.ToString(), text: messageString.Length.Equals(0) ? @"n/a" : messageString.ToString(), time: (int)(dto.ToUnixTimeSeconds() / 60), user: ctx.Member.Id, channel: ctx.Channel.Id, usersToNotify: mentions.Select(a => Generics.GetMention(a)).ToArray()); embed.AddField(@"User", ctx.Member.Mention, true); embed.AddField(@"Time (UTC)", dto.ToString(Generics.DateFormat), true); embed.AddField(@"Remaining time", Generics.GetRemainingTime(dto), true); embed.AddField(@"Notification Identifier", reminder.OriginalMessageId.ToString(), false); if (GetUsersToNotify(reminder.UsersToNotify, out string mentionsString)) { embed.AddField(@"Users to mention", mentionsString, false); } // Let's build the command. using var command = new SqliteCommand(BotDatabase.Instance.DataSource) { CommandText = QQ_AddReminder }; SqliteParameter a = new SqliteParameter("$id", reminder.OriginalMessageId.ToString()) { DbType = DbType.String }; SqliteParameter b = new SqliteParameter("$userid", reminder.User) { DbType = DbType.String }; SqliteParameter c = new SqliteParameter("$channelid", reminder.Channel) { DbType = DbType.String }; SqliteParameter d = new SqliteParameter("$message", reminder.Text) { DbType = DbType.String }; SqliteParameter e = new SqliteParameter("$time", reminder.Time) { DbType = DbType.Int32 }; var stringBuilder = new StringBuilder(); stringBuilder.AppendJoin(' ', reminder.UsersToNotify); SqliteParameter f = new SqliteParameter("$mention", stringBuilder.ToString()) { DbType = DbType.String }; command.Parameters.AddRange(new SqliteParameter[] { a, b, c, d, e, f }); await BotDatabase.Instance.ExecuteNonQuery(command); // Send the response. await ctx.Channel.SendMessageAsync(embed : embed); } if (sendErrorEmbed) { var a = ctx.Channel.SendMessageAsync(embed: embed); var b = GenericResponses.HandleInvalidArguments(ctx); await Task.WhenAll(a, b); } }
/// <summary>List all the reminders</summary> public static async Task ListReminders(CommandContext ctx) { Reminder[] reminders = await ReadTable(); // Check if there are any notifications. If there are none, let the user know. if (reminders.Length > 0) { // There are reminders. var interactivity = Program.BotClient.GetInteractivity(); List <Page> pages = new List <Page>(); var deb = new DiscordEmbedBuilder(); int count = 0; int curPage = 1; // Paginate all the results. const int REMINDERS_PER_PAGE = 5; for (int i = 0; i < reminders.Length; i++) { Reminder reminder = reminders[i]; var dto = DateTimeOffset.FromUnixTimeSeconds(reminder.Time * 60); var valueStringBuilder = new StringBuilder(); valueStringBuilder.Append($"{Generics.GetMention(reminder.User)}: {reminder.Text}\n"); if (GetUsersToNotify(reminder.UsersToNotify, out string mentionsString)) { valueStringBuilder.Append($"**Users to mention:** {mentionsString}\n"); } valueStringBuilder.Append($"**Id:** {reminder.OriginalMessageId}\n"); valueStringBuilder.Append($"**Remaining time:** {Generics.GetRemainingTime(dto)}"); #region a bunny // .". // / | // / / // / ," // .-------.--- / // "._ __.-/ o. o\ // " ( Y ) // ) / // / ( // / Y // .-" | // / _ \ \ // / `. ". ) /' ) // Y )( / /(,/ // ,| / ) // ( | / / // " \_ (__ (__ [nabis] // "-._,)--._,) // o < bunny poopy l0l // ------------------------------------------------ // This ASCII pic can be found at // https://asciiart.website/index.php?art=animals/rabbits #endregion a bunny string name = dto.ToString(Generics.DateFormat); deb.AddField(name, valueStringBuilder.ToString()); count++; if (count == REMINDERS_PER_PAGE || i == reminders.Length - 1) { // Create a new page. deb.WithDescription(Generics.NeutralDirectResponseTemplate( mention: ctx.User.Mention, body: $"Hello {ctx.Member.Mention}, please note you are the only one who can react to this message.\n\n" + $"**Showing {count} reminders out of a total of {reminders.Length}.**")); deb.WithTitle($"Reminders Page {curPage}/{Math.Ceiling((float)reminders.Length / (float)REMINDERS_PER_PAGE)}"); deb.WithColor(Generics.NeutralColor); deb.WithThumbnail(Generics.URL_REMINDER_GENERIC); pages.Add(new Page(embed: deb)); count = 0; curPage++; deb = new DiscordEmbedBuilder(); } // end if } // end for await interactivity.SendPaginatedMessageAsync(ctx.Channel, ctx.User, pages, emojis : Generics.DefaultPaginationEmojis); } else { // There are no reminders. await ctx.Channel.SendMessageAsync( embed : Generics.GenericEmbedTemplate( color: Generics.NeutralColor, description: Generics.NeutralDirectResponseTemplate( mention: ctx.Member.Mention, body: "there are no reminders."), thumbnail: Generics.URL_SPEECH_BUBBLE, title: "Reminders")); } }