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:
Kwoth
2021-12-27 03:46:30 +01:00
parent 9ae030a5c5
commit 1b0392dfab
85 changed files with 1015 additions and 906 deletions

View File

@@ -173,6 +173,14 @@ csharp_preserve_single_line_statements = true
# Naming rules
dotnet_naming_rule.private_readonly_field.symbols = private_readonly_field
dotnet_naming_rule.private_readonly_field.style = begins_with_underscore
dotnet_naming_rule.private_readonly_field.severity = warning
dotnet_naming_rule.private_field.symbols = private_field
dotnet_naming_rule.private_field.style = camel_case
dotnet_naming_rule.private_field.severity = warning
dotnet_naming_rule.const_fields.symbols = const_fields
dotnet_naming_rule.const_fields.style = all_upper
dotnet_naming_rule.const_fields.severity = warning
@@ -209,10 +217,6 @@ dotnet_naming_rule.async_method_should_be_ends_with_async.severity = error
dotnet_naming_rule.async_method_should_be_ends_with_async.symbols = async_method
dotnet_naming_rule.async_method_should_be_ends_with_async.style = ends_with_async
dotnet_naming_rule.private_field_should_be_begins_with_underscore.severity = error
dotnet_naming_rule.private_field_should_be_begins_with_underscore.symbols = private_field
dotnet_naming_rule.private_field_should_be_begins_with_underscore.style = begins_with_underscore
dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = error
dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
@@ -221,10 +225,6 @@ dotnet_naming_rule.local_variable_should_be_camel_case.severity = error
dotnet_naming_rule.local_variable_should_be_camel_case.symbols = local_variable
dotnet_naming_rule.local_variable_should_be_camel_case.style = camel_case
dotnet_naming_rule.public_anything_should_be_pascal_case.severity = error
dotnet_naming_rule.public_anything_should_be_pascal_case.symbols = public_anything
dotnet_naming_rule.public_anything_should_be_pascal_case.style = pascal_case
# Symbol specifications
dotnet_naming_symbols.const_fields.required_modifiers = const
@@ -262,8 +262,12 @@ dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, meth
dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.non_field_members.required_modifiers =
dotnet_naming_symbols.private_readonly_field.applicable_kinds = field
dotnet_naming_symbols.private_readonly_field.applicable_accessibilities = private
dotnet_naming_symbols.private_readonly_field.required_modifiers = readonly
dotnet_naming_symbols.private_field.applicable_kinds = field
dotnet_naming_symbols.private_field.applicable_accessibilities = private
dotnet_naming_symbols.private_field.applicable_accessibilities = private, protected
dotnet_naming_symbols.private_field.required_modifiers =
dotnet_naming_symbols.async_method.applicable_kinds = method, local_function
@@ -274,10 +278,6 @@ dotnet_naming_symbols.local_variable.applicable_kinds = parameter, local
dotnet_naming_symbols.local_variable.applicable_accessibilities = local
dotnet_naming_symbols.local_variable.required_modifiers =
dotnet_naming_symbols.public_anything.applicable_kinds = property, field, event, class, struct, interface, enum, delegate, method
dotnet_naming_symbols.public_anything.applicable_accessibilities = public, internal
dotnet_naming_symbols.public_anything.required_modifiers =
# Naming styles
@@ -330,8 +330,11 @@ resharper_csharp_wrap_parameters_style = chop_if_long
resharper_force_chop_compound_if_expression = true
resharper_keep_existing_linebreaks = false
resharper_max_formal_parameters_on_line = 3
resharper_wrap_chained_binary_expressions = chop_if_long
resharper_wrap_chained_binary_patterns = chop_if_long
resharper_wrap_chained_method_calls = chop_always
resharper_csharp_wrap_before_first_type_parameter_constraint=true
resharper_csharp_place_type_constraints_on_same_line=false
resharper_csharp_wrap_before_extends_colon=true
resharper_csharp_place_constructor_initializer_on_same_line=false
resharper_csharp_wrap_before_first_type_parameter_constraint = true
resharper_csharp_place_type_constraints_on_same_line = false
resharper_csharp_wrap_before_extends_colon = true
resharper_csharp_place_constructor_initializer_on_same_line = false

View File

@@ -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,
}

View File

@@ -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();

View File

@@ -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;

View File

@@ -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)

View File

@@ -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,24 +40,29 @@ 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; }
@@ -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.

View File

@@ -11,22 +11,24 @@ 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")]
@@ -36,23 +38,28 @@ public sealed class Creds : IBotCredentials
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.")]
@@ -68,22 +75,27 @@ go to https://www.patreon.com/portal -> my clients -> create client")]
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")]
@@ -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;

View File

@@ -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();
}
}
}

View File

@@ -1,6 +1,7 @@
namespace NadekoBot.Common;
public interface ICloneable<T> where T : new()
public interface ICloneable<T>
where T : new()
{
public T Clone();
}

View File

@@ -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);

View 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());
}

View File

@@ -4,10 +4,13 @@ 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;
@@ -22,10 +25,10 @@ public readonly struct kwum : IEquatable<kwum>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int InternalCharToValue(in char c)
=> ValidCharacters.IndexOf(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;
@@ -62,22 +65,23 @@ public readonly struct kwum : IEquatable<kwum>
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);

View File

@@ -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()
{
}
}

View File

@@ -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");
}
}

View File

@@ -1,6 +0,0 @@
namespace NadekoBot.Common.ModuleBehaviors;
public interface IInputTransformer
{
Task<string> TransformInput(IGuild guild, IMessageChannel channel, IUser user, string input);
}

View File

@@ -0,0 +1,10 @@
namespace NadekoBot.Common.ModuleBehaviors;
public interface IInputTransformer
{
Task<string> TransformInput(
IGuild guild,
IMessageChannel channel,
IUser user,
string input);
}

View File

@@ -1,43 +1,61 @@
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);
@@ -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;
}
@@ -111,7 +133,7 @@ public abstract class NadekoModule : ModuleBase
var _ = Task.Run(() =>
{
if (arg is not SocketUserMessage userMsg ||
userMsg.Channel is not ITextChannel chan ||
userMsg.Channel is not ITextChannel ||
userMsg.Author.Id != userId ||
userMsg.Channel.Id != channelId)
{
@@ -122,8 +144,10 @@ public abstract class NadekoModule : ModuleBase
{
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()
{
}
}

View File

@@ -1,6 +0,0 @@
namespace NadekoBot.Modules;
public static class NadekoModuleExtensions
{
}

View File

@@ -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()

View File

@@ -1,6 +1,9 @@
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)

View File

@@ -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;
});
}
);
var res = p.ParseArguments<T>(args);
options = res.MapResult(x => x, x => options);
options.NormalizeOptions();

View File

@@ -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; }
}

View File

@@ -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 {
var now = Environment.TickCount;
if (_processorCount == 0 || now - _lastProcessorCountRefreshTicks >= ProcessorCountRefreshIntervalMs)
public static int ProcessorCount
{
_processorCount = Environment.ProcessorCount;
_lastProcessorCountRefreshTicks = now;
get
{
var now = Environment.TickCount;
if (processorCount == 0 ||
now - lastProcessorCountRefreshTicks >= PROCESSOR_COUNT_REFRESH_INTERVAL_MS)
{
processorCount = Environment.ProcessorCount;
lastProcessorCountRefreshTicks = now;
}
return _processorCount;
return processorCount;
}
}
}

View File

@@ -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; }

View File

@@ -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;
@@ -32,16 +30,14 @@ public class EventPubSub : IPubSub
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

View File

@@ -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);
=> 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);
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}

View File

@@ -10,9 +10,10 @@ 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()
{
@@ -23,12 +24,14 @@ public class YamlSeria : IConfigSeria
public string Serialize<T>(T obj)
{
var escapedOutput = _serializer.Serialize(obj);
var output = CodePointRegex.Replace(escapedOutput, me =>
var output = _codePointRegex.Replace(escapedOutput,
me =>
{
var str = me.Groups["code"].Value;
var newString = YamlHelper.UnescapeUnicodeCodePoint(str);
return newString;
});
}
);
return output;
}

View File

