Updated common files

This commit is contained in:
Toastie (DCS Team) 2024-06-26 19:44:07 +12:00
parent b045015efb
commit a63889b2f3
Signed by: toastie_t0ast
GPG key ID: 27F3B6855AFD40A4
61 changed files with 188 additions and 227 deletions

View file

@ -1,4 +1,4 @@
#nullable disable #nullable disable
namespace EllieBot.Common; namespace EllieBot.Common;
public enum AddRemove public enum AddRemove

View file

@ -1,4 +1,4 @@
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
namespace EllieBot.Common.Attributes; namespace EllieBot.Common.Attributes;

View file

@ -1,4 +1,4 @@
namespace EllieBot.Common.Attributes; namespace EllieBot.Common.Attributes;
[AttributeUsage(AttributeTargets.Method)] [AttributeUsage(AttributeTargets.Method)]
public sealed class EllieOptionsAttribute<TOption> : Attribute public sealed class EllieOptionsAttribute<TOption> : Attribute

View file

@ -1,4 +1,4 @@
#nullable disable #nullable disable
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
namespace EllieBot.Common; namespace EllieBot.Common;

View file

@ -1,4 +1,4 @@
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
namespace EllieBot.Common.Attributes; namespace EllieBot.Common.Attributes;

View file

@ -1,4 +1,4 @@
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
namespace EllieBot.Common.Attributes; namespace EllieBot.Common.Attributes;

View file

@ -1,4 +1,4 @@
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
namespace Discord; namespace Discord;

View file

@ -1,4 +1,4 @@
using EllieBot.Common.Yml; using EllieBot.Common.Yml;
using YamlDotNet.Serialization; using YamlDotNet.Serialization;
namespace EllieBot.Common.Attributes; namespace EllieBot.Common.Attributes;

View file

@ -1,4 +1,4 @@
namespace EllieBot.Common.Configs; namespace EllieBot.Common.Configs;
/// <summary> /// <summary>
/// Base interface for available config serializers /// Base interface for available config serializers

View file

