mirror of
https://gitlab.com/Kwoth/nadekobot.git
synced 2025-09-12 02:08:27 -04:00
Fixed some crashes in response strings source generator, reorganized more submodules into their folders
This commit is contained in:
224
src/NadekoBot/Modules/Utility/Remind/RemindCommands.cs
Normal file
224
src/NadekoBot/Modules/Utility/Remind/RemindCommands.cs
Normal file
@@ -0,0 +1,224 @@
|
||||
#nullable disable
|
||||
using Humanizer.Localisation;
|
||||
using NadekoBot.Db;
|
||||
using NadekoBot.Modules.Administration.Services;
|
||||
using NadekoBot.Modules.Utility.Services;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
|
||||
namespace NadekoBot.Modules.Utility;
|
||||
|
||||
public partial class Utility
|
||||
{
|
||||
[Group]
|
||||
public partial class RemindCommands : NadekoSubmodule<RemindService>
|
||||
{
|
||||
public enum MeOrHere
|
||||
{
|
||||
Me,
|
||||
Here
|
||||
}
|
||||
|
||||
public enum Server
|
||||
{
|
||||
Server = int.MinValue,
|
||||
Srvr = int.MinValue,
|
||||
Serv = int.MinValue,
|
||||
S = int.MinValue
|
||||
}
|
||||
|
||||
private readonly DbService _db;
|
||||
private readonly GuildTimezoneService _tz;
|
||||
|
||||
public RemindCommands(DbService db, GuildTimezoneService tz)
|
||||
{
|
||||
_db = db;
|
||||
_tz = tz;
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[Priority(1)]
|
||||
public async partial Task Remind(MeOrHere meorhere, [Leftover] string remindString)
|
||||
{
|
||||
if (!_service.TryParseRemindMessage(remindString, out var remindData))
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.remind_invalid);
|
||||
return;
|
||||
}
|
||||
|
||||
ulong target;
|
||||
target = meorhere == MeOrHere.Me ? ctx.User.Id : ctx.Channel.Id;
|
||||
if (!await RemindInternal(target,
|
||||
meorhere == MeOrHere.Me || ctx.Guild is null,
|
||||
remindData.Time,
|
||||
remindData.What)) await ReplyErrorLocalizedAsync(strs.remind_too_long);
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.ManageMessages)]
|
||||
[Priority(0)]
|
||||
public async partial Task Remind(ITextChannel channel, [Leftover] string remindString)
|
||||
{
|
||||
var perms = ((IGuildUser)ctx.User).GetPermissions(channel);
|
||||
if (!perms.SendMessages || !perms.ViewChannel)
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.cant_read_or_send);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_service.TryParseRemindMessage(remindString, out var remindData))
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.remind_invalid);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (!await RemindInternal(channel.Id, false, remindData.Time, remindData.What))
|
||||
await ReplyErrorLocalizedAsync(strs.remind_too_long);
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
[Priority(0)]
|
||||
public partial Task RemindList(Server _, int page = 1)
|
||||
=> RemindListInternal(page, true);
|
||||
|
||||
[Cmd]
|
||||
[Priority(1)]
|
||||
public partial Task RemindList(int page = 1)
|
||||
=> RemindListInternal(page, false);
|
||||
|
||||
private async Task RemindListInternal(int page, bool isServer)
|
||||
{
|
||||
if (--page < 0)
|
||||
return;
|
||||
|
||||
var embed = _eb.Create()
|
||||
.WithOkColor()
|
||||
.WithTitle(GetText(isServer ? strs.reminder_server_list : strs.reminder_list));
|
||||
|
||||
List<Reminder> rems;
|
||||
await using (var uow = _db.GetDbContext())
|
||||
{
|
||||
if (isServer)
|
||||
rems = uow.Reminders.RemindersForServer(ctx.Guild.Id, page).ToList();
|
||||
else
|
||||
rems = uow.Reminders.RemindersFor(ctx.User.Id, page).ToList();
|
||||
}
|
||||
|
||||
if (rems.Any())
|
||||
{
|
||||
var i = 0;
|
||||
foreach (var rem in rems)
|
||||
{
|
||||
var when = rem.When;
|
||||
var diff = when - DateTime.UtcNow;
|
||||
embed.AddField(
|
||||
$"#{++i + (page * 10)} {rem.When:HH:mm yyyy-MM-dd} UTC "
|
||||
+ $"(in {diff.Humanize(2, minUnit: TimeUnit.Minute, culture: Culture)})",
|
||||
$@"`Target:` {(rem.IsPrivate ? "DM" : "Channel")}
|
||||
`TargetId:` {rem.ChannelId}
|
||||
`Message:` {rem.Message?.TrimTo(50)}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
embed.WithDescription(GetText(strs.reminders_none));
|
||||
}
|
||||
|
||||
embed.AddPaginatedFooter(page + 1, null);
|
||||
await ctx.Channel.EmbedAsync(embed);
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
[Priority(0)]
|
||||
public partial Task RemindDelete(Server _, int index)
|
||||
=> RemindDelete(index, true);
|
||||
|
||||
[Cmd]
|
||||
[Priority(1)]
|
||||
public partial Task RemindDelete(int index)
|
||||
=> RemindDelete(index, false);
|
||||
|
||||
private async Task RemindDelete(int index, bool isServer)
|
||||
{
|
||||
if (--index < 0)
|
||||
return;
|
||||
|
||||
Reminder rem = null;
|
||||
await using (var uow = _db.GetDbContext())
|
||||
{
|
||||
var rems = isServer
|
||||
? uow.Reminders.RemindersForServer(ctx.Guild.Id, index / 10).ToList()
|
||||
: uow.Reminders.RemindersFor(ctx.User.Id, index / 10).ToList();
|
||||
|
||||
var pageIndex = index % 10;
|
||||
if (rems.Count > pageIndex)
|
||||
{
|
||||
rem = rems[pageIndex];
|
||||
uow.Reminders.Remove(rem);
|
||||
uow.SaveChanges();
|
||||
}
|
||||
}
|
||||
|
||||
if (rem is null)
|
||||
await ReplyErrorLocalizedAsync(strs.reminder_not_exist);
|
||||
else
|
||||
await ReplyConfirmLocalizedAsync(strs.reminder_deleted(index + 1));
|
||||
}
|
||||
|
||||
private async Task<bool> RemindInternal(
|
||||
ulong targetId,
|
||||
bool isPrivate,
|
||||
TimeSpan ts,
|
||||
string message)
|
||||
{
|
||||
var time = DateTime.UtcNow + ts;
|
||||
|
||||
if (ts > TimeSpan.FromDays(60))
|
||||
return false;
|
||||
|
||||
if (ctx.Guild is not null)
|
||||
{
|
||||
var perms = ((IGuildUser)ctx.User).GetPermissions((IGuildChannel)ctx.Channel);
|
||||
if (!perms.MentionEveryone) message = message.SanitizeAllMentions();
|
||||
}
|
||||
|
||||
var rem = new Reminder
|
||||
{
|
||||
ChannelId = targetId,
|
||||
IsPrivate = isPrivate,
|
||||
When = time,
|
||||
Message = message,
|
||||
UserId = ctx.User.Id,
|
||||
ServerId = ctx.Guild?.Id ?? 0
|
||||
};
|
||||
|
||||
await using (var uow = _db.GetDbContext())
|
||||
{
|
||||
uow.Reminders.Add(rem);
|
||||
await uow.SaveChangesAsync();
|
||||
}
|
||||
|
||||
var gTime = ctx.Guild is null ? time : TimeZoneInfo.ConvertTime(time, _tz.GetTimeZoneOrUtc(ctx.Guild.Id));
|
||||
try
|
||||
{
|
||||
await SendConfirmAsync("⏰ "
|
||||
+ GetText(strs.remind(
|
||||
Format.Bold(!isPrivate ? $"<#{targetId}>" : ctx.User.Username),
|
||||
Format.Bold(message),
|
||||
ts.Humanize(3, minUnit: TimeUnit.Second, culture: Culture),
|
||||
gTime,
|
||||
gTime)));
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
166
src/NadekoBot/Modules/Utility/Remind/RemindService.cs
Normal file
166
src/NadekoBot/Modules/Utility/Remind/RemindService.cs
Normal file
@@ -0,0 +1,166 @@
|
||||
#nullable disable
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace NadekoBot.Modules.Utility.Services;
|
||||
|
||||
public class RemindService : INService
|
||||
{
|
||||
private readonly Regex _regex =
|
||||
new(
|
||||
@"^(?:in\s?)?\s*(?:(?<mo>\d+)(?:\s?(?:months?|mos?),?))?(?:(?:\sand\s|\s*)?(?<w>\d+)(?:\s?(?:weeks?|w),?))?(?:(?:\sand\s|\s*)?(?<d>\d+)(?:\s?(?:days?|d),?))?(?:(?:\sand\s|\s*)?(?<h>\d+)(?:\s?(?:hours?|h),?))?(?:(?:\sand\s|\s*)?(?<m>\d+)(?:\s?(?:minutes?|mins?|m),?))?\s+(?:to:?\s+)?(?<what>(?:\r\n|[\r\n]|.)+)",
|
||||
RegexOptions.Compiled | RegexOptions.Multiline);
|
||||
|
||||
private readonly DiscordSocketClient _client;
|
||||
private readonly DbService _db;
|
||||
private readonly IBotCredentials _creds;
|
||||
private readonly IEmbedBuilderService _eb;
|
||||
|
||||
public RemindService(
|
||||
DiscordSocketClient client,
|
||||
DbService db,
|
||||
IBotCredentials creds,
|
||||
IEmbedBuilderService eb)
|
||||
{
|
||||
_client = client;
|
||||
_db = db;
|
||||
_creds = creds;
|
||||
_eb = eb;
|
||||
_ = StartReminderLoop();
|
||||
}
|
||||
|
||||
private async Task StartReminderLoop()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
await Task.Delay(15000);
|
||||
try
|
||||
{
|
||||
var now = DateTime.UtcNow;
|
||||
var reminders = await GetRemindersBeforeAsync(now);
|
||||
if (reminders.Count == 0)
|
||||
continue;
|
||||
|
||||
Log.Information($"Executing {reminders.Count} reminders.");
|
||||
|
||||
// make groups of 5, with 1.5 second inbetween each one to ensure against ratelimits
|
||||
foreach (var group in reminders.Chunk(5))
|
||||
{
|
||||
var executedReminders = group.ToList();
|
||||
await executedReminders.Select(ReminderTimerAction).WhenAll();
|
||||
await RemoveReminders(executedReminders);
|
||||
await Task.Delay(1500);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warning($"Error in reminder loop: {ex.Message}");
|
||||
Log.Warning(ex.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task RemoveReminders(List<Reminder> reminders)
|
||||
{
|
||||
await using var uow = _db.GetDbContext();
|
||||
uow.Set<Reminder>().RemoveRange(reminders);
|
||||
|
||||
await uow.SaveChangesAsync();
|
||||
}
|
||||
|
||||
private Task<List<Reminder>> GetRemindersBeforeAsync(DateTime now)
|
||||
{
|
||||
using var uow = _db.GetDbContext();
|
||||
return uow.Reminders
|
||||
.FromSqlInterpolated(
|
||||
$"select * from reminders where ((serverid >> 22) % {_creds.TotalShards}) == {_client.ShardId} and \"when\" < {now};")
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public bool TryParseRemindMessage(string input, out RemindObject obj)
|
||||
{
|
||||
var m = _regex.Match(input);
|
||||
|
||||
obj = default;
|
||||
if (m.Length == 0) return false;
|
||||
|
||||
var values = new Dictionary<string, int>();
|
||||
|
||||
var what = m.Groups["what"].Value;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(what))
|
||||
{
|
||||
Log.Warning("No message provided for the reminder.");
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (var groupName in _regex.GetGroupNames())
|
||||
{
|
||||
if (groupName is "0" or "what") continue;
|
||||
if (string.IsNullOrWhiteSpace(m.Groups[groupName].Value))
|
||||
{
|
||||
values[groupName] = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!int.TryParse(m.Groups[groupName].Value, out var value))
|
||||
{
|
||||
Log.Warning($"Reminder regex group {groupName} has invalid value.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (value < 1)
|
||||
{
|
||||
Log.Warning("Reminder time value has to be an integer greater than 0.");
|
||||
return false;
|
||||
}
|
||||
|
||||
values[groupName] = value;
|
||||
}
|
||||
|
||||
var ts = new TimeSpan((30 * values["mo"]) + (7 * values["w"]) + values["d"], values["h"], values["m"], 0);
|
||||
|
||||
obj = new() { Time = ts, What = what };
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private async Task ReminderTimerAction(Reminder r)
|
||||
{
|
||||
try
|
||||
{
|
||||
IMessageChannel ch;
|
||||
if (r.IsPrivate)
|
||||
{
|
||||
var user = _client.GetUser(r.ChannelId);
|
||||
if (user is null)
|
||||
return;
|
||||
ch = await user.CreateDMChannelAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
ch = _client.GetGuild(r.ServerId)?.GetTextChannel(r.ChannelId);
|
||||
}
|
||||
|
||||
if (ch is null)
|
||||
return;
|
||||
|
||||
await ch.EmbedAsync(_eb.Create()
|
||||
.WithOkColor()
|
||||
.WithTitle("Reminder")
|
||||
.AddField("Created At",
|
||||
r.DateAdded.HasValue ? r.DateAdded.Value.ToLongDateString() : "?")
|
||||
.AddField("By",
|
||||
(await ch.GetUserAsync(r.UserId))?.ToString() ?? r.UserId.ToString()),
|
||||
r.Message);
|
||||
}
|
||||
catch (Exception ex) { Log.Information(ex.Message + $"({r.Id})"); }
|
||||
}
|
||||
|
||||
public struct RemindObject
|
||||
{
|
||||
public string What { get; set; }
|
||||
public TimeSpan Time { get; set; }
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user