@@ -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)
{
@@ -35,7 +43,9 @@ public class ReplacementBuilder
_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,7 +61,8 @@ 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%", () =>
_reps.TryAdd("%server.time%",
() =>
{
var to = TimeZoneInfo.Local;
if (g != null)
@@ -60,10 +71,10 @@ public class ReplacementBuilder
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 =>
_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)
if (from == 0 &&
to == 0)
return rng.Next(0, 11).ToString();
if (from >= to)
return string.Empty;
return rng.Next(from, to + 1).ToString();
});
}
);
return this;
}

View File

@@ -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)
};
}

View File

@@ -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;

View File

@@ -15,13 +15,12 @@ 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) ||
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))) ||
(Footer != null &&
(!string.IsNullOrWhiteSpace(Footer.Text) || !string.IsNullOrWhiteSpace(Footer.IconUrl))) ||
Fields is { Length: > 0 };
public static SmartEmbedText FromEmbed(IEmbed eb, string plainText = null)
@@ -34,32 +33,14 @@ 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;
@@ -68,8 +49,7 @@ public sealed record SmartEmbedText : SmartText
public EmbedBuilder GetEmbed()
{
var embed = new EmbedBuilder()
.WithColor(Color);
var embed = new EmbedBuilder().WithColor(Color);
if (!string.IsNullOrWhiteSpace(Title))
embed.WithTitle(Title);
@@ -77,7 +57,8 @@ 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)
@@ -87,16 +68,20 @@ public sealed record SmartEmbedText : SmartText
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);
}
}

View File

@@ -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
@@ -25,7 +28,8 @@ public abstract record SmartText
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)

View File

@@ -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; }
}

View File

@@ -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; }
}

View File

@@ -17,9 +17,7 @@ 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(() =>
{
@@ -29,14 +27,16 @@ public sealed class ReactionEventWrapper : IDisposable
OnReactionsCleared?.Invoke();
}
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(() =>
{
@@ -46,7 +46,8 @@ public sealed class ReactionEventWrapper : IDisposable
OnReactionRemoved?.Invoke(reaction);
}
catch { }
});
}
);
return Task.CompletedTask;
}
@@ -66,7 +67,8 @@ public sealed class ReactionEventWrapper : IDisposable
catch
{
}
});
}
);
return Task.CompletedTask;
}

View File

@@ -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)
{

View File

@@ -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));
}

View File

@@ -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, }));
}
}

View File

@@ -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);

View File

@@ -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;
}
}

View File

@@ -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)

View File

@@ -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));
}

View File

@@ -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, };
}
}

View File

@@ -21,7 +21,7 @@ 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()));
}
}

View File

@@ -4,8 +4,8 @@ namespace NadekoBot.Common.Yml;
public class Yaml
{
public static ISerializer Serializer => new SerializerBuilder()
.WithTypeInspector(inner => new CommentGatheringTypeInspector(inner))
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)
@@ -15,7 +15,8 @@ public class Yaml
.WithTypeConverter(new UriConverter())
.Build();
public static IDeserializer Deserializer => new DeserializerBuilder()
public static IDeserializer Deserializer
=> new DeserializerBuilder()
.WithNamingConvention(YamlDotNet.Serialization.NamingConventions.CamelCaseNamingConvention.Instance)
.WithTypeConverter(new Rgba32Converter())
.WithTypeConverter(new CultureInfoConverter())

View File

@@ -15,18 +15,19 @@ 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;
}
@@ -35,7 +36,7 @@ public class YamlHelper
}
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;
}
}

View File

@@ -13,12 +13,13 @@ public static class ClubExtensions
.ThenInclude(x => x.User)
.Include(x => x.Users)
.AsQueryable();
public static ClubInfo GetByOwner(this DbSet<ClubInfo> clubs, ulong userId)
=> Include(clubs).FirstOrDefault(c => c.Owner.UserId == userId);
public static ClubInfo GetByOwnerOrAdmin(this DbSet<ClubInfo> clubs, ulong userId)
=> Include(clubs).FirstOrDefault(c => c.Owner.UserId == userId
|| c.Users.Any(u => u.UserId == userId && u.IsClubAdmin));
=> Include(clubs)
.FirstOrDefault(c => c.Owner.UserId == userId || c.Users.Any(u => u.UserId == userId && u.IsClubAdmin));
public static ClubInfo GetByMember(this DbSet<ClubInfo> clubs, ulong userId)
=> Include(clubs).FirstOrDefault(c => c.Users.Any(u => u.UserId == userId));
@@ -27,17 +28,9 @@ public static class ClubExtensions
=> Include(clubs).FirstOrDefault(c => c.Name.ToUpper() == name.ToUpper() && c.Discrim == discrim);
public static int GetNextDiscrim(this DbSet<ClubInfo> clubs, string name)
=> Include(clubs)
.Where(x => x.Name.ToUpper() == name.ToUpper())
.Select(x => x.Discrim)
.DefaultIfEmpty()
.Max() + 1;
=> Include(clubs).Where(x => x.Name.ToUpper() == name.ToUpper()).Select(x => x.Discrim).DefaultIfEmpty().Max() +
1;
public static List<ClubInfo> GetClubLeaderboardPage(this DbSet<ClubInfo> clubs, int page)
=> clubs
.AsNoTracking()
.OrderByDescending(x => x.Xp)
.Skip(page * 9)
.Take(9)
.ToList();
=> clubs.AsNoTracking().OrderByDescending(x => x.Xp).Skip(page * 9).Take(9).ToList();
}

View File

@@ -10,11 +10,7 @@ public static class CustomReactionsExtensions
=> crs.Delete(x => x.GuildId == guildId);
public static IEnumerable<CustomReaction> ForId(this DbSet<CustomReaction> crs, ulong id)
=> crs
.AsNoTracking()
.AsQueryable()
.Where(x => x.GuildId == id)
.ToArray();
=> crs.AsNoTracking().AsQueryable().Where(x => x.GuildId == id).ToList();
public static CustomReaction GetByGuildIdAndInput(this DbSet<CustomReaction> crs, ulong? guildId, string input)
=> crs.FirstOrDefault(x => x.GuildId == guildId && x.Trigger.ToUpper() == input);

View File

@@ -5,6 +5,7 @@ namespace NadekoBot.Db;
public static class DbExtensions
{
public static T GetById<T>(this DbSet<T> set, int id) where T: DbEntity
public static T GetById<T>(this DbSet<T> set, int id)
where T : DbEntity
=> set.FirstOrDefault(x => x.Id == id);
}

View File