@ -1,7 +1,7 @@
#nullable disable #nullable disable
using EllieBot.Common.Yml; using EllieBot.Common.Yml;
namespace Ellie.Common; namespace EllieBot.Common;
public sealed class Creds : IBotCredentials public sealed class Creds : IBotCredentials
{ {
@ -29,6 +29,15 @@ public sealed class Creds : IBotCredentials
""")] """)]
public int TotalShards { get; set; } public int TotalShards { get; set; }
[Comment("""
Pledge 5$ or more on https://patreon.com/elliebot and connect your discord account to Patreon.
Go to https://dashy.elliebot.net and login with your discord account
Go to the Keys page and click "Generate New Key" and copy it here
You and anyone else with the permission to run `.prompt` command will be able to use natural language to run bot's commands.
For example '@Bot how's the weather in Paris' will return the current weather in Paris as if you were to run `.weather Paris` command
""")]
public string EllieAiToken { get; set; }
[Comment( [Comment(
""" """
Login to https://console.cloud.google.com, create a new project, go to APIs & Services -> Library -> YouTube Data API and enable it. Login to https://console.cloud.google.com, create a new project, go to APIs & Services -> Library -> YouTube Data API and enable it.
@ -64,7 +73,7 @@ public sealed class Creds : IBotCredentials
[Comment("""Official cleverbot api key.""")] [Comment("""Official cleverbot api key.""")]
public string CleverbotApiKey { get; set; } public string CleverbotApiKey { get; set; }
[Comment(@"Official GPT-3 api key.")] [Comment(@"OpenAi api key.")]
public string Gpt3ApiKey { get; set; } public string Gpt3ApiKey { get; set; }
[Comment(""" [Comment("""
@ -270,3 +279,6 @@ public class GoogleApiConfig : IGoogleApiConfig
public string SearchId { get; init; } public string SearchId { get; init; }
public string ImageSearchId { get; init; } public string ImageSearchId { get; init; }
} }

View file

@ -1,4 +1,4 @@
using MessageType = Discord.MessageType; using MessageType = Discord.MessageType;
namespace EllieBot.Modules.Administration; namespace EllieBot.Modules.Administration;

View file

@ -1,4 +1,4 @@
#nullable disable #nullable disable
namespace EllieBot.Common; namespace EllieBot.Common;
public class DownloadTracker : IEService public class DownloadTracker : IEService

View file

@ -1,4 +1,4 @@
#nullable disable #nullable disable
namespace EllieBot.Common; namespace EllieBot.Common;
public interface IPlaceholderProvider public interface IPlaceholderProvider

View file

@ -1,4 +1,4 @@
namespace EllieBot; namespace EllieBot;
public abstract class EllieInteractionBase public abstract class EllieInteractionBase
{ {
@ -85,6 +85,9 @@ public abstract class EllieInteractionBase
public Task ExecuteOnActionAsync(SocketMessageComponent smc) public Task ExecuteOnActionAsync(SocketMessageComponent smc)
=> _onAction(smc); => _onAction(smc);
public void SetCompleted()
=> _interactionCompletedSource.TrySetResult(true);
} }
public sealed class EllieModalSubmitHandler public sealed class EllieModalSubmitHandler

View file

@ -1,4 +1,4 @@
namespace EllieBot; namespace EllieBot;
public sealed class EllieButtonInteractionHandler : EllieInteractionBase public sealed class EllieButtonInteractionHandler : EllieInteractionBase
{ {

View file

@ -7,7 +7,7 @@ namespace EllieBot.Common.JsonConverters;
public class CultureInfoConverter : JsonConverter<CultureInfo> public class CultureInfoConverter : JsonConverter<CultureInfo>
{ {
public override CultureInfo Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) public override CultureInfo Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
=> new(reader.GetString() ?? "en_US"); => new(reader.GetString() ?? "en-US");
public override void Write(Utf8JsonWriter writer, CultureInfo value, JsonSerializerOptions options) public override void Write(Utf8JsonWriter writer, CultureInfo value, JsonSerializerOptions options)
=> writer.WriteStringValue(value.Name); => writer.WriteStringValue(value.Name);

View file

@ -0,0 +1,30 @@
using System.Globalization;
using System.Text.Json;
using System.Text.Json.Serialization;
public class NumberToStringConverter : JsonConverter<object>
{
public override bool CanConvert(Type typeToConvert)
=> typeof(string) == typeToConvert;
public override object Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
switch (reader.TokenType)
{
case JsonTokenType.Number:
return reader.TryGetInt64(out var l)
? l.ToString()
: reader.GetDouble().ToString(CultureInfo.InvariantCulture);
case JsonTokenType.String:
return reader.GetString() ?? string.Empty;
default:
{
using var document = JsonDocument.ParseValue(ref reader);
return document.RootElement.Clone().ToString();
}
}
}
public override void Write(Utf8JsonWriter writer, object value, JsonSerializerOptions options)
=> writer.WriteStringValue(value.ToString());
}

View file

@ -1,4 +1,4 @@
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using System.Text.Json; using System.Text.Json;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;

View file

@ -1,4 +1,4 @@
namespace EllieBot.Common.ModuleBehaviors; namespace EllieBot.Common.ModuleBehaviors;
/// <summary> /// <summary>
/// Executed if no command was found for this message /// Executed if no command was found for this message

View file

@ -1,4 +1,4 @@
namespace EllieBot.Common.ModuleBehaviors; namespace EllieBot.Common.ModuleBehaviors;
/// <summary> /// <summary>
/// Implemented by modules to handle non-bot messages received /// Implemented by modules to handle non-bot messages received

View file

@ -1,4 +1,4 @@
namespace EllieBot.Common.ModuleBehaviors; namespace EllieBot.Common.ModuleBehaviors;
/// <summary> /// <summary>
/// This interface's method is executed after the command successfully finished execution. /// This interface's method is executed after the command successfully finished execution.

View file

@ -1,4 +1,4 @@
namespace EllieBot.Common.ModuleBehaviors; namespace EllieBot.Common.ModuleBehaviors;
/// <summary> /// <summary>
/// This interface's method is executed after a command was found but before it was executed. /// This interface's method is executed after a command was found but before it was executed.

View file

@ -1,4 +1,4 @@
namespace EllieBot.Common.ModuleBehaviors; namespace EllieBot.Common.ModuleBehaviors;
/// <summary> /// <summary>
/// Implemented by services which may transform input before a command is searched for /// Implemented by services which may transform input before a command is searched for

View file

@ -1,4 +1,4 @@
namespace EllieBot.Common.ModuleBehaviors; namespace EllieBot.Common.ModuleBehaviors;
/// <summary> /// <summary>
/// All services which need to execute something after /// All services which need to execute something after

View file

@ -1,4 +1,4 @@
using DryIoc; using DryIoc;
namespace EllieBot.Extensions; namespace EllieBot.Extensions;

View file

@ -1,5 +1,12 @@
namespace EllieBot.Modules.Patronage; namespace EllieBot.Modules.Patronage;
public enum LimitedFeatureName
{
ChatBot,
ReactionRole,
Prune,
}
public readonly struct FeatureLimitKey public readonly struct FeatureLimitKey
{ {
public string PrettyName { get; init; } public string PrettyName { get; init; }

View file

@ -1,8 +0,0 @@
namespace EllieBot.Modules.Patronage;
public readonly struct FeatureQuotaStats
{
public (uint Cur, uint Max) Hourly { get; init; }
public (uint Cur, uint Max) Daily { get; init; }
public (uint Cur, uint Max) Monthly { get; init; }
}

View file

@ -31,26 +31,15 @@ public interface IPatronageService
/// </summary> /// </summary>
/// <param name="userId">UserId for which to get the patron data for.</param> /// <param name="userId">UserId for which to get the patron data for.</param>
/// <returns>A patron with the specifeid userId</returns> /// <returns>A patron with the specifeid userId</returns>
public Task<Patron> GetPatronAsync(ulong userId); public Task<Patron?> GetPatronAsync(ulong userId);
/// <summary> Task<bool> LimitHitAsync(LimitedFeatureName key, ulong userId, int amount = 1);
/// Gets the quota statistic for the user/patron specified by the userId Task<bool> LimitForceHit(LimitedFeatureName key, ulong userId, int amount);
/// </summary> Task<QuotaLimit> GetUserLimit(LimitedFeatureName name, ulong userId);
/// <param name="userId">UserId of the user for which to get the quota statistic for</param>
/// <returns>Quota stats for the specified user</returns>
Task<UserQuotaStats> GetUserQuotaStatistic(ulong userId);
Task<Dictionary<LimitedFeatureName, (int, QuotaLimit)>> LimitStats(ulong userId);
Task<FeatureLimit> TryGetFeatureLimitAsync(FeatureLimitKey key, ulong userId, int? defaultValue);
ValueTask<OneOf<(uint Hourly, uint Daily, uint Monthly), QuotaLimit>> TryIncrementQuotaCounterAsync(
ulong userId,
bool isSelf,
FeatureType featureType,
string featureName,
uint? maybeHourly,
uint? maybeDaily,
uint? maybeMonthly);
PatronConfigData GetConfig(); PatronConfigData GetConfig();
int PercentBonus(Patron? user);
int PercentBonus(long amount);
} }

View file

@ -13,7 +13,7 @@ public readonly struct Patron
public ulong UserId { get; init; } public ulong UserId { get; init; }
/// <summary> /// <summary>
/// Amount the Patron is currently pledging or paid /// Amount the Patron is currently pledging or paid in cents
/// </summary> /// </summary>
public int Amount { get; init; } public int Amount { get; init; }

View file

@ -7,31 +7,11 @@ namespace EllieBot.Modules.Patronage;
public partial class PatronConfigData : ICloneable<PatronConfigData> public partial class PatronConfigData : ICloneable<PatronConfigData>
{ {
[Comment("DO NOT CHANGE")] [Comment("DO NOT CHANGE")]
public int Version { get; set; } = 2; public int Version { get; set; } = 3;
[Comment("Whether the patronage feature is enabled")] [Comment("Whether the patronage feature is enabled")]
public bool IsEnabled { get; set; } public bool IsEnabled { get; set; }
[Comment("List of patron only features and relevant quota data")] [Comment("Who can do how much of what")]
public FeatureQuotas Quotas { get; set; } public Dictionary<int, Dictionary<LimitedFeatureName, QuotaLimit>> Limits { get; set; } = new();
public PatronConfigData()
{
Quotas = new();
}
public class FeatureQuotas
{
[Comment("Dictionary of feature names with their respective limits. Set to null for unlimited")]
public Dictionary<string, Dictionary<PatronTier, int?>> Features { get; set; } = new();
[Comment("Dictionary of commands with their respective quota data")]
public Dictionary<string, Dictionary<PatronTier, Dictionary<QuotaPer, uint>?>> Commands { get; set; } = new();
[Comment("Dictionary of groups with their respective quota data")]
public Dictionary<string, Dictionary<PatronTier, Dictionary<QuotaPer, uint>?>> Groups { get; set; } = new();
[Comment("Dictionary of modules with their respective quota data")]
public Dictionary<string, Dictionary<PatronTier, Dictionary<QuotaPer, uint>?>> Modules { get; set; } = new();
}
} }

View file

@ -8,15 +8,6 @@ public static class PatronExtensions
_ => $"Patron Tier {tier}", _ => $"Patron Tier {tier}",
}; };
public static string ToFullName(this QuotaPer per)
=> per switch
{
QuotaPer.PerDay => "per day",
QuotaPer.PerHour => "per hour",
QuotaPer.PerMonth => "per month",
_ => "Unknown",
};
public static DateTime DayOfNextMonth(this DateTime date, int day) public static DateTime DayOfNextMonth(this DateTime date, int day)
{ {
var nextMonth = date.AddMonths(1); var nextMonth = date.AddMonths(1);

View file

@ -10,57 +10,16 @@ public readonly struct QuotaLimit
/// <summary> /// <summary>
/// Amount of usages reached, which is the limit /// Amount of usages reached, which is the limit
/// </summary> /// </summary>
public uint Quota { get; init; } public int Quota { get; init; }
/// <summary> /// <summary>
/// Which period is this quota limit for (hourly, daily, monthly, etc...) /// Which period is this quota limit for (hourly, daily, monthly, etc...)
/// </summary> /// </summary>
public QuotaPer QuotaPeriod { get; init; } public QuotaPer QuotaPeriod { get; init; }
/// <summary> public QuotaLimit(int quota, QuotaPer quotaPeriod)
/// When does this quota limit reset
/// </summary>
public DateTime ResetsAt { get; init; }
/// <summary>
/// Type of the feature this quota limit is for
/// </summary>
public FeatureType FeatureType { get; init; }
/// <summary>
/// Name of the feature this quota limit is for
/// </summary>
public string Feature { get; init; }
/// <summary>
/// Whether it is the user's own quota (true), or server owners (false)
/// </summary>
public bool IsOwnQuota { get; init; }
}
/// <summary>
/// Respresent information about the feature limit
/// </summary>
public readonly struct FeatureLimit
{
/// <summary>
/// Whether this limit comes from the patronage system
/// </summary>
public bool IsPatronLimit { get; init; } = false;
/// <summary>
/// Maximum limit allowed
/// </summary>
public int? Quota { get; init; } = null;
/// <summary>
/// Name of the limit
/// </summary>
public string Name { get; init; } = string.Empty;
public FeatureLimit()
{ {
Quota = quota;
QuotaPeriod = quotaPeriod;
} }
} }

View file

@ -5,4 +5,5 @@ public enum QuotaPer
PerHour, PerHour,
PerDay, PerDay,
PerMonth, PerMonth,
Total,
} }

View file

@ -1,25 +0,0 @@
namespace EllieBot.Modules.Patronage;
public readonly struct UserQuotaStats
{
private static readonly IReadOnlyDictionary<string, FeatureQuotaStats> _emptyDictionary
= new Dictionary<string, FeatureQuotaStats>();
public PatronTier Tier { get; init; }
= PatronTier.None;
public IReadOnlyDictionary<string, FeatureQuotaStats> Features { get; init; }
= _emptyDictionary;
public IReadOnlyDictionary<string, FeatureQuotaStats> Commands { get; init; }
= _emptyDictionary;
public IReadOnlyDictionary<string, FeatureQuotaStats> Groups { get; init; }
= _emptyDictionary;
public IReadOnlyDictionary<string, FeatureQuotaStats> Modules { get; init; }
= _emptyDictionary;
public UserQuotaStats()
{
}
}

View file

@ -1,4 +1,4 @@
#nullable disable #nullable disable
namespace EllieBot.Common.Pokemon; namespace EllieBot.Common.Pokemon;
public class SearchPokemonAbility public class SearchPokemonAbility

View file

@ -95,7 +95,7 @@ public sealed partial class ReplacementPatternStore
Register(_rngRegex, Register(_rngRegex,
match => match =>
{ {
var rng = new EllieRandom(); var rng = new NadekoRandom();
if (!int.TryParse(match.Groups["from"].ToString(), out var from)) if (!int.TryParse(match.Groups["from"].ToString(), out var from))
from = 0; from = 0;
if (!int.TryParse(match.Groups["to"].ToString(), out var to)) if (!int.TryParse(match.Groups["to"].ToString(), out var to))

View file

@ -33,15 +33,15 @@ public sealed class MessageSenderService : IMessageSenderService, IEService
=> new ResponseBuilder(_bs, _bcs, _client) => new ResponseBuilder(_bs, _bcs, _client)
.Channel(smc.Channel); .Channel(smc.Channel);
public EllieEmbedBuilder CreateEmbed() public NadekoEmbedBuilder CreateEmbed()
=> new EllieEmbedBuilder(_bcs); => new NadekoEmbedBuilder(_bcs);
} }
public class EllieEmbedBuilder : EmbedBuilder public class NadekoEmbedBuilder : EmbedBuilder
{ {
private readonly BotConfig _bc; private readonly BotConfig _bc;
public EllieEmbedBuilder(BotConfigService bcs) public NadekoEmbedBuilder(BotConfigService bcs)
{ {
_bc = bcs.Data; _bc = bcs.Data;
} }

View file

@ -12,6 +12,10 @@ public partial class ResponseBuilder
private readonly DiscordSocketClient _client; private readonly DiscordSocketClient _client;
private int currentPage; private int currentPage;
private EllieButtonInteractionHandler left;
private EllieButtonInteractionHandler right;
private EllieInteractionBase? extra;
public PaginationSender( public PaginationSender(
SourcedPaginatedResponseBuilder<T> paginationBuilder, SourcedPaginatedResponseBuilder<T> paginationBuilder,
ResponseBuilder builder) ResponseBuilder builder)
@ -107,6 +111,8 @@ public partial class ResponseBuilder
return (leftBtnInter, maybeInter, rightBtnInter); return (leftBtnInter, maybeInter, rightBtnInter);
} }
(left, extra, right) = await GetInteractions();
async Task UpdatePageAsync(SocketMessageComponent smc) async Task UpdatePageAsync(SocketMessageComponent smc)
{ {
var pageItems = (await _paginationBuilder.ItemsFunc(currentPage)).ToArray(); var pageItems = (await _paginationBuilder.ItemsFunc(currentPage)).ToArray();
@ -114,7 +120,10 @@ public partial class ResponseBuilder
if (_paginationBuilder.AddPaginatedFooter) if (_paginationBuilder.AddPaginatedFooter)
toSend.AddPaginatedFooter(currentPage, lastPage); toSend.AddPaginatedFooter(currentPage, lastPage);
var (left, extra, right) = (await GetInteractions()); left.SetCompleted();
right.SetCompleted();
extra?.SetCompleted();
(left, extra, right) = (await GetInteractions());
var cb = new ComponentBuilder(); var cb = new ComponentBuilder();
left.AddTo(cb); left.AddTo(cb);
@ -126,9 +135,10 @@ public partial class ResponseBuilder
x.Embed = toSend.Build(); x.Embed = toSend.Build();
x.Components = cb.Build(); x.Components = cb.Build();
}); });
await Task.WhenAll(left.RunAsync(smc.Message), extra?.RunAsync(smc.Message) ?? Task.CompletedTask, right.RunAsync(smc.Message));
} }
var (left, extra, right) = await GetInteractions();
var cb = new ComponentBuilder(); var cb = new ComponentBuilder();
left.AddTo(cb); left.AddTo(cb);
@ -147,6 +157,8 @@ public partial class ResponseBuilder
await Task.WhenAll(left.RunAsync(msg), extra?.RunAsync(msg) ?? Task.CompletedTask, right.RunAsync(msg)); await Task.WhenAll(left.RunAsync(msg), extra?.RunAsync(msg) ?? Task.CompletedTask, right.RunAsync(msg));
await Task.Delay(30_000);
await msg.ModifyAsync(mp => mp.Components = new ComponentBuilder().Build()); await msg.ModifyAsync(mp => mp.Components = new ComponentBuilder().Build());
} }
} }

