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