@@ -8,10 +8,15 @@ namespace NadekoBot.Db;
public static class DiscordUserExtensions
{
public static void EnsureUserCreated(this NadekoContext ctx, ulong userId, string username, string discrim, string avatarId)
=> ctx.DiscordUser
.ToLinqToDBTable()
.InsertOrUpdate(() => new()
public static void EnsureUserCreated(
this NadekoContext ctx,
ulong userId,
string username,
string discrim,
string avatarId)
=> ctx.DiscordUser.ToLinqToDBTable()
.InsertOrUpdate(
() => new()
{
UserId = userId,
Username = username,
@@ -20,36 +25,44 @@ public static class DiscordUserExtensions
TotalXp = 0,
CurrencyAmount = 0
},
old => new()
{
Username = username,
Discriminator = discrim,
AvatarId = avatarId,
}, () => new()
{
UserId = userId
});
old => new() { Username = username, Discriminator = discrim, AvatarId = avatarId, },
() => new() { UserId = userId }
);
//temp is only used in updatecurrencystate, so that i don't overwrite real usernames/discrims with Unknown
public static DiscordUser GetOrCreateUser(this NadekoContext ctx, ulong userId, string username, string discrim, string avatarId)
public static DiscordUser GetOrCreateUser(
this NadekoContext ctx,
ulong userId,
string username,
string discrim,
string avatarId)
{
ctx.EnsureUserCreated(userId, username, discrim, avatarId);
return ctx.DiscordUser
.Include(x => x.Club)
ctx.EnsureUserCreated(userId,
username,
discrim,
avatarId
);
return ctx.DiscordUser.Include(x => x.Club)
.First(u => u.UserId == userId);
}
public static DiscordUser GetOrCreateUser(this NadekoContext ctx, IUser original)
=> ctx.GetOrCreateUser(original.Id, original.Username, original.Discriminator, original.AvatarId);
=> ctx.GetOrCreateUser(original.Id,
original.Username,
original.Discriminator,
original.AvatarId
);
public static int GetUserGlobalRank(this DbSet<DiscordUser> users, ulong id)
=> users.AsQueryable()
.Where(x => x.TotalXp > users
.AsQueryable()
.Where(x => x.TotalXp >
users.AsQueryable()
.Where(y => y.UserId == id)
.Select(y => y.TotalXp)
.FirstOrDefault())
.Count() + 1;
.FirstOrDefault()
)
.Count() +
1;
public static DiscordUser[] GetUsersXpLeaderboardFor(this DbSet<DiscordUser> users, int page)
=> users.AsQueryable()
@@ -59,7 +72,11 @@ public static class DiscordUserExtensions
.AsEnumerable()
.ToArray();
public static List<DiscordUser> GetTopRichest(this DbSet<DiscordUser> users, ulong botId, int count, int page = 0)
public static List<DiscordUser> GetTopRichest(
this DbSet<DiscordUser> users,
ulong botId,
int count,
int page = 0)
=> users.AsQueryable()
.Where(c => c.CurrencyAmount > 0 && botId != c.UserId)
.OrderByDescending(c => c.CurrencyAmount)
@@ -67,40 +84,44 @@ public static class DiscordUserExtensions
.Take(count)
.ToList();
public static List<DiscordUser> GetTopRichest(this DbSet<DiscordUser> users, ulong botId, int count)
=> users.AsQueryable()
.Where(c => c.CurrencyAmount > 0 && botId != c.UserId)
.OrderByDescending(c => c.CurrencyAmount)
.Take(count)
.ToList();
public static long GetUserCurrency(this DbSet<DiscordUser> users, ulong userId) =>
users.AsNoTracking()
public static long GetUserCurrency(this DbSet<DiscordUser> users, ulong userId)
=> users.AsNoTracking()
.FirstOrDefault(x => x.UserId == userId)
?.CurrencyAmount ?? 0;
?.CurrencyAmount ??
0;
public static void RemoveFromMany(this DbSet<DiscordUser> users, IEnumerable<ulong> ids)
{
var items = users.AsQueryable().Where(x => ids.Contains(x.UserId));
var items = users.AsQueryable()
.Where(x => ids.Contains(x.UserId));
foreach (var item in items)
{
item.CurrencyAmount = 0;
}
}
public static bool TryUpdateCurrencyState(this NadekoContext ctx, ulong userId, string name, string discrim, string avatarId, long amount, bool allowNegative = false)
public static bool TryUpdateCurrencyState(
this NadekoContext ctx,
ulong userId,
string name,
string discrim,
string avatarId,
long amount,
bool allowNegative = false)
{
if (amount == 0)
return true;
// if remove - try to remove if he has more or equal than the amount
// and return number of rows > 0 (was there a change)
if (amount < 0 && !allowNegative)
if (amount < 0 &&
!allowNegative)
{
var rows = ctx.Database.ExecuteSqlInterpolated($@"
UPDATE DiscordUser
SET CurrencyAmount=CurrencyAmount+{amount}
WHERE UserId={userId} AND CurrencyAmount>={-amount};");
WHERE UserId={userId} AND CurrencyAmount>={-amount};"
);
return rows > 0;
}
@@ -110,7 +131,8 @@ WHERE UserId={userId} AND CurrencyAmount>={-amount};");
var rows = ctx.Database.ExecuteSqlInterpolated($@"
UPDATE DiscordUser
SET CurrencyAmount=CurrencyAmount+{amount}
WHERE UserId={userId};");
WHERE UserId={userId};"
);
return rows > 0;
}
@@ -118,22 +140,22 @@ WHERE UserId={userId};");
// if it exists, sum current amount with the new one, if it doesn't
// he just has the new amount
var updatedUserData = !string.IsNullOrWhiteSpace(name);
name = name ?? "Unknown";
discrim = discrim ?? "????";
avatarId = avatarId ?? "";
name ??= "Unknown";
discrim ??= "????";
avatarId ??= "";
// just update the amount, there is no new user data
if (!updatedUserData)
{
var rows = ctx.Database.ExecuteSqlInterpolated($@"
ctx.Database.ExecuteSqlInterpolated($@"
UPDATE OR IGNORE DiscordUser
SET CurrencyAmount=CurrencyAmount+{amount}
WHERE UserId={userId};
INSERT OR IGNORE INTO DiscordUser (UserId, Username, Discriminator, AvatarId, CurrencyAmount, TotalXp)
VALUES ({userId}, {name}, {discrim}, {avatarId}, {amount}, 0);
");
"
);
}
else
{
@@ -147,14 +169,15 @@ WHERE UserId={userId};
INSERT OR IGNORE INTO DiscordUser (UserId, Username, Discriminator, AvatarId, CurrencyAmount, TotalXp)
VALUES ({userId}, {name}, {discrim}, {avatarId}, {amount}, 0);
");
"
);
}
return true;
}
public static decimal GetTotalCurrency(this DbSet<DiscordUser> users)
=> users
.Sum((Func<DiscordUser, decimal>)(x => x.CurrencyAmount));
=> users.Sum((Func<DiscordUser, decimal>)(x => x.CurrencyAmount));
public static decimal GetTopOnePercentCurrency(this DbSet<DiscordUser> users, ulong botId)
=> users.AsQueryable()

View File

