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