View file

@ -1,4 +1,4 @@
using DryIoc; using DryIoc;
using LinqToDB.Extensions; using LinqToDB.Extensions;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using EllieBot.Modules.Music; using EllieBot.Modules.Music;

View file

@ -9,7 +9,7 @@ namespace EllieBot.Services;
public class CommandHandler : IEService, IReadyExecutor, ICommandHandler public class CommandHandler : IEService, IReadyExecutor, ICommandHandler
{ {
private const int GLOBAL_COMMANDS_COOLDOWN = 750; private const int GLOBAL_COMMANDS_COOLDOWN = 200;
private const float ONE_THOUSANDTH = 1.0f / 1000; private const float ONE_THOUSANDTH = 1.0f / 1000;

View file

@ -1,4 +1,4 @@
#nullable disable #nullable disable
namespace EllieBot.Services; namespace EllieBot.Services;
public interface IBehaviorHandler public interface IBehaviorHandler

View file

@ -1,4 +1,4 @@
#nullable disable #nullable disable
using System.Globalization; using System.Globalization;
namespace EllieBot.Services; namespace EllieBot.Services;

View file

@ -1,4 +1,4 @@
namespace EllieBot.Common; namespace EllieBot.Common;
public interface ITimezoneService public interface ITimezoneService
{ {

View file

@ -53,7 +53,7 @@ public class YtdlOperation
} }
catch (Win32Exception) catch (Win32Exception)
{ {
Log.Error("youtube-dl is likely not installed. " + "Please install it before running the command again"); Log.Error("youtube-dl is likely not installed. Please install it before running the command again");
return default; return default;
} }
catch (Exception ex) catch (Exception ex)

View file

@ -1,4 +1,4 @@
#nullable disable #nullable disable
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using System.Globalization; using System.Globalization;

View file

@ -1,9 +1,9 @@
using EllieBot.Common.Configs; using EllieBot.Common.Configs;
using EllieBot.Common.Yml; using EllieBot.Common.Yml;
using System.Linq.Expressions; using System.Linq.Expressions;
using System.Reflection; using System.Reflection;
namespace EllieBot.Services; namespace NadekoBot.Services;
/// <summary> /// <summary>
/// Base service for all settings services /// Base service for all settings services

View file

@ -1,4 +1,4 @@
#nullable disable #nullable disable
namespace EllieBot.Services; namespace EllieBot.Services;
/// <summary> /// <summary>

View file

@ -1,4 +1,4 @@
#nullable disable #nullable disable
namespace EllieBot.Services; namespace EllieBot.Services;
/// <summary> /// <summary>

View file

@ -1,4 +1,4 @@
#nullable disable #nullable disable
namespace EllieBot.Common.Yml; namespace EllieBot.Common.Yml;
[AttributeUsage(AttributeTargets.Property)] [AttributeUsage(AttributeTargets.Property)]

View file

@ -5,7 +5,6 @@ using YamlDotNet.Serialization.EventEmitters;
namespace EllieBot.Common.Yml; namespace EllieBot.Common.Yml;
public class MultilineScalarFlowStyleEmitter : ChainedEventEmitter public class MultilineScalarFlowStyleEmitter : ChainedEventEmitter
{ {
public MultilineScalarFlowStyleEmitter(IEventEmitter nextEmitter) public MultilineScalarFlowStyleEmitter(IEventEmitter nextEmitter)

View file

@ -27,4 +27,14 @@ public static class CommandContextExtensions
public static Task WarningAsync(this ICommandContext ctx) public static Task WarningAsync(this ICommandContext ctx)
=> ctx.ReactAsync(MsgType.Pending); => ctx.ReactAsync(MsgType.Pending);
public static Task OkAsync(this IUserMessage msg)
=> msg.AddReactionAsync(_okEmoji);
public static Task ErrorAsync(this IUserMessage msg)
=> msg.AddReactionAsync(_errorEmoji);
public static Task WarningAsync(this IUserMessage msg)
=> msg.AddReactionAsync(_warnEmoji);
} }

View file

@ -229,6 +229,7 @@ public static class Extensions
public static IEnumerable<IRole> GetRoles(this IGuildUser user) public static IEnumerable<IRole> GetRoles(this IGuildUser user)
=> user.RoleIds.Select(r => user.Guild.GetRole(r)).Where(r => r is not null); => user.RoleIds.Select(r => user.Guild.GetRole(r)).Where(r => r is not null);
// todo remove
public static void Lap(this Stopwatch sw, string checkpoint) public static void Lap(this Stopwatch sw, string checkpoint)
{ {
Log.Information("Checkpoint {CheckPoint}: {Time}ms", checkpoint, sw.Elapsed.TotalMilliseconds); Log.Information("Checkpoint {CheckPoint}: {Time}ms", checkpoint, sw.Elapsed.TotalMilliseconds);