@@ -21,9 +21,11 @@ public static class GuildConfigExtensions
/// <returns>Guild'p stream role settings</returns>
public static StreamRoleSettings GetStreamRoleSettings(this NadekoContext ctx, ulong guildId)
{
var conf = ctx.GuildConfigsForId(guildId, set => set.Include(y => y.StreamRole)
var conf = ctx.GuildConfigsForId(guildId,
set => set.Include(y => y.StreamRole)
.Include(y => y.StreamRole.Whitelist)
.Include(y => y.StreamRole.Blacklist));
.Include(y => y.StreamRole.Blacklist)
);
if (conf.StreamRole is null)
conf.StreamRole = new();
@@ -31,21 +33,15 @@ public static class GuildConfigExtensions
return conf.StreamRole;
}
private static List<WarningPunishment> DefaultWarnPunishments =>
new() {
new() {
Count = 3,
Punishment = PunishmentAction.Kick
},
new() {
Count = 5,
Punishment = PunishmentAction.Ban
}
private static List<WarningPunishment> DefaultWarnPunishments
=> new()
{
new() { Count = 3, Punishment = PunishmentAction.Kick },
new() { Count = 5, Punishment = PunishmentAction.Ban }
};
private static IQueryable<GuildConfig> IncludeEverything(this DbSet<GuildConfig> configs)
=> configs
.AsQueryable()
=> configs.AsQueryable()
.AsSplitQuery()
.Include(gc => gc.CommandCooldowns)
.Include(gc => gc.FollowedStreams)
@@ -56,9 +52,10 @@ public static class GuildConfigExtensions
.Include(gc => gc.ReactionRoleMessages)
.ThenInclude(x => x.ReactionRoles);
public static IEnumerable<GuildConfig> GetAllGuildConfigs(this DbSet<GuildConfig> configs, List<ulong> availableGuilds)
=> configs
.IncludeEverything()
public static IEnumerable<GuildConfig> GetAllGuildConfigs(
this DbSet<GuildConfig> configs,
List<ulong> availableGuilds)
=> configs.IncludeEverything()
.AsNoTracking()
.Where(x => availableGuilds.Contains(x.GuildId))
.ToList();
@@ -70,15 +67,16 @@ public static class GuildConfigExtensions
/// <param name="guildId">Id of the guide</param>
/// <param name="includes">Use to manipulate the set however you want. Pass null to include everything</param>
/// <returns>Config for the guild</returns>
public static GuildConfig GuildConfigsForId(this NadekoContext ctx, ulong guildId, Func<DbSet<GuildConfig>, IQueryable<GuildConfig>> includes)
public static GuildConfig GuildConfigsForId(
this NadekoContext ctx,
ulong guildId,
Func<DbSet<GuildConfig>, IQueryable<GuildConfig>> includes)
{
GuildConfig config;
if (includes is null)
{
config = ctx
.GuildConfigs
.IncludeEverything()
config = ctx.GuildConfigs.IncludeEverything()
.FirstOrDefault(c => c.GuildId == guildId);
}
else
@@ -95,7 +93,8 @@ public static class GuildConfigExtensions
Permissions = Permissionv2.GetDefaultPermlist,
WarningsInitialized = true,
WarnPunishments = DefaultWarnPunishments,
});
}
);
ctx.SaveChanges();
}
@@ -110,18 +109,14 @@ public static class GuildConfigExtensions
public static LogSetting LogSettingsFor(this NadekoContext ctx, ulong guildId)
{
var logSetting = ctx.LogSettings
.AsQueryable()
var logSetting = ctx.LogSettings.AsQueryable()
.Include(x => x.LogIgnores)
.Where(x => x.GuildId == guildId)
.FirstOrDefault();
if (logSetting is null)
{
ctx.LogSettings.Add(logSetting = new ()
{
GuildId = guildId
});
ctx.LogSettings.Add(logSetting = new() { GuildId = guildId });
ctx.SaveChanges();
}
@@ -139,23 +134,18 @@ public static class GuildConfigExtensions
public static GuildConfig GcWithPermissionsv2For(this NadekoContext ctx, ulong guildId)
{
var config = ctx
.GuildConfigs
.AsQueryable()
var config = ctx.GuildConfigs.AsQueryable()
.Where(gc => gc.GuildId == guildId)
.Include(gc => gc.Permissions)
.FirstOrDefault();
if (config is null) // if there is no guildconfig, create new one
{
ctx.GuildConfigs.Add(config = new()
{
GuildId = guildId,
Permissions = Permissionv2.GetDefaultPermlist
});
ctx.GuildConfigs.Add(config = new() { GuildId = guildId, Permissions = Permissionv2.GetDefaultPermlist });
ctx.SaveChanges();
}
else if (config.Permissions is null || !config.Permissions.Any()) // if no perms, add default ones
else if (config.Permissions is null ||
!config.Permissions.Any()) // if no perms, add default ones
{
config.Permissions = Permissionv2.GetDefaultPermlist;
ctx.SaveChanges();
@@ -165,8 +155,7 @@ public static class GuildConfigExtensions
}
public static IEnumerable<FollowedStream> GetFollowedStreams(this DbSet<GuildConfig> configs)
=> configs
.AsQueryable()
=> configs.AsQueryable()
.Include(x => x.FollowedStreams)
.SelectMany(gc => gc.FollowedStreams)
.ToArray();
@@ -196,7 +185,8 @@ public static class GuildConfigExtensions
.Include(x => x.XpSettings)
.ThenInclude(x => x.CurrencyRewards)
.Include(x => x.XpSettings)
.ThenInclude(x => x.ExclusionList));
.ThenInclude(x => x.ExclusionList)
);
if (gc.XpSettings is null)
gc.XpSettings = new XpSettings();
@@ -205,15 +195,10 @@ public static class GuildConfigExtensions
}
public static IEnumerable<GeneratingChannel> GetGeneratingChannels(this DbSet<GuildConfig> configs)
=> configs
.AsQueryable()
=> configs.AsQueryable()
.Include(x => x.GenerateCurrencyChannelIds)
.Where(x => x.GenerateCurrencyChannelIds.Any())
.SelectMany(x => x.GenerateCurrencyChannelIds)
.Select(x => new GeneratingChannel()
{
ChannelId = x.ChannelId,
GuildId = x.GuildConfig.GuildId
})
.Select(x => new GeneratingChannel() { ChannelId = x.ChannelId, GuildId = x.GuildConfig.GuildId })
.ToArray();
}

View File

@@ -7,17 +7,12 @@ public static class MusicPlayerSettingsExtensions
{
public static async Task<MusicPlayerSettings> ForGuildAsync(this DbSet<MusicPlayerSettings> settings, ulong guildId)
{
var toReturn = await settings
.AsQueryable()
var toReturn = await settings.AsQueryable()
.FirstOrDefaultAsync(x => x.GuildId == guildId);
if (toReturn is null)
{
var newSettings = new MusicPlayerSettings()
{
GuildId = guildId,
PlayerRepeat = PlayerRepeatType.Queue
};
var newSettings = new MusicPlayerSettings() { GuildId = guildId, PlayerRepeat = PlayerRepeatType.Queue };
await settings.AddAsync(newSettings);
return newSettings;

View File

@@ -10,16 +10,14 @@ public static class MusicPlaylistExtensions
if (num < 1)
throw new IndexOutOfRangeException();
return playlists
.AsQueryable()
return playlists.AsQueryable()
.Skip((num - 1) * 20)
.Take(20)
.Include(pl => pl.Songs)
.ToList();
}
public static MusicPlaylist GetWithSongs(this DbSet<MusicPlaylist> playlists, int id) =>
playlists
.Include(mpl => mpl.Songs)
public static MusicPlaylist GetWithSongs(this DbSet<MusicPlaylist> playlists, int id)
=> playlists.Include(mpl => mpl.Songs)
.FirstOrDefault(mpl => mpl.Id == id);
}

View File

@@ -13,9 +13,7 @@ public static class PollExtensions
public static void RemovePoll(this NadekoContext ctx, int id)
{
var p = ctx
.Poll
.Include(x => x.Answers)
var p = ctx.Poll.Include(x => x.Answers)
.Include(x => x.Votes)
.FirstOrDefault(x => x.Id == id);

View File

@@ -6,43 +6,58 @@ namespace NadekoBot.Db;
public static class QuoteExtensions
{
public static IEnumerable<Quote> GetForGuild(this DbSet<Quote> quotes, ulong guildId)
=> quotes.AsQueryable().Where(x => x.GuildId == guildId);
=> quotes.AsQueryable()
.Where(x => x.GuildId == guildId);
public static IEnumerable<Quote> GetGroup(this DbSet<Quote> quotes, ulong guildId, int page, OrderType order)
public static IEnumerable<Quote> GetGroup(
this DbSet<Quote> quotes,
ulong guildId,
int page,
OrderType order)
{
var q = quotes.AsQueryable().Where(x => x.GuildId == guildId);
var q = quotes.AsQueryable()
.Where(x => x.GuildId == guildId);
if (order == OrderType.Keyword)
q = q.OrderBy(x => x.Keyword);
else
q = q.OrderBy(x => x.Id);
return q.Skip(15 * page).Take(15).ToArray();
return q.Skip(15 * page)
.Take(15)
.ToArray();
}
public static async Task<Quote> GetRandomQuoteByKeywordAsync(this DbSet<Quote> quotes, ulong guildId, string keyword)
public static async Task<Quote> GetRandomQuoteByKeywordAsync(
this DbSet<Quote> quotes,
ulong guildId,
string keyword)
{
var rng = new NadekoRandom();
return (await quotes.AsQueryable()
.Where(q => q.GuildId == guildId && q.Keyword == keyword)
.ToListAsync())
.OrderBy(q => rng.Next())
.ToListAsync()).OrderBy(q => rng.Next())
.FirstOrDefault();
}
public static async Task<Quote> SearchQuoteKeywordTextAsync(this DbSet<Quote> quotes, ulong guildId, string keyword, string text)
public static async Task<Quote> SearchQuoteKeywordTextAsync(
this DbSet<Quote> quotes,
ulong guildId,
string keyword,
string text)
{
var rngk = new NadekoRandom();
return (await quotes.AsQueryable()
.Where(q => q.GuildId == guildId
&& q.Keyword == keyword
&& EF.Functions.Like(q.Text.ToUpper(), $"%{text.ToUpper()}%")
.Where(q => q.GuildId == guildId &&
q.Keyword == keyword &&
EF.Functions.Like(q.Text.ToUpper(), $"%{text.ToUpper()}%")
// && q.Text.Contains(text, StringComparison.OrdinalIgnoreCase)
)
.ToListAsync())
.OrderBy(q => rngk.Next())
.ToListAsync()).OrderBy(q => rngk.Next())
.FirstOrDefault();
}
public static void RemoveAllByKeyword(this DbSet<Quote> quotes, ulong guildId, string keyword)
=> quotes.RemoveRange(quotes.AsQueryable().Where(x => x.GuildId == guildId && x.Keyword.ToUpper() == keyword));
=> quotes.RemoveRange(quotes.AsQueryable()
.Where(x => x.GuildId == guildId && x.Keyword.ToUpper() == keyword)
);
}

View File

@@ -5,7 +5,9 @@ namespace NadekoBot.Db;
public static class ReminderExtensions
{
public static IEnumerable<Reminder> GetIncludedReminders(this DbSet<Reminder> reminders, IEnumerable<ulong> guildIds)
public static IEnumerable<Reminder> GetIncludedReminders(
this DbSet<Reminder> reminders,
IEnumerable<ulong> guildIds)
=> reminders.AsQueryable()
.Where(x => guildIds.Contains(x.ServerId) || x.ServerId == 0)
.ToList();

View File

@@ -15,19 +15,16 @@ public static class UserXpExtensions
{
ctx.Add(usr = new()
{
Xp = 0,
UserId = userId,
NotifyOnLevelUp = XpNotificationLocation.None,
GuildId = guildId,
});
Xp = 0, UserId = userId, NotifyOnLevelUp = XpNotificationLocation.None, GuildId = guildId,
}
);
}
return usr;
}
public static List<UserXpStats> GetUsersFor(this DbSet<UserXpStats> xps, ulong guildId, int page)
=> xps
.AsQueryable()
=> xps.AsQueryable()
.AsNoTracking()
.Where(x => x.GuildId == guildId)
.OrderByDescending(x => x.Xp + x.AwardedXp)
@@ -36,8 +33,7 @@ public static class UserXpExtensions
.ToList();
public static List<UserXpStats> GetTopUserXps(this DbSet<UserXpStats> xps, ulong guildId, int count)
=> xps
.AsQueryable()
=> xps.AsQueryable()
.AsNoTracking()
.Where(x => x.GuildId == guildId)
.OrderByDescending(x => x.Xp + x.AwardedXp)
@@ -51,15 +47,17 @@ public static class UserXpExtensions
// FROM UserXpStats
// WHERE UserId = @p2 AND GuildId = @p1
// LIMIT 1));";
=> xps
.AsQueryable()
=> xps.AsQueryable()
.AsNoTracking()
.Where(x => x.GuildId == guildId && x.Xp + x.AwardedXp >
.Where(x => x.GuildId == guildId &&
x.Xp + x.AwardedXp >
xps.AsQueryable()
.Where(y => y.UserId == userId && y.GuildId == guildId)
.Select(y => y.Xp + y.AwardedXp)
.FirstOrDefault())
.Count() + 1;
.FirstOrDefault()
)
.Count() +
1;
public static void ResetGuildUserXp(this DbSet<UserXpStats> xps, ulong userId, ulong guildId)
=> xps.Delete(x => x.UserId == userId && x.GuildId == guildId);

