mirror of
https://gitlab.com/Kwoth/nadekobot.git
synced 2025-09-11 17:58:26 -04:00
Added and applied styles for private readonly fields, private fields to Extensions and Common folders.
- Some renamings and code cleanups - Chained method calls, binary expressions and binary patterns will now break into newlines - Type param constraints and base constructor calls will be on the new line
This commit is contained in:
@@ -1,8 +1,9 @@
|
||||
namespace NadekoBot.Common.TypeReaders;
|
||||
namespace NadekoBot.Common;
|
||||
|
||||
public enum AddRemove
|
||||
{
|
||||
Add = int.MinValue,
|
||||
Remove = int.MinValue + 1,
|
||||
Rem = int.MinValue + 1,
|
||||
Rm = int.MinValue + 1,
|
||||
}
|
@@ -4,13 +4,15 @@ namespace NadekoBot.Common;
|
||||
|
||||
public class AsyncLazy<T> : Lazy<Task<T>>
|
||||
{
|
||||
public AsyncLazy(Func<T> valueFactory) :
|
||||
base(() => Task.Run(valueFactory))
|
||||
{ }
|
||||
public AsyncLazy(Func<T> valueFactory)
|
||||
: base(() => Task.Run(valueFactory))
|
||||
{
|
||||
}
|
||||
|
||||
public AsyncLazy(Func<Task<T>> taskFactory) :
|
||||
base(() => Task.Run(taskFactory))
|
||||
{ }
|
||||
public AsyncLazy(Func<Task<T>> taskFactory)
|
||||
: base(() => Task.Run(taskFactory))
|
||||
{
|
||||
}
|
||||
|
||||
public TaskAwaiter<T> GetAwaiter()
|
||||
=> Value.GetAwaiter();
|
||||
|
@@ -8,10 +8,7 @@ public class CmdStrings
|
||||
public string Description { get; }
|
||||
|
||||
[JsonConstructor]
|
||||
public CmdStrings(
|
||||
[JsonProperty("args")]string[] usages,
|
||||
[JsonProperty("desc")]string description
|
||||
)
|
||||
public CmdStrings([JsonProperty("args")] string[] usages, [JsonProperty("desc")] string description)
|
||||
{
|
||||
Usages = usages;
|
||||
Description = description;
|
||||
|
@@ -3,14 +3,20 @@ using NadekoBot.Services.Database.Models;
|
||||
|
||||
namespace NadekoBot.Common.Collections;
|
||||
|
||||
public class IndexedCollection<T> : IList<T> where T : class, IIndexed
|
||||
public class IndexedCollection<T> : IList<T>
|
||||
where T : class, IIndexed
|
||||
{
|
||||
public List<T> Source { get; }
|
||||
private readonly object _locker = new();
|
||||
|
||||
public int Count => Source.Count;
|
||||
public bool IsReadOnly => false;
|
||||
public int IndexOf(T item) => item.Index;
|
||||
public int Count
|
||||
=> Source.Count;
|
||||
|
||||
public bool IsReadOnly
|
||||
=> false;
|
||||
|
||||
public int IndexOf([NotNull] T item)
|
||||
=> item.Index;
|
||||
|
||||
public IndexedCollection()
|
||||
=> Source = new();
|
||||
@@ -36,16 +42,17 @@ public class IndexedCollection<T> : IList<T> where T : class, IIndexed
|
||||
}
|
||||
}
|
||||
|
||||
public static implicit operator List<T>(IndexedCollection<T> x) =>
|
||||
x.Source;
|
||||
public static implicit operator List<T>(IndexedCollection<T> x)
|
||||
=> x.Source;
|
||||
|
||||
public List<T> ToList() => Source.ToList();
|
||||
public List<T> ToList()
|
||||
=> Source.ToList();
|
||||
|
||||
public IEnumerator<T> GetEnumerator() =>
|
||||
Source.GetEnumerator();
|
||||
public IEnumerator<T> GetEnumerator()
|
||||
=> Source.GetEnumerator();
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() =>
|
||||
Source.GetEnumerator();
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
=> Source.GetEnumerator();
|
||||
|
||||
public void Add(T item)
|
||||
{
|
||||
@@ -82,19 +89,21 @@ public class IndexedCollection<T> : IList<T> where T : class, IIndexed
|
||||
|
||||
public virtual bool Remove(T item)
|
||||
{
|
||||
bool removed;
|
||||
lock (_locker)
|
||||
{
|
||||
if (removed = Source.Remove(item))
|
||||
if (Source.Remove(item))
|
||||
{
|
||||
for (var i = 0; i < Source.Count; i++)
|
||||
{
|
||||
if (Source[i].Index != i)
|
||||
Source[i].Index = i;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return removed;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public virtual void Insert(int index, T item)
|
||||
|
@@ -18,14 +18,16 @@ next to the response. The color depends whether the command
|
||||
is completed, errored or in progress (pending)
|
||||
Color settings below are for the color of those lines.
|
||||
To get color's hex, you can go here https://htmlcolorcodes.com/
|
||||
and copy the hex code fo your selected color (marked as #)")]
|
||||
and copy the hex code fo your selected color (marked as #)"
|
||||
)]
|
||||
public ColorConfig Color { get; set; }
|
||||
|
||||
|
||||
[Comment("Default bot language. It has to be in the list of supported languages (.langli)")]
|
||||
public CultureInfo DefaultLocale { get; set; }
|
||||
|
||||
|
||||
[Comment(@"Style in which executed commands will show up in the console.
|
||||
Allowed values: Simple, Normal, None")]
|
||||
Allowed values: Simple, Normal, None"
|
||||
)]
|
||||
public ConsoleOutputType ConsoleOutputType { get; set; }
|
||||
|
||||
// [Comment(@"For what kind of updates will the bot check.
|
||||
@@ -38,30 +40,35 @@ Allowed values: Simple, Normal, None")]
|
||||
[Comment(@"Do you want any messages sent by users in Bot's DM to be forwarded to the owner(s)?")]
|
||||
public bool ForwardMessages { get; set; }
|
||||
|
||||
[Comment(@"Do you want the message to be forwarded only to the first owner specified in the list of owners (in creds.yml),
|
||||
or all owners? (this might cause the bot to lag if there's a lot of owners specified)")]
|
||||
[Comment(
|
||||
@"Do you want the message to be forwarded only to the first owner specified in the list of owners (in creds.yml),
|
||||
or all owners? (this might cause the bot to lag if there's a lot of owners specified)"
|
||||
)]
|
||||
public bool ForwardToAllOwners { get; set; }
|
||||
|
||||
[Comment(@"When a user DMs the bot with a message which is not a command
|
||||
they will receive this message. Leave empty for no response. The string which will be sent whenever someone DMs the bot.
|
||||
Supports embeds. How it looks: https://puu.sh/B0BLV.png")]
|
||||
Supports embeds. How it looks: https://puu.sh/B0BLV.png"
|
||||
)]
|
||||
[YamlMember(ScalarStyle = ScalarStyle.Literal)]
|
||||
public string DmHelpText { get; set; }
|
||||
|
||||
|
||||
[Comment(@"Only users who send a DM to the bot containing one of the specified words will get a DmHelpText response.
|
||||
Case insensitive.
|
||||
Leave empty to reply with DmHelpText to every DM.")]
|
||||
Leave empty to reply with DmHelpText to every DM."
|
||||
)]
|
||||
public List<string> DmHelpTextKeywords { get; set; }
|
||||
|
||||
[Comment(@"This is the response for the .h command")]
|
||||
[YamlMember(ScalarStyle = ScalarStyle.Literal)]
|
||||
public string HelpText { get; set; }
|
||||
|
||||
[Comment(@"List of modules and commands completely blocked on the bot")]
|
||||
public BlockedConfig Blocked { get; set; }
|
||||
|
||||
[Comment(@"Which string will be used to recognize the commands")]
|
||||
public string Prefix { get; set; }
|
||||
|
||||
|
||||
[Comment(@"Toggles whether your bot will group greet/bye messages into a single message every 5 seconds.
|
||||
1st user who joins will get greeted immediately
|
||||
If more users join within the next 5 seconds, they will be greeted in groups of 5.
|
||||
@@ -70,12 +77,14 @@ Keep in mind this might break some of your embeds - for example if you have %use
|
||||
it will become invalid, as it will resolve to a list of avatars of grouped users.
|
||||
note: This setting is primarily used if you're afraid of raids, or you're running medium/large bots where some
|
||||
servers might get hundreds of people join at once. This is used to prevent the bot from getting ratelimited,
|
||||
and (slightly) reduce the greet spam in those servers.")]
|
||||
and (slightly) reduce the greet spam in those servers."
|
||||
)]
|
||||
public bool GroupGreets { get; set; }
|
||||
|
||||
|
||||
[Comment(@"Whether the bot will rotate through all specified statuses.
|
||||
This setting can be changed via .rots command.
|
||||
See RotatingStatuses submodule in Administration.")]
|
||||
See RotatingStatuses submodule in Administration."
|
||||
)]
|
||||
public bool RotateStatuses { get; set; }
|
||||
|
||||
// [Comment(@"Whether the prefix will be a suffix, or prefix.
|
||||
@@ -159,10 +168,10 @@ public partial class ColorConfig
|
||||
{
|
||||
[Comment(@"Color used for embed responses when command successfully executes")]
|
||||
public Rgba32 Ok { get; set; }
|
||||
|
||||
|
||||
[Comment(@"Color used for embed responses when command has an error")]
|
||||
public Rgba32 Error { get; set; }
|
||||
|
||||
|
||||
[Comment(@"Color used for embed responses while command is doing work or is in progress")]
|
||||
public Rgba32 Pending { get; set; }
|
||||
|
||||
@@ -173,7 +182,7 @@ public partial class ColorConfig
|
||||
Pending = Rgba32.ParseHex("faa61a");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public enum ConsoleOutputType
|
||||
{
|
||||
Normal = 0,
|
||||
|
@@ -11,81 +11,93 @@ public sealed class Creds : IBotCredentials
|
||||
OwnerIds = new List<ulong>();
|
||||
TotalShards = 1;
|
||||
GoogleApiKey = string.Empty;
|
||||
Votes = new(string.Empty, string.Empty, string.Empty, string.Empty);
|
||||
Patreon = new(string.Empty, string.Empty, string.Empty, string.Empty);
|
||||
Votes = new(string.Empty,
|
||||
string.Empty,
|
||||
string.Empty,
|
||||
string.Empty
|
||||
);
|
||||
Patreon = new(string.Empty,
|
||||
string.Empty,
|
||||
string.Empty,
|
||||
string.Empty
|
||||
);
|
||||
BotListToken = string.Empty;
|
||||
CleverbotApiKey = string.Empty;
|
||||
RedisOptions = "localhost:6379,syncTimeout=30000,responseTimeout=30000,allowAdmin=true,password=";
|
||||
Db = new()
|
||||
{
|
||||
Type = "sqlite",
|
||||
ConnectionString = "Data Source=data/NadekoBot.db"
|
||||
};
|
||||
Db = new() { Type = "sqlite", ConnectionString = "Data Source=data/NadekoBot.db" };
|
||||
|
||||
CoordinatorUrl = "http://localhost:3442";
|
||||
|
||||
RestartCommand = new()
|
||||
{
|
||||
};
|
||||
RestartCommand = new();
|
||||
}
|
||||
|
||||
[Comment(@"DO NOT CHANGE")]
|
||||
public int Version { get; set; }
|
||||
|
||||
|
||||
[Comment(@"Bot token. Do not share with anyone ever -> https://discordapp.com/developers/applications/")]
|
||||
public string Token { get; set; }
|
||||
|
||||
[Comment(@"List of Ids of the users who have bot owner permissions
|
||||
**DO NOT ADD PEOPLE YOU DON'T TRUST**")]
|
||||
**DO NOT ADD PEOPLE YOU DON'T TRUST**"
|
||||
)]
|
||||
public ICollection<ulong> OwnerIds { get; set; }
|
||||
|
||||
|
||||
[Comment(@"The number of shards that the bot will running on.
|
||||
Leave at 1 if you don't know what you're doing.")]
|
||||
Leave at 1 if you don't know what you're doing."
|
||||
)]
|
||||
public int TotalShards { get; set; }
|
||||
|
||||
[Comment(@"Login to https://console.cloud.google.com, create a new project, go to APIs & Services -> Library -> YouTube Data API and enable it.
|
||||
|
||||
[Comment(
|
||||
@"Login to https://console.cloud.google.com, create a new project, go to APIs & Services -> Library -> YouTube Data API and enable it.
|
||||
Then, go to APIs and Services -> Credentials and click Create credentials -> API key.
|
||||
Used only for Youtube Data Api (at the moment).")]
|
||||
Used only for Youtube Data Api (at the moment)."
|
||||
)]
|
||||
public string GoogleApiKey { get; set; }
|
||||
|
||||
|
||||
[Comment(@"Settings for voting system for discordbots. Meant for use on global Nadeko.")]
|
||||
public VotesSettings Votes { get; set; }
|
||||
|
||||
|
||||
[Comment(@"Patreon auto reward system settings.
|
||||
go to https://www.patreon.com/portal -> my clients -> create client")]
|
||||
go to https://www.patreon.com/portal -> my clients -> create client"
|
||||
)]
|
||||
public PatreonSettings Patreon { get; set; }
|
||||
|
||||
|
||||
[Comment(@"Api key for sending stats to DiscordBotList.")]
|
||||
public string BotListToken { get; set; }
|
||||
|
||||
|
||||
[Comment(@"Official cleverbot api key.")]
|
||||
public string CleverbotApiKey { get; set; }
|
||||
|
||||
|
||||
[Comment(@"Redis connection string. Don't change if you don't know what you're doing.")]
|
||||
public string RedisOptions { get; set; }
|
||||
|
||||
|
||||
[Comment(@"Database options. Don't change if you don't know what you're doing. Leave null for default values")]
|
||||
public DbOptions Db { get; set; }
|
||||
|
||||
[Comment(@"Address and port of the coordinator endpoint. Leave empty for default.
|
||||
Change only if you've changed the coordinator address or port.")]
|
||||
Change only if you've changed the coordinator address or port."
|
||||
)]
|
||||
public string CoordinatorUrl { get; set; }
|
||||
|
||||
[Comment(@"Api key obtained on https://rapidapi.com (go to MyApps -> Add New App -> Enter Name -> Application key)")]
|
||||
|
||||
[Comment(@"Api key obtained on https://rapidapi.com (go to MyApps -> Add New App -> Enter Name -> Application key)"
|
||||
)]
|
||||
public string RapidApiKey { get; set; }
|
||||
|
||||
[Comment(@"https://locationiq.com api key (register and you will receive the token in the email).
|
||||
Used only for .time command.")]
|
||||
Used only for .time command."
|
||||
)]
|
||||
public string LocationIqApiKey { get; set; }
|
||||
|
||||
[Comment(@"https://timezonedb.com api key (register and you will receive the token in the email).
|
||||
Used only for .time command")]
|
||||
Used only for .time command"
|
||||
)]
|
||||
public string TimezoneDbApiKey { get; set; }
|
||||
|
||||
[Comment(@"https://pro.coinmarketcap.com/account/ api key. There is a free plan for personal use.
|
||||
Used for cryptocurrency related commands.")]
|
||||
Used for cryptocurrency related commands."
|
||||
)]
|
||||
public string CoinmarketcapApiKey { get; set; }
|
||||
|
||||
|
||||
[Comment(@"Api key used for Osu related commands. Obtain this key at https://osu.ppy.sh/p/api")]
|
||||
public string OsuApiKey { get; set; }
|
||||
|
||||
@@ -99,7 +111,8 @@ Linux default
|
||||
args: ""NadekoBot.dll -- {0}""
|
||||
Windows default
|
||||
cmd: NadekoBot.exe
|
||||
args: {0}")]
|
||||
args: {0}"
|
||||
)]
|
||||
public RestartConfig RestartCommand { get; set; }
|
||||
|
||||
|
||||
@@ -107,6 +120,7 @@ Windows default
|
||||
{
|
||||
[Comment(@"Database type. Only sqlite supported atm")]
|
||||
public string Type { get; set; }
|
||||
|
||||
[Comment(@"Connection string. Will default to ""Data Source=data/NadekoBot.db""")]
|
||||
public string ConnectionString { get; set; }
|
||||
}
|
||||
@@ -118,10 +132,16 @@ Windows default
|
||||
public string RefreshToken { get; set; }
|
||||
public string ClientSecret { get; set; }
|
||||
|
||||
[Comment(@"Campaign ID of your patreon page. Go to your patreon page (make sure you're logged in) and type ""prompt('Campaign ID', window.patreon.bootstrap.creator.data.id);"" in the console. (ctrl + shift + i)")]
|
||||
[Comment(
|
||||
@"Campaign ID of your patreon page. Go to your patreon page (make sure you're logged in) and type ""prompt('Campaign ID', window.patreon.bootstrap.creator.data.id);"" in the console. (ctrl + shift + i)"
|
||||
)]
|
||||
public string CampaignId { get; set; }
|
||||
|
||||
public PatreonSettings(string accessToken, string refreshToken, string clientSecret, string campaignId)
|
||||
public PatreonSettings(
|
||||
string accessToken,
|
||||
string refreshToken,
|
||||
string clientSecret,
|
||||
string campaignId)
|
||||
{
|
||||
AccessToken = accessToken;
|
||||
RefreshToken = refreshToken;
|
||||
@@ -131,7 +151,6 @@ Windows default
|
||||
|
||||
public PatreonSettings()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,28 +158,35 @@ Windows default
|
||||
{
|
||||
[Comment(@"top.gg votes service url
|
||||
This is the url of your instance of the NadekoBot.Votes api
|
||||
Example: https://votes.my.cool.bot.com")]
|
||||
Example: https://votes.my.cool.bot.com"
|
||||
)]
|
||||
public string TopggServiceUrl { get; set; }
|
||||
|
||||
|
||||
[Comment(@"Authorization header value sent to the TopGG service url with each request
|
||||
This should be equivalent to the TopggKey in your NadekoBot.Votes api appsettings.json file")]
|
||||
This should be equivalent to the TopggKey in your NadekoBot.Votes api appsettings.json file"
|
||||
)]
|
||||
public string TopggKey { get; set; }
|
||||
|
||||
|
||||
[Comment(@"discords.com votes service url
|
||||
This is the url of your instance of the NadekoBot.Votes api
|
||||
Example: https://votes.my.cool.bot.com")]
|
||||
Example: https://votes.my.cool.bot.com"
|
||||
)]
|
||||
public string DiscordsServiceUrl { get; set; }
|
||||
|
||||
|
||||
[Comment(@"Authorization header value sent to the Discords service url with each request
|
||||
This should be equivalent to the DiscordsKey in your NadekoBot.Votes api appsettings.json file")]
|
||||
This should be equivalent to the DiscordsKey in your NadekoBot.Votes api appsettings.json file"
|
||||
)]
|
||||
public string DiscordsKey { get; set; }
|
||||
|
||||
public VotesSettings()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public VotesSettings(string topggServiceUrl, string topggKey, string discordsServiceUrl, string discordsKey)
|
||||
|
||||
public VotesSettings(
|
||||
string topggServiceUrl,
|
||||
string topggKey,
|
||||
string discordsServiceUrl,
|
||||
string discordsKey)
|
||||
{
|
||||
TopggServiceUrl = topggServiceUrl;
|
||||
TopggKey = topggKey;
|
||||
|
@@ -3,7 +3,7 @@
|
||||
public class DownloadTracker : INService
|
||||
{
|
||||
private ConcurrentDictionary<ulong, DateTime> LastDownloads { get; } = new();
|
||||
private readonly SemaphoreSlim downloadUsersSemaphore = new(1, 1);
|
||||
private readonly SemaphoreSlim _downloadUsersSemaphore = new(1, 1);
|
||||
|
||||
/// <summary>
|
||||
/// Ensures all users on the specified guild were downloaded within the last hour.
|
||||
@@ -12,16 +12,16 @@ public class DownloadTracker : INService
|
||||
/// <returns>Task representing download state</returns>
|
||||
public async Task EnsureUsersDownloadedAsync(IGuild guild)
|
||||
{
|
||||
await downloadUsersSemaphore.WaitAsync();
|
||||
await _downloadUsersSemaphore.WaitAsync();
|
||||
try
|
||||
{
|
||||
var now = DateTime.UtcNow;
|
||||
|
||||
// download once per hour at most
|
||||
var added = LastDownloads.AddOrUpdate(
|
||||
guild.Id,
|
||||
var added = LastDownloads.AddOrUpdate(guild.Id,
|
||||
now,
|
||||
(key, old) => now - old > TimeSpan.FromHours(1) ? now : old);
|
||||
(_, old) => now - old > TimeSpan.FromHours(1) ? now : old
|
||||
);
|
||||
|
||||
// means that this entry was just added - download the users
|
||||
if (added == now)
|
||||
@@ -29,7 +29,7 @@ public class DownloadTracker : INService
|
||||
}
|
||||
finally
|
||||
{
|
||||
downloadUsersSemaphore.Release();
|
||||
_downloadUsersSemaphore.Release();
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,7 +0,0 @@
|
||||
namespace NadekoBot.Extensions;
|
||||
|
||||
public static class BotCredentialsExtensions
|
||||
{
|
||||
public static bool IsOwner(this IBotCredentials creds, IUser user)
|
||||
=> creds.OwnerIds.Contains(user.Id);
|
||||
}
|
@@ -1,75 +0,0 @@
|
||||
using System.Reflection;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using NadekoBot.Modules.Music;
|
||||
using NadekoBot.Modules.Music.Resolvers;
|
||||
using NadekoBot.Modules.Music.Services;
|
||||
using StackExchange.Redis;
|
||||
|
||||
namespace NadekoBot.Extensions;
|
||||
|
||||
public static class ServiceCollectionExtensions
|
||||
{
|
||||
public static IServiceCollection AddBotStringsServices(this IServiceCollection services, int totalShards)
|
||||
=> totalShards <= 1
|
||||
? services
|
||||
.AddSingleton<IStringsSource, LocalFileStringsSource>()
|
||||
.AddSingleton<IBotStringsProvider, LocalBotStringsProvider>()
|
||||
.AddSingleton<IBotStrings, BotStrings>()
|
||||
: services.AddSingleton<IStringsSource, LocalFileStringsSource>()
|
||||
.AddSingleton<IBotStringsProvider, RedisBotStringsProvider>()
|
||||
.AddSingleton<IBotStrings, BotStrings>();
|
||||
|
||||
public static IServiceCollection AddConfigServices(this IServiceCollection services)
|
||||
{
|
||||
var baseType = typeof(ConfigServiceBase<>);
|
||||
|
||||
foreach (var type in Assembly.GetCallingAssembly().ExportedTypes.Where(x => x.IsSealed))
|
||||
{
|
||||
if (type.BaseType?.IsGenericType == true && type.BaseType.GetGenericTypeDefinition() == baseType)
|
||||
{
|
||||
services.AddSingleton(type);
|
||||
services.AddSingleton(x => (IConfigService)x.GetRequiredService(type));
|
||||
}
|
||||
}
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
public static IServiceCollection AddConfigMigrators(this IServiceCollection services)
|
||||
=> services.AddSealedSubclassesOf(typeof(IConfigMigrator));
|
||||
|
||||
public static IServiceCollection AddMusic(this IServiceCollection services)
|
||||
=> services
|
||||
.AddSingleton<IMusicService, MusicService>()
|
||||
.AddSingleton<ITrackResolveProvider, TrackResolveProvider>()
|
||||
.AddSingleton<IYoutubeResolver, YtdlYoutubeResolver>()
|
||||
.AddSingleton<ISoundcloudResolver, SoundcloudResolver>()
|
||||
.AddSingleton<ILocalTrackResolver, LocalTrackResolver>()
|
||||
.AddSingleton<IRadioResolver, RadioResolver>()
|
||||
.AddSingleton<ITrackCacher, RedisTrackCacher>()
|
||||
.AddSingleton<YtLoader>()
|
||||
.AddSingleton<IPlaceholderProvider>(svc => svc.GetRequiredService<IMusicService>());
|
||||
|
||||
// consider using scrutor, because slightly different versions
|
||||
// of this might be needed in several different places
|
||||
public static IServiceCollection AddSealedSubclassesOf(this IServiceCollection services, Type baseType)
|
||||
{
|
||||
var subTypes = Assembly.GetCallingAssembly()
|
||||
.ExportedTypes
|
||||
.Where(type => type.IsSealed && baseType.IsAssignableFrom(type));
|
||||
|
||||
foreach (var subType in subTypes)
|
||||
{
|
||||
services.AddSingleton(baseType, subType);
|
||||
}
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
public static IServiceCollection AddRedis(this IServiceCollection services, string redisOptions)
|
||||
{
|
||||
var conf = ConfigurationOptions.Parse(redisOptions);
|
||||
services.AddSingleton(ConnectionMultiplexer.Connect(conf));
|
||||
return services;
|
||||
}
|
||||
}
|
@@ -6,7 +6,7 @@ public static class Helpers
|
||||
{
|
||||
if (!Console.IsInputRedirected)
|
||||
Console.ReadKey();
|
||||
|
||||
|
||||
Environment.Exit(exitCode);
|
||||
}
|
||||
}
|
@@ -21,7 +21,7 @@ public interface IBotCredentials
|
||||
string CoinmarketcapApiKey { get; }
|
||||
string CoordinatorUrl { get; set; }
|
||||
}
|
||||
|
||||
|
||||
public class RestartConfig
|
||||
{
|
||||
public string Cmd { get; set; }
|
||||
|
@@ -1,6 +1,7 @@
|
||||
namespace NadekoBot.Common;
|
||||
|
||||
public interface ICloneable<T> where T : new()
|
||||
public interface ICloneable<T>
|
||||
where T : new()
|
||||
{
|
||||
public T Clone();
|
||||
}
|
@@ -1,23 +1,13 @@
|
||||
using System.Globalization;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
|
||||
namespace NadekoBot.Common.JsonConverters;
|
||||
|
||||
public class Rgba32Converter : JsonConverter<Rgba32>
|
||||
{
|
||||
public override Rgba32 Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
=> Rgba32.ParseHex(reader.GetString());
|
||||
|
||||
public override void Write(Utf8JsonWriter writer, Rgba32 value, JsonSerializerOptions options)
|
||||
=> writer.WriteStringValue(value.ToHex());
|
||||
}
|
||||
|
||||
public class CultureInfoConverter : JsonConverter<CultureInfo>
|
||||
{
|
||||
public override CultureInfo Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
=> new(reader.GetString());
|
||||
=> new(reader.GetString() ?? "en-US");
|
||||
|
||||
public override void Write(Utf8JsonWriter writer, CultureInfo value, JsonSerializerOptions options)
|
||||
=> writer.WriteStringValue(value.Name);
|
14
src/NadekoBot/Common/JsonConverters/Rgba32Converter.cs
Normal file
14
src/NadekoBot/Common/JsonConverters/Rgba32Converter.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace NadekoBot.Common.JsonConverters;
|
||||
|
||||
public class Rgba32Converter : JsonConverter<Rgba32>
|
||||
{
|
||||
public override Rgba32 Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
=> Rgba32.ParseHex(reader.GetString());
|
||||
|
||||
public override void Write(Utf8JsonWriter writer, Rgba32 value, JsonSerializerOptions options)
|
||||
=> writer.WriteStringValue(value.ToHex());
|
||||
}
|
@@ -4,14 +4,17 @@ namespace NadekoBot.Common;
|
||||
|
||||
// needs proper invalid input check (character array input out of range)
|
||||
// needs negative number support
|
||||
// ReSharper disable once InconsistentNaming
|
||||
#pragma warning disable IDE1006
|
||||
public readonly struct kwum : IEquatable<kwum>
|
||||
#pragma warning restore IDE1006
|
||||
{
|
||||
private readonly int _value;
|
||||
private const string ValidCharacters = "23456789abcdefghijkmnpqrstuvwxyz";
|
||||
private const string VALID_CHARACTERS = "23456789abcdefghijkmnpqrstuvwxyz";
|
||||
|
||||
public kwum(int num)
|
||||
=> _value = num;
|
||||
|
||||
|
||||
public kwum(in char c)
|
||||
{
|
||||
if (!IsValidChar(c))
|
||||
@@ -21,11 +24,11 @@ public readonly struct kwum : IEquatable<kwum>
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static int InternalCharToValue(in char c)
|
||||
=> ValidCharacters.IndexOf(c);
|
||||
private static int InternalCharToValue(in char c)
|
||||
=> VALID_CHARACTERS.IndexOf(c);
|
||||
|
||||
public kwum(in ReadOnlySpan<char> input)
|
||||
{;
|
||||
{
|
||||
_value = 0;
|
||||
for (var index = 0; index < input.Length; index++)
|
||||
{
|
||||
@@ -33,14 +36,14 @@ public readonly struct kwum : IEquatable<kwum>
|
||||
if (!IsValidChar(c))
|
||||
throw new ArgumentException("All characters need to be a valid kwum characters.", nameof(input));
|
||||
|
||||
_value += ValidCharacters.IndexOf(c) * (int)Math.Pow(ValidCharacters.Length, input.Length - index - 1);
|
||||
_value += VALID_CHARACTERS.IndexOf(c) * (int)Math.Pow(VALID_CHARACTERS.Length, input.Length - index - 1);
|
||||
}
|
||||
}
|
||||
|
||||
public static bool TryParse(in ReadOnlySpan<char> input, out kwum value)
|
||||
{
|
||||
value = default;
|
||||
foreach(var c in input)
|
||||
foreach (var c in input)
|
||||
if (!IsValidChar(c))
|
||||
return false;
|
||||
|
||||
@@ -59,25 +62,26 @@ public readonly struct kwum : IEquatable<kwum>
|
||||
|
||||
public static implicit operator long(kwum kwum)
|
||||
=> kwum._value;
|
||||
|
||||
|
||||
public static implicit operator int(kwum kwum)
|
||||
=> kwum._value;
|
||||
|
||||
public static implicit operator kwum(int num)
|
||||
=> new(num);
|
||||
|
||||
public static bool IsValidChar(char c)
|
||||
=> ValidCharacters.Contains(c);
|
||||
=> VALID_CHARACTERS.Contains(c);
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
var count = ValidCharacters.Length;
|
||||
var count = VALID_CHARACTERS.Length;
|
||||
var localValue = _value;
|
||||
var arrSize = (int)Math.Log(localValue, count) + 1;
|
||||
Span<char> chars = new char[arrSize];
|
||||
while (localValue > 0)
|
||||
{
|
||||
localValue = Math.DivRem(localValue, count, out var rem);
|
||||
chars[--arrSize] = ValidCharacters[rem];
|
||||
chars[--arrSize] = VALID_CHARACTERS[rem];
|
||||
}
|
||||
|
||||
return new(chars);
|
||||
|
@@ -4,10 +4,14 @@ namespace NadekoBot.Common;
|
||||
|
||||
public class LbOpts : INadekoCommandOptions
|
||||
{
|
||||
[Option('c', "clean", Default = false, HelpText = "Only show users who are on the server.")]
|
||||
[Option('c',
|
||||
"clean",
|
||||
Default = false,
|
||||
HelpText = "Only show users who are on the server."
|
||||
)]
|
||||
public bool Clean { get; set; }
|
||||
|
||||
public void NormalizeOptions()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
@@ -17,18 +17,21 @@ public class LoginErrorHandler
|
||||
case HttpStatusCode.Unauthorized:
|
||||
Log.Error("Your bot token is wrong.\n" +
|
||||
"You can find the bot token under the Bot tab in the developer page.\n" +
|
||||
"Fix your token in the credentials file and restart the bot");
|
||||
"Fix your token in the credentials file and restart the bot"
|
||||
);
|
||||
break;
|
||||
|
||||
case HttpStatusCode.BadRequest:
|
||||
Log.Error("Something has been incorrectly formatted in your credentials file.\n" +
|
||||
"Use the JSON Guide as reference to fix it and restart the bot.");
|
||||
"Use the JSON Guide as reference to fix it and restart the bot"
|
||||
);
|
||||
Log.Error("If you are on Linux, make sure Redis is installed and running");
|
||||
break;
|
||||
|
||||
case HttpStatusCode.RequestTimeout:
|
||||
Log.Error("The request timed out. Make sure you have no external program blocking the bot " +
|
||||
"from connecting to the internet");
|
||||
"from connecting to the internet"
|
||||
);
|
||||
break;
|
||||
|
||||
case HttpStatusCode.ServiceUnavailable:
|
||||
@@ -38,7 +41,8 @@ public class LoginErrorHandler
|
||||
|
||||
case HttpStatusCode.TooManyRequests:
|
||||
Log.Error("Your bot has been ratelimited by Discord. Please, try again later.\n" +
|
||||
"Global ratelimits usually last for an hour");
|
||||
"Global ratelimits usually last for an hour"
|
||||
);
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -46,6 +50,6 @@ public class LoginErrorHandler
|
||||
break;
|
||||
}
|
||||
|
||||
Log.Fatal(ex.ToString());
|
||||
Log.Fatal(ex, "Fatal error occurred while loading credentials");
|
||||
}
|
||||
}
|
@@ -1,6 +0,0 @@
|
||||
namespace NadekoBot.Common.ModuleBehaviors;
|
||||
|
||||
public interface IInputTransformer
|
||||
{
|
||||
Task<string> TransformInput(IGuild guild, IMessageChannel channel, IUser user, string input);
|
||||
}
|
10
src/NadekoBot/Common/ModuleBehaviors/IInputTransformer.cs
Normal file
10
src/NadekoBot/Common/ModuleBehaviors/IInputTransformer.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace NadekoBot.Common.ModuleBehaviors;
|
||||
|
||||
public interface IInputTransformer
|
||||
{
|
||||
Task<string> TransformInput(
|
||||
IGuild guild,
|
||||
IMessageChannel channel,
|
||||
IUser user,
|
||||
string input);
|
||||
}
|
@@ -3,6 +3,6 @@
|
||||
public interface ILateBlocker
|
||||
{
|
||||
public int Priority { get; }
|
||||
|
||||
|
||||
Task<bool> TryBlockLate(ICommandContext context, string moduleName, CommandInfo command);
|
||||
}
|
@@ -1,60 +1,78 @@
|
||||
using System.Globalization;
|
||||
// ReSharper disable InconsistentNaming
|
||||
|
||||
namespace NadekoBot.Modules;
|
||||
|
||||
[UsedImplicitly(ImplicitUseTargetFlags.Default
|
||||
| ImplicitUseTargetFlags.WithInheritors
|
||||
| ImplicitUseTargetFlags.WithMembers)]
|
||||
[UsedImplicitly(ImplicitUseTargetFlags.Default |
|
||||
ImplicitUseTargetFlags.WithInheritors |
|
||||
ImplicitUseTargetFlags.WithMembers
|
||||
)]
|
||||
public abstract class NadekoModule : ModuleBase
|
||||
{
|
||||
protected CultureInfo _cultureInfo { get; set; }
|
||||
protected CultureInfo Culture { get; set; }
|
||||
public IBotStrings Strings { get; set; }
|
||||
public CommandHandler CmdHandler { get; set; }
|
||||
public ILocalization Localization { get; set; }
|
||||
public IEmbedBuilderService _eb { get; set; }
|
||||
|
||||
public string Prefix => CmdHandler.GetPrefix(ctx.Guild);
|
||||
public string Prefix
|
||||
=> CmdHandler.GetPrefix(ctx.Guild);
|
||||
|
||||
protected ICommandContext ctx => Context;
|
||||
|
||||
protected NadekoModule()
|
||||
{
|
||||
}
|
||||
protected ICommandContext ctx
|
||||
=> Context;
|
||||
|
||||
protected override void BeforeExecute(CommandInfo cmd)
|
||||
=> _cultureInfo = Localization.GetCultureInfo(ctx.Guild?.Id);
|
||||
=> Culture = Localization.GetCultureInfo(ctx.Guild?.Id);
|
||||
|
||||
protected string GetText(in LocStr data) =>
|
||||
Strings.GetText(data, _cultureInfo);
|
||||
protected string GetText(in LocStr data)
|
||||
=> Strings.GetText(data, Culture);
|
||||
|
||||
public Task<IUserMessage> SendErrorAsync(string error)
|
||||
=> ctx.Channel.SendErrorAsync(_eb, error);
|
||||
|
||||
public Task<IUserMessage> SendErrorAsync(string title, string error, string url = null, string footer = null)
|
||||
=> ctx.Channel.SendErrorAsync(_eb, title, error, url, footer);
|
||||
|
||||
|
||||
public Task<IUserMessage> SendErrorAsync(
|
||||
string title,
|
||||
string error,
|
||||
string url = null,
|
||||
string footer = null)
|
||||
=> ctx.Channel.SendErrorAsync(_eb,
|
||||
title,
|
||||
error,
|
||||
url,
|
||||
footer
|
||||
);
|
||||
|
||||
public Task<IUserMessage> SendConfirmAsync(string text)
|
||||
=> ctx.Channel.SendConfirmAsync(_eb, text);
|
||||
|
||||
public Task<IUserMessage> SendConfirmAsync(string title, string text, string url = null, string footer = null)
|
||||
=> ctx.Channel.SendConfirmAsync(_eb, title, text, url, footer);
|
||||
|
||||
|
||||
public Task<IUserMessage> SendConfirmAsync(
|
||||
string title,
|
||||
string text,
|
||||
string url = null,
|
||||
string footer = null)
|
||||
=> ctx.Channel.SendConfirmAsync(_eb,
|
||||
title,
|
||||
text,
|
||||
url,
|
||||
footer
|
||||
);
|
||||
|
||||
public Task<IUserMessage> SendPendingAsync(string text)
|
||||
=> ctx.Channel.SendPendingAsync(_eb, text);
|
||||
|
||||
public Task<IUserMessage> ErrorLocalizedAsync(LocStr str)
|
||||
|
||||
public Task<IUserMessage> ErrorLocalizedAsync(LocStr str)
|
||||
=> SendErrorAsync(GetText(str));
|
||||
|
||||
public Task<IUserMessage> PendingLocalizedAsync(LocStr str)
|
||||
public Task<IUserMessage> PendingLocalizedAsync(LocStr str)
|
||||
=> SendPendingAsync(GetText(str));
|
||||
|
||||
public Task<IUserMessage> ConfirmLocalizedAsync(LocStr str)
|
||||
|
||||
public Task<IUserMessage> ConfirmLocalizedAsync(LocStr str)
|
||||
=> SendConfirmAsync(GetText(str));
|
||||
|
||||
public Task<IUserMessage> ReplyErrorLocalizedAsync(LocStr str)
|
||||
public Task<IUserMessage> ReplyErrorLocalizedAsync(LocStr str)
|
||||
=> SendErrorAsync($"{Format.Bold(ctx.User.ToString())} {GetText(str)}");
|
||||
|
||||
public Task<IUserMessage> ReplyPendingLocalizedAsync(LocStr str)
|
||||
public Task<IUserMessage> ReplyPendingLocalizedAsync(LocStr str)
|
||||
=> SendPendingAsync($"{Format.Bold(ctx.User.ToString())} {GetText(str)}");
|
||||
|
||||
public Task<IUserMessage> ReplyConfirmLocalizedAsync(LocStr str)
|
||||
@@ -62,17 +80,19 @@ public abstract class NadekoModule : ModuleBase
|
||||
|
||||
public async Task<bool> PromptUserConfirmAsync(IEmbedBuilder embed)
|
||||
{
|
||||
embed
|
||||
.WithPendingColor()
|
||||
embed.WithPendingColor()
|
||||
.WithFooter("yes/no");
|
||||
|
||||
var msg = await ctx.Channel.EmbedAsync(embed).ConfigureAwait(false);
|
||||
var msg = await ctx.Channel.EmbedAsync(embed)
|
||||
.ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
var input = await GetUserInputAsync(ctx.User.Id, ctx.Channel.Id).ConfigureAwait(false);
|
||||
var input = await GetUserInputAsync(ctx.User.Id, ctx.Channel.Id)
|
||||
.ConfigureAwait(false);
|
||||
input = input?.ToUpperInvariant();
|
||||
|
||||
if (input != "YES" && input != "Y")
|
||||
if (input != "YES" &&
|
||||
input != "Y")
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -94,7 +114,9 @@ public abstract class NadekoModule : ModuleBase
|
||||
{
|
||||
dsc.MessageReceived += MessageReceived;
|
||||
|
||||
if (await Task.WhenAny(userInputTask.Task, Task.Delay(10000)).ConfigureAwait(false) != userInputTask.Task)
|
||||
if (await Task.WhenAny(userInputTask.Task, Task.Delay(10000))
|
||||
.ConfigureAwait(false) !=
|
||||
userInputTask.Task)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
@@ -109,21 +131,23 @@ public abstract class NadekoModule : ModuleBase
|
||||
Task MessageReceived(SocketMessage arg)
|
||||
{
|
||||
var _ = Task.Run(() =>
|
||||
{
|
||||
if (arg is not SocketUserMessage userMsg ||
|
||||
userMsg.Channel is not ITextChannel chan ||
|
||||
userMsg.Author.Id != userId ||
|
||||
userMsg.Channel.Id != channelId)
|
||||
{
|
||||
if (arg is not SocketUserMessage userMsg ||
|
||||
userMsg.Channel is not ITextChannel ||
|
||||
userMsg.Author.Id != userId ||
|
||||
userMsg.Channel.Id != channelId)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
if (userInputTask.TrySetResult(arg.Content))
|
||||
{
|
||||
userMsg.DeleteAfter(1);
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
if (userInputTask.TrySetResult(arg.Content))
|
||||
{
|
||||
userMsg.DeleteAfter(1);
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
@@ -132,20 +156,12 @@ public abstract class NadekoModule : ModuleBase
|
||||
public abstract class NadekoModule<TService> : NadekoModule
|
||||
{
|
||||
public TService _service { get; set; }
|
||||
|
||||
protected NadekoModule() : base()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class NadekoSubmodule : NadekoModule
|
||||
{
|
||||
protected NadekoSubmodule() : base() { }
|
||||
}
|
||||
|
||||
public abstract class NadekoSubmodule<TService> : NadekoModule<TService>
|
||||
{
|
||||
protected NadekoSubmodule() : base()
|
||||
{
|
||||
}
|
||||
}
|
@@ -1,6 +0,0 @@
|
||||
namespace NadekoBot.Modules;
|
||||
|
||||
public static class NadekoModuleExtensions
|
||||
{
|
||||
|
||||
}
|
@@ -6,7 +6,8 @@ public class NadekoRandom : Random
|
||||
{
|
||||
private readonly RandomNumberGenerator _rng;
|
||||
|
||||
public NadekoRandom() : base()
|
||||
public NadekoRandom()
|
||||
: base()
|
||||
=> _rng = RandomNumberGenerator.Create();
|
||||
|
||||
public override int Next()
|
||||
|
@@ -1,12 +1,15 @@
|
||||
namespace NadekoBot.Common;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace NadekoBot.Common;
|
||||
|
||||
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
|
||||
[SuppressMessage("Style", "IDE0022:Use expression body for methods")]
|
||||
public sealed class NoPublicBotAttribute : PreconditionAttribute
|
||||
{
|
||||
public override Task<PreconditionResult> CheckPermissionsAsync(ICommandContext context, CommandInfo command, IServiceProvider services)
|
||||
{
|
||||
#if GLOBAL_NADEKO
|
||||
return Task.FromResult(PreconditionResult.FromError("Not available on the public bot. To learn how to selfhost a private bot, click [here](https://nadekobot.readthedocs.io/en/latest/)."));
|
||||
return Task.FromResult(PreconditionResult.FromError("Not available on the public bot. To learn how to selfhost a private bot, click [here](https://nadekobot.readthedocs.io/en/latest/)."));
|
||||
#else
|
||||
return Task.FromResult(PreconditionResult.FromSuccess());
|
||||
#endif
|
||||
|
@@ -4,15 +4,18 @@ namespace NadekoBot.Common;
|
||||
|
||||
public static class OptionsParser
|
||||
{
|
||||
public static T ParseFrom<T>(string[] args) where T : INadekoCommandOptions, new()
|
||||
public static T ParseFrom<T>(string[] args)
|
||||
where T : INadekoCommandOptions, new()
|
||||
=> ParseFrom(new T(), args).Item1;
|
||||
|
||||
public static (T, bool) ParseFrom<T>(T options, string[] args) where T : INadekoCommandOptions
|
||||
public static (T, bool) ParseFrom<T>(T options, string[] args)
|
||||
where T : INadekoCommandOptions
|
||||
{
|
||||
using var p = new Parser(x =>
|
||||
{
|
||||
x.HelpWriter = null;
|
||||
});
|
||||
{
|
||||
x.HelpWriter = null;
|
||||
}
|
||||
);
|
||||
var res = p.ParseArguments<T>(args);
|
||||
options = res.MapResult(x => x, x => options);
|
||||
options.NormalizeOptions();
|
||||
|
@@ -4,37 +4,54 @@ namespace NadekoBot.Common;
|
||||
|
||||
public class OsuUserBests
|
||||
{
|
||||
[JsonProperty("beatmap_id")] public string BeatmapId { get; set; }
|
||||
[JsonProperty("beatmap_id")]
|
||||
public string BeatmapId { get; set; }
|
||||
|
||||
[JsonProperty("score_id")] public string ScoreId { get; set; }
|
||||
[JsonProperty("score_id")]
|
||||
public string ScoreId { get; set; }
|
||||
|
||||
[JsonProperty("score")] public string Score { get; set; }
|
||||
[JsonProperty("score")]
|
||||
public string Score { get; set; }
|
||||
|
||||
[JsonProperty("maxcombo")] public string Maxcombo { get; set; }
|
||||
[JsonProperty("maxcombo")]
|
||||
public string Maxcombo { get; set; }
|
||||
|
||||
[JsonProperty("count50")] public double Count50 { get; set; }
|
||||
[JsonProperty("count50")]
|
||||
public double Count50 { get; set; }
|
||||
|
||||
[JsonProperty("count100")] public double Count100 { get; set; }
|
||||
[JsonProperty("count100")]
|
||||
public double Count100 { get; set; }
|
||||
|
||||
[JsonProperty("count300")] public double Count300 { get; set; }
|
||||
[JsonProperty("count300")]
|
||||
public double Count300 { get; set; }
|
||||
|
||||
[JsonProperty("countmiss")] public int Countmiss { get; set; }
|
||||
[JsonProperty("countmiss")]
|
||||
public int Countmiss { get; set; }
|
||||
|
||||
[JsonProperty("countkatu")] public double Countkatu { get; set; }
|
||||
[JsonProperty("countkatu")]
|
||||
public double Countkatu { get; set; }
|
||||
|
||||
[JsonProperty("countgeki")] public double Countgeki { get; set; }
|
||||
[JsonProperty("countgeki")]
|
||||
public double Countgeki { get; set; }
|
||||
|
||||
[JsonProperty("perfect")] public string Perfect { get; set; }
|
||||
[JsonProperty("perfect")]
|
||||
public string Perfect { get; set; }
|
||||
|
||||
[JsonProperty("enabled_mods")] public int EnabledMods { get; set; }
|
||||
[JsonProperty("enabled_mods")]
|
||||
public int EnabledMods { get; set; }
|
||||
|
||||
[JsonProperty("user_id")] public string UserId { get; set; }
|
||||
[JsonProperty("user_id")]
|
||||
public string UserId { get; set; }
|
||||
|
||||
[JsonProperty("date")] public string Date { get; set; }
|
||||
[JsonProperty("date")]
|
||||
public string Date { get; set; }
|
||||
|
||||
[JsonProperty("rank")] public string Rank { get; set; }
|
||||
[JsonProperty("rank")]
|
||||
public string Rank { get; set; }
|
||||
|
||||
[JsonProperty("pp")] public double Pp { get; set; }
|
||||
[JsonProperty("pp")]
|
||||
public double Pp { get; set; }
|
||||
|
||||
[JsonProperty("replay_available")] public string ReplayAvailable { get; set; }
|
||||
[JsonProperty("replay_available")]
|
||||
public string ReplayAvailable { get; set; }
|
||||
}
|
@@ -2,21 +2,24 @@
|
||||
|
||||
public static class PlatformHelper
|
||||
{
|
||||
private const int ProcessorCountRefreshIntervalMs = 30000;
|
||||
private const int PROCESSOR_COUNT_REFRESH_INTERVAL_MS = 30000;
|
||||
|
||||
private static volatile int _processorCount;
|
||||
private static volatile int _lastProcessorCountRefreshTicks;
|
||||
private static volatile int processorCount;
|
||||
private static volatile int lastProcessorCountRefreshTicks;
|
||||
|
||||
public static int ProcessorCount {
|
||||
get {
|
||||
public static int ProcessorCount
|
||||
{
|
||||
get
|
||||
{
|
||||
var now = Environment.TickCount;
|
||||
if (_processorCount == 0 || now - _lastProcessorCountRefreshTicks >= ProcessorCountRefreshIntervalMs)
|
||||
if (processorCount == 0 ||
|
||||
now - lastProcessorCountRefreshTicks >= PROCESSOR_COUNT_REFRESH_INTERVAL_MS)
|
||||
{
|
||||
_processorCount = Environment.ProcessorCount;
|
||||
_lastProcessorCountRefreshTicks = now;
|
||||
processorCount = Environment.ProcessorCount;
|
||||
lastProcessorCountRefreshTicks = now;
|
||||
}
|
||||
|
||||
return _processorCount;
|
||||
return processorCount;
|
||||
}
|
||||
}
|
||||
}
|
@@ -12,19 +12,21 @@ public class SearchPokemon
|
||||
|
||||
public class BaseStatsClass
|
||||
{
|
||||
public int HP { get; set; }
|
||||
public int ATK { get; set; }
|
||||
public int DEF { get; set; }
|
||||
public int SPA { get; set; }
|
||||
public int SPD { get; set; }
|
||||
public int SPE { get; set; }
|
||||
public int Hp { get; set; }
|
||||
public int Atk { get; set; }
|
||||
public int Def { get; set; }
|
||||
public int Spa { get; set; }
|
||||
public int Spd { get; set; }
|
||||
public int Spe { get; set; }
|
||||
|
||||
public override string ToString() => $@"💚**HP:** {HP,-4} ⚔**ATK:** {ATK,-4} 🛡**DEF:** {DEF,-4}
|
||||
✨**SPA:** {SPA,-4} 🎇**SPD:** {SPD,-4} 💨**SPE:** {SPE,-4}";
|
||||
public override string ToString()
|
||||
=> $@"💚**HP:** {Hp,-4} ⚔**ATK:** {Atk,-4} 🛡**DEF:** {Def,-4}
|
||||
✨**SPA:** {Spa,-4} 🎇**SPD:** {Spd,-4} 💨**SPE:** {Spe,-4}";
|
||||
}
|
||||
|
||||
[JsonProperty("num")]
|
||||
public int Id { get; set; }
|
||||
|
||||
public string Species { get; set; }
|
||||
public string[] Types { get; set; }
|
||||
public GenderRatioClass GenderRatio { get; set; }
|
||||
|
@@ -3,22 +3,20 @@
|
||||
public class EventPubSub : IPubSub
|
||||
{
|
||||
private readonly Dictionary<string, Dictionary<Delegate, List<Func<object, ValueTask>>>> _actions = new();
|
||||
private readonly object locker = new();
|
||||
|
||||
private readonly object _locker = new();
|
||||
|
||||
public Task Sub<TData>(in TypedKey<TData> key, Func<TData, ValueTask> action)
|
||||
{
|
||||
Func<object, ValueTask> localAction = obj => action((TData) obj);
|
||||
lock(locker)
|
||||
Func<object, ValueTask> localAction = obj => action((TData)obj);
|
||||
lock (_locker)
|
||||
{
|
||||
Dictionary<Delegate, List<Func<object, ValueTask>>> keyActions;
|
||||
if (!_actions.TryGetValue(key.Key, out keyActions))
|
||||
if (!_actions.TryGetValue(key.Key, out var keyActions))
|
||||
{
|
||||
keyActions = new();
|
||||
_actions[key.Key] = keyActions;
|
||||
}
|
||||
|
||||
List<Func<object, ValueTask>> sameActions;
|
||||
if (!keyActions.TryGetValue(action, out sameActions))
|
||||
if (!keyActions.TryGetValue(action, out var sameActions))
|
||||
{
|
||||
sameActions = new();
|
||||
keyActions[action] = sameActions;
|
||||
@@ -29,19 +27,17 @@ public class EventPubSub : IPubSub
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public Task Pub<TData>(in TypedKey<TData> key, TData data)
|
||||
{
|
||||
lock (locker)
|
||||
lock (_locker)
|
||||
{
|
||||
if(_actions.TryGetValue(key.Key, out var actions))
|
||||
if (_actions.TryGetValue(key.Key, out var actions))
|
||||
{
|
||||
// if this class ever gets used, this needs to be properly implemented
|
||||
// 1. ignore all valuetasks which are completed
|
||||
// 2. return task.whenall all other tasks
|
||||
return Task.WhenAll(actions
|
||||
.SelectMany(kvp => kvp.Value)
|
||||
.Select(action => action(data).AsTask()));
|
||||
return Task.WhenAll(actions.SelectMany(kvp => kvp.Value).Select(action => action(data).AsTask()));
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
@@ -50,12 +46,11 @@ public class EventPubSub : IPubSub
|
||||
|
||||
public Task Unsub<TData>(in TypedKey<TData> key, Func<TData, ValueTask> action)
|
||||
{
|
||||
lock (locker)
|
||||
lock (_locker)
|
||||
{
|
||||
// get subscriptions for this action
|
||||
if (_actions.TryGetValue(key.Key, out var actions))
|
||||
{
|
||||
var hashCode = action.GetHashCode();
|
||||
// get subscriptions which have the same action hash code
|
||||
// note: having this as a list allows for multiple subscriptions of
|
||||
// the same insance's/static method
|
||||
@@ -63,13 +58,13 @@ public class EventPubSub : IPubSub
|
||||
{
|
||||
// remove last subscription
|
||||
sameActions.RemoveAt(sameActions.Count - 1);
|
||||
|
||||
|
||||
// if the last subscription was the only subscription
|
||||
// we can safely remove this action's dictionary entry
|
||||
if (sameActions.Count == 0)
|
||||
{
|
||||
actions.Remove(action);
|
||||
|
||||
|
||||
// if our dictionary has no more elements after
|
||||
// removing the entry
|
||||
// it's safe to remove it from the key's subscriptions
|
||||
|
@@ -5,23 +5,19 @@ namespace NadekoBot.Common;
|
||||
|
||||
public class JsonSeria : ISeria
|
||||
{
|
||||
private readonly JsonSerializerOptions serializerOptions = new()
|
||||
private readonly JsonSerializerOptions _serializerOptions = new()
|
||||
{
|
||||
Converters =
|
||||
{
|
||||
new Rgba32Converter(),
|
||||
new CultureInfoConverter(),
|
||||
}
|
||||
Converters = { new Rgba32Converter(), new CultureInfoConverter(), }
|
||||
};
|
||||
public byte[] Serialize<T>(T data)
|
||||
=> JsonSerializer.SerializeToUtf8Bytes(data, serializerOptions);
|
||||
|
||||
public byte[] Serialize<T>(T data)
|
||||
=> JsonSerializer.SerializeToUtf8Bytes(data, _serializerOptions);
|
||||
|
||||
public T Deserialize<T>(byte[] data)
|
||||
{
|
||||
if (data is null)
|
||||
return default;
|
||||
|
||||
|
||||
return JsonSerializer.Deserialize<T>(data, serializerOptions);
|
||||
return JsonSerializer.Deserialize<T>(data, _serializerOptions);
|
||||
}
|
||||
}
|
@@ -18,13 +18,15 @@ public sealed class RedisPubSub : IPubSub
|
||||
public Task Pub<TData>(in TypedKey<TData> key, TData data)
|
||||
{
|
||||
var serialized = _serializer.Serialize(data);
|
||||
return _multi.GetSubscriber().PublishAsync($"{_creds.RedisKey()}:{key.Key}", serialized, CommandFlags.FireAndForget);
|
||||
return _multi.GetSubscriber()
|
||||
.PublishAsync($"{_creds.RedisKey()}:{key.Key}", serialized, CommandFlags.FireAndForget);
|
||||
}
|
||||
|
||||
public Task Sub<TData>(in TypedKey<TData> key, Func<TData, ValueTask> action)
|
||||
{
|
||||
var eventName = key.Key;
|
||||
return _multi.GetSubscriber().SubscribeAsync($"{_creds.RedisKey()}:{eventName}", async (ch, data) =>
|
||||
|
||||
async void OnSubscribeHandler(RedisChannel _, RedisValue data)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -33,8 +35,10 @@ public sealed class RedisPubSub : IPubSub
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error($"Error handling the event {eventName}: {ex.Message}");
|
||||
Log.Error("Error handling the event {EventName}: {ErrorMessage}", eventName, ex.Message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return _multi.GetSubscriber().SubscribeAsync($"{_creds.RedisKey()}:{eventName}", OnSubscribeHandler);
|
||||
}
|
||||
}
|
@@ -9,18 +9,22 @@ public readonly struct TypedKey<TData>
|
||||
|
||||
public static implicit operator TypedKey<TData>(in string input)
|
||||
=> new(input);
|
||||
|
||||
public static implicit operator string(in TypedKey<TData> input)
|
||||
=> input.Key;
|
||||
|
||||
public static bool operator ==(in TypedKey<TData> left, in TypedKey<TData> right)
|
||||
=> left.Key == right.Key;
|
||||
|
||||
public static bool operator !=(in TypedKey<TData> left, in TypedKey<TData> right)
|
||||
=> !(left == right);
|
||||
|
||||
public override bool Equals(object obj)
|
||||
=> obj is TypedKey<TData> o && o == this;
|
||||
|
||||
public override int GetHashCode() => Key?.GetHashCode() ?? 0;
|
||||
public override int GetHashCode()
|
||||
=> Key?.GetHashCode() ?? 0;
|
||||
|
||||
public override string ToString() => Key;
|
||||
public override string ToString()
|
||||
=> Key;
|
||||
}
|
@@ -10,28 +10,31 @@ public class YamlSeria : IConfigSeria
|
||||
private readonly ISerializer _serializer;
|
||||
private readonly IDeserializer _deserializer;
|
||||
|
||||
private static readonly Regex CodePointRegex
|
||||
= new(@"(\\U(?<code>[a-zA-Z0-9]{8})|\\u(?<code>[a-zA-Z0-9]{4})|\\x(?<code>[a-zA-Z0-9]{2}))",
|
||||
RegexOptions.Compiled);
|
||||
private static readonly Regex _codePointRegex =
|
||||
new(@"(\\U(?<code>[a-zA-Z0-9]{8})|\\u(?<code>[a-zA-Z0-9]{4})|\\x(?<code>[a-zA-Z0-9]{2}))",
|
||||
RegexOptions.Compiled
|
||||
);
|
||||
|
||||
public YamlSeria()
|
||||
{
|
||||
_serializer = Yaml.Serializer;
|
||||
_deserializer = Yaml.Deserializer;
|
||||
}
|
||||
|
||||
|
||||
public string Serialize<T>(T obj)
|
||||
{
|
||||
var escapedOutput = _serializer.Serialize(obj);
|
||||
var output = CodePointRegex.Replace(escapedOutput, me =>
|
||||
{
|
||||
var str = me.Groups["code"].Value;
|
||||
var newString = YamlHelper.UnescapeUnicodeCodePoint(str);
|
||||
return newString;
|
||||
});
|
||||
var output = _codePointRegex.Replace(escapedOutput,
|
||||
me =>
|
||||
{
|
||||
var str = me.Groups["code"].Value;
|
||||
var newString = YamlHelper.UnescapeUnicodeCodePoint(str);
|
||||
return newString;
|
||||
}
|
||||
);
|
||||
return output;
|
||||
}
|
||||
|
||||
public T Deserialize<T>(string data)
|
||||
public T Deserialize<T>(string data)
|
||||
=> _deserializer.Deserialize<T>(data);
|
||||
}
|
@@ -5,21 +5,29 @@ namespace NadekoBot.Common;
|
||||
|
||||
public class ReplacementBuilder
|
||||
{
|
||||
private static readonly Regex rngRegex = new("%rng(?:(?<from>(?:-)?\\d+)-(?<to>(?:-)?\\d+))?%", RegexOptions.Compiled);
|
||||
private static readonly Regex _rngRegex = new("%rng(?:(?<from>(?:-)?\\d+)-(?<to>(?:-)?\\d+))?%",
|
||||
RegexOptions.Compiled
|
||||
);
|
||||
|
||||
private readonly ConcurrentDictionary<string, Func<string>> _reps = new();
|
||||
private readonly ConcurrentDictionary<Regex, Func<Match, string>> _regex = new();
|
||||
|
||||
public ReplacementBuilder()
|
||||
=> WithRngRegex();
|
||||
|
||||
public ReplacementBuilder WithDefault(IUser usr, IMessageChannel ch, SocketGuild g, DiscordSocketClient client)
|
||||
=> this.WithUser(usr)
|
||||
.WithChannel(ch)
|
||||
.WithServer(client, g)
|
||||
.WithClient(client);
|
||||
public ReplacementBuilder WithDefault(
|
||||
IUser usr,
|
||||
IMessageChannel ch,
|
||||
SocketGuild g,
|
||||
DiscordSocketClient client)
|
||||
=> this.WithUser(usr).WithChannel(ch).WithServer(client, g).WithClient(client);
|
||||
|
||||
public ReplacementBuilder WithDefault(ICommandContext ctx) =>
|
||||
WithDefault(ctx.User, ctx.Channel, ctx.Guild as SocketGuild, (DiscordSocketClient)ctx.Client);
|
||||
public ReplacementBuilder WithDefault(ICommandContext ctx)
|
||||
=> WithDefault(ctx.User,
|
||||
ctx.Channel,
|
||||
ctx.Guild as SocketGuild,
|
||||
(DiscordSocketClient)ctx.Client
|
||||
);
|
||||
|
||||
public ReplacementBuilder WithMention(DiscordSocketClient client)
|
||||
{
|
||||
@@ -30,12 +38,14 @@ public class ReplacementBuilder
|
||||
public ReplacementBuilder WithClient(DiscordSocketClient client)
|
||||
{
|
||||
WithMention(client);
|
||||
|
||||
|
||||
_reps.TryAdd("%bot.status%", () => client.Status.ToString());
|
||||
_reps.TryAdd("%bot.latency%", () => client.Latency.ToString());
|
||||
_reps.TryAdd("%bot.name%", () => client.CurrentUser.Username);
|
||||
_reps.TryAdd("%bot.fullname%", () => client.CurrentUser.ToString());
|
||||
_reps.TryAdd("%bot.time%", () => DateTime.Now.ToString("HH:mm " + TimeZoneInfo.Local.StandardName.GetInitials()));
|
||||
_reps.TryAdd("%bot.time%",
|
||||
() => DateTime.Now.ToString("HH:mm " + TimeZoneInfo.Local.StandardName.GetInitials())
|
||||
);
|
||||
_reps.TryAdd("%bot.discrim%", () => client.CurrentUser.Discriminator);
|
||||
_reps.TryAdd("%bot.id%", () => client.CurrentUser.Id.ToString());
|
||||
_reps.TryAdd("%bot.avatar%", () => client.CurrentUser.RealAvatarUrl()?.ToString());
|
||||
@@ -51,19 +61,20 @@ public class ReplacementBuilder
|
||||
_reps.TryAdd("%server.members%", () => g is { } sg ? sg.MemberCount.ToString() : "?");
|
||||
_reps.TryAdd("%server.boosters%", () => g.PremiumSubscriptionCount.ToString());
|
||||
_reps.TryAdd("%server.boost_level%", () => ((int)g.PremiumTier).ToString());
|
||||
_reps.TryAdd("%server.time%", () =>
|
||||
{
|
||||
var to = TimeZoneInfo.Local;
|
||||
if (g != null)
|
||||
_reps.TryAdd("%server.time%",
|
||||
() =>
|
||||
{
|
||||
if (GuildTimezoneService.AllServices.TryGetValue(client.CurrentUser.Id, out var tz))
|
||||
to = tz.GetTimeZoneOrDefault(g.Id) ?? TimeZoneInfo.Local;
|
||||
}
|
||||
var to = TimeZoneInfo.Local;
|
||||
if (g != null)
|
||||
{
|
||||
if (GuildTimezoneService.AllServices.TryGetValue(client.CurrentUser.Id, out var tz))
|
||||
to = tz.GetTimeZoneOrDefault(g.Id) ?? TimeZoneInfo.Local;
|
||||
}
|
||||
|
||||
return TimeZoneInfo.ConvertTime(DateTime.UtcNow,
|
||||
TimeZoneInfo.Utc,
|
||||
to).ToString("HH:mm ") + to.StandardName.GetInitials();
|
||||
});
|
||||
return TimeZoneInfo.ConvertTime(DateTime.UtcNow, TimeZoneInfo.Utc, to).ToString("HH:mm ") +
|
||||
to.StandardName.GetInitials();
|
||||
}
|
||||
);
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -80,7 +91,7 @@ public class ReplacementBuilder
|
||||
|
||||
public ReplacementBuilder WithUser(IUser user)
|
||||
{
|
||||
WithManyUsers(new[] {user});
|
||||
WithManyUsers(new[] { user });
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -92,10 +103,18 @@ public class ReplacementBuilder
|
||||
_reps.TryAdd("%user.discrim%", () => string.Join(" ", users.Select(user => user.Discriminator)));
|
||||
_reps.TryAdd("%user.avatar%", () => string.Join(" ", users.Select(user => user.RealAvatarUrl()?.ToString())));
|
||||
_reps.TryAdd("%user.id%", () => string.Join(" ", users.Select(user => user.Id.ToString())));
|
||||
_reps.TryAdd("%user.created_time%", () => string.Join(" ", users.Select(user => user.CreatedAt.ToString("HH:mm"))));
|
||||
_reps.TryAdd("%user.created_date%", () => string.Join(" ", users.Select(user => user.CreatedAt.ToString("dd.MM.yyyy"))));
|
||||
_reps.TryAdd("%user.joined_time%", () => string.Join(" ", users.Select(user => (user as IGuildUser)?.JoinedAt?.ToString("HH:mm") ?? "-")));
|
||||
_reps.TryAdd("%user.joined_date%", () => string.Join(" ", users.Select(user => (user as IGuildUser)?.JoinedAt?.ToString("dd.MM.yyyy") ?? "-")));
|
||||
_reps.TryAdd("%user.created_time%",
|
||||
() => string.Join(" ", users.Select(user => user.CreatedAt.ToString("HH:mm")))
|
||||
);
|
||||
_reps.TryAdd("%user.created_date%",
|
||||
() => string.Join(" ", users.Select(user => user.CreatedAt.ToString("dd.MM.yyyy")))
|
||||
);
|
||||
_reps.TryAdd("%user.joined_time%",
|
||||
() => string.Join(" ", users.Select(user => (user as IGuildUser)?.JoinedAt?.ToString("HH:mm") ?? "-"))
|
||||
);
|
||||
_reps.TryAdd("%user.joined_date%",
|
||||
() => string.Join(" ", users.Select(user => (user as IGuildUser)?.JoinedAt?.ToString("dd.MM.yyyy") ?? "-"))
|
||||
);
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -110,21 +129,24 @@ public class ReplacementBuilder
|
||||
public ReplacementBuilder WithRngRegex()
|
||||
{
|
||||
var rng = new NadekoRandom();
|
||||
_regex.TryAdd(rngRegex, match =>
|
||||
{
|
||||
if (!int.TryParse(match.Groups["from"].ToString(), out var from))
|
||||
from = 0;
|
||||
if (!int.TryParse(match.Groups["to"].ToString(), out var to))
|
||||
to = 0;
|
||||
_regex.TryAdd(_rngRegex,
|
||||
match =>
|
||||
{
|
||||
if (!int.TryParse(match.Groups["from"].ToString(), out var from))
|
||||
from = 0;
|
||||
if (!int.TryParse(match.Groups["to"].ToString(), out var to))
|
||||
to = 0;
|
||||
|
||||
if (from == 0 && to == 0)
|
||||
return rng.Next(0, 11).ToString();
|
||||
if (from == 0 &&
|
||||
to == 0)
|
||||
return rng.Next(0, 11).ToString();
|
||||
|
||||
if (from >= to)
|
||||
return string.Empty;
|
||||
if (from >= to)
|
||||
return string.Empty;
|
||||
|
||||
return rng.Next(from, to + 1).ToString();
|
||||
});
|
||||
return rng.Next(from, to + 1).ToString();
|
||||
}
|
||||
);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@@ -18,10 +18,10 @@ public class Replacer
|
||||
if (string.IsNullOrWhiteSpace(input))
|
||||
return input;
|
||||
|
||||
foreach (var (Key, Text) in _replacements)
|
||||
foreach (var (key, text) in _replacements)
|
||||
{
|
||||
if (input.Contains(Key))
|
||||
input = input.Replace(Key, Text(), StringComparison.InvariantCulture);
|
||||
if (input.Contains(key))
|
||||
input = input.Replace(key, text(), StringComparison.InvariantCulture);
|
||||
}
|
||||
|
||||
foreach (var item in _regex)
|
||||
@@ -58,8 +58,7 @@ public class Replacer
|
||||
{
|
||||
newEmbedData.Author = new()
|
||||
{
|
||||
Name = Replace(embedData.Author.Name),
|
||||
IconUrl = Replace(embedData.Author.IconUrl)
|
||||
Name = Replace(embedData.Author.Name), IconUrl = Replace(embedData.Author.IconUrl)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -70,9 +69,7 @@ public class Replacer
|
||||
{
|
||||
var newF = new SmartTextEmbedField
|
||||
{
|
||||
Name = Replace(f.Name),
|
||||
Value = Replace(f.Value),
|
||||
Inline = f.Inline
|
||||
Name = Replace(f.Name), Value = Replace(f.Value), Inline = f.Inline
|
||||
};
|
||||
fields.Add(newF);
|
||||
}
|
||||
@@ -84,8 +81,7 @@ public class Replacer
|
||||
{
|
||||
newEmbedData.Footer = new()
|
||||
{
|
||||
Text = Replace(embedData.Footer.Text),
|
||||
IconUrl = Replace(embedData.Footer.IconUrl)
|
||||
Text = Replace(embedData.Footer.Text), IconUrl = Replace(embedData.Footer.IconUrl)
|
||||
};
|
||||
}
|
||||
|
||||
|
@@ -24,9 +24,7 @@ public struct ShmartNumber : IEquatable<ShmartNumber>
|
||||
=> Value.ToString();
|
||||
|
||||
public override bool Equals(object obj)
|
||||
=> obj is ShmartNumber sn
|
||||
? Equals(sn)
|
||||
: false;
|
||||
=> obj is ShmartNumber sn && Equals(sn);
|
||||
|
||||
public bool Equals(ShmartNumber other)
|
||||
=> other.Value == Value;
|
||||
|
@@ -15,14 +15,13 @@ public sealed record SmartEmbedText : SmartText
|
||||
|
||||
public uint Color { get; set; } = 7458112;
|
||||
|
||||
public bool IsValid =>
|
||||
!string.IsNullOrWhiteSpace(Title) ||
|
||||
!string.IsNullOrWhiteSpace(Description) ||
|
||||
!string.IsNullOrWhiteSpace(Url) ||
|
||||
!string.IsNullOrWhiteSpace(Thumbnail) ||
|
||||
!string.IsNullOrWhiteSpace(Image) ||
|
||||
(Footer != null && (!string.IsNullOrWhiteSpace(Footer.Text) || !string.IsNullOrWhiteSpace(Footer.IconUrl))) ||
|
||||
Fields is { Length: > 0 };
|
||||
public bool IsValid
|
||||
=> !string.IsNullOrWhiteSpace(Title) || !string.IsNullOrWhiteSpace(Description) ||
|
||||
!string.IsNullOrWhiteSpace(Url) || !string.IsNullOrWhiteSpace(Thumbnail) ||
|
||||
!string.IsNullOrWhiteSpace(Image) ||
|
||||
(Footer != null &&
|
||||
(!string.IsNullOrWhiteSpace(Footer.Text) || !string.IsNullOrWhiteSpace(Footer.IconUrl))) ||
|
||||
Fields is { Length: > 0 };
|
||||
|
||||
public static SmartEmbedText FromEmbed(IEmbed eb, string plainText = null)
|
||||
{
|
||||
@@ -34,42 +33,23 @@ public sealed record SmartEmbedText : SmartText
|
||||
Url = eb.Url,
|
||||
Thumbnail = eb.Thumbnail?.Url,
|
||||
Image = eb.Image?.Url,
|
||||
Author = eb.Author is { } ea
|
||||
? new()
|
||||
{
|
||||
Name = ea.Name,
|
||||
Url = ea.Url,
|
||||
IconUrl = ea.IconUrl
|
||||
}
|
||||
: null,
|
||||
Footer = eb.Footer is { } ef
|
||||
? new()
|
||||
{
|
||||
Text = ef.Text,
|
||||
IconUrl = ef.IconUrl
|
||||
}
|
||||
: null
|
||||
Author = eb.Author is { } ea ? new() { Name = ea.Name, Url = ea.Url, IconUrl = ea.IconUrl } : null,
|
||||
Footer = eb.Footer is { } ef ? new() { Text = ef.Text, IconUrl = ef.IconUrl } : null
|
||||
};
|
||||
|
||||
if (eb.Fields.Length > 0)
|
||||
set.Fields = eb
|
||||
.Fields
|
||||
.Select(field => new SmartTextEmbedField()
|
||||
{
|
||||
Inline = field.Inline,
|
||||
Name = field.Name,
|
||||
Value = field.Value,
|
||||
})
|
||||
set.Fields = eb.Fields.Select(field
|
||||
=> new SmartTextEmbedField() { Inline = field.Inline, Name = field.Name, Value = field.Value, }
|
||||
)
|
||||
.ToArray();
|
||||
|
||||
set.Color = eb.Color?.RawValue ?? 0;
|
||||
return set;
|
||||
}
|
||||
|
||||
|
||||
public EmbedBuilder GetEmbed()
|
||||
{
|
||||
var embed = new EmbedBuilder()
|
||||
.WithColor(Color);
|
||||
var embed = new EmbedBuilder().WithColor(Color);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(Title))
|
||||
embed.WithTitle(Title);
|
||||
@@ -77,26 +57,31 @@ public sealed record SmartEmbedText : SmartText
|
||||
if (!string.IsNullOrWhiteSpace(Description))
|
||||
embed.WithDescription(Description);
|
||||
|
||||
if (Url != null && Uri.IsWellFormedUriString(Url, UriKind.Absolute))
|
||||
if (Url != null &&
|
||||
Uri.IsWellFormedUriString(Url, UriKind.Absolute))
|
||||
embed.WithUrl(Url);
|
||||
|
||||
if (Footer != null)
|
||||
{
|
||||
embed.WithFooter(efb =>
|
||||
{
|
||||
efb.WithText(Footer.Text);
|
||||
if (Uri.IsWellFormedUriString(Footer.IconUrl, UriKind.Absolute))
|
||||
efb.WithIconUrl(Footer.IconUrl);
|
||||
});
|
||||
{
|
||||
efb.WithText(Footer.Text);
|
||||
if (Uri.IsWellFormedUriString(Footer.IconUrl, UriKind.Absolute))
|
||||
efb.WithIconUrl(Footer.IconUrl);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
if (Thumbnail != null && Uri.IsWellFormedUriString(Thumbnail, UriKind.Absolute))
|
||||
if (Thumbnail != null &&
|
||||
Uri.IsWellFormedUriString(Thumbnail, UriKind.Absolute))
|
||||
embed.WithThumbnailUrl(Thumbnail);
|
||||
|
||||
if (Image != null && Uri.IsWellFormedUriString(Image, UriKind.Absolute))
|
||||
if (Image != null &&
|
||||
Uri.IsWellFormedUriString(Image, UriKind.Absolute))
|
||||
embed.WithImageUrl(Image);
|
||||
|
||||
if (Author != null && !string.IsNullOrWhiteSpace(Author.Name))
|
||||
if (Author != null &&
|
||||
!string.IsNullOrWhiteSpace(Author.Name))
|
||||
{
|
||||
if (!Uri.IsWellFormedUriString(Author.IconUrl, UriKind.Absolute))
|
||||
Author.IconUrl = null;
|
||||
@@ -110,7 +95,8 @@ public sealed record SmartEmbedText : SmartText
|
||||
{
|
||||
foreach (var f in Fields)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(f.Name) && !string.IsNullOrWhiteSpace(f.Value))
|
||||
if (!string.IsNullOrWhiteSpace(f.Name) &&
|
||||
!string.IsNullOrWhiteSpace(f.Value))
|
||||
embed.AddField(f.Name, f.Value, f.Inline);
|
||||
}
|
||||
}
|
||||
|
@@ -4,8 +4,11 @@ namespace NadekoBot;
|
||||
|
||||
public abstract record SmartText
|
||||
{
|
||||
public bool IsEmbed => this is SmartEmbedText;
|
||||
public bool IsPlainText => this is SmartPlainText;
|
||||
public bool IsEmbed
|
||||
=> this is SmartEmbedText;
|
||||
|
||||
public bool IsPlainText
|
||||
=> this is SmartPlainText;
|
||||
|
||||
public static SmartText operator +(SmartText text, string input)
|
||||
=> text switch
|
||||
@@ -14,7 +17,7 @@ public abstract record SmartText
|
||||
SmartPlainText spt => new SmartPlainText(spt.Text + input),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(text))
|
||||
};
|
||||
|
||||
|
||||
public static SmartText operator +(string input, SmartText text)
|
||||
=> text switch
|
||||
{
|
||||
@@ -22,10 +25,11 @@ public abstract record SmartText
|
||||
SmartPlainText spt => new SmartPlainText(input + spt.Text),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(text))
|
||||
};
|
||||
|
||||
|
||||
public static SmartText CreateFrom(string input)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(input) || !input.TrimStart().StartsWith("{"))
|
||||
if (string.IsNullOrWhiteSpace(input) ||
|
||||
!input.TrimStart().StartsWith("{"))
|
||||
{
|
||||
return new SmartPlainText(input);
|
||||
}
|
||||
@@ -34,6 +38,9 @@ public abstract record SmartText
|
||||
{
|
||||
var smartEmbedText = JsonConvert.DeserializeObject<SmartEmbedText>(input);
|
||||
|
||||
if (smartEmbedText is null)
|
||||
throw new();
|
||||
|
||||
smartEmbedText.NormalizeFields();
|
||||
|
||||
if (!smartEmbedText.IsValid)
|
||||
|
@@ -5,8 +5,7 @@ namespace NadekoBot;
|
||||
public class SmartTextEmbedAuthor
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string IconUrl { get; set; }
|
||||
[JsonProperty("icon_url")]
|
||||
private string Icon_Url { set => IconUrl = value; }
|
||||
public string IconUrl { get; set; }
|
||||
public string Url { get; set; }
|
||||
}
|
@@ -2,10 +2,11 @@
|
||||
|
||||
namespace NadekoBot;
|
||||
|
||||
// todo test smarttextembedfooter and smarttextembedauthor
|
||||
|
||||
public class SmartTextEmbedFooter
|
||||
{
|
||||
public string Text { get; set; }
|
||||
public string IconUrl { get; set; }
|
||||
[JsonProperty("icon_url")]
|
||||
private string Icon_Url { set => IconUrl = value; }
|
||||
public string IconUrl { get; set; }
|
||||
}
|
@@ -17,36 +17,37 @@ public sealed class ReactionEventWrapper : IDisposable
|
||||
_client.ReactionsCleared += Discord_ReactionsCleared;
|
||||
}
|
||||
|
||||
private Task Discord_ReactionsCleared(
|
||||
Cacheable<IUserMessage, ulong> msg,
|
||||
Cacheable<IMessageChannel, ulong> channel)
|
||||
private Task Discord_ReactionsCleared(Cacheable<IUserMessage, ulong> msg, Cacheable<IMessageChannel, ulong> channel)
|
||||
{
|
||||
Task.Run(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (msg.Id == Message.Id)
|
||||
OnReactionsCleared?.Invoke();
|
||||
try
|
||||
{
|
||||
if (msg.Id == Message.Id)
|
||||
OnReactionsCleared?.Invoke();
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
catch { }
|
||||
});
|
||||
);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private Task Discord_ReactionRemoved(
|
||||
Cacheable<IUserMessage, ulong> msg,
|
||||
Cacheable<IMessageChannel, ulong> cacheable, SocketReaction reaction)
|
||||
Cacheable<IMessageChannel, ulong> cacheable,
|
||||
SocketReaction reaction)
|
||||
{
|
||||
Task.Run(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (msg.Id == Message.Id)
|
||||
OnReactionRemoved?.Invoke(reaction);
|
||||
try
|
||||
{
|
||||
if (msg.Id == Message.Id)
|
||||
OnReactionRemoved?.Invoke(reaction);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
catch { }
|
||||
});
|
||||
);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
@@ -57,16 +58,17 @@ public sealed class ReactionEventWrapper : IDisposable
|
||||
SocketReaction reaction)
|
||||
{
|
||||
Task.Run(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (msg.Id == Message.Id)
|
||||
OnReactionAdded?.Invoke(reaction);
|
||||
try
|
||||
{
|
||||
if (msg.Id == Message.Id)
|
||||
OnReactionAdded?.Invoke(reaction);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
});
|
||||
);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
@@ -36,10 +36,7 @@ public sealed class CommandOrCrTypeReader : NadekoTypeReader<CommandOrCrInfo>
|
||||
private readonly CustomReactionsService _crs;
|
||||
private readonly CommandHandler _commandHandler;
|
||||
|
||||
public CommandOrCrTypeReader(
|
||||
CommandService cmds,
|
||||
CustomReactionsService crs,
|
||||
CommandHandler commandHandler)
|
||||
public CommandOrCrTypeReader(CommandService cmds, CustomReactionsService crs, CommandHandler commandHandler)
|
||||
{
|
||||
_cmds = cmds;
|
||||
_crs = crs;
|
||||
@@ -58,8 +55,12 @@ public sealed class CommandOrCrTypeReader : NadekoTypeReader<CommandOrCrInfo>
|
||||
var cmd = await new CommandTypeReader(_commandHandler, _cmds).ReadAsync(context, input).ConfigureAwait(false);
|
||||
if (cmd.IsSuccess)
|
||||
{
|
||||
return TypeReaderResult.FromSuccess(new CommandOrCrInfo(((CommandInfo)cmd.Values.First().Value).Name, CommandOrCrInfo.Type.Normal));
|
||||
return TypeReaderResult.FromSuccess(new CommandOrCrInfo(((CommandInfo)cmd.Values.First().Value).Name,
|
||||
CommandOrCrInfo.Type.Normal
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return TypeReaderResult.FromError(CommandError.ParseFailed, "No such command or cr found.");
|
||||
}
|
||||
}
|
||||
@@ -74,7 +75,9 @@ public class CommandOrCrInfo
|
||||
|
||||
public string Name { get; set; }
|
||||
public Type CmdType { get; set; }
|
||||
public bool IsCustom => CmdType == Type.Custom;
|
||||
|
||||
public bool IsCustom
|
||||
=> CmdType == Type.Custom;
|
||||
|
||||
public CommandOrCrInfo(string input, Type type)
|
||||
{
|
||||
|
@@ -12,8 +12,11 @@ public sealed class GuildDateTimeTypeReader : NadekoTypeReader<GuildDateTime>
|
||||
public override Task<TypeReaderResult> ReadAsync(ICommandContext context, string input)
|
||||
{
|
||||
var gdt = Parse(context.Guild.Id, input);
|
||||
if(gdt is null)
|
||||
return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, "Input string is in an incorrect format."));
|
||||
if (gdt is null)
|
||||
return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed,
|
||||
"Input string is in an incorrect format."
|
||||
)
|
||||
);
|
||||
|
||||
return Task.FromResult(TypeReaderResult.FromSuccess(gdt));
|
||||
}
|
||||
|
@@ -10,7 +10,9 @@ public sealed class ModuleTypeReader : NadekoTypeReader<ModuleInfo>
|
||||
public override Task<TypeReaderResult> ReadAsync(ICommandContext context, string input)
|
||||
{
|
||||
input = input.ToUpperInvariant();
|
||||
var module = _cmds.Modules.GroupBy(m => m.GetTopLevelModule()).FirstOrDefault(m => m.Key.Name.ToUpperInvariant() == input)?.Key;
|
||||
var module = _cmds.Modules.GroupBy(m => m.GetTopLevelModule())
|
||||
.FirstOrDefault(m => m.Key.Name.ToUpperInvariant() == input)
|
||||
?.Key;
|
||||
if (module is null)
|
||||
return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, "No such module found."));
|
||||
|
||||
@@ -28,14 +30,14 @@ public sealed class ModuleOrCrTypeReader : NadekoTypeReader<ModuleOrCrInfo>
|
||||
public override Task<TypeReaderResult> ReadAsync(ICommandContext context, string input)
|
||||
{
|
||||
input = input.ToUpperInvariant();
|
||||
var module = _cmds.Modules.GroupBy(m => m.GetTopLevelModule()).FirstOrDefault(m => m.Key.Name.ToUpperInvariant() == input)?.Key;
|
||||
if (module is null && input != "ACTUALCUSTOMREACTIONS")
|
||||
var module = _cmds.Modules.GroupBy(m => m.GetTopLevelModule())
|
||||
.FirstOrDefault(m => m.Key.Name.ToUpperInvariant() == input)
|
||||
?.Key;
|
||||
if (module is null &&
|
||||
input != "ACTUALCUSTOMREACTIONS")
|
||||
return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, "No such module found."));
|
||||
|
||||
return Task.FromResult(TypeReaderResult.FromSuccess(new ModuleOrCrInfo
|
||||
{
|
||||
Name = input,
|
||||
}));
|
||||
return Task.FromResult(TypeReaderResult.FromSuccess(new ModuleOrCrInfo { Name = input, }));
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1,5 +1,6 @@
|
||||
namespace NadekoBot.Common.TypeReaders;
|
||||
|
||||
[MeansImplicitUse(ImplicitUseTargetFlags.Default | ImplicitUseTargetFlags.WithInheritors )]
|
||||
public abstract class NadekoTypeReader<T> : TypeReader
|
||||
{
|
||||
public abstract Task<TypeReaderResult> ReadAsync(ICommandContext ctx, string input);
|
||||
|
@@ -63,12 +63,10 @@ public sealed class ShmartNumberTypeReader : NadekoTypeReader<ShmartNumber>
|
||||
case "MAX":
|
||||
args.Result = Max(ctx);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly Regex percentRegex = new(@"^((?<num>100|\d{1,2})%)$", RegexOptions.Compiled);
|
||||
private static readonly Regex _percentRegex = new(@"^((?<num>100|\d{1,2})%)$", RegexOptions.Compiled);
|
||||
|
||||
private long Cur(ICommandContext ctx)
|
||||
{
|
||||
@@ -80,15 +78,13 @@ public sealed class ShmartNumberTypeReader : NadekoTypeReader<ShmartNumber>
|
||||
{
|
||||
var settings = _gambling.Data;
|
||||
var max = settings.MaxBet;
|
||||
return max == 0
|
||||
? Cur(ctx)
|
||||
: max;
|
||||
return max == 0 ? Cur(ctx) : max;
|
||||
}
|
||||
|
||||
private bool TryHandlePercentage(ICommandContext ctx, string input, out long num)
|
||||
{
|
||||
num = 0;
|
||||
var m = percentRegex.Match(input);
|
||||
var m = _percentRegex.Match(input);
|
||||
if (m.Captures.Count != 0)
|
||||
{
|
||||
if (!long.TryParse(m.Groups["num"].ToString(), out var percent))
|
||||
@@ -97,6 +93,7 @@ public sealed class ShmartNumberTypeReader : NadekoTypeReader<ShmartNumber>
|
||||
num = (long)(Cur(ctx) * (percent / 100.0f));
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
@@ -12,9 +12,7 @@ public class CommentGatheringTypeInspector : TypeInspectorSkeleton
|
||||
=> this.innerTypeDescriptor = innerTypeDescriptor ?? throw new ArgumentNullException("innerTypeDescriptor");
|
||||
|
||||
public override IEnumerable<IPropertyDescriptor> GetProperties(Type type, object container)
|
||||
=> innerTypeDescriptor
|
||||
.GetProperties(type, container)
|
||||
.Select(d => new CommentsPropertyDescriptor(d));
|
||||
=> innerTypeDescriptor.GetProperties(type, container).Select(d => new CommentsPropertyDescriptor(d));
|
||||
|
||||
private sealed class CommentsPropertyDescriptor : IPropertyDescriptor
|
||||
{
|
||||
@@ -31,14 +29,16 @@ public class CommentGatheringTypeInspector : TypeInspectorSkeleton
|
||||
public Type Type
|
||||
=> baseDescriptor.Type;
|
||||
|
||||
public Type TypeOverride {
|
||||
public Type TypeOverride
|
||||
{
|
||||
get => baseDescriptor.TypeOverride;
|
||||
set => baseDescriptor.TypeOverride = value;
|
||||
}
|
||||
|
||||
public int Order { get; set; }
|
||||
|
||||
public ScalarStyle ScalarStyle {
|
||||
public ScalarStyle ScalarStyle
|
||||
{
|
||||
get => baseDescriptor.ScalarStyle;
|
||||
set => baseDescriptor.ScalarStyle = value;
|
||||
}
|
||||
@@ -49,7 +49,8 @@ public class CommentGatheringTypeInspector : TypeInspectorSkeleton
|
||||
public void Write(object target, object value)
|
||||
=> baseDescriptor.Write(target, value);
|
||||
|
||||
public T GetCustomAttribute<T>() where T : Attribute
|
||||
public T GetCustomAttribute<T>()
|
||||
where T : Attribute
|
||||
=> baseDescriptor.GetCustomAttribute<T>();
|
||||
|
||||
public IObjectDescriptor Read(object target)
|
||||
|
@@ -14,8 +14,8 @@ public class CommentsObjectGraphVisitor : ChainedObjectGraphVisitor
|
||||
|
||||
public override bool EnterMapping(IPropertyDescriptor key, IObjectDescriptor value, IEmitter context)
|
||||
{
|
||||
var commentsDescriptor = value as CommentsObjectDescriptor;
|
||||
if (commentsDescriptor != null && !string.IsNullOrWhiteSpace(commentsDescriptor.Comment))
|
||||
if (value is CommentsObjectDescriptor commentsDescriptor &&
|
||||
!string.IsNullOrWhiteSpace(commentsDescriptor.Comment))
|
||||
{
|
||||
context.Emit(new Comment(commentsDescriptor.Comment.Replace("\n", "\n# "), false));
|
||||
}
|
||||
|
@@ -7,11 +7,12 @@ namespace NadekoBot.Common.Yml;
|
||||
public class MultilineScalarFlowStyleEmitter : ChainedEventEmitter
|
||||
{
|
||||
public MultilineScalarFlowStyleEmitter(IEventEmitter nextEmitter)
|
||||
: base(nextEmitter) { }
|
||||
: base(nextEmitter)
|
||||
{
|
||||
}
|
||||
|
||||
public override void Emit(ScalarEventInfo eventInfo, IEmitter emitter)
|
||||
{
|
||||
|
||||
if (typeof(string).IsAssignableFrom(eventInfo.Source.Type))
|
||||
{
|
||||
var value = eventInfo.Source.Value as string;
|
||||
@@ -19,10 +20,7 @@ public class MultilineScalarFlowStyleEmitter : ChainedEventEmitter
|
||||
{
|
||||
var isMultiLine = value.IndexOfAny(new char[] { '\r', '\n', '\x85', '\x2028', '\x2029' }) >= 0;
|
||||
if (isMultiLine)
|
||||
eventInfo = new(eventInfo.Source)
|
||||
{
|
||||
Style = ScalarStyle.Literal,
|
||||
};
|
||||
eventInfo = new(eventInfo.Source) { Style = ScalarStyle.Literal, };
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -21,11 +21,11 @@ public class Rgba32Converter : IYamlTypeConverter
|
||||
public void WriteYaml(IEmitter emitter, object value, Type type)
|
||||
{
|
||||
var color = (Rgba32)value;
|
||||
var val = (uint) ((color.B << 0) | (color.G << 8) | (color.R << 16));
|
||||
var val = (uint)((color.B << 0) | (color.G << 8) | (color.R << 16));
|
||||
emitter.Emit(new Scalar(val.ToString("X6").ToLower()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public class CultureInfoConverter : IYamlTypeConverter
|
||||
{
|
||||
public bool Accepts(Type type)
|
||||
|
@@ -4,22 +4,23 @@ namespace NadekoBot.Common.Yml;
|
||||
|
||||
public class Yaml
|
||||
{
|
||||
public static ISerializer Serializer => new SerializerBuilder()
|
||||
.WithTypeInspector(inner => new CommentGatheringTypeInspector(inner))
|
||||
.WithEmissionPhaseObjectGraphVisitor(args => new CommentsObjectGraphVisitor(args.InnerVisitor))
|
||||
.WithEventEmitter(args => new MultilineScalarFlowStyleEmitter(args))
|
||||
.WithNamingConvention(YamlDotNet.Serialization.NamingConventions.CamelCaseNamingConvention.Instance)
|
||||
.WithIndentedSequences()
|
||||
.WithTypeConverter(new Rgba32Converter())
|
||||
.WithTypeConverter(new CultureInfoConverter())
|
||||
.WithTypeConverter(new UriConverter())
|
||||
.Build();
|
||||
public static ISerializer Serializer
|
||||
=> new SerializerBuilder().WithTypeInspector(inner => new CommentGatheringTypeInspector(inner))
|
||||
.WithEmissionPhaseObjectGraphVisitor(args => new CommentsObjectGraphVisitor(args.InnerVisitor))
|
||||
.WithEventEmitter(args => new MultilineScalarFlowStyleEmitter(args))
|
||||
.WithNamingConvention(YamlDotNet.Serialization.NamingConventions.CamelCaseNamingConvention.Instance)
|
||||
.WithIndentedSequences()
|
||||
.WithTypeConverter(new Rgba32Converter())
|
||||
.WithTypeConverter(new CultureInfoConverter())
|
||||
.WithTypeConverter(new UriConverter())
|
||||
.Build();
|
||||
|
||||
public static IDeserializer Deserializer => new DeserializerBuilder()
|
||||
.WithNamingConvention(YamlDotNet.Serialization.NamingConventions.CamelCaseNamingConvention.Instance)
|
||||
.WithTypeConverter(new Rgba32Converter())
|
||||
.WithTypeConverter(new CultureInfoConverter())
|
||||
.WithTypeConverter(new UriConverter())
|
||||
.IgnoreUnmatchedProperties()
|
||||
.Build();
|
||||
public static IDeserializer Deserializer
|
||||
=> new DeserializerBuilder()
|
||||
.WithNamingConvention(YamlDotNet.Serialization.NamingConventions.CamelCaseNamingConvention.Instance)
|
||||
.WithTypeConverter(new Rgba32Converter())
|
||||
.WithTypeConverter(new CultureInfoConverter())
|
||||
.WithTypeConverter(new UriConverter())
|
||||
.IgnoreUnmatchedProperties()
|
||||
.Build();
|
||||
}
|
@@ -15,27 +15,28 @@ public class YamlHelper
|
||||
|
||||
// Scan the character value.
|
||||
|
||||
foreach(var c in point)
|
||||
foreach (var c in point)
|
||||
{
|
||||
if (!IsHex(c))
|
||||
{
|
||||
return point;
|
||||
}
|
||||
|
||||
character = (character << 4) + AsHex(c);
|
||||
}
|
||||
|
||||
// Check the value and write the character.
|
||||
|
||||
if (character is >= 0xD800 and <= 0xDFFF or > 0x10FFFF)
|
||||
if (character is (>= 0xD800 and <= 0xDFFF) or > 0x10FFFF)
|
||||
{
|
||||
return point;
|
||||
}
|
||||
|
||||
return char.ConvertFromUtf32(character);
|
||||
}
|
||||
|
||||
|
||||
public static bool IsHex(char c)
|
||||
=> c is >= '0' and <= '9' or >= 'A' and <= 'F' or >= 'a' and <= 'f';
|
||||
=> c is (>= '0' and <= '9') or (>= 'A' and <= 'F') or (>= 'a' and <= 'f');
|
||||
|
||||
public static int AsHex(char c)
|
||||
{
|
||||
@@ -43,10 +44,12 @@ public class YamlHelper
|
||||
{
|
||||
return c - '0';
|
||||
}
|
||||
|
||||
if (c <= 'F')
|
||||
{
|
||||
return c - 'A' + 10;
|
||||
}
|
||||
|
||||
return c - 'a' + 10;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user