Updated Utility module
This commit is contained in:
parent
0466701e28
commit
4732254805
29 changed files with 470 additions and 64 deletions
314
src/EllieBot/Modules/Utility/Ai/AiAssistantService.cs
Normal file
314
src/EllieBot/Modules/Utility/Ai/AiAssistantService.cs
Normal file
|
@ -0,0 +1,314 @@
|
||||||
|
using EllieBot.Common.ModuleBehaviors;
|
||||||
|
using EllieBot.Modules.Administration;
|
||||||
|
using EllieBot.Modules.Games.Services;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Http.Json;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.Json;
|
||||||
|
using JsonSerializer = System.Text.Json.JsonSerializer;
|
||||||
|
|
||||||
|
namespace EllieBot.Modules.Utility;
|
||||||
|
|
||||||
|
public enum GetCommandErrorResult
|
||||||
|
{
|
||||||
|
RateLimitHit,
|
||||||
|
NotAuthorized,
|
||||||
|
Disregard,
|
||||||
|
Unknown
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class AiAssistantService
|
||||||
|
: IAiAssistantService, IReadyExecutor,
|
||||||
|
IExecOnMessage,
|
||||||
|
IEService
|
||||||
|
{
|
||||||
|
private IReadOnlyCollection<AiCommandModel> _commands = [];
|
||||||
|
|
||||||
|
private readonly IBotStrings _strings;
|
||||||
|
private readonly IHttpClientFactory _httpFactory;
|
||||||
|
private readonly CommandService _cmds;
|
||||||
|
private readonly IBotCredsProvider _credsProvider;
|
||||||
|
private readonly DiscordSocketClient _client;
|
||||||
|
private readonly ICommandHandler _cmdHandler;
|
||||||
|
private readonly BotConfigService _bcs;
|
||||||
|
private readonly IMessageSenderService _sender;
|
||||||
|
|
||||||
|
private readonly JsonSerializerOptions _serializerOptions = new();
|
||||||
|
private readonly IPermissionChecker _permChecker;
|
||||||
|
private readonly IBotCache _botCache;
|
||||||
|
private readonly ChatterBotService _cbs;
|
||||||
|
|
||||||
|
public AiAssistantService(
|
||||||
|
DiscordSocketClient client,
|
||||||
|
IBotStrings strings,
|
||||||
|
IHttpClientFactory httpFactory,
|
||||||
|
CommandService cmds,
|
||||||
|
IBotCredsProvider credsProvider,
|
||||||
|
ICommandHandler cmdHandler,
|
||||||
|
BotConfigService bcs,
|
||||||
|
IPermissionChecker permChecker,
|
||||||
|
IBotCache botCache,
|
||||||
|
ChatterBotService cbs,
|
||||||
|
IMessageSenderService sender)
|
||||||
|
{
|
||||||
|
_client = client;
|
||||||
|
_strings = strings;
|
||||||
|
_httpFactory = httpFactory;
|
||||||
|
_cmds = cmds;
|
||||||
|
_credsProvider = credsProvider;
|
||||||
|
_cmdHandler = cmdHandler;
|
||||||
|
_bcs = bcs;
|
||||||
|
_sender = sender;
|
||||||
|
_permChecker = permChecker;
|
||||||
|
_botCache = botCache;
|
||||||
|
_cbs = cbs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<OneOf.OneOf<EllieCommandCallModel, GetCommandErrorResult>> TryGetCommandAsync(
|
||||||
|
ulong userId,
|
||||||
|
string prompt,
|
||||||
|
IReadOnlyCollection<AiCommandModel> commands,
|
||||||
|
string prefix)
|
||||||
|
{
|
||||||
|
using var content = new StringContent(
|
||||||
|
JsonSerializer.Serialize(new
|
||||||
|
{
|
||||||
|
query = prompt,
|
||||||
|
commands = commands.ToDictionary(x => x.Name,
|
||||||
|
x => new AiCommandModel()
|
||||||
|
{
|
||||||
|
Desc = string.Format(x.Desc ?? "", prefix),
|
||||||
|
Params = x.Params,
|
||||||
|
Name = x.Name
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
Encoding.UTF8,
|
||||||
|
"application/json"
|
||||||
|
);
|
||||||
|
|
||||||
|
using var request = new HttpRequestMessage();
|
||||||
|
request.Method = HttpMethod.Post;
|
||||||
|
// request.RequestUri = new("https://nai.nadeko.bot/get-command");
|
||||||
|
request.RequestUri = new("https://nai.nadeko.bot/get-command");
|
||||||
|
request.Content = content;
|
||||||
|
|
||||||
|
var creds = _credsProvider.GetCreds();
|
||||||
|
|
||||||
|
request.Headers.TryAddWithoutValidation("x-auth-token", creds.EllieAiToken);
|
||||||
|
request.Headers.TryAddWithoutValidation("x-auth-userid", userId.ToString());
|
||||||
|
|
||||||
|
|
||||||
|
using var client = _httpFactory.CreateClient();
|
||||||
|
|
||||||
|
// todo customize according to the bot's config
|
||||||
|
// - CurrencyName
|
||||||
|
// -
|
||||||
|
|
||||||
|
using var response = await client.SendAsync(request);
|
||||||
|
|
||||||
|
if (response.StatusCode == HttpStatusCode.TooManyRequests)
|
||||||
|
{
|
||||||
|
return GetCommandErrorResult.RateLimitHit;
|
||||||
|
}
|
||||||
|
else if (response.StatusCode == HttpStatusCode.Unauthorized)
|
||||||
|
{
|
||||||
|
return GetCommandErrorResult.NotAuthorized;
|
||||||
|
}
|
||||||
|
|
||||||
|
var funcModel = await response.Content.ReadFromJsonAsync<CommandPromptResultModel>();
|
||||||
|
|
||||||
|
|
||||||
|
if (funcModel?.Name == "disregard")
|
||||||
|
{
|
||||||
|
Log.Warning("Disregarding the prompt: {Prompt}", prompt);
|
||||||
|
return GetCommandErrorResult.Disregard;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (funcModel is null)
|
||||||
|
return GetCommandErrorResult.Unknown;
|
||||||
|
|
||||||
|
var comModel = new EllieCommandCallModel()
|
||||||
|
{
|
||||||
|
Name = funcModel.Name,
|
||||||
|
Arguments = funcModel.Arguments
|
||||||
|
.OrderBy(param => _commands.FirstOrDefault(x => x.Name == funcModel.Name)
|
||||||
|
?.Params
|
||||||
|
.Select((x, i) => (x, i))
|
||||||
|
.Where(x => x.x.Name == param.Key)
|
||||||
|
.Select(x => x.i)
|
||||||
|
.FirstOrDefault())
|
||||||
|
.Select(x => x.Value)
|
||||||
|
.Where(x => !string.IsNullOrWhiteSpace(x))
|
||||||
|
.ToArray(),
|
||||||
|
Remaining = funcModel.Remaining
|
||||||
|
};
|
||||||
|
|
||||||
|
return comModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IReadOnlyCollection<AiCommandModel> GetCommands()
|
||||||
|
=> _commands;
|
||||||
|
|
||||||
|
public Task OnReadyAsync()
|
||||||
|
{
|
||||||
|
var cmds = _cmds.Commands
|
||||||
|
.Select(x => (MethodName: x.Summary, CommandName: x.Aliases[0]))
|
||||||
|
.Where(x => !x.MethodName.Contains("///"))
|
||||||
|
.Distinct()
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
var funcs = new List<AiCommandModel>();
|
||||||
|
foreach (var (method, cmd) in cmds)
|
||||||
|
{
|
||||||
|
var commandStrings = _strings.GetCommandStrings(method);
|
||||||
|
|
||||||
|
if (commandStrings is null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
funcs.Add(new()
|
||||||
|
{
|
||||||
|
Name = cmd,
|
||||||
|
Desc = commandStrings?.Desc?.Replace("currency", "flowers") ?? string.Empty,
|
||||||
|
Params = commandStrings?.Params.FirstOrDefault()
|
||||||
|
?.Select(x => new AiCommandParamModel()
|
||||||
|
{
|
||||||
|
Desc = x.Value.Desc,
|
||||||
|
Name = x.Key,
|
||||||
|
})
|
||||||
|
.ToArray()
|
||||||
|
?? []
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_commands = funcs;
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Priority
|
||||||
|
=> 2;
|
||||||
|
|
||||||
|
public async Task<bool> ExecOnMessageAsync(IGuild guild, IUserMessage msg)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(_credsProvider.GetCreds().EllieAiToken))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (guild is not SocketGuild sg)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var nadekoId = _client.CurrentUser.Id;
|
||||||
|
|
||||||
|
var channel = msg.Channel as ITextChannel;
|
||||||
|
if (channel is null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var normalMention = $"<@{nadekoId}> ";
|
||||||
|
var nickMention = $"<@!{nadekoId}> ";
|
||||||
|
string query;
|
||||||
|
if (msg.Content.StartsWith(normalMention, StringComparison.InvariantCulture))
|
||||||
|
query = msg.Content[normalMention.Length..].Trim();
|
||||||
|
else if (msg.Content.StartsWith(nickMention, StringComparison.InvariantCulture))
|
||||||
|
query = msg.Content[nickMention.Length..].Trim();
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var success = await TryExecuteAiCommand(guild, msg, channel, query);
|
||||||
|
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> TryExecuteAiCommand(
|
||||||
|
IGuild guild,
|
||||||
|
IUserMessage msg,
|
||||||
|
ITextChannel channel,
|
||||||
|
string query)
|
||||||
|
{
|
||||||
|
// check permissions
|
||||||
|
var pcResult = await _permChecker.CheckPermsAsync(
|
||||||
|
guild,
|
||||||
|
msg.Channel,
|
||||||
|
msg.Author,
|
||||||
|
"Utility",
|
||||||
|
"prompt"
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!pcResult.IsAllowed)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
using var _ = channel.EnterTypingState();
|
||||||
|
|
||||||
|
var result = await TryGetCommandAsync(msg.Author.Id, query, _commands, _cmdHandler.GetPrefix(guild.Id));
|
||||||
|
|
||||||
|
if (result.TryPickT0(out var model, out var error))
|
||||||
|
{
|
||||||
|
if (model.Name == ".ai_chat")
|
||||||
|
{
|
||||||
|
if (guild is not SocketGuild sg)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var sess = _cbs.GetOrCreateSession(guild.Id);
|
||||||
|
if (sess is null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
await _cbs.RunChatterBot(sg, msg, channel, sess, query);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var commandString = GetCommandString(model);
|
||||||
|
|
||||||
|
var msgTask = _sender.Response(channel)
|
||||||
|
.Embed(_sender.CreateEmbed()
|
||||||
|
.WithOkColor()
|
||||||
|
.WithAuthor(msg.Author.GlobalName,
|
||||||
|
msg.Author.RealAvatarUrl().ToString())
|
||||||
|
.WithDescription(commandString))
|
||||||
|
.SendAsync();
|
||||||
|
|
||||||
|
|
||||||
|
await _cmdHandler.TryRunCommand(
|
||||||
|
(SocketGuild)guild,
|
||||||
|
(ISocketMessageChannel)channel,
|
||||||
|
new DoAsUserMessage((SocketUserMessage)msg, msg.Author, commandString));
|
||||||
|
|
||||||
|
var cmdMsg = await msgTask;
|
||||||
|
|
||||||
|
cmdMsg.DeleteAfter(5);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error == GetCommandErrorResult.Disregard)
|
||||||
|
{
|
||||||
|
// await msg.ErrorAsync();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var key = new TypedKey<bool>($"sub_error:{msg.Author.Id}:{error}");
|
||||||
|
|
||||||
|
if (!await _botCache.AddAsync(key, true, TimeSpan.FromDays(1), overwrite: false))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var errorMsg = error switch
|
||||||
|
{
|
||||||
|
GetCommandErrorResult.RateLimitHit
|
||||||
|
=> "You've spent your daily requests quota.",
|
||||||
|
GetCommandErrorResult.NotAuthorized
|
||||||
|
=> "In order to use this command you have to have a 5$ or higher subscription at <https://patreon.com/nadekobot>",
|
||||||
|
GetCommandErrorResult.Unknown
|
||||||
|
=> "The service is temporarily unavailable.",
|
||||||
|
_ => throw new ArgumentOutOfRangeException()
|
||||||
|
};
|
||||||
|
|
||||||
|
await _sender.Response(channel)
|
||||||
|
.Error(errorMsg)
|
||||||
|
.SendAsync();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetCommandString(EllieCommandCallModel res)
|
||||||
|
=> $"{_bcs.Data.Prefix}{res.Name} {res.Arguments.Select((x, i) => GetParamString(x, i + 1 == res.Arguments.Count)).Join(" ")}";
|
||||||
|
|
||||||
|
private static string GetParamString(string val, bool isLast)
|
||||||
|
=> isLast ? val : "\"" + val + "\"";
|
||||||
|
}
|
15
src/EllieBot/Modules/Utility/Ai/AiCommandModel.cs
Normal file
15
src/EllieBot/Modules/Utility/Ai/AiCommandModel.cs
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace EllieBot.Modules.Utility;
|
||||||
|
|
||||||
|
public sealed class AiCommandModel
|
||||||
|
{
|
||||||
|
[JsonPropertyName("name")]
|
||||||
|
public required string Name { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("desc")]
|
||||||
|
public required string Desc { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("params")]
|
||||||
|
public required IReadOnlyList<AiCommandParamModel> Params { get; set; }
|
||||||
|
}
|
12
src/EllieBot/Modules/Utility/Ai/AiCommandParamModel.cs
Normal file
12
src/EllieBot/Modules/Utility/Ai/AiCommandParamModel.cs
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace EllieBot.Modules.Utility;
|
||||||
|
|
||||||
|
public sealed class AiCommandParamModel
|
||||||
|
{
|
||||||
|
[JsonPropertyName("name")]
|
||||||
|
public required string Name { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("desc")]
|
||||||
|
public required string Desc { get; set; }
|
||||||
|
}
|
16
src/EllieBot/Modules/Utility/Ai/CommandPromptResultModel.cs
Normal file
16
src/EllieBot/Modules/Utility/Ai/CommandPromptResultModel.cs
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace EllieBot.Modules.Utility;
|
||||||
|
|
||||||
|
public sealed class CommandPromptResultModel
|
||||||
|
{
|
||||||
|
[JsonPropertyName("name")]
|
||||||
|
public required string Name { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("arguments")]
|
||||||
|
public required Dictionary<string, string> Arguments { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("remaining")]
|
||||||
|
[JsonConverter(typeof(NumberToStringConverter))]
|
||||||
|
public required string Remaining { get; set; }
|
||||||
|
}
|
8
src/EllieBot/Modules/Utility/Ai/EllieCommandCallModel.cs
Normal file
8
src/EllieBot/Modules/Utility/Ai/EllieCommandCallModel.cs
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
namespace EllieBot.Modules.Utility;
|
||||||
|
|
||||||
|
public sealed class EllieCommandCallModel
|
||||||
|
{
|
||||||
|
public required string Name { get; set; }
|
||||||
|
public required IReadOnlyList<string> Arguments { get; set; }
|
||||||
|
public required string Remaining { get; set; }
|
||||||
|
}
|
20
src/EllieBot/Modules/Utility/Ai/IAiAssistantService.cs
Normal file
20
src/EllieBot/Modules/Utility/Ai/IAiAssistantService.cs
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
using OneOf;
|
||||||
|
|
||||||
|
namespace EllieBot.Modules.Utility;
|
||||||
|
|
||||||
|
public interface IAiAssistantService
|
||||||
|
{
|
||||||
|
Task<OneOf<EllieCommandCallModel, GetCommandErrorResult>> TryGetCommandAsync(
|
||||||
|
ulong userId,
|
||||||
|
string prompt,
|
||||||
|
IReadOnlyCollection<AiCommandModel> commands,
|
||||||
|
string prefix);
|
||||||
|
|
||||||
|
IReadOnlyCollection<AiCommandModel> GetCommands();
|
||||||
|
|
||||||
|
Task<bool> TryExecuteAiCommand(
|
||||||
|
IGuild guild,
|
||||||
|
IUserMessage msg,
|
||||||
|
ITextChannel channel,
|
||||||
|
string query);
|
||||||
|
}
|
23
src/EllieBot/Modules/Utility/Ai/UtilityCommands.cs
Normal file
23
src/EllieBot/Modules/Utility/Ai/UtilityCommands.cs
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
using EllieBot.Modules.Administration;
|
||||||
|
|
||||||
|
namespace EllieBot.Modules.Utility;
|
||||||
|
|
||||||
|
public partial class UtilityCommands
|
||||||
|
{
|
||||||
|
public class PromptCommands : EllieModule<IAiAssistantService>
|
||||||
|
{
|
||||||
|
[Cmd]
|
||||||
|
[RequireContext(ContextType.Guild)]
|
||||||
|
public async Task Prompt([Leftover] string query)
|
||||||
|
{
|
||||||
|
await ctx.Channel.TriggerTypingAsync();
|
||||||
|
var res = await _service.TryExecuteAiCommand(ctx.Guild, ctx.Message, (ITextChannel)ctx.Channel, query);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetCommandString(EllieCommandCallModel res)
|
||||||
|
=> $"{_bcs.Data.Prefix}{res.Name} {res.Arguments.Select((x, i) => GetParamString(x, i + 1 == res.Arguments.Count)).Join(" ")}";
|
||||||
|
|
||||||
|
private static string GetParamString(string val, bool isLast)
|
||||||
|
=> isLast ? val : "\"" + val + "\"";
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
#nullable disable
|
#nullable disable
|
||||||
namespace EllieBot.Modules.Utility;
|
namespace EllieBot.Modules.Utility;
|
||||||
|
|
||||||
public partial class Utility
|
public partial class Utility
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
#nullable disable
|
#nullable disable
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using EllieBot.Modules.Patronage;
|
using EllieBot.Modules.Patronage;
|
||||||
|
|
||||||
|
@ -144,9 +144,9 @@ public partial class Utility
|
||||||
true)
|
true)
|
||||||
.WithOkColor();
|
.WithOkColor();
|
||||||
|
|
||||||
var patron = await _ps.GetPatronAsync(user.Id);
|
var mPatron = await _ps.GetPatronAsync(user.Id);
|
||||||
|
|
||||||
if (patron.Tier != PatronTier.None)
|
if (mPatron is {} patron && patron.Tier != PatronTier.None)
|
||||||
{
|
{
|
||||||
embed.WithFooter(patron.Tier switch
|
embed.WithFooter(patron.Tier switch
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
#nullable disable
|
#nullable disable
|
||||||
using CommandLine;
|
using CommandLine;
|
||||||
|
|
||||||
namespace EllieBot.Modules.Utility.Services;
|
namespace EllieBot.Modules.Utility.Services;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
#nullable disable warnings
|
#nullable disable warnings
|
||||||
using LinqToDB;
|
using LinqToDB;
|
||||||
using LinqToDB.EntityFrameworkCore;
|
using LinqToDB.EntityFrameworkCore;
|
||||||
using EllieBot.Common.Yml;
|
using EllieBot.Common.Yml;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
#nullable disable warnings
|
#nullable disable warnings
|
||||||
using LinqToDB;
|
using LinqToDB;
|
||||||
using LinqToDB.EntityFrameworkCore;
|
using LinqToDB.EntityFrameworkCore;
|
||||||
using EllieBot.Db.Models;
|
using EllieBot.Db.Models;
|
||||||
|
|
|
@ -113,10 +113,9 @@ public partial class Utility
|
||||||
foreach (var rem in rems)
|
foreach (var rem in rems)
|
||||||
{
|
{
|
||||||
var when = rem.When;
|
var when = rem.When;
|
||||||
var diff = when - DateTime.UtcNow;
|
|
||||||
embed.AddField(
|
embed.AddField(
|
||||||
$"#{++i + (page * 10)} {rem.When:HH:mm yyyy-MM-dd} UTC "
|
$"#{++i + (page * 10)} {rem.When:HH:mm yyyy-MM-dd} UTC "
|
||||||
+ $"(in {diff.ToPrettyStringHm()})",
|
+ $"{TimestampTag.FromDateTime(when)}",
|
||||||
$@"`Target:` {(rem.IsPrivate ? "DM" : "Channel")}
|
$@"`Target:` {(rem.IsPrivate ? "DM" : "Channel")}
|
||||||
`TargetId:` {rem.ChannelId}
|
`TargetId:` {rem.ChannelId}
|
||||||
`Message:` {rem.Message?.TrimTo(50)}");
|
`Message:` {rem.Message?.TrimTo(50)}");
|
||||||
|
@ -203,16 +202,15 @@ public partial class Utility
|
||||||
await uow.SaveChangesAsync();
|
await uow.SaveChangesAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
var gTime = ctx.Guild is null ? time : TimeZoneInfo.ConvertTime(time, _tz.GetTimeZoneOrUtc(ctx.Guild.Id));
|
// var gTime = ctx.Guild is null ? time : TimeZoneInfo.ConvertTime(time, _tz.GetTimeZoneOrUtc(ctx.Guild.Id));
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await Response()
|
await Response()
|
||||||
.Confirm($"\u23f0 {GetText(strs.remind(
|
.Confirm($"\u23f0 {GetText(strs.remind2(
|
||||||
Format.Bold(!isPrivate ? $"<#{targetId}>" : ctx.User.Username),
|
Format.Bold(!isPrivate ? $"<#{targetId}>" : ctx.User.Username),
|
||||||
Format.Bold(message),
|
Format.Bold(message),
|
||||||
ts.ToPrettyStringHm(),
|
TimestampTag.FromDateTime(DateTime.UtcNow.Add(ts), TimestampTagStyles.Relative),
|
||||||
gTime,
|
TimestampTag.FormatFromDateTime(time, TimestampTagStyles.ShortDateTime)))}")
|
||||||
gTime))}")
|
|
||||||
.SendAsync();
|
.SendAsync();
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
#nullable disable
|
#nullable disable
|
||||||
using EllieBot.Db.Models;
|
using EllieBot.Db.Models;
|
||||||
|
|
||||||
namespace EllieBot.Modules.Utility.Services;
|
namespace EllieBot.Modules.Utility.Services;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
#nullable disable
|
#nullable disable
|
||||||
using EllieBot.Modules.Utility.Services;
|
using EllieBot.Modules.Utility.Services;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
#nullable disable
|
#nullable disable
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
|
||||||
namespace EllieBot.Modules.Utility.Common;
|
namespace EllieBot.Modules.Utility.Common;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// ReSharper disable InconsistentNaming
|
// ReSharper disable InconsistentNaming
|
||||||
#nullable disable
|
#nullable disable
|
||||||
namespace EllieBot.Modules.Utility;
|
namespace EllieBot.Modules.Utility;
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
#nullable disable
|
#nullable disable
|
||||||
namespace EllieBot.Modules.Utility.Common.Exceptions;
|
namespace EllieBot.Modules.Utility.Common.Exceptions;
|
||||||
|
|
||||||
public class StreamRoleNotFoundException : Exception
|
public class StreamRoleNotFoundException : Exception
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
#nullable disable
|
#nullable disable
|
||||||
namespace EllieBot.Modules.Utility.Common.Exceptions;
|
namespace EllieBot.Modules.Utility.Common.Exceptions;
|
||||||
|
|
||||||
public class StreamRolePermissionException : Exception
|
public class StreamRolePermissionException : Exception
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
#nullable disable
|
#nullable disable
|
||||||
namespace EllieBot.Modules.Utility.Common;
|
namespace EllieBot.Modules.Utility.Common;
|
||||||
|
|
||||||
public enum StreamRoleListType
|
public enum StreamRoleListType
|
||||||
|
|
Loading…
Reference in a new issue