View File

@@ -7,21 +7,24 @@ namespace NadekoBot.Db;
public class WaifuInfoStats
{
public string FullName { get; set; }
public int Price { get; set; }
public string ClaimerName { get; set; }
public string AffinityName { get; set; }
public int AffinityCount { get; set; }
public int DivorceCount { get; set; }
public int ClaimCount { get; set; }
public List<WaifuItem> Items { get; set; }
public List<string> Claims { get; set; }
public List<string> Fans { get; set; }
public string FullName { get; init; }
public int Price { get; init; }
public string ClaimerName { get; init; }
public string AffinityName { get; init; }
public int AffinityCount { get; init; }
public int DivorceCount { get; init; }
public int ClaimCount { get; init; }
public List<WaifuItem> Items { get; init; }
public List<string> Claims { get; init; }
public List<string> Fans { get; init; }
}
public static class WaifuExtensions
{
public static WaifuInfo ByWaifuUserId(this DbSet<WaifuInfo> waifus, ulong userId, Func<DbSet<WaifuInfo>, IQueryable<WaifuInfo>> includes = null)
public static WaifuInfo ByWaifuUserId(
this DbSet<WaifuInfo> waifus,
ulong userId,
Func<DbSet<WaifuInfo>, IQueryable<WaifuInfo>> includes = null)
{
if (includes is null)
{
@@ -59,23 +62,20 @@ public static class WaifuExtensions
Username = x.Waifu.Username,
Discrim = x.Waifu.Discriminator,
Price = x.Price,
})
}
)
.ToList();
}
public static decimal GetTotalValue(this DbSet<WaifuInfo> waifus)
=> waifus
.AsQueryable()
=> waifus.AsQueryable()
.Where(x => x.ClaimerId != null)
.Sum(x => x.Price);
public static ulong GetWaifuUserId(this DbSet<WaifuInfo> waifus, ulong ownerId, string name)
=> waifus
.AsQueryable()
=> waifus.AsQueryable()
.AsNoTracking()
.Where(x => x.Claimer.UserId == ownerId
&& x.Waifu.Username + "#" + x.Waifu.Discriminator == name)
.Where(x => x.Claimer.UserId == ownerId && x.Waifu.Username + "#" + x.Waifu.Discriminator == name)
.Select(x => x.Waifu.UserId)
.FirstOrDefault();
@@ -83,14 +83,17 @@ public static class WaifuExtensions
{
ctx.Database.ExecuteSqlInterpolated($@"
INSERT OR IGNORE INTO WaifuInfo (AffinityId, ClaimerId, Price, WaifuId)
VALUES ({null}, {null}, {1}, (SELECT Id FROM DiscordUser WHERE UserId={userId}));");
VALUES ({null}, {null}, {1}, (SELECT Id FROM DiscordUser WHERE UserId={userId}));"
);
var toReturn = ctx.WaifuInfo
.AsQueryable()
.Where(w => w.WaifuId == ctx.Set<DiscordUser>()
var toReturn = ctx.WaifuInfo.AsQueryable()
.Where(w => w.WaifuId ==
ctx.Set<DiscordUser>()
.AsQueryable()
.Where(u => u.UserId == userId)
.Select(u => u.Id).FirstOrDefault())
.Select(u => u.Id)
.FirstOrDefault()
)
.Select(w => new WaifuInfoStats
{
FullName = ctx.Set<DiscordUser>()
@@ -98,54 +101,41 @@ VALUES ({null}, {null}, {1}, (SELECT Id FROM DiscordUser WHERE UserId={userId}))
.Where(u => u.UserId == userId)
.Select(u => u.Username + "#" + u.Discriminator)
.FirstOrDefault(),
AffinityCount = ctx.Set<WaifuUpdate>()
.AsQueryable()
.Count(x => x.UserId == w.WaifuId &&
x.UpdateType == WaifuUpdateType.AffinityChanged &&
x.NewId != null),
x.NewId != null
),
AffinityName = ctx.Set<DiscordUser>()
.AsQueryable()
.Where(u => u.Id == w.AffinityId)
.Select(u => u.Username + "#" + u.Discriminator)
.FirstOrDefault(),
ClaimCount = ctx.WaifuInfo
.AsQueryable()
ClaimCount = ctx.WaifuInfo.AsQueryable()
.Count(x => x.ClaimerId == w.WaifuId),
ClaimerName = ctx.Set<DiscordUser>()
.AsQueryable()
.Where(u => u.Id == w.ClaimerId)
.Select(u => u.Username + "#" + u.Discriminator)
.FirstOrDefault(),
DivorceCount = ctx
.Set<WaifuUpdate>()
DivorceCount = ctx.Set<WaifuUpdate>()
.AsQueryable()
.Count(x => x.OldId == w.WaifuId &&
x.NewId == null &&
x.UpdateType == WaifuUpdateType.Claimed),
.Count(x => x.OldId == w.WaifuId && x.NewId == null && x.UpdateType == WaifuUpdateType.Claimed),
Price = w.Price,
Claims = ctx.WaifuInfo
.AsQueryable()
Claims = ctx.WaifuInfo.AsQueryable()
.Include(x => x.Waifu)
.Where(x => x.ClaimerId == w.WaifuId)
.Select(x => x.Waifu.Username + "#" + x.Waifu.Discriminator)
.ToList(),
Fans = ctx.WaifuInfo
.AsQueryable()
Fans = ctx.WaifuInfo.AsQueryable()
.Include(x => x.Waifu)
.Where(x => x.AffinityId == w.WaifuId)
.Select(x => x.Waifu.Username + "#" + x.Waifu.Discriminator)
.ToList(),
Items = w.Items,
})
}
)
.FirstOrDefault();
if (toReturn is null)

View File

