mirror of
https://gitlab.com/Kwoth/nadekobot.git
synced 2025-09-10 17:28:27 -04:00
Applied codestyle to all .cs files
This commit is contained in:
@@ -339,7 +339,7 @@ resharper_keep_existing_linebreaks = false
|
|||||||
resharper_max_formal_parameters_on_line = 3
|
resharper_max_formal_parameters_on_line = 3
|
||||||
resharper_wrap_chained_binary_expressions = chop_if_long
|
resharper_wrap_chained_binary_expressions = chop_if_long
|
||||||
resharper_wrap_chained_binary_patterns = chop_if_long
|
resharper_wrap_chained_binary_patterns = chop_if_long
|
||||||
resharper_wrap_chained_method_calls = wrap_if_long
|
resharper_wrap_chained_method_calls = chop_if_long
|
||||||
|
|
||||||
resharper_csharp_wrap_before_first_type_parameter_constraint = true
|
resharper_csharp_wrap_before_first_type_parameter_constraint = true
|
||||||
resharper_csharp_place_type_constraints_on_same_line = false
|
resharper_csharp_place_type_constraints_on_same_line = false
|
||||||
|
@@ -1,35 +1,35 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
|
using Discord.Interactions;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using NadekoBot.Common.Configs;
|
||||||
|
using NadekoBot.Common.ModuleBehaviors;
|
||||||
|
using NadekoBot.Db;
|
||||||
|
using NadekoBot.Modules.Administration.Services;
|
||||||
using NadekoBot.Services.Database.Models;
|
using NadekoBot.Services.Database.Models;
|
||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using NadekoBot.Common.ModuleBehaviors;
|
using RunMode = Discord.Commands.RunMode;
|
||||||
using NadekoBot.Common.Configs;
|
|
||||||
using NadekoBot.Db;
|
|
||||||
using NadekoBot.Modules.Administration.Services;
|
|
||||||
using Discord.Interactions;
|
|
||||||
|
|
||||||
namespace NadekoBot;
|
namespace NadekoBot;
|
||||||
|
|
||||||
public sealed class Bot
|
public sealed class Bot
|
||||||
{
|
{
|
||||||
|
public event Func<GuildConfig, Task> JoinedGuild = delegate { return Task.CompletedTask; };
|
||||||
|
|
||||||
|
public DiscordSocketClient Client { get; }
|
||||||
|
public ImmutableArray<GuildConfig> AllGuildConfigs { get; private set; }
|
||||||
|
|
||||||
|
private IServiceProvider Services { get; set; }
|
||||||
|
|
||||||
|
public string Mention { get; private set; }
|
||||||
|
public bool IsReady { get; private set; }
|
||||||
private readonly IBotCredentials _creds;
|
private readonly IBotCredentials _creds;
|
||||||
private readonly CommandService _commandService;
|
private readonly CommandService _commandService;
|
||||||
private readonly DbService _db;
|
private readonly DbService _db;
|
||||||
private readonly IBotCredsProvider _credsProvider;
|
private readonly IBotCredsProvider _credsProvider;
|
||||||
private readonly InteractionService _interactionService;
|
private readonly InteractionService _interactionService;
|
||||||
|
|
||||||
public event Func<GuildConfig, Task> JoinedGuild = delegate { return Task.CompletedTask; };
|
|
||||||
|
|
||||||
public DiscordSocketClient Client { get; }
|
|
||||||
public ImmutableArray<GuildConfig> AllGuildConfigs { get; private set; }
|
|
||||||
|
|
||||||
private IServiceProvider Services { get; set; }
|
|
||||||
|
|
||||||
public string Mention { get; private set; }
|
|
||||||
public bool IsReady { get; private set; }
|
|
||||||
|
|
||||||
public Bot(int shardId, int? totalShards)
|
public Bot(int shardId, int? totalShards)
|
||||||
{
|
{
|
||||||
if (shardId < 0)
|
if (shardId < 0)
|
||||||
@@ -37,13 +37,10 @@ public sealed class Bot
|
|||||||
|
|
||||||
_credsProvider = new BotCredsProvider(totalShards);
|
_credsProvider = new BotCredsProvider(totalShards);
|
||||||
_creds = _credsProvider.GetCreds();
|
_creds = _credsProvider.GetCreds();
|
||||||
|
|
||||||
_db = new(_creds);
|
_db = new(_creds);
|
||||||
|
|
||||||
if (shardId == 0)
|
if (shardId == 0) _db.Setup();
|
||||||
{
|
|
||||||
_db.Setup();
|
|
||||||
}
|
|
||||||
|
|
||||||
Client = new(new()
|
Client = new(new()
|
||||||
{
|
{
|
||||||
@@ -55,14 +52,10 @@ public sealed class Bot
|
|||||||
AlwaysDownloadUsers = false,
|
AlwaysDownloadUsers = false,
|
||||||
AlwaysResolveStickers = false,
|
AlwaysResolveStickers = false,
|
||||||
AlwaysDownloadDefaultStickers = false,
|
AlwaysDownloadDefaultStickers = false,
|
||||||
GatewayIntents = GatewayIntents.All,
|
GatewayIntents = GatewayIntents.All
|
||||||
});
|
});
|
||||||
|
|
||||||
_commandService = new(new()
|
_commandService = new(new() { CaseSensitiveCommands = false, DefaultRunMode = RunMode.Sync });
|
||||||
{
|
|
||||||
CaseSensitiveCommands = false,
|
|
||||||
DefaultRunMode = Discord.Commands.RunMode.Sync,
|
|
||||||
});
|
|
||||||
|
|
||||||
_interactionService = new(Client.Rest);
|
_interactionService = new(Client.Rest);
|
||||||
|
|
||||||
@@ -85,49 +78,41 @@ public sealed class Bot
|
|||||||
uow.EnsureUserCreated(bot.Id, bot.Username, bot.Discriminator, bot.AvatarId);
|
uow.EnsureUserCreated(bot.Id, bot.Username, bot.Discriminator, bot.AvatarId);
|
||||||
AllGuildConfigs = uow.GuildConfigs.GetAllGuildConfigs(startingGuildIdList).ToImmutableArray();
|
AllGuildConfigs = uow.GuildConfigs.GetAllGuildConfigs(startingGuildIdList).ToImmutableArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
var svcs = new ServiceCollection()
|
var svcs = new ServiceCollection().AddTransient(_ => _credsProvider.GetCreds()) // bot creds
|
||||||
.AddTransient<IBotCredentials>(_ => _credsProvider.GetCreds()) // bot creds
|
.AddSingleton(_credsProvider)
|
||||||
.AddSingleton<IBotCredsProvider>(_credsProvider)
|
.AddSingleton(_db) // database
|
||||||
.AddSingleton(_db) // database
|
.AddRedis(_creds.RedisOptions) // redis
|
||||||
.AddRedis(_creds.RedisOptions) // redis
|
.AddSingleton(Client) // discord socket client
|
||||||
.AddSingleton(Client) // discord socket client
|
.AddSingleton(_commandService)
|
||||||
.AddSingleton(_commandService)
|
.AddSingleton(_interactionService)
|
||||||
.AddSingleton(_interactionService)
|
.AddSingleton(this)
|
||||||
.AddSingleton(this)
|
.AddSingleton<ISeria, JsonSeria>()
|
||||||
.AddSingleton<ISeria, JsonSeria>()
|
.AddSingleton<IPubSub, RedisPubSub>()
|
||||||
.AddSingleton<IPubSub, RedisPubSub>()
|
.AddSingleton<IConfigSeria, YamlSeria>()
|
||||||
.AddSingleton<IConfigSeria, YamlSeria>()
|
.AddBotStringsServices(_creds.TotalShards)
|
||||||
.AddBotStringsServices(_creds.TotalShards)
|
.AddConfigServices()
|
||||||
.AddConfigServices()
|
.AddConfigMigrators()
|
||||||
.AddConfigMigrators()
|
.AddMemoryCache()
|
||||||
.AddMemoryCache()
|
// music
|
||||||
// music
|
.AddMusic();
|
||||||
.AddMusic()
|
// admin
|
||||||
// admin
|
|
||||||
#if GLOBAL_NADEKO
|
#if GLOBAL_NADEKO
|
||||||
.AddSingleton<ILogCommandService, DummyLogCommandService>()
|
svcs.AddSingleton<ILogCommandService, DummyLogCommandService>();
|
||||||
#else
|
#else
|
||||||
.AddSingleton<ILogCommandService, LogCommandService>()
|
svcs.AddSingleton<ILogCommandService, LogCommandService>();
|
||||||
#endif
|
#endif
|
||||||
;
|
|
||||||
|
|
||||||
svcs.AddHttpClient();
|
svcs.AddHttpClient();
|
||||||
svcs.AddHttpClient("memelist").ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler
|
svcs.AddHttpClient("memelist")
|
||||||
{
|
.ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler { AllowAutoRedirect = false });
|
||||||
AllowAutoRedirect = false
|
|
||||||
});
|
|
||||||
|
|
||||||
if (Environment.GetEnvironmentVariable("NADEKOBOT_IS_COORDINATED") != "1")
|
if (Environment.GetEnvironmentVariable("NADEKOBOT_IS_COORDINATED") != "1")
|
||||||
{
|
|
||||||
svcs.AddSingleton<ICoordinator, SingleProcessCoordinator>();
|
svcs.AddSingleton<ICoordinator, SingleProcessCoordinator>();
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
|
||||||
svcs.AddSingleton<RemoteGrpcCoordinator>()
|
svcs.AddSingleton<RemoteGrpcCoordinator>()
|
||||||
.AddSingleton<ICoordinator>(x => x.GetRequiredService<RemoteGrpcCoordinator>())
|
.AddSingleton<ICoordinator>(x => x.GetRequiredService<RemoteGrpcCoordinator>())
|
||||||
.AddSingleton<IReadyExecutor>(x => x.GetRequiredService<RemoteGrpcCoordinator>());
|
.AddSingleton<IReadyExecutor>(x => x.GetRequiredService<RemoteGrpcCoordinator>());
|
||||||
}
|
|
||||||
|
|
||||||
svcs.AddSingleton<RedisLocalDataCache>()
|
svcs.AddSingleton<RedisLocalDataCache>()
|
||||||
.AddSingleton<ILocalDataCache>(x => x.GetRequiredService<RedisLocalDataCache>())
|
.AddSingleton<ILocalDataCache>(x => x.GetRequiredService<RedisLocalDataCache>())
|
||||||
@@ -135,37 +120,31 @@ public sealed class Bot
|
|||||||
.AddSingleton<IImageCache>(x => x.GetRequiredService<RedisImagesCache>())
|
.AddSingleton<IImageCache>(x => x.GetRequiredService<RedisImagesCache>())
|
||||||
.AddSingleton<IReadyExecutor>(x => x.GetRequiredService<RedisImagesCache>())
|
.AddSingleton<IReadyExecutor>(x => x.GetRequiredService<RedisImagesCache>())
|
||||||
.AddSingleton<IDataCache, RedisCache>();
|
.AddSingleton<IDataCache, RedisCache>();
|
||||||
|
|
||||||
svcs.Scan(scan => scan
|
svcs.Scan(scan => scan.FromAssemblyOf<IReadyExecutor>()
|
||||||
.FromAssemblyOf<IReadyExecutor>()
|
.AddClasses(classes => classes.AssignableToAny(
|
||||||
.AddClasses(classes => classes
|
// services
|
||||||
.AssignableToAny(
|
typeof(INService),
|
||||||
// services
|
|
||||||
typeof(INService),
|
// behaviours
|
||||||
|
typeof(IEarlyBehavior),
|
||||||
// behaviours
|
typeof(ILateBlocker),
|
||||||
typeof(IEarlyBehavior),
|
typeof(IInputTransformer),
|
||||||
typeof(ILateBlocker),
|
typeof(ILateExecutor))
|
||||||
typeof(IInputTransformer),
|
|
||||||
typeof(ILateExecutor))
|
|
||||||
#if GLOBAL_NADEKO
|
#if GLOBAL_NADEKO
|
||||||
.WithoutAttribute<NoPublicBotAttribute>()
|
.WithoutAttribute<NoPublicBotAttribute>()
|
||||||
#endif
|
#endif
|
||||||
)
|
)
|
||||||
.AsSelfWithInterfaces()
|
.AsSelfWithInterfaces()
|
||||||
.WithSingletonLifetime()
|
.WithSingletonLifetime());
|
||||||
);
|
|
||||||
|
|
||||||
//initialize Services
|
//initialize Services
|
||||||
Services = svcs.BuildServiceProvider();
|
Services = svcs.BuildServiceProvider();
|
||||||
var exec = Services.GetRequiredService<IBehaviourExecutor>();
|
var exec = Services.GetRequiredService<IBehaviourExecutor>();
|
||||||
exec.Initialize();
|
exec.Initialize();
|
||||||
|
|
||||||
if (Client.ShardId == 0)
|
if (Client.ShardId == 0) ApplyConfigMigrations();
|
||||||
{
|
|
||||||
ApplyConfigMigrations();
|
|
||||||
}
|
|
||||||
|
|
||||||
_ = LoadTypeReaders(typeof(Bot).Assembly);
|
_ = LoadTypeReaders(typeof(Bot).Assembly);
|
||||||
|
|
||||||
sw.Stop();
|
sw.Stop();
|
||||||
@@ -176,10 +155,7 @@ public sealed class Bot
|
|||||||
{
|
{
|
||||||
// execute all migrators
|
// execute all migrators
|
||||||
var migrators = Services.GetServices<IConfigMigrator>();
|
var migrators = Services.GetServices<IConfigMigrator>();
|
||||||
foreach (var migrator in migrators)
|
foreach (var migrator in migrators) migrator.EnsureMigrated();
|
||||||
{
|
|
||||||
migrator.EnsureMigrated();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private IEnumerable<object> LoadTypeReaders(Assembly assembly)
|
private IEnumerable<object> LoadTypeReaders(Assembly assembly)
|
||||||
@@ -225,10 +201,7 @@ public sealed class Bot
|
|||||||
clientReady.TrySetResult(true);
|
clientReady.TrySetResult(true);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
foreach (var chan in await Client.GetDMChannelsAsync())
|
foreach (var chan in await Client.GetDMChannelsAsync()) await chan.CloseAsync();
|
||||||
{
|
|
||||||
await chan.CloseAsync();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
@@ -259,10 +232,10 @@ public sealed class Bot
|
|||||||
Client.Ready += SetClientReady;
|
Client.Ready += SetClientReady;
|
||||||
await clientReady.Task;
|
await clientReady.Task;
|
||||||
Client.Ready -= SetClientReady;
|
Client.Ready -= SetClientReady;
|
||||||
|
|
||||||
Client.JoinedGuild += Client_JoinedGuild;
|
Client.JoinedGuild += Client_JoinedGuild;
|
||||||
Client.LeftGuild += Client_LeftGuild;
|
Client.LeftGuild += Client_LeftGuild;
|
||||||
|
|
||||||
Log.Information("Shard {ShardId} logged in", Client.ShardId);
|
Log.Information("Shard {ShardId} logged in", Client.ShardId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -282,6 +255,7 @@ public sealed class Bot
|
|||||||
{
|
{
|
||||||
gc = uow.GuildConfigsForId(arg.Id, null);
|
gc = uow.GuildConfigsForId(arg.Id, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
await JoinedGuild.Invoke(gc);
|
await JoinedGuild.Invoke(gc);
|
||||||
});
|
});
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
@@ -323,7 +297,7 @@ public sealed class Bot
|
|||||||
private Task ExecuteReadySubscriptions()
|
private Task ExecuteReadySubscriptions()
|
||||||
{
|
{
|
||||||
var readyExecutors = Services.GetServices<IReadyExecutor>();
|
var readyExecutors = Services.GetServices<IReadyExecutor>();
|
||||||
var tasks = readyExecutors.Select(async toExec =>
|
var tasks = readyExecutors.Select(async toExec =>
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -356,4 +330,4 @@ public sealed class Bot
|
|||||||
await RunAsync();
|
await RunAsync();
|
||||||
await Task.Delay(-1);
|
await Task.Delay(-1);
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -6,5 +6,5 @@ public enum AddRemove
|
|||||||
Add = int.MinValue,
|
Add = int.MinValue,
|
||||||
Remove = int.MinValue + 1,
|
Remove = int.MinValue + 1,
|
||||||
Rem = int.MinValue + 1,
|
Rem = int.MinValue + 1,
|
||||||
Rm = int.MinValue + 1,
|
Rm = int.MinValue + 1
|
||||||
}
|
}
|
@@ -1,4 +1,4 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
namespace NadekoBot.Common;
|
namespace NadekoBot.Common;
|
||||||
@@ -17,4 +17,4 @@ public class AsyncLazy<T> : Lazy<Task<T>>
|
|||||||
|
|
||||||
public TaskAwaiter<T> GetAwaiter()
|
public TaskAwaiter<T> GetAwaiter()
|
||||||
=> Value.GetAwaiter();
|
=> Value.GetAwaiter();
|
||||||
}
|
}
|
@@ -9,4 +9,4 @@ public sealed class AliasesAttribute : AliasAttribute
|
|||||||
: base(CommandNameLoadHelper.GetAliasesFor(memberName))
|
: base(CommandNameLoadHelper.GetAliasesFor(memberName))
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,9 +1,10 @@
|
|||||||
|
using YamlDotNet.Serialization;
|
||||||
|
|
||||||
namespace NadekoBot.Common.Attributes;
|
namespace NadekoBot.Common.Attributes;
|
||||||
|
|
||||||
public static class CommandNameLoadHelper
|
public static class CommandNameLoadHelper
|
||||||
{
|
{
|
||||||
private static readonly YamlDotNet.Serialization.IDeserializer _deserializer =
|
private static readonly IDeserializer _deserializer = new Deserializer();
|
||||||
new YamlDotNet.Serialization.Deserializer();
|
|
||||||
|
|
||||||
public static Lazy<Dictionary<string, string[]>> LazyCommandAliases = new(() => LoadCommandNames());
|
public static Lazy<Dictionary<string, string[]>> LazyCommandAliases = new(() => LoadCommandNames());
|
||||||
|
|
||||||
@@ -26,4 +27,4 @@ public static class CommandNameLoadHelper
|
|||||||
: methodName;
|
: methodName;
|
||||||
return toReturn;
|
return toReturn;
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -5,9 +5,9 @@ namespace NadekoBot.Common.Attributes;
|
|||||||
[AttributeUsage(AttributeTargets.Method)]
|
[AttributeUsage(AttributeTargets.Method)]
|
||||||
public sealed class NadekoCommandAttribute : CommandAttribute
|
public sealed class NadekoCommandAttribute : CommandAttribute
|
||||||
{
|
{
|
||||||
|
public string MethodName { get; }
|
||||||
|
|
||||||
public NadekoCommandAttribute([CallerMemberName] string memberName = "")
|
public NadekoCommandAttribute([CallerMemberName] string memberName = "")
|
||||||
: base(CommandNameLoadHelper.GetCommandNameFor(memberName))
|
: base(CommandNameLoadHelper.GetCommandNameFor(memberName))
|
||||||
=> this.MethodName = memberName.ToLowerInvariant();
|
=> MethodName = memberName.ToLowerInvariant();
|
||||||
|
}
|
||||||
public string MethodName { get; }
|
|
||||||
}
|
|
@@ -7,4 +7,4 @@ internal sealed class NadekoModuleAttribute : GroupAttribute
|
|||||||
: base(moduleName)
|
: base(moduleName)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -6,5 +6,5 @@ public sealed class NadekoOptionsAttribute : Attribute
|
|||||||
public Type OptionType { get; set; }
|
public Type OptionType { get; set; }
|
||||||
|
|
||||||
public NadekoOptionsAttribute(Type t)
|
public NadekoOptionsAttribute(Type t)
|
||||||
=> this.OptionType = t;
|
=> OptionType = t;
|
||||||
}
|
}
|
@@ -14,7 +14,6 @@ public sealed class OwnerOnlyAttribute : PreconditionAttribute
|
|||||||
|
|
||||||
return Task.FromResult(creds.IsOwner(context.User) || context.Client.CurrentUser.Id == context.User.Id
|
return Task.FromResult(creds.IsOwner(context.User) || context.Client.CurrentUser.Id == context.User.Id
|
||||||
? PreconditionResult.FromSuccess()
|
? PreconditionResult.FromSuccess()
|
||||||
: PreconditionResult.FromError("Not owner")
|
: PreconditionResult.FromError("Not owner"));
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -33,4 +33,4 @@ public sealed class RatelimitAttribute : PreconditionAttribute
|
|||||||
|
|
||||||
return Task.FromResult(PreconditionResult.FromError(msgContent));
|
return Task.FromResult(PreconditionResult.FromError(msgContent));
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -6,6 +6,16 @@ namespace Discord;
|
|||||||
[AttributeUsage(AttributeTargets.Method)]
|
[AttributeUsage(AttributeTargets.Method)]
|
||||||
public class UserPermAttribute : RequireUserPermissionAttribute
|
public class UserPermAttribute : RequireUserPermissionAttribute
|
||||||
{
|
{
|
||||||
|
public UserPermAttribute(GuildPerm permission)
|
||||||
|
: base(permission)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public UserPermAttribute(ChannelPerm permission)
|
||||||
|
: base(permission)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public override Task<PreconditionResult> CheckPermissionsAsync(
|
public override Task<PreconditionResult> CheckPermissionsAsync(
|
||||||
ICommandContext context,
|
ICommandContext context,
|
||||||
CommandInfo command,
|
CommandInfo command,
|
||||||
@@ -17,14 +27,4 @@ public class UserPermAttribute : RequireUserPermissionAttribute
|
|||||||
|
|
||||||
return base.CheckPermissionsAsync(context, command, services);
|
return base.CheckPermissionsAsync(context, command, services);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
public UserPermAttribute(GuildPerm permission)
|
|
||||||
: base(permission)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public UserPermAttribute(ChannelPerm permission)
|
|
||||||
: base(permission)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,4 +1,4 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace NadekoBot.Common;
|
namespace NadekoBot.Common;
|
||||||
@@ -14,4 +14,4 @@ public class CmdStrings
|
|||||||
Usages = usages;
|
Usages = usages;
|
||||||
Description = description;
|
Description = description;
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,4 +1,4 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
// License MIT
|
// License MIT
|
||||||
// Source: https://github.com/i3arnon/ConcurrentHashSet
|
// Source: https://github.com/i3arnon/ConcurrentHashSet
|
||||||
|
|
||||||
@@ -7,12 +7,12 @@ using System.Diagnostics;
|
|||||||
namespace System.Collections.Generic;
|
namespace System.Collections.Generic;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a thread-safe hash-based unique collection.
|
/// Represents a thread-safe hash-based unique collection.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="T">The type of the items in the collection.</typeparam>
|
/// <typeparam name="T">The type of the items in the collection.</typeparam>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// All public members of <see cref="ConcurrentHashSet{T}"/> are thread-safe and may be used
|
/// All public members of <see cref="ConcurrentHashSet{T}" /> are thread-safe and may be used
|
||||||
/// concurrently from multiple threads.
|
/// concurrently from multiple threads.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
[DebuggerDisplay("Count = {Count}")]
|
[DebuggerDisplay("Count = {Count}")]
|
||||||
public sealed class ConcurrentHashSet<T> : IReadOnlyCollection<T>, ICollection<T>
|
public sealed class ConcurrentHashSet<T> : IReadOnlyCollection<T>, ICollection<T>
|
||||||
@@ -20,53 +20,16 @@ public sealed class ConcurrentHashSet<T> : IReadOnlyCollection<T>, ICollection<T
|
|||||||
private const int DefaultCapacity = 31;
|
private const int DefaultCapacity = 31;
|
||||||
private const int MaxLockNumber = 1024;
|
private const int MaxLockNumber = 1024;
|
||||||
|
|
||||||
private readonly IEqualityComparer<T> _comparer;
|
|
||||||
private readonly bool _growLockArray;
|
|
||||||
|
|
||||||
private int _budget;
|
|
||||||
private volatile Tables _tables;
|
|
||||||
|
|
||||||
private static int DefaultConcurrencyLevel
|
private static int DefaultConcurrencyLevel
|
||||||
=> PlatformHelper.ProcessorCount;
|
=> PlatformHelper.ProcessorCount;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the number of items contained in the <see
|
/// Gets a value that indicates whether the <see cref="ConcurrentHashSet{T}" /> is empty.
|
||||||
/// cref="ConcurrentHashSet{T}"/>.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The number of items contained in the <see
|
/// <value>
|
||||||
/// cref="ConcurrentHashSet{T}"/>.</value>
|
/// true if the <see cref="ConcurrentHashSet{T}" /> is empty; otherwise,
|
||||||
/// <remarks>Count has snapshot semantics and represents the number of items in the <see
|
/// false.
|
||||||
/// cref="ConcurrentHashSet{T}"/>
|
/// </value>
|
||||||
/// at the moment when Count was accessed.</remarks>
|
|
||||||
public int Count
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
var count = 0;
|
|
||||||
var acquiredLocks = 0;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
AcquireAllLocks(ref acquiredLocks);
|
|
||||||
|
|
||||||
for (var i = 0; i < _tables.CountPerLock.Length; i++)
|
|
||||||
{
|
|
||||||
count += _tables.CountPerLock[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
ReleaseLocks(0, acquiredLocks);
|
|
||||||
}
|
|
||||||
|
|
||||||
return count;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a value that indicates whether the <see cref="ConcurrentHashSet{T}"/> is empty.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>true if the <see cref="ConcurrentHashSet{T}"/> is empty; otherwise,
|
|
||||||
/// false.</value>
|
|
||||||
public bool IsEmpty
|
public bool IsEmpty
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
@@ -77,12 +40,8 @@ public sealed class ConcurrentHashSet<T> : IReadOnlyCollection<T>, ICollection<T
|
|||||||
AcquireAllLocks(ref acquiredLocks);
|
AcquireAllLocks(ref acquiredLocks);
|
||||||
|
|
||||||
for (var i = 0; i < _tables.CountPerLock.Length; i++)
|
for (var i = 0; i < _tables.CountPerLock.Length; i++)
|
||||||
{
|
|
||||||
if (_tables.CountPerLock[i] != 0)
|
if (_tables.CountPerLock[i] != 0)
|
||||||
{
|
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
@@ -93,91 +52,158 @@ public sealed class ConcurrentHashSet<T> : IReadOnlyCollection<T>, ICollection<T
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ICollection<T>.IsReadOnly
|
||||||
|
=> false;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see
|
/// Gets the number of items contained in the
|
||||||
/// cref="ConcurrentHashSet{T}"/>
|
/// <see
|
||||||
/// class that is empty, has the default concurrency level, has the default initial capacity, and
|
/// cref="ConcurrentHashSet{T}" />
|
||||||
/// uses the default comparer for the item type.
|
/// .
|
||||||
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// The number of items contained in the
|
||||||
|
/// <see
|
||||||
|
/// cref="ConcurrentHashSet{T}" />
|
||||||
|
/// .
|
||||||
|
/// </value>
|
||||||
|
/// <remarks>
|
||||||
|
/// Count has snapshot semantics and represents the number of items in the
|
||||||
|
/// <see
|
||||||
|
/// cref="ConcurrentHashSet{T}" />
|
||||||
|
/// at the moment when Count was accessed.
|
||||||
|
/// </remarks>
|
||||||
|
public int Count
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var count = 0;
|
||||||
|
var acquiredLocks = 0;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
AcquireAllLocks(ref acquiredLocks);
|
||||||
|
|
||||||
|
for (var i = 0; i < _tables.CountPerLock.Length; i++) count += _tables.CountPerLock[i];
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
ReleaseLocks(0, acquiredLocks);
|
||||||
|
}
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly IEqualityComparer<T> _comparer;
|
||||||
|
private readonly bool _growLockArray;
|
||||||
|
|
||||||
|
private int _budget;
|
||||||
|
private volatile Tables _tables;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the
|
||||||
|
/// <see
|
||||||
|
/// cref="ConcurrentHashSet{T}" />
|
||||||
|
/// class that is empty, has the default concurrency level, has the default initial capacity, and
|
||||||
|
/// uses the default comparer for the item type.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ConcurrentHashSet()
|
public ConcurrentHashSet()
|
||||||
: this(DefaultConcurrencyLevel,
|
: this(DefaultConcurrencyLevel, DefaultCapacity, true, EqualityComparer<T>.Default)
|
||||||
DefaultCapacity,
|
|
||||||
true,
|
|
||||||
EqualityComparer<T>.Default)
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see
|
/// Initializes a new instance of the
|
||||||
/// cref="ConcurrentHashSet{T}"/>
|
/// <see
|
||||||
/// class that is empty, has the specified concurrency level and capacity, and uses the default
|
/// cref="ConcurrentHashSet{T}" />
|
||||||
/// comparer for the item type.
|
/// class that is empty, has the specified concurrency level and capacity, and uses the default
|
||||||
|
/// comparer for the item type.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="concurrencyLevel">The estimated number of threads that will update the
|
/// <param name="concurrencyLevel">
|
||||||
/// <see cref="ConcurrentHashSet{T}"/> concurrently.</param>
|
/// The estimated number of threads that will update the
|
||||||
/// <param name="capacity">The initial number of elements that the <see
|
/// <see cref="ConcurrentHashSet{T}" /> concurrently.
|
||||||
/// cref="ConcurrentHashSet{T}"/>
|
/// </param>
|
||||||
/// can contain.</param>
|
/// <param name="capacity">
|
||||||
/// <exception cref="T:System.ArgumentOutOfRangeException"><paramref name="concurrencyLevel"/> is
|
/// The initial number of elements that the
|
||||||
/// less than 1.</exception>
|
/// <see
|
||||||
/// <exception cref="T:System.ArgumentOutOfRangeException"> <paramref name="capacity"/> is less than
|
/// cref="ConcurrentHashSet{T}" />
|
||||||
/// 0.</exception>
|
/// can contain.
|
||||||
|
/// </param>
|
||||||
|
/// <exception cref="T:System.ArgumentOutOfRangeException">
|
||||||
|
/// <paramref name="concurrencyLevel" /> is
|
||||||
|
/// less than 1.
|
||||||
|
/// </exception>
|
||||||
|
/// <exception cref="T:System.ArgumentOutOfRangeException">
|
||||||
|
/// <paramref name="capacity" /> is less than
|
||||||
|
/// 0.
|
||||||
|
/// </exception>
|
||||||
public ConcurrentHashSet(int concurrencyLevel, int capacity)
|
public ConcurrentHashSet(int concurrencyLevel, int capacity)
|
||||||
: this(concurrencyLevel,
|
: this(concurrencyLevel, capacity, false, EqualityComparer<T>.Default)
|
||||||
capacity,
|
|
||||||
false,
|
|
||||||
EqualityComparer<T>.Default)
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="ConcurrentHashSet{T}"/>
|
/// Initializes a new instance of the <see cref="ConcurrentHashSet{T}" />
|
||||||
/// class that contains elements copied from the specified <see
|
/// class that contains elements copied from the specified
|
||||||
/// cref="T:System.Collections.IEnumerable{T}"/>, has the default concurrency
|
/// <see
|
||||||
/// level, has the default initial capacity, and uses the default comparer for the item type.
|
/// cref="T:System.Collections.IEnumerable{T}" />
|
||||||
|
/// , has the default concurrency
|
||||||
|
/// level, has the default initial capacity, and uses the default comparer for the item type.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="collection">The <see
|
/// <param name="collection">
|
||||||
/// cref="T:System.Collections.IEnumerable{T}"/> whose elements are copied to
|
/// The
|
||||||
/// the new
|
/// <see
|
||||||
/// <see cref="ConcurrentHashSet{T}"/>.</param>
|
/// cref="T:System.Collections.IEnumerable{T}" />
|
||||||
/// <exception cref="T:System.ArgumentNullException"><paramref name="collection"/> is a null reference.</exception>
|
/// whose elements are copied to
|
||||||
|
/// the new
|
||||||
|
/// <see cref="ConcurrentHashSet{T}" />.
|
||||||
|
/// </param>
|
||||||
|
/// <exception cref="T:System.ArgumentNullException"><paramref name="collection" /> is a null reference.</exception>
|
||||||
public ConcurrentHashSet(IEnumerable<T> collection)
|
public ConcurrentHashSet(IEnumerable<T> collection)
|
||||||
: this(collection, EqualityComparer<T>.Default)
|
: this(collection, EqualityComparer<T>.Default)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="ConcurrentHashSet{T}"/>
|
/// Initializes a new instance of the <see cref="ConcurrentHashSet{T}" />
|
||||||
/// class that is empty, has the specified concurrency level and capacity, and uses the specified
|
/// class that is empty, has the specified concurrency level and capacity, and uses the specified
|
||||||
/// <see cref="T:System.Collections.Generic.IEqualityComparer{T}"/>.
|
/// <see cref="T:System.Collections.Generic.IEqualityComparer{T}" />.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="comparer">The <see cref="T:System.Collections.Generic.IEqualityComparer{T}"/>
|
/// <param name="comparer">
|
||||||
/// implementation to use when comparing items.</param>
|
/// The <see cref="T:System.Collections.Generic.IEqualityComparer{T}" />
|
||||||
/// <exception cref="T:System.ArgumentNullException"><paramref name="comparer"/> is a null reference.</exception>
|
/// implementation to use when comparing items.
|
||||||
|
/// </param>
|
||||||
|
/// <exception cref="T:System.ArgumentNullException"><paramref name="comparer" /> is a null reference.</exception>
|
||||||
public ConcurrentHashSet(IEqualityComparer<T> comparer)
|
public ConcurrentHashSet(IEqualityComparer<T> comparer)
|
||||||
: this(DefaultConcurrencyLevel,
|
: this(DefaultConcurrencyLevel, DefaultCapacity, true, comparer)
|
||||||
DefaultCapacity,
|
|
||||||
true,
|
|
||||||
comparer)
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="ConcurrentHashSet{T}"/>
|
/// Initializes a new instance of the <see cref="ConcurrentHashSet{T}" />
|
||||||
/// class that contains elements copied from the specified <see
|
/// class that contains elements copied from the specified
|
||||||
/// cref="T:System.Collections.IEnumerable"/>, has the default concurrency level, has the default
|
/// <see
|
||||||
/// initial capacity, and uses the specified
|
/// cref="T:System.Collections.IEnumerable" />
|
||||||
/// <see cref="T:System.Collections.Generic.IEqualityComparer{T}"/>.
|
/// , has the default concurrency level, has the default
|
||||||
|
/// initial capacity, and uses the specified
|
||||||
|
/// <see cref="T:System.Collections.Generic.IEqualityComparer{T}" />.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="collection">The <see
|
/// <param name="collection">
|
||||||
/// cref="T:System.Collections.IEnumerable{T}"/> whose elements are copied to
|
/// The
|
||||||
/// the new
|
/// <see
|
||||||
/// <see cref="ConcurrentHashSet{T}"/>.</param>
|
/// cref="T:System.Collections.IEnumerable{T}" />
|
||||||
/// <param name="comparer">The <see cref="T:System.Collections.Generic.IEqualityComparer{T}"/>
|
/// whose elements are copied to
|
||||||
/// implementation to use when comparing items.</param>
|
/// the new
|
||||||
/// <exception cref="T:System.ArgumentNullException"><paramref name="collection"/> is a null reference
|
/// <see cref="ConcurrentHashSet{T}" />.
|
||||||
/// (Nothing in Visual Basic). -or-
|
/// </param>
|
||||||
/// <paramref name="comparer"/> is a null reference (Nothing in Visual Basic).
|
/// <param name="comparer">
|
||||||
|
/// The <see cref="T:System.Collections.Generic.IEqualityComparer{T}" />
|
||||||
|
/// implementation to use when comparing items.
|
||||||
|
/// </param>
|
||||||
|
/// <exception cref="T:System.ArgumentNullException">
|
||||||
|
/// <paramref name="collection" /> is a null reference
|
||||||
|
/// (Nothing in Visual Basic). -or-
|
||||||
|
/// <paramref name="comparer" /> is a null reference (Nothing in Visual Basic).
|
||||||
/// </exception>
|
/// </exception>
|
||||||
public ConcurrentHashSet(IEnumerable<T> collection, IEqualityComparer<T> comparer)
|
public ConcurrentHashSet(IEnumerable<T> collection, IEqualityComparer<T> comparer)
|
||||||
: this(comparer)
|
: this(comparer)
|
||||||
@@ -189,30 +215,33 @@ public sealed class ConcurrentHashSet<T> : IReadOnlyCollection<T>, ICollection<T
|
|||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="ConcurrentHashSet{T}"/>
|
/// Initializes a new instance of the <see cref="ConcurrentHashSet{T}" />
|
||||||
/// class that contains elements copied from the specified <see cref="T:System.Collections.IEnumerable"/>,
|
/// class that contains elements copied from the specified <see cref="T:System.Collections.IEnumerable" />,
|
||||||
/// has the specified concurrency level, has the specified initial capacity, and uses the specified
|
/// has the specified concurrency level, has the specified initial capacity, and uses the specified
|
||||||
/// <see cref="T:System.Collections.Generic.IEqualityComparer{T}"/>.
|
/// <see cref="T:System.Collections.Generic.IEqualityComparer{T}" />.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="concurrencyLevel">The estimated number of threads that will update the
|
/// <param name="concurrencyLevel">
|
||||||
/// <see cref="ConcurrentHashSet{T}"/> concurrently.</param>
|
/// The estimated number of threads that will update the
|
||||||
/// <param name="collection">The <see cref="T:System.Collections.IEnumerable{T}"/> whose elements are copied to the new
|
/// <see cref="ConcurrentHashSet{T}" /> concurrently.
|
||||||
/// <see cref="ConcurrentHashSet{T}"/>.</param>
|
/// </param>
|
||||||
/// <param name="comparer">The <see cref="T:System.Collections.Generic.IEqualityComparer{T}"/> implementation to use
|
/// <param name="collection">
|
||||||
/// when comparing items.</param>
|
/// The <see cref="T:System.Collections.IEnumerable{T}" /> whose elements are copied to the new
|
||||||
|
/// <see cref="ConcurrentHashSet{T}" />.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="comparer">
|
||||||
|
/// The <see cref="T:System.Collections.Generic.IEqualityComparer{T}" /> implementation to use
|
||||||
|
/// when comparing items.
|
||||||
|
/// </param>
|
||||||
/// <exception cref="T:System.ArgumentNullException">
|
/// <exception cref="T:System.ArgumentNullException">
|
||||||
/// <paramref name="collection"/> is a null reference.
|
/// <paramref name="collection" /> is a null reference.
|
||||||
/// -or-
|
/// -or-
|
||||||
/// <paramref name="comparer"/> is a null reference.
|
/// <paramref name="comparer" /> is a null reference.
|
||||||
/// </exception>
|
/// </exception>
|
||||||
/// <exception cref="T:System.ArgumentOutOfRangeException">
|
/// <exception cref="T:System.ArgumentOutOfRangeException">
|
||||||
/// <paramref name="concurrencyLevel"/> is less than 1.
|
/// <paramref name="concurrencyLevel" /> is less than 1.
|
||||||
/// </exception>
|
/// </exception>
|
||||||
public ConcurrentHashSet(int concurrencyLevel, IEnumerable<T> collection, IEqualityComparer<T> comparer)
|
public ConcurrentHashSet(int concurrencyLevel, IEnumerable<T> collection, IEqualityComparer<T> comparer)
|
||||||
: this(concurrencyLevel,
|
: this(concurrencyLevel, DefaultCapacity, false, comparer)
|
||||||
DefaultCapacity,
|
|
||||||
false,
|
|
||||||
comparer)
|
|
||||||
{
|
{
|
||||||
if (collection is null) throw new ArgumentNullException(nameof(collection));
|
if (collection is null) throw new ArgumentNullException(nameof(collection));
|
||||||
if (comparer is null) throw new ArgumentNullException(nameof(comparer));
|
if (comparer is null) throw new ArgumentNullException(nameof(comparer));
|
||||||
@@ -221,47 +250,49 @@ public sealed class ConcurrentHashSet<T> : IReadOnlyCollection<T>, ICollection<T
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="ConcurrentHashSet{T}"/>
|
/// Initializes a new instance of the <see cref="ConcurrentHashSet{T}" />
|
||||||
/// class that is empty, has the specified concurrency level, has the specified initial capacity, and
|
/// class that is empty, has the specified concurrency level, has the specified initial capacity, and
|
||||||
/// uses the specified <see cref="T:System.Collections.Generic.IEqualityComparer{T}"/>.
|
/// uses the specified <see cref="T:System.Collections.Generic.IEqualityComparer{T}" />.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="concurrencyLevel">The estimated number of threads that will update the
|
/// <param name="concurrencyLevel">
|
||||||
/// <see cref="ConcurrentHashSet{T}"/> concurrently.</param>
|
/// The estimated number of threads that will update the
|
||||||
/// <param name="capacity">The initial number of elements that the <see
|
/// <see cref="ConcurrentHashSet{T}" /> concurrently.
|
||||||
/// cref="ConcurrentHashSet{T}"/>
|
/// </param>
|
||||||
/// can contain.</param>
|
/// <param name="capacity">
|
||||||
/// <param name="comparer">The <see cref="T:System.Collections.Generic.IEqualityComparer{T}"/>
|
/// The initial number of elements that the
|
||||||
/// implementation to use when comparing items.</param>
|
/// <see
|
||||||
|
/// cref="ConcurrentHashSet{T}" />
|
||||||
|
/// can contain.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="comparer">
|
||||||
|
/// The <see cref="T:System.Collections.Generic.IEqualityComparer{T}" />
|
||||||
|
/// implementation to use when comparing items.
|
||||||
|
/// </param>
|
||||||
/// <exception cref="T:System.ArgumentOutOfRangeException">
|
/// <exception cref="T:System.ArgumentOutOfRangeException">
|
||||||
/// <paramref name="concurrencyLevel"/> is less than 1. -or-
|
/// <paramref name="concurrencyLevel" /> is less than 1. -or-
|
||||||
/// <paramref name="capacity"/> is less than 0.
|
/// <paramref name="capacity" /> is less than 0.
|
||||||
/// </exception>
|
/// </exception>
|
||||||
/// <exception cref="T:System.ArgumentNullException"><paramref name="comparer"/> is a null reference.</exception>
|
/// <exception cref="T:System.ArgumentNullException"><paramref name="comparer" /> is a null reference.</exception>
|
||||||
public ConcurrentHashSet(int concurrencyLevel, int capacity, IEqualityComparer<T> comparer)
|
public ConcurrentHashSet(int concurrencyLevel, int capacity, IEqualityComparer<T> comparer)
|
||||||
: this(concurrencyLevel,
|
: this(concurrencyLevel, capacity, false, comparer)
|
||||||
capacity,
|
|
||||||
false,
|
|
||||||
comparer)
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
private ConcurrentHashSet(int concurrencyLevel, int capacity, bool growLockArray, IEqualityComparer<T> comparer)
|
private ConcurrentHashSet(
|
||||||
|
int concurrencyLevel,
|
||||||
|
int capacity,
|
||||||
|
bool growLockArray,
|
||||||
|
IEqualityComparer<T> comparer)
|
||||||
{
|
{
|
||||||
if (concurrencyLevel < 1) throw new ArgumentOutOfRangeException(nameof(concurrencyLevel));
|
if (concurrencyLevel < 1) throw new ArgumentOutOfRangeException(nameof(concurrencyLevel));
|
||||||
if (capacity < 0) throw new ArgumentOutOfRangeException(nameof(capacity));
|
if (capacity < 0) throw new ArgumentOutOfRangeException(nameof(capacity));
|
||||||
|
|
||||||
// The capacity should be at least as large as the concurrency level. Otherwise, we would have locks that don't guard
|
// The capacity should be at least as large as the concurrency level. Otherwise, we would have locks that don't guard
|
||||||
// any buckets.
|
// any buckets.
|
||||||
if (capacity < concurrencyLevel)
|
if (capacity < concurrencyLevel) capacity = concurrencyLevel;
|
||||||
{
|
|
||||||
capacity = concurrencyLevel;
|
|
||||||
}
|
|
||||||
|
|
||||||
var locks = new object[concurrencyLevel];
|
var locks = new object[concurrencyLevel];
|
||||||
for (var i = 0; i < locks.Length; i++)
|
for (var i = 0; i < locks.Length; i++) locks[i] = new();
|
||||||
{
|
|
||||||
locks[i] = new();
|
|
||||||
}
|
|
||||||
|
|
||||||
var countPerLock = new int[locks.Length];
|
var countPerLock = new int[locks.Length];
|
||||||
var buckets = new Node[capacity];
|
var buckets = new Node[capacity];
|
||||||
@@ -273,18 +304,7 @@ public sealed class ConcurrentHashSet<T> : IReadOnlyCollection<T>, ICollection<T
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds the specified item to the <see cref="ConcurrentHashSet{T}"/>.
|
/// Removes all items from the <see cref="ConcurrentHashSet{T}" />.
|
||||||
/// </summary>
|
|
||||||
/// <param name="item">The item to add.</param>
|
|
||||||
/// <returns>true if the items was added to the <see cref="ConcurrentHashSet{T}"/>
|
|
||||||
/// successfully; false if it already exists.</returns>
|
|
||||||
/// <exception cref="T:System.OverflowException">The <see cref="ConcurrentHashSet{T}"/>
|
|
||||||
/// contains too many items.</exception>
|
|
||||||
public bool Add(T item)
|
|
||||||
=> AddInternal(item, _comparer.GetHashCode(item), true);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Removes all items from the <see cref="ConcurrentHashSet{T}"/>.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Clear()
|
public void Clear()
|
||||||
{
|
{
|
||||||
@@ -304,11 +324,11 @@ public sealed class ConcurrentHashSet<T> : IReadOnlyCollection<T>, ICollection<T
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Determines whether the <see cref="ConcurrentHashSet{T}"/> contains the specified
|
/// Determines whether the <see cref="ConcurrentHashSet{T}" /> contains the specified
|
||||||
/// item.
|
/// item.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="item">The item to locate in the <see cref="ConcurrentHashSet{T}"/>.</param>
|
/// <param name="item">The item to locate in the <see cref="ConcurrentHashSet{T}" />.</param>
|
||||||
/// <returns>true if the <see cref="ConcurrentHashSet{T}"/> contains the item; otherwise, false.</returns>
|
/// <returns>true if the <see cref="ConcurrentHashSet{T}" /> contains the item; otherwise, false.</returns>
|
||||||
public bool Contains(T item)
|
public bool Contains(T item)
|
||||||
{
|
{
|
||||||
var hashcode = _comparer.GetHashCode(item);
|
var hashcode = _comparer.GetHashCode(item);
|
||||||
@@ -324,11 +344,8 @@ public sealed class ConcurrentHashSet<T> : IReadOnlyCollection<T>, ICollection<T
|
|||||||
|
|
||||||
while (current != null)
|
while (current != null)
|
||||||
{
|
{
|
||||||
if (hashcode == current.Hashcode &&
|
if (hashcode == current.Hashcode && _comparer.Equals(current.Item, item))
|
||||||
_comparer.Equals(current.Item, item))
|
|
||||||
{
|
|
||||||
return true;
|
return true;
|
||||||
}
|
|
||||||
|
|
||||||
current = current.Next;
|
current = current.Next;
|
||||||
}
|
}
|
||||||
@@ -336,73 +353,53 @@ public sealed class ConcurrentHashSet<T> : IReadOnlyCollection<T>, ICollection<T
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
void ICollection<T>.Add(T item)
|
||||||
/// Attempts to remove the item from the <see cref="ConcurrentHashSet{T}"/>.
|
=> Add(item);
|
||||||
/// </summary>
|
|
||||||
/// <param name="item">The item to remove.</param>
|
void ICollection<T>.CopyTo(T[] array, int arrayIndex)
|
||||||
/// <returns>true if an item was removed successfully; otherwise, false.</returns>
|
|
||||||
public bool TryRemove(T item)
|
|
||||||
{
|
{
|
||||||
var hashcode = _comparer.GetHashCode(item);
|
if (array is null) throw new ArgumentNullException(nameof(array));
|
||||||
while (true)
|
if (arrayIndex < 0) throw new ArgumentOutOfRangeException(nameof(arrayIndex));
|
||||||
|
|
||||||
|
var locksAcquired = 0;
|
||||||
|
try
|
||||||
{
|
{
|
||||||
var tables = _tables;
|
AcquireAllLocks(ref locksAcquired);
|
||||||
|
|
||||||
GetBucketAndLockNo(hashcode,
|
var count = 0;
|
||||||
out var bucketNo,
|
|
||||||
out var lockNo,
|
|
||||||
tables.Buckets.Length,
|
|
||||||
tables.Locks.Length);
|
|
||||||
|
|
||||||
lock (tables.Locks[lockNo])
|
for (var i = 0; i < _tables.Locks.Length && count >= 0; i++) count += _tables.CountPerLock[i];
|
||||||
{
|
|
||||||
// If the table just got resized, we may not be holding the right lock, and must retry.
|
|
||||||
// This should be a rare occurrence.
|
|
||||||
if (tables != _tables)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
Node previous = null;
|
if (array.Length - count < arrayIndex || count < 0) //"count" itself or "count + arrayIndex" can overflow
|
||||||
for (var current = tables.Buckets[bucketNo]; current != null; current = current.Next)
|
throw new ArgumentException(
|
||||||
{
|
"The index is equal to or greater than the length of the array, or the number of elements in the set is greater than the available space from index to the end of the destination array.");
|
||||||
Debug.Assert((previous is null && current == tables.Buckets[bucketNo]) || previous.Next == current);
|
|
||||||
|
|
||||||
if (hashcode == current.Hashcode &&
|
CopyToItems(array, arrayIndex);
|
||||||
_comparer.Equals(current.Item, item))
|
}
|
||||||
{
|
finally
|
||||||
if (previous is null)
|
{
|
||||||
{
|
ReleaseLocks(0, locksAcquired);
|
||||||
Volatile.Write(ref tables.Buckets[bucketNo], current.Next);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
previous.Next = current.Next;
|
|
||||||
}
|
|
||||||
|
|
||||||
tables.CountPerLock[lockNo]--;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
previous = current;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ICollection<T>.Remove(T item)
|
||||||
|
=> TryRemove(item);
|
||||||
|
|
||||||
IEnumerator IEnumerable.GetEnumerator()
|
IEnumerator IEnumerable.GetEnumerator()
|
||||||
=> GetEnumerator();
|
=> GetEnumerator();
|
||||||
|
|
||||||
/// <summary>Returns an enumerator that iterates through the <see
|
/// <summary>
|
||||||
/// cref="ConcurrentHashSet{T}"/>.</summary>
|
/// Returns an enumerator that iterates through the
|
||||||
/// <returns>An enumerator for the <see cref="ConcurrentHashSet{T}"/>.</returns>
|
/// <see
|
||||||
|
/// cref="ConcurrentHashSet{T}" />
|
||||||
|
/// .
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>An enumerator for the <see cref="ConcurrentHashSet{T}" />.</returns>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// The enumerator returned from the collection is safe to use concurrently with
|
/// The enumerator returned from the collection is safe to use concurrently with
|
||||||
/// reads and writes to the collection, however it does not represent a moment-in-time snapshot
|
/// reads and writes to the collection, however it does not represent a moment-in-time snapshot
|
||||||
/// of the collection. The contents exposed through the enumerator may contain modifications
|
/// of the collection. The contents exposed through the enumerator may contain modifications
|
||||||
/// made to the collection after <see cref="GetEnumerator"/> was called.
|
/// made to the collection after <see cref="GetEnumerator" /> was called.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public IEnumerator<T> GetEnumerator()
|
public IEnumerator<T> GetEnumerator()
|
||||||
{
|
{
|
||||||
@@ -421,58 +418,70 @@ public sealed class ConcurrentHashSet<T> : IReadOnlyCollection<T>, ICollection<T
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ICollection<T>.Add(T item)
|
/// <summary>
|
||||||
=> Add(item);
|
/// Adds the specified item to the <see cref="ConcurrentHashSet{T}" />.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="item">The item to add.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// true if the items was added to the <see cref="ConcurrentHashSet{T}" />
|
||||||
|
/// successfully; false if it already exists.
|
||||||
|
/// </returns>
|
||||||
|
/// <exception cref="T:System.OverflowException">
|
||||||
|
/// The <see cref="ConcurrentHashSet{T}" />
|
||||||
|
/// contains too many items.
|
||||||
|
/// </exception>
|
||||||
|
public bool Add(T item)
|
||||||
|
=> AddInternal(item, _comparer.GetHashCode(item), true);
|
||||||
|
|
||||||
bool ICollection<T>.IsReadOnly
|
/// <summary>
|
||||||
=> false;
|
/// Attempts to remove the item from the <see cref="ConcurrentHashSet{T}" />.
|
||||||
|
/// </summary>
|
||||||
void ICollection<T>.CopyTo(T[] array, int arrayIndex)
|
/// <param name="item">The item to remove.</param>
|
||||||
|
/// <returns>true if an item was removed successfully; otherwise, false.</returns>
|
||||||
|
public bool TryRemove(T item)
|
||||||
{
|
{
|
||||||
if (array is null) throw new ArgumentNullException(nameof(array));
|
var hashcode = _comparer.GetHashCode(item);
|
||||||
if (arrayIndex < 0) throw new ArgumentOutOfRangeException(nameof(arrayIndex));
|
while (true)
|
||||||
|
|
||||||
var locksAcquired = 0;
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
AcquireAllLocks(ref locksAcquired);
|
var tables = _tables;
|
||||||
|
|
||||||
var count = 0;
|
GetBucketAndLockNo(hashcode, out var bucketNo, out var lockNo, tables.Buckets.Length, tables.Locks.Length);
|
||||||
|
|
||||||
for (var i = 0; i < _tables.Locks.Length && count >= 0; i++)
|
lock (tables.Locks[lockNo])
|
||||||
{
|
{
|
||||||
count += _tables.CountPerLock[i];
|
// If the table just got resized, we may not be holding the right lock, and must retry.
|
||||||
|
// This should be a rare occurrence.
|
||||||
|
if (tables != _tables) continue;
|
||||||
|
|
||||||
|
Node previous = null;
|
||||||
|
for (var current = tables.Buckets[bucketNo]; current != null; current = current.Next)
|
||||||
|
{
|
||||||
|
Debug.Assert((previous is null && current == tables.Buckets[bucketNo]) || previous.Next == current);
|
||||||
|
|
||||||
|
if (hashcode == current.Hashcode && _comparer.Equals(current.Item, item))
|
||||||
|
{
|
||||||
|
if (previous is null)
|
||||||
|
Volatile.Write(ref tables.Buckets[bucketNo], current.Next);
|
||||||
|
else
|
||||||
|
previous.Next = current.Next;
|
||||||
|
|
||||||
|
tables.CountPerLock[lockNo]--;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
previous = current;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (array.Length - count < arrayIndex ||
|
return false;
|
||||||
count < 0) //"count" itself or "count + arrayIndex" can overflow
|
|
||||||
{
|
|
||||||
throw new ArgumentException(
|
|
||||||
"The index is equal to or greater than the length of the array, or the number of elements in the set is greater than the available space from index to the end of the destination array.");
|
|
||||||
}
|
|
||||||
|
|
||||||
CopyToItems(array, arrayIndex);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
ReleaseLocks(0, locksAcquired);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ICollection<T>.Remove(T item)
|
|
||||||
=> TryRemove(item);
|
|
||||||
|
|
||||||
private void InitializeFromCollection(IEnumerable<T> collection)
|
private void InitializeFromCollection(IEnumerable<T> collection)
|
||||||
{
|
{
|
||||||
foreach (var item in collection)
|
foreach (var item in collection) AddInternal(item, _comparer.GetHashCode(item), false);
|
||||||
{
|
|
||||||
AddInternal(item, _comparer.GetHashCode(item), false);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_budget == 0)
|
if (_budget == 0) _budget = _tables.Buckets.Length / _tables.Locks.Length;
|
||||||
{
|
|
||||||
_budget = _tables.Buckets.Length / _tables.Locks.Length;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool AddInternal(T item, int hashcode, bool acquireLock)
|
private bool AddInternal(T item, int hashcode, bool acquireLock)
|
||||||
@@ -480,11 +489,7 @@ public sealed class ConcurrentHashSet<T> : IReadOnlyCollection<T>, ICollection<T
|
|||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
var tables = _tables;
|
var tables = _tables;
|
||||||
GetBucketAndLockNo(hashcode,
|
GetBucketAndLockNo(hashcode, out var bucketNo, out var lockNo, tables.Buckets.Length, tables.Locks.Length);
|
||||||
out var bucketNo,
|
|
||||||
out var lockNo,
|
|
||||||
tables.Buckets.Length,
|
|
||||||
tables.Locks.Length);
|
|
||||||
|
|
||||||
var resizeDesired = false;
|
var resizeDesired = false;
|
||||||
var lockTaken = false;
|
var lockTaken = false;
|
||||||
@@ -495,21 +500,15 @@ public sealed class ConcurrentHashSet<T> : IReadOnlyCollection<T>, ICollection<T
|
|||||||
|
|
||||||
// If the table just got resized, we may not be holding the right lock, and must retry.
|
// If the table just got resized, we may not be holding the right lock, and must retry.
|
||||||
// This should be a rare occurrence.
|
// This should be a rare occurrence.
|
||||||
if (tables != _tables)
|
if (tables != _tables) continue;
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try to find this item in the bucket
|
// Try to find this item in the bucket
|
||||||
Node previous = null;
|
Node previous = null;
|
||||||
for (var current = tables.Buckets[bucketNo]; current != null; current = current.Next)
|
for (var current = tables.Buckets[bucketNo]; current != null; current = current.Next)
|
||||||
{
|
{
|
||||||
Debug.Assert((previous is null && current == tables.Buckets[bucketNo]) || previous.Next == current);
|
Debug.Assert((previous is null && current == tables.Buckets[bucketNo]) || previous.Next == current);
|
||||||
if (hashcode == current.Hashcode &&
|
if (hashcode == current.Hashcode && _comparer.Equals(current.Item, item))
|
||||||
_comparer.Equals(current.Item, item))
|
|
||||||
{
|
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
|
|
||||||
previous = current;
|
previous = current;
|
||||||
}
|
}
|
||||||
@@ -526,10 +525,7 @@ public sealed class ConcurrentHashSet<T> : IReadOnlyCollection<T>, ICollection<T
|
|||||||
// It is also possible that GrowTable will increase the budget but won't resize the bucket table.
|
// It is also possible that GrowTable will increase the budget but won't resize the bucket table.
|
||||||
// That happens if the bucket table is found to be poorly utilized due to a bad hash function.
|
// That happens if the bucket table is found to be poorly utilized due to a bad hash function.
|
||||||
//
|
//
|
||||||
if (tables.CountPerLock[lockNo] > _budget)
|
if (tables.CountPerLock[lockNo] > _budget) resizeDesired = true;
|
||||||
{
|
|
||||||
resizeDesired = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
@@ -545,10 +541,7 @@ public sealed class ConcurrentHashSet<T> : IReadOnlyCollection<T>, ICollection<T
|
|||||||
// - As a result, it is possible that GrowTable will be called unnecessarily. But, GrowTable will obtain lock 0
|
// - As a result, it is possible that GrowTable will be called unnecessarily. But, GrowTable will obtain lock 0
|
||||||
// and then verify that the table we passed to it as the argument is still the current table.
|
// and then verify that the table we passed to it as the argument is still the current table.
|
||||||
//
|
//
|
||||||
if (resizeDesired)
|
if (resizeDesired) GrowTable(tables);
|
||||||
{
|
|
||||||
GrowTable(tables);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -561,7 +554,11 @@ public sealed class ConcurrentHashSet<T> : IReadOnlyCollection<T>, ICollection<T
|
|||||||
return bucketNo;
|
return bucketNo;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void GetBucketAndLockNo(int hashcode, out int bucketNo, out int lockNo, int bucketCount,
|
private static void GetBucketAndLockNo(
|
||||||
|
int hashcode,
|
||||||
|
out int bucketNo,
|
||||||
|
out int lockNo,
|
||||||
|
int bucketCount,
|
||||||
int lockCount)
|
int lockCount)
|
||||||
{
|
{
|
||||||
bucketNo = (hashcode & 0x7fffffff) % bucketCount;
|
bucketNo = (hashcode & 0x7fffffff) % bucketCount;
|
||||||
@@ -582,19 +579,14 @@ public sealed class ConcurrentHashSet<T> : IReadOnlyCollection<T>, ICollection<T
|
|||||||
|
|
||||||
// Make sure nobody resized the table while we were waiting for lock 0:
|
// Make sure nobody resized the table while we were waiting for lock 0:
|
||||||
if (tables != _tables)
|
if (tables != _tables)
|
||||||
{
|
|
||||||
// We assume that since the table reference is different, it was already resized (or the budget
|
// We assume that since the table reference is different, it was already resized (or the budget
|
||||||
// was adjusted). If we ever decide to do table shrinking, or replace the table for other reasons,
|
// was adjusted). If we ever decide to do table shrinking, or replace the table for other reasons,
|
||||||
// we will have to revisit this logic.
|
// we will have to revisit this logic.
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
// Compute the (approx.) total size. Use an Int64 accumulation variable to avoid an overflow.
|
// Compute the (approx.) total size. Use an Int64 accumulation variable to avoid an overflow.
|
||||||
long approxCount = 0;
|
long approxCount = 0;
|
||||||
for (var i = 0; i < tables.CountPerLock.Length; i++)
|
for (var i = 0; i < tables.CountPerLock.Length; i++) approxCount += tables.CountPerLock[i];
|
||||||
{
|
|
||||||
approxCount += tables.CountPerLock[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// If the bucket array is too empty, double the budget instead of resizing the table
|
// If the bucket array is too empty, double the budget instead of resizing the table
|
||||||
@@ -602,10 +594,7 @@ public sealed class ConcurrentHashSet<T> : IReadOnlyCollection<T>, ICollection<T
|
|||||||
if (approxCount < tables.Buckets.Length / 4)
|
if (approxCount < tables.Buckets.Length / 4)
|
||||||
{
|
{
|
||||||
_budget = 2 * _budget;
|
_budget = 2 * _budget;
|
||||||
if (_budget < 0)
|
if (_budget < 0) _budget = int.MaxValue;
|
||||||
{
|
|
||||||
_budget = int.MaxValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -623,17 +612,11 @@ public sealed class ConcurrentHashSet<T> : IReadOnlyCollection<T>, ICollection<T
|
|||||||
|
|
||||||
// Now, we only need to check odd integers, and find the first that is not divisible
|
// Now, we only need to check odd integers, and find the first that is not divisible
|
||||||
// by 3, 5 or 7.
|
// by 3, 5 or 7.
|
||||||
while (newLength % 3 == 0 || newLength % 5 == 0 || newLength % 7 == 0)
|
while (newLength % 3 == 0 || newLength % 5 == 0 || newLength % 7 == 0) newLength += 2;
|
||||||
{
|
|
||||||
newLength += 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
Debug.Assert(newLength % 2 != 0);
|
Debug.Assert(newLength % 2 != 0);
|
||||||
|
|
||||||
if (newLength > maxArrayLength)
|
if (newLength > maxArrayLength) maximizeTableSize = true;
|
||||||
{
|
|
||||||
maximizeTableSize = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (OverflowException)
|
catch (OverflowException)
|
||||||
@@ -662,15 +645,8 @@ public sealed class ConcurrentHashSet<T> : IReadOnlyCollection<T>, ICollection<T
|
|||||||
if (_growLockArray && tables.Locks.Length < MaxLockNumber)
|
if (_growLockArray && tables.Locks.Length < MaxLockNumber)
|
||||||
{
|
{
|
||||||
newLocks = new object[tables.Locks.Length * 2];
|
newLocks = new object[tables.Locks.Length * 2];
|
||||||
Array.Copy(tables.Locks,
|
Array.Copy(tables.Locks, 0, newLocks, 0, tables.Locks.Length);
|
||||||
0,
|
for (var i = tables.Locks.Length; i < newLocks.Length; i++) newLocks[i] = new();
|
||||||
newLocks,
|
|
||||||
0,
|
|
||||||
tables.Locks.Length);
|
|
||||||
for (var i = tables.Locks.Length; i < newLocks.Length; i++)
|
|
||||||
{
|
|
||||||
newLocks[i] = new();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var newBuckets = new Node[newLength];
|
var newBuckets = new Node[newLength];
|
||||||
@@ -718,10 +694,8 @@ public sealed class ConcurrentHashSet<T> : IReadOnlyCollection<T>, ICollection<T
|
|||||||
var elems = this.Where(predicate);
|
var elems = this.Where(predicate);
|
||||||
var removed = 0;
|
var removed = 0;
|
||||||
foreach (var elem in elems)
|
foreach (var elem in elems)
|
||||||
{
|
if (TryRemove(elem))
|
||||||
if (this.TryRemove(elem))
|
|
||||||
removed++;
|
removed++;
|
||||||
}
|
|
||||||
|
|
||||||
return removed;
|
return removed;
|
||||||
}
|
}
|
||||||
@@ -751,10 +725,7 @@ public sealed class ConcurrentHashSet<T> : IReadOnlyCollection<T>, ICollection<T
|
|||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
if (lockTaken)
|
if (lockTaken) locksAcquired++;
|
||||||
{
|
|
||||||
locksAcquired++;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -763,22 +734,17 @@ public sealed class ConcurrentHashSet<T> : IReadOnlyCollection<T>, ICollection<T
|
|||||||
{
|
{
|
||||||
Debug.Assert(fromInclusive <= toExclusive);
|
Debug.Assert(fromInclusive <= toExclusive);
|
||||||
|
|
||||||
for (var i = fromInclusive; i < toExclusive; i++)
|
for (var i = fromInclusive; i < toExclusive; i++) Monitor.Exit(_tables.Locks[i]);
|
||||||
{
|
|
||||||
Monitor.Exit(_tables.Locks[i]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CopyToItems(T[] array, int index)
|
private void CopyToItems(T[] array, int index)
|
||||||
{
|
{
|
||||||
var buckets = _tables.Buckets;
|
var buckets = _tables.Buckets;
|
||||||
for (var i = 0; i < buckets.Length; i++)
|
for (var i = 0; i < buckets.Length; i++)
|
||||||
|
for (var current = buckets[i]; current != null; current = current.Next)
|
||||||
{
|
{
|
||||||
for (var current = buckets[i]; current != null; current = current.Next)
|
array[index] = current.Item;
|
||||||
{
|
index++; //this should never flow, CopyToItems is only called when there's no overflow risk
|
||||||
array[index] = current.Item;
|
|
||||||
index++; //this should never flow, CopyToItems is only called when there's no overflow risk
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -799,8 +765,8 @@ public sealed class ConcurrentHashSet<T> : IReadOnlyCollection<T>, ICollection<T
|
|||||||
|
|
||||||
private sealed class Node
|
private sealed class Node
|
||||||
{
|
{
|
||||||
public readonly T Item;
|
|
||||||
public readonly int Hashcode;
|
public readonly int Hashcode;
|
||||||
|
public readonly T Item;
|
||||||
|
|
||||||
public volatile Node Next;
|
public volatile Node Next;
|
||||||
|
|
||||||
@@ -811,4 +777,4 @@ public sealed class ConcurrentHashSet<T> : IReadOnlyCollection<T>, ICollection<T
|
|||||||
Next = next;
|
Next = next;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,6 +1,6 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
using System.Collections;
|
|
||||||
using NadekoBot.Services.Database.Models;
|
using NadekoBot.Services.Database.Models;
|
||||||
|
using System.Collections;
|
||||||
|
|
||||||
namespace NadekoBot.Common.Collections;
|
namespace NadekoBot.Common.Collections;
|
||||||
|
|
||||||
@@ -8,7 +8,6 @@ public class IndexedCollection<T> : IList<T>
|
|||||||
where T : class, IIndexed
|
where T : class, IIndexed
|
||||||
{
|
{
|
||||||
public List<T> Source { get; }
|
public List<T> Source { get; }
|
||||||
private readonly object _locker = new();
|
|
||||||
|
|
||||||
public int Count
|
public int Count
|
||||||
=> Source.Count;
|
=> Source.Count;
|
||||||
@@ -16,8 +15,20 @@ public class IndexedCollection<T> : IList<T>
|
|||||||
public bool IsReadOnly
|
public bool IsReadOnly
|
||||||
=> false;
|
=> false;
|
||||||
|
|
||||||
public int IndexOf([NotNull] T item)
|
public virtual T this[int index]
|
||||||
=> item.Index;
|
{
|
||||||
|
get => Source[index];
|
||||||
|
set
|
||||||
|
{
|
||||||
|
lock (_locker)
|
||||||
|
{
|
||||||
|
value.Index = index;
|
||||||
|
Source[index] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly object _locker = new();
|
||||||
|
|
||||||
public IndexedCollection()
|
public IndexedCollection()
|
||||||
=> Source = new();
|
=> Source = new();
|
||||||
@@ -31,23 +42,8 @@ public class IndexedCollection<T> : IList<T>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateIndexes()
|
public int IndexOf([NotNull] T item)
|
||||||
{
|
=> item.Index;
|
||||||
lock (_locker)
|
|
||||||
{
|
|
||||||
for (var i = 0; i < Source.Count; i++)
|
|
||||||
{
|
|
||||||
if (Source[i].Index != i)
|
|
||||||
Source[i].Index = i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static implicit operator List<T>(IndexedCollection<T> x)
|
|
||||||
=> x.Source;
|
|
||||||
|
|
||||||
public List<T> ToList()
|
|
||||||
=> Source.ToList();
|
|
||||||
|
|
||||||
public IEnumerator<T> GetEnumerator()
|
public IEnumerator<T> GetEnumerator()
|
||||||
=> Source.GetEnumerator();
|
=> Source.GetEnumerator();
|
||||||
@@ -95,10 +91,8 @@ public class IndexedCollection<T> : IList<T>
|
|||||||
if (Source.Remove(item))
|
if (Source.Remove(item))
|
||||||
{
|
{
|
||||||
for (var i = 0; i < Source.Count; i++)
|
for (var i = 0; i < Source.Count; i++)
|
||||||
{
|
|
||||||
if (Source[i].Index != i)
|
if (Source[i].Index != i)
|
||||||
Source[i].Index = i;
|
Source[i].Index = i;
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -112,10 +106,7 @@ public class IndexedCollection<T> : IList<T>
|
|||||||
lock (_locker)
|
lock (_locker)
|
||||||
{
|
{
|
||||||
Source.Insert(index, item);
|
Source.Insert(index, item);
|
||||||
for (var i = index; i < Source.Count; i++)
|
for (var i = index; i < Source.Count; i++) Source[i].Index = i;
|
||||||
{
|
|
||||||
Source[i].Index = i;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -124,23 +115,23 @@ public class IndexedCollection<T> : IList<T>
|
|||||||
lock (_locker)
|
lock (_locker)
|
||||||
{
|
{
|
||||||
Source.RemoveAt(index);
|
Source.RemoveAt(index);
|
||||||
for (var i = index; i < Source.Count; i++)
|
for (var i = index; i < Source.Count; i++) Source[i].Index = i;
|
||||||
{
|
|
||||||
Source[i].Index = i;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual T this[int index]
|
public void UpdateIndexes()
|
||||||
{
|
{
|
||||||
get => Source[index];
|
lock (_locker)
|
||||||
set
|
|
||||||
{
|
{
|
||||||
lock (_locker)
|
for (var i = 0; i < Source.Count; i++)
|
||||||
{
|
if (Source[i].Index != i)
|
||||||
value.Index = index;
|
Source[i].Index = i;
|
||||||
Source[index] = value;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
public static implicit operator List<T>(IndexedCollection<T> x)
|
||||||
|
=> x.Source;
|
||||||
|
|
||||||
|
public List<T> ToList()
|
||||||
|
=> Source.ToList();
|
||||||
|
}
|
@@ -1,4 +1,4 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
namespace NadekoBot.Common;
|
namespace NadekoBot.Common;
|
||||||
|
|
||||||
public class CommandData
|
public class CommandData
|
||||||
@@ -6,4 +6,4 @@ public class CommandData
|
|||||||
public string Cmd { get; set; }
|
public string Cmd { get; set; }
|
||||||
public string Desc { get; set; }
|
public string Desc { get; set; }
|
||||||
public string[] Usage { get; set; }
|
public string[] Usage { get; set; }
|
||||||
}
|
}
|
@@ -1,8 +1,8 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
using System.Globalization;
|
|
||||||
using Cloneable;
|
using Cloneable;
|
||||||
using NadekoBot.Common.Yml;
|
using NadekoBot.Common.Yml;
|
||||||
using SixLabors.ImageSharp.PixelFormats;
|
using SixLabors.ImageSharp.PixelFormats;
|
||||||
|
using System.Globalization;
|
||||||
using YamlDotNet.Core;
|
using YamlDotNet.Core;
|
||||||
using YamlDotNet.Serialization;
|
using YamlDotNet.Serialization;
|
||||||
|
|
||||||
@@ -19,16 +19,14 @@ next to the response. The color depends whether the command
|
|||||||
is completed, errored or in progress (pending)
|
is completed, errored or in progress (pending)
|
||||||
Color settings below are for the color of those lines.
|
Color settings below are for the color of those lines.
|
||||||
To get color's hex, you can go here https://htmlcolorcodes.com/
|
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; }
|
public ColorConfig Color { get; set; }
|
||||||
|
|
||||||
[Comment("Default bot language. It has to be in the list of supported languages (.langli)")]
|
[Comment("Default bot language. It has to be in the list of supported languages (.langli)")]
|
||||||
public CultureInfo DefaultLocale { get; set; }
|
public CultureInfo DefaultLocale { get; set; }
|
||||||
|
|
||||||
[Comment(@"Style in which executed commands will show up in the console.
|
[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; }
|
public ConsoleOutputType ConsoleOutputType { get; set; }
|
||||||
|
|
||||||
// [Comment(@"For what kind of updates will the bot check.
|
// [Comment(@"For what kind of updates will the bot check.
|
||||||
@@ -43,21 +41,18 @@ Allowed values: Simple, Normal, None"
|
|||||||
|
|
||||||
[Comment(
|
[Comment(
|
||||||
@"Do you want the message to be forwarded only to the first owner specified in the list of owners (in creds.yml),
|
@"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)"
|
or all owners? (this might cause the bot to lag if there's a lot of owners specified)")]
|
||||||
)]
|
|
||||||
public bool ForwardToAllOwners { get; set; }
|
public bool ForwardToAllOwners { get; set; }
|
||||||
|
|
||||||
[Comment(@"When a user DMs the bot with a message which is not a command
|
[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.
|
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)]
|
[YamlMember(ScalarStyle = ScalarStyle.Literal)]
|
||||||
public string DmHelpText { get; set; }
|
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.
|
[Comment(@"Only users who send a DM to the bot containing one of the specified words will get a DmHelpText response.
|
||||||
Case insensitive.
|
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; }
|
public List<string> DmHelpTextKeywords { get; set; }
|
||||||
|
|
||||||
[Comment(@"This is the response for the .h command")]
|
[Comment(@"This is the response for the .h command")]
|
||||||
@@ -78,29 +73,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.
|
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
|
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,
|
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; }
|
public bool GroupGreets { get; set; }
|
||||||
|
|
||||||
[Comment(@"Whether the bot will rotate through all specified statuses.
|
[Comment(@"Whether the bot will rotate through all specified statuses.
|
||||||
This setting can be changed via .rots command.
|
This setting can be changed via .rots command.
|
||||||
See RotatingStatuses submodule in Administration."
|
See RotatingStatuses submodule in Administration.")]
|
||||||
)]
|
|
||||||
public bool RotateStatuses { get; set; }
|
public bool RotateStatuses { get; set; }
|
||||||
|
|
||||||
// [Comment(@"Whether the prefix will be a suffix, or prefix.
|
|
||||||
// For example, if your prefix is ! you will run a command called 'cash' by typing either
|
|
||||||
// '!cash @Someone' if your prefixIsSuffix: false or
|
|
||||||
// 'cash @Someone!' if your prefixIsSuffix: true")]
|
|
||||||
// public bool PrefixIsSuffix { get; set; }
|
|
||||||
|
|
||||||
// public string Prefixed(string text) => PrefixIsSuffix
|
|
||||||
// ? text + Prefix
|
|
||||||
// : Prefix + text;
|
|
||||||
|
|
||||||
public string Prefixed(string text)
|
|
||||||
=> Prefix + text;
|
|
||||||
|
|
||||||
public BotConfig()
|
public BotConfig()
|
||||||
{
|
{
|
||||||
var color = new ColorConfig();
|
var color = new ColorConfig();
|
||||||
@@ -149,6 +129,19 @@ See RotatingStatuses submodule in Administration."
|
|||||||
"can you do"
|
"can you do"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// [Comment(@"Whether the prefix will be a suffix, or prefix.
|
||||||
|
// For example, if your prefix is ! you will run a command called 'cash' by typing either
|
||||||
|
// '!cash @Someone' if your prefixIsSuffix: false or
|
||||||
|
// 'cash @Someone!' if your prefixIsSuffix: true")]
|
||||||
|
// public bool PrefixIsSuffix { get; set; }
|
||||||
|
|
||||||
|
// public string Prefixed(string text) => PrefixIsSuffix
|
||||||
|
// ? text + Prefix
|
||||||
|
// : Prefix + text;
|
||||||
|
|
||||||
|
public string Prefixed(string text)
|
||||||
|
=> Prefix + text;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Cloneable]
|
[Cloneable]
|
||||||
@@ -188,5 +181,5 @@ public enum ConsoleOutputType
|
|||||||
{
|
{
|
||||||
Normal = 0,
|
Normal = 0,
|
||||||
Simple = 1,
|
Simple = 1,
|
||||||
None = 2,
|
None = 2
|
||||||
}
|
}
|
@@ -1,18 +1,18 @@
|
|||||||
namespace NadekoBot.Common.Configs;
|
namespace NadekoBot.Common.Configs;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Base interface for available config serializers
|
/// Base interface for available config serializers
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IConfigSeria
|
public interface IConfigSeria
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Serialize the object to string
|
/// Serialize the object to string
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Serialize<T>(T obj)
|
public string Serialize<T>(T obj)
|
||||||
where T: notnull;
|
where T : notnull;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Deserialize string data into an object of the specified type
|
/// Deserialize string data into an object of the specified type
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public T Deserialize<T>(string data);
|
public T Deserialize<T>(string data);
|
||||||
}
|
}
|
@@ -5,33 +5,6 @@ namespace NadekoBot.Common;
|
|||||||
|
|
||||||
public sealed class Creds : IBotCredentials
|
public sealed class Creds : IBotCredentials
|
||||||
{
|
{
|
||||||
public Creds()
|
|
||||||
{
|
|
||||||
Version = 1;
|
|
||||||
Token = string.Empty;
|
|
||||||
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
|
|
||||||
);
|
|
||||||
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" };
|
|
||||||
|
|
||||||
CoordinatorUrl = "http://localhost:3442";
|
|
||||||
|
|
||||||
RestartCommand = new();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Comment(@"DO NOT CHANGE")]
|
[Comment(@"DO NOT CHANGE")]
|
||||||
public int Version { get; set; }
|
public int Version { get; set; }
|
||||||
|
|
||||||
@@ -39,28 +12,24 @@ public sealed class Creds : IBotCredentials
|
|||||||
public string Token { get; set; }
|
public string Token { get; set; }
|
||||||
|
|
||||||
[Comment(@"List of Ids of the users who have bot owner permissions
|
[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; }
|
public ICollection<ulong> OwnerIds { get; set; }
|
||||||
|
|
||||||
[Comment(@"The number of shards that the bot will running on.
|
[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; }
|
public int TotalShards { get; set; }
|
||||||
|
|
||||||
[Comment(
|
[Comment(
|
||||||
@"Login to https://console.cloud.google.com, create a new project, go to APIs & Services -> Library -> YouTube Data API and enable it.
|
@"Login to https://console.cloud.google.com, create a new project, go to APIs & Services -> Library -> YouTube Data API and enable it.
|
||||||
Then, go to APIs and Services -> Credentials and click Create credentials -> API key.
|
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; }
|
public string GoogleApiKey { get; set; }
|
||||||
|
|
||||||
[Comment(@"Settings for voting system for discordbots. Meant for use on global Nadeko.")]
|
[Comment(@"Settings for voting system for discordbots. Meant for use on global Nadeko.")]
|
||||||
public VotesSettings Votes { get; set; }
|
public VotesSettings Votes { get; set; }
|
||||||
|
|
||||||
[Comment(@"Patreon auto reward system settings.
|
[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; }
|
public PatreonSettings Patreon { get; set; }
|
||||||
|
|
||||||
[Comment(@"Api key for sending stats to DiscordBotList.")]
|
[Comment(@"Api key for sending stats to DiscordBotList.")]
|
||||||
@@ -76,27 +45,23 @@ go to https://www.patreon.com/portal -> my clients -> create client"
|
|||||||
public DbOptions Db { get; set; }
|
public DbOptions Db { get; set; }
|
||||||
|
|
||||||
[Comment(@"Address and port of the coordinator endpoint. Leave empty for default.
|
[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; }
|
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; }
|
public string RapidApiKey { get; set; }
|
||||||
|
|
||||||
[Comment(@"https://locationiq.com api key (register and you will receive the token in the email).
|
[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; }
|
public string LocationIqApiKey { get; set; }
|
||||||
|
|
||||||
[Comment(@"https://timezonedb.com api key (register and you will receive the token in the email).
|
[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; }
|
public string TimezoneDbApiKey { get; set; }
|
||||||
|
|
||||||
[Comment(@"https://pro.coinmarketcap.com/account/ api key. There is a free plan for personal use.
|
[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; }
|
public string CoinmarketcapApiKey { get; set; }
|
||||||
|
|
||||||
[Comment(@"Api key used for Osu related commands. Obtain this key at https://osu.ppy.sh/p/api")]
|
[Comment(@"Api key used for Osu related commands. Obtain this key at https://osu.ppy.sh/p/api")]
|
||||||
@@ -112,10 +77,28 @@ Linux default
|
|||||||
args: ""NadekoBot.dll -- {0}""
|
args: ""NadekoBot.dll -- {0}""
|
||||||
Windows default
|
Windows default
|
||||||
cmd: NadekoBot.exe
|
cmd: NadekoBot.exe
|
||||||
args: {0}"
|
args: {0}")]
|
||||||
)]
|
|
||||||
public RestartConfig RestartCommand { get; set; }
|
public RestartConfig RestartCommand { get; set; }
|
||||||
|
|
||||||
|
public Creds()
|
||||||
|
{
|
||||||
|
Version = 1;
|
||||||
|
Token = string.Empty;
|
||||||
|
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);
|
||||||
|
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" };
|
||||||
|
|
||||||
|
CoordinatorUrl = "http://localhost:3442";
|
||||||
|
|
||||||
|
RestartCommand = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public class DbOptions
|
public class DbOptions
|
||||||
{
|
{
|
||||||
@@ -134,8 +117,7 @@ Windows default
|
|||||||
public string ClientSecret { get; set; }
|
public string ClientSecret { get; set; }
|
||||||
|
|
||||||
[Comment(
|
[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)"
|
@"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 string CampaignId { get; set; }
|
||||||
|
|
||||||
public PatreonSettings(
|
public PatreonSettings(
|
||||||
@@ -159,24 +141,20 @@ Windows default
|
|||||||
{
|
{
|
||||||
[Comment(@"top.gg votes service url
|
[Comment(@"top.gg votes service url
|
||||||
This is the url of your instance of the NadekoBot.Votes api
|
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; }
|
public string TopggServiceUrl { get; set; }
|
||||||
|
|
||||||
[Comment(@"Authorization header value sent to the TopGG service url with each request
|
[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; }
|
public string TopggKey { get; set; }
|
||||||
|
|
||||||
[Comment(@"discords.com votes service url
|
[Comment(@"discords.com votes service url
|
||||||
This is the url of your instance of the NadekoBot.Votes api
|
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; }
|
public string DiscordsServiceUrl { get; set; }
|
||||||
|
|
||||||
[Comment(@"Authorization header value sent to the Discords service url with each request
|
[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 string DiscordsKey { get; set; }
|
||||||
|
|
||||||
public VotesSettings()
|
public VotesSettings()
|
||||||
@@ -229,14 +207,14 @@ This should be equivalent to the DiscordsKey in your NadekoBot.Votes api appsett
|
|||||||
|
|
||||||
public class RestartConfig
|
public class RestartConfig
|
||||||
{
|
{
|
||||||
public RestartConfig(string cmd, string args)
|
|
||||||
{
|
|
||||||
this.Cmd = cmd;
|
|
||||||
this.Args = args;
|
|
||||||
}
|
|
||||||
|
|
||||||
public string Cmd { get; set; }
|
public string Cmd { get; set; }
|
||||||
public string Args { get; set; }
|
public string Args { get; set; }
|
||||||
|
|
||||||
|
public RestartConfig(string cmd, string args)
|
||||||
|
{
|
||||||
|
Cmd = cmd;
|
||||||
|
Args = args;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -7,7 +7,7 @@ public class DownloadTracker : INService
|
|||||||
private readonly SemaphoreSlim _downloadUsersSemaphore = new(1, 1);
|
private readonly SemaphoreSlim _downloadUsersSemaphore = new(1, 1);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Ensures all users on the specified guild were downloaded within the last hour.
|
/// Ensures all users on the specified guild were downloaded within the last hour.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="guild">Guild to check and potentially download users from</param>
|
/// <param name="guild">Guild to check and potentially download users from</param>
|
||||||
/// <returns>Task representing download state</returns>
|
/// <returns>Task representing download state</returns>
|
||||||
@@ -21,8 +21,7 @@ public class DownloadTracker : INService
|
|||||||
// download once per hour at most
|
// download once per hour at most
|
||||||
var added = LastDownloads.AddOrUpdate(guild.Id,
|
var added = LastDownloads.AddOrUpdate(guild.Id,
|
||||||
now,
|
now,
|
||||||
(_, 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
|
// means that this entry was just added - download the users
|
||||||
if (added == now)
|
if (added == now)
|
||||||
@@ -33,4 +32,4 @@ public class DownloadTracker : INService
|
|||||||
_downloadUsersSemaphore.Release();
|
_downloadUsersSemaphore.Release();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,4 +1,4 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
namespace NadekoBot.Common;
|
namespace NadekoBot.Common;
|
||||||
|
|
||||||
public static class Helpers
|
public static class Helpers
|
||||||
@@ -10,4 +10,4 @@ public static class Helpers
|
|||||||
|
|
||||||
Environment.Exit(exitCode);
|
Environment.Exit(exitCode);
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,4 +1,4 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
namespace NadekoBot;
|
namespace NadekoBot;
|
||||||
|
|
||||||
public interface IBotCredentials
|
public interface IBotCredentials
|
||||||
@@ -27,4 +27,4 @@ public class RestartConfig
|
|||||||
{
|
{
|
||||||
public string Cmd { get; set; }
|
public string Cmd { get; set; }
|
||||||
public string Args { get; set; }
|
public string Args { get; set; }
|
||||||
}
|
}
|
@@ -1,8 +1,8 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
namespace NadekoBot.Common;
|
namespace NadekoBot.Common;
|
||||||
|
|
||||||
public interface ICloneable<T>
|
public interface ICloneable<T>
|
||||||
where T : new()
|
where T : new()
|
||||||
{
|
{
|
||||||
public T Clone();
|
public T Clone();
|
||||||
}
|
}
|
@@ -1,4 +1,4 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
namespace NadekoBot;
|
namespace NadekoBot;
|
||||||
|
|
||||||
public interface IEmbedBuilder
|
public interface IEmbedBuilder
|
||||||
@@ -19,5 +19,5 @@ public enum EmbedColor
|
|||||||
{
|
{
|
||||||
Ok,
|
Ok,
|
||||||
Pending,
|
Pending,
|
||||||
Error,
|
Error
|
||||||
}
|
}
|
@@ -1,7 +1,7 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
namespace NadekoBot.Common;
|
namespace NadekoBot.Common;
|
||||||
|
|
||||||
public interface INadekoCommandOptions
|
public interface INadekoCommandOptions
|
||||||
{
|
{
|
||||||
void NormalizeOptions();
|
void NormalizeOptions();
|
||||||
}
|
}
|
@@ -4,4 +4,4 @@ namespace NadekoBot.Common;
|
|||||||
public interface IPlaceholderProvider
|
public interface IPlaceholderProvider
|
||||||
{
|
{
|
||||||
public IEnumerable<(string Name, Func<string> Func)> GetPlaceholders();
|
public IEnumerable<(string Name, Func<string> Func)> GetPlaceholders();
|
||||||
}
|
}
|
@@ -1,4 +1,4 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
using NadekoBot.Common.Yml;
|
using NadekoBot.Common.Yml;
|
||||||
|
|
||||||
namespace NadekoBot.Common;
|
namespace NadekoBot.Common;
|
||||||
@@ -46,4 +46,4 @@ public class ImageUrls
|
|||||||
{
|
{
|
||||||
public Uri Bg { get; set; }
|
public Uri Bg { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -11,4 +11,4 @@ public class CultureInfoConverter : JsonConverter<CultureInfo>
|
|||||||
|
|
||||||
public override void Write(Utf8JsonWriter writer, CultureInfo value, JsonSerializerOptions options)
|
public override void Write(Utf8JsonWriter writer, CultureInfo value, JsonSerializerOptions options)
|
||||||
=> writer.WriteStringValue(value.Name);
|
=> writer.WriteStringValue(value.Name);
|
||||||
}
|
}
|
@@ -11,4 +11,4 @@ public class Rgba32Converter : JsonConverter<Rgba32>
|
|||||||
|
|
||||||
public override void Write(Utf8JsonWriter writer, Rgba32 value, JsonSerializerOptions options)
|
public override void Write(Utf8JsonWriter writer, Rgba32 value, JsonSerializerOptions options)
|
||||||
=> writer.WriteStringValue(value.ToHex());
|
=> writer.WriteStringValue(value.ToHex());
|
||||||
}
|
}
|
@@ -1,4 +1,4 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
namespace NadekoBot.Common;
|
namespace NadekoBot.Common;
|
||||||
@@ -10,8 +10,8 @@ namespace NadekoBot.Common;
|
|||||||
public readonly struct kwum : IEquatable<kwum>
|
public readonly struct kwum : IEquatable<kwum>
|
||||||
#pragma warning restore IDE1006
|
#pragma warning restore IDE1006
|
||||||
{
|
{
|
||||||
private readonly int _value;
|
|
||||||
private const string VALID_CHARACTERS = "23456789abcdefghijkmnpqrstuvwxyz";
|
private const string VALID_CHARACTERS = "23456789abcdefghijkmnpqrstuvwxyz";
|
||||||
|
private readonly int _value;
|
||||||
|
|
||||||
public kwum(int num)
|
public kwum(int num)
|
||||||
=> _value = num;
|
=> _value = num;
|
||||||
@@ -24,10 +24,6 @@ public readonly struct kwum : IEquatable<kwum>
|
|||||||
_value = InternalCharToValue(c);
|
_value = InternalCharToValue(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
private static int InternalCharToValue(in char c)
|
|
||||||
=> VALID_CHARACTERS.IndexOf(c);
|
|
||||||
|
|
||||||
public kwum(in ReadOnlySpan<char> input)
|
public kwum(in ReadOnlySpan<char> input)
|
||||||
{
|
{
|
||||||
_value = 0;
|
_value = 0;
|
||||||
@@ -41,6 +37,10 @@ public readonly struct kwum : IEquatable<kwum>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private static int InternalCharToValue(in char c)
|
||||||
|
=> VALID_CHARACTERS.IndexOf(c);
|
||||||
|
|
||||||
public static bool TryParse(in ReadOnlySpan<char> input, out kwum value)
|
public static bool TryParse(in ReadOnlySpan<char> input, out kwum value)
|
||||||
{
|
{
|
||||||
value = default;
|
value = default;
|
||||||
@@ -96,4 +96,4 @@ public readonly struct kwum : IEquatable<kwum>
|
|||||||
|
|
||||||
public override int GetHashCode()
|
public override int GetHashCode()
|
||||||
=> _value.GetHashCode();
|
=> _value.GetHashCode();
|
||||||
}
|
}
|
@@ -1,18 +1,14 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
using CommandLine;
|
using CommandLine;
|
||||||
|
|
||||||
namespace NadekoBot.Common;
|
namespace NadekoBot.Common;
|
||||||
|
|
||||||
public class LbOpts : INadekoCommandOptions
|
public class LbOpts : INadekoCommandOptions
|
||||||
{
|
{
|
||||||
[Option('c',
|
[Option('c', "clean", Default = false, HelpText = "Only show users who are on the server.")]
|
||||||
"clean",
|
|
||||||
Default = false,
|
|
||||||
HelpText = "Only show users who are on the server."
|
|
||||||
)]
|
|
||||||
public bool Clean { get; set; }
|
public bool Clean { get; set; }
|
||||||
|
|
||||||
public void NormalizeOptions()
|
public void NormalizeOptions()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,4 +1,4 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
@@ -16,23 +16,20 @@ public class LoginErrorHandler
|
|||||||
switch (ex.HttpCode)
|
switch (ex.HttpCode)
|
||||||
{
|
{
|
||||||
case HttpStatusCode.Unauthorized:
|
case HttpStatusCode.Unauthorized:
|
||||||
Log.Error("Your bot token is wrong.\n" +
|
Log.Error("Your bot token is wrong.\n"
|
||||||
"You can find the bot token under the Bot tab in the developer page.\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;
|
break;
|
||||||
|
|
||||||
case HttpStatusCode.BadRequest:
|
case HttpStatusCode.BadRequest:
|
||||||
Log.Error("Something has been incorrectly formatted in your credentials file.\n" +
|
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");
|
Log.Error("If you are on Linux, make sure Redis is installed and running");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case HttpStatusCode.RequestTimeout:
|
case HttpStatusCode.RequestTimeout:
|
||||||
Log.Error("The request timed out. Make sure you have no external program blocking the bot " +
|
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;
|
break;
|
||||||
|
|
||||||
case HttpStatusCode.ServiceUnavailable:
|
case HttpStatusCode.ServiceUnavailable:
|
||||||
@@ -41,9 +38,8 @@ public class LoginErrorHandler
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case HttpStatusCode.TooManyRequests:
|
case HttpStatusCode.TooManyRequests:
|
||||||
Log.Error("Your bot has been ratelimited by Discord. Please, try again later.\n" +
|
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;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@@ -53,4 +49,4 @@ public class LoginErrorHandler
|
|||||||
|
|
||||||
Log.Fatal(ex, "Fatal error occurred while loading credentials");
|
Log.Fatal(ex, "Fatal error occurred while loading credentials");
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,10 +1,10 @@
|
|||||||
namespace NadekoBot.Common.ModuleBehaviors;
|
namespace NadekoBot.Common.ModuleBehaviors;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Implemented by modules which block execution before anything is executed
|
/// Implemented by modules which block execution before anything is executed
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IEarlyBehavior
|
public interface IEarlyBehavior
|
||||||
{
|
{
|
||||||
int Priority { get; }
|
int Priority { get; }
|
||||||
Task<bool> RunBehavior(IGuild guild, IUserMessage msg);
|
Task<bool> RunBehavior(IGuild guild, IUserMessage msg);
|
||||||
}
|
}
|
@@ -7,4 +7,4 @@ public interface IInputTransformer
|
|||||||
IMessageChannel channel,
|
IMessageChannel channel,
|
||||||
IUser user,
|
IUser user,
|
||||||
string input);
|
string input);
|
||||||
}
|
}
|
@@ -5,4 +5,4 @@ public interface ILateBlocker
|
|||||||
public int Priority { get; }
|
public int Priority { get; }
|
||||||
|
|
||||||
Task<bool> TryBlockLate(ICommandContext context, string moduleName, CommandInfo command);
|
Task<bool> TryBlockLate(ICommandContext context, string moduleName, CommandInfo command);
|
||||||
}
|
}
|
@@ -1,9 +1,9 @@
|
|||||||
namespace NadekoBot.Common.ModuleBehaviors;
|
namespace NadekoBot.Common.ModuleBehaviors;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Last thing to be executed, won't stop further executions
|
/// Last thing to be executed, won't stop further executions
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface ILateExecutor
|
public interface ILateExecutor
|
||||||
{
|
{
|
||||||
Task LateExecute(IGuild guild, IUserMessage msg);
|
Task LateExecute(IGuild guild, IUserMessage msg);
|
||||||
}
|
}
|
@@ -1,13 +1,13 @@
|
|||||||
namespace NadekoBot.Common.ModuleBehaviors;
|
namespace NadekoBot.Common.ModuleBehaviors;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// All services which need to execute something after
|
/// All services which need to execute something after
|
||||||
/// the bot is ready should implement this interface
|
/// the bot is ready should implement this interface
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IReadyExecutor
|
public interface IReadyExecutor
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Executed when bot is ready
|
/// Executed when bot is ready
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Task OnReadyAsync();
|
public Task OnReadyAsync();
|
||||||
}
|
}
|
@@ -1,13 +1,13 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
|
|
||||||
// ReSharper disable InconsistentNaming
|
// ReSharper disable InconsistentNaming
|
||||||
|
|
||||||
namespace NadekoBot.Modules;
|
namespace NadekoBot.Modules;
|
||||||
|
|
||||||
[UsedImplicitly(ImplicitUseTargetFlags.Default |
|
[UsedImplicitly(ImplicitUseTargetFlags.Default
|
||||||
ImplicitUseTargetFlags.WithInheritors |
|
| ImplicitUseTargetFlags.WithInheritors
|
||||||
ImplicitUseTargetFlags.WithMembers
|
| ImplicitUseTargetFlags.WithMembers)]
|
||||||
)]
|
|
||||||
public abstract class NadekoModule : ModuleBase
|
public abstract class NadekoModule : ModuleBase
|
||||||
{
|
{
|
||||||
protected CultureInfo Culture { get; set; }
|
protected CultureInfo Culture { get; set; }
|
||||||
@@ -36,12 +36,7 @@ public abstract class NadekoModule : ModuleBase
|
|||||||
string error,
|
string error,
|
||||||
string url = null,
|
string url = null,
|
||||||
string footer = null)
|
string footer = null)
|
||||||
=> ctx.Channel.SendErrorAsync(_eb,
|
=> ctx.Channel.SendErrorAsync(_eb, title, error, url, footer);
|
||||||
title,
|
|
||||||
error,
|
|
||||||
url,
|
|
||||||
footer
|
|
||||||
);
|
|
||||||
|
|
||||||
public Task<IUserMessage> SendConfirmAsync(string text)
|
public Task<IUserMessage> SendConfirmAsync(string text)
|
||||||
=> ctx.Channel.SendConfirmAsync(_eb, text);
|
=> ctx.Channel.SendConfirmAsync(_eb, text);
|
||||||
@@ -51,12 +46,7 @@ public abstract class NadekoModule : ModuleBase
|
|||||||
string text,
|
string text,
|
||||||
string url = null,
|
string url = null,
|
||||||
string footer = null)
|
string footer = null)
|
||||||
=> ctx.Channel.SendConfirmAsync(_eb,
|
=> ctx.Channel.SendConfirmAsync(_eb, title, text, url, footer);
|
||||||
title,
|
|
||||||
text,
|
|
||||||
url,
|
|
||||||
footer
|
|
||||||
);
|
|
||||||
|
|
||||||
public Task<IUserMessage> SendPendingAsync(string text)
|
public Task<IUserMessage> SendPendingAsync(string text)
|
||||||
=> ctx.Channel.SendPendingAsync(_eb, text);
|
=> ctx.Channel.SendPendingAsync(_eb, text);
|
||||||
@@ -81,8 +71,7 @@ public abstract class NadekoModule : ModuleBase
|
|||||||
|
|
||||||
public async Task<bool> PromptUserConfirmAsync(IEmbedBuilder embed)
|
public async Task<bool> PromptUserConfirmAsync(IEmbedBuilder embed)
|
||||||
{
|
{
|
||||||
embed.WithPendingColor()
|
embed.WithPendingColor().WithFooter("yes/no");
|
||||||
.WithFooter("yes/no");
|
|
||||||
|
|
||||||
var msg = await ctx.Channel.EmbedAsync(embed);
|
var msg = await ctx.Channel.EmbedAsync(embed);
|
||||||
try
|
try
|
||||||
@@ -90,11 +79,8 @@ public abstract class NadekoModule : ModuleBase
|
|||||||
var input = await GetUserInputAsync(ctx.User.Id, ctx.Channel.Id);
|
var input = await GetUserInputAsync(ctx.User.Id, ctx.Channel.Id);
|
||||||
input = input?.ToUpperInvariant();
|
input = input?.ToUpperInvariant();
|
||||||
|
|
||||||
if (input != "YES" &&
|
if (input != "YES" && input != "Y")
|
||||||
input != "Y")
|
|
||||||
{
|
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -113,11 +99,8 @@ public abstract class NadekoModule : ModuleBase
|
|||||||
{
|
{
|
||||||
dsc.MessageReceived += MessageReceived;
|
dsc.MessageReceived += MessageReceived;
|
||||||
|
|
||||||
if (await Task.WhenAny(userInputTask.Task, Task.Delay(10000)) !=
|
if (await Task.WhenAny(userInputTask.Task, Task.Delay(10000)) != userInputTask.Task)
|
||||||
userInputTask.Task)
|
|
||||||
{
|
|
||||||
return null;
|
return null;
|
||||||
}
|
|
||||||
|
|
||||||
return await userInputTask.Task;
|
return await userInputTask.Task;
|
||||||
}
|
}
|
||||||
@@ -129,23 +112,17 @@ public abstract class NadekoModule : ModuleBase
|
|||||||
Task MessageReceived(SocketMessage arg)
|
Task MessageReceived(SocketMessage arg)
|
||||||
{
|
{
|
||||||
var _ = Task.Run(() =>
|
var _ = Task.Run(() =>
|
||||||
{
|
{
|
||||||
if (arg is not SocketUserMessage userMsg ||
|
if (arg is not SocketUserMessage userMsg
|
||||||
userMsg.Channel is not ITextChannel ||
|
|| userMsg.Channel is not ITextChannel
|
||||||
userMsg.Author.Id != userId ||
|
|| userMsg.Author.Id != userId
|
||||||
userMsg.Channel.Id != channelId)
|
|| userMsg.Channel.Id != channelId)
|
||||||
{
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (userInputTask.TrySetResult(arg.Content))
|
|
||||||
{
|
|
||||||
userMsg.DeleteAfter(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
|
||||||
);
|
if (userInputTask.TrySetResult(arg.Content)) userMsg.DeleteAfter(1);
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
});
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -162,4 +139,4 @@ public abstract class NadekoSubmodule : NadekoModule
|
|||||||
|
|
||||||
public abstract class NadekoSubmodule<TService> : NadekoModule<TService>
|
public abstract class NadekoSubmodule<TService> : NadekoModule<TService>
|
||||||
{
|
{
|
||||||
}
|
}
|
@@ -1,4 +1,4 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
|
|
||||||
namespace NadekoBot.Common;
|
namespace NadekoBot.Common;
|
||||||
@@ -8,7 +8,6 @@ public class NadekoRandom : Random
|
|||||||
private readonly RandomNumberGenerator _rng;
|
private readonly RandomNumberGenerator _rng;
|
||||||
|
|
||||||
public NadekoRandom()
|
public NadekoRandom()
|
||||||
: base()
|
|
||||||
=> _rng = RandomNumberGenerator.Create();
|
=> _rng = RandomNumberGenerator.Create();
|
||||||
|
|
||||||
public override int Next()
|
public override int Next()
|
||||||
@@ -67,4 +66,4 @@ public class NadekoRandom : Random
|
|||||||
_rng.GetBytes(bytes);
|
_rng.GetBytes(bytes);
|
||||||
return BitConverter.ToDouble(bytes, 0);
|
return BitConverter.ToDouble(bytes, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,4 +1,4 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
|
||||||
namespace NadekoBot.Common;
|
namespace NadekoBot.Common;
|
||||||
@@ -7,7 +7,10 @@ namespace NadekoBot.Common;
|
|||||||
[SuppressMessage("Style", "IDE0022:Use expression body for methods")]
|
[SuppressMessage("Style", "IDE0022:Use expression body for methods")]
|
||||||
public sealed class NoPublicBotAttribute : PreconditionAttribute
|
public sealed class NoPublicBotAttribute : PreconditionAttribute
|
||||||
{
|
{
|
||||||
public override Task<PreconditionResult> CheckPermissionsAsync(ICommandContext context, CommandInfo command, IServiceProvider services)
|
public override Task<PreconditionResult> CheckPermissionsAsync(
|
||||||
|
ICommandContext context,
|
||||||
|
CommandInfo command,
|
||||||
|
IServiceProvider services)
|
||||||
{
|
{
|
||||||
#if GLOBAL_NADEKO
|
#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/)."));
|
||||||
@@ -15,4 +18,4 @@ public sealed class NoPublicBotAttribute : PreconditionAttribute
|
|||||||
return Task.FromResult(PreconditionResult.FromSuccess());
|
return Task.FromResult(PreconditionResult.FromSuccess());
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -44,4 +44,4 @@ public class OldImageUrls
|
|||||||
{
|
{
|
||||||
public Uri Bg { get; set; }
|
public Uri Bg { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,4 +1,4 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
using CommandLine;
|
using CommandLine;
|
||||||
|
|
||||||
namespace NadekoBot.Common;
|
namespace NadekoBot.Common;
|
||||||
@@ -13,13 +13,12 @@ public static class OptionsParser
|
|||||||
where T : INadekoCommandOptions
|
where T : INadekoCommandOptions
|
||||||
{
|
{
|
||||||
using var p = new Parser(x =>
|
using var p = new Parser(x =>
|
||||||
{
|
{
|
||||||
x.HelpWriter = null;
|
x.HelpWriter = null;
|
||||||
}
|
});
|
||||||
);
|
|
||||||
var res = p.ParseArguments<T>(args);
|
var res = p.ParseArguments<T>(args);
|
||||||
options = res.MapResult(x => x, x => options);
|
options = res.MapResult(x => x, x => options);
|
||||||
options.NormalizeOptions();
|
options.NormalizeOptions();
|
||||||
return (options, res.Tag == ParserResultType.Parsed);
|
return (options, res.Tag == ParserResultType.Parsed);
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,4 +1,4 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
namespace NadekoBot.Common;
|
namespace NadekoBot.Common;
|
||||||
|
|
||||||
public class OsuMapData
|
public class OsuMapData
|
||||||
@@ -6,4 +6,4 @@ public class OsuMapData
|
|||||||
public string Title { get; set; }
|
public string Title { get; set; }
|
||||||
public string Artist { get; set; }
|
public string Artist { get; set; }
|
||||||
public string Version { get; set; }
|
public string Version { get; set; }
|
||||||
}
|
}
|
@@ -1,4 +1,4 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace NadekoBot.Common;
|
namespace NadekoBot.Common;
|
||||||
@@ -55,4 +55,4 @@ public class OsuUserBests
|
|||||||
|
|
||||||
[JsonProperty("replay_available")]
|
[JsonProperty("replay_available")]
|
||||||
public string ReplayAvailable { get; set; }
|
public string ReplayAvailable { get; set; }
|
||||||
}
|
}
|
@@ -1,4 +1,4 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
namespace NadekoBot.Common;
|
namespace NadekoBot.Common;
|
||||||
|
|
||||||
public static class PlatformHelper
|
public static class PlatformHelper
|
||||||
@@ -13,8 +13,7 @@ public static class PlatformHelper
|
|||||||
get
|
get
|
||||||
{
|
{
|
||||||
var now = Environment.TickCount;
|
var now = Environment.TickCount;
|
||||||
if (processorCount == 0 ||
|
if (processorCount == 0 || now - lastProcessorCountRefreshTicks >= PROCESSOR_COUNT_REFRESH_INTERVAL_MS)
|
||||||
now - lastProcessorCountRefreshTicks >= PROCESSOR_COUNT_REFRESH_INTERVAL_MS)
|
|
||||||
{
|
{
|
||||||
processorCount = Environment.ProcessorCount;
|
processorCount = Environment.ProcessorCount;
|
||||||
lastProcessorCountRefreshTicks = now;
|
lastProcessorCountRefreshTicks = now;
|
||||||
@@ -23,4 +22,4 @@ public static class PlatformHelper
|
|||||||
return processorCount;
|
return processorCount;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,8 +1,8 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
namespace NadekoBot.Common.Pokemon;
|
namespace NadekoBot.Common.Pokemon;
|
||||||
|
|
||||||
public class PokemonNameId
|
public class PokemonNameId
|
||||||
{
|
{
|
||||||
public int Id { get; set; }
|
public int Id { get; set; }
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
}
|
}
|
@@ -1,10 +1,24 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace NadekoBot.Common.Pokemon;
|
namespace NadekoBot.Common.Pokemon;
|
||||||
|
|
||||||
public class SearchPokemon
|
public class SearchPokemon
|
||||||
{
|
{
|
||||||
|
[JsonProperty("num")]
|
||||||
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
public string Species { get; set; }
|
||||||
|
public string[] Types { get; set; }
|
||||||
|
public GenderRatioClass GenderRatio { get; set; }
|
||||||
|
public BaseStatsClass BaseStats { get; set; }
|
||||||
|
public Dictionary<string, string> Abilities { get; set; }
|
||||||
|
public float HeightM { get; set; }
|
||||||
|
public float WeightKg { get; set; }
|
||||||
|
public string Color { get; set; }
|
||||||
|
public string[] Evos { get; set; }
|
||||||
|
public string[] EggGroups { get; set; }
|
||||||
|
|
||||||
public class GenderRatioClass
|
public class GenderRatioClass
|
||||||
{
|
{
|
||||||
public float M { get; set; }
|
public float M { get; set; }
|
||||||
@@ -24,18 +38,4 @@ public class SearchPokemon
|
|||||||
=> $@"💚**HP:** {Hp,-4} ⚔**ATK:** {Atk,-4} 🛡**DEF:** {Def,-4}
|
=> $@"💚**HP:** {Hp,-4} ⚔**ATK:** {Atk,-4} 🛡**DEF:** {Def,-4}
|
||||||
✨**SPA:** {Spa,-4} 🎇**SPD:** {Spd,-4} 💨**SPE:** {Spe,-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; }
|
|
||||||
public BaseStatsClass BaseStats { get; set; }
|
|
||||||
public Dictionary<string, string> Abilities { get; set; }
|
|
||||||
public float HeightM { get; set; }
|
|
||||||
public float WeightKg { get; set; }
|
|
||||||
public string Color { get; set; }
|
|
||||||
public string[] Evos { get; set; }
|
|
||||||
public string[] EggGroups { get; set; }
|
|
||||||
}
|
|
@@ -7,4 +7,4 @@ public class SearchPokemonAbility
|
|||||||
public string ShortDesc { get; set; }
|
public string ShortDesc { get; set; }
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
public float Rating { get; set; }
|
public float Rating { get; set; }
|
||||||
}
|
}
|
@@ -33,15 +33,10 @@ public class EventPubSub : IPubSub
|
|||||||
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
|
// if this class ever gets used, this needs to be properly implemented
|
||||||
// 1. ignore all valuetasks which are completed
|
// 1. ignore all valuetasks which are completed
|
||||||
// 2. run all other tasks in parallel
|
// 2. run all other tasks in parallel
|
||||||
return actions
|
return actions.SelectMany(kvp => kvp.Value).Select(action => action(data).AsTask()).WhenAll();
|
||||||
.SelectMany(kvp => kvp.Value)
|
|
||||||
.Select(action => action(data).AsTask())
|
|
||||||
.WhenAll();
|
|
||||||
}
|
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
@@ -53,7 +48,6 @@ public class EventPubSub : IPubSub
|
|||||||
{
|
{
|
||||||
// get subscriptions for this action
|
// get subscriptions for this action
|
||||||
if (_actions.TryGetValue(key.Key, out var actions))
|
if (_actions.TryGetValue(key.Key, out var actions))
|
||||||
{
|
|
||||||
// get subscriptions which have the same action hash code
|
// get subscriptions which have the same action hash code
|
||||||
// note: having this as a list allows for multiple subscriptions of
|
// note: having this as a list allows for multiple subscriptions of
|
||||||
// the same insance's/static method
|
// the same insance's/static method
|
||||||
@@ -71,15 +65,11 @@ public class EventPubSub : IPubSub
|
|||||||
// if our dictionary has no more elements after
|
// if our dictionary has no more elements after
|
||||||
// removing the entry
|
// removing the entry
|
||||||
// it's safe to remove it from the key's subscriptions
|
// it's safe to remove it from the key's subscriptions
|
||||||
if (actions.Count == 0)
|
if (actions.Count == 0) _actions.Remove(key.Key);
|
||||||
{
|
|
||||||
_actions.Remove(key.Key);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -4,4 +4,4 @@ public interface IPubSub
|
|||||||
{
|
{
|
||||||
public Task Pub<TData>(in TypedKey<TData> key, TData data);
|
public Task Pub<TData>(in TypedKey<TData> key, TData data);
|
||||||
public Task Sub<TData>(in TypedKey<TData> key, Func<TData?, ValueTask> action);
|
public Task Sub<TData>(in TypedKey<TData> key, Func<TData?, ValueTask> action);
|
||||||
}
|
}
|
@@ -4,4 +4,4 @@ public interface ISeria
|
|||||||
{
|
{
|
||||||
byte[] Serialize<T>(T data);
|
byte[] Serialize<T>(T data);
|
||||||
T? Deserialize<T>(byte[]? data);
|
T? Deserialize<T>(byte[]? data);
|
||||||
}
|
}
|
@@ -1,5 +1,5 @@
|
|||||||
using System.Text.Json;
|
|
||||||
using NadekoBot.Common.JsonConverters;
|
using NadekoBot.Common.JsonConverters;
|
||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
namespace NadekoBot.Common;
|
namespace NadekoBot.Common;
|
||||||
|
|
||||||
@@ -7,7 +7,7 @@ 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)
|
public byte[] Serialize<T>(T data)
|
||||||
@@ -20,4 +20,4 @@ public class JsonSeria : ISeria
|
|||||||
|
|
||||||
return JsonSerializer.Deserialize<T>(data, _serializerOptions);
|
return JsonSerializer.Deserialize<T>(data, _serializerOptions);
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -4,9 +4,9 @@ namespace NadekoBot.Common;
|
|||||||
|
|
||||||
public sealed class RedisPubSub : IPubSub
|
public sealed class RedisPubSub : IPubSub
|
||||||
{
|
{
|
||||||
|
private readonly IBotCredentials _creds;
|
||||||
private readonly ConnectionMultiplexer _multi;
|
private readonly ConnectionMultiplexer _multi;
|
||||||
private readonly ISeria _serializer;
|
private readonly ISeria _serializer;
|
||||||
private readonly IBotCredentials _creds;
|
|
||||||
|
|
||||||
public RedisPubSub(ConnectionMultiplexer multi, ISeria serializer, IBotCredentials creds)
|
public RedisPubSub(ConnectionMultiplexer multi, ISeria serializer, IBotCredentials creds)
|
||||||
{
|
{
|
||||||
@@ -19,7 +19,7 @@ public sealed class RedisPubSub : IPubSub
|
|||||||
{
|
{
|
||||||
var serialized = _serializer.Serialize(data);
|
var serialized = _serializer.Serialize(data);
|
||||||
return _multi.GetSubscriber()
|
return _multi.GetSubscriber()
|
||||||
.PublishAsync($"{_creds.RedisKey()}:{key.Key}", serialized, CommandFlags.FireAndForget);
|
.PublishAsync($"{_creds.RedisKey()}:{key.Key}", serialized, CommandFlags.FireAndForget);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task Sub<TData>(in TypedKey<TData> key, Func<TData?, ValueTask> action)
|
public Task Sub<TData>(in TypedKey<TData> key, Func<TData?, ValueTask> action)
|
||||||
@@ -41,4 +41,4 @@ public sealed class RedisPubSub : IPubSub
|
|||||||
|
|
||||||
return _multi.GetSubscriber().SubscribeAsync($"{_creds.RedisKey()}:{eventName}", OnSubscribeHandler);
|
return _multi.GetSubscriber().SubscribeAsync($"{_creds.RedisKey()}:{eventName}", OnSubscribeHandler);
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -27,4 +27,4 @@ public readonly struct TypedKey<TData>
|
|||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
=> Key;
|
=> Key;
|
||||||
}
|
}
|
@@ -1,19 +1,18 @@
|
|||||||
using System.Text.RegularExpressions;
|
|
||||||
using NadekoBot.Common.Yml;
|
|
||||||
using NadekoBot.Common.Configs;
|
using NadekoBot.Common.Configs;
|
||||||
|
using NadekoBot.Common.Yml;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
using YamlDotNet.Serialization;
|
using YamlDotNet.Serialization;
|
||||||
|
|
||||||
namespace NadekoBot.Common;
|
namespace NadekoBot.Common;
|
||||||
|
|
||||||
public class YamlSeria : IConfigSeria
|
public class YamlSeria : IConfigSeria
|
||||||
{
|
{
|
||||||
private readonly ISerializer _serializer;
|
|
||||||
private readonly IDeserializer _deserializer;
|
|
||||||
|
|
||||||
private static readonly Regex _codePointRegex =
|
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}))",
|
new(@"(\\U(?<code>[a-zA-Z0-9]{8})|\\u(?<code>[a-zA-Z0-9]{4})|\\x(?<code>[a-zA-Z0-9]{2}))",
|
||||||
RegexOptions.Compiled
|
RegexOptions.Compiled);
|
||||||
);
|
|
||||||
|
private readonly IDeserializer _deserializer;
|
||||||
|
private readonly ISerializer _serializer;
|
||||||
|
|
||||||
public YamlSeria()
|
public YamlSeria()
|
||||||
{
|
{
|
||||||
@@ -22,7 +21,7 @@ public class YamlSeria : IConfigSeria
|
|||||||
}
|
}
|
||||||
|
|
||||||
public string Serialize<T>(T obj)
|
public string Serialize<T>(T obj)
|
||||||
where T: notnull
|
where T : notnull
|
||||||
{
|
{
|
||||||
var escapedOutput = _serializer.Serialize(obj);
|
var escapedOutput = _serializer.Serialize(obj);
|
||||||
var output = _codePointRegex.Replace(escapedOutput,
|
var output = _codePointRegex.Replace(escapedOutput,
|
||||||
@@ -31,11 +30,10 @@ public class YamlSeria : IConfigSeria
|
|||||||
var str = me.Groups["code"].Value;
|
var str = me.Groups["code"].Value;
|
||||||
var newString = YamlHelper.UnescapeUnicodeCodePoint(str);
|
var newString = YamlHelper.UnescapeUnicodeCodePoint(str);
|
||||||
return newString;
|
return newString;
|
||||||
}
|
});
|
||||||
);
|
|
||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
public T Deserialize<T>(string data)
|
public T Deserialize<T>(string data)
|
||||||
=> _deserializer.Deserialize<T>(data);
|
=> _deserializer.Deserialize<T>(data);
|
||||||
}
|
}
|
@@ -7,11 +7,11 @@ namespace NadekoBot.Common;
|
|||||||
public class ReplacementBuilder
|
public class ReplacementBuilder
|
||||||
{
|
{
|
||||||
private static readonly Regex _rngRegex = new("%rng(?:(?<from>(?:-)?\\d+)-(?<to>(?:-)?\\d+))?%",
|
private static readonly Regex _rngRegex = new("%rng(?:(?<from>(?:-)?\\d+)-(?<to>(?:-)?\\d+))?%",
|
||||||
RegexOptions.Compiled
|
RegexOptions.Compiled);
|
||||||
);
|
|
||||||
|
private readonly ConcurrentDictionary<Regex, Func<Match, string>> _regex = new();
|
||||||
|
|
||||||
private readonly ConcurrentDictionary<string, Func<string>> _reps = new();
|
private readonly ConcurrentDictionary<string, Func<string>> _reps = new();
|
||||||
private readonly ConcurrentDictionary<Regex, Func<Match, string>> _regex = new();
|
|
||||||
|
|
||||||
public ReplacementBuilder()
|
public ReplacementBuilder()
|
||||||
=> WithRngRegex();
|
=> WithRngRegex();
|
||||||
@@ -21,14 +21,10 @@ public class ReplacementBuilder
|
|||||||
IMessageChannel ch,
|
IMessageChannel ch,
|
||||||
SocketGuild g,
|
SocketGuild g,
|
||||||
DiscordSocketClient client)
|
DiscordSocketClient client)
|
||||||
=> this.WithUser(usr).WithChannel(ch).WithServer(client, g).WithClient(client);
|
=> WithUser(usr).WithChannel(ch).WithServer(client, g).WithClient(client);
|
||||||
|
|
||||||
public ReplacementBuilder WithDefault(ICommandContext ctx)
|
public ReplacementBuilder WithDefault(ICommandContext ctx)
|
||||||
=> WithDefault(ctx.User,
|
=> WithDefault(ctx.User, ctx.Channel, ctx.Guild as SocketGuild, (DiscordSocketClient)ctx.Client);
|
||||||
ctx.Channel,
|
|
||||||
ctx.Guild as SocketGuild,
|
|
||||||
(DiscordSocketClient)ctx.Client
|
|
||||||
);
|
|
||||||
|
|
||||||
public ReplacementBuilder WithMention(DiscordSocketClient client)
|
public ReplacementBuilder WithMention(DiscordSocketClient client)
|
||||||
{
|
{
|
||||||
@@ -45,8 +41,7 @@ public class ReplacementBuilder
|
|||||||
_reps.TryAdd("%bot.name%", () => client.CurrentUser.Username);
|
_reps.TryAdd("%bot.name%", () => client.CurrentUser.Username);
|
||||||
_reps.TryAdd("%bot.fullname%", () => client.CurrentUser.ToString());
|
_reps.TryAdd("%bot.fullname%", () => client.CurrentUser.ToString());
|
||||||
_reps.TryAdd("%bot.time%",
|
_reps.TryAdd("%bot.time%",
|
||||||
() => DateTime.Now.ToString("HH:mm " + TimeZoneInfo.Local.StandardName.GetInitials())
|
() => DateTime.Now.ToString("HH:mm " + TimeZoneInfo.Local.StandardName.GetInitials()));
|
||||||
);
|
|
||||||
_reps.TryAdd("%bot.discrim%", () => client.CurrentUser.Discriminator);
|
_reps.TryAdd("%bot.discrim%", () => client.CurrentUser.Discriminator);
|
||||||
_reps.TryAdd("%bot.id%", () => client.CurrentUser.Id.ToString());
|
_reps.TryAdd("%bot.id%", () => client.CurrentUser.Id.ToString());
|
||||||
_reps.TryAdd("%bot.avatar%", () => client.CurrentUser.RealAvatarUrl()?.ToString());
|
_reps.TryAdd("%bot.avatar%", () => client.CurrentUser.RealAvatarUrl()?.ToString());
|
||||||
@@ -68,15 +63,12 @@ public class ReplacementBuilder
|
|||||||
{
|
{
|
||||||
var to = TimeZoneInfo.Local;
|
var to = TimeZoneInfo.Local;
|
||||||
if (g != null)
|
if (g != null)
|
||||||
{
|
|
||||||
if (GuildTimezoneService.AllServices.TryGetValue(client.CurrentUser.Id, out var tz))
|
if (GuildTimezoneService.AllServices.TryGetValue(client.CurrentUser.Id, out var tz))
|
||||||
to = tz.GetTimeZoneOrDefault(g.Id) ?? TimeZoneInfo.Local;
|
to = tz.GetTimeZoneOrDefault(g.Id) ?? TimeZoneInfo.Local;
|
||||||
}
|
|
||||||
|
|
||||||
return TimeZoneInfo.ConvertTime(DateTime.UtcNow, TimeZoneInfo.Utc, to).ToString("HH:mm ") +
|
return TimeZoneInfo.ConvertTime(DateTime.UtcNow, TimeZoneInfo.Utc, to).ToString("HH:mm ")
|
||||||
to.StandardName.GetInitials();
|
+ to.StandardName.GetInitials();
|
||||||
}
|
});
|
||||||
);
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,17 +99,14 @@ public class ReplacementBuilder
|
|||||||
_reps.TryAdd("%user.avatar%", () => string.Join(" ", users.Select(user => user.RealAvatarUrl()?.ToString())));
|
_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.id%", () => string.Join(" ", users.Select(user => user.Id.ToString())));
|
||||||
_reps.TryAdd("%user.created_time%",
|
_reps.TryAdd("%user.created_time%",
|
||||||
() => string.Join(" ", users.Select(user => user.CreatedAt.ToString("HH:mm")))
|
() => string.Join(" ", users.Select(user => user.CreatedAt.ToString("HH:mm"))));
|
||||||
);
|
|
||||||
_reps.TryAdd("%user.created_date%",
|
_reps.TryAdd("%user.created_date%",
|
||||||
() => string.Join(" ", users.Select(user => user.CreatedAt.ToString("dd.MM.yyyy")))
|
() => string.Join(" ", users.Select(user => user.CreatedAt.ToString("dd.MM.yyyy"))));
|
||||||
);
|
|
||||||
_reps.TryAdd("%user.joined_time%",
|
_reps.TryAdd("%user.joined_time%",
|
||||||
() => string.Join(" ", users.Select(user => (user as IGuildUser)?.JoinedAt?.ToString("HH:mm") ?? "-"))
|
() => string.Join(" ", users.Select(user => (user as IGuildUser)?.JoinedAt?.ToString("HH:mm") ?? "-")));
|
||||||
);
|
|
||||||
_reps.TryAdd("%user.joined_date%",
|
_reps.TryAdd("%user.joined_date%",
|
||||||
() => string.Join(" ", users.Select(user => (user as IGuildUser)?.JoinedAt?.ToString("dd.MM.yyyy") ?? "-"))
|
() => string.Join(" ",
|
||||||
);
|
users.Select(user => (user as IGuildUser)?.JoinedAt?.ToString("dd.MM.yyyy") ?? "-")));
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -140,16 +129,14 @@ public class ReplacementBuilder
|
|||||||
if (!int.TryParse(match.Groups["to"].ToString(), out var to))
|
if (!int.TryParse(match.Groups["to"].ToString(), out var to))
|
||||||
to = 0;
|
to = 0;
|
||||||
|
|
||||||
if (from == 0 &&
|
if (from == 0 && to == 0)
|
||||||
to == 0)
|
|
||||||
return rng.Next(0, 11).ToString();
|
return rng.Next(0, 11).ToString();
|
||||||
|
|
||||||
if (from >= to)
|
if (from >= to)
|
||||||
return string.Empty;
|
return string.Empty;
|
||||||
|
|
||||||
return rng.Next(from, to + 1).ToString();
|
return rng.Next(from, to + 1).ToString();
|
||||||
}
|
});
|
||||||
);
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -165,13 +152,9 @@ public class ReplacementBuilder
|
|||||||
public ReplacementBuilder WithProviders(IEnumerable<IPlaceholderProvider> phProviders)
|
public ReplacementBuilder WithProviders(IEnumerable<IPlaceholderProvider> phProviders)
|
||||||
{
|
{
|
||||||
foreach (var provider in phProviders)
|
foreach (var provider in phProviders)
|
||||||
{
|
foreach (var ovr in provider.GetPlaceholders())
|
||||||
foreach (var ovr in provider.GetPlaceholders())
|
_reps.TryAdd(ovr.Name, ovr.Func);
|
||||||
{
|
|
||||||
_reps.TryAdd(ovr.Name, ovr.Func);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,12 +1,12 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
namespace NadekoBot.Common;
|
namespace NadekoBot.Common;
|
||||||
|
|
||||||
public class Replacer
|
public class Replacer
|
||||||
{
|
{
|
||||||
private readonly IEnumerable<(string Key, Func<string> Text)> _replacements;
|
|
||||||
private readonly IEnumerable<(Regex Regex, Func<Match, string> Replacement)> _regex;
|
private readonly IEnumerable<(Regex Regex, Func<Match, string> Replacement)> _regex;
|
||||||
|
private readonly IEnumerable<(string Key, Func<string> Text)> _replacements;
|
||||||
|
|
||||||
public Replacer(IEnumerable<(string, Func<string>)> replacements, IEnumerable<(Regex, Func<Match, string>)> regex)
|
public Replacer(IEnumerable<(string, Func<string>)> replacements, IEnumerable<(Regex, Func<Match, string>)> regex)
|
||||||
{
|
{
|
||||||
@@ -20,15 +20,10 @@ public class Replacer
|
|||||||
return input;
|
return input;
|
||||||
|
|
||||||
foreach (var (key, text) in _replacements)
|
foreach (var (key, text) in _replacements)
|
||||||
{
|
|
||||||
if (input.Contains(key))
|
if (input.Contains(key))
|
||||||
input = input.Replace(key, text(), StringComparison.InvariantCulture);
|
input = input.Replace(key, text(), StringComparison.InvariantCulture);
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var item in _regex)
|
foreach (var item in _regex) input = item.Regex.Replace(input, m => item.Replacement(m));
|
||||||
{
|
|
||||||
input = item.Regex.Replace(input, m => item.Replacement(m));
|
|
||||||
}
|
|
||||||
|
|
||||||
return input;
|
return input;
|
||||||
}
|
}
|
||||||
@@ -56,12 +51,10 @@ public class Replacer
|
|||||||
Url = Replace(embedData.Url)
|
Url = Replace(embedData.Url)
|
||||||
};
|
};
|
||||||
if (embedData.Author != null)
|
if (embedData.Author != null)
|
||||||
{
|
|
||||||
newEmbedData.Author = new()
|
newEmbedData.Author = new()
|
||||||
{
|
{
|
||||||
Name = Replace(embedData.Author.Name), IconUrl = Replace(embedData.Author.IconUrl)
|
Name = Replace(embedData.Author.Name), IconUrl = Replace(embedData.Author.IconUrl)
|
||||||
};
|
};
|
||||||
}
|
|
||||||
|
|
||||||
if (embedData.Fields != null)
|
if (embedData.Fields != null)
|
||||||
{
|
{
|
||||||
@@ -79,15 +72,13 @@ public class Replacer
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (embedData.Footer != null)
|
if (embedData.Footer != null)
|
||||||
{
|
|
||||||
newEmbedData.Footer = new()
|
newEmbedData.Footer = new()
|
||||||
{
|
{
|
||||||
Text = Replace(embedData.Footer.Text), IconUrl = Replace(embedData.Footer.IconUrl)
|
Text = Replace(embedData.Footer.Text), IconUrl = Replace(embedData.Footer.IconUrl)
|
||||||
};
|
};
|
||||||
}
|
|
||||||
|
|
||||||
newEmbedData.Color = embedData.Color;
|
newEmbedData.Color = embedData.Color;
|
||||||
|
|
||||||
return newEmbedData;
|
return newEmbedData;
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,4 +1,4 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Serialization;
|
using Newtonsoft.Json.Serialization;
|
||||||
|
|
||||||
@@ -12,4 +12,4 @@ public class RequireObjectPropertiesContractResolver : DefaultContractResolver
|
|||||||
contract.ItemRequired = Required.DisallowNull;
|
contract.ItemRequired = Required.DisallowNull;
|
||||||
return contract;
|
return contract;
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,4 +1,4 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
namespace NadekoBot.Common;
|
namespace NadekoBot.Common;
|
||||||
|
|
||||||
public struct ShmartNumber : IEquatable<ShmartNumber>
|
public struct ShmartNumber : IEquatable<ShmartNumber>
|
||||||
@@ -38,4 +38,4 @@ public struct ShmartNumber : IEquatable<ShmartNumber>
|
|||||||
|
|
||||||
public static bool operator !=(ShmartNumber left, ShmartNumber right)
|
public static bool operator !=(ShmartNumber left, ShmartNumber right)
|
||||||
=> !(left == right);
|
=> !(left == right);
|
||||||
}
|
}
|
@@ -1,4 +1,4 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
namespace NadekoBot;
|
namespace NadekoBot;
|
||||||
|
|
||||||
public sealed record SmartEmbedText : SmartText
|
public sealed record SmartEmbedText : SmartText
|
||||||
@@ -17,12 +17,14 @@ public sealed record SmartEmbedText : SmartText
|
|||||||
public uint Color { get; set; } = 7458112;
|
public uint Color { get; set; } = 7458112;
|
||||||
|
|
||||||
public bool IsValid
|
public bool IsValid
|
||||||
=> !string.IsNullOrWhiteSpace(Title) || !string.IsNullOrWhiteSpace(Description) ||
|
=> !string.IsNullOrWhiteSpace(Title)
|
||||||
!string.IsNullOrWhiteSpace(Url) || !string.IsNullOrWhiteSpace(Thumbnail) ||
|
|| !string.IsNullOrWhiteSpace(Description)
|
||||||
!string.IsNullOrWhiteSpace(Image) ||
|
|| !string.IsNullOrWhiteSpace(Url)
|
||||||
(Footer != null &&
|
|| !string.IsNullOrWhiteSpace(Thumbnail)
|
||||||
(!string.IsNullOrWhiteSpace(Footer.Text) || !string.IsNullOrWhiteSpace(Footer.IconUrl))) ||
|
|| !string.IsNullOrWhiteSpace(Image)
|
||||||
Fields is { Length: > 0 };
|
|| (Footer != null
|
||||||
|
&& (!string.IsNullOrWhiteSpace(Footer.Text) || !string.IsNullOrWhiteSpace(Footer.IconUrl)))
|
||||||
|
|| Fields is { Length: > 0 };
|
||||||
|
|
||||||
public static SmartEmbedText FromEmbed(IEmbed eb, string plainText = null)
|
public static SmartEmbedText FromEmbed(IEmbed eb, string plainText = null)
|
||||||
{
|
{
|
||||||
@@ -40,9 +42,11 @@ public sealed record SmartEmbedText : SmartText
|
|||||||
|
|
||||||
if (eb.Fields.Length > 0)
|
if (eb.Fields.Length > 0)
|
||||||
set.Fields = eb.Fields.Select(field
|
set.Fields = eb.Fields.Select(field
|
||||||
=> new SmartTextEmbedField() { Inline = field.Inline, Name = field.Name, Value = field.Value, }
|
=> new SmartTextEmbedField
|
||||||
)
|
{
|
||||||
.ToArray();
|
Inline = field.Inline, Name = field.Name, Value = field.Value
|
||||||
|
})
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
set.Color = eb.Color?.RawValue ?? 0;
|
set.Color = eb.Color?.RawValue ?? 0;
|
||||||
return set;
|
return set;
|
||||||
@@ -58,31 +62,24 @@ public sealed record SmartEmbedText : SmartText
|
|||||||
if (!string.IsNullOrWhiteSpace(Description))
|
if (!string.IsNullOrWhiteSpace(Description))
|
||||||
embed.WithDescription(Description);
|
embed.WithDescription(Description);
|
||||||
|
|
||||||
if (Url != null &&
|
if (Url != null && Uri.IsWellFormedUriString(Url, UriKind.Absolute))
|
||||||
Uri.IsWellFormedUriString(Url, UriKind.Absolute))
|
|
||||||
embed.WithUrl(Url);
|
embed.WithUrl(Url);
|
||||||
|
|
||||||
if (Footer != null)
|
if (Footer != null)
|
||||||
{
|
|
||||||
embed.WithFooter(efb =>
|
embed.WithFooter(efb =>
|
||||||
{
|
{
|
||||||
efb.WithText(Footer.Text);
|
efb.WithText(Footer.Text);
|
||||||
if (Uri.IsWellFormedUriString(Footer.IconUrl, UriKind.Absolute))
|
if (Uri.IsWellFormedUriString(Footer.IconUrl, UriKind.Absolute))
|
||||||
efb.WithIconUrl(Footer.IconUrl);
|
efb.WithIconUrl(Footer.IconUrl);
|
||||||
}
|
});
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Thumbnail != null &&
|
if (Thumbnail != null && Uri.IsWellFormedUriString(Thumbnail, UriKind.Absolute))
|
||||||
Uri.IsWellFormedUriString(Thumbnail, UriKind.Absolute))
|
|
||||||
embed.WithThumbnailUrl(Thumbnail);
|
embed.WithThumbnailUrl(Thumbnail);
|
||||||
|
|
||||||
if (Image != null &&
|
if (Image != null && Uri.IsWellFormedUriString(Image, UriKind.Absolute))
|
||||||
Uri.IsWellFormedUriString(Image, UriKind.Absolute))
|
|
||||||
embed.WithImageUrl(Image);
|
embed.WithImageUrl(Image);
|
||||||
|
|
||||||
if (Author != null &&
|
if (Author != null && !string.IsNullOrWhiteSpace(Author.Name))
|
||||||
!string.IsNullOrWhiteSpace(Author.Name))
|
|
||||||
{
|
{
|
||||||
if (!Uri.IsWellFormedUriString(Author.IconUrl, UriKind.Absolute))
|
if (!Uri.IsWellFormedUriString(Author.IconUrl, UriKind.Absolute))
|
||||||
Author.IconUrl = null;
|
Author.IconUrl = null;
|
||||||
@@ -93,14 +90,9 @@ public sealed record SmartEmbedText : SmartText
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (Fields != null)
|
if (Fields != null)
|
||||||
{
|
|
||||||
foreach (var f in Fields)
|
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);
|
embed.AddField(f.Name, f.Value, f.Inline);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return embed;
|
return embed;
|
||||||
}
|
}
|
||||||
@@ -108,12 +100,10 @@ public sealed record SmartEmbedText : SmartText
|
|||||||
public void NormalizeFields()
|
public void NormalizeFields()
|
||||||
{
|
{
|
||||||
if (Fields is { Length: > 0 })
|
if (Fields is { Length: > 0 })
|
||||||
{
|
|
||||||
foreach (var f in Fields)
|
foreach (var f in Fields)
|
||||||
{
|
{
|
||||||
f.Name = f.Name.TrimTo(256);
|
f.Name = f.Name.TrimTo(256);
|
||||||
f.Value = f.Value.TrimTo(1024);
|
f.Value = f.Value.TrimTo(1024);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,4 +1,4 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
namespace NadekoBot;
|
namespace NadekoBot;
|
||||||
|
|
||||||
public sealed record SmartPlainText : SmartText
|
public sealed record SmartPlainText : SmartText
|
||||||
@@ -16,4 +16,4 @@ public sealed record SmartPlainText : SmartText
|
|||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
=> Text;
|
=> Text;
|
||||||
}
|
}
|
@@ -1,4 +1,4 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace NadekoBot;
|
namespace NadekoBot;
|
||||||
@@ -29,11 +29,8 @@ public abstract record SmartText
|
|||||||
|
|
||||||
public static SmartText CreateFrom(string input)
|
public static SmartText CreateFrom(string input)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(input) ||
|
if (string.IsNullOrWhiteSpace(input) || !input.TrimStart().StartsWith("{"))
|
||||||
!input.TrimStart().StartsWith("{"))
|
|
||||||
{
|
|
||||||
return new SmartPlainText(input);
|
return new SmartPlainText(input);
|
||||||
}
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -41,13 +38,10 @@ public abstract record SmartText
|
|||||||
|
|
||||||
if (smartEmbedText is null)
|
if (smartEmbedText is null)
|
||||||
throw new();
|
throw new();
|
||||||
|
|
||||||
smartEmbedText.NormalizeFields();
|
smartEmbedText.NormalizeFields();
|
||||||
|
|
||||||
if (!smartEmbedText.IsValid)
|
if (!smartEmbedText.IsValid) return new SmartPlainText(input);
|
||||||
{
|
|
||||||
return new SmartPlainText(input);
|
|
||||||
}
|
|
||||||
|
|
||||||
return smartEmbedText;
|
return smartEmbedText;
|
||||||
}
|
}
|
||||||
@@ -56,4 +50,4 @@ public abstract record SmartText
|
|||||||
return new SmartPlainText(input);
|
return new SmartPlainText(input);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,4 +1,4 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace NadekoBot;
|
namespace NadekoBot;
|
||||||
@@ -6,7 +6,9 @@ namespace NadekoBot;
|
|||||||
public class SmartTextEmbedAuthor
|
public class SmartTextEmbedAuthor
|
||||||
{
|
{
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
|
|
||||||
[JsonProperty("icon_url")]
|
[JsonProperty("icon_url")]
|
||||||
public string IconUrl { get; set; }
|
public string IconUrl { get; set; }
|
||||||
|
|
||||||
public string Url { get; set; }
|
public string Url { get; set; }
|
||||||
}
|
}
|
@@ -1,4 +1,4 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
namespace NadekoBot;
|
namespace NadekoBot;
|
||||||
|
|
||||||
public class SmartTextEmbedField
|
public class SmartTextEmbedField
|
||||||
@@ -6,4 +6,4 @@ public class SmartTextEmbedField
|
|||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
public string Value { get; set; }
|
public string Value { get; set; }
|
||||||
public bool Inline { get; set; }
|
public bool Inline { get; set; }
|
||||||
}
|
}
|
@@ -8,6 +8,7 @@ namespace NadekoBot;
|
|||||||
public class SmartTextEmbedFooter
|
public class SmartTextEmbedFooter
|
||||||
{
|
{
|
||||||
public string Text { get; set; }
|
public string Text { get; set; }
|
||||||
|
|
||||||
[JsonProperty("icon_url")]
|
[JsonProperty("icon_url")]
|
||||||
public string IconUrl { get; set; }
|
public string IconUrl { get; set; }
|
||||||
}
|
}
|
@@ -1,13 +1,17 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
namespace NadekoBot.Common;
|
namespace NadekoBot.Common;
|
||||||
|
|
||||||
public sealed class ReactionEventWrapper : IDisposable
|
public sealed class ReactionEventWrapper : IDisposable
|
||||||
{
|
{
|
||||||
public IUserMessage Message { get; }
|
|
||||||
public event Action<SocketReaction> OnReactionAdded = delegate { };
|
public event Action<SocketReaction> OnReactionAdded = delegate { };
|
||||||
public event Action<SocketReaction> OnReactionRemoved = delegate { };
|
public event Action<SocketReaction> OnReactionRemoved = delegate { };
|
||||||
public event Action OnReactionsCleared = delegate { };
|
public event Action OnReactionsCleared = delegate { };
|
||||||
|
|
||||||
|
public IUserMessage Message { get; }
|
||||||
|
private readonly DiscordSocketClient _client;
|
||||||
|
|
||||||
|
private bool disposing;
|
||||||
|
|
||||||
public ReactionEventWrapper(DiscordSocketClient client, IUserMessage msg)
|
public ReactionEventWrapper(DiscordSocketClient client, IUserMessage msg)
|
||||||
{
|
{
|
||||||
Message = msg ?? throw new ArgumentNullException(nameof(msg));
|
Message = msg ?? throw new ArgumentNullException(nameof(msg));
|
||||||
@@ -18,18 +22,25 @@ public sealed class ReactionEventWrapper : IDisposable
|
|||||||
_client.ReactionsCleared += Discord_ReactionsCleared;
|
_client.ReactionsCleared += Discord_ReactionsCleared;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (disposing)
|
||||||
|
return;
|
||||||
|
disposing = true;
|
||||||
|
UnsubAll();
|
||||||
|
}
|
||||||
|
|
||||||
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(() =>
|
Task.Run(() =>
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
try
|
if (msg.Id == Message.Id)
|
||||||
{
|
OnReactionsCleared?.Invoke();
|
||||||
if (msg.Id == Message.Id)
|
|
||||||
OnReactionsCleared?.Invoke();
|
|
||||||
}
|
|
||||||
catch { }
|
|
||||||
}
|
}
|
||||||
);
|
catch { }
|
||||||
|
});
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
@@ -40,15 +51,14 @@ public sealed class ReactionEventWrapper : IDisposable
|
|||||||
SocketReaction reaction)
|
SocketReaction reaction)
|
||||||
{
|
{
|
||||||
Task.Run(() =>
|
Task.Run(() =>
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
try
|
if (msg.Id == Message.Id)
|
||||||
{
|
OnReactionRemoved?.Invoke(reaction);
|
||||||
if (msg.Id == Message.Id)
|
|
||||||
OnReactionRemoved?.Invoke(reaction);
|
|
||||||
}
|
|
||||||
catch { }
|
|
||||||
}
|
}
|
||||||
);
|
catch { }
|
||||||
|
});
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
@@ -59,17 +69,16 @@ public sealed class ReactionEventWrapper : IDisposable
|
|||||||
SocketReaction reaction)
|
SocketReaction reaction)
|
||||||
{
|
{
|
||||||
Task.Run(() =>
|
Task.Run(() =>
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
try
|
if (msg.Id == Message.Id)
|
||||||
{
|
OnReactionAdded?.Invoke(reaction);
|
||||||
if (msg.Id == Message.Id)
|
|
||||||
OnReactionAdded?.Invoke(reaction);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
);
|
catch
|
||||||
|
{
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
@@ -83,15 +92,4 @@ public sealed class ReactionEventWrapper : IDisposable
|
|||||||
OnReactionRemoved = null;
|
OnReactionRemoved = null;
|
||||||
OnReactionsCleared = null;
|
OnReactionsCleared = null;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
private bool disposing = false;
|
|
||||||
private readonly DiscordSocketClient _client;
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
if (disposing)
|
|
||||||
return;
|
|
||||||
disposing = true;
|
|
||||||
UnsubAll();
|
|
||||||
}
|
|
||||||
}
|
|
@@ -5,8 +5,8 @@ namespace NadekoBot.Common.TypeReaders;
|
|||||||
|
|
||||||
public sealed class CommandTypeReader : NadekoTypeReader<CommandInfo>
|
public sealed class CommandTypeReader : NadekoTypeReader<CommandInfo>
|
||||||
{
|
{
|
||||||
private readonly CommandHandler _handler;
|
|
||||||
private readonly CommandService _cmds;
|
private readonly CommandService _cmds;
|
||||||
|
private readonly CommandHandler _handler;
|
||||||
|
|
||||||
public CommandTypeReader(CommandHandler handler, CommandService cmds)
|
public CommandTypeReader(CommandHandler handler, CommandService cmds)
|
||||||
{
|
{
|
||||||
@@ -34,8 +34,8 @@ public sealed class CommandTypeReader : NadekoTypeReader<CommandInfo>
|
|||||||
public sealed class CommandOrCrTypeReader : NadekoTypeReader<CommandOrCrInfo>
|
public sealed class CommandOrCrTypeReader : NadekoTypeReader<CommandOrCrInfo>
|
||||||
{
|
{
|
||||||
private readonly CommandService _cmds;
|
private readonly CommandService _cmds;
|
||||||
private readonly CustomReactionsService _crs;
|
|
||||||
private readonly CommandHandler _commandHandler;
|
private readonly CommandHandler _commandHandler;
|
||||||
|
private readonly CustomReactionsService _crs;
|
||||||
|
|
||||||
public CommandOrCrTypeReader(CommandService cmds, CustomReactionsService crs, CommandHandler commandHandler)
|
public CommandOrCrTypeReader(CommandService cmds, CustomReactionsService crs, CommandHandler commandHandler)
|
||||||
{
|
{
|
||||||
@@ -49,18 +49,12 @@ public sealed class CommandOrCrTypeReader : NadekoTypeReader<CommandOrCrInfo>
|
|||||||
input = input.ToUpperInvariant();
|
input = input.ToUpperInvariant();
|
||||||
|
|
||||||
if (_crs.ReactionExists(context.Guild?.Id, input))
|
if (_crs.ReactionExists(context.Guild?.Id, input))
|
||||||
{
|
|
||||||
return TypeReaderResult.FromSuccess(new CommandOrCrInfo(input, CommandOrCrInfo.Type.Custom));
|
return TypeReaderResult.FromSuccess(new CommandOrCrInfo(input, CommandOrCrInfo.Type.Custom));
|
||||||
}
|
|
||||||
|
|
||||||
var cmd = await new CommandTypeReader(_commandHandler, _cmds).ReadAsync(context, input);
|
var cmd = await new CommandTypeReader(_commandHandler, _cmds).ReadAsync(context, input);
|
||||||
if (cmd.IsSuccess)
|
if (cmd.IsSuccess)
|
||||||
{
|
|
||||||
return TypeReaderResult.FromSuccess(new CommandOrCrInfo(((CommandInfo)cmd.Values.First().Value).Name,
|
return TypeReaderResult.FromSuccess(new CommandOrCrInfo(((CommandInfo)cmd.Values.First().Value).Name,
|
||||||
CommandOrCrInfo.Type.Normal
|
CommandOrCrInfo.Type.Normal));
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return TypeReaderResult.FromError(CommandError.ParseFailed, "No such command or cr found.");
|
return TypeReaderResult.FromError(CommandError.ParseFailed, "No such command or cr found.");
|
||||||
}
|
}
|
||||||
@@ -71,7 +65,7 @@ public class CommandOrCrInfo
|
|||||||
public enum Type
|
public enum Type
|
||||||
{
|
{
|
||||||
Normal,
|
Normal,
|
||||||
Custom,
|
Custom
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
@@ -82,7 +76,7 @@ public class CommandOrCrInfo
|
|||||||
|
|
||||||
public CommandOrCrInfo(string input, Type type)
|
public CommandOrCrInfo(string input, Type type)
|
||||||
{
|
{
|
||||||
this.Name = input;
|
Name = input;
|
||||||
this.CmdType = type;
|
CmdType = type;
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,4 +1,4 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
namespace NadekoBot.Common.TypeReaders;
|
namespace NadekoBot.Common.TypeReaders;
|
||||||
|
|
||||||
public sealed class EmoteTypeReader : NadekoTypeReader<Emote>
|
public sealed class EmoteTypeReader : NadekoTypeReader<Emote>
|
||||||
@@ -10,4 +10,4 @@ public sealed class EmoteTypeReader : NadekoTypeReader<Emote>
|
|||||||
|
|
||||||
return Task.FromResult(TypeReaderResult.FromSuccess(emote));
|
return Task.FromResult(TypeReaderResult.FromSuccess(emote));
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,4 +1,4 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
using NadekoBot.Modules.Administration.Services;
|
using NadekoBot.Modules.Administration.Services;
|
||||||
|
|
||||||
namespace NadekoBot.Common.TypeReaders;
|
namespace NadekoBot.Common.TypeReaders;
|
||||||
@@ -15,9 +15,7 @@ public sealed class GuildDateTimeTypeReader : NadekoTypeReader<GuildDateTime>
|
|||||||
var gdt = Parse(context.Guild.Id, input);
|
var gdt = Parse(context.Guild.Id, input);
|
||||||
if (gdt is null)
|
if (gdt is null)
|
||||||
return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed,
|
return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed,
|
||||||
"Input string is in an incorrect format."
|
"Input string is in an incorrect format."));
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
return Task.FromResult(TypeReaderResult.FromSuccess(gdt));
|
return Task.FromResult(TypeReaderResult.FromSuccess(gdt));
|
||||||
}
|
}
|
||||||
@@ -48,4 +46,4 @@ public class GuildDateTime
|
|||||||
InputTime = inputTime;
|
InputTime = inputTime;
|
||||||
InputTimeUtc = TimeZoneInfo.ConvertTime(inputTime, Timezone, TimeZoneInfo.Utc);
|
InputTimeUtc = TimeZoneInfo.ConvertTime(inputTime, Timezone, TimeZoneInfo.Utc);
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,4 +1,4 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
namespace NadekoBot.Common.TypeReaders;
|
namespace NadekoBot.Common.TypeReaders;
|
||||||
|
|
||||||
public sealed class GuildTypeReader : NadekoTypeReader<IGuild>
|
public sealed class GuildTypeReader : NadekoTypeReader<IGuild>
|
||||||
@@ -12,12 +12,14 @@ public sealed class GuildTypeReader : NadekoTypeReader<IGuild>
|
|||||||
{
|
{
|
||||||
input = input.Trim().ToUpperInvariant();
|
input = input.Trim().ToUpperInvariant();
|
||||||
var guilds = _client.Guilds;
|
var guilds = _client.Guilds;
|
||||||
var guild = guilds.FirstOrDefault(g => g.Id.ToString().Trim().ToUpperInvariant() == input) ?? //by id
|
var guild = guilds.FirstOrDefault(g => g.Id.ToString().Trim().ToUpperInvariant() == input)
|
||||||
|
?? //by id
|
||||||
guilds.FirstOrDefault(g => g.Name.Trim().ToUpperInvariant() == input); //by name
|
guilds.FirstOrDefault(g => g.Name.Trim().ToUpperInvariant() == input); //by name
|
||||||
|
|
||||||
if (guild != null)
|
if (guild != null)
|
||||||
return Task.FromResult(TypeReaderResult.FromSuccess(guild));
|
return Task.FromResult(TypeReaderResult.FromSuccess(guild));
|
||||||
|
|
||||||
return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, "No guild by that name or Id found"));
|
return Task.FromResult(
|
||||||
|
TypeReaderResult.FromError(CommandError.ParseFailed, "No guild by that name or Id found"));
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,4 +1,4 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
namespace NadekoBot.Common.TypeReaders;
|
namespace NadekoBot.Common.TypeReaders;
|
||||||
|
|
||||||
public sealed class KwumTypeReader : NadekoTypeReader<kwum>
|
public sealed class KwumTypeReader : NadekoTypeReader<kwum>
|
||||||
@@ -16,4 +16,4 @@ public sealed class SmartTextTypeReader : NadekoTypeReader<SmartText>
|
|||||||
{
|
{
|
||||||
public override Task<TypeReaderResult> ReadAsync(ICommandContext ctx, string input)
|
public override Task<TypeReaderResult> ReadAsync(ICommandContext ctx, string input)
|
||||||
=> Task.FromResult(TypeReaderResult.FromSuccess(SmartText.CreateFrom(input)));
|
=> Task.FromResult(TypeReaderResult.FromSuccess(SmartText.CreateFrom(input)));
|
||||||
}
|
}
|
@@ -1,25 +1,26 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
namespace NadekoBot.Common.TypeReaders.Models;
|
namespace NadekoBot.Common.TypeReaders.Models;
|
||||||
|
|
||||||
public class PermissionAction
|
public class PermissionAction
|
||||||
{
|
{
|
||||||
public static PermissionAction Enable => new(true);
|
public static PermissionAction Enable
|
||||||
public static PermissionAction Disable => new(false);
|
=> new(true);
|
||||||
|
|
||||||
|
public static PermissionAction Disable
|
||||||
|
=> new(false);
|
||||||
|
|
||||||
public bool Value { get; }
|
public bool Value { get; }
|
||||||
|
|
||||||
public PermissionAction(bool value)
|
public PermissionAction(bool value)
|
||||||
=> this.Value = value;
|
=> Value = value;
|
||||||
|
|
||||||
public override bool Equals(object obj)
|
public override bool Equals(object obj)
|
||||||
{
|
{
|
||||||
if (obj is null || GetType() != obj.GetType())
|
if (obj is null || GetType() != obj.GetType()) return false;
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.Value == ((PermissionAction)obj).Value;
|
return Value == ((PermissionAction)obj).Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override int GetHashCode() => Value.GetHashCode();
|
public override int GetHashCode()
|
||||||
}
|
=> Value.GetHashCode();
|
||||||
|
}
|
@@ -1,27 +1,24 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
namespace NadekoBot.Common.TypeReaders.Models;
|
namespace NadekoBot.Common.TypeReaders.Models;
|
||||||
|
|
||||||
public class StoopidTime
|
public class StoopidTime
|
||||||
{
|
{
|
||||||
public string Input { get; set; }
|
|
||||||
public TimeSpan Time { get; set; }
|
|
||||||
|
|
||||||
private static readonly Regex _regex = new(
|
private static readonly Regex _regex = new(
|
||||||
@"^(?:(?<months>\d)mo)?(?:(?<weeks>\d{1,2})w)?(?:(?<days>\d{1,2})d)?(?:(?<hours>\d{1,4})h)?(?:(?<minutes>\d{1,5})m)?(?:(?<seconds>\d{1,6})s)?$",
|
@"^(?:(?<months>\d)mo)?(?:(?<weeks>\d{1,2})w)?(?:(?<days>\d{1,2})d)?(?:(?<hours>\d{1,4})h)?(?:(?<minutes>\d{1,5})m)?(?:(?<seconds>\d{1,6})s)?$",
|
||||||
RegexOptions.Compiled | RegexOptions.Multiline);
|
RegexOptions.Compiled | RegexOptions.Multiline);
|
||||||
|
|
||||||
|
public string Input { get; set; }
|
||||||
|
public TimeSpan Time { get; set; }
|
||||||
|
|
||||||
private StoopidTime() { }
|
private StoopidTime() { }
|
||||||
|
|
||||||
public static StoopidTime FromInput(string input)
|
public static StoopidTime FromInput(string input)
|
||||||
{
|
{
|
||||||
var m = _regex.Match(input);
|
var m = _regex.Match(input);
|
||||||
|
|
||||||
if (m.Length == 0)
|
if (m.Length == 0) throw new ArgumentException("Invalid string input format.");
|
||||||
{
|
|
||||||
throw new ArgumentException("Invalid string input format.");
|
|
||||||
}
|
|
||||||
|
|
||||||
var output = string.Empty;
|
var output = string.Empty;
|
||||||
var namesAndValues = new Dictionary<string, int>();
|
var namesAndValues = new Dictionary<string, int>();
|
||||||
@@ -35,29 +32,18 @@ public class StoopidTime
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (value < 1)
|
if (value < 1) throw new ArgumentException($"Invalid {groupName} value.");
|
||||||
{
|
|
||||||
throw new ArgumentException($"Invalid {groupName} value.");
|
|
||||||
}
|
|
||||||
|
|
||||||
namesAndValues[groupName] = value;
|
namesAndValues[groupName] = value;
|
||||||
output += m.Groups[groupName].Value + " " + groupName + " ";
|
output += m.Groups[groupName].Value + " " + groupName + " ";
|
||||||
}
|
}
|
||||||
var ts = new TimeSpan((30 * namesAndValues["months"]) +
|
|
||||||
(7 * namesAndValues["weeks"]) +
|
var ts = new TimeSpan((30 * namesAndValues["months"]) + (7 * namesAndValues["weeks"]) + namesAndValues["days"],
|
||||||
namesAndValues["days"],
|
|
||||||
namesAndValues["hours"],
|
namesAndValues["hours"],
|
||||||
namesAndValues["minutes"],
|
namesAndValues["minutes"],
|
||||||
namesAndValues["seconds"]);
|
namesAndValues["seconds"]);
|
||||||
if (ts > TimeSpan.FromDays(90))
|
if (ts > TimeSpan.FromDays(90)) throw new ArgumentException("Time is too long.");
|
||||||
{
|
|
||||||
throw new ArgumentException("Time is too long.");
|
|
||||||
}
|
|
||||||
|
|
||||||
return new()
|
return new() { Input = input, Time = ts };
|
||||||
{
|
|
||||||
Input = input,
|
|
||||||
Time = ts,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,4 +1,4 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
namespace NadekoBot.Common.TypeReaders;
|
namespace NadekoBot.Common.TypeReaders;
|
||||||
|
|
||||||
public sealed class ModuleTypeReader : NadekoTypeReader<ModuleInfo>
|
public sealed class ModuleTypeReader : NadekoTypeReader<ModuleInfo>
|
||||||
@@ -12,8 +12,8 @@ public sealed class ModuleTypeReader : NadekoTypeReader<ModuleInfo>
|
|||||||
{
|
{
|
||||||
input = input.ToUpperInvariant();
|
input = input.ToUpperInvariant();
|
||||||
var module = _cmds.Modules.GroupBy(m => m.GetTopLevelModule())
|
var module = _cmds.Modules.GroupBy(m => m.GetTopLevelModule())
|
||||||
.FirstOrDefault(m => m.Key.Name.ToUpperInvariant() == input)
|
.FirstOrDefault(m => m.Key.Name.ToUpperInvariant() == input)
|
||||||
?.Key;
|
?.Key;
|
||||||
if (module is null)
|
if (module is null)
|
||||||
return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, "No such module found."));
|
return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, "No such module found."));
|
||||||
|
|
||||||
@@ -32,17 +32,16 @@ public sealed class ModuleOrCrTypeReader : NadekoTypeReader<ModuleOrCrInfo>
|
|||||||
{
|
{
|
||||||
input = input.ToUpperInvariant();
|
input = input.ToUpperInvariant();
|
||||||
var module = _cmds.Modules.GroupBy(m => m.GetTopLevelModule())
|
var module = _cmds.Modules.GroupBy(m => m.GetTopLevelModule())
|
||||||
.FirstOrDefault(m => m.Key.Name.ToUpperInvariant() == input)
|
.FirstOrDefault(m => m.Key.Name.ToUpperInvariant() == input)
|
||||||
?.Key;
|
?.Key;
|
||||||
if (module is null &&
|
if (module is null && input != "ACTUALCUSTOMREACTIONS")
|
||||||
input != "ACTUALCUSTOMREACTIONS")
|
|
||||||
return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, "No such module found."));
|
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 }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class ModuleOrCrInfo
|
public sealed class ModuleOrCrInfo
|
||||||
{
|
{
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
}
|
}
|
@@ -1,11 +1,11 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
namespace NadekoBot.Common.TypeReaders;
|
namespace NadekoBot.Common.TypeReaders;
|
||||||
|
|
||||||
[MeansImplicitUse(ImplicitUseTargetFlags.Default | ImplicitUseTargetFlags.WithInheritors )]
|
[MeansImplicitUse(ImplicitUseTargetFlags.Default | ImplicitUseTargetFlags.WithInheritors)]
|
||||||
public abstract class NadekoTypeReader<T> : TypeReader
|
public abstract class NadekoTypeReader<T> : TypeReader
|
||||||
{
|
{
|
||||||
public abstract Task<TypeReaderResult> ReadAsync(ICommandContext ctx, string input);
|
public abstract Task<TypeReaderResult> ReadAsync(ICommandContext ctx, string input);
|
||||||
|
|
||||||
public override Task<TypeReaderResult> ReadAsync(ICommandContext ctx, string input, IServiceProvider services)
|
public override Task<TypeReaderResult> ReadAsync(ICommandContext ctx, string input, IServiceProvider services)
|
||||||
=> ReadAsync(ctx, input);
|
=> ReadAsync(ctx, input);
|
||||||
}
|
}
|
@@ -1,10 +1,10 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
using NadekoBot.Common.TypeReaders.Models;
|
using NadekoBot.Common.TypeReaders.Models;
|
||||||
|
|
||||||
namespace NadekoBot.Common.TypeReaders;
|
namespace NadekoBot.Common.TypeReaders;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Used instead of bool for more flexible keywords for true/false only in the permission module
|
/// Used instead of bool for more flexible keywords for true/false only in the permission module
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class PermissionActionTypeReader : NadekoTypeReader<PermissionAction>
|
public sealed class PermissionActionTypeReader : NadekoTypeReader<PermissionAction>
|
||||||
{
|
{
|
||||||
@@ -32,7 +32,8 @@ public sealed class PermissionActionTypeReader : NadekoTypeReader<PermissionActi
|
|||||||
case "BAN":
|
case "BAN":
|
||||||
return Task.FromResult(TypeReaderResult.FromSuccess(PermissionAction.Disable));
|
return Task.FromResult(TypeReaderResult.FromSuccess(PermissionAction.Disable));
|
||||||
default:
|
default:
|
||||||
return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, "Did not receive a valid boolean value"));
|
return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed,
|
||||||
|
"Did not receive a valid boolean value"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,6 +1,7 @@
|
|||||||
|
using Color = SixLabors.ImageSharp.Color;
|
||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
namespace NadekoBot.Common.TypeReaders;
|
namespace NadekoBot.Common.TypeReaders;
|
||||||
using Color = SixLabors.ImageSharp.Color;
|
|
||||||
|
|
||||||
public sealed class Rgba32TypeReader : NadekoTypeReader<Color>
|
public sealed class Rgba32TypeReader : NadekoTypeReader<Color>
|
||||||
{
|
{
|
||||||
@@ -18,4 +19,4 @@ public sealed class Rgba32TypeReader : NadekoTypeReader<Color>
|
|||||||
return TypeReaderResult.FromError(CommandError.ParseFailed, "Parameter is not a valid color hex.");
|
return TypeReaderResult.FromError(CommandError.ParseFailed, "Parameter is not a valid color hex.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,12 +1,14 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
using NadekoBot.Db;
|
using NadekoBot.Db;
|
||||||
using NadekoBot.Modules.Gambling.Services;
|
using NadekoBot.Modules.Gambling.Services;
|
||||||
|
using NCalc;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
namespace NadekoBot.Common.TypeReaders;
|
namespace NadekoBot.Common.TypeReaders;
|
||||||
|
|
||||||
public sealed class ShmartNumberTypeReader : NadekoTypeReader<ShmartNumber>
|
public sealed class ShmartNumberTypeReader : NadekoTypeReader<ShmartNumber>
|
||||||
{
|
{
|
||||||
|
private static readonly Regex _percentRegex = new(@"^((?<num>100|\d{1,2})%)$", RegexOptions.Compiled);
|
||||||
private readonly DbService _db;
|
private readonly DbService _db;
|
||||||
private readonly GamblingConfigService _gambling;
|
private readonly GamblingConfigService _gambling;
|
||||||
|
|
||||||
@@ -33,7 +35,7 @@ public sealed class ShmartNumberTypeReader : NadekoTypeReader<ShmartNumber>
|
|||||||
return TypeReaderResult.FromSuccess(new ShmartNumber(num, i));
|
return TypeReaderResult.FromSuccess(new ShmartNumber(num, i));
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var expr = new NCalc.Expression(i, NCalc.EvaluateOptions.IgnoreCase);
|
var expr = new Expression(i, EvaluateOptions.IgnoreCase);
|
||||||
expr.EvaluateParameter += (str, ev) => EvaluateParam(str, ev, context);
|
expr.EvaluateParameter += (str, ev) => EvaluateParam(str, ev, context);
|
||||||
var lon = (long)decimal.Parse(expr.Evaluate().ToString());
|
var lon = (long)decimal.Parse(expr.Evaluate().ToString());
|
||||||
return TypeReaderResult.FromSuccess(new ShmartNumber(lon, input));
|
return TypeReaderResult.FromSuccess(new ShmartNumber(lon, input));
|
||||||
@@ -44,7 +46,7 @@ public sealed class ShmartNumberTypeReader : NadekoTypeReader<ShmartNumber>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void EvaluateParam(string name, NCalc.ParameterArgs args, ICommandContext ctx)
|
private void EvaluateParam(string name, ParameterArgs args, ICommandContext ctx)
|
||||||
{
|
{
|
||||||
switch (name.ToUpperInvariant())
|
switch (name.ToUpperInvariant())
|
||||||
{
|
{
|
||||||
@@ -67,8 +69,6 @@ public sealed class ShmartNumberTypeReader : NadekoTypeReader<ShmartNumber>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static readonly Regex _percentRegex = new(@"^((?<num>100|\d{1,2})%)$", RegexOptions.Compiled);
|
|
||||||
|
|
||||||
private long Cur(ICommandContext ctx)
|
private long Cur(ICommandContext ctx)
|
||||||
{
|
{
|
||||||
using var uow = _db.GetDbContext();
|
using var uow = _db.GetDbContext();
|
||||||
@@ -97,4 +97,4 @@ public sealed class ShmartNumberTypeReader : NadekoTypeReader<ShmartNumber>
|
|||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,4 +1,4 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
using NadekoBot.Common.TypeReaders.Models;
|
using NadekoBot.Common.TypeReaders.Models;
|
||||||
|
|
||||||
namespace NadekoBot.Common.TypeReaders;
|
namespace NadekoBot.Common.TypeReaders;
|
||||||
@@ -19,4 +19,4 @@ public sealed class StoopidTimeTypeReader : NadekoTypeReader<StoopidTime>
|
|||||||
return Task.FromResult(TypeReaderResult.FromError(CommandError.Exception, ex.Message));
|
return Task.FromResult(TypeReaderResult.FromError(CommandError.Exception, ex.Message));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,4 +1,4 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
namespace NadekoBot.Common.Yml;
|
namespace NadekoBot.Common.Yml;
|
||||||
|
|
||||||
public class CommentAttribute : Attribute
|
public class CommentAttribute : Attribute
|
||||||
@@ -7,4 +7,4 @@ public class CommentAttribute : Attribute
|
|||||||
|
|
||||||
public CommentAttribute(string comment)
|
public CommentAttribute(string comment)
|
||||||
=> Comment = comment;
|
=> Comment = comment;
|
||||||
}
|
}
|
@@ -1,4 +1,4 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
using YamlDotNet.Core;
|
using YamlDotNet.Core;
|
||||||
using YamlDotNet.Serialization;
|
using YamlDotNet.Serialization;
|
||||||
using YamlDotNet.Serialization.TypeInspectors;
|
using YamlDotNet.Serialization.TypeInspectors;
|
||||||
@@ -17,15 +17,7 @@ public class CommentGatheringTypeInspector : TypeInspectorSkeleton
|
|||||||
|
|
||||||
private sealed class CommentsPropertyDescriptor : IPropertyDescriptor
|
private sealed class CommentsPropertyDescriptor : IPropertyDescriptor
|
||||||
{
|
{
|
||||||
private readonly IPropertyDescriptor baseDescriptor;
|
public string Name { get; }
|
||||||
|
|
||||||
public CommentsPropertyDescriptor(IPropertyDescriptor baseDescriptor)
|
|
||||||
{
|
|
||||||
this.baseDescriptor = baseDescriptor;
|
|
||||||
Name = baseDescriptor.Name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public string Name { get; set; }
|
|
||||||
|
|
||||||
public Type Type
|
public Type Type
|
||||||
=> baseDescriptor.Type;
|
=> baseDescriptor.Type;
|
||||||
@@ -47,6 +39,14 @@ public class CommentGatheringTypeInspector : TypeInspectorSkeleton
|
|||||||
public bool CanWrite
|
public bool CanWrite
|
||||||
=> baseDescriptor.CanWrite;
|
=> baseDescriptor.CanWrite;
|
||||||
|
|
||||||
|
private readonly IPropertyDescriptor baseDescriptor;
|
||||||
|
|
||||||
|
public CommentsPropertyDescriptor(IPropertyDescriptor baseDescriptor)
|
||||||
|
{
|
||||||
|
this.baseDescriptor = baseDescriptor;
|
||||||
|
Name = baseDescriptor.Name;
|
||||||
|
}
|
||||||
|
|
||||||
public void Write(object target, object value)
|
public void Write(object target, object value)
|
||||||
=> baseDescriptor.Write(target, value);
|
=> baseDescriptor.Write(target, value);
|
||||||
|
|
||||||
@@ -62,4 +62,4 @@ public class CommentGatheringTypeInspector : TypeInspectorSkeleton
|
|||||||
: baseDescriptor.Read(target);
|
: baseDescriptor.Read(target);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,4 +1,4 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
using YamlDotNet.Core;
|
using YamlDotNet.Core;
|
||||||
using YamlDotNet.Serialization;
|
using YamlDotNet.Serialization;
|
||||||
|
|
||||||
@@ -6,15 +6,7 @@ namespace NadekoBot.Common.Yml;
|
|||||||
|
|
||||||
public sealed class CommentsObjectDescriptor : IObjectDescriptor
|
public sealed class CommentsObjectDescriptor : IObjectDescriptor
|
||||||
{
|
{
|
||||||
private readonly IObjectDescriptor innerDescriptor;
|
public string Comment { get; }
|
||||||
|
|
||||||
public CommentsObjectDescriptor(IObjectDescriptor innerDescriptor, string comment)
|
|
||||||
{
|
|
||||||
this.innerDescriptor = innerDescriptor;
|
|
||||||
this.Comment = comment;
|
|
||||||
}
|
|
||||||
|
|
||||||
public string Comment { get; private set; }
|
|
||||||
|
|
||||||
public object Value
|
public object Value
|
||||||
=> innerDescriptor.Value;
|
=> innerDescriptor.Value;
|
||||||
@@ -27,4 +19,12 @@ public sealed class CommentsObjectDescriptor : IObjectDescriptor
|
|||||||
|
|
||||||
public ScalarStyle ScalarStyle
|
public ScalarStyle ScalarStyle
|
||||||
=> innerDescriptor.ScalarStyle;
|
=> innerDescriptor.ScalarStyle;
|
||||||
}
|
|
||||||
|
private readonly IObjectDescriptor innerDescriptor;
|
||||||
|
|
||||||
|
public CommentsObjectDescriptor(IObjectDescriptor innerDescriptor, string comment)
|
||||||
|
{
|
||||||
|
this.innerDescriptor = innerDescriptor;
|
||||||
|
Comment = comment;
|
||||||
|
}
|
||||||
|
}
|
@@ -1,4 +1,4 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
using YamlDotNet.Core;
|
using YamlDotNet.Core;
|
||||||
using YamlDotNet.Core.Events;
|
using YamlDotNet.Core.Events;
|
||||||
using YamlDotNet.Serialization;
|
using YamlDotNet.Serialization;
|
||||||
@@ -15,12 +15,10 @@ public class CommentsObjectGraphVisitor : ChainedObjectGraphVisitor
|
|||||||
|
|
||||||
public override bool EnterMapping(IPropertyDescriptor key, IObjectDescriptor value, IEmitter context)
|
public override bool EnterMapping(IPropertyDescriptor key, IObjectDescriptor value, IEmitter context)
|
||||||
{
|
{
|
||||||
if (value is CommentsObjectDescriptor commentsDescriptor &&
|
if (value is CommentsObjectDescriptor commentsDescriptor
|
||||||
!string.IsNullOrWhiteSpace(commentsDescriptor.Comment))
|
&& !string.IsNullOrWhiteSpace(commentsDescriptor.Comment))
|
||||||
{
|
|
||||||
context.Emit(new Comment(commentsDescriptor.Comment.Replace("\n", "\n# "), false));
|
context.Emit(new Comment(commentsDescriptor.Comment.Replace("\n", "\n# "), false));
|
||||||
}
|
|
||||||
|
|
||||||
return base.EnterMapping(key, value, context);
|
return base.EnterMapping(key, value, context);
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,4 +1,4 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
using YamlDotNet.Core;
|
using YamlDotNet.Core;
|
||||||
using YamlDotNet.Serialization;
|
using YamlDotNet.Serialization;
|
||||||
using YamlDotNet.Serialization.EventEmitters;
|
using YamlDotNet.Serialization.EventEmitters;
|
||||||
@@ -19,12 +19,12 @@ public class MultilineScalarFlowStyleEmitter : ChainedEventEmitter
|
|||||||
var value = eventInfo.Source.Value as string;
|
var value = eventInfo.Source.Value as string;
|
||||||
if (!string.IsNullOrEmpty(value))
|
if (!string.IsNullOrEmpty(value))
|
||||||
{
|
{
|
||||||
var isMultiLine = value.IndexOfAny(new char[] { '\r', '\n', '\x85', '\x2028', '\x2029' }) >= 0;
|
var isMultiLine = value.IndexOfAny(new[] { '\r', '\n', '\x85', '\x2028', '\x2029' }) >= 0;
|
||||||
if (isMultiLine)
|
if (isMultiLine)
|
||||||
eventInfo = new(eventInfo.Source) { Style = ScalarStyle.Literal, };
|
eventInfo = new(eventInfo.Source) { Style = ScalarStyle.Literal };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
nextEmitter.Emit(eventInfo, emitter);
|
nextEmitter.Emit(eventInfo, emitter);
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,6 +1,6 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
using System.Globalization;
|
|
||||||
using SixLabors.ImageSharp.PixelFormats;
|
using SixLabors.ImageSharp.PixelFormats;
|
||||||
|
using System.Globalization;
|
||||||
using YamlDotNet.Core;
|
using YamlDotNet.Core;
|
||||||
using YamlDotNet.Core.Events;
|
using YamlDotNet.Core.Events;
|
||||||
using YamlDotNet.Serialization;
|
using YamlDotNet.Serialization;
|
||||||
@@ -44,4 +44,4 @@ public class CultureInfoConverter : IYamlTypeConverter
|
|||||||
var ci = (CultureInfo)value;
|
var ci = (CultureInfo)value;
|
||||||
emitter.Emit(new Scalar(ci.Name));
|
emitter.Emit(new Scalar(ci.Name));
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,4 +1,4 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
using YamlDotNet.Core;
|
using YamlDotNet.Core;
|
||||||
using YamlDotNet.Core.Events;
|
using YamlDotNet.Core.Events;
|
||||||
using YamlDotNet.Serialization;
|
using YamlDotNet.Serialization;
|
||||||
@@ -22,4 +22,4 @@ public class UriConverter : IYamlTypeConverter
|
|||||||
var uri = (Uri)value;
|
var uri = (Uri)value;
|
||||||
emitter.Emit(new Scalar(uri.ToString()));
|
emitter.Emit(new Scalar(uri.ToString()));
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,5 +1,6 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
using YamlDotNet.Serialization;
|
using YamlDotNet.Serialization;
|
||||||
|
using YamlDotNet.Serialization.NamingConventions;
|
||||||
|
|
||||||
namespace NadekoBot.Common.Yml;
|
namespace NadekoBot.Common.Yml;
|
||||||
|
|
||||||
@@ -7,21 +8,21 @@ public class Yaml
|
|||||||
{
|
{
|
||||||
public static ISerializer Serializer
|
public static ISerializer Serializer
|
||||||
=> new SerializerBuilder().WithTypeInspector(inner => new CommentGatheringTypeInspector(inner))
|
=> new SerializerBuilder().WithTypeInspector(inner => new CommentGatheringTypeInspector(inner))
|
||||||
.WithEmissionPhaseObjectGraphVisitor(args => new CommentsObjectGraphVisitor(args.InnerVisitor))
|
.WithEmissionPhaseObjectGraphVisitor(args
|
||||||
.WithEventEmitter(args => new MultilineScalarFlowStyleEmitter(args))
|
=> new CommentsObjectGraphVisitor(args.InnerVisitor))
|
||||||
.WithNamingConvention(YamlDotNet.Serialization.NamingConventions.CamelCaseNamingConvention.Instance)
|
.WithEventEmitter(args => new MultilineScalarFlowStyleEmitter(args))
|
||||||
.WithIndentedSequences()
|
.WithNamingConvention(CamelCaseNamingConvention.Instance)
|
||||||
.WithTypeConverter(new Rgba32Converter())
|
.WithIndentedSequences()
|
||||||
.WithTypeConverter(new CultureInfoConverter())
|
.WithTypeConverter(new Rgba32Converter())
|
||||||
.WithTypeConverter(new UriConverter())
|
.WithTypeConverter(new CultureInfoConverter())
|
||||||
.Build();
|
.WithTypeConverter(new UriConverter())
|
||||||
|
.Build();
|
||||||
|
|
||||||
public static IDeserializer Deserializer
|
public static IDeserializer Deserializer
|
||||||
=> new DeserializerBuilder()
|
=> new DeserializerBuilder().WithNamingConvention(CamelCaseNamingConvention.Instance)
|
||||||
.WithNamingConvention(YamlDotNet.Serialization.NamingConventions.CamelCaseNamingConvention.Instance)
|
.WithTypeConverter(new Rgba32Converter())
|
||||||
.WithTypeConverter(new Rgba32Converter())
|
.WithTypeConverter(new CultureInfoConverter())
|
||||||
.WithTypeConverter(new CultureInfoConverter())
|
.WithTypeConverter(new UriConverter())
|
||||||
.WithTypeConverter(new UriConverter())
|
.IgnoreUnmatchedProperties()
|
||||||
.IgnoreUnmatchedProperties()
|
.Build();
|
||||||
.Build();
|
}
|
||||||
}
|
|
@@ -1,12 +1,12 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
namespace NadekoBot.Common.Yml;
|
namespace NadekoBot.Common.Yml;
|
||||||
|
|
||||||
public class YamlHelper
|
public class YamlHelper
|
||||||
{
|
{
|
||||||
// https://github.com/aaubry/YamlDotNet/blob/0f4cc205e8b2dd8ef6589d96de32bf608a687c6f/YamlDotNet/Core/Scanner.cs#L1687
|
// https://github.com/aaubry/YamlDotNet/blob/0f4cc205e8b2dd8ef6589d96de32bf608a687c6f/YamlDotNet/Core/Scanner.cs#L1687
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This is modified code from yamldotnet's repo which handles parsing unicode code points
|
/// This is modified code from yamldotnet's repo which handles parsing unicode code points
|
||||||
/// it is needed as yamldotnet doesn't support unescaped unicode characters
|
/// it is needed as yamldotnet doesn't support unescaped unicode characters
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="point">Unicode code point</param>
|
/// <param name="point">Unicode code point</param>
|
||||||
/// <returns>Actual character</returns>
|
/// <returns>Actual character</returns>
|
||||||
@@ -18,20 +18,14 @@ public class YamlHelper
|
|||||||
|
|
||||||
foreach (var c in point)
|
foreach (var c in point)
|
||||||
{
|
{
|
||||||
if (!IsHex(c))
|
if (!IsHex(c)) return point;
|
||||||
{
|
|
||||||
return point;
|
|
||||||
}
|
|
||||||
|
|
||||||
character = (character << 4) + AsHex(c);
|
character = (character << 4) + AsHex(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check the value and write the character.
|
// 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 point;
|
|
||||||
}
|
|
||||||
|
|
||||||
return char.ConvertFromUtf32(character);
|
return char.ConvertFromUtf32(character);
|
||||||
}
|
}
|
||||||
@@ -41,16 +35,10 @@ public class YamlHelper
|
|||||||
|
|
||||||
public static int AsHex(char c)
|
public static int AsHex(char c)
|
||||||
{
|
{
|
||||||
if (c <= '9')
|
if (c <= '9') return c - '0';
|
||||||
{
|
|
||||||
return c - '0';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (c <= 'F')
|
if (c <= 'F') return c - 'A' + 10;
|
||||||
{
|
|
||||||
return c - 'A' + 10;
|
|
||||||
}
|
|
||||||
|
|
||||||
return c - 'a' + 10;
|
return c - 'a' + 10;
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,4 +1,4 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using NadekoBot.Db.Models;
|
using NadekoBot.Db.Models;
|
||||||
|
|
||||||
@@ -8,12 +8,12 @@ public static class ClubExtensions
|
|||||||
{
|
{
|
||||||
private static IQueryable<ClubInfo> Include(this DbSet<ClubInfo> clubs)
|
private static IQueryable<ClubInfo> Include(this DbSet<ClubInfo> clubs)
|
||||||
=> clubs.Include(x => x.Owner)
|
=> clubs.Include(x => x.Owner)
|
||||||
.Include(x => x.Applicants)
|
.Include(x => x.Applicants)
|
||||||
.ThenInclude(x => x.User)
|
.ThenInclude(x => x.User)
|
||||||
.Include(x => x.Bans)
|
.Include(x => x.Bans)
|
||||||
.ThenInclude(x => x.User)
|
.ThenInclude(x => x.User)
|
||||||
.Include(x => x.Users)
|
.Include(x => x.Users)
|
||||||
.AsQueryable();
|
.AsQueryable();
|
||||||
|
|
||||||
public static ClubInfo GetByOwner(this DbSet<ClubInfo> clubs, ulong userId)
|
public static ClubInfo GetByOwner(this DbSet<ClubInfo> clubs, ulong userId)
|
||||||
=> Include(clubs).FirstOrDefault(c => c.Owner.UserId == userId);
|
=> Include(clubs).FirstOrDefault(c => c.Owner.UserId == userId);
|
||||||
@@ -29,9 +29,9 @@ public static class ClubExtensions
|
|||||||
=> Include(clubs).FirstOrDefault(c => c.Name.ToUpper() == name.ToUpper() && c.Discrim == discrim);
|
=> Include(clubs).FirstOrDefault(c => c.Name.ToUpper() == name.ToUpper() && c.Discrim == discrim);
|
||||||
|
|
||||||
public static int GetNextDiscrim(this DbSet<ClubInfo> clubs, string name)
|
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() +
|
=> Include(clubs).Where(x => x.Name.ToUpper() == name.ToUpper()).Select(x => x.Discrim).DefaultIfEmpty().Max()
|
||||||
1;
|
+ 1;
|
||||||
|
|
||||||
public static List<ClubInfo> GetClubLeaderboardPage(this DbSet<ClubInfo> clubs, int page)
|
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();
|
||||||
}
|
}
|
@@ -1,4 +1,4 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using NadekoBot.Services.Database.Models;
|
using NadekoBot.Services.Database.Models;
|
||||||
|
|
||||||
@@ -8,10 +8,10 @@ public static class CurrencyTransactionExtensions
|
|||||||
{
|
{
|
||||||
public static List<CurrencyTransaction> GetPageFor(this DbSet<CurrencyTransaction> set, ulong userId, int page)
|
public static List<CurrencyTransaction> GetPageFor(this DbSet<CurrencyTransaction> set, ulong userId, int page)
|
||||||
=> set.AsQueryable()
|
=> set.AsQueryable()
|
||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
.Where(x => x.UserId == userId)
|
.Where(x => x.UserId == userId)
|
||||||
.OrderByDescending(x => x.DateAdded)
|
.OrderByDescending(x => x.DateAdded)
|
||||||
.Skip(15 * page)
|
.Skip(15 * page)
|
||||||
.Take(15)
|
.Take(15)
|
||||||
.ToList();
|
.ToList();
|
||||||
}
|
}
|
@@ -1,6 +1,6 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using LinqToDB;
|
using LinqToDB;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
using NadekoBot.Services.Database.Models;
|
using NadekoBot.Services.Database.Models;
|
||||||
|
|
||||||
namespace NadekoBot.Db;
|
namespace NadekoBot.Db;
|
||||||
@@ -15,4 +15,4 @@ public static class CustomReactionsExtensions
|
|||||||
|
|
||||||
public static CustomReaction GetByGuildIdAndInput(this DbSet<CustomReaction> crs, ulong? guildId, string input)
|
public static CustomReaction GetByGuildIdAndInput(this DbSet<CustomReaction> crs, ulong? guildId, string input)
|
||||||
=> crs.FirstOrDefault(x => x.GuildId == guildId && x.Trigger.ToUpper() == input);
|
=> crs.FirstOrDefault(x => x.GuildId == guildId && x.Trigger.ToUpper() == input);
|
||||||
}
|
}
|
@@ -1,4 +1,4 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using NadekoBot.Services.Database.Models;
|
using NadekoBot.Services.Database.Models;
|
||||||
|
|
||||||
@@ -9,4 +9,4 @@ public static class DbExtensions
|
|||||||
public static T GetById<T>(this DbSet<T> set, int id)
|
public static T GetById<T>(this DbSet<T> set, int id)
|
||||||
where T : DbEntity
|
where T : DbEntity
|
||||||
=> set.FirstOrDefault(x => x.Id == id);
|
=> set.FirstOrDefault(x => x.Id == id);
|
||||||
}
|
}
|
@@ -1,8 +1,8 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
using NadekoBot.Db.Models;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using LinqToDB;
|
using LinqToDB;
|
||||||
using LinqToDB.EntityFrameworkCore;
|
using LinqToDB.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using NadekoBot.Db.Models;
|
||||||
using NadekoBot.Services.Database;
|
using NadekoBot.Services.Database;
|
||||||
|
|
||||||
namespace NadekoBot.Db;
|
namespace NadekoBot.Db;
|
||||||
@@ -16,19 +16,18 @@ public static class DiscordUserExtensions
|
|||||||
string discrim,
|
string discrim,
|
||||||
string avatarId)
|
string avatarId)
|
||||||
=> ctx.DiscordUser.ToLinqToDBTable()
|
=> ctx.DiscordUser.ToLinqToDBTable()
|
||||||
.InsertOrUpdate(
|
.InsertOrUpdate(
|
||||||
() => new()
|
() => new()
|
||||||
{
|
{
|
||||||
UserId = userId,
|
UserId = userId,
|
||||||
Username = username,
|
Username = username,
|
||||||
Discriminator = discrim,
|
Discriminator = discrim,
|
||||||
AvatarId = avatarId,
|
AvatarId = avatarId,
|
||||||
TotalXp = 0,
|
TotalXp = 0,
|
||||||
CurrencyAmount = 0
|
CurrencyAmount = 0
|
||||||
},
|
},
|
||||||
old => new() { Username = username, Discriminator = discrim, AvatarId = avatarId, },
|
old => new() { Username = username, Discriminator = discrim, AvatarId = avatarId },
|
||||||
() => new() { UserId = userId }
|
() => new() { UserId = userId });
|
||||||
);
|
|
||||||
|
|
||||||
//temp is only used in updatecurrencystate, so that i don't overwrite real usernames/discrims with Unknown
|
//temp is only used in updatecurrencystate, so that i don't overwrite real usernames/discrims with Unknown
|
||||||
public static DiscordUser GetOrCreateUser(
|
public static DiscordUser GetOrCreateUser(
|
||||||
@@ -38,40 +37,22 @@ public static class DiscordUserExtensions
|
|||||||
string discrim,
|
string discrim,
|
||||||
string avatarId)
|
string avatarId)
|
||||||
{
|
{
|
||||||
ctx.EnsureUserCreated(userId,
|
ctx.EnsureUserCreated(userId, username, discrim, avatarId);
|
||||||
username,
|
return ctx.DiscordUser.Include(x => x.Club).First(u => u.UserId == userId);
|
||||||
discrim,
|
|
||||||
avatarId
|
|
||||||
);
|
|
||||||
return ctx.DiscordUser.Include(x => x.Club)
|
|
||||||
.First(u => u.UserId == userId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static DiscordUser GetOrCreateUser(this NadekoContext ctx, IUser original)
|
public static DiscordUser GetOrCreateUser(this NadekoContext ctx, IUser original)
|
||||||
=> ctx.GetOrCreateUser(original.Id,
|
=> ctx.GetOrCreateUser(original.Id, original.Username, original.Discriminator, original.AvatarId);
|
||||||
original.Username,
|
|
||||||
original.Discriminator,
|
|
||||||
original.AvatarId
|
|
||||||
);
|
|
||||||
|
|
||||||
public static int GetUserGlobalRank(this DbSet<DiscordUser> users, ulong id)
|
public static int GetUserGlobalRank(this DbSet<DiscordUser> users, ulong id)
|
||||||
=> users.AsQueryable()
|
=> users.AsQueryable()
|
||||||
.Where(x => x.TotalXp >
|
.Where(x => x.TotalXp
|
||||||
users.AsQueryable()
|
> users.AsQueryable().Where(y => y.UserId == id).Select(y => y.TotalXp).FirstOrDefault())
|
||||||
.Where(y => y.UserId == id)
|
.Count()
|
||||||
.Select(y => y.TotalXp)
|
+ 1;
|
||||||
.FirstOrDefault()
|
|
||||||
)
|
|
||||||
.Count() +
|
|
||||||
1;
|
|
||||||
|
|
||||||
public static DiscordUser[] GetUsersXpLeaderboardFor(this DbSet<DiscordUser> users, int page)
|
public static DiscordUser[] GetUsersXpLeaderboardFor(this DbSet<DiscordUser> users, int page)
|
||||||
=> users.AsQueryable()
|
=> users.AsQueryable().OrderByDescending(x => x.TotalXp).Skip(page * 9).Take(9).AsEnumerable().ToArray();
|
||||||
.OrderByDescending(x => x.TotalXp)
|
|
||||||
.Skip(page * 9)
|
|
||||||
.Take(9)
|
|
||||||
.AsEnumerable()
|
|
||||||
.ToArray();
|
|
||||||
|
|
||||||
public static List<DiscordUser> GetTopRichest(
|
public static List<DiscordUser> GetTopRichest(
|
||||||
this DbSet<DiscordUser> users,
|
this DbSet<DiscordUser> users,
|
||||||
@@ -79,26 +60,19 @@ public static class DiscordUserExtensions
|
|||||||
int count,
|
int count,
|
||||||
int page = 0)
|
int page = 0)
|
||||||
=> users.AsQueryable()
|
=> users.AsQueryable()
|
||||||
.Where(c => c.CurrencyAmount > 0 && botId != c.UserId)
|
.Where(c => c.CurrencyAmount > 0 && botId != c.UserId)
|
||||||
.OrderByDescending(c => c.CurrencyAmount)
|
.OrderByDescending(c => c.CurrencyAmount)
|
||||||
.Skip(page * 9)
|
.Skip(page * 9)
|
||||||
.Take(count)
|
.Take(count)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
public static long GetUserCurrency(this DbSet<DiscordUser> users, ulong userId)
|
public static long GetUserCurrency(this DbSet<DiscordUser> users, ulong userId)
|
||||||
=> users.AsNoTracking()
|
=> users.AsNoTracking().FirstOrDefault(x => x.UserId == userId)?.CurrencyAmount ?? 0;
|
||||||
.FirstOrDefault(x => x.UserId == userId)
|
|
||||||
?.CurrencyAmount ??
|
|
||||||
0;
|
|
||||||
|
|
||||||
public static void RemoveFromMany(this DbSet<DiscordUser> users, IEnumerable<ulong> ids)
|
public static void RemoveFromMany(this DbSet<DiscordUser> users, IEnumerable<ulong> ids)
|
||||||
{
|
{
|
||||||
var items = users.AsQueryable()
|
var items = users.AsQueryable().Where(x => ids.Contains(x.UserId));
|
||||||
.Where(x => ids.Contains(x.UserId));
|
foreach (var item in items) item.CurrencyAmount = 0;
|
||||||
foreach (var item in items)
|
|
||||||
{
|
|
||||||
item.CurrencyAmount = 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool TryUpdateCurrencyState(
|
public static bool TryUpdateCurrencyState(
|
||||||
@@ -115,14 +89,12 @@ public static class DiscordUserExtensions
|
|||||||
|
|
||||||
// if remove - try to remove if he has more or equal than the amount
|
// if remove - try to remove if he has more or equal than the amount
|
||||||
// and return number of rows > 0 (was there a change)
|
// and return number of rows > 0 (was there a change)
|
||||||
if (amount < 0 &&
|
if (amount < 0 && !allowNegative)
|
||||||
!allowNegative)
|
|
||||||
{
|
{
|
||||||
var rows = ctx.Database.ExecuteSqlInterpolated($@"
|
var rows = ctx.Database.ExecuteSqlInterpolated($@"
|
||||||
UPDATE DiscordUser
|
UPDATE DiscordUser
|
||||||
SET CurrencyAmount=CurrencyAmount+{amount}
|
SET CurrencyAmount=CurrencyAmount+{amount}
|
||||||
WHERE UserId={userId} AND CurrencyAmount>={-amount};"
|
WHERE UserId={userId} AND CurrencyAmount>={-amount};");
|
||||||
);
|
|
||||||
return rows > 0;
|
return rows > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,8 +104,7 @@ WHERE UserId={userId} AND CurrencyAmount>={-amount};"
|
|||||||
var rows = ctx.Database.ExecuteSqlInterpolated($@"
|
var rows = ctx.Database.ExecuteSqlInterpolated($@"
|
||||||
UPDATE DiscordUser
|
UPDATE DiscordUser
|
||||||
SET CurrencyAmount=CurrencyAmount+{amount}
|
SET CurrencyAmount=CurrencyAmount+{amount}
|
||||||
WHERE UserId={userId};"
|
WHERE UserId={userId};");
|
||||||
);
|
|
||||||
return rows > 0;
|
return rows > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -147,7 +118,6 @@ WHERE UserId={userId};"
|
|||||||
|
|
||||||
// just update the amount, there is no new user data
|
// just update the amount, there is no new user data
|
||||||
if (!updatedUserData)
|
if (!updatedUserData)
|
||||||
{
|
|
||||||
ctx.Database.ExecuteSqlInterpolated($@"
|
ctx.Database.ExecuteSqlInterpolated($@"
|
||||||
UPDATE OR IGNORE DiscordUser
|
UPDATE OR IGNORE DiscordUser
|
||||||
SET CurrencyAmount=CurrencyAmount+{amount}
|
SET CurrencyAmount=CurrencyAmount+{amount}
|
||||||
@@ -155,11 +125,8 @@ WHERE UserId={userId};
|
|||||||
|
|
||||||
INSERT OR IGNORE INTO DiscordUser (UserId, Username, Discriminator, AvatarId, CurrencyAmount, TotalXp)
|
INSERT OR IGNORE INTO DiscordUser (UserId, Username, Discriminator, AvatarId, CurrencyAmount, TotalXp)
|
||||||
VALUES ({userId}, {name}, {discrim}, {avatarId}, {amount}, 0);
|
VALUES ({userId}, {name}, {discrim}, {avatarId}, {amount}, 0);
|
||||||
"
|
");
|
||||||
);
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
|
||||||
ctx.Database.ExecuteSqlInterpolated($@"
|
ctx.Database.ExecuteSqlInterpolated($@"
|
||||||
UPDATE OR IGNORE DiscordUser
|
UPDATE OR IGNORE DiscordUser
|
||||||
SET CurrencyAmount=CurrencyAmount+{amount},
|
SET CurrencyAmount=CurrencyAmount+{amount},
|
||||||
@@ -170,9 +137,7 @@ WHERE UserId={userId};
|
|||||||
|
|
||||||
INSERT OR IGNORE INTO DiscordUser (UserId, Username, Discriminator, AvatarId, CurrencyAmount, TotalXp)
|
INSERT OR IGNORE INTO DiscordUser (UserId, Username, Discriminator, AvatarId, CurrencyAmount, TotalXp)
|
||||||
VALUES ({userId}, {name}, {discrim}, {avatarId}, {amount}, 0);
|
VALUES ({userId}, {name}, {discrim}, {avatarId}, {amount}, 0);
|
||||||
"
|
");
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -182,8 +147,8 @@ VALUES ({userId}, {name}, {discrim}, {avatarId}, {amount}, 0);
|
|||||||
|
|
||||||
public static decimal GetTopOnePercentCurrency(this DbSet<DiscordUser> users, ulong botId)
|
public static decimal GetTopOnePercentCurrency(this DbSet<DiscordUser> users, ulong botId)
|
||||||
=> users.AsQueryable()
|
=> users.AsQueryable()
|
||||||
.Where(x => x.UserId != botId)
|
.Where(x => x.UserId != botId)
|
||||||
.OrderByDescending(x => x.CurrencyAmount)
|
.OrderByDescending(x => x.CurrencyAmount)
|
||||||
.Take(users.Count() / 100 == 0 ? 1 : users.Count() / 100)
|
.Take(users.Count() / 100 == 0 ? 1 : users.Count() / 100)
|
||||||
.Sum(x => x.CurrencyAmount);
|
.Sum(x => x.CurrencyAmount);
|
||||||
}
|
}
|
@@ -1,21 +1,22 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
using NadekoBot.Services.Database.Models;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using NadekoBot.Services.Database;
|
|
||||||
using NadekoBot.Db.Models;
|
using NadekoBot.Db.Models;
|
||||||
|
using NadekoBot.Services.Database;
|
||||||
|
using NadekoBot.Services.Database.Models;
|
||||||
|
|
||||||
namespace NadekoBot.Db;
|
namespace NadekoBot.Db;
|
||||||
|
|
||||||
public static class GuildConfigExtensions
|
public static class GuildConfigExtensions
|
||||||
{
|
{
|
||||||
public class GeneratingChannel
|
private static List<WarningPunishment> DefaultWarnPunishments
|
||||||
{
|
=> new()
|
||||||
public ulong GuildId { get; set; }
|
{
|
||||||
public ulong ChannelId { get; set; }
|
new() { Count = 3, Punishment = PunishmentAction.Kick },
|
||||||
}
|
new() { Count = 5, Punishment = PunishmentAction.Ban }
|
||||||
|
};
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets full stream role settings for the guild with the specified id.
|
/// Gets full stream role settings for the guild with the specified id.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="ctx">Db Context</param>
|
/// <param name="ctx">Db Context</param>
|
||||||
/// <param name="guildId">Id of the guild to get stream role settings for.</param>
|
/// <param name="guildId">Id of the guild to get stream role settings for.</param>
|
||||||
@@ -24,9 +25,8 @@ public static class GuildConfigExtensions
|
|||||||
{
|
{
|
||||||
var conf = ctx.GuildConfigsForId(guildId,
|
var conf = ctx.GuildConfigsForId(guildId,
|
||||||
set => set.Include(y => y.StreamRole)
|
set => set.Include(y => y.StreamRole)
|
||||||
.Include(y => y.StreamRole.Whitelist)
|
.Include(y => y.StreamRole.Whitelist)
|
||||||
.Include(y => y.StreamRole.Blacklist)
|
.Include(y => y.StreamRole.Blacklist));
|
||||||
);
|
|
||||||
|
|
||||||
if (conf.StreamRole is null)
|
if (conf.StreamRole is null)
|
||||||
conf.StreamRole = new();
|
conf.StreamRole = new();
|
||||||
@@ -34,35 +34,25 @@ public static class GuildConfigExtensions
|
|||||||
return conf.StreamRole;
|
return conf.StreamRole;
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
private static IQueryable<GuildConfig> IncludeEverything(this DbSet<GuildConfig> configs)
|
||||||
=> configs.AsQueryable()
|
=> configs.AsQueryable()
|
||||||
.AsSplitQuery()
|
.AsSplitQuery()
|
||||||
.Include(gc => gc.CommandCooldowns)
|
.Include(gc => gc.CommandCooldowns)
|
||||||
.Include(gc => gc.FollowedStreams)
|
.Include(gc => gc.FollowedStreams)
|
||||||
.Include(gc => gc.StreamRole)
|
.Include(gc => gc.StreamRole)
|
||||||
.Include(gc => gc.XpSettings)
|
.Include(gc => gc.XpSettings)
|
||||||
.ThenInclude(x => x.ExclusionList)
|
.ThenInclude(x => x.ExclusionList)
|
||||||
.Include(gc => gc.DelMsgOnCmdChannels)
|
.Include(gc => gc.DelMsgOnCmdChannels)
|
||||||
.Include(gc => gc.ReactionRoleMessages)
|
.Include(gc => gc.ReactionRoleMessages)
|
||||||
.ThenInclude(x => x.ReactionRoles);
|
.ThenInclude(x => x.ReactionRoles);
|
||||||
|
|
||||||
public static IEnumerable<GuildConfig> GetAllGuildConfigs(
|
public static IEnumerable<GuildConfig> GetAllGuildConfigs(
|
||||||
this DbSet<GuildConfig> configs,
|
this DbSet<GuildConfig> configs,
|
||||||
List<ulong> availableGuilds)
|
List<ulong> availableGuilds)
|
||||||
=> configs.IncludeEverything()
|
=> configs.IncludeEverything().AsNoTracking().Where(x => availableGuilds.Contains(x.GuildId)).ToList();
|
||||||
.AsNoTracking()
|
|
||||||
.Where(x => availableGuilds.Contains(x.GuildId))
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets and creates if it doesn't exist a config for a guild.
|
/// Gets and creates if it doesn't exist a config for a guild.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="ctx">Context</param>
|
/// <param name="ctx">Context</param>
|
||||||
/// <param name="guildId">Id of the guide</param>
|
/// <param name="guildId">Id of the guide</param>
|
||||||
@@ -77,8 +67,7 @@ public static class GuildConfigExtensions
|
|||||||
|
|
||||||
if (includes is null)
|
if (includes is null)
|
||||||
{
|
{
|
||||||
config = ctx.GuildConfigs.IncludeEverything()
|
config = ctx.GuildConfigs.IncludeEverything().FirstOrDefault(c => c.GuildId == guildId);
|
||||||
.FirstOrDefault(c => c.GuildId == guildId);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -89,13 +78,12 @@ public static class GuildConfigExtensions
|
|||||||
if (config is null)
|
if (config is null)
|
||||||
{
|
{
|
||||||
ctx.GuildConfigs.Add(config = new()
|
ctx.GuildConfigs.Add(config = new()
|
||||||
{
|
{
|
||||||
GuildId = guildId,
|
GuildId = guildId,
|
||||||
Permissions = Permissionv2.GetDefaultPermlist,
|
Permissions = Permissionv2.GetDefaultPermlist,
|
||||||
WarningsInitialized = true,
|
WarningsInitialized = true,
|
||||||
WarnPunishments = DefaultWarnPunishments,
|
WarnPunishments = DefaultWarnPunishments
|
||||||
}
|
});
|
||||||
);
|
|
||||||
ctx.SaveChanges();
|
ctx.SaveChanges();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,9 +99,9 @@ public static class GuildConfigExtensions
|
|||||||
public static LogSetting LogSettingsFor(this NadekoContext ctx, ulong guildId)
|
public static LogSetting LogSettingsFor(this NadekoContext ctx, ulong guildId)
|
||||||
{
|
{
|
||||||
var logSetting = ctx.LogSettings.AsQueryable()
|
var logSetting = ctx.LogSettings.AsQueryable()
|
||||||
.Include(x => x.LogIgnores)
|
.Include(x => x.LogIgnores)
|
||||||
.Where(x => x.GuildId == guildId)
|
.Where(x => x.GuildId == guildId)
|
||||||
.FirstOrDefault();
|
.FirstOrDefault();
|
||||||
|
|
||||||
if (logSetting is null)
|
if (logSetting is null)
|
||||||
{
|
{
|
||||||
@@ -126,9 +114,7 @@ public static class GuildConfigExtensions
|
|||||||
|
|
||||||
public static IEnumerable<GuildConfig> Permissionsv2ForAll(this DbSet<GuildConfig> configs, List<ulong> include)
|
public static IEnumerable<GuildConfig> Permissionsv2ForAll(this DbSet<GuildConfig> configs, List<ulong> include)
|
||||||
{
|
{
|
||||||
var query = configs.AsQueryable()
|
var query = configs.AsQueryable().Where(x => include.Contains(x.GuildId)).Include(gc => gc.Permissions);
|
||||||
.Where(x => include.Contains(x.GuildId))
|
|
||||||
.Include(gc => gc.Permissions);
|
|
||||||
|
|
||||||
return query.ToList();
|
return query.ToList();
|
||||||
}
|
}
|
||||||
@@ -136,17 +122,16 @@ public static class GuildConfigExtensions
|
|||||||
public static GuildConfig GcWithPermissionsv2For(this NadekoContext ctx, ulong guildId)
|
public static GuildConfig GcWithPermissionsv2For(this NadekoContext ctx, ulong guildId)
|
||||||
{
|
{
|
||||||
var config = ctx.GuildConfigs.AsQueryable()
|
var config = ctx.GuildConfigs.AsQueryable()
|
||||||
.Where(gc => gc.GuildId == guildId)
|
.Where(gc => gc.GuildId == guildId)
|
||||||
.Include(gc => gc.Permissions)
|
.Include(gc => gc.Permissions)
|
||||||
.FirstOrDefault();
|
.FirstOrDefault();
|
||||||
|
|
||||||
if (config is null) // if there is no guildconfig, create new one
|
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();
|
ctx.SaveChanges();
|
||||||
}
|
}
|
||||||
else if (config.Permissions is null ||
|
else if (config.Permissions is null || !config.Permissions.Any()) // if no perms, add default ones
|
||||||
!config.Permissions.Any()) // if no perms, add default ones
|
|
||||||
{
|
{
|
||||||
config.Permissions = Permissionv2.GetDefaultPermlist;
|
config.Permissions = Permissionv2.GetDefaultPermlist;
|
||||||
ctx.SaveChanges();
|
ctx.SaveChanges();
|
||||||
@@ -156,17 +141,14 @@ public static class GuildConfigExtensions
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static IEnumerable<FollowedStream> GetFollowedStreams(this DbSet<GuildConfig> configs)
|
public static IEnumerable<FollowedStream> GetFollowedStreams(this DbSet<GuildConfig> configs)
|
||||||
=> configs.AsQueryable()
|
=> configs.AsQueryable().Include(x => x.FollowedStreams).SelectMany(gc => gc.FollowedStreams).ToArray();
|
||||||
.Include(x => x.FollowedStreams)
|
|
||||||
.SelectMany(gc => gc.FollowedStreams)
|
|
||||||
.ToArray();
|
|
||||||
|
|
||||||
public static IEnumerable<FollowedStream> GetFollowedStreams(this DbSet<GuildConfig> configs, List<ulong> included)
|
public static IEnumerable<FollowedStream> GetFollowedStreams(this DbSet<GuildConfig> configs, List<ulong> included)
|
||||||
=> configs.AsQueryable()
|
=> configs.AsQueryable()
|
||||||
.Where(gc => included.Contains(gc.GuildId))
|
.Where(gc => included.Contains(gc.GuildId))
|
||||||
.Include(gc => gc.FollowedStreams)
|
.Include(gc => gc.FollowedStreams)
|
||||||
.SelectMany(gc => gc.FollowedStreams)
|
.SelectMany(gc => gc.FollowedStreams)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
public static void SetCleverbotEnabled(this DbSet<GuildConfig> configs, ulong id, bool cleverbotEnabled)
|
public static void SetCleverbotEnabled(this DbSet<GuildConfig> configs, ulong id, bool cleverbotEnabled)
|
||||||
{
|
{
|
||||||
@@ -182,24 +164,29 @@ public static class GuildConfigExtensions
|
|||||||
{
|
{
|
||||||
var gc = ctx.GuildConfigsForId(guildId,
|
var gc = ctx.GuildConfigsForId(guildId,
|
||||||
set => set.Include(x => x.XpSettings)
|
set => set.Include(x => x.XpSettings)
|
||||||
.ThenInclude(x => x.RoleRewards)
|
.ThenInclude(x => x.RoleRewards)
|
||||||
.Include(x => x.XpSettings)
|
.Include(x => x.XpSettings)
|
||||||
.ThenInclude(x => x.CurrencyRewards)
|
.ThenInclude(x => x.CurrencyRewards)
|
||||||
.Include(x => x.XpSettings)
|
.Include(x => x.XpSettings)
|
||||||
.ThenInclude(x => x.ExclusionList)
|
.ThenInclude(x => x.ExclusionList));
|
||||||
);
|
|
||||||
|
|
||||||
if (gc.XpSettings is null)
|
if (gc.XpSettings is null)
|
||||||
gc.XpSettings = new XpSettings();
|
gc.XpSettings = new();
|
||||||
|
|
||||||
return gc.XpSettings;
|
return gc.XpSettings;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IEnumerable<GeneratingChannel> GetGeneratingChannels(this DbSet<GuildConfig> configs)
|
public static IEnumerable<GeneratingChannel> GetGeneratingChannels(this DbSet<GuildConfig> configs)
|
||||||
=> configs.AsQueryable()
|
=> configs.AsQueryable()
|
||||||
.Include(x => x.GenerateCurrencyChannelIds)
|
.Include(x => x.GenerateCurrencyChannelIds)
|
||||||
.Where(x => x.GenerateCurrencyChannelIds.Any())
|
.Where(x => x.GenerateCurrencyChannelIds.Any())
|
||||||
.SelectMany(x => x.GenerateCurrencyChannelIds)
|
.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();
|
.ToArray();
|
||||||
}
|
|
||||||
|
public class GeneratingChannel
|
||||||
|
{
|
||||||
|
public ulong GuildId { get; set; }
|
||||||
|
public ulong ChannelId { get; set; }
|
||||||
|
}
|
||||||
|
}
|
@@ -1,4 +1,4 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using NadekoBot.Services.Database.Models;
|
using NadekoBot.Services.Database.Models;
|
||||||
|
|
||||||
@@ -8,12 +8,11 @@ public static class MusicPlayerSettingsExtensions
|
|||||||
{
|
{
|
||||||
public static async Task<MusicPlayerSettings> ForGuildAsync(this DbSet<MusicPlayerSettings> settings, ulong guildId)
|
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);
|
||||||
.FirstOrDefaultAsync(x => x.GuildId == guildId);
|
|
||||||
|
|
||||||
if (toReturn is null)
|
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);
|
await settings.AddAsync(newSettings);
|
||||||
return newSettings;
|
return newSettings;
|
||||||
@@ -21,4 +20,4 @@ public static class MusicPlayerSettingsExtensions
|
|||||||
|
|
||||||
return toReturn;
|
return toReturn;
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,4 +1,4 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using NadekoBot.Services.Database.Models;
|
using NadekoBot.Services.Database.Models;
|
||||||
|
|
||||||
@@ -11,14 +11,9 @@ public static class MusicPlaylistExtensions
|
|||||||
if (num < 1)
|
if (num < 1)
|
||||||
throw new IndexOutOfRangeException();
|
throw new IndexOutOfRangeException();
|
||||||
|
|
||||||
return playlists.AsQueryable()
|
return playlists.AsQueryable().Skip((num - 1) * 20).Take(20).Include(pl => pl.Songs).ToList();
|
||||||
.Skip((num - 1) * 20)
|
|
||||||
.Take(20)
|
|
||||||
.Include(pl => pl.Songs)
|
|
||||||
.ToList();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static MusicPlaylist GetWithSongs(this DbSet<MusicPlaylist> playlists, int id)
|
public static MusicPlaylist GetWithSongs(this DbSet<MusicPlaylist> playlists, int id)
|
||||||
=> playlists.Include(mpl => mpl.Songs)
|
=> playlists.Include(mpl => mpl.Songs).FirstOrDefault(mpl => mpl.Id == id);
|
||||||
.FirstOrDefault(mpl => mpl.Id == id);
|
}
|
||||||
}
|
|
@@ -1,4 +1,4 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using NadekoBot.Services.Database;
|
using NadekoBot.Services.Database;
|
||||||
using NadekoBot.Services.Database.Models;
|
using NadekoBot.Services.Database.Models;
|
||||||
@@ -8,15 +8,11 @@ namespace NadekoBot.Db;
|
|||||||
public static class PollExtensions
|
public static class PollExtensions
|
||||||
{
|
{
|
||||||
public static IEnumerable<Poll> GetAllPolls(this DbSet<Poll> polls)
|
public static IEnumerable<Poll> GetAllPolls(this DbSet<Poll> polls)
|
||||||
=> polls.Include(x => x.Answers)
|
=> polls.Include(x => x.Answers).Include(x => x.Votes).ToArray();
|
||||||
.Include(x => x.Votes)
|
|
||||||
.ToArray();
|
|
||||||
|
|
||||||
public static void RemovePoll(this NadekoContext ctx, int id)
|
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);
|
||||||
.Include(x => x.Votes)
|
|
||||||
.FirstOrDefault(x => x.Id == id);
|
|
||||||
|
|
||||||
if (p is null)
|
if (p is null)
|
||||||
return;
|
return;
|
||||||
@@ -35,4 +31,4 @@ public static class PollExtensions
|
|||||||
|
|
||||||
ctx.Poll.Remove(p);
|
ctx.Poll.Remove(p);
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,14 +1,13 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
using NadekoBot.Services.Database.Models;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using NadekoBot.Services.Database.Models;
|
||||||
|
|
||||||
namespace NadekoBot.Db;
|
namespace NadekoBot.Db;
|
||||||
|
|
||||||
public static class QuoteExtensions
|
public static class QuoteExtensions
|
||||||
{
|
{
|
||||||
public static IEnumerable<Quote> GetForGuild(this DbSet<Quote> quotes, ulong guildId)
|
public static IEnumerable<Quote> GetForGuild(this DbSet<Quote> quotes, ulong guildId)
|
||||||
=> quotes.AsQueryable()
|
=> quotes.AsQueryable().Where(x => x.GuildId == guildId);
|
||||||
.Where(x => x.GuildId == guildId);
|
|
||||||
|
|
||||||
public static IEnumerable<Quote> GetGroup(
|
public static IEnumerable<Quote> GetGroup(
|
||||||
this DbSet<Quote> quotes,
|
this DbSet<Quote> quotes,
|
||||||
@@ -16,16 +15,13 @@ public static class QuoteExtensions
|
|||||||
int page,
|
int page,
|
||||||
OrderType order)
|
OrderType order)
|
||||||
{
|
{
|
||||||
var q = quotes.AsQueryable()
|
var q = quotes.AsQueryable().Where(x => x.GuildId == guildId);
|
||||||
.Where(x => x.GuildId == guildId);
|
|
||||||
if (order == OrderType.Keyword)
|
if (order == OrderType.Keyword)
|
||||||
q = q.OrderBy(x => x.Keyword);
|
q = q.OrderBy(x => x.Keyword);
|
||||||
else
|
else
|
||||||
q = q.OrderBy(x => x.Id);
|
q = q.OrderBy(x => x.Id);
|
||||||
|
|
||||||
return q.Skip(15 * page)
|
return q.Skip(15 * page).Take(15).ToArray();
|
||||||
.Take(15)
|
|
||||||
.ToArray();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task<Quote> GetRandomQuoteByKeywordAsync(
|
public static async Task<Quote> GetRandomQuoteByKeywordAsync(
|
||||||
@@ -34,10 +30,9 @@ public static class QuoteExtensions
|
|||||||
string keyword)
|
string keyword)
|
||||||
{
|
{
|
||||||
var rng = new NadekoRandom();
|
var rng = new NadekoRandom();
|
||||||
return (await quotes.AsQueryable()
|
return (await quotes.AsQueryable().Where(q => q.GuildId == guildId && q.Keyword == keyword).ToListAsync())
|
||||||
.Where(q => q.GuildId == guildId && q.Keyword == keyword)
|
.OrderBy(q => rng.Next())
|
||||||
.ToListAsync()).OrderBy(q => rng.Next())
|
.FirstOrDefault();
|
||||||
.FirstOrDefault();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task<Quote> SearchQuoteKeywordTextAsync(
|
public static async Task<Quote> SearchQuoteKeywordTextAsync(
|
||||||
@@ -48,17 +43,15 @@ public static class QuoteExtensions
|
|||||||
{
|
{
|
||||||
var rngk = new NadekoRandom();
|
var rngk = new NadekoRandom();
|
||||||
return (await quotes.AsQueryable()
|
return (await quotes.AsQueryable()
|
||||||
.Where(q => q.GuildId == guildId &&
|
.Where(q => q.GuildId == guildId
|
||||||
q.Keyword == keyword &&
|
&& q.Keyword == keyword
|
||||||
EF.Functions.Like(q.Text.ToUpper(), $"%{text.ToUpper()}%")
|
&& EF.Functions.Like(q.Text.ToUpper(), $"%{text.ToUpper()}%")
|
||||||
// && q.Text.Contains(text, StringComparison.OrdinalIgnoreCase)
|
// && q.Text.Contains(text, StringComparison.OrdinalIgnoreCase)
|
||||||
)
|
)
|
||||||
.ToListAsync()).OrderBy(q => rngk.Next())
|
.ToListAsync()).OrderBy(q => rngk.Next())
|
||||||
.FirstOrDefault();
|
.FirstOrDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void RemoveAllByKeyword(this DbSet<Quote> quotes, ulong guildId, string keyword)
|
public static void RemoveAllByKeyword(this DbSet<Quote> quotes, ulong guildId, string keyword)
|
||||||
=> quotes.RemoveRange(quotes.AsQueryable()
|
=> quotes.RemoveRange(quotes.AsQueryable().Where(x => x.GuildId == guildId && x.Keyword.ToUpper() == keyword));
|
||||||
.Where(x => x.GuildId == guildId && x.Keyword.ToUpper() == keyword)
|
}
|
||||||
);
|
|
||||||
}
|
|
@@ -1,6 +1,6 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
using NadekoBot.Services.Database.Models;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using NadekoBot.Services.Database.Models;
|
||||||
|
|
||||||
namespace NadekoBot.Db;
|
namespace NadekoBot.Db;
|
||||||
|
|
||||||
@@ -9,21 +9,15 @@ public static class ReminderExtensions
|
|||||||
public static IEnumerable<Reminder> GetIncludedReminders(
|
public static IEnumerable<Reminder> GetIncludedReminders(
|
||||||
this DbSet<Reminder> reminders,
|
this DbSet<Reminder> reminders,
|
||||||
IEnumerable<ulong> guildIds)
|
IEnumerable<ulong> guildIds)
|
||||||
=> reminders.AsQueryable()
|
=> reminders.AsQueryable().Where(x => guildIds.Contains(x.ServerId) || x.ServerId == 0).ToList();
|
||||||
.Where(x => guildIds.Contains(x.ServerId) || x.ServerId == 0)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
public static IEnumerable<Reminder> RemindersFor(this DbSet<Reminder> reminders, ulong userId, int page)
|
public static IEnumerable<Reminder> RemindersFor(this DbSet<Reminder> reminders, ulong userId, int page)
|
||||||
=> reminders.AsQueryable()
|
=> reminders.AsQueryable().Where(x => x.UserId == userId).OrderBy(x => x.DateAdded).Skip(page * 10).Take(10);
|
||||||
.Where(x => x.UserId == userId)
|
|
||||||
.OrderBy(x => x.DateAdded)
|
|
||||||
.Skip(page * 10)
|
|
||||||
.Take(10);
|
|
||||||
|
|
||||||
public static IEnumerable<Reminder> RemindersForServer(this DbSet<Reminder> reminders, ulong serverId, int page)
|
public static IEnumerable<Reminder> RemindersForServer(this DbSet<Reminder> reminders, ulong serverId, int page)
|
||||||
=> reminders.AsQueryable()
|
=> reminders.AsQueryable()
|
||||||
.Where(x => x.ServerId == serverId)
|
.Where(x => x.ServerId == serverId)
|
||||||
.OrderBy(x => x.DateAdded)
|
.OrderBy(x => x.DateAdded)
|
||||||
.Skip(page * 10)
|
.Skip(page * 10)
|
||||||
.Take(10);
|
.Take(10);
|
||||||
}
|
}
|
@@ -1,6 +1,6 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
using NadekoBot.Services.Database.Models;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using NadekoBot.Services.Database.Models;
|
||||||
|
|
||||||
namespace NadekoBot.Db;
|
namespace NadekoBot.Db;
|
||||||
|
|
||||||
@@ -18,7 +18,5 @@ public static class SelfAssignableRolesExtensions
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static IEnumerable<SelfAssignedRole> GetFromGuild(this DbSet<SelfAssignedRole> roles, ulong guildId)
|
public static IEnumerable<SelfAssignedRole> GetFromGuild(this DbSet<SelfAssignedRole> roles, ulong guildId)
|
||||||
=> roles.AsQueryable()
|
=> roles.AsQueryable().Where(s => s.GuildId == guildId).ToArray();
|
||||||
.Where(s => s.GuildId == guildId)
|
}
|
||||||
.ToArray();
|
|
||||||
}
|
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user