@@ -14,17 +14,24 @@ public static class WarningExtensions
return query.ToArray();
}
public static bool Forgive(this DbSet<Warning> warnings, ulong guildId, ulong userId, string mod, int index)
public static bool Forgive(
this DbSet<Warning> warnings,
ulong guildId,
ulong userId,
string mod,
int index)
{
if (index < 0)
throw new ArgumentOutOfRangeException(nameof(index));
var warn = warnings.AsQueryable().Where(x => x.GuildId == guildId && x.UserId == userId)
var warn = warnings.AsQueryable()
.Where(x => x.GuildId == guildId && x.UserId == userId)
.OrderByDescending(x => x.DateAdded)
.Skip(index)
.FirstOrDefault();
if (warn is null || warn.Forgiven)
if (warn is null ||
warn.Forgiven)
return false;
warn.Forgiven = true;
@@ -32,8 +39,13 @@ public static class WarningExtensions
return true;
}
public static async Task ForgiveAll(this DbSet<Warning> warnings, ulong guildId, ulong userId, string mod)
=> await warnings.AsQueryable().Where(x => x.GuildId == guildId && x.UserId == userId)
public static async Task ForgiveAll(
this DbSet<Warning> warnings,
ulong guildId,
ulong userId,
string mod)
=> await warnings.AsQueryable()
.Where(x => x.GuildId == guildId && x.UserId == userId)
.ForEachAsync(x =>
{
if (x.Forgiven != true)
@@ -41,8 +53,11 @@ public static class WarningExtensions
x.Forgiven = true;
x.ForgivenBy = mod;
}
});
}
);
public static Warning[] GetForGuild(this DbSet<Warning> warnings, ulong id)
=> warnings.AsQueryable().Where(x => x.GuildId == id).ToArray();
=> warnings.AsQueryable()
.Where(x => x.GuildId == id)
.ToArray();
}

View File

@@ -43,8 +43,8 @@ public partial class Administration
[Priority(0)]
public async Task LanguageSet()
=> await ReplyConfirmLocalizedAsync(strs.lang_set_show(
Format.Bold(_cultureInfo.ToString()),
Format.Bold(_cultureInfo.NativeName)));
Format.Bold(Culture.ToString()),
Format.Bold(Culture.NativeName)));
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]

View File

@@ -1002,8 +1002,7 @@ public sealed class LogCommandService : ILogCommandService
{
try
{
var msg = optMsg.Value as IUserMessage;
if (msg is null || msg.IsAuthor(_client))
if (optMsg.Value is not IUserMessage msg || msg.IsAuthor(_client))
return;
if (_ignoreMessageIds.Contains(msg.Id))
@@ -1058,8 +1057,7 @@ public sealed class LogCommandService : ILogCommandService
if (imsg2 is not IUserMessage after || after.IsAuthor(_client))
return;
var before = (optmsg.HasValue ? optmsg.Value : null) as IUserMessage;
if (before is null)
if ((optmsg.HasValue ? optmsg.Value : null) is not IUserMessage before)
return;
if (ch is not ITextChannel channel)

View File

@@ -167,9 +167,7 @@ public class VcRoleService : INService
private Task ClientOnUserVoiceStateUpdated(SocketUser usr, SocketVoiceState oldState,
SocketVoiceState newState)
{
var gusr = usr as SocketGuildUser;
if (gusr is null)
if (usr is not SocketGuildUser gusr)
return Task.CompletedTask;
var oldVc = oldState.VoiceChannel;

View File

@@ -465,7 +465,7 @@ public partial class Administration
.AddField(GetText(strs.duration),
time.Time.Humanize(3,
minUnit: TimeUnit.Minute,
culture: _cultureInfo),
culture: Culture),
true);
if (dmFailed)

View File

@@ -152,8 +152,7 @@ public class ReactionEvent : ICurrencyEvent
{
if (_emote.Name != r.Emote.Name)
return;
var gu = (r.User.IsSpecified ? r.User.Value : null) as IGuildUser;
if (gu is null // no unknown users, as they could be bots, or alts
if ((r.User.IsSpecified ? r.User.Value : null) is not IGuildUser gu // no unknown users, as they could be bots, or alts
|| msg.Id != _msg.Id // same message
|| gu.IsBot // no bots
|| (DateTime.UtcNow - gu.CreatedAt).TotalDays <= 5 // no recently created accounts

View File

@@ -35,7 +35,7 @@ public partial class Gambling : GamblingModule<GamblingService>
private string n(long cur)
{
var flowersCi = (CultureInfo)_cultureInfo.Clone();
var flowersCi = (CultureInfo)Culture.Clone();
flowersCi.NumberFormat.CurrencySymbol = CurrencySign;
Log.Information(string.Join(",", flowersCi.NumberFormat.NativeDigits));
return cur.ToString("C0", flowersCi);
@@ -59,12 +59,12 @@ public partial class Gambling : GamblingModule<GamblingService>
}
var embed = _eb.Create()
.WithTitle(GetText(strs.economy_state))
.AddField(GetText(strs.currency_owned), ((BigInteger)(ec.Cash - ec.Bot)).ToString("N", _cultureInfo) + CurrencySign)
.AddField(GetText(strs.currency_owned), ((BigInteger)(ec.Cash - ec.Bot)).ToString("N", Culture) + CurrencySign)
.AddField(GetText(strs.currency_one_percent), (onePercent * 100).ToString("F2") + "%")
.AddField(GetText(strs.currency_planted), (BigInteger)ec.Planted)
.AddField(GetText(strs.owned_waifus_total), (BigInteger)ec.Waifus + CurrencySign)
.AddField(GetText(strs.bot_currency), n(ec.Bot))
.AddField(GetText(strs.total), ((BigInteger)(ec.Cash + ec.Planted + ec.Waifus)).ToString("N", _cultureInfo) + CurrencySign)
.AddField(GetText(strs.total), ((BigInteger)(ec.Cash + ec.Planted + ec.Waifus)).ToString("N", Culture) + CurrencySign)
.WithOkColor();
// ec.Cash already contains ec.Bot as it's the total of all values in the CurrencyAmount column of the DiscordUser table
await ctx.Channel.EmbedAsync(embed).ConfigureAwait(false);

View File

@@ -27,8 +27,7 @@ public class CurrencyEventsService : INService
EventOptions opts, Func<CurrencyEvent.Type, EventOptions, long, IEmbedBuilder> embed)
{
var g = _client.GetGuild(guildId);
var ch = g?.GetChannel(channelId) as SocketTextChannel;
if (ch is null)
if (g?.GetChannel(channelId) is not SocketTextChannel ch)
return false;
ICurrencyEvent ce;

View File

@@ -159,8 +159,7 @@ public class PlantPickService : INService
private Task PotentialFlowerGeneration(IUserMessage imsg)
{
var msg = imsg as SocketUserMessage;
if (msg is null || msg.Author.IsBot)
if (imsg is not SocketUserMessage msg || msg.Author.IsBot)
return Task.CompletedTask;
if (imsg.Channel is not ITextChannel channel)

View File

@@ -196,8 +196,7 @@ public class TriviaGame
var umsg = imsg as SocketUserMessage;
var textChannel = umsg?.Channel as ITextChannel;
if (textChannel is null || textChannel.Guild != Guild)
if (umsg?.Channel is not ITextChannel textChannel || textChannel.Guild != Guild)
return;
var guildUser = (IGuildUser)umsg.Author;

View File

@@ -133,8 +133,7 @@ public class TypingGame
{
if (imsg.Author.IsBot)
return;
var msg = imsg as SocketUserMessage;
if (msg is null)
if (imsg is not SocketUserMessage msg)
return;
if (this.Channel is null || this.Channel.Id != msg.Channel.Id) return;

View File

@@ -97,9 +97,8 @@ public sealed class FilterService : IEarlyBehavior
var _ = Task.Run(() =>
{
var guild = (channel as ITextChannel)?.Guild;
var usrMsg = newMsg as IUserMessage;
if (guild is null || usrMsg is null)
if (guild is null || newMsg is not IUserMessage usrMsg)
return Task.CompletedTask;
return RunBehavior(guild, usrMsg);

View File

@@ -56,7 +56,7 @@ public partial class Searches
.WithDescription(string.IsNullOrWhiteSpace(kvp.Value.Desc)
? kvp.Value.ShortDesc
: kvp.Value.Desc)
.AddField(GetText(strs.rating), kvp.Value.Rating.ToString(_cultureInfo), true));
.AddField(GetText(strs.rating), kvp.Value.Rating.ToString(Culture), true));
return;
}
}

View File

@@ -626,9 +626,7 @@ public class SearchesService : INService
var results = elems.Select(elem =>
{
var anchor = elem.QuerySelector(".result__a") as IHtmlAnchorElement;
if (anchor is null)
if (elem.QuerySelector(".result__a") is not IHtmlAnchorElement anchor)
return null;
var href = anchor.Href;

View File

@@ -127,7 +127,7 @@ public partial class Utility
var diff = when - DateTime.UtcNow;
embed.AddField(
$"#{++i + (page * 10)} {rem.When:HH:mm yyyy-MM-dd} UTC " +
$"(in {diff.Humanize(2, minUnit: TimeUnit.Minute, culture: _cultureInfo)})",
$"(in {diff.Humanize(2, minUnit: TimeUnit.Minute, culture: Culture)})",
$@"`Target:` {(rem.IsPrivate ? "DM" : "Channel")}
`TargetId:` {rem.ChannelId}
`Message:` {rem.Message?.TrimTo(50)}", false);
@@ -230,7 +230,7 @@ public partial class Utility
"⏰ " + GetText(strs.remind(
Format.Bold(!isPrivate ? $"<#{targetId}>" : ctx.User.Username),
Format.Bold(message),
ts.Humanize(3, minUnit: TimeUnit.Second, culture: _cultureInfo),
ts.Humanize(3, minUnit: TimeUnit.Second, culture: Culture),
gTime, gTime))).ConfigureAwait(false);
}
catch

View File

@@ -106,14 +106,6 @@ public partial class Xp : NadekoModule<XpService>
}, allRewards.Count, 9);
}
public enum AddRemove
{
Add = 0,
Remove = 1,
Rm = 1,
Rem = 1,
}
[NadekoCommand, Aliases]
[UserPerm(GuildPerm.Administrator)]
[BotPerm(GuildPerm.ManageRoles)]

View File

@@ -8,7 +8,6 @@
<RunWorkingDirectory>$(MSBuildProjectDirectory)</RunWorkingDirectory>
<OutputType>exe</OutputType>
<ApplicationIcon>nadeko_icon.ico</ApplicationIcon>
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
</PropertyGroup>
<ItemGroup>

View File

@@ -7,16 +7,16 @@ namespace NadekoBot.Services;
public sealed class RedisImagesCache : IImageCache, IReadyExecutor
{
private readonly ConnectionMultiplexer _con;
private readonly IBotCredentials _creds;
private readonly HttpClient _http;
private readonly string _imagesPath;
private IDatabase _db => _con.GetDatabase();
private IDatabase Db
=> _con.GetDatabase();
private const string _basePath = "data/";
private const string _cardsPath = $"{_basePath}images/cards";
private const string BASE_PATH = "data/";
private const string CARDS_PATH = $"{BASE_PATH}images/cards";
public ImageUrls ImageUrls { get; private set; }
@@ -70,7 +70,7 @@ public sealed class RedisImagesCache : IImageCache, IReadyExecutor
public byte[] GetCard(string key)
// since cards are always local for now, don't cache them
=> File.ReadAllBytes(Path.Join(_cardsPath, key + ".jpg"));
=> File.ReadAllBytes(Path.Join(CARDS_PATH, key + ".jpg"));
public async Task OnReadyAsync()
{
@@ -85,7 +85,7 @@ public sealed class RedisImagesCache : IImageCache, IReadyExecutor
_con = con;
_creds = creds;
_http = new();
_imagesPath = Path.Combine(_basePath, "images.yml");
_imagesPath = Path.Combine(BASE_PATH, "images.yml");
Migrate();
@@ -95,58 +95,50 @@ public sealed class RedisImagesCache : IImageCache, IReadyExecutor
private void Migrate()
{
// migrate to yml
if (File.Exists(Path.Combine(_basePath, "images.json")))
if (File.Exists(Path.Combine(BASE_PATH, "images.json")))
{
var oldFilePath = Path.Combine(_basePath, "images.json");
var backupFilePath = Path.Combine(_basePath, "images.json.backup");
var oldFilePath = Path.Combine(BASE_PATH, "images.json");
var backupFilePath = Path.Combine(BASE_PATH, "images.json.backup");
var oldData = JsonConvert.DeserializeObject<OldImageUrls>(
File.ReadAllText(oldFilePath));
var oldData = JsonConvert.DeserializeObject<OldImageUrls>(File.ReadAllText(oldFilePath));
if (oldData is not null)
{
var newData = new ImageUrls()
{
Coins = new()
Coins =
new()
{
Heads = oldData.Coins.Heads.Length == 1 &&
oldData.Coins.Heads[0].ToString() == "https://nadeko-pictures.nyc3.digitaloceanspaces.com/other/coins/heads.png"
oldData.Coins.Heads[0].ToString() ==
"https://nadeko-pictures.nyc3.digitaloceanspaces.com/other/coins/heads.png"
? new[] { new Uri("https://cdn.nadeko.bot/coins/heads3.png") }
: oldData.Coins.Heads,
Tails = oldData.Coins.Tails.Length == 1 &&
oldData.Coins.Tails[0].ToString() == "https://nadeko-pictures.nyc3.digitaloceanspaces.com/other/coins/tails.png"
oldData.Coins.Tails[0].ToString() ==
"https://nadeko-pictures.nyc3.digitaloceanspaces.com/other/coins/tails.png"
? new[] { new Uri("https://cdn.nadeko.bot/coins/tails3.png") }
: oldData.Coins.Tails,
},
Dice = oldData.Dice.Map(x => x.ToNewCdn()),
Currency = oldData.Currency.Map(x => x.ToNewCdn()),
Rategirl = new()
Rategirl =
new()
{
Dot = oldData.Rategirl.Dot.ToNewCdn(),
Matrix = oldData.Rategirl.Matrix.ToNewCdn()
},
Rip = new()
{
Bg = oldData.Rip.Bg.ToNewCdn(),
Overlay = oldData.Rip.Overlay.ToNewCdn(),
Dot = oldData.Rategirl.Dot.ToNewCdn(), Matrix = oldData.Rategirl.Matrix.ToNewCdn()
},
Rip = new() { Bg = oldData.Rip.Bg.ToNewCdn(), Overlay = oldData.Rip.Overlay.ToNewCdn(), },
Slots = new()
{
Bg = new("https://cdn.nadeko.bot/slots/slots_bg.png"),
Emojis = new[]
{
"https://cdn.nadeko.bot/slots/0.png",
"https://cdn.nadeko.bot/slots/1.png",
"https://cdn.nadeko.bot/slots/2.png",
"https://cdn.nadeko.bot/slots/3.png",
"https://cdn.nadeko.bot/slots/4.png",
"https://cdn.nadeko.bot/slots/5.png"
"https://cdn.nadeko.bot/slots/0.png", "https://cdn.nadeko.bot/slots/1.png",
"https://cdn.nadeko.bot/slots/2.png", "https://cdn.nadeko.bot/slots/3.png",
"https://cdn.nadeko.bot/slots/4.png", "https://cdn.nadeko.bot/slots/5.png"
}.Map(x => new Uri(x))
},
Xp = new()
{
Bg = oldData.Xp.Bg.ToNewCdn(),
},
Xp = new() { Bg = oldData.Xp.Bg.ToNewCdn(), },
Version = 2,
};
@@ -216,25 +208,25 @@ public sealed class RedisImagesCache : IImageCache, IReadyExecutor
if (data is null)
return;
await _db.StringSetAsync(GetRedisKey(key), data);
await Db.StringSetAsync(GetRedisKey(key), data);
}
private async Task Load(ImageKeys key, Uri[] uris)
{
await _db.KeyDeleteAsync(GetRedisKey(key));
await Db.KeyDeleteAsync(GetRedisKey(key));
var imageData = await Task.WhenAll(uris.Select(GetImageData));
var vals = imageData
.Where(x => x is not null)
.Select(x => (RedisValue)x)
.ToArray();
var vals = imageData.Where(x => x is not null).Select(x => (RedisValue)x).ToArray();
await _db.ListRightPushAsync(GetRedisKey(key), vals);
await Db.ListRightPushAsync(GetRedisKey(key), vals);
if (uris.Length != vals.Length)
{
Log.Information("{Loaded}/{Max} URIs for the key '{ImageKey}' have been loaded.\n" +
"Some of the supplied URIs are either unavailable or invalid.",
vals.Length, uris.Length, key);
"Some of the supplied URIs are either unavailable or invalid",
vals.Length,
uris.Length,
key
);
}
}
@@ -267,20 +259,19 @@ public sealed class RedisImagesCache : IImageCache, IReadyExecutor
private async Task<bool> AllKeysExist()
{
var tasks = await Task.WhenAll(GetAllKeys()
.Select(x => _db.KeyExistsAsync(GetRedisKey(x))));
var tasks = await Task.WhenAll(GetAllKeys().Select(x => Db.KeyExistsAsync(GetRedisKey(x))));
return tasks.All(exist => exist);
}
private IEnumerable<ImageKeys> GetAllKeys() =>
Enum.GetValues<ImageKeys>();
private IEnumerable<ImageKeys> GetAllKeys()
=> Enum.GetValues<ImageKeys>();
private byte[][] GetByteArrayData(ImageKeys key)
=> _db.ListRange(GetRedisKey(key)).Map(x => (byte[])x);
=> Db.ListRange(GetRedisKey(key)).Map(x => (byte[])x);
private byte[] GetByteData(ImageKeys key)
=> _db.StringGet(GetRedisKey(key));
=> Db.StringGet(GetRedisKey(key));
private RedisKey GetRedisKey(ImageKeys key)
=> _creds.RedisKey() + "_image_" + key;

View File

@@ -10,35 +10,33 @@ public class RedisLocalDataCache : ILocalDataCache
private readonly ConnectionMultiplexer _con;
private readonly IBotCredentials _creds;
private IDatabase _db => _con.GetDatabase();
private const string pokemonAbilitiesFile = "data/pokemon/pokemon_abilities.json";
private const string pokemonListFile = "data/pokemon/pokemon_list.json";
private const string pokemonMapPath = "data/pokemon/name-id_map.json";
private const string questionsFile = "data/trivia_questions.json";
private const string POKEMON_ABILITIES_FILE = "data/pokemon/pokemon_abilities.json";
private const string POKEMON_LIST_FILE = "data/pokemon/pokemon_list.json";
private const string POKEMON_MAP_PATH = "data/pokemon/name-id_map.json";
private const string QUESTIONS_FILE = "data/trivia_questions.json";
public IReadOnlyDictionary<string, SearchPokemon> Pokemons
{
get => Get<Dictionary<string, SearchPokemon>>("pokemon_list");
private set => Set("pokemon_list", value);
private init => Set("pokemon_list", value);
}
public IReadOnlyDictionary<string, SearchPokemonAbility> PokemonAbilities
{
get => Get<Dictionary<string, SearchPokemonAbility>>("pokemon_abilities");
private set => Set("pokemon_abilities", value);
private init => Set("pokemon_abilities", value);
}
public TriviaQuestion[] TriviaQuestions
{
get => Get<TriviaQuestion[]>("trivia_questions");
private set => Set("trivia_questions", value);
private init => Set("trivia_questions", value);
}
public IReadOnlyDictionary<int, string> PokemonMap
{
get => Get<Dictionary<int, string>>("pokemon_map");
private set => Set("pokemon_map", value);
private init => Set("pokemon_map", value);
}
public RedisLocalDataCache(ConnectionMultiplexer con, IBotCredentials creds, DiscordSocketClient client)
@@ -49,29 +47,33 @@ public class RedisLocalDataCache : ILocalDataCache
if (shardId == 0)
{
if (!File.Exists(pokemonListFile))
if (!File.Exists(POKEMON_LIST_FILE))
{
Log.Warning($"{pokemonListFile} is missing. Pokemon abilities not loaded");
Log.Warning($"{POKEMON_LIST_FILE} is missing. Pokemon abilities not loaded");
}
else
{
Pokemons = JsonConvert.DeserializeObject<Dictionary<string, SearchPokemon>>(File.ReadAllText(pokemonListFile));
Pokemons =
JsonConvert.DeserializeObject<Dictionary<string, SearchPokemon>>(File.ReadAllText(POKEMON_LIST_FILE));
}
if (!File.Exists(pokemonAbilitiesFile))
if (!File.Exists(POKEMON_ABILITIES_FILE))
{
Log.Warning($"{pokemonAbilitiesFile} is missing. Pokemon abilities not loaded.");
Log.Warning($"{POKEMON_ABILITIES_FILE} is missing. Pokemon abilities not loaded.");
}
else
{
PokemonAbilities = JsonConvert.DeserializeObject<Dictionary<string, SearchPokemonAbility>>(File.ReadAllText(pokemonAbilitiesFile));
PokemonAbilities =
JsonConvert.DeserializeObject<Dictionary<string, SearchPokemonAbility>>(
File.ReadAllText(POKEMON_ABILITIES_FILE)
);
}
try
{
TriviaQuestions = JsonConvert.DeserializeObject<TriviaQuestion[]>(File.ReadAllText(questionsFile));
PokemonMap = JsonConvert.DeserializeObject<PokemonNameId[]>(File.ReadAllText(pokemonMapPath))
.ToDictionary(x => x.Id, x => x.Name);
TriviaQuestions = JsonConvert.DeserializeObject<TriviaQuestion[]>(File.ReadAllText(QUESTIONS_FILE));
PokemonMap = JsonConvert.DeserializeObject<PokemonNameId[]>(File.ReadAllText(POKEMON_MAP_PATH))
?.ToDictionary(x => x.Id, x => x.Name) ?? new();
}
catch (Exception ex)
{
@@ -81,9 +83,10 @@ public class RedisLocalDataCache : ILocalDataCache
}
}
private T Get<T>(string key) where T : class
=> JsonConvert.DeserializeObject<T>(_db.StringGet($"{_creds.RedisKey()}_localdata_{key}"));
private T Get<T>(string key)
where T : class
=> JsonConvert.DeserializeObject<T>(_con.GetDatabase().StringGet($"{_creds.RedisKey()}_localdata_{key}"));
private void Set(string key, object obj)
=> _db.StringSet($"{_creds.RedisKey()}_localdata_{key}", JsonConvert.SerializeObject(obj));
=> _con.GetDatabase().StringSet($"{_creds.RedisKey()}_localdata_{key}", JsonConvert.SerializeObject(obj));
}

View File

@@ -11,8 +11,7 @@ public static class ServiceCollectionExtensions
{
public static IServiceCollection AddBotStringsServices(this IServiceCollection services, int totalShards)
=> totalShards <= 1
? services
.AddSingleton<IStringsSource, LocalFileStringsSource>()
? services.AddSingleton<IStringsSource, LocalFileStringsSource>()
.AddSingleton<IBotStringsProvider, LocalBotStringsProvider>()
.AddSingleton<IBotStrings, BotStrings>()
: services.AddSingleton<IStringsSource, LocalFileStringsSource>()
@@ -25,7 +24,8 @@ public static class ServiceCollectionExtensions
foreach (var type in Assembly.GetCallingAssembly().ExportedTypes.Where(x => x.IsSealed))
{
if (type.BaseType?.IsGenericType == true && type.BaseType.GetGenericTypeDefinition() == baseType)
if (type.BaseType?.IsGenericType == true &&
type.BaseType.GetGenericTypeDefinition() == baseType)
{
services.AddSingleton(type);
services.AddSingleton(x => (IConfigService)x.GetRequiredService(type));
@@ -39,8 +39,7 @@ public static class ServiceCollectionExtensions
=> services.AddSealedSubclassesOf(typeof(IConfigMigrator));
public static IServiceCollection AddMusic(this IServiceCollection services)
=> services
.AddSingleton<IMusicService, MusicService>()
=> services.AddSingleton<IMusicService, MusicService>()
.AddSingleton<ITrackResolveProvider, TrackResolveProvider>()
.AddSingleton<IYoutubeResolver, YtdlYoutubeResolver>()
.AddSingleton<ISoundcloudResolver, SoundcloudResolver>()
@@ -55,8 +54,7 @@ public static class ServiceCollectionExtensions
public static IServiceCollection AddSealedSubclassesOf(this IServiceCollection services, Type baseType)
{
var subTypes = Assembly.GetCallingAssembly()
.ExportedTypes
.Where(type => type.IsSealed && baseType.IsAssignableFrom(type));
.ExportedTypes.Where(type => type.IsSealed && baseType.IsAssignableFrom(type));
foreach (var subType in subTypes)
{