mirror of
https://gitlab.com/Kwoth/nadekobot.git
synced 2025-09-10 17:28:27 -04:00
Global usings and file scoped namespaces
This commit is contained in:
@@ -3,6 +3,7 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net6.0</TargetFramework>
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
<LangVersion>10.0</LangVersion>
|
<LangVersion>10.0</LangVersion>
|
||||||
|
<EnablePreviewFeatures>True</EnablePreviewFeatures>
|
||||||
<IsPackable>false</IsPackable>
|
<IsPackable>false</IsPackable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
@@ -6,11 +6,8 @@ using NadekoBot.Common;
|
|||||||
using NadekoBot.Services;
|
using NadekoBot.Services;
|
||||||
using NadekoBot.Services.Database.Models;
|
using NadekoBot.Services.Database.Models;
|
||||||
using NadekoBot.Extensions;
|
using NadekoBot.Extensions;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@@ -19,83 +16,81 @@ using NadekoBot.Common.ModuleBehaviors;
|
|||||||
using NadekoBot.Common.Configs;
|
using NadekoBot.Common.Configs;
|
||||||
using NadekoBot.Db;
|
using NadekoBot.Db;
|
||||||
using NadekoBot.Modules.Administration.Services;
|
using NadekoBot.Modules.Administration.Services;
|
||||||
using NadekoBot.Modules.Searches;
|
|
||||||
using Serilog;
|
|
||||||
|
|
||||||
namespace NadekoBot
|
namespace NadekoBot;
|
||||||
|
|
||||||
|
public sealed class Bot
|
||||||
{
|
{
|
||||||
public sealed class Bot
|
private readonly IBotCredentials _creds;
|
||||||
|
private readonly CommandService _commandService;
|
||||||
|
private readonly DbService _db;
|
||||||
|
private readonly IBotCredsProvider _credsProvider;
|
||||||
|
|
||||||
|
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)
|
||||||
{
|
{
|
||||||
private readonly IBotCredentials _creds;
|
if (shardId < 0)
|
||||||
private readonly CommandService _commandService;
|
throw new ArgumentOutOfRangeException(nameof(shardId));
|
||||||
private readonly DbService _db;
|
|
||||||
private readonly IBotCredsProvider _credsProvider;
|
|
||||||
|
|
||||||
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; }
|
_credsProvider = new BotCredsProvider(totalShards);
|
||||||
|
_creds = _credsProvider.GetCreds();
|
||||||
public string Mention { get; private set; }
|
|
||||||
public bool IsReady { get; private set; }
|
|
||||||
|
|
||||||
public Bot(int shardId, int? totalShards)
|
|
||||||
{
|
|
||||||
if (shardId < 0)
|
|
||||||
throw new ArgumentOutOfRangeException(nameof(shardId));
|
|
||||||
|
|
||||||
_credsProvider = new BotCredsProvider(totalShards);
|
|
||||||
_creds = _credsProvider.GetCreds();
|
|
||||||
|
|
||||||
_db = new DbService(_creds);
|
_db = new DbService(_creds);
|
||||||
|
|
||||||
if (shardId == 0)
|
if (shardId == 0)
|
||||||
{
|
{
|
||||||
_db.Setup();
|
_db.Setup();
|
||||||
}
|
}
|
||||||
|
|
||||||
Client = new DiscordSocketClient(new DiscordSocketConfig
|
Client = new DiscordSocketClient(new DiscordSocketConfig
|
||||||
{
|
{
|
||||||
MessageCacheSize = 50,
|
MessageCacheSize = 50,
|
||||||
LogLevel = LogSeverity.Warning,
|
LogLevel = LogSeverity.Warning,
|
||||||
ConnectionTimeout = int.MaxValue,
|
ConnectionTimeout = int.MaxValue,
|
||||||
TotalShards = _creds.TotalShards,
|
TotalShards = _creds.TotalShards,
|
||||||
ShardId = shardId,
|
ShardId = shardId,
|
||||||
AlwaysDownloadUsers = false,
|
AlwaysDownloadUsers = false,
|
||||||
ExclusiveBulkDelete = true,
|
ExclusiveBulkDelete = true,
|
||||||
});
|
});
|
||||||
|
|
||||||
_commandService = new CommandService(new CommandServiceConfig()
|
_commandService = new CommandService(new CommandServiceConfig()
|
||||||
{
|
{
|
||||||
CaseSensitiveCommands = false,
|
CaseSensitiveCommands = false,
|
||||||
DefaultRunMode = RunMode.Sync,
|
DefaultRunMode = RunMode.Sync,
|
||||||
});
|
});
|
||||||
|
|
||||||
#if GLOBAL_NADEKO || DEBUG
|
#if GLOBAL_NADEKO || DEBUG
|
||||||
Client.Log += Client_Log;
|
Client.Log += Client_Log;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<ulong> GetCurrentGuildIds()
|
public List<ulong> GetCurrentGuildIds()
|
||||||
|
{
|
||||||
|
return Client.Guilds.Select(x => x.Id).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddServices()
|
||||||
|
{
|
||||||
|
var startingGuildIdList = GetCurrentGuildIds();
|
||||||
|
var sw = Stopwatch.StartNew();
|
||||||
|
var _bot = Client.CurrentUser;
|
||||||
|
|
||||||
|
using (var uow = _db.GetDbContext())
|
||||||
{
|
{
|
||||||
return Client.Guilds.Select(x => x.Id).ToList();
|
uow.EnsureUserCreated(_bot.Id, _bot.Username, _bot.Discriminator, _bot.AvatarId);
|
||||||
|
AllGuildConfigs = uow.GuildConfigs.GetAllGuildConfigs(startingGuildIdList).ToImmutableArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AddServices()
|
|
||||||
{
|
|
||||||
var startingGuildIdList = GetCurrentGuildIds();
|
|
||||||
var sw = Stopwatch.StartNew();
|
|
||||||
var _bot = Client.CurrentUser;
|
|
||||||
|
|
||||||
using (var uow = _db.GetDbContext())
|
|
||||||
{
|
|
||||||
uow.EnsureUserCreated(_bot.Id, _bot.Username, _bot.Discriminator, _bot.AvatarId);
|
|
||||||
AllGuildConfigs = uow.GuildConfigs.GetAllGuildConfigs(startingGuildIdList).ToImmutableArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
var svcs = new ServiceCollection()
|
var svcs = new ServiceCollection()
|
||||||
.AddTransient<IBotCredentials>(_ => _credsProvider.GetCreds()) // bot creds
|
.AddTransient<IBotCredentials>(_ => _credsProvider.GetCreds()) // bot creds
|
||||||
.AddSingleton<IBotCredsProvider>(_credsProvider)
|
.AddSingleton<IBotCredsProvider>(_credsProvider)
|
||||||
.AddSingleton(_db) // database
|
.AddSingleton(_db) // database
|
||||||
@@ -118,248 +113,247 @@ namespace NadekoBot
|
|||||||
#else
|
#else
|
||||||
.AddSingleton<ILogCommandService, LogCommandService>()
|
.AddSingleton<ILogCommandService, LogCommandService>()
|
||||||
#endif
|
#endif
|
||||||
;
|
;
|
||||||
|
|
||||||
svcs.AddHttpClient();
|
svcs.AddHttpClient();
|
||||||
svcs.AddHttpClient("memelist").ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler
|
svcs.AddHttpClient("memelist").ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler
|
||||||
{
|
{
|
||||||
AllowAutoRedirect = false
|
AllowAutoRedirect = false
|
||||||
});
|
});
|
||||||
|
|
||||||
if (Environment.GetEnvironmentVariable("NADEKOBOT_IS_COORDINATED") != "1")
|
if (Environment.GetEnvironmentVariable("NADEKOBOT_IS_COORDINATED") != "1")
|
||||||
{
|
{
|
||||||
svcs.AddSingleton<ICoordinator, SingleProcessCoordinator>();
|
svcs.AddSingleton<ICoordinator, SingleProcessCoordinator>();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
svcs.AddSingleton<RemoteGrpcCoordinator>()
|
svcs.AddSingleton<RemoteGrpcCoordinator>()
|
||||||
.AddSingleton<ICoordinator>(x => x.GetRequiredService<RemoteGrpcCoordinator>())
|
.AddSingleton<ICoordinator>(x => x.GetRequiredService<RemoteGrpcCoordinator>())
|
||||||
.AddSingleton<IReadyExecutor>(x => x.GetRequiredService<RemoteGrpcCoordinator>());
|
.AddSingleton<IReadyExecutor>(x => x.GetRequiredService<RemoteGrpcCoordinator>());
|
||||||
}
|
}
|
||||||
|
|
||||||
svcs.AddSingleton<RedisLocalDataCache>()
|
svcs.AddSingleton<RedisLocalDataCache>()
|
||||||
.AddSingleton<ILocalDataCache>(x => x.GetRequiredService<RedisLocalDataCache>())
|
.AddSingleton<ILocalDataCache>(x => x.GetRequiredService<RedisLocalDataCache>())
|
||||||
.AddSingleton<RedisImagesCache>()
|
.AddSingleton<RedisImagesCache>()
|
||||||
.AddSingleton<IImageCache>(x => x.GetRequiredService<RedisImagesCache>())
|
.AddSingleton<IImageCache>(x => x.GetRequiredService<RedisImagesCache>())
|
||||||
.AddSingleton<IReadyExecutor>(x => x.GetRequiredService<RedisImagesCache>())
|
.AddSingleton<IReadyExecutor>(x => x.GetRequiredService<RedisImagesCache>())
|
||||||
.AddSingleton<IDataCache, RedisCache>();
|
.AddSingleton<IDataCache, RedisCache>();
|
||||||
|
|
||||||
svcs.Scan(scan => scan
|
svcs.Scan(scan => scan
|
||||||
.FromAssemblyOf<IReadyExecutor>()
|
.FromAssemblyOf<IReadyExecutor>()
|
||||||
.AddClasses(classes => classes
|
.AddClasses(classes => classes
|
||||||
.AssignableToAny(
|
.AssignableToAny(
|
||||||
// services
|
// services
|
||||||
typeof(INService),
|
typeof(INService),
|
||||||
|
|
||||||
// behaviours
|
// behaviours
|
||||||
typeof(IEarlyBehavior),
|
typeof(IEarlyBehavior),
|
||||||
typeof(ILateBlocker),
|
typeof(ILateBlocker),
|
||||||
typeof(IInputTransformer),
|
typeof(IInputTransformer),
|
||||||
typeof(ILateExecutor))
|
typeof(ILateExecutor))
|
||||||
#if GLOBAL_NADEKO
|
#if GLOBAL_NADEKO
|
||||||
.WithoutAttribute<NoPublicBotAttribute>()
|
.WithoutAttribute<NoPublicBotAttribute>()
|
||||||
#endif
|
#endif
|
||||||
)
|
)
|
||||||
.AsSelfWithInterfaces()
|
.AsSelfWithInterfaces()
|
||||||
.WithSingletonLifetime()
|
.WithSingletonLifetime()
|
||||||
);
|
);
|
||||||
|
|
||||||
//initialize Services
|
//initialize Services
|
||||||
Services = svcs.BuildServiceProvider();
|
Services = svcs.BuildServiceProvider();
|
||||||
var exec = Services.GetRequiredService<IBehaviourExecutor>();
|
var exec = Services.GetRequiredService<IBehaviourExecutor>();
|
||||||
exec.Initialize();
|
exec.Initialize();
|
||||||
|
|
||||||
if (Client.ShardId == 0)
|
if (Client.ShardId == 0)
|
||||||
{
|
{
|
||||||
ApplyConfigMigrations();
|
ApplyConfigMigrations();
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = LoadTypeReaders(typeof(Bot).Assembly);
|
_ = LoadTypeReaders(typeof(Bot).Assembly);
|
||||||
|
|
||||||
sw.Stop();
|
sw.Stop();
|
||||||
Log.Information($"All services loaded in {sw.Elapsed.TotalSeconds:F2}s");
|
Log.Information($"All services loaded in {sw.Elapsed.TotalSeconds:F2}s");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ApplyConfigMigrations()
|
private void ApplyConfigMigrations()
|
||||||
|
{
|
||||||
|
// execute all migrators
|
||||||
|
var migrators = Services.GetServices<IConfigMigrator>();
|
||||||
|
foreach (var migrator in migrators)
|
||||||
{
|
{
|
||||||
// execute all migrators
|
migrator.EnsureMigrated();
|
||||||
var migrators = Services.GetServices<IConfigMigrator>();
|
|
||||||
foreach (var migrator in migrators)
|
|
||||||
{
|
|
||||||
migrator.EnsureMigrated();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private IEnumerable<object> LoadTypeReaders(Assembly assembly)
|
|
||||||
{
|
|
||||||
Type[] allTypes;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
allTypes = assembly.GetTypes();
|
|
||||||
}
|
|
||||||
catch (ReflectionTypeLoadException ex)
|
|
||||||
{
|
|
||||||
Log.Warning(ex.LoaderExceptions[0], "Error getting types");
|
|
||||||
return Enumerable.Empty<object>();
|
|
||||||
}
|
|
||||||
var filteredTypes = allTypes
|
|
||||||
.Where(x => x.IsSubclassOf(typeof(TypeReader))
|
|
||||||
&& x.BaseType.GetGenericArguments().Length > 0
|
|
||||||
&& !x.IsAbstract);
|
|
||||||
|
|
||||||
var toReturn = new List<object>();
|
|
||||||
foreach (var ft in filteredTypes)
|
|
||||||
{
|
|
||||||
var x = (TypeReader)ActivatorUtilities.CreateInstance(Services, ft);
|
|
||||||
var baseType = ft.BaseType;
|
|
||||||
var typeArgs = baseType.GetGenericArguments();
|
|
||||||
_commandService.AddTypeReader(typeArgs[0], x);
|
|
||||||
toReturn.Add(x);
|
|
||||||
}
|
|
||||||
|
|
||||||
return toReturn;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task LoginAsync(string token)
|
|
||||||
{
|
|
||||||
var clientReady = new TaskCompletionSource<bool>();
|
|
||||||
|
|
||||||
Task SetClientReady()
|
|
||||||
{
|
|
||||||
var _ = Task.Run(async () =>
|
|
||||||
{
|
|
||||||
clientReady.TrySetResult(true);
|
|
||||||
try
|
|
||||||
{
|
|
||||||
foreach (var chan in (await Client.GetDMChannelsAsync().ConfigureAwait(false)))
|
|
||||||
{
|
|
||||||
await chan.CloseAsync().ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
// ignored
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
//connect
|
|
||||||
Log.Information("Shard {ShardId} logging in ...", Client.ShardId);
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await Client.LoginAsync(TokenType.Bot, token).ConfigureAwait(false);
|
|
||||||
await Client.StartAsync().ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
catch (HttpException ex)
|
|
||||||
{
|
|
||||||
LoginErrorHandler.Handle(ex);
|
|
||||||
Helpers.ReadErrorAndExit(3);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
LoginErrorHandler.Handle(ex);
|
|
||||||
Helpers.ReadErrorAndExit(4);
|
|
||||||
}
|
|
||||||
|
|
||||||
Client.Ready += SetClientReady;
|
|
||||||
await clientReady.Task.ConfigureAwait(false);
|
|
||||||
Client.Ready -= SetClientReady;
|
|
||||||
|
|
||||||
Client.JoinedGuild += Client_JoinedGuild;
|
|
||||||
Client.LeftGuild += Client_LeftGuild;
|
|
||||||
|
|
||||||
Log.Information("Shard {0} logged in.", Client.ShardId);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Task Client_LeftGuild(SocketGuild arg)
|
|
||||||
{
|
|
||||||
Log.Information("Left server: {0} [{1}]", arg?.Name, arg?.Id);
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Task Client_JoinedGuild(SocketGuild arg)
|
|
||||||
{
|
|
||||||
Log.Information($"Joined server: {0} [{1}]", arg.Name, arg.Id);
|
|
||||||
var _ = Task.Run(async () =>
|
|
||||||
{
|
|
||||||
GuildConfig gc;
|
|
||||||
using (var uow = _db.GetDbContext())
|
|
||||||
{
|
|
||||||
gc = uow.GuildConfigsForId(arg.Id);
|
|
||||||
}
|
|
||||||
await JoinedGuild.Invoke(gc).ConfigureAwait(false);
|
|
||||||
});
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task RunAsync()
|
|
||||||
{
|
|
||||||
var sw = Stopwatch.StartNew();
|
|
||||||
|
|
||||||
await LoginAsync(_creds.Token).ConfigureAwait(false);
|
|
||||||
|
|
||||||
Mention = Client.CurrentUser.Mention;
|
|
||||||
Log.Information("Shard {ShardId} loading services...", Client.ShardId);
|
|
||||||
try
|
|
||||||
{
|
|
||||||
AddServices();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Log.Error(ex, "Error adding services");
|
|
||||||
Helpers.ReadErrorAndExit(9);
|
|
||||||
}
|
|
||||||
|
|
||||||
sw.Stop();
|
|
||||||
Log.Information("Shard {ShardId} connected in {Elapsed:F2}s", Client.ShardId, sw.Elapsed.TotalSeconds);
|
|
||||||
var commandHandler = Services.GetRequiredService<CommandHandler>();
|
|
||||||
|
|
||||||
// start handling messages received in commandhandler
|
|
||||||
await commandHandler.StartHandling().ConfigureAwait(false);
|
|
||||||
|
|
||||||
await _commandService.AddModulesAsync(typeof(Bot).Assembly, Services);
|
|
||||||
|
|
||||||
IsReady = true;
|
|
||||||
_ = Task.Run(ExecuteReadySubscriptions);
|
|
||||||
Log.Information("Shard {ShardId} ready", Client.ShardId);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Task ExecuteReadySubscriptions()
|
|
||||||
{
|
|
||||||
var readyExecutors = Services.GetServices<IReadyExecutor>();
|
|
||||||
var tasks = readyExecutors.Select(async toExec =>
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await toExec.OnReadyAsync();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Log.Error(ex,
|
|
||||||
"Failed running OnReadyAsync method on {Type} type: {Message}",
|
|
||||||
toExec.GetType().Name,
|
|
||||||
ex.Message);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return Task.WhenAll(tasks);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Task Client_Log(LogMessage arg)
|
|
||||||
{
|
|
||||||
if (arg.Exception != null)
|
|
||||||
Log.Warning(arg.Exception, arg.Source + " | " + arg.Message);
|
|
||||||
else
|
|
||||||
Log.Warning(arg.Source + " | " + arg.Message);
|
|
||||||
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task RunAndBlockAsync()
|
|
||||||
{
|
|
||||||
await RunAsync().ConfigureAwait(false);
|
|
||||||
await Task.Delay(-1).ConfigureAwait(false);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
private IEnumerable<object> LoadTypeReaders(Assembly assembly)
|
||||||
|
{
|
||||||
|
Type[] allTypes;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
allTypes = assembly.GetTypes();
|
||||||
|
}
|
||||||
|
catch (ReflectionTypeLoadException ex)
|
||||||
|
{
|
||||||
|
Log.Warning(ex.LoaderExceptions[0], "Error getting types");
|
||||||
|
return Enumerable.Empty<object>();
|
||||||
|
}
|
||||||
|
var filteredTypes = allTypes
|
||||||
|
.Where(x => x.IsSubclassOf(typeof(TypeReader))
|
||||||
|
&& x.BaseType.GetGenericArguments().Length > 0
|
||||||
|
&& !x.IsAbstract);
|
||||||
|
|
||||||
|
var toReturn = new List<object>();
|
||||||
|
foreach (var ft in filteredTypes)
|
||||||
|
{
|
||||||
|
var x = (TypeReader)ActivatorUtilities.CreateInstance(Services, ft);
|
||||||
|
var baseType = ft.BaseType;
|
||||||
|
var typeArgs = baseType.GetGenericArguments();
|
||||||
|
_commandService.AddTypeReader(typeArgs[0], x);
|
||||||
|
toReturn.Add(x);
|
||||||
|
}
|
||||||
|
|
||||||
|
return toReturn;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task LoginAsync(string token)
|
||||||
|
{
|
||||||
|
var clientReady = new TaskCompletionSource<bool>();
|
||||||
|
|
||||||
|
Task SetClientReady()
|
||||||
|
{
|
||||||
|
var _ = Task.Run(async () =>
|
||||||
|
{
|
||||||
|
clientReady.TrySetResult(true);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
foreach (var chan in (await Client.GetDMChannelsAsync().ConfigureAwait(false)))
|
||||||
|
{
|
||||||
|
await chan.CloseAsync().ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// ignored
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
//connect
|
||||||
|
Log.Information("Shard {ShardId} logging in ...", Client.ShardId);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await Client.LoginAsync(TokenType.Bot, token).ConfigureAwait(false);
|
||||||
|
await Client.StartAsync().ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
catch (HttpException ex)
|
||||||
|
{
|
||||||
|
LoginErrorHandler.Handle(ex);
|
||||||
|
Helpers.ReadErrorAndExit(3);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LoginErrorHandler.Handle(ex);
|
||||||
|
Helpers.ReadErrorAndExit(4);
|
||||||
|
}
|
||||||
|
|
||||||
|
Client.Ready += SetClientReady;
|
||||||
|
await clientReady.Task.ConfigureAwait(false);
|
||||||
|
Client.Ready -= SetClientReady;
|
||||||
|
|
||||||
|
Client.JoinedGuild += Client_JoinedGuild;
|
||||||
|
Client.LeftGuild += Client_LeftGuild;
|
||||||
|
|
||||||
|
Log.Information("Shard {0} logged in.", Client.ShardId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task Client_LeftGuild(SocketGuild arg)
|
||||||
|
{
|
||||||
|
Log.Information("Left server: {0} [{1}]", arg?.Name, arg?.Id);
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task Client_JoinedGuild(SocketGuild arg)
|
||||||
|
{
|
||||||
|
Log.Information($"Joined server: {0} [{1}]", arg.Name, arg.Id);
|
||||||
|
var _ = Task.Run(async () =>
|
||||||
|
{
|
||||||
|
GuildConfig gc;
|
||||||
|
using (var uow = _db.GetDbContext())
|
||||||
|
{
|
||||||
|
gc = uow.GuildConfigsForId(arg.Id);
|
||||||
|
}
|
||||||
|
await JoinedGuild.Invoke(gc).ConfigureAwait(false);
|
||||||
|
});
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task RunAsync()
|
||||||
|
{
|
||||||
|
var sw = Stopwatch.StartNew();
|
||||||
|
|
||||||
|
await LoginAsync(_creds.Token).ConfigureAwait(false);
|
||||||
|
|
||||||
|
Mention = Client.CurrentUser.Mention;
|
||||||
|
Log.Information("Shard {ShardId} loading services...", Client.ShardId);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
AddServices();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Error(ex, "Error adding services");
|
||||||
|
Helpers.ReadErrorAndExit(9);
|
||||||
|
}
|
||||||
|
|
||||||
|
sw.Stop();
|
||||||
|
Log.Information("Shard {ShardId} connected in {Elapsed:F2}s", Client.ShardId, sw.Elapsed.TotalSeconds);
|
||||||
|
var commandHandler = Services.GetRequiredService<CommandHandler>();
|
||||||
|
|
||||||
|
// start handling messages received in commandhandler
|
||||||
|
await commandHandler.StartHandling().ConfigureAwait(false);
|
||||||
|
|
||||||
|
await _commandService.AddModulesAsync(typeof(Bot).Assembly, Services);
|
||||||
|
|
||||||
|
IsReady = true;
|
||||||
|
_ = Task.Run(ExecuteReadySubscriptions);
|
||||||
|
Log.Information("Shard {ShardId} ready", Client.ShardId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task ExecuteReadySubscriptions()
|
||||||
|
{
|
||||||
|
var readyExecutors = Services.GetServices<IReadyExecutor>();
|
||||||
|
var tasks = readyExecutors.Select(async toExec =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await toExec.OnReadyAsync();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Error(ex,
|
||||||
|
"Failed running OnReadyAsync method on {Type} type: {Message}",
|
||||||
|
toExec.GetType().Name,
|
||||||
|
ex.Message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return Task.WhenAll(tasks);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task Client_Log(LogMessage arg)
|
||||||
|
{
|
||||||
|
if (arg.Exception != null)
|
||||||
|
Log.Warning(arg.Exception, arg.Source + " | " + arg.Message);
|
||||||
|
else
|
||||||
|
Log.Warning(arg.Source + " | " + arg.Message);
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task RunAndBlockAsync()
|
||||||
|
{
|
||||||
|
await RunAsync().ConfigureAwait(false);
|
||||||
|
await Task.Delay(-1).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
@@ -1,20 +1,17 @@
|
|||||||
using System;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace NadekoBot.Common
|
namespace NadekoBot.Common;
|
||||||
|
|
||||||
|
public class AsyncLazy<T> : Lazy<Task<T>>
|
||||||
{
|
{
|
||||||
public class AsyncLazy<T> : Lazy<Task<T>>
|
public AsyncLazy(Func<T> valueFactory) :
|
||||||
{
|
base(() => Task.Run(valueFactory))
|
||||||
public AsyncLazy(Func<T> valueFactory) :
|
{ }
|
||||||
base(() => Task.Run(valueFactory))
|
|
||||||
{ }
|
|
||||||
|
|
||||||
public AsyncLazy(Func<Task<T>> taskFactory) :
|
public AsyncLazy(Func<Task<T>> taskFactory) :
|
||||||
base(() => Task.Run(taskFactory))
|
base(() => Task.Run(taskFactory))
|
||||||
{ }
|
{ }
|
||||||
|
|
||||||
public TaskAwaiter<T> GetAwaiter() { return Value.GetAwaiter(); }
|
public TaskAwaiter<T> GetAwaiter() { return Value.GetAwaiter(); }
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
|
@@ -1,18 +1,13 @@
|
|||||||
using System;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
using Discord.Commands;
|
using Discord.Commands;
|
||||||
using NadekoBot.Services;
|
|
||||||
namespace NadekoBot.Common.Attributes
|
namespace NadekoBot.Common.Attributes;
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Method)]
|
||||||
|
public sealed class AliasesAttribute : AliasAttribute
|
||||||
{
|
{
|
||||||
[AttributeUsage(AttributeTargets.Method)]
|
public AliasesAttribute([CallerMemberName] string memberName = "")
|
||||||
public sealed class AliasesAttribute : AliasAttribute
|
: base(CommandNameLoadHelper.GetAliasesFor(memberName))
|
||||||
{
|
{
|
||||||
public AliasesAttribute([CallerMemberName] string memberName = "")
|
|
||||||
: base(CommandNameLoadHelper.GetAliasesFor(memberName))
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,15 +1,14 @@
|
|||||||
using Discord.Commands;
|
using Discord.Commands;
|
||||||
|
|
||||||
namespace Discord
|
namespace Discord;
|
||||||
{
|
|
||||||
public class BotPermAttribute : RequireBotPermissionAttribute
|
|
||||||
{
|
|
||||||
public BotPermAttribute(GuildPerm permission) : base((GuildPermission)permission)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public BotPermAttribute(ChannelPerm permission) : base((ChannelPermission)permission)
|
public class BotPermAttribute : RequireBotPermissionAttribute
|
||||||
{
|
{
|
||||||
}
|
public BotPermAttribute(GuildPerm permission) : base((GuildPermission)permission)
|
||||||
|
{
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
public BotPermAttribute(ChannelPerm permission) : base((ChannelPermission)permission)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
@@ -1,36 +1,32 @@
|
|||||||
using System;
|
using System.IO;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace NadekoBot.Common.Attributes
|
namespace NadekoBot.Common.Attributes;
|
||||||
|
|
||||||
|
public static class CommandNameLoadHelper
|
||||||
{
|
{
|
||||||
public static class CommandNameLoadHelper
|
|
||||||
|
private static YamlDotNet.Serialization.IDeserializer _deserializer
|
||||||
|
= new YamlDotNet.Serialization.Deserializer();
|
||||||
|
|
||||||
|
public static Lazy<Dictionary<string, string[]>> LazyCommandAliases
|
||||||
|
= new Lazy<Dictionary<string, string[]>>(() => LoadCommandNames());
|
||||||
|
public static Dictionary<string, string[]> LoadCommandNames(string aliasesFilePath = "data/aliases.yml")
|
||||||
{
|
{
|
||||||
|
var text = File.ReadAllText(aliasesFilePath);
|
||||||
private static YamlDotNet.Serialization.IDeserializer _deserializer
|
return _deserializer.Deserialize<Dictionary<string, string[]>>(text);
|
||||||
= new YamlDotNet.Serialization.Deserializer();
|
}
|
||||||
|
|
||||||
public static Lazy<Dictionary<string, string[]>> LazyCommandAliases
|
|
||||||
= new Lazy<Dictionary<string, string[]>>(() => LoadCommandNames());
|
|
||||||
public static Dictionary<string, string[]> LoadCommandNames(string aliasesFilePath = "data/aliases.yml")
|
|
||||||
{
|
|
||||||
var text = File.ReadAllText(aliasesFilePath);
|
|
||||||
return _deserializer.Deserialize<Dictionary<string, string[]>>(text);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string[] GetAliasesFor(string methodName)
|
public static string[] GetAliasesFor(string methodName)
|
||||||
=> LazyCommandAliases.Value.TryGetValue(methodName.ToLowerInvariant(), out var aliases) && aliases.Length > 1
|
=> LazyCommandAliases.Value.TryGetValue(methodName.ToLowerInvariant(), out var aliases) && aliases.Length > 1
|
||||||
? aliases.Skip(1).ToArray()
|
? aliases.Skip(1).ToArray()
|
||||||
: Array.Empty<string>();
|
: Array.Empty<string>();
|
||||||
|
|
||||||
public static string GetCommandNameFor(string methodName)
|
public static string GetCommandNameFor(string methodName)
|
||||||
{
|
{
|
||||||
methodName = methodName.ToLowerInvariant();
|
methodName = methodName.ToLowerInvariant();
|
||||||
var toReturn = LazyCommandAliases.Value.TryGetValue(methodName, out var aliases) && aliases.Length > 0
|
var toReturn = LazyCommandAliases.Value.TryGetValue(methodName, out var aliases) && aliases.Length > 0
|
||||||
? aliases[0]
|
? aliases[0]
|
||||||
: methodName;
|
: methodName;
|
||||||
return toReturn;
|
return toReturn;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,16 +1,12 @@
|
|||||||
using System;
|
using Discord.Commands;
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
using Discord.Commands;
|
|
||||||
using NadekoBot.Services;
|
|
||||||
|
|
||||||
namespace NadekoBot.Common.Attributes
|
namespace NadekoBot.Common.Attributes;
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Method)]
|
||||||
|
public sealed class DescriptionAttribute : SummaryAttribute
|
||||||
{
|
{
|
||||||
[AttributeUsage(AttributeTargets.Method)]
|
// Localization.LoadCommand(memberName.ToLowerInvariant()).Desc
|
||||||
public sealed class DescriptionAttribute : SummaryAttribute
|
public DescriptionAttribute(string text = "") : base(text)
|
||||||
{
|
{
|
||||||
// Localization.LoadCommand(memberName.ToLowerInvariant()).Desc
|
|
||||||
public DescriptionAttribute(string text = "") : base(text)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,9 +1,8 @@
|
|||||||
namespace Discord.Commands
|
namespace Discord.Commands;
|
||||||
|
|
||||||
|
public class LeftoverAttribute : RemainderAttribute
|
||||||
{
|
{
|
||||||
public class LeftoverAttribute : RemainderAttribute
|
public LeftoverAttribute()
|
||||||
{
|
{
|
||||||
public LeftoverAttribute()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,19 +1,16 @@
|
|||||||
using System;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
using Discord.Commands;
|
using Discord.Commands;
|
||||||
using NadekoBot.Services;
|
|
||||||
|
|
||||||
namespace NadekoBot.Common.Attributes
|
namespace NadekoBot.Common.Attributes;
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Method)]
|
||||||
|
public sealed class NadekoCommandAttribute : CommandAttribute
|
||||||
{
|
{
|
||||||
[AttributeUsage(AttributeTargets.Method)]
|
public NadekoCommandAttribute([CallerMemberName] string memberName="")
|
||||||
public sealed class NadekoCommandAttribute : CommandAttribute
|
: base(CommandNameLoadHelper.GetCommandNameFor(memberName))
|
||||||
{
|
{
|
||||||
public NadekoCommandAttribute([CallerMemberName] string memberName="")
|
this.MethodName = memberName.ToLowerInvariant();
|
||||||
: base(CommandNameLoadHelper.GetCommandNameFor(memberName))
|
|
||||||
{
|
|
||||||
this.MethodName = memberName.ToLowerInvariant();
|
|
||||||
}
|
|
||||||
|
|
||||||
public string MethodName { get; }
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
public string MethodName { get; }
|
||||||
|
}
|
@@ -1,14 +1,11 @@
|
|||||||
using System;
|
using Discord.Commands;
|
||||||
using Discord.Commands;
|
|
||||||
|
|
||||||
namespace NadekoBot.Common.Attributes
|
namespace NadekoBot.Common.Attributes;
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Class)]
|
||||||
|
sealed class NadekoModuleAttribute : GroupAttribute
|
||||||
{
|
{
|
||||||
[AttributeUsage(AttributeTargets.Class)]
|
public NadekoModuleAttribute(string moduleName) : base(moduleName)
|
||||||
sealed class NadekoModuleAttribute : GroupAttribute
|
|
||||||
{
|
{
|
||||||
public NadekoModuleAttribute(string moduleName) : base(moduleName)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@@ -1,15 +1,12 @@
|
|||||||
using System;
|
namespace NadekoBot.Common.Attributes;
|
||||||
|
|
||||||
namespace NadekoBot.Common.Attributes
|
[AttributeUsage(AttributeTargets.Method)]
|
||||||
|
public sealed class NadekoOptionsAttribute : Attribute
|
||||||
{
|
{
|
||||||
[AttributeUsage(AttributeTargets.Method)]
|
public Type OptionType { get; set; }
|
||||||
public sealed class NadekoOptionsAttribute : Attribute
|
|
||||||
{
|
|
||||||
public Type OptionType { get; set; }
|
|
||||||
|
|
||||||
public NadekoOptionsAttribute(Type t)
|
public NadekoOptionsAttribute(Type t)
|
||||||
{
|
{
|
||||||
this.OptionType = t;
|
this.OptionType = t;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,20 +1,18 @@
|
|||||||
using System;
|
using System.Threading.Tasks;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Discord.Commands;
|
using Discord.Commands;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using NadekoBot.Extensions;
|
using NadekoBot.Extensions;
|
||||||
using NadekoBot.Services;
|
using NadekoBot.Services;
|
||||||
|
|
||||||
namespace NadekoBot.Common.Attributes
|
namespace NadekoBot.Common.Attributes;
|
||||||
{
|
|
||||||
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
|
|
||||||
public sealed class OwnerOnlyAttribute : PreconditionAttribute
|
|
||||||
{
|
|
||||||
public override Task<PreconditionResult> CheckPermissionsAsync(ICommandContext context, CommandInfo executingCommand, IServiceProvider services)
|
|
||||||
{
|
|
||||||
var creds = services.GetRequiredService<IBotCredsProvider>().GetCreds();
|
|
||||||
|
|
||||||
return Task.FromResult((creds.IsOwner(context.User) || context.Client.CurrentUser.Id == context.User.Id ? PreconditionResult.FromSuccess() : PreconditionResult.FromError("Not owner")));
|
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
|
||||||
}
|
public sealed class OwnerOnlyAttribute : PreconditionAttribute
|
||||||
|
{
|
||||||
|
public override Task<PreconditionResult> CheckPermissionsAsync(ICommandContext context, CommandInfo executingCommand, IServiceProvider services)
|
||||||
|
{
|
||||||
|
var creds = services.GetRequiredService<IBotCredsProvider>().GetCreds();
|
||||||
|
|
||||||
|
return Task.FromResult((creds.IsOwner(context.User) || context.Client.CurrentUser.Id == context.User.Id ? PreconditionResult.FromSuccess() : PreconditionResult.FromError("Not owner")));
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,38 +1,36 @@
|
|||||||
using Discord.Commands;
|
using Discord.Commands;
|
||||||
using NadekoBot.Services;
|
using NadekoBot.Services;
|
||||||
using System;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
namespace NadekoBot.Common.Attributes
|
namespace NadekoBot.Common.Attributes;
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Method)]
|
||||||
|
public sealed class RatelimitAttribute : PreconditionAttribute
|
||||||
{
|
{
|
||||||
[AttributeUsage(AttributeTargets.Method)]
|
public int Seconds { get; }
|
||||||
public sealed class RatelimitAttribute : PreconditionAttribute
|
|
||||||
|
public RatelimitAttribute(int seconds)
|
||||||
{
|
{
|
||||||
public int Seconds { get; }
|
if (seconds <= 0)
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(seconds));
|
||||||
|
|
||||||
public RatelimitAttribute(int seconds)
|
Seconds = seconds;
|
||||||
{
|
|
||||||
if (seconds <= 0)
|
|
||||||
throw new ArgumentOutOfRangeException(nameof(seconds));
|
|
||||||
|
|
||||||
Seconds = seconds;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override Task<PreconditionResult> CheckPermissionsAsync(ICommandContext context, CommandInfo command, IServiceProvider services)
|
|
||||||
{
|
|
||||||
if (Seconds == 0)
|
|
||||||
return Task.FromResult(PreconditionResult.FromSuccess());
|
|
||||||
|
|
||||||
var cache = services.GetRequiredService<IDataCache>();
|
|
||||||
var rem = cache.TryAddRatelimit(context.User.Id, command.Name, Seconds);
|
|
||||||
|
|
||||||
if(rem is null)
|
|
||||||
return Task.FromResult(PreconditionResult.FromSuccess());
|
|
||||||
|
|
||||||
var msgContent = $"You can use this command again in {rem.Value.TotalSeconds:F1}s.";
|
|
||||||
|
|
||||||
return Task.FromResult(PreconditionResult.FromError(msgContent));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
public override Task<PreconditionResult> CheckPermissionsAsync(ICommandContext context, CommandInfo command, IServiceProvider services)
|
||||||
|
{
|
||||||
|
if (Seconds == 0)
|
||||||
|
return Task.FromResult(PreconditionResult.FromSuccess());
|
||||||
|
|
||||||
|
var cache = services.GetRequiredService<IDataCache>();
|
||||||
|
var rem = cache.TryAddRatelimit(context.User.Id, command.Name, Seconds);
|
||||||
|
|
||||||
|
if(rem is null)
|
||||||
|
return Task.FromResult(PreconditionResult.FromSuccess());
|
||||||
|
|
||||||
|
var msgContent = $"You can use this command again in {rem.Value.TotalSeconds:F1}s.";
|
||||||
|
|
||||||
|
return Task.FromResult(PreconditionResult.FromError(msgContent));
|
||||||
|
}
|
||||||
|
}
|
@@ -1,21 +1,16 @@
|
|||||||
using System;
|
using Discord.Commands;
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
using Discord.Commands;
|
|
||||||
using NadekoBot.Services;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
|
|
||||||
namespace NadekoBot.Common.Attributes
|
namespace NadekoBot.Common.Attributes;
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Method)]
|
||||||
|
public sealed class UsageAttribute : RemarksAttribute
|
||||||
{
|
{
|
||||||
[AttributeUsage(AttributeTargets.Method)]
|
// public static string GetUsage(string memberName)
|
||||||
public sealed class UsageAttribute : RemarksAttribute
|
// {
|
||||||
|
// var usage = Localization.LoadCommand(memberName.ToLowerInvariant()).Usage;
|
||||||
|
// return JsonConvert.SerializeObject(usage);
|
||||||
|
// }
|
||||||
|
public UsageAttribute(string text = "") : base(text)
|
||||||
{
|
{
|
||||||
// public static string GetUsage(string memberName)
|
|
||||||
// {
|
|
||||||
// var usage = Localization.LoadCommand(memberName.ToLowerInvariant()).Usage;
|
|
||||||
// return JsonConvert.SerializeObject(usage);
|
|
||||||
// }
|
|
||||||
public UsageAttribute(string text = "") : base(text)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,33 +1,31 @@
|
|||||||
using System;
|
using System.Threading.Tasks;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Discord.Commands;
|
using Discord.Commands;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using NadekoBot.Modules.Administration.Services;
|
using NadekoBot.Modules.Administration.Services;
|
||||||
|
|
||||||
namespace Discord
|
namespace Discord;
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
|
||||||
|
public class UserPermAttribute : PreconditionAttribute
|
||||||
{
|
{
|
||||||
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
|
public RequireUserPermissionAttribute UserPermissionAttribute { get; }
|
||||||
public class UserPermAttribute : PreconditionAttribute
|
|
||||||
|
public UserPermAttribute(GuildPerm permission)
|
||||||
{
|
{
|
||||||
public RequireUserPermissionAttribute UserPermissionAttribute { get; }
|
UserPermissionAttribute = new RequireUserPermissionAttribute((GuildPermission)permission);
|
||||||
|
|
||||||
public UserPermAttribute(GuildPerm permission)
|
|
||||||
{
|
|
||||||
UserPermissionAttribute = new RequireUserPermissionAttribute((GuildPermission)permission);
|
|
||||||
}
|
|
||||||
|
|
||||||
public UserPermAttribute(ChannelPerm permission)
|
|
||||||
{
|
|
||||||
UserPermissionAttribute = new RequireUserPermissionAttribute((ChannelPermission)permission);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override Task<PreconditionResult> CheckPermissionsAsync(ICommandContext context, CommandInfo command, IServiceProvider services)
|
|
||||||
{
|
|
||||||
var permService = services.GetRequiredService<DiscordPermOverrideService>();
|
|
||||||
if (permService.TryGetOverrides(context.Guild?.Id ?? 0, command.Name.ToUpperInvariant(), out var _))
|
|
||||||
return Task.FromResult(PreconditionResult.FromSuccess());
|
|
||||||
|
|
||||||
return UserPermissionAttribute.CheckPermissionsAsync(context, command, services);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
public UserPermAttribute(ChannelPerm permission)
|
||||||
|
{
|
||||||
|
UserPermissionAttribute = new RequireUserPermissionAttribute((ChannelPermission)permission);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Task<PreconditionResult> CheckPermissionsAsync(ICommandContext context, CommandInfo command, IServiceProvider services)
|
||||||
|
{
|
||||||
|
var permService = services.GetRequiredService<DiscordPermOverrideService>();
|
||||||
|
if (permService.TryGetOverrides(context.Guild?.Id ?? 0, command.Name.ToUpperInvariant(), out var _))
|
||||||
|
return Task.FromResult(PreconditionResult.FromSuccess());
|
||||||
|
|
||||||
|
return UserPermissionAttribute.CheckPermissionsAsync(context, command, services);
|
||||||
|
}
|
||||||
|
}
|
@@ -1,20 +1,19 @@
|
|||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace NadekoBot.Common
|
namespace NadekoBot.Common;
|
||||||
{
|
|
||||||
public class CmdStrings
|
|
||||||
{
|
|
||||||
public string[] Usages { get; }
|
|
||||||
public string Description { get; }
|
|
||||||
|
|
||||||
[JsonConstructor]
|
public class CmdStrings
|
||||||
public CmdStrings(
|
{
|
||||||
[JsonProperty("args")]string[] usages,
|
public string[] Usages { get; }
|
||||||
[JsonProperty("desc")]string description
|
public string Description { get; }
|
||||||
)
|
|
||||||
{
|
[JsonConstructor]
|
||||||
Usages = usages;
|
public CmdStrings(
|
||||||
Description = description;
|
[JsonProperty("args")]string[] usages,
|
||||||
}
|
[JsonProperty("desc")]string description
|
||||||
|
)
|
||||||
|
{
|
||||||
|
Usages = usages;
|
||||||
|
Description = description;
|
||||||
}
|
}
|
||||||
}
|
}
|
File diff suppressed because it is too large
Load Diff
@@ -1,77 +1,74 @@
|
|||||||
using System;
|
using System.Collections;
|
||||||
using System.Collections;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace NadekoBot.Common.Collections
|
namespace NadekoBot.Common.Collections;
|
||||||
|
|
||||||
|
public static class DisposableReadOnlyListExtensions
|
||||||
{
|
{
|
||||||
public static class DisposableReadOnlyListExtensions
|
public static IDisposableReadOnlyList<T> AsDisposable<T>(this IReadOnlyList<T> arr) where T : IDisposable
|
||||||
{
|
=> new DisposableReadOnlyList<T>(arr);
|
||||||
public static IDisposableReadOnlyList<T> AsDisposable<T>(this IReadOnlyList<T> arr) where T : IDisposable
|
|
||||||
=> new DisposableReadOnlyList<T>(arr);
|
|
||||||
|
|
||||||
public static IDisposableReadOnlyList<KeyValuePair<TKey, TValue>> AsDisposable<TKey, TValue>(this IReadOnlyList<KeyValuePair<TKey, TValue>> arr) where TValue : IDisposable
|
public static IDisposableReadOnlyList<KeyValuePair<TKey, TValue>> AsDisposable<TKey, TValue>(this IReadOnlyList<KeyValuePair<TKey, TValue>> arr) where TValue : IDisposable
|
||||||
=> new DisposableReadOnlyList<TKey, TValue>(arr);
|
=> new DisposableReadOnlyList<TKey, TValue>(arr);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IDisposableReadOnlyList<T> : IReadOnlyList<T>, IDisposable
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class DisposableReadOnlyList<T> : IDisposableReadOnlyList<T>
|
||||||
|
where T : IDisposable
|
||||||
|
{
|
||||||
|
private readonly IReadOnlyList<T> _arr;
|
||||||
|
|
||||||
|
public int Count => _arr.Count;
|
||||||
|
|
||||||
|
public T this[int index] => _arr[index];
|
||||||
|
|
||||||
|
public DisposableReadOnlyList(IReadOnlyList<T> arr)
|
||||||
|
{
|
||||||
|
this._arr = arr;
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface IDisposableReadOnlyList<T> : IReadOnlyList<T>, IDisposable
|
public IEnumerator<T> GetEnumerator()
|
||||||
|
=> _arr.GetEnumerator();
|
||||||
|
|
||||||
|
IEnumerator IEnumerable.GetEnumerator()
|
||||||
|
=> _arr.GetEnumerator();
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
{
|
{
|
||||||
}
|
foreach (var item in _arr)
|
||||||
|
|
||||||
public sealed class DisposableReadOnlyList<T> : IDisposableReadOnlyList<T>
|
|
||||||
where T : IDisposable
|
|
||||||
{
|
|
||||||
private readonly IReadOnlyList<T> _arr;
|
|
||||||
|
|
||||||
public int Count => _arr.Count;
|
|
||||||
|
|
||||||
public T this[int index] => _arr[index];
|
|
||||||
|
|
||||||
public DisposableReadOnlyList(IReadOnlyList<T> arr)
|
|
||||||
{
|
{
|
||||||
this._arr = arr;
|
item.Dispose();
|
||||||
}
|
|
||||||
|
|
||||||
public IEnumerator<T> GetEnumerator()
|
|
||||||
=> _arr.GetEnumerator();
|
|
||||||
|
|
||||||
IEnumerator IEnumerable.GetEnumerator()
|
|
||||||
=> _arr.GetEnumerator();
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
foreach (var item in _arr)
|
|
||||||
{
|
|
||||||
item.Dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public sealed class DisposableReadOnlyList<T, U> : IDisposableReadOnlyList<KeyValuePair<T, U>>
|
|
||||||
where U : IDisposable
|
|
||||||
{
|
|
||||||
private readonly IReadOnlyList<KeyValuePair<T, U>> _arr;
|
|
||||||
|
|
||||||
public int Count => _arr.Count;
|
|
||||||
|
|
||||||
KeyValuePair<T, U> IReadOnlyList<KeyValuePair<T, U>>.this[int index] => _arr[index];
|
|
||||||
|
|
||||||
public DisposableReadOnlyList(IReadOnlyList<KeyValuePair<T, U>> arr)
|
|
||||||
{
|
|
||||||
this._arr = arr;
|
|
||||||
}
|
|
||||||
|
|
||||||
public IEnumerator<KeyValuePair<T, U>> GetEnumerator() =>
|
|
||||||
_arr.GetEnumerator();
|
|
||||||
|
|
||||||
IEnumerator IEnumerable.GetEnumerator() =>
|
|
||||||
_arr.GetEnumerator();
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
foreach (var item in _arr)
|
|
||||||
{
|
|
||||||
item.Value.Dispose();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public sealed class DisposableReadOnlyList<T, U> : IDisposableReadOnlyList<KeyValuePair<T, U>>
|
||||||
|
where U : IDisposable
|
||||||
|
{
|
||||||
|
private readonly IReadOnlyList<KeyValuePair<T, U>> _arr;
|
||||||
|
|
||||||
|
public int Count => _arr.Count;
|
||||||
|
|
||||||
|
KeyValuePair<T, U> IReadOnlyList<KeyValuePair<T, U>>.this[int index] => _arr[index];
|
||||||
|
|
||||||
|
public DisposableReadOnlyList(IReadOnlyList<KeyValuePair<T, U>> arr)
|
||||||
|
{
|
||||||
|
this._arr = arr;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerator<KeyValuePair<T, U>> GetEnumerator() =>
|
||||||
|
_arr.GetEnumerator();
|
||||||
|
|
||||||
|
IEnumerator IEnumerable.GetEnumerator() =>
|
||||||
|
_arr.GetEnumerator();
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
foreach (var item in _arr)
|
||||||
|
{
|
||||||
|
item.Value.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -1,141 +1,138 @@
|
|||||||
using System.Collections;
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using NadekoBot.Services.Database.Models;
|
using NadekoBot.Services.Database.Models;
|
||||||
|
|
||||||
namespace NadekoBot.Common.Collections
|
namespace NadekoBot.Common.Collections;
|
||||||
|
|
||||||
|
public class IndexedCollection<T> : IList<T> where T : class, IIndexed
|
||||||
{
|
{
|
||||||
public class IndexedCollection<T> : IList<T> where T : class, IIndexed
|
public List<T> Source { get; }
|
||||||
|
private readonly object _locker = new object();
|
||||||
|
|
||||||
|
public int Count => Source.Count;
|
||||||
|
public bool IsReadOnly => false;
|
||||||
|
public int IndexOf(T item) => item.Index;
|
||||||
|
|
||||||
|
public IndexedCollection()
|
||||||
{
|
{
|
||||||
public List<T> Source { get; }
|
Source = new List<T>();
|
||||||
private readonly object _locker = new object();
|
}
|
||||||
|
|
||||||
public int Count => Source.Count;
|
|
||||||
public bool IsReadOnly => false;
|
|
||||||
public int IndexOf(T item) => item.Index;
|
|
||||||
|
|
||||||
public IndexedCollection()
|
|
||||||
{
|
|
||||||
Source = new List<T>();
|
|
||||||
}
|
|
||||||
|
|
||||||
public IndexedCollection(IEnumerable<T> source)
|
public IndexedCollection(IEnumerable<T> source)
|
||||||
|
{
|
||||||
|
lock (_locker)
|
||||||
{
|
{
|
||||||
lock (_locker)
|
Source = source.OrderBy(x => x.Index).ToList();
|
||||||
|
UpdateIndexes();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateIndexes()
|
||||||
|
{
|
||||||
|
lock (_locker)
|
||||||
|
{
|
||||||
|
for (var i = 0; i < Source.Count; i++)
|
||||||
{
|
{
|
||||||
Source = source.OrderBy(x => x.Index).ToList();
|
if (Source[i].Index != i)
|
||||||
UpdateIndexes();
|
Source[i].Index = i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void UpdateIndexes()
|
public static implicit operator List<T>(IndexedCollection<T> x) =>
|
||||||
|
x.Source;
|
||||||
|
|
||||||
|
public List<T> ToList() => Source.ToList();
|
||||||
|
|
||||||
|
public IEnumerator<T> GetEnumerator() =>
|
||||||
|
Source.GetEnumerator();
|
||||||
|
|
||||||
|
IEnumerator IEnumerable.GetEnumerator() =>
|
||||||
|
Source.GetEnumerator();
|
||||||
|
|
||||||
|
public void Add(T item)
|
||||||
|
{
|
||||||
|
lock (_locker)
|
||||||
{
|
{
|
||||||
lock (_locker)
|
item.Index = Source.Count;
|
||||||
|
Source.Add(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void Clear()
|
||||||
|
{
|
||||||
|
lock (_locker)
|
||||||
|
{
|
||||||
|
Source.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Contains(T item)
|
||||||
|
{
|
||||||
|
lock (_locker)
|
||||||
|
{
|
||||||
|
return Source.Contains(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CopyTo(T[] array, int arrayIndex)
|
||||||
|
{
|
||||||
|
lock (_locker)
|
||||||
|
{
|
||||||
|
Source.CopyTo(array, arrayIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual bool Remove(T item)
|
||||||
|
{
|
||||||
|
bool removed;
|
||||||
|
lock (_locker)
|
||||||
|
{
|
||||||
|
if (removed = Source.Remove(item))
|
||||||
{
|
{
|
||||||
for (var i = 0; i < Source.Count; i++)
|
for (int i = 0; i < Source.Count; i++)
|
||||||
{
|
{
|
||||||
if (Source[i].Index != i)
|
if (Source[i].Index != i)
|
||||||
Source[i].Index = i;
|
Source[i].Index = i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return removed;
|
||||||
|
}
|
||||||
|
|
||||||
public static implicit operator List<T>(IndexedCollection<T> x) =>
|
public virtual void Insert(int index, T item)
|
||||||
x.Source;
|
{
|
||||||
|
lock (_locker)
|
||||||
public List<T> ToList() => Source.ToList();
|
|
||||||
|
|
||||||
public IEnumerator<T> GetEnumerator() =>
|
|
||||||
Source.GetEnumerator();
|
|
||||||
|
|
||||||
IEnumerator IEnumerable.GetEnumerator() =>
|
|
||||||
Source.GetEnumerator();
|
|
||||||
|
|
||||||
public void Add(T item)
|
|
||||||
{
|
{
|
||||||
lock (_locker)
|
Source.Insert(index, item);
|
||||||
|
for (int i = index; i < Source.Count; i++)
|
||||||
{
|
{
|
||||||
item.Index = Source.Count;
|
Source[i].Index = i;
|
||||||
Source.Add(item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual void Clear()
|
|
||||||
{
|
|
||||||
lock (_locker)
|
|
||||||
{
|
|
||||||
Source.Clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Contains(T item)
|
|
||||||
{
|
|
||||||
lock (_locker)
|
|
||||||
{
|
|
||||||
return Source.Contains(item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void CopyTo(T[] array, int arrayIndex)
|
|
||||||
{
|
|
||||||
lock (_locker)
|
|
||||||
{
|
|
||||||
Source.CopyTo(array, arrayIndex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual bool Remove(T item)
|
|
||||||
{
|
|
||||||
bool removed;
|
|
||||||
lock (_locker)
|
|
||||||
{
|
|
||||||
if (removed = Source.Remove(item))
|
|
||||||
{
|
|
||||||
for (int i = 0; i < Source.Count; i++)
|
|
||||||
{
|
|
||||||
if (Source[i].Index != i)
|
|
||||||
Source[i].Index = i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return removed;
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual void Insert(int index, T item)
|
|
||||||
{
|
|
||||||
lock (_locker)
|
|
||||||
{
|
|
||||||
Source.Insert(index, item);
|
|
||||||
for (int i = index; i < Source.Count; i++)
|
|
||||||
{
|
|
||||||
Source[i].Index = i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual void RemoveAt(int index)
|
|
||||||
{
|
|
||||||
lock (_locker)
|
|
||||||
{
|
|
||||||
Source.RemoveAt(index);
|
|
||||||
for (int i = index; i < Source.Count; i++)
|
|
||||||
{
|
|
||||||
Source[i].Index = i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual T this[int index]
|
|
||||||
{
|
|
||||||
get { return Source[index]; }
|
|
||||||
set
|
|
||||||
{
|
|
||||||
lock (_locker)
|
|
||||||
{
|
|
||||||
value.Index = index;
|
|
||||||
Source[index] = value;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
public virtual void RemoveAt(int index)
|
||||||
|
{
|
||||||
|
lock (_locker)
|
||||||
|
{
|
||||||
|
Source.RemoveAt(index);
|
||||||
|
for (int i = index; i < Source.Count; i++)
|
||||||
|
{
|
||||||
|
Source[i].Index = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual T this[int index]
|
||||||
|
{
|
||||||
|
get { return Source[index]; }
|
||||||
|
set
|
||||||
|
{
|
||||||
|
lock (_locker)
|
||||||
|
{
|
||||||
|
value.Index = index;
|
||||||
|
Source[index] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -1,9 +1,8 @@
|
|||||||
namespace NadekoBot.Common
|
namespace NadekoBot.Common;
|
||||||
|
|
||||||
|
public class CommandData
|
||||||
{
|
{
|
||||||
public class CommandData
|
public string Cmd { get; set; }
|
||||||
{
|
public string Desc { get; set; }
|
||||||
public string Cmd { get; set; }
|
public string[] Usage { get; set; }
|
||||||
public string Desc { get; set; }
|
}
|
||||||
public string[] Usage { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,69 +1,68 @@
|
|||||||
using System.Collections.Generic;
|
using System.Globalization;
|
||||||
using System.Globalization;
|
|
||||||
using Cloneable;
|
using Cloneable;
|
||||||
using NadekoBot.Common.Yml;
|
using NadekoBot.Common.Yml;
|
||||||
using SixLabors.ImageSharp.PixelFormats;
|
using SixLabors.ImageSharp.PixelFormats;
|
||||||
using YamlDotNet.Core;
|
using YamlDotNet.Core;
|
||||||
using YamlDotNet.Serialization;
|
using YamlDotNet.Serialization;
|
||||||
|
|
||||||
namespace NadekoBot.Common.Configs
|
namespace NadekoBot.Common.Configs;
|
||||||
{
|
|
||||||
[Cloneable]
|
|
||||||
public sealed partial class BotConfig : ICloneable<BotConfig>
|
|
||||||
{
|
|
||||||
[Comment(@"DO NOT CHANGE")]
|
|
||||||
public int Version { get; set; } = 2;
|
|
||||||
|
|
||||||
[Comment(@"Most commands, when executed, have a small colored line
|
[Cloneable]
|
||||||
|
public sealed partial class BotConfig : ICloneable<BotConfig>
|
||||||
|
{
|
||||||
|
[Comment(@"DO NOT CHANGE")]
|
||||||
|
public int Version { get; set; } = 2;
|
||||||
|
|
||||||
|
[Comment(@"Most commands, when executed, have a small colored line
|
||||||
next to the response. The color depends whether the command
|
next to the response. The color depends whether the command
|
||||||
is completed, errored or in progress (pending)
|
is completed, errored or in progress (pending)
|
||||||
Color settings below are for the color of those lines.
|
Color settings below are for the color of those lines.
|
||||||
To get color's hex, you can go here https://htmlcolorcodes.com/
|
To get color's hex, you can go here https://htmlcolorcodes.com/
|
||||||
and copy the hex code fo your selected color (marked as #)")]
|
and copy the hex code fo your selected color (marked as #)")]
|
||||||
public ColorConfig Color { get; set; }
|
public ColorConfig Color { get; set; }
|
||||||
|
|
||||||
[Comment("Default bot language. It has to be in the list of supported languages (.langli)")]
|
[Comment("Default bot language. It has to be in the list of supported languages (.langli)")]
|
||||||
public CultureInfo DefaultLocale { get; set; }
|
public CultureInfo DefaultLocale { get; set; }
|
||||||
|
|
||||||
[Comment(@"Style in which executed commands will show up in the console.
|
[Comment(@"Style in which executed commands will show up in the console.
|
||||||
Allowed values: Simple, Normal, None")]
|
Allowed values: Simple, Normal, None")]
|
||||||
public ConsoleOutputType ConsoleOutputType { get; set; }
|
public ConsoleOutputType ConsoleOutputType { get; set; }
|
||||||
|
|
||||||
// [Comment(@"For what kind of updates will the bot check.
|
// [Comment(@"For what kind of updates will the bot check.
|
||||||
// Allowed values: Release, Commit, None")]
|
// Allowed values: Release, Commit, None")]
|
||||||
// public UpdateCheckType CheckForUpdates { get; set; }
|
// public UpdateCheckType CheckForUpdates { get; set; }
|
||||||
|
|
||||||
// [Comment(@"How often will the bot check for updates, in hours")]
|
// [Comment(@"How often will the bot check for updates, in hours")]
|
||||||
// public int CheckUpdateInterval { get; set; }
|
// public int CheckUpdateInterval { get; set; }
|
||||||
|
|
||||||
[Comment(@"Do you want any messages sent by users in Bot's DM to be forwarded to the owner(s)?")]
|
[Comment(@"Do you want any messages sent by users in Bot's DM to be forwarded to the owner(s)?")]
|
||||||
public bool ForwardMessages { get; set; }
|
public bool ForwardMessages { get; set; }
|
||||||
|
|
||||||
[Comment(@"Do you want the message to be forwarded only to the first owner specified in the list of owners (in creds.yml),
|
[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; }
|
public bool ForwardToAllOwners { get; set; }
|
||||||
|
|
||||||
[Comment(@"When a user DMs the bot with a message which is not a command
|
[Comment(@"When a user DMs the bot with a message which is not a command
|
||||||
they will receive this message. Leave empty for no response. The string which will be sent whenever someone DMs the bot.
|
they will receive this message. Leave empty for no response. The string which will be sent whenever someone DMs the bot.
|
||||||
Supports embeds. How it looks: https://puu.sh/B0BLV.png")]
|
Supports embeds. How it looks: https://puu.sh/B0BLV.png")]
|
||||||
[YamlMember(ScalarStyle = ScalarStyle.Literal)]
|
[YamlMember(ScalarStyle = ScalarStyle.Literal)]
|
||||||
public string DmHelpText { get; set; }
|
public string DmHelpText { get; set; }
|
||||||
|
|
||||||
[Comment(@"Only users who send a DM to the bot containing one of the specified words will get a DmHelpText response.
|
[Comment(@"Only users who send a DM to the bot containing one of the specified words will get a DmHelpText response.
|
||||||
Case insensitive.
|
Case insensitive.
|
||||||
Leave empty to reply with DmHelpText to every DM.")]
|
Leave empty to reply with DmHelpText to every DM.")]
|
||||||
public List<string> DmHelpTextKeywords { get; set; }
|
public List<string> DmHelpTextKeywords { get; set; }
|
||||||
|
|
||||||
[Comment(@"This is the response for the .h command")]
|
[Comment(@"This is the response for the .h command")]
|
||||||
[YamlMember(ScalarStyle = ScalarStyle.Literal)]
|
[YamlMember(ScalarStyle = ScalarStyle.Literal)]
|
||||||
public string HelpText { get; set; }
|
public string HelpText { get; set; }
|
||||||
[Comment(@"List of modules and commands completely blocked on the bot")]
|
[Comment(@"List of modules and commands completely blocked on the bot")]
|
||||||
public BlockedConfig Blocked { get; set; }
|
public BlockedConfig Blocked { get; set; }
|
||||||
|
|
||||||
[Comment(@"Which string will be used to recognize the commands")]
|
[Comment(@"Which string will be used to recognize the commands")]
|
||||||
public string Prefix { get; set; }
|
public string Prefix { get; set; }
|
||||||
|
|
||||||
[Comment(@"Toggles whether your bot will group greet/bye messages into a single message every 5 seconds.
|
[Comment(@"Toggles whether your bot will group greet/bye messages into a single message every 5 seconds.
|
||||||
1st user who joins will get greeted immediately
|
1st user who joins will get greeted immediately
|
||||||
If more users join within the next 5 seconds, they will be greeted in groups of 5.
|
If more users join within the next 5 seconds, they will be greeted in groups of 5.
|
||||||
This will cause %user.mention% and other placeholders to be replaced with multiple users.
|
This will cause %user.mention% and other placeholders to be replaced with multiple users.
|
||||||
@@ -72,12 +71,12 @@ it will become invalid, as it will resolve to a list of avatars of grouped users
|
|||||||
note: This setting is primarily used if you're afraid of raids, or you're running medium/large bots where some
|
note: This setting is primarily used if you're afraid of raids, or you're running medium/large bots where some
|
||||||
servers might get hundreds of people join at once. This is used to prevent the bot from getting ratelimited,
|
servers might get hundreds of people join at once. This is used to prevent the bot from getting ratelimited,
|
||||||
and (slightly) reduce the greet spam in those servers.")]
|
and (slightly) reduce the greet spam in those servers.")]
|
||||||
public bool GroupGreets { get; set; }
|
public bool GroupGreets { get; set; }
|
||||||
|
|
||||||
[Comment(@"Whether the bot will rotate through all specified statuses.
|
[Comment(@"Whether the bot will rotate through all specified statuses.
|
||||||
This setting can be changed via .rots command.
|
This setting can be changed via .rots command.
|
||||||
See RotatingStatuses submodule in Administration.")]
|
See RotatingStatuses submodule in Administration.")]
|
||||||
public bool RotateStatuses { get; set; }
|
public bool RotateStatuses { get; set; }
|
||||||
|
|
||||||
// [Comment(@"Whether the prefix will be a suffix, or prefix.
|
// [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
|
// For example, if your prefix is ! you will run a command called 'cash' by typing either
|
||||||
@@ -85,23 +84,23 @@ See RotatingStatuses submodule in Administration.")]
|
|||||||
// 'cash @Someone!' if your prefixIsSuffix: true")]
|
// 'cash @Someone!' if your prefixIsSuffix: true")]
|
||||||
// public bool PrefixIsSuffix { get; set; }
|
// public bool PrefixIsSuffix { get; set; }
|
||||||
|
|
||||||
// public string Prefixed(string text) => PrefixIsSuffix
|
// public string Prefixed(string text) => PrefixIsSuffix
|
||||||
// ? text + Prefix
|
// ? text + Prefix
|
||||||
// : Prefix + text;
|
// : Prefix + text;
|
||||||
|
|
||||||
public string Prefixed(string text)
|
public string Prefixed(string text)
|
||||||
=> Prefix + text;
|
=> Prefix + text;
|
||||||
|
|
||||||
public BotConfig()
|
public BotConfig()
|
||||||
{
|
{
|
||||||
var color = new ColorConfig();
|
var color = new ColorConfig();
|
||||||
Color = color;
|
Color = color;
|
||||||
DefaultLocale = new CultureInfo("en-US");
|
DefaultLocale = new CultureInfo("en-US");
|
||||||
ConsoleOutputType = ConsoleOutputType.Normal;
|
ConsoleOutputType = ConsoleOutputType.Normal;
|
||||||
ForwardMessages = false;
|
ForwardMessages = false;
|
||||||
ForwardToAllOwners = false;
|
ForwardToAllOwners = false;
|
||||||
DmHelpText = @"{""description"": ""Type `%prefix%h` for help.""}";
|
DmHelpText = @"{""description"": ""Type `%prefix%h` for help.""}";
|
||||||
HelpText = @"{
|
HelpText = @"{
|
||||||
""title"": ""To invite me to your server, use this link"",
|
""title"": ""To invite me to your server, use this link"",
|
||||||
""description"": ""https://discordapp.com/oauth2/authorize?client_id={0}&scope=bot&permissions=66186303"",
|
""description"": ""https://discordapp.com/oauth2/authorize?client_id={0}&scope=bot&permissions=66186303"",
|
||||||
""color"": 53380,
|
""color"": 53380,
|
||||||
@@ -126,59 +125,58 @@ See RotatingStatuses submodule in Administration.")]
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
}";
|
}";
|
||||||
var blocked = new BlockedConfig();
|
var blocked = new BlockedConfig();
|
||||||
Blocked = blocked;
|
Blocked = blocked;
|
||||||
Prefix = ".";
|
Prefix = ".";
|
||||||
RotateStatuses = false;
|
RotateStatuses = false;
|
||||||
GroupGreets = false;
|
GroupGreets = false;
|
||||||
DmHelpTextKeywords = new List<string>()
|
DmHelpTextKeywords = new List<string>()
|
||||||
{
|
|
||||||
"help",
|
|
||||||
"commands",
|
|
||||||
"cmds",
|
|
||||||
"module",
|
|
||||||
"can you do"
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Cloneable]
|
|
||||||
public sealed partial class BlockedConfig
|
|
||||||
{
|
|
||||||
public HashSet<string> Commands { get; set; }
|
|
||||||
public HashSet<string> Modules { get; set; }
|
|
||||||
|
|
||||||
public BlockedConfig()
|
|
||||||
{
|
{
|
||||||
Modules = new HashSet<string>();
|
"help",
|
||||||
Commands = new HashSet<string>();
|
"commands",
|
||||||
}
|
"cmds",
|
||||||
|
"module",
|
||||||
|
"can you do"
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Cloneable]
|
[Cloneable]
|
||||||
public partial class ColorConfig
|
public sealed partial class BlockedConfig
|
||||||
|
{
|
||||||
|
public HashSet<string> Commands { get; set; }
|
||||||
|
public HashSet<string> Modules { get; set; }
|
||||||
|
|
||||||
|
public BlockedConfig()
|
||||||
{
|
{
|
||||||
[Comment(@"Color used for embed responses when command successfully executes")]
|
Modules = new HashSet<string>();
|
||||||
public Rgba32 Ok { get; set; }
|
Commands = new HashSet<string>();
|
||||||
|
|
||||||
[Comment(@"Color used for embed responses when command has an error")]
|
|
||||||
public Rgba32 Error { get; set; }
|
|
||||||
|
|
||||||
[Comment(@"Color used for embed responses while command is doing work or is in progress")]
|
|
||||||
public Rgba32 Pending { get; set; }
|
|
||||||
|
|
||||||
public ColorConfig()
|
|
||||||
{
|
|
||||||
Ok = Rgba32.ParseHex("00e584");
|
|
||||||
Error = Rgba32.ParseHex("ee281f");
|
|
||||||
Pending = Rgba32.ParseHex("faa61a");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Cloneable]
|
||||||
|
public partial class ColorConfig
|
||||||
|
{
|
||||||
|
[Comment(@"Color used for embed responses when command successfully executes")]
|
||||||
|
public Rgba32 Ok { get; set; }
|
||||||
|
|
||||||
|
[Comment(@"Color used for embed responses when command has an error")]
|
||||||
|
public Rgba32 Error { get; set; }
|
||||||
|
|
||||||
|
[Comment(@"Color used for embed responses while command is doing work or is in progress")]
|
||||||
|
public Rgba32 Pending { get; set; }
|
||||||
|
|
||||||
|
public ColorConfig()
|
||||||
|
{
|
||||||
|
Ok = Rgba32.ParseHex("00e584");
|
||||||
|
Error = Rgba32.ParseHex("ee281f");
|
||||||
|
Pending = Rgba32.ParseHex("faa61a");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public enum ConsoleOutputType
|
public enum ConsoleOutputType
|
||||||
{
|
{
|
||||||
Normal = 0,
|
Normal = 0,
|
||||||
Simple = 1,
|
Simple = 1,
|
||||||
None = 2,
|
None = 2,
|
||||||
}
|
|
||||||
}
|
}
|
@@ -1,18 +1,17 @@
|
|||||||
namespace NadekoBot.Common.Configs
|
namespace NadekoBot.Common.Configs;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Base interface for available config serializers
|
||||||
|
/// </summary>
|
||||||
|
public interface IConfigSeria
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Base interface for available config serializers
|
/// Serialize the object to string
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IConfigSeria
|
public string Serialize<T>(T obj);
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Serialize the object to string
|
|
||||||
/// </summary>
|
|
||||||
public string Serialize<T>(T obj);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Deserialize string data into an object of the specified type
|
/// Deserialize string data into an object of the specified type
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public T Deserialize<T>(string data);
|
public T Deserialize<T>(string data);
|
||||||
}
|
|
||||||
}
|
}
|
@@ -1,97 +1,95 @@
|
|||||||
using System.Collections.Generic;
|
using NadekoBot.Common.Yml;
|
||||||
using NadekoBot.Common.Yml;
|
|
||||||
using YamlDotNet.Serialization;
|
|
||||||
|
|
||||||
namespace NadekoBot.Common
|
namespace NadekoBot.Common;
|
||||||
|
|
||||||
|
public sealed class Creds : IBotCredentials
|
||||||
{
|
{
|
||||||
public sealed class Creds : IBotCredentials
|
public Creds()
|
||||||
{
|
{
|
||||||
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()
|
||||||
{
|
{
|
||||||
Version = 1;
|
Type = "sqlite",
|
||||||
Token = string.Empty;
|
ConnectionString = "Data Source=data/NadekoBot.db"
|
||||||
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";
|
CoordinatorUrl = "http://localhost:3442";
|
||||||
|
|
||||||
RestartCommand = new()
|
RestartCommand = new()
|
||||||
{
|
{
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
[Comment(@"DO NOT CHANGE")]
|
[Comment(@"DO NOT CHANGE")]
|
||||||
public int Version { get; set; }
|
public int Version { get; set; }
|
||||||
|
|
||||||
[Comment(@"Bot token. Do not share with anyone ever -> https://discordapp.com/developers/applications/")]
|
[Comment(@"Bot token. Do not share with anyone ever -> https://discordapp.com/developers/applications/")]
|
||||||
public string Token { get; set; }
|
public string Token { get; set; }
|
||||||
|
|
||||||
[Comment(@"List of Ids of the users who have bot owner permissions
|
[Comment(@"List of Ids of the users who have bot owner permissions
|
||||||
**DO NOT ADD PEOPLE YOU DON'T TRUST**")]
|
**DO NOT ADD PEOPLE YOU DON'T TRUST**")]
|
||||||
public ICollection<ulong> OwnerIds { get; set; }
|
public ICollection<ulong> OwnerIds { get; set; }
|
||||||
|
|
||||||
[Comment(@"The number of shards that the bot will running on.
|
[Comment(@"The number of shards that the bot will running on.
|
||||||
Leave at 1 if you don't know what you're doing.")]
|
Leave at 1 if you don't know what you're doing.")]
|
||||||
public int TotalShards { get; set; }
|
public int TotalShards { get; set; }
|
||||||
|
|
||||||
[Comment(@"Login to https://console.cloud.google.com, create a new project, go to APIs & Services -> Library -> YouTube Data API and enable it.
|
[Comment(@"Login to https://console.cloud.google.com, create a new project, go to APIs & Services -> Library -> YouTube Data API and enable it.
|
||||||
Then, go to APIs and Services -> Credentials and click Create credentials -> API key.
|
Then, go to APIs and Services -> Credentials and click Create credentials -> API key.
|
||||||
Used only for Youtube Data Api (at the moment).")]
|
Used only for Youtube Data Api (at the moment).")]
|
||||||
public string GoogleApiKey { get; set; }
|
public string GoogleApiKey { get; set; }
|
||||||
|
|
||||||
[Comment(@"Settings for voting system for discordbots. Meant for use on global Nadeko.")]
|
[Comment(@"Settings for voting system for discordbots. Meant for use on global Nadeko.")]
|
||||||
public VotesSettings Votes { get; set; }
|
public VotesSettings Votes { get; set; }
|
||||||
|
|
||||||
[Comment(@"Patreon auto reward system settings.
|
[Comment(@"Patreon auto reward system settings.
|
||||||
go to https://www.patreon.com/portal -> my clients -> create client")]
|
go to https://www.patreon.com/portal -> my clients -> create client")]
|
||||||
public PatreonSettings Patreon { get; set; }
|
public PatreonSettings Patreon { get; set; }
|
||||||
|
|
||||||
[Comment(@"Api key for sending stats to DiscordBotList.")]
|
[Comment(@"Api key for sending stats to DiscordBotList.")]
|
||||||
public string BotListToken { get; set; }
|
public string BotListToken { get; set; }
|
||||||
|
|
||||||
[Comment(@"Official cleverbot api key.")]
|
[Comment(@"Official cleverbot api key.")]
|
||||||
public string CleverbotApiKey { get; set; }
|
public string CleverbotApiKey { get; set; }
|
||||||
|
|
||||||
[Comment(@"Redis connection string. Don't change if you don't know what you're doing.")]
|
[Comment(@"Redis connection string. Don't change if you don't know what you're doing.")]
|
||||||
public string RedisOptions { get; set; }
|
public string RedisOptions { get; set; }
|
||||||
|
|
||||||
[Comment(@"Database options. Don't change if you don't know what you're doing. Leave null for default values")]
|
[Comment(@"Database options. Don't change if you don't know what you're doing. Leave null for default values")]
|
||||||
public DbOptions Db { get; set; }
|
public DbOptions Db { get; set; }
|
||||||
|
|
||||||
[Comment(@"Address and port of the coordinator endpoint. Leave empty for default.
|
[Comment(@"Address and port of the coordinator endpoint. Leave empty for default.
|
||||||
Change only if you've changed the coordinator address or port.")]
|
Change only if you've changed the coordinator address or port.")]
|
||||||
public string CoordinatorUrl { get; set; }
|
public string CoordinatorUrl { get; set; }
|
||||||
|
|
||||||
[Comment(@"Api key obtained on https://rapidapi.com (go to MyApps -> Add New App -> Enter Name -> Application key)")]
|
[Comment(@"Api key obtained on https://rapidapi.com (go to MyApps -> Add New App -> Enter Name -> Application key)")]
|
||||||
public string RapidApiKey { get; set; }
|
public string RapidApiKey { get; set; }
|
||||||
|
|
||||||
[Comment(@"https://locationiq.com api key (register and you will receive the token in the email).
|
[Comment(@"https://locationiq.com api key (register and you will receive the token in the email).
|
||||||
Used only for .time command.")]
|
Used only for .time command.")]
|
||||||
public string LocationIqApiKey { get; set; }
|
public string LocationIqApiKey { get; set; }
|
||||||
|
|
||||||
[Comment(@"https://timezonedb.com api key (register and you will receive the token in the email).
|
[Comment(@"https://timezonedb.com api key (register and you will receive the token in the email).
|
||||||
Used only for .time command")]
|
Used only for .time command")]
|
||||||
public string TimezoneDbApiKey { get; set; }
|
public string TimezoneDbApiKey { get; set; }
|
||||||
|
|
||||||
[Comment(@"https://pro.coinmarketcap.com/account/ api key. There is a free plan for personal use.
|
[Comment(@"https://pro.coinmarketcap.com/account/ api key. There is a free plan for personal use.
|
||||||
Used for cryptocurrency related commands.")]
|
Used for cryptocurrency related commands.")]
|
||||||
public string CoinmarketcapApiKey { get; set; }
|
public string CoinmarketcapApiKey { get; set; }
|
||||||
|
|
||||||
[Comment(@"Api key used for Osu related commands. Obtain this key at https://osu.ppy.sh/p/api")]
|
[Comment(@"Api key used for Osu related commands. Obtain this key at https://osu.ppy.sh/p/api")]
|
||||||
public string OsuApiKey { get; set; }
|
public string OsuApiKey { get; set; }
|
||||||
|
|
||||||
[Comment(@"Command and args which will be used to restart the bot.
|
[Comment(@"Command and args which will be used to restart the bot.
|
||||||
Only used if bot is executed directly (NOT through the coordinator)
|
Only used if bot is executed directly (NOT through the coordinator)
|
||||||
placeholders:
|
placeholders:
|
||||||
{0} -> shard id
|
{0} -> shard id
|
||||||
@@ -102,118 +100,117 @@ Linux default
|
|||||||
Windows default
|
Windows default
|
||||||
cmd: NadekoBot.exe
|
cmd: NadekoBot.exe
|
||||||
args: {0}")]
|
args: {0}")]
|
||||||
public RestartConfig RestartCommand { get; set; }
|
public RestartConfig RestartCommand { get; set; }
|
||||||
|
|
||||||
|
|
||||||
public class DbOptions
|
public class DbOptions
|
||||||
|
{
|
||||||
|
[Comment(@"Database type. Only sqlite supported atm")]
|
||||||
|
public string Type { get; set; }
|
||||||
|
[Comment(@"Connection string. Will default to ""Data Source=data/NadekoBot.db""")]
|
||||||
|
public string ConnectionString { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// todo fixup patreon
|
||||||
|
public sealed record PatreonSettings
|
||||||
|
{
|
||||||
|
public string ClientId { get; set; }
|
||||||
|
public string AccessToken { get; set; }
|
||||||
|
public string RefreshToken { get; set; }
|
||||||
|
public string ClientSecret { get; set; }
|
||||||
|
|
||||||
|
[Comment(@"Campaign ID of your patreon page. Go to your patreon page (make sure you're logged in) and type ""prompt('Campaign ID', window.patreon.bootstrap.creator.data.id);"" in the console. (ctrl + shift + i)")]
|
||||||
|
public string CampaignId { get; set; }
|
||||||
|
|
||||||
|
public PatreonSettings(string accessToken, string refreshToken, string clientSecret, string campaignId)
|
||||||
{
|
{
|
||||||
[Comment(@"Database type. Only sqlite supported atm")]
|
AccessToken = accessToken;
|
||||||
public string Type { get; set; }
|
RefreshToken = refreshToken;
|
||||||
[Comment(@"Connection string. Will default to ""Data Source=data/NadekoBot.db""")]
|
ClientSecret = clientSecret;
|
||||||
public string ConnectionString { get; set; }
|
CampaignId = campaignId;
|
||||||
}
|
|
||||||
|
|
||||||
// todo fixup patreon
|
|
||||||
public sealed record PatreonSettings
|
|
||||||
{
|
|
||||||
public string ClientId { get; set; }
|
|
||||||
public string AccessToken { get; set; }
|
|
||||||
public string RefreshToken { get; set; }
|
|
||||||
public string ClientSecret { get; set; }
|
|
||||||
|
|
||||||
[Comment(@"Campaign ID of your patreon page. Go to your patreon page (make sure you're logged in) and type ""prompt('Campaign ID', window.patreon.bootstrap.creator.data.id);"" in the console. (ctrl + shift + i)")]
|
|
||||||
public string CampaignId { get; set; }
|
|
||||||
|
|
||||||
public PatreonSettings(string accessToken, string refreshToken, string clientSecret, string campaignId)
|
|
||||||
{
|
|
||||||
AccessToken = accessToken;
|
|
||||||
RefreshToken = refreshToken;
|
|
||||||
ClientSecret = clientSecret;
|
|
||||||
CampaignId = campaignId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PatreonSettings()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed record VotesSettings
|
public PatreonSettings()
|
||||||
{
|
{
|
||||||
[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")]
|
|
||||||
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")]
|
|
||||||
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")]
|
|
||||||
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")]
|
|
||||||
public string DiscordsKey { get; set; }
|
|
||||||
|
|
||||||
public VotesSettings()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public VotesSettings(string topggServiceUrl, string topggKey, string discordsServiceUrl, string discordsKey)
|
|
||||||
{
|
|
||||||
TopggServiceUrl = topggServiceUrl;
|
|
||||||
TopggKey = topggKey;
|
|
||||||
DiscordsServiceUrl = discordsServiceUrl;
|
|
||||||
DiscordsKey = discordsKey;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class Old
|
|
||||||
{
|
|
||||||
public string Token { get; set; } = string.Empty;
|
|
||||||
public ulong[] OwnerIds { get; set; } = new ulong[1];
|
|
||||||
public string LoLApiKey { get; set; } = string.Empty;
|
|
||||||
public string GoogleApiKey { get; set; } = string.Empty;
|
|
||||||
public string MashapeKey { get; set; } = string.Empty;
|
|
||||||
public string OsuApiKey { get; set; } = string.Empty;
|
|
||||||
public string SoundCloudClientId { get; set; } = string.Empty;
|
|
||||||
public string CleverbotApiKey { get; set; } = string.Empty;
|
|
||||||
public string CarbonKey { get; set; } = string.Empty;
|
|
||||||
public int TotalShards { get; set; } = 1;
|
|
||||||
public string PatreonAccessToken { get; set; } = string.Empty;
|
|
||||||
public string PatreonCampaignId { get; set; } = "334038";
|
|
||||||
public RestartConfig RestartCommand { get; set; } = null;
|
|
||||||
|
|
||||||
public string ShardRunCommand { get; set; } = string.Empty;
|
|
||||||
public string ShardRunArguments { get; set; } = string.Empty;
|
|
||||||
public int? ShardRunPort { get; set; } = null;
|
|
||||||
public string MiningProxyUrl { get; set; } = string.Empty;
|
|
||||||
public string MiningProxyCreds { get; set; } = string.Empty;
|
|
||||||
|
|
||||||
public string BotListToken { get; set; } = string.Empty;
|
|
||||||
public string TwitchClientId { get; set; } = string.Empty;
|
|
||||||
public string VotesToken { get; set; } = string.Empty;
|
|
||||||
public string VotesUrl { get; set; } = string.Empty;
|
|
||||||
public string RedisOptions { get; set; } = string.Empty;
|
|
||||||
public string LocationIqApiKey { get; set; } = string.Empty;
|
|
||||||
public string TimezoneDbApiKey { get; set; } = string.Empty;
|
|
||||||
public string CoinmarketcapApiKey { get; set; } = string.Empty;
|
|
||||||
|
|
||||||
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 sealed record VotesSettings
|
||||||
|
{
|
||||||
|
[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")]
|
||||||
|
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")]
|
||||||
|
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")]
|
||||||
|
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")]
|
||||||
|
public string DiscordsKey { get; set; }
|
||||||
|
|
||||||
|
public VotesSettings()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public VotesSettings(string topggServiceUrl, string topggKey, string discordsServiceUrl, string discordsKey)
|
||||||
|
{
|
||||||
|
TopggServiceUrl = topggServiceUrl;
|
||||||
|
TopggKey = topggKey;
|
||||||
|
DiscordsServiceUrl = discordsServiceUrl;
|
||||||
|
DiscordsKey = discordsKey;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Old
|
||||||
|
{
|
||||||
|
public string Token { get; set; } = string.Empty;
|
||||||
|
public ulong[] OwnerIds { get; set; } = new ulong[1];
|
||||||
|
public string LoLApiKey { get; set; } = string.Empty;
|
||||||
|
public string GoogleApiKey { get; set; } = string.Empty;
|
||||||
|
public string MashapeKey { get; set; } = string.Empty;
|
||||||
|
public string OsuApiKey { get; set; } = string.Empty;
|
||||||
|
public string SoundCloudClientId { get; set; } = string.Empty;
|
||||||
|
public string CleverbotApiKey { get; set; } = string.Empty;
|
||||||
|
public string CarbonKey { get; set; } = string.Empty;
|
||||||
|
public int TotalShards { get; set; } = 1;
|
||||||
|
public string PatreonAccessToken { get; set; } = string.Empty;
|
||||||
|
public string PatreonCampaignId { get; set; } = "334038";
|
||||||
|
public RestartConfig RestartCommand { get; set; } = null;
|
||||||
|
|
||||||
|
public string ShardRunCommand { get; set; } = string.Empty;
|
||||||
|
public string ShardRunArguments { get; set; } = string.Empty;
|
||||||
|
public int? ShardRunPort { get; set; } = null;
|
||||||
|
public string MiningProxyUrl { get; set; } = string.Empty;
|
||||||
|
public string MiningProxyCreds { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public string BotListToken { get; set; } = string.Empty;
|
||||||
|
public string TwitchClientId { get; set; } = string.Empty;
|
||||||
|
public string VotesToken { get; set; } = string.Empty;
|
||||||
|
public string VotesUrl { get; set; } = string.Empty;
|
||||||
|
public string RedisOptions { get; set; } = string.Empty;
|
||||||
|
public string LocationIqApiKey { get; set; } = string.Empty;
|
||||||
|
public string TimezoneDbApiKey { get; set; } = string.Empty;
|
||||||
|
public string CoinmarketcapApiKey { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -1,43 +1,41 @@
|
|||||||
using NadekoBot.Services;
|
using NadekoBot.Services;
|
||||||
using System;
|
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Discord;
|
using Discord;
|
||||||
|
|
||||||
namespace NadekoBot.Common
|
namespace NadekoBot.Common;
|
||||||
|
|
||||||
|
public class DownloadTracker : INService
|
||||||
{
|
{
|
||||||
public class DownloadTracker : INService
|
private ConcurrentDictionary<ulong, DateTime> LastDownloads { get; } = new ConcurrentDictionary<ulong, DateTime>();
|
||||||
|
private SemaphoreSlim downloadUsersSemaphore = new SemaphoreSlim(1, 1);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 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>
|
||||||
|
public async Task EnsureUsersDownloadedAsync(IGuild guild)
|
||||||
{
|
{
|
||||||
private ConcurrentDictionary<ulong, DateTime> LastDownloads { get; } = new ConcurrentDictionary<ulong, DateTime>();
|
await downloadUsersSemaphore.WaitAsync();
|
||||||
private SemaphoreSlim downloadUsersSemaphore = new SemaphoreSlim(1, 1);
|
try
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 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>
|
|
||||||
public async Task EnsureUsersDownloadedAsync(IGuild guild)
|
|
||||||
{
|
{
|
||||||
await downloadUsersSemaphore.WaitAsync();
|
var now = DateTime.UtcNow;
|
||||||
try
|
|
||||||
{
|
|
||||||
var now = DateTime.UtcNow;
|
|
||||||
|
|
||||||
// download once per hour at most
|
// download once per hour at most
|
||||||
var added = LastDownloads.AddOrUpdate(
|
var added = LastDownloads.AddOrUpdate(
|
||||||
guild.Id,
|
guild.Id,
|
||||||
now,
|
now,
|
||||||
(key, old) => (now - old) > TimeSpan.FromHours(1) ? now : old);
|
(key, old) => (now - old) > TimeSpan.FromHours(1) ? now : old);
|
||||||
|
|
||||||
// means that this entry was just added - download the users
|
// means that this entry was just added - download the users
|
||||||
if (added == now)
|
if (added == now)
|
||||||
await guild.DownloadUsersAsync();
|
await guild.DownloadUsersAsync();
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
downloadUsersSemaphore.Release();
|
downloadUsersSemaphore.Release();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,11 +1,9 @@
|
|||||||
using Discord;
|
using Discord;
|
||||||
using NadekoBot.Common;
|
|
||||||
|
|
||||||
namespace NadekoBot.Extensions
|
namespace NadekoBot.Extensions;
|
||||||
|
|
||||||
|
public static class BotCredentialsExtensions
|
||||||
{
|
{
|
||||||
public static class BotCredentialsExtensions
|
public static bool IsOwner(this IBotCredentials creds, IUser user)
|
||||||
{
|
=> creds.OwnerIds.Contains(user.Id);
|
||||||
public static bool IsOwner(this IBotCredentials creds, IUser user)
|
|
||||||
=> creds.OwnerIds.Contains(user.Id);
|
|
||||||
}
|
|
||||||
}
|
}
|
@@ -1,81 +1,77 @@
|
|||||||
using System;
|
using System.Reflection;
|
||||||
using System.Linq;
|
|
||||||
using System.Reflection;
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using NadekoBot.Common;
|
using NadekoBot.Common;
|
||||||
using NadekoBot.Modules.Music;
|
using NadekoBot.Modules.Music;
|
||||||
using NadekoBot.Services;
|
using NadekoBot.Services;
|
||||||
using NadekoBot.Modules.Administration.Services;
|
|
||||||
using NadekoBot.Modules.Music.Resolvers;
|
using NadekoBot.Modules.Music.Resolvers;
|
||||||
using NadekoBot.Modules.Music.Services;
|
using NadekoBot.Modules.Music.Services;
|
||||||
using StackExchange.Redis;
|
using StackExchange.Redis;
|
||||||
|
|
||||||
namespace NadekoBot.Extensions
|
namespace NadekoBot.Extensions;
|
||||||
|
|
||||||
|
public static class ServiceCollectionExtensions
|
||||||
{
|
{
|
||||||
public static class ServiceCollectionExtensions
|
public static IServiceCollection AddBotStringsServices(this IServiceCollection services, int totalShards)
|
||||||
|
=> totalShards <= 1
|
||||||
|
? services
|
||||||
|
.AddSingleton<IStringsSource, LocalFileStringsSource>()
|
||||||
|
.AddSingleton<IBotStringsProvider, LocalBotStringsProvider>()
|
||||||
|
.AddSingleton<IBotStrings, BotStrings>()
|
||||||
|
: services.AddSingleton<IStringsSource, LocalFileStringsSource>()
|
||||||
|
.AddSingleton<IBotStringsProvider, RedisBotStringsProvider>()
|
||||||
|
.AddSingleton<IBotStrings, BotStrings>();
|
||||||
|
|
||||||
|
public static IServiceCollection AddConfigServices(this IServiceCollection services)
|
||||||
{
|
{
|
||||||
public static IServiceCollection AddBotStringsServices(this IServiceCollection services, int totalShards)
|
var baseType = typeof(ConfigServiceBase<>);
|
||||||
=> totalShards <= 1
|
|
||||||
? services
|
|
||||||
.AddSingleton<IStringsSource, LocalFileStringsSource>()
|
|
||||||
.AddSingleton<IBotStringsProvider, LocalBotStringsProvider>()
|
|
||||||
.AddSingleton<IBotStrings, BotStrings>()
|
|
||||||
: services.AddSingleton<IStringsSource, LocalFileStringsSource>()
|
|
||||||
.AddSingleton<IBotStringsProvider, RedisBotStringsProvider>()
|
|
||||||
.AddSingleton<IBotStrings, BotStrings>();
|
|
||||||
|
|
||||||
public static IServiceCollection AddConfigServices(this IServiceCollection services)
|
foreach (var type in Assembly.GetCallingAssembly().ExportedTypes.Where(x => x.IsSealed))
|
||||||
{
|
{
|
||||||
var baseType = typeof(ConfigServiceBase<>);
|
if (type.BaseType?.IsGenericType == true && type.BaseType.GetGenericTypeDefinition() == baseType)
|
||||||
|
|
||||||
foreach (var type in Assembly.GetCallingAssembly().ExportedTypes.Where(x => x.IsSealed))
|
|
||||||
{
|
{
|
||||||
if (type.BaseType?.IsGenericType == true && type.BaseType.GetGenericTypeDefinition() == baseType)
|
services.AddSingleton(type);
|
||||||
{
|
services.AddSingleton(x => (IConfigService)x.GetRequiredService(type));
|
||||||
services.AddSingleton(type);
|
|
||||||
services.AddSingleton(x => (IConfigService)x.GetRequiredService(type));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return services;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IServiceCollection AddConfigMigrators(this IServiceCollection services)
|
return services;
|
||||||
=> services.AddSealedSubclassesOf(typeof(IConfigMigrator));
|
}
|
||||||
|
|
||||||
public static IServiceCollection AddMusic(this IServiceCollection services)
|
public static IServiceCollection AddConfigMigrators(this IServiceCollection services)
|
||||||
=> services
|
=> services.AddSealedSubclassesOf(typeof(IConfigMigrator));
|
||||||
.AddSingleton<IMusicService, MusicService>()
|
|
||||||
.AddSingleton<ITrackResolveProvider, TrackResolveProvider>()
|
public static IServiceCollection AddMusic(this IServiceCollection services)
|
||||||
.AddSingleton<IYoutubeResolver, YtdlYoutubeResolver>()
|
=> services
|
||||||
.AddSingleton<ISoundcloudResolver, SoundcloudResolver>()
|
.AddSingleton<IMusicService, MusicService>()
|
||||||
.AddSingleton<ILocalTrackResolver, LocalTrackResolver>()
|
.AddSingleton<ITrackResolveProvider, TrackResolveProvider>()
|
||||||
.AddSingleton<IRadioResolver, RadioResolver>()
|
.AddSingleton<IYoutubeResolver, YtdlYoutubeResolver>()
|
||||||
.AddSingleton<ITrackCacher, RedisTrackCacher>()
|
.AddSingleton<ISoundcloudResolver, SoundcloudResolver>()
|
||||||
.AddSingleton<YtLoader>()
|
.AddSingleton<ILocalTrackResolver, LocalTrackResolver>()
|
||||||
.AddSingleton<IPlaceholderProvider>(svc => svc.GetRequiredService<IMusicService>());
|
.AddSingleton<IRadioResolver, RadioResolver>()
|
||||||
|
.AddSingleton<ITrackCacher, RedisTrackCacher>()
|
||||||
|
.AddSingleton<YtLoader>()
|
||||||
|
.AddSingleton<IPlaceholderProvider>(svc => svc.GetRequiredService<IMusicService>());
|
||||||
|
|
||||||
// consider using scrutor, because slightly different versions
|
// consider using scrutor, because slightly different versions
|
||||||
// of this might be needed in several different places
|
// of this might be needed in several different places
|
||||||
public static IServiceCollection AddSealedSubclassesOf(this IServiceCollection services, Type baseType)
|
public static IServiceCollection AddSealedSubclassesOf(this IServiceCollection services, Type baseType)
|
||||||
|
{
|
||||||
|
var subTypes = Assembly.GetCallingAssembly()
|
||||||
|
.ExportedTypes
|
||||||
|
.Where(type => type.IsSealed && baseType.IsAssignableFrom(type));
|
||||||
|
|
||||||
|
foreach (var subType in subTypes)
|
||||||
{
|
{
|
||||||
var subTypes = Assembly.GetCallingAssembly()
|
services.AddSingleton(baseType, subType);
|
||||||
.ExportedTypes
|
|
||||||
.Where(type => type.IsSealed && baseType.IsAssignableFrom(type));
|
|
||||||
|
|
||||||
foreach (var subType in subTypes)
|
|
||||||
{
|
|
||||||
services.AddSingleton(baseType, subType);
|
|
||||||
}
|
|
||||||
|
|
||||||
return services;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IServiceCollection AddRedis(this IServiceCollection services, string redisOptions)
|
return services;
|
||||||
{
|
}
|
||||||
var conf = ConfigurationOptions.Parse(redisOptions);
|
|
||||||
services.AddSingleton(ConnectionMultiplexer.Connect(conf));
|
public static IServiceCollection AddRedis(this IServiceCollection services, string redisOptions)
|
||||||
return services;
|
{
|
||||||
}
|
var conf = ConfigurationOptions.Parse(redisOptions);
|
||||||
|
services.AddSingleton(ConnectionMultiplexer.Connect(conf));
|
||||||
|
return services;
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,255 +1,250 @@
|
|||||||
using System;
|
namespace Discord;
|
||||||
|
// just a copy paste from discord.net in order to rename it, for compatibility iwth v3 which is gonna use custom lib
|
||||||
|
|
||||||
namespace Discord
|
// Summary:
|
||||||
|
// Defines the available permissions for a channel.
|
||||||
|
[Flags]
|
||||||
|
public enum GuildPerm : ulong
|
||||||
{
|
{
|
||||||
// just a copy paste from discord.net in order to rename it, for compatibility iwth v3 which is gonna use custom lib
|
|
||||||
|
|
||||||
|
|
||||||
// Summary:
|
|
||||||
// Defines the available permissions for a channel.
|
|
||||||
[Flags]
|
|
||||||
public enum GuildPerm : ulong
|
|
||||||
{
|
|
||||||
//
|
|
||||||
// Summary:
|
|
||||||
// Allows creation of instant invites.
|
|
||||||
CreateInstantInvite = 1,
|
|
||||||
//
|
|
||||||
// Summary:
|
|
||||||
// Allows kicking members.
|
|
||||||
//
|
|
||||||
// Remarks:
|
|
||||||
// This permission requires the owner account to use two-factor authentication when
|
|
||||||
// used on a guild that has server-wide 2FA enabled.
|
|
||||||
KickMembers = 2,
|
|
||||||
//
|
|
||||||
// Summary:
|
|
||||||
// Allows banning members.
|
|
||||||
//
|
|
||||||
// Remarks:
|
|
||||||
// This permission requires the owner account to use two-factor authentication when
|
|
||||||
// used on a guild that has server-wide 2FA enabled.
|
|
||||||
BanMembers = 4,
|
|
||||||
//
|
|
||||||
// Summary:
|
|
||||||
// Allows all permissions and bypasses channel permission overwrites.
|
|
||||||
//
|
|
||||||
// Remarks:
|
|
||||||
// This permission requires the owner account to use two-factor authentication when
|
|
||||||
// used on a guild that has server-wide 2FA enabled.
|
|
||||||
Administrator = 8,
|
|
||||||
//
|
|
||||||
// Summary:
|
|
||||||
// Allows management and editing of channels.
|
|
||||||
//
|
|
||||||
// Remarks:
|
|
||||||
// This permission requires the owner account to use two-factor authentication when
|
|
||||||
// used on a guild that has server-wide 2FA enabled.
|
|
||||||
ManageChannels = 16,
|
|
||||||
//
|
|
||||||
// Summary:
|
|
||||||
// Allows management and editing of the guild.
|
|
||||||
//
|
|
||||||
// Remarks:
|
|
||||||
// This permission requires the owner account to use two-factor authentication when
|
|
||||||
// used on a guild that has server-wide 2FA enabled.
|
|
||||||
ManageGuild = 32,
|
|
||||||
//
|
|
||||||
// Summary:
|
|
||||||
// Allows for the addition of reactions to messages.
|
|
||||||
AddReactions = 64,
|
|
||||||
//
|
|
||||||
// Summary:
|
|
||||||
// Allows for viewing of audit logs.
|
|
||||||
ViewAuditLog = 128,
|
|
||||||
PrioritySpeaker = 256,
|
|
||||||
ReadMessages = 1024,
|
|
||||||
ViewChannel = 1024,
|
|
||||||
SendMessages = 2048,
|
|
||||||
//
|
|
||||||
// Summary:
|
|
||||||
// Allows for sending of text-to-speech messages.
|
|
||||||
SendTTSMessages = 4096,
|
|
||||||
//
|
|
||||||
// Summary:
|
|
||||||
// Allows for deletion of other users messages.
|
|
||||||
//
|
|
||||||
// Remarks:
|
|
||||||
// This permission requires the owner account to use two-factor authentication when
|
|
||||||
// used on a guild that has server-wide 2FA enabled.
|
|
||||||
ManageMessages = 8192,
|
|
||||||
//
|
|
||||||
// Summary:
|
|
||||||
// Allows links sent by users with this permission will be auto-embedded.
|
|
||||||
EmbedLinks = 16384,
|
|
||||||
//
|
|
||||||
// Summary:
|
|
||||||
// Allows for uploading images and files.
|
|
||||||
AttachFiles = 32768,
|
|
||||||
//
|
|
||||||
// Summary:
|
|
||||||
// Allows for reading of message history.
|
|
||||||
ReadMessageHistory = 65536,
|
|
||||||
//
|
|
||||||
// Summary:
|
|
||||||
// Allows for using the @everyone tag to notify all users in a channel, and the
|
|
||||||
// @here tag to notify all online users in a channel.
|
|
||||||
MentionEveryone = 131072,
|
|
||||||
//
|
|
||||||
// Summary:
|
|
||||||
// Allows the usage of custom emojis from other servers.
|
|
||||||
UseExternalEmojis = 262144,
|
|
||||||
//
|
|
||||||
// Summary:
|
|
||||||
// Allows for joining of a voice channel.
|
|
||||||
Connect = 1048576,
|
|
||||||
//
|
|
||||||
// Summary:
|
|
||||||
// Allows for speaking in a voice channel.
|
|
||||||
Speak = 2097152,
|
|
||||||
//
|
|
||||||
// Summary:
|
|
||||||
// Allows for muting members in a voice channel.
|
|
||||||
MuteMembers = 4194304,
|
|
||||||
//
|
|
||||||
// Summary:
|
|
||||||
// Allows for deafening of members in a voice channel.
|
|
||||||
DeafenMembers = 8388608,
|
|
||||||
//
|
|
||||||
// Summary:
|
|
||||||
// Allows for moving of members between voice channels.
|
|
||||||
MoveMembers = 16777216,
|
|
||||||
//
|
|
||||||
// Summary:
|
|
||||||
// Allows for using voice-activity-detection in a voice channel.
|
|
||||||
UseVAD = 33554432,
|
|
||||||
//
|
|
||||||
// Summary:
|
|
||||||
// Allows for modification of own nickname.
|
|
||||||
ChangeNickname = 67108864,
|
|
||||||
//
|
|
||||||
// Summary:
|
|
||||||
// Allows for modification of other users nicknames.
|
|
||||||
ManageNicknames = 134217728,
|
|
||||||
//
|
|
||||||
// Summary:
|
|
||||||
// Allows management and editing of roles.
|
|
||||||
//
|
|
||||||
// Remarks:
|
|
||||||
// This permission requires the owner account to use two-factor authentication when
|
|
||||||
// used on a guild that has server-wide 2FA enabled.
|
|
||||||
ManageRoles = 268435456,
|
|
||||||
//
|
|
||||||
// Summary:
|
|
||||||
// Allows management and editing of webhooks.
|
|
||||||
//
|
|
||||||
// Remarks:
|
|
||||||
// This permission requires the owner account to use two-factor authentication when
|
|
||||||
// used on a guild that has server-wide 2FA enabled.
|
|
||||||
ManageWebhooks = 536870912,
|
|
||||||
//
|
|
||||||
// Summary:
|
|
||||||
// Allows management and editing of emojis.
|
|
||||||
//
|
|
||||||
// Remarks:
|
|
||||||
// This permission requires the owner account to use two-factor authentication when
|
|
||||||
// used on a guild that has server-wide 2FA enabled.
|
|
||||||
ManageEmojis = 1073741824
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// Summary:
|
// Summary:
|
||||||
// Defines the available permissions for a channel.
|
// Allows creation of instant invites.
|
||||||
[Flags]
|
CreateInstantInvite = 1,
|
||||||
public enum ChannelPerm : ulong
|
//
|
||||||
{
|
// Summary:
|
||||||
//
|
// Allows kicking members.
|
||||||
// Summary:
|
//
|
||||||
// Allows creation of instant invites.
|
// Remarks:
|
||||||
CreateInstantInvite = 1,
|
// This permission requires the owner account to use two-factor authentication when
|
||||||
//
|
// used on a guild that has server-wide 2FA enabled.
|
||||||
// Summary:
|
KickMembers = 2,
|
||||||
// Allows management and editing of channels.
|
//
|
||||||
ManageChannel = 16,
|
// Summary:
|
||||||
//
|
// Allows banning members.
|
||||||
// Summary:
|
//
|
||||||
// Allows for the addition of reactions to messages.
|
// Remarks:
|
||||||
AddReactions = 64,
|
// This permission requires the owner account to use two-factor authentication when
|
||||||
PrioritySpeaker = 256,
|
// used on a guild that has server-wide 2FA enabled.
|
||||||
//
|
BanMembers = 4,
|
||||||
// Summary:
|
//
|
||||||
// Allows for reading of messages. This flag is obsolete, use Discord.ChannelPermission.ViewChannel
|
// Summary:
|
||||||
// instead.
|
// Allows all permissions and bypasses channel permission overwrites.
|
||||||
ReadMessages = 1024,
|
//
|
||||||
//
|
// Remarks:
|
||||||
// Summary:
|
// This permission requires the owner account to use two-factor authentication when
|
||||||
// Allows guild members to view a channel, which includes reading messages in text
|
// used on a guild that has server-wide 2FA enabled.
|
||||||
// channels.
|
Administrator = 8,
|
||||||
ViewChannel = 1024,
|
//
|
||||||
//
|
// Summary:
|
||||||
// Summary:
|
// Allows management and editing of channels.
|
||||||
// Allows for sending messages in a channel.
|
//
|
||||||
SendMessages = 2048,
|
// Remarks:
|
||||||
//
|
// This permission requires the owner account to use two-factor authentication when
|
||||||
// Summary:
|
// used on a guild that has server-wide 2FA enabled.
|
||||||
// Allows for sending of text-to-speech messages.
|
ManageChannels = 16,
|
||||||
SendTTSMessages = 4096,
|
//
|
||||||
//
|
// Summary:
|
||||||
// Summary:
|
// Allows management and editing of the guild.
|
||||||
// Allows for deletion of other users messages.
|
//
|
||||||
ManageMessages = 8192,
|
// Remarks:
|
||||||
//
|
// This permission requires the owner account to use two-factor authentication when
|
||||||
// Summary:
|
// used on a guild that has server-wide 2FA enabled.
|
||||||
// Allows links sent by users with this permission will be auto-embedded.
|
ManageGuild = 32,
|
||||||
EmbedLinks = 16384,
|
//
|
||||||
//
|
// Summary:
|
||||||
// Summary:
|
// Allows for the addition of reactions to messages.
|
||||||
// Allows for uploading images and files.
|
AddReactions = 64,
|
||||||
AttachFiles = 32768,
|
//
|
||||||
//
|
// Summary:
|
||||||
// Summary:
|
// Allows for viewing of audit logs.
|
||||||
// Allows for reading of message history.
|
ViewAuditLog = 128,
|
||||||
ReadMessageHistory = 65536,
|
PrioritySpeaker = 256,
|
||||||
//
|
ReadMessages = 1024,
|
||||||
// Summary:
|
ViewChannel = 1024,
|
||||||
// Allows for using the @everyone tag to notify all users in a channel, and the
|
SendMessages = 2048,
|
||||||
// @here tag to notify all online users in a channel.
|
//
|
||||||
MentionEveryone = 131072,
|
// Summary:
|
||||||
//
|
// Allows for sending of text-to-speech messages.
|
||||||
// Summary:
|
SendTTSMessages = 4096,
|
||||||
// Allows the usage of custom emojis from other servers.
|
//
|
||||||
UseExternalEmojis = 262144,
|
// Summary:
|
||||||
//
|
// Allows for deletion of other users messages.
|
||||||
// Summary:
|
//
|
||||||
// Allows for joining of a voice channel.
|
// Remarks:
|
||||||
Connect = 1048576,
|
// This permission requires the owner account to use two-factor authentication when
|
||||||
//
|
// used on a guild that has server-wide 2FA enabled.
|
||||||
// Summary:
|
ManageMessages = 8192,
|
||||||
// Allows for speaking in a voice channel.
|
//
|
||||||
Speak = 2097152,
|
// Summary:
|
||||||
//
|
// Allows links sent by users with this permission will be auto-embedded.
|
||||||
// Summary:
|
EmbedLinks = 16384,
|
||||||
// Allows for muting members in a voice channel.
|
//
|
||||||
MuteMembers = 4194304,
|
// Summary:
|
||||||
//
|
// Allows for uploading images and files.
|
||||||
// Summary:
|
AttachFiles = 32768,
|
||||||
// Allows for deafening of members in a voice channel.
|
//
|
||||||
DeafenMembers = 8388608,
|
// Summary:
|
||||||
//
|
// Allows for reading of message history.
|
||||||
// Summary:
|
ReadMessageHistory = 65536,
|
||||||
// Allows for moving of members between voice channels.
|
//
|
||||||
MoveMembers = 16777216,
|
// Summary:
|
||||||
//
|
// Allows for using the @everyone tag to notify all users in a channel, and the
|
||||||
// Summary:
|
// @here tag to notify all online users in a channel.
|
||||||
// Allows for using voice-activity-detection in a voice channel.
|
MentionEveryone = 131072,
|
||||||
UseVAD = 33554432,
|
//
|
||||||
//
|
// Summary:
|
||||||
// Summary:
|
// Allows the usage of custom emojis from other servers.
|
||||||
// Allows management and editing of roles.
|
UseExternalEmojis = 262144,
|
||||||
ManageRoles = 268435456,
|
//
|
||||||
//
|
// Summary:
|
||||||
// Summary:
|
// Allows for joining of a voice channel.
|
||||||
// Allows management and editing of webhooks.
|
Connect = 1048576,
|
||||||
ManageWebhooks = 536870912
|
//
|
||||||
}
|
// Summary:
|
||||||
|
// Allows for speaking in a voice channel.
|
||||||
|
Speak = 2097152,
|
||||||
|
//
|
||||||
|
// Summary:
|
||||||
|
// Allows for muting members in a voice channel.
|
||||||
|
MuteMembers = 4194304,
|
||||||
|
//
|
||||||
|
// Summary:
|
||||||
|
// Allows for deafening of members in a voice channel.
|
||||||
|
DeafenMembers = 8388608,
|
||||||
|
//
|
||||||
|
// Summary:
|
||||||
|
// Allows for moving of members between voice channels.
|
||||||
|
MoveMembers = 16777216,
|
||||||
|
//
|
||||||
|
// Summary:
|
||||||
|
// Allows for using voice-activity-detection in a voice channel.
|
||||||
|
UseVAD = 33554432,
|
||||||
|
//
|
||||||
|
// Summary:
|
||||||
|
// Allows for modification of own nickname.
|
||||||
|
ChangeNickname = 67108864,
|
||||||
|
//
|
||||||
|
// Summary:
|
||||||
|
// Allows for modification of other users nicknames.
|
||||||
|
ManageNicknames = 134217728,
|
||||||
|
//
|
||||||
|
// Summary:
|
||||||
|
// Allows management and editing of roles.
|
||||||
|
//
|
||||||
|
// Remarks:
|
||||||
|
// This permission requires the owner account to use two-factor authentication when
|
||||||
|
// used on a guild that has server-wide 2FA enabled.
|
||||||
|
ManageRoles = 268435456,
|
||||||
|
//
|
||||||
|
// Summary:
|
||||||
|
// Allows management and editing of webhooks.
|
||||||
|
//
|
||||||
|
// Remarks:
|
||||||
|
// This permission requires the owner account to use two-factor authentication when
|
||||||
|
// used on a guild that has server-wide 2FA enabled.
|
||||||
|
ManageWebhooks = 536870912,
|
||||||
|
//
|
||||||
|
// Summary:
|
||||||
|
// Allows management and editing of emojis.
|
||||||
|
//
|
||||||
|
// Remarks:
|
||||||
|
// This permission requires the owner account to use two-factor authentication when
|
||||||
|
// used on a guild that has server-wide 2FA enabled.
|
||||||
|
ManageEmojis = 1073741824
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Summary:
|
||||||
|
// Defines the available permissions for a channel.
|
||||||
|
[Flags]
|
||||||
|
public enum ChannelPerm : ulong
|
||||||
|
{
|
||||||
|
//
|
||||||
|
// Summary:
|
||||||
|
// Allows creation of instant invites.
|
||||||
|
CreateInstantInvite = 1,
|
||||||
|
//
|
||||||
|
// Summary:
|
||||||
|
// Allows management and editing of channels.
|
||||||
|
ManageChannel = 16,
|
||||||
|
//
|
||||||
|
// Summary:
|
||||||
|
// Allows for the addition of reactions to messages.
|
||||||
|
AddReactions = 64,
|
||||||
|
PrioritySpeaker = 256,
|
||||||
|
//
|
||||||
|
// Summary:
|
||||||
|
// Allows for reading of messages. This flag is obsolete, use Discord.ChannelPermission.ViewChannel
|
||||||
|
// instead.
|
||||||
|
ReadMessages = 1024,
|
||||||
|
//
|
||||||
|
// Summary:
|
||||||
|
// Allows guild members to view a channel, which includes reading messages in text
|
||||||
|
// channels.
|
||||||
|
ViewChannel = 1024,
|
||||||
|
//
|
||||||
|
// Summary:
|
||||||
|
// Allows for sending messages in a channel.
|
||||||
|
SendMessages = 2048,
|
||||||
|
//
|
||||||
|
// Summary:
|
||||||
|
// Allows for sending of text-to-speech messages.
|
||||||
|
SendTTSMessages = 4096,
|
||||||
|
//
|
||||||
|
// Summary:
|
||||||
|
// Allows for deletion of other users messages.
|
||||||
|
ManageMessages = 8192,
|
||||||
|
//
|
||||||
|
// Summary:
|
||||||
|
// Allows links sent by users with this permission will be auto-embedded.
|
||||||
|
EmbedLinks = 16384,
|
||||||
|
//
|
||||||
|
// Summary:
|
||||||
|
// Allows for uploading images and files.
|
||||||
|
AttachFiles = 32768,
|
||||||
|
//
|
||||||
|
// Summary:
|
||||||
|
// Allows for reading of message history.
|
||||||
|
ReadMessageHistory = 65536,
|
||||||
|
//
|
||||||
|
// Summary:
|
||||||
|
// Allows for using the @everyone tag to notify all users in a channel, and the
|
||||||
|
// @here tag to notify all online users in a channel.
|
||||||
|
MentionEveryone = 131072,
|
||||||
|
//
|
||||||
|
// Summary:
|
||||||
|
// Allows the usage of custom emojis from other servers.
|
||||||
|
UseExternalEmojis = 262144,
|
||||||
|
//
|
||||||
|
// Summary:
|
||||||
|
// Allows for joining of a voice channel.
|
||||||
|
Connect = 1048576,
|
||||||
|
//
|
||||||
|
// Summary:
|
||||||
|
// Allows for speaking in a voice channel.
|
||||||
|
Speak = 2097152,
|
||||||
|
//
|
||||||
|
// Summary:
|
||||||
|
// Allows for muting members in a voice channel.
|
||||||
|
MuteMembers = 4194304,
|
||||||
|
//
|
||||||
|
// Summary:
|
||||||
|
// Allows for deafening of members in a voice channel.
|
||||||
|
DeafenMembers = 8388608,
|
||||||
|
//
|
||||||
|
// Summary:
|
||||||
|
// Allows for moving of members between voice channels.
|
||||||
|
MoveMembers = 16777216,
|
||||||
|
//
|
||||||
|
// Summary:
|
||||||
|
// Allows for using voice-activity-detection in a voice channel.
|
||||||
|
UseVAD = 33554432,
|
||||||
|
//
|
||||||
|
// Summary:
|
||||||
|
// Allows management and editing of roles.
|
||||||
|
ManageRoles = 268435456,
|
||||||
|
//
|
||||||
|
// Summary:
|
||||||
|
// Allows management and editing of webhooks.
|
||||||
|
ManageWebhooks = 536870912
|
||||||
|
}
|
@@ -1,15 +1,12 @@
|
|||||||
using System;
|
namespace NadekoBot.Common;
|
||||||
|
|
||||||
namespace NadekoBot.Common
|
public static class Helpers
|
||||||
{
|
{
|
||||||
public static class Helpers
|
public static void ReadErrorAndExit(int exitCode)
|
||||||
{
|
{
|
||||||
public static void ReadErrorAndExit(int exitCode)
|
if (!Console.IsInputRedirected)
|
||||||
{
|
Console.ReadKey();
|
||||||
if (!Console.IsInputRedirected)
|
|
||||||
Console.ReadKey();
|
|
||||||
|
|
||||||
Environment.Exit(exitCode);
|
Environment.Exit(exitCode);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,36 +1,31 @@
|
|||||||
using System.Collections.Generic;
|
using NadekoBot.Common;
|
||||||
using Discord;
|
|
||||||
using System.Collections.Immutable;
|
|
||||||
using System.Linq;
|
|
||||||
using NadekoBot.Common;
|
|
||||||
|
|
||||||
namespace NadekoBot
|
namespace NadekoBot;
|
||||||
|
|
||||||
|
public interface IBotCredentials
|
||||||
{
|
{
|
||||||
public interface IBotCredentials
|
string Token { get; }
|
||||||
{
|
string GoogleApiKey { get; }
|
||||||
string Token { get; }
|
ICollection<ulong> OwnerIds { get; }
|
||||||
string GoogleApiKey { get; }
|
string RapidApiKey { get; }
|
||||||
ICollection<ulong> OwnerIds { get; }
|
|
||||||
string RapidApiKey { get; }
|
|
||||||
|
|
||||||
Creds.DbOptions Db { get; }
|
Creds.DbOptions Db { get; }
|
||||||
string OsuApiKey { get; }
|
string OsuApiKey { get; }
|
||||||
int TotalShards { get; }
|
int TotalShards { get; }
|
||||||
Creds.PatreonSettings Patreon { get; }
|
Creds.PatreonSettings Patreon { get; }
|
||||||
string CleverbotApiKey { get; }
|
string CleverbotApiKey { get; }
|
||||||
RestartConfig RestartCommand { get; }
|
RestartConfig RestartCommand { get; }
|
||||||
Creds.VotesSettings Votes { get; }
|
Creds.VotesSettings Votes { get; }
|
||||||
string BotListToken { get; }
|
string BotListToken { get; }
|
||||||
string RedisOptions { get; }
|
string RedisOptions { get; }
|
||||||
string LocationIqApiKey { get; }
|
string LocationIqApiKey { get; }
|
||||||
string TimezoneDbApiKey { get; }
|
string TimezoneDbApiKey { get; }
|
||||||
string CoinmarketcapApiKey { get; }
|
string CoinmarketcapApiKey { get; }
|
||||||
string CoordinatorUrl { get; set; }
|
string CoordinatorUrl { get; set; }
|
||||||
}
|
|
||||||
|
|
||||||
public class RestartConfig
|
|
||||||
{
|
|
||||||
public string Cmd { get; set; }
|
|
||||||
public string Args { get; set; }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class RestartConfig
|
||||||
|
{
|
||||||
|
public string Cmd { get; set; }
|
||||||
|
public string Args { get; set; }
|
||||||
|
}
|
@@ -1,7 +1,6 @@
|
|||||||
namespace NadekoBot.Common
|
namespace NadekoBot.Common;
|
||||||
|
|
||||||
|
public interface ICloneable<T> where T : new()
|
||||||
{
|
{
|
||||||
public interface ICloneable<T> where T : new()
|
public T Clone();
|
||||||
{
|
|
||||||
public T Clone();
|
|
||||||
}
|
|
||||||
}
|
}
|
@@ -1,25 +1,24 @@
|
|||||||
using Discord;
|
using Discord;
|
||||||
|
|
||||||
namespace NadekoBot
|
namespace NadekoBot;
|
||||||
{
|
|
||||||
public interface IEmbedBuilder
|
|
||||||
{
|
|
||||||
IEmbedBuilder WithDescription(string desc);
|
|
||||||
IEmbedBuilder WithTitle(string title);
|
|
||||||
IEmbedBuilder AddField(string title, object value, bool isInline = false);
|
|
||||||
IEmbedBuilder WithFooter(string text, string iconUrl = null);
|
|
||||||
IEmbedBuilder WithAuthor(string name, string iconUrl = null, string url = null);
|
|
||||||
IEmbedBuilder WithColor(EmbedColor color);
|
|
||||||
Embed Build();
|
|
||||||
IEmbedBuilder WithUrl(string url);
|
|
||||||
IEmbedBuilder WithImageUrl(string url);
|
|
||||||
IEmbedBuilder WithThumbnailUrl(string url);
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum EmbedColor
|
public interface IEmbedBuilder
|
||||||
{
|
{
|
||||||
Ok,
|
IEmbedBuilder WithDescription(string desc);
|
||||||
Pending,
|
IEmbedBuilder WithTitle(string title);
|
||||||
Error,
|
IEmbedBuilder AddField(string title, object value, bool isInline = false);
|
||||||
}
|
IEmbedBuilder WithFooter(string text, string iconUrl = null);
|
||||||
|
IEmbedBuilder WithAuthor(string name, string iconUrl = null, string url = null);
|
||||||
|
IEmbedBuilder WithColor(EmbedColor color);
|
||||||
|
Embed Build();
|
||||||
|
IEmbedBuilder WithUrl(string url);
|
||||||
|
IEmbedBuilder WithImageUrl(string url);
|
||||||
|
IEmbedBuilder WithThumbnailUrl(string url);
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum EmbedColor
|
||||||
|
{
|
||||||
|
Ok,
|
||||||
|
Pending,
|
||||||
|
Error,
|
||||||
}
|
}
|
@@ -1,7 +1,6 @@
|
|||||||
namespace NadekoBot.Common
|
namespace NadekoBot.Common;
|
||||||
|
|
||||||
|
public interface INadekoCommandOptions
|
||||||
{
|
{
|
||||||
public interface INadekoCommandOptions
|
void NormalizeOptions();
|
||||||
{
|
}
|
||||||
void NormalizeOptions();
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,10 +1,6 @@
|
|||||||
using System;
|
namespace NadekoBot.Common;
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace NadekoBot.Common
|
public interface IPlaceholderProvider
|
||||||
{
|
{
|
||||||
public interface IPlaceholderProvider
|
public IEnumerable<(string Name, Func<string> Func)> GetPlaceholders();
|
||||||
{
|
|
||||||
public IEnumerable<(string Name, Func<string> Func)> GetPlaceholders();
|
|
||||||
}
|
|
||||||
}
|
}
|
@@ -1,50 +1,48 @@
|
|||||||
using System;
|
using NadekoBot.Common.Yml;
|
||||||
using NadekoBot.Common.Yml;
|
|
||||||
|
|
||||||
namespace NadekoBot.Common
|
namespace NadekoBot.Common;
|
||||||
|
|
||||||
|
public class ImageUrls
|
||||||
{
|
{
|
||||||
public class ImageUrls
|
[Comment("DO NOT CHANGE")]
|
||||||
|
public int Version { get; set; } = 3;
|
||||||
|
|
||||||
|
public CoinData Coins { get; set; }
|
||||||
|
public Uri[] Currency { get; set; }
|
||||||
|
public Uri[] Dice { get; set; }
|
||||||
|
public RategirlData Rategirl { get; set; }
|
||||||
|
public XpData Xp { get; set; }
|
||||||
|
|
||||||
|
//new
|
||||||
|
public RipData Rip { get; set; }
|
||||||
|
public SlotData Slots { get; set; }
|
||||||
|
|
||||||
|
public class RipData
|
||||||
{
|
{
|
||||||
[Comment("DO NOT CHANGE")]
|
public Uri Bg { get; set; }
|
||||||
public int Version { get; set; } = 3;
|
public Uri Overlay { get; set; }
|
||||||
|
|
||||||
public CoinData Coins { get; set; }
|
|
||||||
public Uri[] Currency { get; set; }
|
|
||||||
public Uri[] Dice { get; set; }
|
|
||||||
public RategirlData Rategirl { get; set; }
|
|
||||||
public XpData Xp { get; set; }
|
|
||||||
|
|
||||||
//new
|
|
||||||
public RipData Rip { get; set; }
|
|
||||||
public SlotData Slots { get; set; }
|
|
||||||
|
|
||||||
public class RipData
|
|
||||||
{
|
|
||||||
public Uri Bg { get; set; }
|
|
||||||
public Uri Overlay { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class SlotData
|
|
||||||
{
|
|
||||||
public Uri[] Emojis { get; set; }
|
|
||||||
public Uri Bg { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class CoinData
|
|
||||||
{
|
|
||||||
public Uri[] Heads { get; set; }
|
|
||||||
public Uri[] Tails { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class RategirlData
|
|
||||||
{
|
|
||||||
public Uri Matrix { get; set; }
|
|
||||||
public Uri Dot { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class XpData
|
|
||||||
{
|
|
||||||
public Uri Bg { get; set; }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
public class SlotData
|
||||||
|
{
|
||||||
|
public Uri[] Emojis { get; set; }
|
||||||
|
public Uri Bg { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class CoinData
|
||||||
|
{
|
||||||
|
public Uri[] Heads { get; set; }
|
||||||
|
public Uri[] Tails { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class RategirlData
|
||||||
|
{
|
||||||
|
public Uri Matrix { get; set; }
|
||||||
|
public Uri Dot { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class XpData
|
||||||
|
{
|
||||||
|
public Uri Bg { get; set; }
|
||||||
|
}
|
||||||
|
}
|
@@ -1,34 +1,32 @@
|
|||||||
using System;
|
using System.Globalization;
|
||||||
using System.Globalization;
|
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
using SixLabors.ImageSharp.PixelFormats;
|
using SixLabors.ImageSharp.PixelFormats;
|
||||||
|
|
||||||
namespace NadekoBot.Common.JsonConverters
|
namespace NadekoBot.Common.JsonConverters;
|
||||||
|
|
||||||
|
public class Rgba32Converter : JsonConverter<Rgba32>
|
||||||
{
|
{
|
||||||
public class Rgba32Converter : JsonConverter<Rgba32>
|
public override Rgba32 Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||||
{
|
{
|
||||||
public override Rgba32 Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
return Rgba32.ParseHex(reader.GetString());
|
||||||
{
|
|
||||||
return Rgba32.ParseHex(reader.GetString());
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Write(Utf8JsonWriter writer, Rgba32 value, JsonSerializerOptions options)
|
|
||||||
{
|
|
||||||
writer.WriteStringValue(value.ToHex());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class CultureInfoConverter : JsonConverter<CultureInfo>
|
|
||||||
{
|
|
||||||
public override CultureInfo Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
|
||||||
{
|
|
||||||
return new CultureInfo(reader.GetString());
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Write(Utf8JsonWriter writer, CultureInfo value, JsonSerializerOptions options)
|
public override void Write(Utf8JsonWriter writer, Rgba32 value, JsonSerializerOptions options)
|
||||||
{
|
{
|
||||||
writer.WriteStringValue(value.Name);
|
writer.WriteStringValue(value.ToHex());
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class CultureInfoConverter : JsonConverter<CultureInfo>
|
||||||
|
{
|
||||||
|
public override CultureInfo Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||||
|
{
|
||||||
|
return new CultureInfo(reader.GetString());
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Write(Utf8JsonWriter writer, CultureInfo value, JsonSerializerOptions options)
|
||||||
|
{
|
||||||
|
writer.WriteStringValue(value.Name);
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,98 +1,96 @@
|
|||||||
using System;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
|
|
||||||
namespace NadekoBot.Common
|
namespace NadekoBot.Common;
|
||||||
|
|
||||||
|
// needs proper invalid input check (character array input out of range)
|
||||||
|
// needs negative number support
|
||||||
|
public readonly struct kwum : IEquatable<kwum>
|
||||||
{
|
{
|
||||||
// needs proper invalid input check (character array input out of range)
|
private readonly int _value;
|
||||||
// needs negative number support
|
private const string ValidCharacters = "23456789abcdefghijkmnpqrstuvwxyz";
|
||||||
public readonly struct kwum : IEquatable<kwum>
|
|
||||||
|
public kwum(int num)
|
||||||
|
=> _value = num;
|
||||||
|
|
||||||
|
public kwum(in char c)
|
||||||
{
|
{
|
||||||
private readonly int _value;
|
if (!IsValidChar(c))
|
||||||
private const string ValidCharacters = "23456789abcdefghijkmnpqrstuvwxyz";
|
throw new ArgumentException("Character needs to be a valid kwum character.", nameof(c));
|
||||||
|
|
||||||
public kwum(int num)
|
_value = InternalCharToValue(c);
|
||||||
=> _value = num;
|
}
|
||||||
|
|
||||||
public kwum(in char c)
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private static int InternalCharToValue(in char c)
|
||||||
|
=> ValidCharacters.IndexOf(c);
|
||||||
|
|
||||||
|
public kwum(in ReadOnlySpan<char> input)
|
||||||
|
{;
|
||||||
|
_value = 0;
|
||||||
|
for (var index = 0; index < input.Length; index++)
|
||||||
{
|
{
|
||||||
|
var c = input[index];
|
||||||
if (!IsValidChar(c))
|
if (!IsValidChar(c))
|
||||||
throw new ArgumentException("Character needs to be a valid kwum character.", nameof(c));
|
throw new ArgumentException("All characters need to be a valid kwum characters.", nameof(input));
|
||||||
|
|
||||||
_value = InternalCharToValue(c);
|
_value += ValidCharacters.IndexOf(c) * (int)Math.Pow(ValidCharacters.Length, input.Length - index - 1);
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
private static int InternalCharToValue(in char c)
|
|
||||||
=> ValidCharacters.IndexOf(c);
|
|
||||||
|
|
||||||
public kwum(in ReadOnlySpan<char> input)
|
|
||||||
{;
|
|
||||||
_value = 0;
|
|
||||||
for (var index = 0; index < input.Length; index++)
|
|
||||||
{
|
|
||||||
var c = input[index];
|
|
||||||
if (!IsValidChar(c))
|
|
||||||
throw new ArgumentException("All characters need to be a valid kwum characters.", nameof(input));
|
|
||||||
|
|
||||||
_value += ValidCharacters.IndexOf(c) * (int)Math.Pow(ValidCharacters.Length, input.Length - index - 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool TryParse(in ReadOnlySpan<char> input, out kwum value)
|
|
||||||
{
|
|
||||||
value = default;
|
|
||||||
foreach(var c in input)
|
|
||||||
if (!IsValidChar(c))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
value = new kwum(input);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static kwum operator +(kwum left, kwum right)
|
|
||||||
=> new kwum(left._value + right._value);
|
|
||||||
|
|
||||||
public static bool operator ==(kwum left, kwum right)
|
|
||||||
=> left._value == right._value;
|
|
||||||
|
|
||||||
public static bool operator !=(kwum left, kwum right)
|
|
||||||
=> !(left == right);
|
|
||||||
|
|
||||||
public static implicit operator long(kwum kwum)
|
|
||||||
=> kwum._value;
|
|
||||||
|
|
||||||
public static implicit operator int(kwum kwum)
|
|
||||||
=> kwum._value;
|
|
||||||
public static implicit operator kwum(int num)
|
|
||||||
=> new kwum(num);
|
|
||||||
|
|
||||||
public static bool IsValidChar(char c)
|
|
||||||
=> ValidCharacters.Contains(c);
|
|
||||||
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
var count = ValidCharacters.Length;
|
|
||||||
var localValue = _value;
|
|
||||||
var arrSize = (int)Math.Log(localValue, count) + 1;
|
|
||||||
Span<char> chars = new char[arrSize];
|
|
||||||
while (localValue > 0)
|
|
||||||
{
|
|
||||||
localValue = Math.DivRem(localValue, count, out var rem);
|
|
||||||
chars[--arrSize] = ValidCharacters[(int)rem];
|
|
||||||
}
|
|
||||||
|
|
||||||
return new string(chars);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool Equals(object obj)
|
|
||||||
=> obj is kwum kw && kw == this;
|
|
||||||
|
|
||||||
public bool Equals(kwum other)
|
|
||||||
=> other == this;
|
|
||||||
|
|
||||||
public override int GetHashCode()
|
|
||||||
{
|
|
||||||
return _value.GetHashCode();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static bool TryParse(in ReadOnlySpan<char> input, out kwum value)
|
||||||
|
{
|
||||||
|
value = default;
|
||||||
|
foreach(var c in input)
|
||||||
|
if (!IsValidChar(c))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
value = new kwum(input);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static kwum operator +(kwum left, kwum right)
|
||||||
|
=> new kwum(left._value + right._value);
|
||||||
|
|
||||||
|
public static bool operator ==(kwum left, kwum right)
|
||||||
|
=> left._value == right._value;
|
||||||
|
|
||||||
|
public static bool operator !=(kwum left, kwum right)
|
||||||
|
=> !(left == right);
|
||||||
|
|
||||||
|
public static implicit operator long(kwum kwum)
|
||||||
|
=> kwum._value;
|
||||||
|
|
||||||
|
public static implicit operator int(kwum kwum)
|
||||||
|
=> kwum._value;
|
||||||
|
public static implicit operator kwum(int num)
|
||||||
|
=> new kwum(num);
|
||||||
|
|
||||||
|
public static bool IsValidChar(char c)
|
||||||
|
=> ValidCharacters.Contains(c);
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
var count = ValidCharacters.Length;
|
||||||
|
var localValue = _value;
|
||||||
|
var arrSize = (int)Math.Log(localValue, count) + 1;
|
||||||
|
Span<char> chars = new char[arrSize];
|
||||||
|
while (localValue > 0)
|
||||||
|
{
|
||||||
|
localValue = Math.DivRem(localValue, count, out var rem);
|
||||||
|
chars[--arrSize] = ValidCharacters[(int)rem];
|
||||||
|
}
|
||||||
|
|
||||||
|
return new string(chars);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Equals(object obj)
|
||||||
|
=> obj is kwum kw && kw == this;
|
||||||
|
|
||||||
|
public bool Equals(kwum other)
|
||||||
|
=> other == this;
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return _value.GetHashCode();
|
||||||
|
}
|
||||||
}
|
}
|
@@ -1,14 +1,13 @@
|
|||||||
using CommandLine;
|
using CommandLine;
|
||||||
|
|
||||||
namespace NadekoBot.Common
|
namespace NadekoBot.Common;
|
||||||
{
|
|
||||||
public class LbOpts : INadekoCommandOptions
|
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 bool Clean { get; set; }
|
||||||
public void NormalizeOptions()
|
public void NormalizeOptions()
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,57 +1,54 @@
|
|||||||
using System;
|
using System.Net;
|
||||||
using System.Net;
|
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using Discord.Net;
|
using Discord.Net;
|
||||||
using Serilog;
|
|
||||||
|
|
||||||
namespace NadekoBot.Common
|
namespace NadekoBot.Common;
|
||||||
|
|
||||||
|
public class LoginErrorHandler
|
||||||
{
|
{
|
||||||
public class LoginErrorHandler
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static void Handle(Exception ex)
|
||||||
{
|
{
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
Log.Fatal(ex, "A fatal error has occurred while attempting to connect to Discord");
|
||||||
public static void Handle(Exception ex)
|
}
|
||||||
{
|
|
||||||
Log.Fatal(ex, "A fatal error has occurred while attempting to connect to Discord");
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static void Handle(HttpException ex)
|
public static void Handle(HttpException ex)
|
||||||
|
{
|
||||||
|
switch (ex.HttpCode)
|
||||||
{
|
{
|
||||||
switch (ex.HttpCode)
|
case HttpStatusCode.Unauthorized:
|
||||||
{
|
Log.Error("Your bot token is wrong.\n" +
|
||||||
case HttpStatusCode.Unauthorized:
|
"You can find the bot token under the Bot tab in the developer page.\n" +
|
||||||
Log.Error("Your bot token is wrong.\n" +
|
"Fix your token in the credentials file and restart the bot");
|
||||||
"You can find the bot token under the Bot tab in the developer page.\n" +
|
break;
|
||||||
"Fix your token in the credentials file and restart the bot");
|
|
||||||
break;
|
|
||||||
|
|
||||||
case HttpStatusCode.BadRequest:
|
case HttpStatusCode.BadRequest:
|
||||||
Log.Error("Something has been incorrectly formatted in your credentials file.\n" +
|
Log.Error("Something has been incorrectly formatted in your credentials file.\n" +
|
||||||
"Use the JSON Guide as reference to fix it and restart the bot.");
|
"Use the JSON Guide as reference to fix it and restart the bot.");
|
||||||
Log.Error("If you are on Linux, make sure Redis is installed and running");
|
Log.Error("If you are on Linux, make sure Redis is installed and running");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case HttpStatusCode.RequestTimeout:
|
case HttpStatusCode.RequestTimeout:
|
||||||
Log.Error("The request timed out. Make sure you have no external program blocking the bot " +
|
Log.Error("The request timed out. Make sure you have no external program blocking the bot " +
|
||||||
"from connecting to the internet");
|
"from connecting to the internet");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case HttpStatusCode.ServiceUnavailable:
|
case HttpStatusCode.ServiceUnavailable:
|
||||||
case HttpStatusCode.InternalServerError:
|
case HttpStatusCode.InternalServerError:
|
||||||
Log.Error("Discord is having internal issues. Please, try again later");
|
Log.Error("Discord is having internal issues. Please, try again later");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case HttpStatusCode.TooManyRequests:
|
case HttpStatusCode.TooManyRequests:
|
||||||
Log.Error("Your bot has been ratelimited by Discord. Please, try again later.\n" +
|
Log.Error("Your bot has been ratelimited by Discord. Please, try again later.\n" +
|
||||||
"Global ratelimits usually last for an hour");
|
"Global ratelimits usually last for an hour");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
Log.Warning("An error occurred while attempting to connect to Discord");
|
Log.Warning("An error occurred while attempting to connect to Discord");
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
|
|
||||||
Log.Fatal(ex.ToString());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Log.Fatal(ex.ToString());
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,14 +1,13 @@
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Discord;
|
using Discord;
|
||||||
|
|
||||||
namespace NadekoBot.Common.ModuleBehaviors
|
namespace NadekoBot.Common.ModuleBehaviors;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Implemented by modules which block execution before anything is executed
|
||||||
|
/// </summary>
|
||||||
|
public interface IEarlyBehavior
|
||||||
{
|
{
|
||||||
/// <summary>
|
int Priority { get; }
|
||||||
/// Implemented by modules which block execution before anything is executed
|
Task<bool> RunBehavior(IGuild guild, IUserMessage msg);
|
||||||
/// </summary>
|
}
|
||||||
public interface IEarlyBehavior
|
|
||||||
{
|
|
||||||
int Priority { get; }
|
|
||||||
Task<bool> RunBehavior(IGuild guild, IUserMessage msg);
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,10 +1,9 @@
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Discord;
|
using Discord;
|
||||||
|
|
||||||
namespace NadekoBot.Common.ModuleBehaviors
|
namespace NadekoBot.Common.ModuleBehaviors;
|
||||||
|
|
||||||
|
public interface IInputTransformer
|
||||||
{
|
{
|
||||||
public interface IInputTransformer
|
Task<string> TransformInput(IGuild guild, IMessageChannel channel, IUser user, string input);
|
||||||
{
|
}
|
||||||
Task<string> TransformInput(IGuild guild, IMessageChannel channel, IUser user, string input);
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,13 +1,11 @@
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Discord.Commands;
|
using Discord.Commands;
|
||||||
using Discord.WebSocket;
|
|
||||||
|
|
||||||
namespace NadekoBot.Common.ModuleBehaviors
|
namespace NadekoBot.Common.ModuleBehaviors;
|
||||||
|
|
||||||
|
public interface ILateBlocker
|
||||||
{
|
{
|
||||||
public interface ILateBlocker
|
public int Priority { get; }
|
||||||
{
|
|
||||||
public int Priority { get; }
|
|
||||||
|
|
||||||
Task<bool> TryBlockLate(ICommandContext context, string moduleName, CommandInfo command);
|
Task<bool> TryBlockLate(ICommandContext context, string moduleName, CommandInfo command);
|
||||||
}
|
}
|
||||||
}
|
|
@@ -1,13 +1,12 @@
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Discord;
|
using Discord;
|
||||||
|
|
||||||
namespace NadekoBot.Common.ModuleBehaviors
|
namespace NadekoBot.Common.ModuleBehaviors;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Last thing to be executed, won't stop further executions
|
||||||
|
/// </summary>
|
||||||
|
public interface ILateExecutor
|
||||||
{
|
{
|
||||||
/// <summary>
|
Task LateExecute(IGuild guild, IUserMessage msg);
|
||||||
/// Last thing to be executed, won't stop further executions
|
}
|
||||||
/// </summary>
|
|
||||||
public interface ILateExecutor
|
|
||||||
{
|
|
||||||
Task LateExecute(IGuild guild, IUserMessage msg);
|
|
||||||
}
|
|
||||||
}
|
|
@@ -2,33 +2,32 @@
|
|||||||
using Discord.WebSocket;
|
using Discord.WebSocket;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace NadekoBot.Common.ModuleBehaviors
|
namespace NadekoBot.Common.ModuleBehaviors;
|
||||||
|
|
||||||
|
public struct ModuleBehaviorResult
|
||||||
{
|
{
|
||||||
public struct ModuleBehaviorResult
|
public bool Blocked { get; set; }
|
||||||
|
public string NewInput { get; set; }
|
||||||
|
|
||||||
|
public static ModuleBehaviorResult None() => new ModuleBehaviorResult
|
||||||
{
|
{
|
||||||
public bool Blocked { get; set; }
|
Blocked = false,
|
||||||
public string NewInput { get; set; }
|
NewInput = null,
|
||||||
|
};
|
||||||
|
|
||||||
public static ModuleBehaviorResult None() => new ModuleBehaviorResult
|
public static ModuleBehaviorResult FromBlocked(bool blocked) => new ModuleBehaviorResult
|
||||||
{
|
|
||||||
Blocked = false,
|
|
||||||
NewInput = null,
|
|
||||||
};
|
|
||||||
|
|
||||||
public static ModuleBehaviorResult FromBlocked(bool blocked) => new ModuleBehaviorResult
|
|
||||||
{
|
|
||||||
Blocked = blocked,
|
|
||||||
NewInput = null,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface IModuleBehavior
|
|
||||||
{
|
{
|
||||||
/// <summary>
|
Blocked = blocked,
|
||||||
/// Negative priority means it will try to apply as early as possible
|
NewInput = null,
|
||||||
/// Positive priority menas it will try to apply as late as possible
|
};
|
||||||
/// </summary>
|
|
||||||
int Priority { get; }
|
|
||||||
Task<ModuleBehaviorResult> ApplyBehavior(DiscordSocketClient client, IGuild guild, IUserMessage msg);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public interface IModuleBehavior
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Negative priority means it will try to apply as early as possible
|
||||||
|
/// Positive priority menas it will try to apply as late as possible
|
||||||
|
/// </summary>
|
||||||
|
int Priority { get; }
|
||||||
|
Task<ModuleBehaviorResult> ApplyBehavior(DiscordSocketClient client, IGuild guild, IUserMessage msg);
|
||||||
|
}
|
@@ -1,16 +1,15 @@
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace NadekoBot.Common.ModuleBehaviors
|
namespace NadekoBot.Common.ModuleBehaviors;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// All services which need to execute something after
|
||||||
|
/// the bot is ready should implement this interface
|
||||||
|
/// </summary>
|
||||||
|
public interface IReadyExecutor
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// All services which need to execute something after
|
/// Executed when bot is ready
|
||||||
/// the bot is ready should implement this interface
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IReadyExecutor
|
public Task OnReadyAsync();
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Executed when bot is ready
|
|
||||||
/// </summary>
|
|
||||||
public Task OnReadyAsync();
|
|
||||||
}
|
|
||||||
}
|
}
|
@@ -6,152 +6,151 @@ using NadekoBot.Extensions;
|
|||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace NadekoBot.Modules
|
namespace NadekoBot.Modules;
|
||||||
|
|
||||||
|
public abstract class NadekoModule : ModuleBase
|
||||||
{
|
{
|
||||||
public abstract class NadekoModule : ModuleBase
|
protected CultureInfo _cultureInfo { get; set; }
|
||||||
|
public IBotStrings Strings { get; set; }
|
||||||
|
public CommandHandler CmdHandler { get; set; }
|
||||||
|
public ILocalization Localization { get; set; }
|
||||||
|
public IEmbedBuilderService _eb { get; set; }
|
||||||
|
|
||||||
|
public string Prefix => CmdHandler.GetPrefix(ctx.Guild);
|
||||||
|
|
||||||
|
protected ICommandContext ctx => Context;
|
||||||
|
|
||||||
|
protected NadekoModule()
|
||||||
{
|
{
|
||||||
protected CultureInfo _cultureInfo { get; set; }
|
}
|
||||||
public IBotStrings Strings { get; set; }
|
|
||||||
public CommandHandler CmdHandler { get; set; }
|
|
||||||
public ILocalization Localization { get; set; }
|
|
||||||
public IEmbedBuilderService _eb { get; set; }
|
|
||||||
|
|
||||||
public string Prefix => CmdHandler.GetPrefix(ctx.Guild);
|
protected override void BeforeExecute(CommandInfo cmd)
|
||||||
|
{
|
||||||
|
_cultureInfo = Localization.GetCultureInfo(ctx.Guild?.Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected string GetText(in LocStr data) =>
|
||||||
|
Strings.GetText(data, _cultureInfo);
|
||||||
|
|
||||||
protected ICommandContext ctx => Context;
|
public Task<IUserMessage> SendErrorAsync(string error)
|
||||||
|
=> ctx.Channel.SendErrorAsync(_eb, error);
|
||||||
|
|
||||||
|
public Task<IUserMessage> SendErrorAsync(string title, string error, string url = null, string footer = null)
|
||||||
|
=> ctx.Channel.SendErrorAsync(_eb, title, error, url, footer);
|
||||||
|
|
||||||
|
public Task<IUserMessage> SendConfirmAsync(string text)
|
||||||
|
=> ctx.Channel.SendConfirmAsync(_eb, text);
|
||||||
|
|
||||||
|
public Task<IUserMessage> SendConfirmAsync(string title, string text, string url = null, string footer = null)
|
||||||
|
=> ctx.Channel.SendConfirmAsync(_eb, title, text, url, footer);
|
||||||
|
|
||||||
|
public Task<IUserMessage> SendPendingAsync(string text)
|
||||||
|
=> ctx.Channel.SendPendingAsync(_eb, text);
|
||||||
|
|
||||||
|
public Task<IUserMessage> ErrorLocalizedAsync(LocStr str)
|
||||||
|
=> SendErrorAsync(GetText(str));
|
||||||
|
|
||||||
protected NadekoModule()
|
public Task<IUserMessage> PendingLocalizedAsync(LocStr str)
|
||||||
|
=> SendPendingAsync(GetText(str));
|
||||||
|
|
||||||
|
public Task<IUserMessage> ConfirmLocalizedAsync(LocStr str)
|
||||||
|
=> SendConfirmAsync(GetText(str));
|
||||||
|
|
||||||
|
public Task<IUserMessage> ReplyErrorLocalizedAsync(LocStr str)
|
||||||
|
=> SendErrorAsync($"{Format.Bold(ctx.User.ToString())} {GetText(str)}");
|
||||||
|
|
||||||
|
public Task<IUserMessage> ReplyPendingLocalizedAsync(LocStr str)
|
||||||
|
=> SendPendingAsync($"{Format.Bold(ctx.User.ToString())} {GetText(str)}");
|
||||||
|
|
||||||
|
public Task<IUserMessage> ReplyConfirmLocalizedAsync(LocStr str)
|
||||||
|
=> SendConfirmAsync($"{Format.Bold(ctx.User.ToString())} {GetText(str)}");
|
||||||
|
|
||||||
|
public async Task<bool> PromptUserConfirmAsync(IEmbedBuilder embed)
|
||||||
|
{
|
||||||
|
embed
|
||||||
|
.WithPendingColor()
|
||||||
|
.WithFooter("yes/no");
|
||||||
|
|
||||||
|
var msg = await ctx.Channel.EmbedAsync(embed).ConfigureAwait(false);
|
||||||
|
try
|
||||||
{
|
{
|
||||||
|
var input = await GetUserInputAsync(ctx.User.Id, ctx.Channel.Id).ConfigureAwait(false);
|
||||||
|
input = input?.ToUpperInvariant();
|
||||||
|
|
||||||
|
if (input != "YES" && input != "Y")
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
var _ = Task.Run(() => msg.DeleteAsync());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TypeConverter typeConverter = TypeDescriptor.GetConverter(propType); ?
|
||||||
|
public async Task<string> GetUserInputAsync(ulong userId, ulong channelId)
|
||||||
|
{
|
||||||
|
var userInputTask = new TaskCompletionSource<string>();
|
||||||
|
var dsc = (DiscordSocketClient)ctx.Client;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
dsc.MessageReceived += MessageReceived;
|
||||||
|
|
||||||
|
if ((await Task.WhenAny(userInputTask.Task, Task.Delay(10000)).ConfigureAwait(false)) != userInputTask.Task)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return await userInputTask.Task.ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
dsc.MessageReceived -= MessageReceived;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void BeforeExecute(CommandInfo cmd)
|
Task MessageReceived(SocketMessage arg)
|
||||||
{
|
{
|
||||||
_cultureInfo = Localization.GetCultureInfo(ctx.Guild?.Id);
|
var _ = Task.Run(() =>
|
||||||
}
|
|
||||||
|
|
||||||
protected string GetText(in LocStr data) =>
|
|
||||||
Strings.GetText(data, _cultureInfo);
|
|
||||||
|
|
||||||
public Task<IUserMessage> SendErrorAsync(string error)
|
|
||||||
=> ctx.Channel.SendErrorAsync(_eb, error);
|
|
||||||
|
|
||||||
public Task<IUserMessage> SendErrorAsync(string title, string error, string url = null, string footer = null)
|
|
||||||
=> ctx.Channel.SendErrorAsync(_eb, title, error, url, footer);
|
|
||||||
|
|
||||||
public Task<IUserMessage> SendConfirmAsync(string text)
|
|
||||||
=> ctx.Channel.SendConfirmAsync(_eb, text);
|
|
||||||
|
|
||||||
public Task<IUserMessage> SendConfirmAsync(string title, string text, string url = null, string footer = null)
|
|
||||||
=> ctx.Channel.SendConfirmAsync(_eb, title, text, url, footer);
|
|
||||||
|
|
||||||
public Task<IUserMessage> SendPendingAsync(string text)
|
|
||||||
=> ctx.Channel.SendPendingAsync(_eb, text);
|
|
||||||
|
|
||||||
public Task<IUserMessage> ErrorLocalizedAsync(LocStr str)
|
|
||||||
=> SendErrorAsync(GetText(str));
|
|
||||||
|
|
||||||
public Task<IUserMessage> PendingLocalizedAsync(LocStr str)
|
|
||||||
=> SendPendingAsync(GetText(str));
|
|
||||||
|
|
||||||
public Task<IUserMessage> ConfirmLocalizedAsync(LocStr str)
|
|
||||||
=> SendConfirmAsync(GetText(str));
|
|
||||||
|
|
||||||
public Task<IUserMessage> ReplyErrorLocalizedAsync(LocStr str)
|
|
||||||
=> SendErrorAsync($"{Format.Bold(ctx.User.ToString())} {GetText(str)}");
|
|
||||||
|
|
||||||
public Task<IUserMessage> ReplyPendingLocalizedAsync(LocStr str)
|
|
||||||
=> SendPendingAsync($"{Format.Bold(ctx.User.ToString())} {GetText(str)}");
|
|
||||||
|
|
||||||
public Task<IUserMessage> ReplyConfirmLocalizedAsync(LocStr str)
|
|
||||||
=> SendConfirmAsync($"{Format.Bold(ctx.User.ToString())} {GetText(str)}");
|
|
||||||
|
|
||||||
public async Task<bool> PromptUserConfirmAsync(IEmbedBuilder embed)
|
|
||||||
{
|
|
||||||
embed
|
|
||||||
.WithPendingColor()
|
|
||||||
.WithFooter("yes/no");
|
|
||||||
|
|
||||||
var msg = await ctx.Channel.EmbedAsync(embed).ConfigureAwait(false);
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
var input = await GetUserInputAsync(ctx.User.Id, ctx.Channel.Id).ConfigureAwait(false);
|
if (!(arg is SocketUserMessage userMsg) ||
|
||||||
input = input?.ToUpperInvariant();
|
!(userMsg.Channel is ITextChannel chan) ||
|
||||||
|
userMsg.Author.Id != userId ||
|
||||||
if (input != "YES" && input != "Y")
|
userMsg.Channel.Id != channelId)
|
||||||
{
|
{
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
var _ = Task.Run(() => msg.DeleteAsync());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TypeConverter typeConverter = TypeDescriptor.GetConverter(propType); ?
|
|
||||||
public async Task<string> GetUserInputAsync(ulong userId, ulong channelId)
|
|
||||||
{
|
|
||||||
var userInputTask = new TaskCompletionSource<string>();
|
|
||||||
var dsc = (DiscordSocketClient)ctx.Client;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
dsc.MessageReceived += MessageReceived;
|
|
||||||
|
|
||||||
if ((await Task.WhenAny(userInputTask.Task, Task.Delay(10000)).ConfigureAwait(false)) != userInputTask.Task)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return await userInputTask.Task.ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
dsc.MessageReceived -= MessageReceived;
|
|
||||||
}
|
|
||||||
|
|
||||||
Task MessageReceived(SocketMessage arg)
|
|
||||||
{
|
|
||||||
var _ = Task.Run(() =>
|
|
||||||
{
|
|
||||||
if (!(arg is SocketUserMessage userMsg) ||
|
|
||||||
!(userMsg.Channel is ITextChannel chan) ||
|
|
||||||
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;
|
||||||
});
|
}
|
||||||
|
|
||||||
|
if (userInputTask.TrySetResult(arg.Content))
|
||||||
|
{
|
||||||
|
userMsg.DeleteAfter(1);
|
||||||
|
}
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
});
|
||||||
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public abstract class NadekoModule<TService> : NadekoModule
|
public abstract class NadekoModule<TService> : NadekoModule
|
||||||
|
{
|
||||||
|
public TService _service { get; set; }
|
||||||
|
|
||||||
|
protected NadekoModule() : base()
|
||||||
{
|
{
|
||||||
public TService _service { get; set; }
|
|
||||||
|
|
||||||
protected NadekoModule() : base()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public abstract class NadekoSubmodule : NadekoModule
|
public abstract class NadekoSubmodule : NadekoModule
|
||||||
{
|
{
|
||||||
protected NadekoSubmodule() : base() { }
|
protected NadekoSubmodule() : base() { }
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract class NadekoSubmodule<TService> : NadekoModule<TService>
|
public abstract class NadekoSubmodule<TService> : NadekoModule<TService>
|
||||||
|
{
|
||||||
|
protected NadekoSubmodule() : base()
|
||||||
{
|
{
|
||||||
protected NadekoSubmodule() : base()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,7 +1,6 @@
|
|||||||
namespace NadekoBot.Modules
|
namespace NadekoBot.Modules;
|
||||||
|
|
||||||
|
public static class NadekoModuleExtensions
|
||||||
{
|
{
|
||||||
public static class NadekoModuleExtensions
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
|
@@ -1,74 +1,72 @@
|
|||||||
using System;
|
using System.Security.Cryptography;
|
||||||
using System.Security.Cryptography;
|
|
||||||
|
|
||||||
namespace NadekoBot.Common
|
namespace NadekoBot.Common;
|
||||||
|
|
||||||
|
public class NadekoRandom : Random
|
||||||
{
|
{
|
||||||
public class NadekoRandom : Random
|
readonly RandomNumberGenerator _rng;
|
||||||
|
|
||||||
|
public NadekoRandom() : base()
|
||||||
{
|
{
|
||||||
readonly RandomNumberGenerator _rng;
|
_rng = RandomNumberGenerator.Create();
|
||||||
|
|
||||||
public NadekoRandom() : base()
|
|
||||||
{
|
|
||||||
_rng = RandomNumberGenerator.Create();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override int Next()
|
|
||||||
{
|
|
||||||
var bytes = new byte[sizeof(int)];
|
|
||||||
_rng.GetBytes(bytes);
|
|
||||||
return Math.Abs(BitConverter.ToInt32(bytes, 0));
|
|
||||||
}
|
|
||||||
|
|
||||||
public override int Next(int maxValue)
|
|
||||||
{
|
|
||||||
if (maxValue <= 0)
|
|
||||||
throw new ArgumentOutOfRangeException(nameof(maxValue));
|
|
||||||
var bytes = new byte[sizeof(int)];
|
|
||||||
_rng.GetBytes(bytes);
|
|
||||||
return Math.Abs(BitConverter.ToInt32(bytes, 0)) % maxValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override int Next(int minValue, int maxValue)
|
|
||||||
{
|
|
||||||
if (minValue > maxValue)
|
|
||||||
throw new ArgumentOutOfRangeException(nameof(maxValue));
|
|
||||||
if (minValue == maxValue)
|
|
||||||
return minValue;
|
|
||||||
var bytes = new byte[sizeof(int)];
|
|
||||||
_rng.GetBytes(bytes);
|
|
||||||
var sign = Math.Sign(BitConverter.ToInt32(bytes, 0));
|
|
||||||
return (sign * BitConverter.ToInt32(bytes, 0)) % (maxValue - minValue) + minValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long NextLong(long minValue, long maxValue)
|
|
||||||
{
|
|
||||||
if (minValue > maxValue)
|
|
||||||
throw new ArgumentOutOfRangeException(nameof(maxValue));
|
|
||||||
if (minValue == maxValue)
|
|
||||||
return minValue;
|
|
||||||
var bytes = new byte[sizeof(long)];
|
|
||||||
_rng.GetBytes(bytes);
|
|
||||||
var sign = Math.Sign(BitConverter.ToInt64(bytes, 0));
|
|
||||||
return (sign * BitConverter.ToInt64(bytes, 0)) % (maxValue - minValue) + minValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void NextBytes(byte[] buffer)
|
|
||||||
{
|
|
||||||
_rng.GetBytes(buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override double Sample()
|
|
||||||
{
|
|
||||||
var bytes = new byte[sizeof(double)];
|
|
||||||
_rng.GetBytes(bytes);
|
|
||||||
return Math.Abs(BitConverter.ToDouble(bytes, 0) / double.MaxValue + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override double NextDouble()
|
|
||||||
{
|
|
||||||
var bytes = new byte[sizeof(double)];
|
|
||||||
_rng.GetBytes(bytes);
|
|
||||||
return BitConverter.ToDouble(bytes, 0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
public override int Next()
|
||||||
|
{
|
||||||
|
var bytes = new byte[sizeof(int)];
|
||||||
|
_rng.GetBytes(bytes);
|
||||||
|
return Math.Abs(BitConverter.ToInt32(bytes, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int Next(int maxValue)
|
||||||
|
{
|
||||||
|
if (maxValue <= 0)
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(maxValue));
|
||||||
|
var bytes = new byte[sizeof(int)];
|
||||||
|
_rng.GetBytes(bytes);
|
||||||
|
return Math.Abs(BitConverter.ToInt32(bytes, 0)) % maxValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int Next(int minValue, int maxValue)
|
||||||
|
{
|
||||||
|
if (minValue > maxValue)
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(maxValue));
|
||||||
|
if (minValue == maxValue)
|
||||||
|
return minValue;
|
||||||
|
var bytes = new byte[sizeof(int)];
|
||||||
|
_rng.GetBytes(bytes);
|
||||||
|
var sign = Math.Sign(BitConverter.ToInt32(bytes, 0));
|
||||||
|
return (sign * BitConverter.ToInt32(bytes, 0)) % (maxValue - minValue) + minValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long NextLong(long minValue, long maxValue)
|
||||||
|
{
|
||||||
|
if (minValue > maxValue)
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(maxValue));
|
||||||
|
if (minValue == maxValue)
|
||||||
|
return minValue;
|
||||||
|
var bytes = new byte[sizeof(long)];
|
||||||
|
_rng.GetBytes(bytes);
|
||||||
|
var sign = Math.Sign(BitConverter.ToInt64(bytes, 0));
|
||||||
|
return (sign * BitConverter.ToInt64(bytes, 0)) % (maxValue - minValue) + minValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void NextBytes(byte[] buffer)
|
||||||
|
{
|
||||||
|
_rng.GetBytes(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override double Sample()
|
||||||
|
{
|
||||||
|
var bytes = new byte[sizeof(double)];
|
||||||
|
_rng.GetBytes(bytes);
|
||||||
|
return Math.Abs(BitConverter.ToDouble(bytes, 0) / double.MaxValue + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override double NextDouble()
|
||||||
|
{
|
||||||
|
var bytes = new byte[sizeof(double)];
|
||||||
|
_rng.GetBytes(bytes);
|
||||||
|
return BitConverter.ToDouble(bytes, 0);
|
||||||
|
}
|
||||||
|
}
|
@@ -1,19 +1,17 @@
|
|||||||
using System;
|
using System.Threading.Tasks;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Discord.Commands;
|
using Discord.Commands;
|
||||||
|
|
||||||
namespace NadekoBot.Common
|
namespace NadekoBot.Common;
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
|
||||||
|
public sealed class NoPublicBotAttribute : PreconditionAttribute
|
||||||
{
|
{
|
||||||
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
|
public override Task<PreconditionResult> CheckPermissionsAsync(ICommandContext context, CommandInfo command, IServiceProvider services)
|
||||||
public sealed class NoPublicBotAttribute : PreconditionAttribute
|
|
||||||
{
|
{
|
||||||
public override Task<PreconditionResult> CheckPermissionsAsync(ICommandContext context, CommandInfo command, IServiceProvider services)
|
|
||||||
{
|
|
||||||
#if GLOBAL_NADEKO
|
#if GLOBAL_NADEKO
|
||||||
return Task.FromResult(PreconditionResult.FromError("Not available on the public bot. To learn how to selfhost a private bot, click [here](https://nadekobot.readthedocs.io/en/latest/)."));
|
return Task.FromResult(PreconditionResult.FromError("Not available on the public bot. To learn how to selfhost a private bot, click [here](https://nadekobot.readthedocs.io/en/latest/)."));
|
||||||
#else
|
#else
|
||||||
return Task.FromResult(PreconditionResult.FromSuccess());
|
return Task.FromResult(PreconditionResult.FromSuccess());
|
||||||
#endif
|
#endif
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,49 +1,46 @@
|
|||||||
using System;
|
namespace NadekoBot.Common;
|
||||||
|
|
||||||
namespace NadekoBot.Common
|
public class OldImageUrls
|
||||||
{
|
{
|
||||||
public class OldImageUrls
|
public int Version { get; set; } = 2;
|
||||||
|
|
||||||
|
public CoinData Coins { get; set; }
|
||||||
|
public Uri[] Currency { get; set; }
|
||||||
|
public Uri[] Dice { get; set; }
|
||||||
|
public RategirlData Rategirl { get; set; }
|
||||||
|
public XpData Xp { get; set; }
|
||||||
|
|
||||||
|
//new
|
||||||
|
public RipData Rip { get; set; }
|
||||||
|
public SlotData Slots { get; set; }
|
||||||
|
|
||||||
|
public class RipData
|
||||||
{
|
{
|
||||||
public int Version { get; set; } = 2;
|
public Uri Bg { get; set; }
|
||||||
|
public Uri Overlay { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
public CoinData Coins { get; set; }
|
public class SlotData
|
||||||
public Uri[] Currency { get; set; }
|
{
|
||||||
public Uri[] Dice { get; set; }
|
public Uri[] Emojis { get; set; }
|
||||||
public RategirlData Rategirl { get; set; }
|
public Uri[] Numbers { get; set; }
|
||||||
public XpData Xp { get; set; }
|
public Uri Bg { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
//new
|
public class CoinData
|
||||||
public RipData Rip { get; set; }
|
{
|
||||||
public SlotData Slots { get; set; }
|
public Uri[] Heads { get; set; }
|
||||||
|
public Uri[] Tails { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
public class RipData
|
public class RategirlData
|
||||||
{
|
{
|
||||||
public Uri Bg { get; set; }
|
public Uri Matrix { get; set; }
|
||||||
public Uri Overlay { get; set; }
|
public Uri Dot { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class SlotData
|
public class XpData
|
||||||
{
|
{
|
||||||
public Uri[] Emojis { get; set; }
|
public Uri Bg { get; set; }
|
||||||
public Uri[] Numbers { get; set; }
|
|
||||||
public Uri Bg { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class CoinData
|
|
||||||
{
|
|
||||||
public Uri[] Heads { get; set; }
|
|
||||||
public Uri[] Tails { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class RategirlData
|
|
||||||
{
|
|
||||||
public Uri Matrix { get; set; }
|
|
||||||
public Uri Dot { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class XpData
|
|
||||||
{
|
|
||||||
public Uri Bg { get; set; }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,24 +1,23 @@
|
|||||||
using CommandLine;
|
using CommandLine;
|
||||||
|
|
||||||
namespace NadekoBot.Common
|
namespace NadekoBot.Common;
|
||||||
{
|
|
||||||
public static class OptionsParser
|
|
||||||
{
|
|
||||||
public static T ParseFrom<T>(string[] args) where T : INadekoCommandOptions, new()
|
|
||||||
=> ParseFrom(new T(), args).Item1;
|
|
||||||
|
|
||||||
public static (T, bool) ParseFrom<T>(T options, string[] args) where T : INadekoCommandOptions
|
public static class OptionsParser
|
||||||
|
{
|
||||||
|
public static T ParseFrom<T>(string[] args) where T : INadekoCommandOptions, new()
|
||||||
|
=> ParseFrom(new T(), args).Item1;
|
||||||
|
|
||||||
|
public static (T, bool) ParseFrom<T>(T options, string[] args) where T : INadekoCommandOptions
|
||||||
|
{
|
||||||
|
using (var p = new Parser(x =>
|
||||||
|
{
|
||||||
|
x.HelpWriter = null;
|
||||||
|
}))
|
||||||
{
|
{
|
||||||
using (var p = new Parser(x =>
|
var res = p.ParseArguments<T>(args);
|
||||||
{
|
options = res.MapResult(x => x, x => options);
|
||||||
x.HelpWriter = null;
|
options.NormalizeOptions();
|
||||||
}))
|
return (options, res.Tag == ParserResultType.Parsed);
|
||||||
{
|
|
||||||
var res = p.ParseArguments<T>(args);
|
|
||||||
options = res.MapResult(x => x, x => options);
|
|
||||||
options.NormalizeOptions();
|
|
||||||
return (options, res.Tag == ParserResultType.Parsed);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,9 +1,8 @@
|
|||||||
namespace NadekoBot.Common
|
namespace NadekoBot.Common;
|
||||||
|
|
||||||
|
public class OsuMapData
|
||||||
{
|
{
|
||||||
public class OsuMapData
|
public string Title { get; set; }
|
||||||
{
|
public string Artist { get; set; }
|
||||||
public string Title { get; set; }
|
public string Version { get; set; }
|
||||||
public string Artist { get; set; }
|
|
||||||
public string Version { get; set; }
|
|
||||||
}
|
|
||||||
}
|
}
|
@@ -1,41 +1,40 @@
|
|||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace NadekoBot.Common
|
namespace NadekoBot.Common;
|
||||||
|
|
||||||
|
public class OsuUserBests
|
||||||
{
|
{
|
||||||
public class OsuUserBests
|
[JsonProperty("beatmap_id")] public string BeatmapId { get; set; }
|
||||||
{
|
|
||||||
[JsonProperty("beatmap_id")] public string BeatmapId { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty("score_id")] public string ScoreId { get; set; }
|
[JsonProperty("score_id")] public string ScoreId { get; set; }
|
||||||
|
|
||||||
[JsonProperty("score")] public string Score { get; set; }
|
[JsonProperty("score")] public string Score { get; set; }
|
||||||
|
|
||||||
[JsonProperty("maxcombo")] public string Maxcombo { get; set; }
|
[JsonProperty("maxcombo")] public string Maxcombo { get; set; }
|
||||||
|
|
||||||
[JsonProperty("count50")] public double Count50 { get; set; }
|
[JsonProperty("count50")] public double Count50 { get; set; }
|
||||||
|
|
||||||
[JsonProperty("count100")] public double Count100 { get; set; }
|
[JsonProperty("count100")] public double Count100 { get; set; }
|
||||||
|
|
||||||
[JsonProperty("count300")] public double Count300 { get; set; }
|
[JsonProperty("count300")] public double Count300 { get; set; }
|
||||||
|
|
||||||
[JsonProperty("countmiss")] public int Countmiss { get; set; }
|
[JsonProperty("countmiss")] public int Countmiss { get; set; }
|
||||||
|
|
||||||
[JsonProperty("countkatu")] public double Countkatu { get; set; }
|
[JsonProperty("countkatu")] public double Countkatu { get; set; }
|
||||||
|
|
||||||
[JsonProperty("countgeki")] public double Countgeki { get; set; }
|
[JsonProperty("countgeki")] public double Countgeki { get; set; }
|
||||||
|
|
||||||
[JsonProperty("perfect")] public string Perfect { get; set; }
|
[JsonProperty("perfect")] public string Perfect { get; set; }
|
||||||
|
|
||||||
[JsonProperty("enabled_mods")] public int EnabledMods { get; set; }
|
[JsonProperty("enabled_mods")] public int EnabledMods { get; set; }
|
||||||
|
|
||||||
[JsonProperty("user_id")] public string UserId { get; set; }
|
[JsonProperty("user_id")] public string UserId { get; set; }
|
||||||
|
|
||||||
[JsonProperty("date")] public string Date { get; set; }
|
[JsonProperty("date")] public string Date { get; set; }
|
||||||
|
|
||||||
[JsonProperty("rank")] public string Rank { get; set; }
|
[JsonProperty("rank")] public string Rank { get; set; }
|
||||||
|
|
||||||
[JsonProperty("pp")] public double Pp { get; set; }
|
[JsonProperty("pp")] public double Pp { get; set; }
|
||||||
|
|
||||||
[JsonProperty("replay_available")] public string ReplayAvailable { get; set; }
|
[JsonProperty("replay_available")] public string ReplayAvailable { get; set; }
|
||||||
}
|
|
||||||
}
|
}
|
@@ -1,25 +1,22 @@
|
|||||||
using System;
|
namespace NadekoBot.Common;
|
||||||
|
|
||||||
namespace NadekoBot.Common
|
public static class PlatformHelper
|
||||||
{
|
{
|
||||||
public static class PlatformHelper
|
private const int ProcessorCountRefreshIntervalMs = 30000;
|
||||||
{
|
|
||||||
private const int ProcessorCountRefreshIntervalMs = 30000;
|
|
||||||
|
|
||||||
private static volatile int _processorCount;
|
private static volatile int _processorCount;
|
||||||
private static volatile int _lastProcessorCountRefreshTicks;
|
private static volatile int _lastProcessorCountRefreshTicks;
|
||||||
|
|
||||||
public static int ProcessorCount {
|
public static int ProcessorCount {
|
||||||
get {
|
get {
|
||||||
var now = Environment.TickCount;
|
var now = Environment.TickCount;
|
||||||
if (_processorCount == 0 || (now - _lastProcessorCountRefreshTicks) >= ProcessorCountRefreshIntervalMs)
|
if (_processorCount == 0 || (now - _lastProcessorCountRefreshTicks) >= ProcessorCountRefreshIntervalMs)
|
||||||
{
|
{
|
||||||
_processorCount = Environment.ProcessorCount;
|
_processorCount = Environment.ProcessorCount;
|
||||||
_lastProcessorCountRefreshTicks = now;
|
_lastProcessorCountRefreshTicks = now;
|
||||||
}
|
|
||||||
|
|
||||||
return _processorCount;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return _processorCount;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,8 +1,7 @@
|
|||||||
namespace NadekoBot.Common.Pokemon
|
namespace NadekoBot.Common.Pokemon;
|
||||||
|
|
||||||
|
public class PokemonNameId
|
||||||
{
|
{
|
||||||
public class PokemonNameId
|
public int Id { get; set; }
|
||||||
{
|
public string Name { get; set; }
|
||||||
public int Id { get; set; }
|
}
|
||||||
public string Name { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,40 +1,38 @@
|
|||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace NadekoBot.Common.Pokemon
|
namespace NadekoBot.Common.Pokemon;
|
||||||
|
|
||||||
|
public class SearchPokemon
|
||||||
{
|
{
|
||||||
public class SearchPokemon
|
public class GenderRatioClass
|
||||||
{
|
{
|
||||||
public class GenderRatioClass
|
public float M { get; set; }
|
||||||
{
|
public float F { get; set; }
|
||||||
public float M { get; set; }
|
|
||||||
public float F { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class BaseStatsClass
|
|
||||||
{
|
|
||||||
public int HP { get; set; }
|
|
||||||
public int ATK { get; set; }
|
|
||||||
public int DEF { get; set; }
|
|
||||||
public int SPA { get; set; }
|
|
||||||
public int SPD { get; set; }
|
|
||||||
public int SPE { get; set; }
|
|
||||||
|
|
||||||
public override string ToString() => $@"💚**HP:** {HP,-4} ⚔**ATK:** {ATK,-4} 🛡**DEF:** {DEF,-4}
|
|
||||||
✨**SPA:** {SPA,-4} 🎇**SPD:** {SPD,-4} 💨**SPE:** {SPE,-4}";
|
|
||||||
}
|
|
||||||
|
|
||||||
[JsonProperty("num")]
|
|
||||||
public int Id { get; set; }
|
|
||||||
public string Species { get; set; }
|
|
||||||
public string[] Types { get; set; }
|
|
||||||
public GenderRatioClass GenderRatio { get; set; }
|
|
||||||
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 BaseStatsClass
|
||||||
|
{
|
||||||
|
public int HP { get; set; }
|
||||||
|
public int ATK { get; set; }
|
||||||
|
public int DEF { get; set; }
|
||||||
|
public int SPA { get; set; }
|
||||||
|
public int SPD { get; set; }
|
||||||
|
public int SPE { get; set; }
|
||||||
|
|
||||||
|
public override string ToString() => $@"💚**HP:** {HP,-4} ⚔**ATK:** {ATK,-4} 🛡**DEF:** {DEF,-4}
|
||||||
|
✨**SPA:** {SPA,-4} 🎇**SPD:** {SPD,-4} 💨**SPE:** {SPE,-4}";
|
||||||
|
}
|
||||||
|
|
||||||
|
[JsonProperty("num")]
|
||||||
|
public int Id { get; set; }
|
||||||
|
public string Species { get; set; }
|
||||||
|
public string[] Types { get; set; }
|
||||||
|
public GenderRatioClass GenderRatio { get; set; }
|
||||||
|
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; }
|
||||||
|
}
|
@@ -1,10 +1,9 @@
|
|||||||
namespace NadekoBot.Common.Pokemon
|
namespace NadekoBot.Common.Pokemon;
|
||||||
|
|
||||||
|
public class SearchPokemonAbility
|
||||||
{
|
{
|
||||||
public class SearchPokemonAbility
|
public string Desc { get; set; }
|
||||||
{
|
public string ShortDesc { get; set; }
|
||||||
public string Desc { get; set; }
|
public string Name { get; set; }
|
||||||
public string ShortDesc { get; set; }
|
public float Rating { get; set; }
|
||||||
public string Name { get; set; }
|
}
|
||||||
public float Rating { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,95 +1,90 @@
|
|||||||
using System;
|
using System.Threading.Tasks;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace NadekoBot.Common
|
namespace NadekoBot.Common;
|
||||||
|
|
||||||
|
public class EventPubSub : IPubSub
|
||||||
{
|
{
|
||||||
public class EventPubSub : IPubSub
|
private readonly Dictionary<string, Dictionary<Delegate, List<Func<object, ValueTask>>>> _actions
|
||||||
|
= new Dictionary<string, Dictionary<Delegate, List<Func<object, ValueTask>>>>();
|
||||||
|
private readonly object locker = new object();
|
||||||
|
|
||||||
|
public Task Sub<TData>(in TypedKey<TData> key, Func<TData, ValueTask> action)
|
||||||
{
|
{
|
||||||
private readonly Dictionary<string, Dictionary<Delegate, List<Func<object, ValueTask>>>> _actions
|
Func<object, ValueTask> localAction = obj => action((TData) obj);
|
||||||
= new Dictionary<string, Dictionary<Delegate, List<Func<object, ValueTask>>>>();
|
lock(locker)
|
||||||
private readonly object locker = new object();
|
|
||||||
|
|
||||||
public Task Sub<TData>(in TypedKey<TData> key, Func<TData, ValueTask> action)
|
|
||||||
{
|
{
|
||||||
Func<object, ValueTask> localAction = obj => action((TData) obj);
|
Dictionary<Delegate, List<Func<object, ValueTask>>> keyActions;
|
||||||
lock(locker)
|
if (!_actions.TryGetValue(key.Key, out keyActions))
|
||||||
{
|
{
|
||||||
Dictionary<Delegate, List<Func<object, ValueTask>>> keyActions;
|
keyActions = new Dictionary<Delegate, List<Func<object, ValueTask>>>();
|
||||||
if (!_actions.TryGetValue(key.Key, out keyActions))
|
_actions[key.Key] = keyActions;
|
||||||
{
|
|
||||||
keyActions = new Dictionary<Delegate, List<Func<object, ValueTask>>>();
|
|
||||||
_actions[key.Key] = keyActions;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Func<object, ValueTask>> sameActions;
|
|
||||||
if (!keyActions.TryGetValue(action, out sameActions))
|
|
||||||
{
|
|
||||||
sameActions = new List<Func<object, ValueTask>>();
|
|
||||||
keyActions[action] = sameActions;
|
|
||||||
}
|
|
||||||
|
|
||||||
sameActions.Add(localAction);
|
|
||||||
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public Task Pub<TData>(in TypedKey<TData> key, TData data)
|
|
||||||
{
|
|
||||||
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. return task.whenall all other tasks
|
|
||||||
return Task.WhenAll(actions
|
|
||||||
.SelectMany(kvp => kvp.Value)
|
|
||||||
.Select(action => action(data).AsTask()));
|
|
||||||
}
|
|
||||||
|
|
||||||
return Task.CompletedTask;
|
List<Func<object, ValueTask>> sameActions;
|
||||||
|
if (!keyActions.TryGetValue(action, out sameActions))
|
||||||
|
{
|
||||||
|
sameActions = new List<Func<object, ValueTask>>();
|
||||||
|
keyActions[action] = sameActions;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public Task Unsub<TData>(in TypedKey<TData> key, Func<TData, ValueTask> action)
|
sameActions.Add(localAction);
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task Pub<TData>(in TypedKey<TData> key, TData data)
|
||||||
|
{
|
||||||
|
lock (locker)
|
||||||
{
|
{
|
||||||
lock (locker)
|
if(_actions.TryGetValue(key.Key, out var actions))
|
||||||
{
|
{
|
||||||
// get subscriptions for this action
|
// if this class ever gets used, this needs to be properly implemented
|
||||||
if (_actions.TryGetValue(key.Key, out var actions))
|
// 1. ignore all valuetasks which are completed
|
||||||
|
// 2. return task.whenall all other tasks
|
||||||
|
return Task.WhenAll(actions
|
||||||
|
.SelectMany(kvp => kvp.Value)
|
||||||
|
.Select(action => action(data).AsTask()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task Unsub<TData>(in TypedKey<TData> key, Func<TData, ValueTask> action)
|
||||||
|
{
|
||||||
|
lock (locker)
|
||||||
|
{
|
||||||
|
// get subscriptions for this action
|
||||||
|
if (_actions.TryGetValue(key.Key, out var actions))
|
||||||
|
{
|
||||||
|
var hashCode = action.GetHashCode();
|
||||||
|
// get subscriptions which have the same action hash code
|
||||||
|
// note: having this as a list allows for multiple subscriptions of
|
||||||
|
// the same insance's/static method
|
||||||
|
if (actions.TryGetValue(action, out var sameActions))
|
||||||
{
|
{
|
||||||
var hashCode = action.GetHashCode();
|
// remove last subscription
|
||||||
// get subscriptions which have the same action hash code
|
sameActions.RemoveAt(sameActions.Count - 1);
|
||||||
// note: having this as a list allows for multiple subscriptions of
|
|
||||||
// the same insance's/static method
|
|
||||||
if (actions.TryGetValue(action, out var sameActions))
|
|
||||||
{
|
|
||||||
// remove last subscription
|
|
||||||
sameActions.RemoveAt(sameActions.Count - 1);
|
|
||||||
|
|
||||||
// if the last subscription was the only subscription
|
// if the last subscription was the only subscription
|
||||||
// we can safely remove this action's dictionary entry
|
// we can safely remove this action's dictionary entry
|
||||||
if (sameActions.Count == 0)
|
if (sameActions.Count == 0)
|
||||||
{
|
{
|
||||||
actions.Remove(action);
|
actions.Remove(action);
|
||||||
|
|
||||||
// if our dictionary has no more elements after
|
// if our dictionary has no more elements after
|
||||||
// removing the entry
|
// removing the entry
|
||||||
// it's safe to remove it from the key's subscriptions
|
// it's safe to remove it from the key's subscriptions
|
||||||
if (actions.Count == 0)
|
if (actions.Count == 0)
|
||||||
{
|
{
|
||||||
_actions.Remove(key.Key);
|
_actions.Remove(key.Key);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@@ -1,11 +1,9 @@
|
|||||||
using System;
|
using System.Threading.Tasks;
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace NadekoBot.Common
|
namespace NadekoBot.Common;
|
||||||
|
|
||||||
|
public interface IPubSub
|
||||||
{
|
{
|
||||||
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);
|
||||||
public Task Pub<TData>(in TypedKey<TData> key, TData data);
|
|
||||||
public Task Sub<TData>(in TypedKey<TData> key, Func<TData, ValueTask> action);
|
|
||||||
}
|
|
||||||
}
|
}
|
@@ -1,8 +1,7 @@
|
|||||||
namespace NadekoBot.Common
|
namespace NadekoBot.Common;
|
||||||
|
|
||||||
|
public interface ISeria
|
||||||
{
|
{
|
||||||
public interface ISeria
|
byte[] Serialize<T>(T data);
|
||||||
{
|
T Deserialize<T>(byte[] data);
|
||||||
byte[] Serialize<T>(T data);
|
|
||||||
T Deserialize<T>(byte[] data);
|
|
||||||
}
|
|
||||||
}
|
}
|
@@ -1,28 +1,27 @@
|
|||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using NadekoBot.Common.JsonConverters;
|
using NadekoBot.Common.JsonConverters;
|
||||||
|
|
||||||
namespace NadekoBot.Common
|
namespace NadekoBot.Common;
|
||||||
{
|
|
||||||
public class JsonSeria : ISeria
|
|
||||||
{
|
|
||||||
private JsonSerializerOptions serializerOptions = new JsonSerializerOptions()
|
|
||||||
{
|
|
||||||
Converters =
|
|
||||||
{
|
|
||||||
new Rgba32Converter(),
|
|
||||||
new CultureInfoConverter(),
|
|
||||||
}
|
|
||||||
};
|
|
||||||
public byte[] Serialize<T>(T data)
|
|
||||||
=> JsonSerializer.SerializeToUtf8Bytes(data, serializerOptions);
|
|
||||||
|
|
||||||
public T Deserialize<T>(byte[] data)
|
public class JsonSeria : ISeria
|
||||||
|
{
|
||||||
|
private JsonSerializerOptions serializerOptions = new JsonSerializerOptions()
|
||||||
|
{
|
||||||
|
Converters =
|
||||||
{
|
{
|
||||||
if (data is null)
|
new Rgba32Converter(),
|
||||||
return default;
|
new CultureInfoConverter(),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
public byte[] Serialize<T>(T data)
|
||||||
|
=> JsonSerializer.SerializeToUtf8Bytes(data, serializerOptions);
|
||||||
|
|
||||||
|
public T Deserialize<T>(byte[] data)
|
||||||
|
{
|
||||||
|
if (data is null)
|
||||||
|
return default;
|
||||||
|
|
||||||
|
|
||||||
return JsonSerializer.Deserialize<T>(data, serializerOptions);
|
return JsonSerializer.Deserialize<T>(data, serializerOptions);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,46 +1,42 @@
|
|||||||
using System;
|
using System.Threading.Tasks;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using NadekoBot.Services;
|
|
||||||
using NadekoBot.Extensions;
|
using NadekoBot.Extensions;
|
||||||
using Serilog;
|
|
||||||
using StackExchange.Redis;
|
using StackExchange.Redis;
|
||||||
|
|
||||||
namespace NadekoBot.Common
|
namespace NadekoBot.Common;
|
||||||
|
|
||||||
|
public sealed class RedisPubSub : IPubSub
|
||||||
{
|
{
|
||||||
public sealed class RedisPubSub : IPubSub
|
private readonly ConnectionMultiplexer _multi;
|
||||||
|
private readonly ISeria _serializer;
|
||||||
|
private readonly IBotCredentials _creds;
|
||||||
|
|
||||||
|
public RedisPubSub(ConnectionMultiplexer multi, ISeria serializer, IBotCredentials creds)
|
||||||
{
|
{
|
||||||
private readonly ConnectionMultiplexer _multi;
|
_multi = multi;
|
||||||
private readonly ISeria _serializer;
|
_serializer = serializer;
|
||||||
private readonly IBotCredentials _creds;
|
_creds = creds;
|
||||||
|
}
|
||||||
|
|
||||||
public RedisPubSub(ConnectionMultiplexer multi, ISeria serializer, IBotCredentials creds)
|
public Task Pub<TData>(in TypedKey<TData> key, TData data)
|
||||||
{
|
{
|
||||||
_multi = multi;
|
var serialized = _serializer.Serialize(data);
|
||||||
_serializer = serializer;
|
return _multi.GetSubscriber().PublishAsync($"{_creds.RedisKey()}:{key.Key}", serialized, CommandFlags.FireAndForget);
|
||||||
_creds = creds;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public Task Pub<TData>(in TypedKey<TData> key, TData data)
|
public Task Sub<TData>(in TypedKey<TData> key, Func<TData, ValueTask> action)
|
||||||
|
{
|
||||||
|
var eventName = key.Key;
|
||||||
|
return _multi.GetSubscriber().SubscribeAsync($"{_creds.RedisKey()}:{eventName}", async (ch, data) =>
|
||||||
{
|
{
|
||||||
var serialized = _serializer.Serialize(data);
|
try
|
||||||
return _multi.GetSubscriber().PublishAsync($"{_creds.RedisKey()}:{key.Key}", serialized, CommandFlags.FireAndForget);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task Sub<TData>(in TypedKey<TData> key, Func<TData, ValueTask> action)
|
|
||||||
{
|
|
||||||
var eventName = key.Key;
|
|
||||||
return _multi.GetSubscriber().SubscribeAsync($"{_creds.RedisKey()}:{eventName}", async (ch, data) =>
|
|
||||||
{
|
{
|
||||||
try
|
var dataObj = _serializer.Deserialize<TData>(data);
|
||||||
{
|
await action(dataObj);
|
||||||
var dataObj = _serializer.Deserialize<TData>(data);
|
}
|
||||||
await action(dataObj);
|
catch (Exception ex)
|
||||||
}
|
{
|
||||||
catch (Exception ex)
|
Log.Error($"Error handling the event {eventName}: {ex.Message}");
|
||||||
{
|
}
|
||||||
Log.Error($"Error handling the event {eventName}: {ex.Message}");
|
});
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,29 +1,28 @@
|
|||||||
namespace NadekoBot.Common
|
namespace NadekoBot.Common;
|
||||||
|
|
||||||
|
public readonly struct TypedKey<TData>
|
||||||
{
|
{
|
||||||
public readonly struct TypedKey<TData>
|
public readonly string Key;
|
||||||
|
|
||||||
|
public TypedKey(in string key)
|
||||||
{
|
{
|
||||||
public readonly string Key;
|
Key = key;
|
||||||
|
|
||||||
public TypedKey(in string key)
|
|
||||||
{
|
|
||||||
Key = key;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static implicit operator TypedKey<TData>(in string input)
|
|
||||||
=> new TypedKey<TData>(input);
|
|
||||||
public static implicit operator string(in TypedKey<TData> input)
|
|
||||||
=> input.Key;
|
|
||||||
|
|
||||||
public static bool operator ==(in TypedKey<TData> left, in TypedKey<TData> right)
|
|
||||||
=> left.Key == right.Key;
|
|
||||||
public static bool operator !=(in TypedKey<TData> left, in TypedKey<TData> right)
|
|
||||||
=> !(left == right);
|
|
||||||
|
|
||||||
public override bool Equals(object obj)
|
|
||||||
=> obj is TypedKey<TData> o && o == this;
|
|
||||||
|
|
||||||
public override int GetHashCode() => Key?.GetHashCode() ?? 0;
|
|
||||||
|
|
||||||
public override string ToString() => Key;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static implicit operator TypedKey<TData>(in string input)
|
||||||
|
=> new TypedKey<TData>(input);
|
||||||
|
public static implicit operator string(in TypedKey<TData> input)
|
||||||
|
=> input.Key;
|
||||||
|
|
||||||
|
public static bool operator ==(in TypedKey<TData> left, in TypedKey<TData> right)
|
||||||
|
=> left.Key == right.Key;
|
||||||
|
public static bool operator !=(in TypedKey<TData> left, in TypedKey<TData> right)
|
||||||
|
=> !(left == right);
|
||||||
|
|
||||||
|
public override bool Equals(object obj)
|
||||||
|
=> obj is TypedKey<TData> o && o == this;
|
||||||
|
|
||||||
|
public override int GetHashCode() => Key?.GetHashCode() ?? 0;
|
||||||
|
|
||||||
|
public override string ToString() => Key;
|
||||||
}
|
}
|
@@ -3,36 +3,35 @@ using NadekoBot.Common.Yml;
|
|||||||
using NadekoBot.Common.Configs;
|
using NadekoBot.Common.Configs;
|
||||||
using YamlDotNet.Serialization;
|
using YamlDotNet.Serialization;
|
||||||
|
|
||||||
namespace NadekoBot.Common
|
namespace NadekoBot.Common;
|
||||||
|
|
||||||
|
public class YamlSeria : IConfigSeria
|
||||||
{
|
{
|
||||||
public class YamlSeria : IConfigSeria
|
private readonly ISerializer _serializer;
|
||||||
|
private readonly IDeserializer _deserializer;
|
||||||
|
|
||||||
|
private static readonly Regex CodePointRegex
|
||||||
|
= new Regex(@"(\\U(?<code>[a-zA-Z0-9]{8})|\\u(?<code>[a-zA-Z0-9]{4})|\\x(?<code>[a-zA-Z0-9]{2}))",
|
||||||
|
RegexOptions.Compiled);
|
||||||
|
|
||||||
|
public YamlSeria()
|
||||||
{
|
{
|
||||||
private readonly ISerializer _serializer;
|
_serializer = Yaml.Serializer;
|
||||||
private readonly IDeserializer _deserializer;
|
_deserializer = Yaml.Deserializer;
|
||||||
|
|
||||||
private static readonly Regex CodePointRegex
|
|
||||||
= new Regex(@"(\\U(?<code>[a-zA-Z0-9]{8})|\\u(?<code>[a-zA-Z0-9]{4})|\\x(?<code>[a-zA-Z0-9]{2}))",
|
|
||||||
RegexOptions.Compiled);
|
|
||||||
|
|
||||||
public YamlSeria()
|
|
||||||
{
|
|
||||||
_serializer = Yaml.Serializer;
|
|
||||||
_deserializer = Yaml.Deserializer;
|
|
||||||
}
|
|
||||||
|
|
||||||
public string Serialize<T>(T obj)
|
|
||||||
{
|
|
||||||
var escapedOutput = _serializer.Serialize(obj);
|
|
||||||
var output = CodePointRegex.Replace(escapedOutput, me =>
|
|
||||||
{
|
|
||||||
var str = me.Groups["code"].Value;
|
|
||||||
var newString = YamlHelper.UnescapeUnicodeCodePoint(str);
|
|
||||||
return newString;
|
|
||||||
});
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
|
|
||||||
public T Deserialize<T>(string data)
|
|
||||||
=> _deserializer.Deserialize<T>(data);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string Serialize<T>(T obj)
|
||||||
|
{
|
||||||
|
var escapedOutput = _serializer.Serialize(obj);
|
||||||
|
var output = CodePointRegex.Replace(escapedOutput, me =>
|
||||||
|
{
|
||||||
|
var str = me.Groups["code"].Value;
|
||||||
|
var newString = YamlHelper.UnescapeUnicodeCodePoint(str);
|
||||||
|
return newString;
|
||||||
|
});
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
public T Deserialize<T>(string data)
|
||||||
|
=> _deserializer.Deserialize<T>(data);
|
||||||
}
|
}
|
@@ -3,235 +3,229 @@ using Discord.Commands;
|
|||||||
using Discord.WebSocket;
|
using Discord.WebSocket;
|
||||||
using NadekoBot.Extensions;
|
using NadekoBot.Extensions;
|
||||||
using NadekoBot.Modules.Administration.Services;
|
using NadekoBot.Modules.Administration.Services;
|
||||||
using NadekoBot.Modules.Music.Services;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using NadekoBot.Common;
|
|
||||||
|
|
||||||
namespace NadekoBot.Common.Replacements
|
namespace NadekoBot.Common.Replacements;
|
||||||
|
|
||||||
|
public class ReplacementBuilder
|
||||||
{
|
{
|
||||||
public class ReplacementBuilder
|
private static readonly Regex rngRegex = new Regex("%rng(?:(?<from>(?:-)?\\d+)-(?<to>(?:-)?\\d+))?%", RegexOptions.Compiled);
|
||||||
|
private ConcurrentDictionary<string, Func<string>> _reps = new ConcurrentDictionary<string, Func<string>>();
|
||||||
|
private ConcurrentDictionary<Regex, Func<Match, string>> _regex = new ConcurrentDictionary<Regex, Func<Match, string>>();
|
||||||
|
|
||||||
|
public ReplacementBuilder()
|
||||||
{
|
{
|
||||||
private static readonly Regex rngRegex = new Regex("%rng(?:(?<from>(?:-)?\\d+)-(?<to>(?:-)?\\d+))?%", RegexOptions.Compiled);
|
WithRngRegex();
|
||||||
private ConcurrentDictionary<string, Func<string>> _reps = new ConcurrentDictionary<string, Func<string>>();
|
}
|
||||||
private ConcurrentDictionary<Regex, Func<Match, string>> _regex = new ConcurrentDictionary<Regex, Func<Match, string>>();
|
|
||||||
|
|
||||||
public ReplacementBuilder()
|
public ReplacementBuilder WithDefault(IUser usr, IMessageChannel ch, SocketGuild g, DiscordSocketClient client)
|
||||||
|
{
|
||||||
|
return this.WithUser(usr)
|
||||||
|
.WithChannel(ch)
|
||||||
|
.WithServer(client, g)
|
||||||
|
.WithClient(client);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReplacementBuilder WithDefault(ICommandContext ctx) =>
|
||||||
|
WithDefault(ctx.User, ctx.Channel, ctx.Guild as SocketGuild, (DiscordSocketClient)ctx.Client);
|
||||||
|
|
||||||
|
public ReplacementBuilder WithMention(DiscordSocketClient client)
|
||||||
|
{
|
||||||
|
/*OBSOLETE*/
|
||||||
|
_reps.TryAdd("%mention%", () => $"<@{client.CurrentUser.Id}>");
|
||||||
|
/*NEW*/
|
||||||
|
_reps.TryAdd("%bot.mention%", () => client.CurrentUser.Mention);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReplacementBuilder WithClient(DiscordSocketClient client)
|
||||||
|
{
|
||||||
|
WithMention(client);
|
||||||
|
|
||||||
|
/*OBSOLETE*/
|
||||||
|
_reps.TryAdd("%shardid%", () => client.ShardId.ToString());
|
||||||
|
_reps.TryAdd("%time%", () => DateTime.Now.ToString("HH:mm " + TimeZoneInfo.Local.StandardName.GetInitials()));
|
||||||
|
|
||||||
|
/*NEW*/
|
||||||
|
_reps.TryAdd("%bot.status%", () => client.Status.ToString());
|
||||||
|
_reps.TryAdd("%bot.latency%", () => client.Latency.ToString());
|
||||||
|
_reps.TryAdd("%bot.name%", () => client.CurrentUser.Username);
|
||||||
|
_reps.TryAdd("%bot.fullname%", () => client.CurrentUser.ToString());
|
||||||
|
_reps.TryAdd("%bot.time%", () => DateTime.Now.ToString("HH:mm " + TimeZoneInfo.Local.StandardName.GetInitials()));
|
||||||
|
_reps.TryAdd("%bot.discrim%", () => client.CurrentUser.Discriminator);
|
||||||
|
_reps.TryAdd("%bot.id%", () => client.CurrentUser.Id.ToString());
|
||||||
|
_reps.TryAdd("%bot.avatar%", () => client.CurrentUser.RealAvatarUrl()?.ToString());
|
||||||
|
|
||||||
|
WithStats(client);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReplacementBuilder WithServer(DiscordSocketClient client, SocketGuild g)
|
||||||
|
{
|
||||||
|
/*OBSOLETE*/
|
||||||
|
_reps.TryAdd("%sid%", () => g is null ? "DM" : g.Id.ToString());
|
||||||
|
_reps.TryAdd("%server%", () => g is null ? "DM" : g.Name);
|
||||||
|
_reps.TryAdd("%members%", () => g != null && g is SocketGuild sg ? sg.MemberCount.ToString() : "?");
|
||||||
|
_reps.TryAdd("%server_time%", () =>
|
||||||
{
|
{
|
||||||
WithRngRegex();
|
TimeZoneInfo to = TimeZoneInfo.Local;
|
||||||
}
|
if (g != null)
|
||||||
|
|
||||||
public ReplacementBuilder WithDefault(IUser usr, IMessageChannel ch, SocketGuild g, DiscordSocketClient client)
|
|
||||||
{
|
|
||||||
return this.WithUser(usr)
|
|
||||||
.WithChannel(ch)
|
|
||||||
.WithServer(client, g)
|
|
||||||
.WithClient(client);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ReplacementBuilder WithDefault(ICommandContext ctx) =>
|
|
||||||
WithDefault(ctx.User, ctx.Channel, ctx.Guild as SocketGuild, (DiscordSocketClient)ctx.Client);
|
|
||||||
|
|
||||||
public ReplacementBuilder WithMention(DiscordSocketClient client)
|
|
||||||
{
|
|
||||||
/*OBSOLETE*/
|
|
||||||
_reps.TryAdd("%mention%", () => $"<@{client.CurrentUser.Id}>");
|
|
||||||
/*NEW*/
|
|
||||||
_reps.TryAdd("%bot.mention%", () => client.CurrentUser.Mention);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ReplacementBuilder WithClient(DiscordSocketClient client)
|
|
||||||
{
|
|
||||||
WithMention(client);
|
|
||||||
|
|
||||||
/*OBSOLETE*/
|
|
||||||
_reps.TryAdd("%shardid%", () => client.ShardId.ToString());
|
|
||||||
_reps.TryAdd("%time%", () => DateTime.Now.ToString("HH:mm " + TimeZoneInfo.Local.StandardName.GetInitials()));
|
|
||||||
|
|
||||||
/*NEW*/
|
|
||||||
_reps.TryAdd("%bot.status%", () => client.Status.ToString());
|
|
||||||
_reps.TryAdd("%bot.latency%", () => client.Latency.ToString());
|
|
||||||
_reps.TryAdd("%bot.name%", () => client.CurrentUser.Username);
|
|
||||||
_reps.TryAdd("%bot.fullname%", () => client.CurrentUser.ToString());
|
|
||||||
_reps.TryAdd("%bot.time%", () => DateTime.Now.ToString("HH:mm " + TimeZoneInfo.Local.StandardName.GetInitials()));
|
|
||||||
_reps.TryAdd("%bot.discrim%", () => client.CurrentUser.Discriminator);
|
|
||||||
_reps.TryAdd("%bot.id%", () => client.CurrentUser.Id.ToString());
|
|
||||||
_reps.TryAdd("%bot.avatar%", () => client.CurrentUser.RealAvatarUrl()?.ToString());
|
|
||||||
|
|
||||||
WithStats(client);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ReplacementBuilder WithServer(DiscordSocketClient client, SocketGuild g)
|
|
||||||
{
|
|
||||||
/*OBSOLETE*/
|
|
||||||
_reps.TryAdd("%sid%", () => g is null ? "DM" : g.Id.ToString());
|
|
||||||
_reps.TryAdd("%server%", () => g is null ? "DM" : g.Name);
|
|
||||||
_reps.TryAdd("%members%", () => g != null && g is SocketGuild sg ? sg.MemberCount.ToString() : "?");
|
|
||||||
_reps.TryAdd("%server_time%", () =>
|
|
||||||
{
|
{
|
||||||
TimeZoneInfo to = TimeZoneInfo.Local;
|
if (GuildTimezoneService.AllServices.TryGetValue(client.CurrentUser.Id, out var tz))
|
||||||
if (g != null)
|
to = tz.GetTimeZoneOrDefault(g.Id) ?? TimeZoneInfo.Local;
|
||||||
{
|
|
||||||
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();
|
|
||||||
});
|
|
||||||
/*NEW*/
|
|
||||||
_reps.TryAdd("%server.id%", () => g is null ? "DM" : g.Id.ToString());
|
|
||||||
_reps.TryAdd("%server.name%", () => g is null ? "DM" : g.Name);
|
|
||||||
_reps.TryAdd("%server.members%", () => g != null && g is SocketGuild sg ? sg.MemberCount.ToString() : "?");
|
|
||||||
_reps.TryAdd("%server.boosters%", () => g.PremiumSubscriptionCount.ToString());
|
|
||||||
_reps.TryAdd("%server.boost_level%", () => ((int)g.PremiumTier).ToString());
|
|
||||||
_reps.TryAdd("%server.time%", () =>
|
|
||||||
{
|
|
||||||
TimeZoneInfo 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 this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ReplacementBuilder WithChannel(IMessageChannel ch)
|
|
||||||
{
|
|
||||||
/*OBSOLETE*/
|
|
||||||
_reps.TryAdd("%channel%", () => (ch as ITextChannel)?.Mention ?? "#" + ch.Name);
|
|
||||||
_reps.TryAdd("%chname%", () => ch.Name);
|
|
||||||
_reps.TryAdd("%cid%", () => ch?.Id.ToString());
|
|
||||||
/*NEW*/
|
|
||||||
_reps.TryAdd("%channel.mention%", () => (ch as ITextChannel)?.Mention ?? "#" + ch.Name);
|
|
||||||
_reps.TryAdd("%channel.name%", () => ch.Name);
|
|
||||||
_reps.TryAdd("%channel.id%", () => ch.Id.ToString());
|
|
||||||
_reps.TryAdd("%channel.created%", () => ch.CreatedAt.ToString("HH:mm dd.MM.yyyy"));
|
|
||||||
_reps.TryAdd("%channel.nsfw%", () => (ch as ITextChannel)?.IsNsfw.ToString() ?? "-");
|
|
||||||
_reps.TryAdd("%channel.topic%", () => (ch as ITextChannel)?.Topic ?? "-");
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ReplacementBuilder WithUser(IUser user)
|
|
||||||
{
|
|
||||||
// /*OBSOLETE*/
|
|
||||||
// _reps.TryAdd("%user%", () => user.Mention);
|
|
||||||
// _reps.TryAdd("%userfull%", () => user.ToString());
|
|
||||||
// _reps.TryAdd("%username%", () => user.Username);
|
|
||||||
// _reps.TryAdd("%userdiscrim%", () => user.Discriminator);
|
|
||||||
// _reps.TryAdd("%useravatar%", () => user.RealAvatarUrl()?.ToString());
|
|
||||||
// _reps.TryAdd("%id%", () => user.Id.ToString());
|
|
||||||
// _reps.TryAdd("%uid%", () => user.Id.ToString());
|
|
||||||
// /*NEW*/
|
|
||||||
// _reps.TryAdd("%user.mention%", () => user.Mention);
|
|
||||||
// _reps.TryAdd("%user.fullname%", () => user.ToString());
|
|
||||||
// _reps.TryAdd("%user.name%", () => user.Username);
|
|
||||||
// _reps.TryAdd("%user.discrim%", () => user.Discriminator);
|
|
||||||
// _reps.TryAdd("%user.avatar%", () => user.RealAvatarUrl()?.ToString());
|
|
||||||
// _reps.TryAdd("%user.id%", () => user.Id.ToString());
|
|
||||||
// _reps.TryAdd("%user.created_time%", () => user.CreatedAt.ToString("HH:mm"));
|
|
||||||
// _reps.TryAdd("%user.created_date%", () => user.CreatedAt.ToString("dd.MM.yyyy"));
|
|
||||||
// _reps.TryAdd("%user.joined_time%", () => (user as IGuildUser)?.JoinedAt?.ToString("HH:mm") ?? "-");
|
|
||||||
// _reps.TryAdd("%user.joined_date%", () => (user as IGuildUser)?.JoinedAt?.ToString("dd.MM.yyyy") ?? "-");
|
|
||||||
WithManyUsers(new[] {user});
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ReplacementBuilder WithManyUsers(IEnumerable<IUser> users)
|
|
||||||
{
|
|
||||||
/*OBSOLETE*/
|
|
||||||
_reps.TryAdd("%user%", () => string.Join(" ", users.Select(user => user.Mention)));
|
|
||||||
_reps.TryAdd("%userfull%", () => string.Join(" ", users.Select(user => user.ToString())));
|
|
||||||
_reps.TryAdd("%username%", () => string.Join(" ", users.Select(user => user.Username)));
|
|
||||||
_reps.TryAdd("%userdiscrim%", () => string.Join(" ", users.Select(user => user.Discriminator)));
|
|
||||||
_reps.TryAdd("%useravatar%", () => string.Join(" ", users.Select(user => user.RealAvatarUrl()?.ToString())));
|
|
||||||
_reps.TryAdd("%id%", () => string.Join(" ", users.Select(user => user.Id.ToString())));
|
|
||||||
_reps.TryAdd("%uid%", () => string.Join(" ", users.Select(user => user.Id.ToString())));
|
|
||||||
/*NEW*/
|
|
||||||
_reps.TryAdd("%user.mention%", () => string.Join(" ", users.Select(user => user.Mention)));
|
|
||||||
_reps.TryAdd("%user.fullname%", () => string.Join(" ", users.Select(user => user.ToString())));
|
|
||||||
_reps.TryAdd("%user.name%", () => string.Join(" ", users.Select(user => user.Username)));
|
|
||||||
_reps.TryAdd("%user.discrim%", () => string.Join(" ", users.Select(user => user.Discriminator)));
|
|
||||||
_reps.TryAdd("%user.avatar%", () => string.Join(" ", users.Select(user => user.RealAvatarUrl()?.ToString())));
|
|
||||||
_reps.TryAdd("%user.id%", () => string.Join(" ", users.Select(user => user.Id.ToString())));
|
|
||||||
_reps.TryAdd("%user.created_time%", () => string.Join(" ", users.Select(user => user.CreatedAt.ToString("HH:mm"))));
|
|
||||||
_reps.TryAdd("%user.created_date%", () => string.Join(" ", users.Select(user => user.CreatedAt.ToString("dd.MM.yyyy"))));
|
|
||||||
_reps.TryAdd("%user.joined_time%", () => string.Join(" ", users.Select(user => (user as IGuildUser)?.JoinedAt?.ToString("HH:mm") ?? "-")));
|
|
||||||
_reps.TryAdd("%user.joined_date%", () => string.Join(" ", users.Select(user => (user as IGuildUser)?.JoinedAt?.ToString("dd.MM.yyyy") ?? "-")));
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
private ReplacementBuilder WithStats(DiscordSocketClient c)
|
|
||||||
{
|
|
||||||
/*OBSOLETE*/
|
|
||||||
_reps.TryAdd("%servers%", () => c.Guilds.Count.ToString());
|
|
||||||
#if !GLOBAL_NADEKO
|
|
||||||
_reps.TryAdd("%users%", () => c.Guilds.Sum(g => g.MemberCount).ToString());
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/*NEW*/
|
|
||||||
_reps.TryAdd("%shard.servercount%", () => c.Guilds.Count.ToString());
|
|
||||||
#if !GLOBAL_NADEKO
|
|
||||||
_reps.TryAdd("%shard.usercount%", () => c.Guilds.Sum(g => g.MemberCount).ToString());
|
|
||||||
#endif
|
|
||||||
_reps.TryAdd("%shard.id%", () => c.ShardId.ToString());
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ReplacementBuilder WithRngRegex()
|
|
||||||
{
|
|
||||||
var rng = new NadekoRandom();
|
|
||||||
_regex.TryAdd(rngRegex, (match) =>
|
|
||||||
{
|
|
||||||
if (!int.TryParse(match.Groups["from"].ToString(), out var from))
|
|
||||||
from = 0;
|
|
||||||
if (!int.TryParse(match.Groups["to"].ToString(), out var to))
|
|
||||||
to = 0;
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ReplacementBuilder WithOverride(string key, Func<string> output)
|
|
||||||
{
|
|
||||||
_reps.AddOrUpdate(key, output, delegate { return output; });
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Replacer Build()
|
|
||||||
{
|
|
||||||
return new Replacer(_reps.Select(x => (x.Key, x.Value)).ToArray(), _regex.Select(x => (x.Key, x.Value)).ToArray());
|
|
||||||
}
|
|
||||||
|
|
||||||
public ReplacementBuilder WithProviders(IEnumerable<IPlaceholderProvider> phProviders)
|
|
||||||
{
|
|
||||||
foreach (var provider in phProviders)
|
|
||||||
{
|
|
||||||
foreach (var ovr in provider.GetPlaceholders())
|
|
||||||
{
|
|
||||||
_reps.TryAdd(ovr.Name, ovr.Func);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return this;
|
return TimeZoneInfo.ConvertTime(DateTime.UtcNow,
|
||||||
}
|
TimeZoneInfo.Utc,
|
||||||
|
to).ToString("HH:mm ") + to.StandardName.GetInitials();
|
||||||
|
});
|
||||||
|
/*NEW*/
|
||||||
|
_reps.TryAdd("%server.id%", () => g is null ? "DM" : g.Id.ToString());
|
||||||
|
_reps.TryAdd("%server.name%", () => g is null ? "DM" : g.Name);
|
||||||
|
_reps.TryAdd("%server.members%", () => g != null && g is SocketGuild sg ? sg.MemberCount.ToString() : "?");
|
||||||
|
_reps.TryAdd("%server.boosters%", () => g.PremiumSubscriptionCount.ToString());
|
||||||
|
_reps.TryAdd("%server.boost_level%", () => ((int)g.PremiumTier).ToString());
|
||||||
|
_reps.TryAdd("%server.time%", () =>
|
||||||
|
{
|
||||||
|
TimeZoneInfo 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 this;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
public ReplacementBuilder WithChannel(IMessageChannel ch)
|
||||||
|
{
|
||||||
|
/*OBSOLETE*/
|
||||||
|
_reps.TryAdd("%channel%", () => (ch as ITextChannel)?.Mention ?? "#" + ch.Name);
|
||||||
|
_reps.TryAdd("%chname%", () => ch.Name);
|
||||||
|
_reps.TryAdd("%cid%", () => ch?.Id.ToString());
|
||||||
|
/*NEW*/
|
||||||
|
_reps.TryAdd("%channel.mention%", () => (ch as ITextChannel)?.Mention ?? "#" + ch.Name);
|
||||||
|
_reps.TryAdd("%channel.name%", () => ch.Name);
|
||||||
|
_reps.TryAdd("%channel.id%", () => ch.Id.ToString());
|
||||||
|
_reps.TryAdd("%channel.created%", () => ch.CreatedAt.ToString("HH:mm dd.MM.yyyy"));
|
||||||
|
_reps.TryAdd("%channel.nsfw%", () => (ch as ITextChannel)?.IsNsfw.ToString() ?? "-");
|
||||||
|
_reps.TryAdd("%channel.topic%", () => (ch as ITextChannel)?.Topic ?? "-");
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReplacementBuilder WithUser(IUser user)
|
||||||
|
{
|
||||||
|
// /*OBSOLETE*/
|
||||||
|
// _reps.TryAdd("%user%", () => user.Mention);
|
||||||
|
// _reps.TryAdd("%userfull%", () => user.ToString());
|
||||||
|
// _reps.TryAdd("%username%", () => user.Username);
|
||||||
|
// _reps.TryAdd("%userdiscrim%", () => user.Discriminator);
|
||||||
|
// _reps.TryAdd("%useravatar%", () => user.RealAvatarUrl()?.ToString());
|
||||||
|
// _reps.TryAdd("%id%", () => user.Id.ToString());
|
||||||
|
// _reps.TryAdd("%uid%", () => user.Id.ToString());
|
||||||
|
// /*NEW*/
|
||||||
|
// _reps.TryAdd("%user.mention%", () => user.Mention);
|
||||||
|
// _reps.TryAdd("%user.fullname%", () => user.ToString());
|
||||||
|
// _reps.TryAdd("%user.name%", () => user.Username);
|
||||||
|
// _reps.TryAdd("%user.discrim%", () => user.Discriminator);
|
||||||
|
// _reps.TryAdd("%user.avatar%", () => user.RealAvatarUrl()?.ToString());
|
||||||
|
// _reps.TryAdd("%user.id%", () => user.Id.ToString());
|
||||||
|
// _reps.TryAdd("%user.created_time%", () => user.CreatedAt.ToString("HH:mm"));
|
||||||
|
// _reps.TryAdd("%user.created_date%", () => user.CreatedAt.ToString("dd.MM.yyyy"));
|
||||||
|
// _reps.TryAdd("%user.joined_time%", () => (user as IGuildUser)?.JoinedAt?.ToString("HH:mm") ?? "-");
|
||||||
|
// _reps.TryAdd("%user.joined_date%", () => (user as IGuildUser)?.JoinedAt?.ToString("dd.MM.yyyy") ?? "-");
|
||||||
|
WithManyUsers(new[] {user});
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReplacementBuilder WithManyUsers(IEnumerable<IUser> users)
|
||||||
|
{
|
||||||
|
/*OBSOLETE*/
|
||||||
|
_reps.TryAdd("%user%", () => string.Join(" ", users.Select(user => user.Mention)));
|
||||||
|
_reps.TryAdd("%userfull%", () => string.Join(" ", users.Select(user => user.ToString())));
|
||||||
|
_reps.TryAdd("%username%", () => string.Join(" ", users.Select(user => user.Username)));
|
||||||
|
_reps.TryAdd("%userdiscrim%", () => string.Join(" ", users.Select(user => user.Discriminator)));
|
||||||
|
_reps.TryAdd("%useravatar%", () => string.Join(" ", users.Select(user => user.RealAvatarUrl()?.ToString())));
|
||||||
|
_reps.TryAdd("%id%", () => string.Join(" ", users.Select(user => user.Id.ToString())));
|
||||||
|
_reps.TryAdd("%uid%", () => string.Join(" ", users.Select(user => user.Id.ToString())));
|
||||||
|
/*NEW*/
|
||||||
|
_reps.TryAdd("%user.mention%", () => string.Join(" ", users.Select(user => user.Mention)));
|
||||||
|
_reps.TryAdd("%user.fullname%", () => string.Join(" ", users.Select(user => user.ToString())));
|
||||||
|
_reps.TryAdd("%user.name%", () => string.Join(" ", users.Select(user => user.Username)));
|
||||||
|
_reps.TryAdd("%user.discrim%", () => string.Join(" ", users.Select(user => user.Discriminator)));
|
||||||
|
_reps.TryAdd("%user.avatar%", () => string.Join(" ", users.Select(user => user.RealAvatarUrl()?.ToString())));
|
||||||
|
_reps.TryAdd("%user.id%", () => string.Join(" ", users.Select(user => user.Id.ToString())));
|
||||||
|
_reps.TryAdd("%user.created_time%", () => string.Join(" ", users.Select(user => user.CreatedAt.ToString("HH:mm"))));
|
||||||
|
_reps.TryAdd("%user.created_date%", () => string.Join(" ", users.Select(user => user.CreatedAt.ToString("dd.MM.yyyy"))));
|
||||||
|
_reps.TryAdd("%user.joined_time%", () => string.Join(" ", users.Select(user => (user as IGuildUser)?.JoinedAt?.ToString("HH:mm") ?? "-")));
|
||||||
|
_reps.TryAdd("%user.joined_date%", () => string.Join(" ", users.Select(user => (user as IGuildUser)?.JoinedAt?.ToString("dd.MM.yyyy") ?? "-")));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ReplacementBuilder WithStats(DiscordSocketClient c)
|
||||||
|
{
|
||||||
|
/*OBSOLETE*/
|
||||||
|
_reps.TryAdd("%servers%", () => c.Guilds.Count.ToString());
|
||||||
|
#if !GLOBAL_NADEKO
|
||||||
|
_reps.TryAdd("%users%", () => c.Guilds.Sum(g => g.MemberCount).ToString());
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*NEW*/
|
||||||
|
_reps.TryAdd("%shard.servercount%", () => c.Guilds.Count.ToString());
|
||||||
|
#if !GLOBAL_NADEKO
|
||||||
|
_reps.TryAdd("%shard.usercount%", () => c.Guilds.Sum(g => g.MemberCount).ToString());
|
||||||
|
#endif
|
||||||
|
_reps.TryAdd("%shard.id%", () => c.ShardId.ToString());
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReplacementBuilder WithRngRegex()
|
||||||
|
{
|
||||||
|
var rng = new NadekoRandom();
|
||||||
|
_regex.TryAdd(rngRegex, (match) =>
|
||||||
|
{
|
||||||
|
if (!int.TryParse(match.Groups["from"].ToString(), out var from))
|
||||||
|
from = 0;
|
||||||
|
if (!int.TryParse(match.Groups["to"].ToString(), out var to))
|
||||||
|
to = 0;
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReplacementBuilder WithOverride(string key, Func<string> output)
|
||||||
|
{
|
||||||
|
_reps.AddOrUpdate(key, output, delegate { return output; });
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Replacer Build()
|
||||||
|
{
|
||||||
|
return new Replacer(_reps.Select(x => (x.Key, x.Value)).ToArray(), _regex.Select(x => (x.Key, x.Value)).ToArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReplacementBuilder WithProviders(IEnumerable<IPlaceholderProvider> phProviders)
|
||||||
|
{
|
||||||
|
foreach (var provider in phProviders)
|
||||||
|
{
|
||||||
|
foreach (var ovr in provider.GetPlaceholders())
|
||||||
|
{
|
||||||
|
_reps.TryAdd(ovr.Name, ovr.Func);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
@@ -1,91 +1,88 @@
|
|||||||
using System;
|
using System.Text.RegularExpressions;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
|
|
||||||
namespace NadekoBot.Common.Replacements
|
namespace NadekoBot.Common.Replacements;
|
||||||
|
|
||||||
|
public class Replacer
|
||||||
{
|
{
|
||||||
public class Replacer
|
private readonly IEnumerable<(string Key, Func<string> Text)> _replacements;
|
||||||
|
private readonly IEnumerable<(Regex Regex, Func<Match, string> Replacement)> _regex;
|
||||||
|
|
||||||
|
public Replacer(IEnumerable<(string, Func<string>)> replacements, IEnumerable<(Regex, Func<Match, string>)> regex)
|
||||||
{
|
{
|
||||||
private readonly IEnumerable<(string Key, Func<string> Text)> _replacements;
|
_replacements = replacements;
|
||||||
private readonly IEnumerable<(Regex Regex, Func<Match, string> Replacement)> _regex;
|
_regex = regex;
|
||||||
|
|
||||||
public Replacer(IEnumerable<(string, Func<string>)> replacements, IEnumerable<(Regex, Func<Match, string>)> regex)
|
|
||||||
{
|
|
||||||
_replacements = replacements;
|
|
||||||
_regex = regex;
|
|
||||||
}
|
|
||||||
|
|
||||||
public string Replace(string input)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrWhiteSpace(input))
|
|
||||||
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));
|
|
||||||
}
|
|
||||||
|
|
||||||
return input;
|
|
||||||
}
|
|
||||||
|
|
||||||
public SmartText Replace(SmartText data)
|
|
||||||
=> data switch
|
|
||||||
{
|
|
||||||
SmartEmbedText embedData => Replace(embedData),
|
|
||||||
SmartPlainText plain => Replace(plain),
|
|
||||||
_ => throw new ArgumentOutOfRangeException(nameof(data), "Unsupported argument type")
|
|
||||||
};
|
|
||||||
|
|
||||||
public SmartPlainText Replace(SmartPlainText plainText)
|
|
||||||
=> Replace(plainText.Text);
|
|
||||||
|
|
||||||
public SmartEmbedText Replace(SmartEmbedText embedData)
|
|
||||||
{
|
|
||||||
var newEmbedData = new SmartEmbedText();
|
|
||||||
newEmbedData.PlainText = Replace(embedData.PlainText);
|
|
||||||
newEmbedData.Description = Replace(embedData.Description);
|
|
||||||
newEmbedData.Title = Replace(embedData.Title);
|
|
||||||
newEmbedData.Thumbnail = Replace(embedData.Thumbnail);
|
|
||||||
newEmbedData.Image = Replace(embedData.Image);
|
|
||||||
newEmbedData.Url = Replace(embedData.Url);
|
|
||||||
if (embedData.Author != null)
|
|
||||||
{
|
|
||||||
newEmbedData.Author = new SmartTextEmbedAuthor();
|
|
||||||
newEmbedData.Author.Name = Replace(embedData.Author.Name);
|
|
||||||
newEmbedData.Author.IconUrl = Replace(embedData.Author.IconUrl);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (embedData.Fields != null)
|
|
||||||
{
|
|
||||||
var fields = new List<SmartTextEmbedField>();
|
|
||||||
foreach (var f in embedData.Fields)
|
|
||||||
{
|
|
||||||
var newF = new SmartTextEmbedField();
|
|
||||||
newF.Name = Replace(f.Name);
|
|
||||||
newF.Value = Replace(f.Value);
|
|
||||||
newF.Inline = f.Inline;
|
|
||||||
fields.Add(newF);
|
|
||||||
}
|
|
||||||
|
|
||||||
newEmbedData.Fields = fields.ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (embedData.Footer != null)
|
|
||||||
{
|
|
||||||
newEmbedData.Footer = new SmartTextEmbedFooter();
|
|
||||||
newEmbedData.Footer.Text = Replace(embedData.Footer.Text);
|
|
||||||
newEmbedData.Footer.IconUrl = Replace(embedData.Footer.IconUrl);
|
|
||||||
}
|
|
||||||
|
|
||||||
newEmbedData.Color = embedData.Color;
|
|
||||||
|
|
||||||
return newEmbedData;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
public string Replace(string input)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(input))
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SmartText Replace(SmartText data)
|
||||||
|
=> data switch
|
||||||
|
{
|
||||||
|
SmartEmbedText embedData => Replace(embedData),
|
||||||
|
SmartPlainText plain => Replace(plain),
|
||||||
|
_ => throw new ArgumentOutOfRangeException(nameof(data), "Unsupported argument type")
|
||||||
|
};
|
||||||
|
|
||||||
|
public SmartPlainText Replace(SmartPlainText plainText)
|
||||||
|
=> Replace(plainText.Text);
|
||||||
|
|
||||||
|
public SmartEmbedText Replace(SmartEmbedText embedData)
|
||||||
|
{
|
||||||
|
var newEmbedData = new SmartEmbedText();
|
||||||
|
newEmbedData.PlainText = Replace(embedData.PlainText);
|
||||||
|
newEmbedData.Description = Replace(embedData.Description);
|
||||||
|
newEmbedData.Title = Replace(embedData.Title);
|
||||||
|
newEmbedData.Thumbnail = Replace(embedData.Thumbnail);
|
||||||
|
newEmbedData.Image = Replace(embedData.Image);
|
||||||
|
newEmbedData.Url = Replace(embedData.Url);
|
||||||
|
if (embedData.Author != null)
|
||||||
|
{
|
||||||
|
newEmbedData.Author = new SmartTextEmbedAuthor();
|
||||||
|
newEmbedData.Author.Name = Replace(embedData.Author.Name);
|
||||||
|
newEmbedData.Author.IconUrl = Replace(embedData.Author.IconUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (embedData.Fields != null)
|
||||||
|
{
|
||||||
|
var fields = new List<SmartTextEmbedField>();
|
||||||
|
foreach (var f in embedData.Fields)
|
||||||
|
{
|
||||||
|
var newF = new SmartTextEmbedField();
|
||||||
|
newF.Name = Replace(f.Name);
|
||||||
|
newF.Value = Replace(f.Value);
|
||||||
|
newF.Inline = f.Inline;
|
||||||
|
fields.Add(newF);
|
||||||
|
}
|
||||||
|
|
||||||
|
newEmbedData.Fields = fields.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (embedData.Footer != null)
|
||||||
|
{
|
||||||
|
newEmbedData.Footer = new SmartTextEmbedFooter();
|
||||||
|
newEmbedData.Footer.Text = Replace(embedData.Footer.Text);
|
||||||
|
newEmbedData.Footer.IconUrl = Replace(embedData.Footer.IconUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
newEmbedData.Color = embedData.Color;
|
||||||
|
|
||||||
|
return newEmbedData;
|
||||||
|
}
|
||||||
|
}
|
@@ -1,16 +1,14 @@
|
|||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Serialization;
|
using Newtonsoft.Json.Serialization;
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace NadekoBot.Common
|
namespace NadekoBot.Common;
|
||||||
|
|
||||||
|
public class RequireObjectPropertiesContractResolver : DefaultContractResolver
|
||||||
{
|
{
|
||||||
public class RequireObjectPropertiesContractResolver : DefaultContractResolver
|
protected override JsonObjectContract CreateObjectContract(Type objectType)
|
||||||
{
|
{
|
||||||
protected override JsonObjectContract CreateObjectContract(Type objectType)
|
var contract = base.CreateObjectContract(objectType);
|
||||||
{
|
contract.ItemRequired = Required.DisallowNull;
|
||||||
var contract = base.CreateObjectContract(objectType);
|
return contract;
|
||||||
contract.ItemRequired = Required.DisallowNull;
|
|
||||||
return contract;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,63 +1,60 @@
|
|||||||
using System;
|
namespace NadekoBot.Common;
|
||||||
|
|
||||||
namespace NadekoBot.Common
|
public struct ShmartNumber : IEquatable<ShmartNumber>
|
||||||
{
|
{
|
||||||
public struct ShmartNumber : IEquatable<ShmartNumber>
|
public long Value { get; }
|
||||||
|
public string Input { get; }
|
||||||
|
|
||||||
|
public ShmartNumber(long val, string input = null)
|
||||||
{
|
{
|
||||||
public long Value { get; }
|
Value = val;
|
||||||
public string Input { get; }
|
Input = input;
|
||||||
|
|
||||||
public ShmartNumber(long val, string input = null)
|
|
||||||
{
|
|
||||||
Value = val;
|
|
||||||
Input = input;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static implicit operator ShmartNumber(long num)
|
|
||||||
{
|
|
||||||
return new ShmartNumber(num);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static implicit operator long(ShmartNumber num)
|
|
||||||
{
|
|
||||||
return num.Value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static implicit operator ShmartNumber(int num)
|
|
||||||
{
|
|
||||||
return new ShmartNumber(num);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
return Value.ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool Equals(object obj)
|
|
||||||
{
|
|
||||||
return obj is ShmartNumber sn
|
|
||||||
? Equals(sn)
|
|
||||||
: false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Equals(ShmartNumber other)
|
|
||||||
{
|
|
||||||
return other.Value == Value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override int GetHashCode()
|
|
||||||
{
|
|
||||||
return Value.GetHashCode() ^ Input.GetHashCode(StringComparison.InvariantCulture);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool operator ==(ShmartNumber left, ShmartNumber right)
|
|
||||||
{
|
|
||||||
return left.Equals(right);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool operator !=(ShmartNumber left, ShmartNumber right)
|
|
||||||
{
|
|
||||||
return !(left == right);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
public static implicit operator ShmartNumber(long num)
|
||||||
|
{
|
||||||
|
return new ShmartNumber(num);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static implicit operator long(ShmartNumber num)
|
||||||
|
{
|
||||||
|
return num.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static implicit operator ShmartNumber(int num)
|
||||||
|
{
|
||||||
|
return new ShmartNumber(num);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return Value.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Equals(object obj)
|
||||||
|
{
|
||||||
|
return obj is ShmartNumber sn
|
||||||
|
? Equals(sn)
|
||||||
|
: false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Equals(ShmartNumber other)
|
||||||
|
{
|
||||||
|
return other.Value == Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return Value.GetHashCode() ^ Input.GetHashCode(StringComparison.InvariantCulture);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator ==(ShmartNumber left, ShmartNumber right)
|
||||||
|
{
|
||||||
|
return left.Equals(right);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator !=(ShmartNumber left, ShmartNumber right)
|
||||||
|
{
|
||||||
|
return !(left == right);
|
||||||
|
}
|
||||||
|
}
|
@@ -1,137 +1,133 @@
|
|||||||
using System;
|
using Discord;
|
||||||
using System.Linq;
|
|
||||||
using Discord;
|
|
||||||
using NadekoBot.Extensions;
|
using NadekoBot.Extensions;
|
||||||
using NadekoBot.Services;
|
|
||||||
|
|
||||||
namespace NadekoBot
|
namespace NadekoBot;
|
||||||
|
|
||||||
|
public sealed record SmartEmbedText : SmartText
|
||||||
{
|
{
|
||||||
public sealed record SmartEmbedText : SmartText
|
public string PlainText { get; set; }
|
||||||
|
public string Title { get; set; }
|
||||||
|
public string Description { get; set; }
|
||||||
|
public string Url { get; set; }
|
||||||
|
public string Thumbnail { get; set; }
|
||||||
|
public string Image { get; set; }
|
||||||
|
|
||||||
|
public SmartTextEmbedAuthor Author { get; set; }
|
||||||
|
public SmartTextEmbedFooter Footer { get; set; }
|
||||||
|
public SmartTextEmbedField[] Fields { get; set; }
|
||||||
|
|
||||||
|
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 != null && Fields.Length > 0);
|
||||||
|
|
||||||
|
public static SmartEmbedText FromEmbed(IEmbed eb, string plainText = null)
|
||||||
{
|
{
|
||||||
public string PlainText { get; set; }
|
var set = new SmartEmbedText();
|
||||||
public string Title { get; set; }
|
|
||||||
public string Description { get; set; }
|
|
||||||
public string Url { get; set; }
|
|
||||||
public string Thumbnail { get; set; }
|
|
||||||
public string Image { get; set; }
|
|
||||||
|
|
||||||
public SmartTextEmbedAuthor Author { get; set; }
|
|
||||||
public SmartTextEmbedFooter Footer { get; set; }
|
|
||||||
public SmartTextEmbedField[] Fields { get; set; }
|
|
||||||
|
|
||||||
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 != null && Fields.Length > 0);
|
|
||||||
|
|
||||||
public static SmartEmbedText FromEmbed(IEmbed eb, string plainText = null)
|
|
||||||
{
|
|
||||||
var set = new SmartEmbedText();
|
|
||||||
|
|
||||||
set.PlainText = plainText;
|
set.PlainText = plainText;
|
||||||
set.Title = eb.Title;
|
set.Title = eb.Title;
|
||||||
set.Description = eb.Description;
|
set.Description = eb.Description;
|
||||||
set.Url = eb.Url;
|
set.Url = eb.Url;
|
||||||
set.Thumbnail = eb.Thumbnail?.Url;
|
set.Thumbnail = eb.Thumbnail?.Url;
|
||||||
set.Image = eb.Image?.Url;
|
set.Image = eb.Image?.Url;
|
||||||
set.Author = eb.Author is EmbedAuthor ea
|
set.Author = eb.Author is EmbedAuthor ea
|
||||||
? new()
|
? new()
|
||||||
{
|
{
|
||||||
Name = ea.Name,
|
Name = ea.Name,
|
||||||
Url = ea.Url,
|
Url = ea.Url,
|
||||||
IconUrl = ea.IconUrl
|
IconUrl = ea.IconUrl
|
||||||
}
|
}
|
||||||
: null;
|
: null;
|
||||||
set.Footer = eb.Footer is EmbedFooter ef
|
set.Footer = eb.Footer is EmbedFooter ef
|
||||||
? new()
|
? new()
|
||||||
{
|
{
|
||||||
Text = ef.Text,
|
Text = ef.Text,
|
||||||
IconUrl = ef.IconUrl
|
IconUrl = ef.IconUrl
|
||||||
}
|
}
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
if (eb.Fields.Length > 0)
|
if (eb.Fields.Length > 0)
|
||||||
set.Fields = eb
|
set.Fields = eb
|
||||||
.Fields
|
.Fields
|
||||||
.Select(field => new SmartTextEmbedField()
|
.Select(field => new SmartTextEmbedField()
|
||||||
{
|
{
|
||||||
Inline = field.Inline,
|
Inline = field.Inline,
|
||||||
Name = field.Name,
|
Name = field.Name,
|
||||||
Value = field.Value,
|
Value = field.Value,
|
||||||
})
|
})
|
||||||
.ToArray();
|
.ToArray();
|
||||||
|
|
||||||
set.Color = eb.Color?.RawValue ?? 0;
|
set.Color = eb.Color?.RawValue ?? 0;
|
||||||
return set;
|
return set;
|
||||||
}
|
}
|
||||||
|
|
||||||
public EmbedBuilder GetEmbed()
|
public EmbedBuilder GetEmbed()
|
||||||
|
{
|
||||||
|
var embed = new EmbedBuilder()
|
||||||
|
.WithColor(Color);
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(Title))
|
||||||
|
embed.WithTitle(Title);
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(Description))
|
||||||
|
embed.WithDescription(Description);
|
||||||
|
|
||||||
|
if (Url != null && Uri.IsWellFormedUriString(Url, UriKind.Absolute))
|
||||||
|
embed.WithUrl(Url);
|
||||||
|
|
||||||
|
if (Footer != null)
|
||||||
{
|
{
|
||||||
var embed = new EmbedBuilder()
|
embed.WithFooter(efb =>
|
||||||
.WithColor(Color);
|
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(Title))
|
|
||||||
embed.WithTitle(Title);
|
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(Description))
|
|
||||||
embed.WithDescription(Description);
|
|
||||||
|
|
||||||
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.WithText(Footer.Text);
|
efb.WithIconUrl(Footer.IconUrl);
|
||||||
if (Uri.IsWellFormedUriString(Footer.IconUrl, UriKind.Absolute))
|
});
|
||||||
efb.WithIconUrl(Footer.IconUrl);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Thumbnail != null && Uri.IsWellFormedUriString(Thumbnail, UriKind.Absolute))
|
|
||||||
embed.WithThumbnailUrl(Thumbnail);
|
|
||||||
|
|
||||||
if (Image != null && Uri.IsWellFormedUriString(Image, UriKind.Absolute))
|
|
||||||
embed.WithImageUrl(Image);
|
|
||||||
|
|
||||||
if (Author != null && !string.IsNullOrWhiteSpace(Author.Name))
|
|
||||||
{
|
|
||||||
if (!Uri.IsWellFormedUriString(Author.IconUrl, UriKind.Absolute))
|
|
||||||
Author.IconUrl = null;
|
|
||||||
if (!Uri.IsWellFormedUriString(Author.Url, UriKind.Absolute))
|
|
||||||
Author.Url = null;
|
|
||||||
|
|
||||||
embed.WithAuthor(Author.Name, Author.IconUrl, Author.Url);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Fields != null)
|
|
||||||
{
|
|
||||||
foreach (var f in Fields)
|
|
||||||
{
|
|
||||||
if (!string.IsNullOrWhiteSpace(f.Name) && !string.IsNullOrWhiteSpace(f.Value))
|
|
||||||
embed.AddField(f.Name, f.Value, f.Inline);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return embed;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void NormalizeFields()
|
if (Thumbnail != null && Uri.IsWellFormedUriString(Thumbnail, UriKind.Absolute))
|
||||||
|
embed.WithThumbnailUrl(Thumbnail);
|
||||||
|
|
||||||
|
if (Image != null && Uri.IsWellFormedUriString(Image, UriKind.Absolute))
|
||||||
|
embed.WithImageUrl(Image);
|
||||||
|
|
||||||
|
if (Author != null && !string.IsNullOrWhiteSpace(Author.Name))
|
||||||
{
|
{
|
||||||
if (Fields != null && Fields.Length > 0)
|
if (!Uri.IsWellFormedUriString(Author.IconUrl, UriKind.Absolute))
|
||||||
|
Author.IconUrl = null;
|
||||||
|
if (!Uri.IsWellFormedUriString(Author.Url, UriKind.Absolute))
|
||||||
|
Author.Url = null;
|
||||||
|
|
||||||
|
embed.WithAuthor(Author.Name, Author.IconUrl, Author.Url);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Fields != null)
|
||||||
|
{
|
||||||
|
foreach (var f in Fields)
|
||||||
{
|
{
|
||||||
foreach (var f in Fields)
|
if (!string.IsNullOrWhiteSpace(f.Name) && !string.IsNullOrWhiteSpace(f.Value))
|
||||||
{
|
embed.AddField(f.Name, f.Value, f.Inline);
|
||||||
f.Name = f.Name.TrimTo(256);
|
}
|
||||||
f.Value = f.Value.TrimTo(1024);
|
}
|
||||||
}
|
|
||||||
|
return embed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void NormalizeFields()
|
||||||
|
{
|
||||||
|
if (Fields != null && Fields.Length > 0)
|
||||||
|
{
|
||||||
|
foreach (var f in Fields)
|
||||||
|
{
|
||||||
|
f.Name = f.Name.TrimTo(256);
|
||||||
|
f.Value = f.Value.TrimTo(1024);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,23 +1,22 @@
|
|||||||
namespace NadekoBot
|
namespace NadekoBot;
|
||||||
|
|
||||||
|
public sealed record SmartPlainText : SmartText
|
||||||
{
|
{
|
||||||
public sealed record SmartPlainText : SmartText
|
public string Text { get; init; }
|
||||||
|
|
||||||
|
public SmartPlainText(string text)
|
||||||
{
|
{
|
||||||
public string Text { get; init; }
|
Text = text;
|
||||||
|
}
|
||||||
|
|
||||||
public SmartPlainText(string text)
|
public static implicit operator SmartPlainText(string input)
|
||||||
{
|
=> new SmartPlainText(input);
|
||||||
Text = text;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static implicit operator SmartPlainText(string input)
|
public static implicit operator string(SmartPlainText input)
|
||||||
=> new SmartPlainText(input);
|
=> input.Text;
|
||||||
|
|
||||||
public static implicit operator string(SmartPlainText input)
|
public override string ToString()
|
||||||
=> input.Text;
|
{
|
||||||
|
return Text;
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
return Text;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,53 +1,51 @@
|
|||||||
using System;
|
using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json;
|
|
||||||
|
|
||||||
namespace NadekoBot
|
namespace NadekoBot;
|
||||||
|
|
||||||
|
public abstract record SmartText
|
||||||
{
|
{
|
||||||
public abstract record SmartText
|
public bool IsEmbed => this is SmartEmbedText;
|
||||||
{
|
public bool IsPlainText => this is SmartPlainText;
|
||||||
public bool IsEmbed => this is SmartEmbedText;
|
|
||||||
public bool IsPlainText => this is SmartPlainText;
|
|
||||||
|
|
||||||
public static SmartText operator +(SmartText text, string input)
|
public static SmartText operator +(SmartText text, string input)
|
||||||
=> text switch
|
=> text switch
|
||||||
{
|
|
||||||
SmartEmbedText set => set with { PlainText = set.PlainText + input },
|
|
||||||
SmartPlainText spt => new SmartPlainText(spt.Text + input),
|
|
||||||
_ => throw new ArgumentOutOfRangeException(nameof(text))
|
|
||||||
};
|
|
||||||
|
|
||||||
public static SmartText operator +(string input, SmartText text)
|
|
||||||
=> text switch
|
|
||||||
{
|
|
||||||
SmartEmbedText set => set with { PlainText = input + set.PlainText },
|
|
||||||
SmartPlainText spt => new SmartPlainText(input + spt.Text),
|
|
||||||
_ => throw new ArgumentOutOfRangeException(nameof(text))
|
|
||||||
};
|
|
||||||
|
|
||||||
public static SmartText CreateFrom(string input)
|
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(input) || !input.TrimStart().StartsWith("{"))
|
SmartEmbedText set => set with { PlainText = set.PlainText + input },
|
||||||
|
SmartPlainText spt => new SmartPlainText(spt.Text + input),
|
||||||
|
_ => throw new ArgumentOutOfRangeException(nameof(text))
|
||||||
|
};
|
||||||
|
|
||||||
|
public static SmartText operator +(string input, SmartText text)
|
||||||
|
=> text switch
|
||||||
|
{
|
||||||
|
SmartEmbedText set => set with { PlainText = input + set.PlainText },
|
||||||
|
SmartPlainText spt => new SmartPlainText(input + spt.Text),
|
||||||
|
_ => throw new ArgumentOutOfRangeException(nameof(text))
|
||||||
|
};
|
||||||
|
|
||||||
|
public static SmartText CreateFrom(string input)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(input) || !input.TrimStart().StartsWith("{"))
|
||||||
|
{
|
||||||
|
return new SmartPlainText(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var smartEmbedText = JsonConvert.DeserializeObject<SmartEmbedText>(input);
|
||||||
|
|
||||||
|
smartEmbedText.NormalizeFields();
|
||||||
|
|
||||||
|
if (!smartEmbedText.IsValid)
|
||||||
{
|
{
|
||||||
return new SmartPlainText(input);
|
return new SmartPlainText(input);
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
return smartEmbedText;
|
||||||
{
|
}
|
||||||
var smartEmbedText = JsonConvert.DeserializeObject<SmartEmbedText>(input);
|
catch
|
||||||
|
{
|
||||||
smartEmbedText.NormalizeFields();
|
return new SmartPlainText(input);
|
||||||
|
|
||||||
if (!smartEmbedText.IsValid)
|
|
||||||
{
|
|
||||||
return new SmartPlainText(input);
|
|
||||||
}
|
|
||||||
|
|
||||||
return smartEmbedText;
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
return new SmartPlainText(input);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,13 +1,12 @@
|
|||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace NadekoBot
|
namespace NadekoBot;
|
||||||
|
|
||||||
|
public class SmartTextEmbedAuthor
|
||||||
{
|
{
|
||||||
public class SmartTextEmbedAuthor
|
public string Name { get; set; }
|
||||||
{
|
public string IconUrl { get; set; }
|
||||||
public string Name { get; set; }
|
[JsonProperty("icon_url")]
|
||||||
public string IconUrl { get; set; }
|
private string Icon_Url { set => IconUrl = value; }
|
||||||
[JsonProperty("icon_url")]
|
public string Url { get; set; }
|
||||||
private string Icon_Url { set => IconUrl = value; }
|
|
||||||
public string Url { get; set; }
|
|
||||||
}
|
|
||||||
}
|
}
|
@@ -1,9 +1,8 @@
|
|||||||
namespace NadekoBot
|
namespace NadekoBot;
|
||||||
|
|
||||||
|
public class SmartTextEmbedField
|
||||||
{
|
{
|
||||||
public class SmartTextEmbedField
|
public string Name { get; set; }
|
||||||
{
|
public string Value { get; set; }
|
||||||
public string Name { get; set; }
|
public bool Inline { get; set; }
|
||||||
public string Value { get; set; }
|
|
||||||
public bool Inline { get; set; }
|
|
||||||
}
|
|
||||||
}
|
}
|
@@ -1,12 +1,11 @@
|
|||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace NadekoBot
|
namespace NadekoBot;
|
||||||
|
|
||||||
|
public class SmartTextEmbedFooter
|
||||||
{
|
{
|
||||||
public class SmartTextEmbedFooter
|
public string Text { get; set; }
|
||||||
{
|
public string IconUrl { get; set; }
|
||||||
public string Text { get; set; }
|
[JsonProperty("icon_url")]
|
||||||
public string IconUrl { get; set; }
|
private string Icon_Url { set => IconUrl = value; }
|
||||||
[JsonProperty("icon_url")]
|
|
||||||
private string Icon_Url { set => IconUrl = value; }
|
|
||||||
}
|
|
||||||
}
|
}
|
@@ -1,91 +1,89 @@
|
|||||||
using Discord;
|
using Discord;
|
||||||
using Discord.WebSocket;
|
using Discord.WebSocket;
|
||||||
using System;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace NadekoBot.Common
|
namespace NadekoBot.Common;
|
||||||
|
|
||||||
|
public sealed class ReactionEventWrapper : IDisposable
|
||||||
{
|
{
|
||||||
public sealed class ReactionEventWrapper : IDisposable
|
public IUserMessage Message { get; }
|
||||||
|
public event Action<SocketReaction> OnReactionAdded = delegate { };
|
||||||
|
public event Action<SocketReaction> OnReactionRemoved = delegate { };
|
||||||
|
public event Action OnReactionsCleared = delegate { };
|
||||||
|
|
||||||
|
public ReactionEventWrapper(DiscordSocketClient client, IUserMessage msg)
|
||||||
{
|
{
|
||||||
public IUserMessage Message { get; }
|
Message = msg ?? throw new ArgumentNullException(nameof(msg));
|
||||||
public event Action<SocketReaction> OnReactionAdded = delegate { };
|
_client = client;
|
||||||
public event Action<SocketReaction> OnReactionRemoved = delegate { };
|
|
||||||
public event Action OnReactionsCleared = delegate { };
|
|
||||||
|
|
||||||
public ReactionEventWrapper(DiscordSocketClient client, IUserMessage msg)
|
_client.ReactionAdded += Discord_ReactionAdded;
|
||||||
{
|
_client.ReactionRemoved += Discord_ReactionRemoved;
|
||||||
Message = msg ?? throw new ArgumentNullException(nameof(msg));
|
_client.ReactionsCleared += Discord_ReactionsCleared;
|
||||||
_client = client;
|
|
||||||
|
|
||||||
_client.ReactionAdded += Discord_ReactionAdded;
|
|
||||||
_client.ReactionRemoved += Discord_ReactionRemoved;
|
|
||||||
_client.ReactionsCleared += Discord_ReactionsCleared;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Task Discord_ReactionsCleared(Cacheable<IUserMessage, ulong> msg, ISocketMessageChannel channel)
|
|
||||||
{
|
|
||||||
Task.Run(() =>
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (msg.Id == Message.Id)
|
|
||||||
OnReactionsCleared?.Invoke();
|
|
||||||
}
|
|
||||||
catch { }
|
|
||||||
});
|
|
||||||
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Task Discord_ReactionRemoved(Cacheable<IUserMessage, ulong> msg, ISocketMessageChannel channel, SocketReaction reaction)
|
|
||||||
{
|
|
||||||
Task.Run(() =>
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (msg.Id == Message.Id)
|
|
||||||
OnReactionRemoved?.Invoke(reaction);
|
|
||||||
}
|
|
||||||
catch { }
|
|
||||||
});
|
|
||||||
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Task Discord_ReactionAdded(Cacheable<IUserMessage, ulong> msg, ISocketMessageChannel channel, SocketReaction reaction)
|
|
||||||
{
|
|
||||||
Task.Run(() =>
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (msg.Id == Message.Id)
|
|
||||||
OnReactionAdded?.Invoke(reaction);
|
|
||||||
}
|
|
||||||
catch { }
|
|
||||||
});
|
|
||||||
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void UnsubAll()
|
|
||||||
{
|
|
||||||
_client.ReactionAdded -= Discord_ReactionAdded;
|
|
||||||
_client.ReactionRemoved -= Discord_ReactionRemoved;
|
|
||||||
_client.ReactionsCleared -= Discord_ReactionsCleared;
|
|
||||||
OnReactionAdded = null;
|
|
||||||
OnReactionRemoved = null;
|
|
||||||
OnReactionsCleared = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool disposing = false;
|
|
||||||
private readonly DiscordSocketClient _client;
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
if (disposing)
|
|
||||||
return;
|
|
||||||
disposing = true;
|
|
||||||
UnsubAll();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
private Task Discord_ReactionsCleared(Cacheable<IUserMessage, ulong> msg, ISocketMessageChannel channel)
|
||||||
|
{
|
||||||
|
Task.Run(() =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (msg.Id == Message.Id)
|
||||||
|
OnReactionsCleared?.Invoke();
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
});
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task Discord_ReactionRemoved(Cacheable<IUserMessage, ulong> msg, ISocketMessageChannel channel, SocketReaction reaction)
|
||||||
|
{
|
||||||
|
Task.Run(() =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (msg.Id == Message.Id)
|
||||||
|
OnReactionRemoved?.Invoke(reaction);
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
});
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task Discord_ReactionAdded(Cacheable<IUserMessage, ulong> msg, ISocketMessageChannel channel, SocketReaction reaction)
|
||||||
|
{
|
||||||
|
Task.Run(() =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (msg.Id == Message.Id)
|
||||||
|
OnReactionAdded?.Invoke(reaction);
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
});
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UnsubAll()
|
||||||
|
{
|
||||||
|
_client.ReactionAdded -= Discord_ReactionAdded;
|
||||||
|
_client.ReactionRemoved -= Discord_ReactionRemoved;
|
||||||
|
_client.ReactionsCleared -= Discord_ReactionsCleared;
|
||||||
|
OnReactionAdded = null;
|
||||||
|
OnReactionRemoved = null;
|
||||||
|
OnReactionsCleared = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool disposing = false;
|
||||||
|
private readonly DiscordSocketClient _client;
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (disposing)
|
||||||
|
return;
|
||||||
|
disposing = true;
|
||||||
|
UnsubAll();
|
||||||
|
}
|
||||||
|
}
|
@@ -1,9 +1,8 @@
|
|||||||
namespace NadekoBot.Common.TypeReaders
|
namespace NadekoBot.Common.TypeReaders;
|
||||||
|
|
||||||
|
public enum AddRemove
|
||||||
{
|
{
|
||||||
public enum AddRemove
|
Add = int.MinValue,
|
||||||
{
|
Rem = int.MinValue + 1,
|
||||||
Add = int.MinValue,
|
Rm = int.MinValue + 1,
|
||||||
Rem = int.MinValue + 1,
|
}
|
||||||
Rm = int.MinValue + 1,
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,90 +1,87 @@
|
|||||||
using System;
|
using System.Threading.Tasks;
|
||||||
using System.Linq;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Discord.Commands;
|
using Discord.Commands;
|
||||||
using NadekoBot.Services;
|
using NadekoBot.Services;
|
||||||
using NadekoBot.Modules.CustomReactions.Services;
|
using NadekoBot.Modules.CustomReactions.Services;
|
||||||
|
|
||||||
namespace NadekoBot.Common.TypeReaders
|
namespace NadekoBot.Common.TypeReaders;
|
||||||
|
|
||||||
|
public sealed class CommandTypeReader : NadekoTypeReader<CommandInfo>
|
||||||
{
|
{
|
||||||
public sealed class CommandTypeReader : NadekoTypeReader<CommandInfo>
|
private readonly CommandHandler _handler;
|
||||||
|
private readonly CommandService _cmds;
|
||||||
|
|
||||||
|
public CommandTypeReader(CommandHandler handler, CommandService cmds)
|
||||||
{
|
{
|
||||||
private readonly CommandHandler _handler;
|
_handler = handler;
|
||||||
private readonly CommandService _cmds;
|
_cmds = cmds;
|
||||||
|
|
||||||
public CommandTypeReader(CommandHandler handler, CommandService cmds)
|
|
||||||
{
|
|
||||||
_handler = handler;
|
|
||||||
_cmds = cmds;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override Task<TypeReaderResult> ReadAsync(ICommandContext context, string input)
|
|
||||||
{
|
|
||||||
input = input.ToUpperInvariant();
|
|
||||||
var prefix = _handler.GetPrefix(context.Guild);
|
|
||||||
if (!input.StartsWith(prefix.ToUpperInvariant(), StringComparison.InvariantCulture))
|
|
||||||
return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, "No such command found."));
|
|
||||||
|
|
||||||
input = input.Substring(prefix.Length);
|
|
||||||
|
|
||||||
var cmd = _cmds.Commands.FirstOrDefault(c => c.Aliases.Select(a => a.ToUpperInvariant()).Contains(input));
|
|
||||||
if (cmd is null)
|
|
||||||
return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, "No such command found."));
|
|
||||||
|
|
||||||
return Task.FromResult(TypeReaderResult.FromSuccess(cmd));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class CommandOrCrTypeReader : NadekoTypeReader<CommandOrCrInfo>
|
public override Task<TypeReaderResult> ReadAsync(ICommandContext context, string input)
|
||||||
{
|
{
|
||||||
private readonly CommandService _cmds;
|
input = input.ToUpperInvariant();
|
||||||
private readonly CustomReactionsService _crs;
|
var prefix = _handler.GetPrefix(context.Guild);
|
||||||
private readonly CommandHandler _commandHandler;
|
if (!input.StartsWith(prefix.ToUpperInvariant(), StringComparison.InvariantCulture))
|
||||||
|
return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, "No such command found."));
|
||||||
|
|
||||||
public CommandOrCrTypeReader(
|
input = input.Substring(prefix.Length);
|
||||||
CommandService cmds,
|
|
||||||
CustomReactionsService crs,
|
|
||||||
CommandHandler commandHandler)
|
|
||||||
{
|
|
||||||
_cmds = cmds;
|
|
||||||
_crs = crs;
|
|
||||||
_commandHandler = commandHandler;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async Task<TypeReaderResult> ReadAsync(ICommandContext context, string input)
|
var cmd = _cmds.Commands.FirstOrDefault(c => c.Aliases.Select(a => a.ToUpperInvariant()).Contains(input));
|
||||||
{
|
if (cmd is null)
|
||||||
input = input.ToUpperInvariant();
|
return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, "No such command found."));
|
||||||
|
|
||||||
if (_crs.ReactionExists(context.Guild?.Id, input))
|
return Task.FromResult(TypeReaderResult.FromSuccess(cmd));
|
||||||
{
|
|
||||||
return TypeReaderResult.FromSuccess(new CommandOrCrInfo(input, CommandOrCrInfo.Type.Custom));
|
|
||||||
}
|
|
||||||
|
|
||||||
var cmd = await new CommandTypeReader(_commandHandler, _cmds).ReadAsync(context, input).ConfigureAwait(false);
|
|
||||||
if (cmd.IsSuccess)
|
|
||||||
{
|
|
||||||
return TypeReaderResult.FromSuccess(new CommandOrCrInfo(((CommandInfo)cmd.Values.First().Value).Name, CommandOrCrInfo.Type.Normal));
|
|
||||||
}
|
|
||||||
return TypeReaderResult.FromError(CommandError.ParseFailed, "No such command or cr found.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class CommandOrCrInfo
|
|
||||||
{
|
|
||||||
public enum Type
|
|
||||||
{
|
|
||||||
Normal,
|
|
||||||
Custom,
|
|
||||||
}
|
|
||||||
|
|
||||||
public string Name { get; set; }
|
|
||||||
public Type CmdType { get; set; }
|
|
||||||
public bool IsCustom => CmdType == Type.Custom;
|
|
||||||
|
|
||||||
public CommandOrCrInfo(string input, Type type)
|
|
||||||
{
|
|
||||||
this.Name = input;
|
|
||||||
this.CmdType = type;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public sealed class CommandOrCrTypeReader : NadekoTypeReader<CommandOrCrInfo>
|
||||||
|
{
|
||||||
|
private readonly CommandService _cmds;
|
||||||
|
private readonly CustomReactionsService _crs;
|
||||||
|
private readonly CommandHandler _commandHandler;
|
||||||
|
|
||||||
|
public CommandOrCrTypeReader(
|
||||||
|
CommandService cmds,
|
||||||
|
CustomReactionsService crs,
|
||||||
|
CommandHandler commandHandler)
|
||||||
|
{
|
||||||
|
_cmds = cmds;
|
||||||
|
_crs = crs;
|
||||||
|
_commandHandler = commandHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task<TypeReaderResult> ReadAsync(ICommandContext context, string input)
|
||||||
|
{
|
||||||
|
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).ConfigureAwait(false);
|
||||||
|
if (cmd.IsSuccess)
|
||||||
|
{
|
||||||
|
return TypeReaderResult.FromSuccess(new CommandOrCrInfo(((CommandInfo)cmd.Values.First().Value).Name, CommandOrCrInfo.Type.Normal));
|
||||||
|
}
|
||||||
|
return TypeReaderResult.FromError(CommandError.ParseFailed, "No such command or cr found.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class CommandOrCrInfo
|
||||||
|
{
|
||||||
|
public enum Type
|
||||||
|
{
|
||||||
|
Normal,
|
||||||
|
Custom,
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Name { get; set; }
|
||||||
|
public Type CmdType { get; set; }
|
||||||
|
public bool IsCustom => CmdType == Type.Custom;
|
||||||
|
|
||||||
|
public CommandOrCrInfo(string input, Type type)
|
||||||
|
{
|
||||||
|
this.Name = input;
|
||||||
|
this.CmdType = type;
|
||||||
|
}
|
||||||
|
}
|
@@ -2,16 +2,15 @@
|
|||||||
using Discord;
|
using Discord;
|
||||||
using Discord.Commands;
|
using Discord.Commands;
|
||||||
|
|
||||||
namespace NadekoBot.Common.TypeReaders
|
namespace NadekoBot.Common.TypeReaders;
|
||||||
{
|
|
||||||
public sealed class EmoteTypeReader : NadekoTypeReader<Emote>
|
|
||||||
{
|
|
||||||
public override Task<TypeReaderResult> ReadAsync(ICommandContext ctx, string input)
|
|
||||||
{
|
|
||||||
if (!Emote.TryParse(input, out var emote))
|
|
||||||
return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, "Input is not a valid emote"));
|
|
||||||
|
|
||||||
return Task.FromResult(TypeReaderResult.FromSuccess(emote));
|
public sealed class EmoteTypeReader : NadekoTypeReader<Emote>
|
||||||
}
|
{
|
||||||
|
public override Task<TypeReaderResult> ReadAsync(ICommandContext ctx, string input)
|
||||||
|
{
|
||||||
|
if (!Emote.TryParse(input, out var emote))
|
||||||
|
return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, "Input is not a valid emote"));
|
||||||
|
|
||||||
|
return Task.FromResult(TypeReaderResult.FromSuccess(emote));
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,53 +1,51 @@
|
|||||||
using System;
|
using System.Threading.Tasks;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Discord.Commands;
|
using Discord.Commands;
|
||||||
using NadekoBot.Modules.Administration.Services;
|
using NadekoBot.Modules.Administration.Services;
|
||||||
|
|
||||||
namespace NadekoBot.Common.TypeReaders
|
namespace NadekoBot.Common.TypeReaders;
|
||||||
|
|
||||||
|
public sealed class GuildDateTimeTypeReader : NadekoTypeReader<GuildDateTime>
|
||||||
{
|
{
|
||||||
public sealed class GuildDateTimeTypeReader : NadekoTypeReader<GuildDateTime>
|
private readonly GuildTimezoneService _gts;
|
||||||
|
|
||||||
|
public GuildDateTimeTypeReader(GuildTimezoneService gts)
|
||||||
{
|
{
|
||||||
private readonly GuildTimezoneService _gts;
|
_gts = gts;
|
||||||
|
}
|
||||||
public GuildDateTimeTypeReader(GuildTimezoneService gts)
|
|
||||||
{
|
|
||||||
_gts = gts;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override Task<TypeReaderResult> ReadAsync(ICommandContext context, string input)
|
public override Task<TypeReaderResult> ReadAsync(ICommandContext context, string input)
|
||||||
{
|
{
|
||||||
var gdt = Parse(context.Guild.Id, input);
|
var gdt = Parse(context.Guild.Id, input);
|
||||||
if(gdt is null)
|
if(gdt is null)
|
||||||
return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, "Input string is in an incorrect format."));
|
return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, "Input string is in an incorrect format."));
|
||||||
|
|
||||||
return Task.FromResult(TypeReaderResult.FromSuccess(gdt));
|
return Task.FromResult(TypeReaderResult.FromSuccess(gdt));
|
||||||
}
|
|
||||||
|
|
||||||
private GuildDateTime Parse(ulong guildId, string input)
|
|
||||||
{
|
|
||||||
if (!DateTime.TryParse(input, out var dt))
|
|
||||||
return null;
|
|
||||||
|
|
||||||
var tz = _gts.GetTimeZoneOrUtc(guildId);
|
|
||||||
|
|
||||||
return new(tz, dt);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class GuildDateTime
|
private GuildDateTime Parse(ulong guildId, string input)
|
||||||
{
|
{
|
||||||
public TimeZoneInfo Timezone { get; }
|
if (!DateTime.TryParse(input, out var dt))
|
||||||
public DateTime CurrentGuildTime { get; }
|
return null;
|
||||||
public DateTime InputTime { get; }
|
|
||||||
public DateTime InputTimeUtc { get; }
|
|
||||||
|
|
||||||
public GuildDateTime(TimeZoneInfo guildTimezone, DateTime inputTime)
|
var tz = _gts.GetTimeZoneOrUtc(guildId);
|
||||||
{
|
|
||||||
var now = DateTime.UtcNow;
|
return new(tz, dt);
|
||||||
Timezone = guildTimezone;
|
|
||||||
CurrentGuildTime = TimeZoneInfo.ConvertTime(now, TimeZoneInfo.Utc, Timezone);
|
|
||||||
InputTime = inputTime;
|
|
||||||
InputTimeUtc = TimeZoneInfo.ConvertTime(inputTime, Timezone, TimeZoneInfo.Utc);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class GuildDateTime
|
||||||
|
{
|
||||||
|
public TimeZoneInfo Timezone { get; }
|
||||||
|
public DateTime CurrentGuildTime { get; }
|
||||||
|
public DateTime InputTime { get; }
|
||||||
|
public DateTime InputTimeUtc { get; }
|
||||||
|
|
||||||
|
public GuildDateTime(TimeZoneInfo guildTimezone, DateTime inputTime)
|
||||||
|
{
|
||||||
|
var now = DateTime.UtcNow;
|
||||||
|
Timezone = guildTimezone;
|
||||||
|
CurrentGuildTime = TimeZoneInfo.ConvertTime(now, TimeZoneInfo.Utc, Timezone);
|
||||||
|
InputTime = inputTime;
|
||||||
|
InputTimeUtc = TimeZoneInfo.ConvertTime(inputTime, Timezone, TimeZoneInfo.Utc);
|
||||||
|
}
|
||||||
|
}
|
@@ -1,31 +1,29 @@
|
|||||||
using System.Linq;
|
using System.Threading.Tasks;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Discord.Commands;
|
using Discord.Commands;
|
||||||
using Discord.WebSocket;
|
using Discord.WebSocket;
|
||||||
using Discord;
|
using Discord;
|
||||||
|
|
||||||
namespace NadekoBot.Common.TypeReaders
|
namespace NadekoBot.Common.TypeReaders;
|
||||||
|
|
||||||
|
public sealed class GuildTypeReader : NadekoTypeReader<IGuild>
|
||||||
{
|
{
|
||||||
public sealed class GuildTypeReader : NadekoTypeReader<IGuild>
|
private readonly DiscordSocketClient _client;
|
||||||
|
|
||||||
|
public GuildTypeReader(DiscordSocketClient client)
|
||||||
{
|
{
|
||||||
private readonly DiscordSocketClient _client;
|
_client = client;
|
||||||
|
|
||||||
public GuildTypeReader(DiscordSocketClient client)
|
|
||||||
{
|
|
||||||
_client = client;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override Task<TypeReaderResult> ReadAsync(ICommandContext context, string input)
|
|
||||||
{
|
|
||||||
input = input.Trim().ToUpperInvariant();
|
|
||||||
var guilds = _client.Guilds;
|
|
||||||
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"));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
public override Task<TypeReaderResult> ReadAsync(ICommandContext context, string input)
|
||||||
|
{
|
||||||
|
input = input.Trim().ToUpperInvariant();
|
||||||
|
var guilds = _client.Guilds;
|
||||||
|
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"));
|
||||||
|
}
|
||||||
|
}
|
@@ -1,24 +1,23 @@
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Discord.Commands;
|
using Discord.Commands;
|
||||||
|
|
||||||
namespace NadekoBot.Common.TypeReaders
|
namespace NadekoBot.Common.TypeReaders;
|
||||||
|
|
||||||
|
public sealed class KwumTypeReader : NadekoTypeReader<kwum>
|
||||||
{
|
{
|
||||||
public sealed class KwumTypeReader : NadekoTypeReader<kwum>
|
public override Task<TypeReaderResult> ReadAsync(ICommandContext context, string input)
|
||||||
{
|
{
|
||||||
public override Task<TypeReaderResult> ReadAsync(ICommandContext context, string input)
|
if (kwum.TryParse(input, out var val))
|
||||||
{
|
return Task.FromResult(TypeReaderResult.FromSuccess(val));
|
||||||
if (kwum.TryParse(input, out var val))
|
|
||||||
return Task.FromResult(TypeReaderResult.FromSuccess(val));
|
|
||||||
|
|
||||||
return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, "Input is not a valid kwum"));
|
return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, "Input is not a valid kwum"));
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public sealed class SmartTextTypeReader : NadekoTypeReader<SmartText>
|
public sealed class SmartTextTypeReader : NadekoTypeReader<SmartText>
|
||||||
|
{
|
||||||
|
public override Task<TypeReaderResult> ReadAsync(ICommandContext ctx, string input)
|
||||||
{
|
{
|
||||||
public override Task<TypeReaderResult> ReadAsync(ICommandContext ctx, string input)
|
return Task.FromResult(TypeReaderResult.FromSuccess(SmartText.CreateFrom(input)));
|
||||||
{
|
|
||||||
return Task.FromResult(TypeReaderResult.FromSuccess(SmartText.CreateFrom(input)));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,27 +1,26 @@
|
|||||||
namespace NadekoBot.Common.TypeReaders.Models
|
namespace NadekoBot.Common.TypeReaders.Models;
|
||||||
|
|
||||||
|
public class PermissionAction
|
||||||
{
|
{
|
||||||
public class PermissionAction
|
public static PermissionAction Enable => new PermissionAction(true);
|
||||||
|
public static PermissionAction Disable => new PermissionAction(false);
|
||||||
|
|
||||||
|
public bool Value { get; }
|
||||||
|
|
||||||
|
public PermissionAction(bool value)
|
||||||
{
|
{
|
||||||
public static PermissionAction Enable => new PermissionAction(true);
|
this.Value = value;
|
||||||
public static PermissionAction Disable => new PermissionAction(false);
|
|
||||||
|
|
||||||
public bool Value { get; }
|
|
||||||
|
|
||||||
public PermissionAction(bool value)
|
|
||||||
{
|
|
||||||
this.Value = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool Equals(object obj)
|
|
||||||
{
|
|
||||||
if (obj is null || GetType() != obj.GetType())
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.Value == ((PermissionAction)obj).Value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override int GetHashCode() => Value.GetHashCode();
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
public override bool Equals(object obj)
|
||||||
|
{
|
||||||
|
if (obj is null || GetType() != obj.GetType())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.Value == ((PermissionAction)obj).Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetHashCode() => Value.GetHashCode();
|
||||||
|
}
|
@@ -1,65 +1,62 @@
|
|||||||
using System;
|
using System.Text.RegularExpressions;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
|
|
||||||
namespace NadekoBot.Common.TypeReaders.Models
|
namespace NadekoBot.Common.TypeReaders.Models;
|
||||||
|
|
||||||
|
public class StoopidTime
|
||||||
{
|
{
|
||||||
public class StoopidTime
|
public string Input { get; set; }
|
||||||
|
public TimeSpan Time { get; set; }
|
||||||
|
|
||||||
|
private static readonly Regex _regex = new Regex(
|
||||||
|
@"^(?:(?<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);
|
||||||
|
|
||||||
|
private StoopidTime() { }
|
||||||
|
|
||||||
|
public static StoopidTime FromInput(string input)
|
||||||
{
|
{
|
||||||
public string Input { get; set; }
|
var m = _regex.Match(input);
|
||||||
public TimeSpan Time { get; set; }
|
|
||||||
|
|
||||||
private static readonly Regex _regex = new Regex(
|
if (m.Length == 0)
|
||||||
@"^(?:(?<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);
|
|
||||||
|
|
||||||
private StoopidTime() { }
|
|
||||||
|
|
||||||
public static StoopidTime FromInput(string input)
|
|
||||||
{
|
{
|
||||||
var m = _regex.Match(input);
|
throw new ArgumentException("Invalid string input format.");
|
||||||
|
|
||||||
if (m.Length == 0)
|
|
||||||
{
|
|
||||||
throw new ArgumentException("Invalid string input format.");
|
|
||||||
}
|
|
||||||
|
|
||||||
string output = "";
|
|
||||||
var namesAndValues = new Dictionary<string, int>();
|
|
||||||
|
|
||||||
foreach (var groupName in _regex.GetGroupNames())
|
|
||||||
{
|
|
||||||
if (groupName == "0") continue;
|
|
||||||
if (!int.TryParse(m.Groups[groupName].Value, out var value))
|
|
||||||
{
|
|
||||||
namesAndValues[groupName] = 0;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
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"],
|
|
||||||
namesAndValues["hours"],
|
|
||||||
namesAndValues["minutes"],
|
|
||||||
namesAndValues["seconds"]);
|
|
||||||
if (ts > TimeSpan.FromDays(90))
|
|
||||||
{
|
|
||||||
throw new ArgumentException("Time is too long.");
|
|
||||||
}
|
|
||||||
|
|
||||||
return new StoopidTime()
|
|
||||||
{
|
|
||||||
Input = input,
|
|
||||||
Time = ts,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
string output = "";
|
||||||
|
var namesAndValues = new Dictionary<string, int>();
|
||||||
|
|
||||||
|
foreach (var groupName in _regex.GetGroupNames())
|
||||||
|
{
|
||||||
|
if (groupName == "0") continue;
|
||||||
|
if (!int.TryParse(m.Groups[groupName].Value, out var value))
|
||||||
|
{
|
||||||
|
namesAndValues[groupName] = 0;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
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"],
|
||||||
|
namesAndValues["hours"],
|
||||||
|
namesAndValues["minutes"],
|
||||||
|
namesAndValues["seconds"]);
|
||||||
|
if (ts > TimeSpan.FromDays(90))
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Time is too long.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return new StoopidTime()
|
||||||
|
{
|
||||||
|
Input = input,
|
||||||
|
Time = ts,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,55 +1,53 @@
|
|||||||
using System.Linq;
|
using System.Threading.Tasks;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Discord.Commands;
|
using Discord.Commands;
|
||||||
using NadekoBot.Extensions;
|
using NadekoBot.Extensions;
|
||||||
|
|
||||||
namespace NadekoBot.Common.TypeReaders
|
namespace NadekoBot.Common.TypeReaders;
|
||||||
|
|
||||||
|
public sealed class ModuleTypeReader : NadekoTypeReader<ModuleInfo>
|
||||||
{
|
{
|
||||||
public sealed class ModuleTypeReader : NadekoTypeReader<ModuleInfo>
|
private readonly CommandService _cmds;
|
||||||
|
|
||||||
|
public ModuleTypeReader(CommandService cmds)
|
||||||
{
|
{
|
||||||
private readonly CommandService _cmds;
|
_cmds = cmds;
|
||||||
|
|
||||||
public ModuleTypeReader(CommandService cmds)
|
|
||||||
{
|
|
||||||
_cmds = cmds;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override Task<TypeReaderResult> ReadAsync(ICommandContext context, string input)
|
|
||||||
{
|
|
||||||
input = input.ToUpperInvariant();
|
|
||||||
var module = _cmds.Modules.GroupBy(m => m.GetTopLevelModule()).FirstOrDefault(m => m.Key.Name.ToUpperInvariant() == input)?.Key;
|
|
||||||
if (module is null)
|
|
||||||
return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, "No such module found."));
|
|
||||||
|
|
||||||
return Task.FromResult(TypeReaderResult.FromSuccess(module));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class ModuleOrCrTypeReader : NadekoTypeReader<ModuleOrCrInfo>
|
public override Task<TypeReaderResult> ReadAsync(ICommandContext context, string input)
|
||||||
{
|
{
|
||||||
private readonly CommandService _cmds;
|
input = input.ToUpperInvariant();
|
||||||
|
var module = _cmds.Modules.GroupBy(m => m.GetTopLevelModule()).FirstOrDefault(m => m.Key.Name.ToUpperInvariant() == input)?.Key;
|
||||||
|
if (module is null)
|
||||||
|
return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, "No such module found."));
|
||||||
|
|
||||||
public ModuleOrCrTypeReader(CommandService cmds)
|
return Task.FromResult(TypeReaderResult.FromSuccess(module));
|
||||||
{
|
|
||||||
_cmds = cmds;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override Task<TypeReaderResult> ReadAsync(ICommandContext context, string input)
|
|
||||||
{
|
|
||||||
input = input.ToUpperInvariant();
|
|
||||||
var module = _cmds.Modules.GroupBy(m => m.GetTopLevelModule()).FirstOrDefault(m => m.Key.Name.ToUpperInvariant() == input)?.Key;
|
|
||||||
if (module is null && input != "ACTUALCUSTOMREACTIONS")
|
|
||||||
return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, "No such module found."));
|
|
||||||
|
|
||||||
return Task.FromResult(TypeReaderResult.FromSuccess(new ModuleOrCrInfo
|
|
||||||
{
|
|
||||||
Name = input,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public sealed class ModuleOrCrInfo
|
|
||||||
{
|
|
||||||
public string Name { get; set; }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public sealed class ModuleOrCrTypeReader : NadekoTypeReader<ModuleOrCrInfo>
|
||||||
|
{
|
||||||
|
private readonly CommandService _cmds;
|
||||||
|
|
||||||
|
public ModuleOrCrTypeReader(CommandService cmds)
|
||||||
|
{
|
||||||
|
_cmds = cmds;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Task<TypeReaderResult> ReadAsync(ICommandContext context, string input)
|
||||||
|
{
|
||||||
|
input = input.ToUpperInvariant();
|
||||||
|
var module = _cmds.Modules.GroupBy(m => m.GetTopLevelModule()).FirstOrDefault(m => m.Key.Name.ToUpperInvariant() == input)?.Key;
|
||||||
|
if (module is null && input != "ACTUALCUSTOMREACTIONS")
|
||||||
|
return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, "No such module found."));
|
||||||
|
|
||||||
|
return Task.FromResult(TypeReaderResult.FromSuccess(new ModuleOrCrInfo
|
||||||
|
{
|
||||||
|
Name = input,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class ModuleOrCrInfo
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
}
|
@@ -1,14 +1,12 @@
|
|||||||
using System;
|
using System.Threading.Tasks;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Discord.Commands;
|
using Discord.Commands;
|
||||||
|
|
||||||
namespace NadekoBot.Common.TypeReaders
|
namespace NadekoBot.Common.TypeReaders;
|
||||||
{
|
|
||||||
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)
|
public abstract class NadekoTypeReader<T> : TypeReader
|
||||||
=> ReadAsync(ctx, input);
|
{
|
||||||
}
|
public abstract Task<TypeReaderResult> ReadAsync(ICommandContext ctx, string input);
|
||||||
}
|
|
||||||
|
public override Task<TypeReaderResult> ReadAsync(ICommandContext ctx, string input, IServiceProvider services)
|
||||||
|
=> ReadAsync(ctx, input);
|
||||||
|
}
|
@@ -2,39 +2,38 @@
|
|||||||
using Discord.Commands;
|
using Discord.Commands;
|
||||||
using NadekoBot.Common.TypeReaders.Models;
|
using NadekoBot.Common.TypeReaders.Models;
|
||||||
|
|
||||||
namespace NadekoBot.Common.TypeReaders
|
namespace NadekoBot.Common.TypeReaders;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Used instead of bool for more flexible keywords for true/false only in the permission module
|
||||||
|
/// </summary>
|
||||||
|
public sealed class PermissionActionTypeReader : NadekoTypeReader<PermissionAction>
|
||||||
{
|
{
|
||||||
/// <summary>
|
public override Task<TypeReaderResult> ReadAsync(ICommandContext context, string input)
|
||||||
/// Used instead of bool for more flexible keywords for true/false only in the permission module
|
|
||||||
/// </summary>
|
|
||||||
public sealed class PermissionActionTypeReader : NadekoTypeReader<PermissionAction>
|
|
||||||
{
|
{
|
||||||
public override Task<TypeReaderResult> ReadAsync(ICommandContext context, string input)
|
input = input.ToUpperInvariant();
|
||||||
|
switch (input)
|
||||||
{
|
{
|
||||||
input = input.ToUpperInvariant();
|
case "1":
|
||||||
switch (input)
|
case "T":
|
||||||
{
|
case "TRUE":
|
||||||
case "1":
|
case "ENABLE":
|
||||||
case "T":
|
case "ENABLED":
|
||||||
case "TRUE":
|
case "ALLOW":
|
||||||
case "ENABLE":
|
case "PERMIT":
|
||||||
case "ENABLED":
|
case "UNBAN":
|
||||||
case "ALLOW":
|
return Task.FromResult(TypeReaderResult.FromSuccess(PermissionAction.Enable));
|
||||||
case "PERMIT":
|
case "0":
|
||||||
case "UNBAN":
|
case "F":
|
||||||
return Task.FromResult(TypeReaderResult.FromSuccess(PermissionAction.Enable));
|
case "FALSE":
|
||||||
case "0":
|
case "DENY":
|
||||||
case "F":
|
case "DISABLE":
|
||||||
case "FALSE":
|
case "DISABLED":
|
||||||
case "DENY":
|
case "DISALLOW":
|
||||||
case "DISABLE":
|
case "BAN":
|
||||||
case "DISABLED":
|
return Task.FromResult(TypeReaderResult.FromSuccess(PermissionAction.Disable));
|
||||||
case "DISALLOW":
|
default:
|
||||||
case "BAN":
|
return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, "Did not receive a valid boolean value"));
|
||||||
return Task.FromResult(TypeReaderResult.FromSuccess(PermissionAction.Disable));
|
|
||||||
default:
|
|
||||||
return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, "Did not receive a valid boolean value"));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,25 +1,23 @@
|
|||||||
using System;
|
using System.Threading.Tasks;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Discord.Commands;
|
using Discord.Commands;
|
||||||
using SixLabors.ImageSharp;
|
using SixLabors.ImageSharp;
|
||||||
|
|
||||||
namespace NadekoBot.Common.TypeReaders
|
namespace NadekoBot.Common.TypeReaders;
|
||||||
{
|
|
||||||
public sealed class Rgba32TypeReader : NadekoTypeReader<Color>
|
|
||||||
{
|
|
||||||
public override async Task<TypeReaderResult> ReadAsync(ICommandContext context, string input)
|
|
||||||
{
|
|
||||||
await Task.Yield();
|
|
||||||
|
|
||||||
input = input.Replace("#", "", StringComparison.InvariantCulture);
|
public sealed class Rgba32TypeReader : NadekoTypeReader<Color>
|
||||||
try
|
{
|
||||||
{
|
public override async Task<TypeReaderResult> ReadAsync(ICommandContext context, string input)
|
||||||
return TypeReaderResult.FromSuccess(Color.ParseHex(input));
|
{
|
||||||
}
|
await Task.Yield();
|
||||||
catch
|
|
||||||
{
|
input = input.Replace("#", "", StringComparison.InvariantCulture);
|
||||||
return TypeReaderResult.FromError(CommandError.ParseFailed, "Parameter is not a valid color hex.");
|
try
|
||||||
}
|
{
|
||||||
|
return TypeReaderResult.FromSuccess(Color.ParseHex(input));
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return TypeReaderResult.FromError(CommandError.ParseFailed, "Parameter is not a valid color hex.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,107 +1,105 @@
|
|||||||
using Discord.Commands;
|
using Discord.Commands;
|
||||||
using System;
|
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using NadekoBot.Db;
|
using NadekoBot.Db;
|
||||||
using NadekoBot.Modules.Gambling.Services;
|
using NadekoBot.Modules.Gambling.Services;
|
||||||
using NadekoBot.Services;
|
using NadekoBot.Services;
|
||||||
|
|
||||||
namespace NadekoBot.Common.TypeReaders
|
namespace NadekoBot.Common.TypeReaders;
|
||||||
|
|
||||||
|
public sealed class ShmartNumberTypeReader : NadekoTypeReader<ShmartNumber>
|
||||||
{
|
{
|
||||||
public sealed class ShmartNumberTypeReader : NadekoTypeReader<ShmartNumber>
|
private readonly DbService _db;
|
||||||
|
private readonly GamblingConfigService _gambling;
|
||||||
|
|
||||||
|
public ShmartNumberTypeReader(DbService db, GamblingConfigService gambling)
|
||||||
{
|
{
|
||||||
private readonly DbService _db;
|
_db = db;
|
||||||
private readonly GamblingConfigService _gambling;
|
_gambling = gambling;
|
||||||
|
}
|
||||||
|
|
||||||
public ShmartNumberTypeReader(DbService db, GamblingConfigService gambling)
|
public override async Task<TypeReaderResult> ReadAsync(ICommandContext context, string input)
|
||||||
|
{
|
||||||
|
await Task.Yield();
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(input))
|
||||||
|
return TypeReaderResult.FromError(CommandError.ParseFailed, "Input is empty.");
|
||||||
|
|
||||||
|
var i = input.Trim().ToUpperInvariant();
|
||||||
|
|
||||||
|
i = i.Replace("K", "000");
|
||||||
|
|
||||||
|
//can't add m because it will conflict with max atm
|
||||||
|
|
||||||
|
if (TryHandlePercentage(context, i, out var num))
|
||||||
|
return TypeReaderResult.FromSuccess(new ShmartNumber(num, i));
|
||||||
|
try
|
||||||
{
|
{
|
||||||
_db = db;
|
var expr = new NCalc.Expression(i, NCalc.EvaluateOptions.IgnoreCase);
|
||||||
_gambling = gambling;
|
expr.EvaluateParameter += (str, ev) => EvaluateParam(str, ev, context);
|
||||||
|
var lon = (long)(decimal.Parse(expr.Evaluate().ToString()));
|
||||||
|
return TypeReaderResult.FromSuccess(new ShmartNumber(lon, input));
|
||||||
}
|
}
|
||||||
|
catch (Exception)
|
||||||
public override async Task<TypeReaderResult> ReadAsync(ICommandContext context, string input)
|
|
||||||
{
|
{
|
||||||
await Task.Yield();
|
return TypeReaderResult.FromError(CommandError.ParseFailed, $"Invalid input: {input}");
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(input))
|
|
||||||
return TypeReaderResult.FromError(CommandError.ParseFailed, "Input is empty.");
|
|
||||||
|
|
||||||
var i = input.Trim().ToUpperInvariant();
|
|
||||||
|
|
||||||
i = i.Replace("K", "000");
|
|
||||||
|
|
||||||
//can't add m because it will conflict with max atm
|
|
||||||
|
|
||||||
if (TryHandlePercentage(context, i, out var num))
|
|
||||||
return TypeReaderResult.FromSuccess(new ShmartNumber(num, i));
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var expr = new NCalc.Expression(i, NCalc.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));
|
|
||||||
}
|
|
||||||
catch (Exception)
|
|
||||||
{
|
|
||||||
return TypeReaderResult.FromError(CommandError.ParseFailed, $"Invalid input: {input}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void EvaluateParam(string name, NCalc.ParameterArgs args, ICommandContext ctx)
|
|
||||||
{
|
|
||||||
switch (name.ToUpperInvariant())
|
|
||||||
{
|
|
||||||
case "PI":
|
|
||||||
args.Result = Math.PI;
|
|
||||||
break;
|
|
||||||
case "E":
|
|
||||||
args.Result = Math.E;
|
|
||||||
break;
|
|
||||||
case "ALL":
|
|
||||||
case "ALLIN":
|
|
||||||
args.Result = Cur(ctx);
|
|
||||||
break;
|
|
||||||
case "HALF":
|
|
||||||
args.Result = Cur(ctx) / 2;
|
|
||||||
break;
|
|
||||||
case "MAX":
|
|
||||||
args.Result = Max(ctx);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static readonly Regex percentRegex = new Regex(@"^((?<num>100|\d{1,2})%)$", RegexOptions.Compiled);
|
|
||||||
|
|
||||||
private long Cur(ICommandContext ctx)
|
|
||||||
{
|
|
||||||
using var uow = _db.GetDbContext();
|
|
||||||
return uow.DiscordUser.GetUserCurrency(ctx.User.Id);
|
|
||||||
}
|
|
||||||
|
|
||||||
private long Max(ICommandContext ctx)
|
|
||||||
{
|
|
||||||
var settings = _gambling.Data;
|
|
||||||
var max = settings.MaxBet;
|
|
||||||
return max == 0
|
|
||||||
? Cur(ctx)
|
|
||||||
: max;
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool TryHandlePercentage(ICommandContext ctx, string input, out long num)
|
|
||||||
{
|
|
||||||
num = 0;
|
|
||||||
var m = percentRegex.Match(input);
|
|
||||||
if (m.Captures.Count != 0)
|
|
||||||
{
|
|
||||||
if (!long.TryParse(m.Groups["num"].ToString(), out var percent))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
num = (long)(Cur(ctx) * (percent / 100.0f));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
private void EvaluateParam(string name, NCalc.ParameterArgs args, ICommandContext ctx)
|
||||||
|
{
|
||||||
|
switch (name.ToUpperInvariant())
|
||||||
|
{
|
||||||
|
case "PI":
|
||||||
|
args.Result = Math.PI;
|
||||||
|
break;
|
||||||
|
case "E":
|
||||||
|
args.Result = Math.E;
|
||||||
|
break;
|
||||||
|
case "ALL":
|
||||||
|
case "ALLIN":
|
||||||
|
args.Result = Cur(ctx);
|
||||||
|
break;
|
||||||
|
case "HALF":
|
||||||
|
args.Result = Cur(ctx) / 2;
|
||||||
|
break;
|
||||||
|
case "MAX":
|
||||||
|
args.Result = Max(ctx);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static readonly Regex percentRegex = new Regex(@"^((?<num>100|\d{1,2})%)$", RegexOptions.Compiled);
|
||||||
|
|
||||||
|
private long Cur(ICommandContext ctx)
|
||||||
|
{
|
||||||
|
using var uow = _db.GetDbContext();
|
||||||
|
return uow.DiscordUser.GetUserCurrency(ctx.User.Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
private long Max(ICommandContext ctx)
|
||||||
|
{
|
||||||
|
var settings = _gambling.Data;
|
||||||
|
var max = settings.MaxBet;
|
||||||
|
return max == 0
|
||||||
|
? Cur(ctx)
|
||||||
|
: max;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TryHandlePercentage(ICommandContext ctx, string input, out long num)
|
||||||
|
{
|
||||||
|
num = 0;
|
||||||
|
var m = percentRegex.Match(input);
|
||||||
|
if (m.Captures.Count != 0)
|
||||||
|
{
|
||||||
|
if (!long.TryParse(m.Groups["num"].ToString(), out var percent))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
num = (long)(Cur(ctx) * (percent / 100.0f));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
@@ -1,25 +1,23 @@
|
|||||||
using Discord.Commands;
|
using Discord.Commands;
|
||||||
using NadekoBot.Common.TypeReaders.Models;
|
using NadekoBot.Common.TypeReaders.Models;
|
||||||
using System;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace NadekoBot.Common.TypeReaders
|
namespace NadekoBot.Common.TypeReaders;
|
||||||
|
|
||||||
|
public sealed class StoopidTimeTypeReader : NadekoTypeReader<StoopidTime>
|
||||||
{
|
{
|
||||||
public sealed class StoopidTimeTypeReader : NadekoTypeReader<StoopidTime>
|
public override Task<TypeReaderResult> ReadAsync(ICommandContext context, string input)
|
||||||
{
|
{
|
||||||
public override Task<TypeReaderResult> ReadAsync(ICommandContext context, string input)
|
if (string.IsNullOrWhiteSpace(input))
|
||||||
|
return Task.FromResult(TypeReaderResult.FromError(CommandError.Unsuccessful, "Input is empty."));
|
||||||
|
try
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(input))
|
var time = StoopidTime.FromInput(input);
|
||||||
return Task.FromResult(TypeReaderResult.FromError(CommandError.Unsuccessful, "Input is empty."));
|
return Task.FromResult(TypeReaderResult.FromSuccess(time));
|
||||||
try
|
}
|
||||||
{
|
catch (Exception ex)
|
||||||
var time = StoopidTime.FromInput(input);
|
{
|
||||||
return Task.FromResult(TypeReaderResult.FromSuccess(time));
|
return Task.FromResult(TypeReaderResult.FromError(CommandError.Exception, ex.Message));
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
return Task.FromResult(TypeReaderResult.FromError(CommandError.Exception, ex.Message));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,14 +1,11 @@
|
|||||||
using System;
|
namespace NadekoBot.Common.Yml;
|
||||||
|
|
||||||
namespace NadekoBot.Common.Yml
|
public class CommentAttribute : Attribute
|
||||||
{
|
{
|
||||||
public class CommentAttribute : Attribute
|
public string Comment { get; }
|
||||||
{
|
|
||||||
public string Comment { get; }
|
|
||||||
|
|
||||||
public CommentAttribute(string comment)
|
public CommentAttribute(string comment)
|
||||||
{
|
{
|
||||||
Comment = comment;
|
Comment = comment;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,73 +1,69 @@
|
|||||||
using System;
|
using YamlDotNet.Core;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using YamlDotNet.Core;
|
|
||||||
using YamlDotNet.Serialization;
|
using YamlDotNet.Serialization;
|
||||||
using YamlDotNet.Serialization.TypeInspectors;
|
using YamlDotNet.Serialization.TypeInspectors;
|
||||||
|
|
||||||
namespace NadekoBot.Common.Yml
|
namespace NadekoBot.Common.Yml;
|
||||||
|
|
||||||
|
public class CommentGatheringTypeInspector : TypeInspectorSkeleton
|
||||||
{
|
{
|
||||||
public class CommentGatheringTypeInspector : TypeInspectorSkeleton
|
private readonly ITypeInspector innerTypeDescriptor;
|
||||||
|
|
||||||
|
public CommentGatheringTypeInspector(ITypeInspector innerTypeDescriptor)
|
||||||
{
|
{
|
||||||
private readonly ITypeInspector innerTypeDescriptor;
|
this.innerTypeDescriptor = innerTypeDescriptor ?? throw new ArgumentNullException("innerTypeDescriptor");
|
||||||
|
}
|
||||||
|
|
||||||
public CommentGatheringTypeInspector(ITypeInspector innerTypeDescriptor)
|
public override IEnumerable<IPropertyDescriptor> GetProperties(Type type, object container)
|
||||||
|
{
|
||||||
|
return innerTypeDescriptor
|
||||||
|
.GetProperties(type, container)
|
||||||
|
.Select(d => new CommentsPropertyDescriptor(d));
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class CommentsPropertyDescriptor : IPropertyDescriptor
|
||||||
|
{
|
||||||
|
private readonly IPropertyDescriptor baseDescriptor;
|
||||||
|
|
||||||
|
public CommentsPropertyDescriptor(IPropertyDescriptor baseDescriptor)
|
||||||
{
|
{
|
||||||
this.innerTypeDescriptor = innerTypeDescriptor ?? throw new ArgumentNullException("innerTypeDescriptor");
|
this.baseDescriptor = baseDescriptor;
|
||||||
|
Name = baseDescriptor.Name;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override IEnumerable<IPropertyDescriptor> GetProperties(Type type, object container)
|
public string Name { get; set; }
|
||||||
{
|
|
||||||
return innerTypeDescriptor
|
public Type Type { get { return baseDescriptor.Type; } }
|
||||||
.GetProperties(type, container)
|
|
||||||
.Select(d => new CommentsPropertyDescriptor(d));
|
public Type TypeOverride {
|
||||||
|
get { return baseDescriptor.TypeOverride; }
|
||||||
|
set { baseDescriptor.TypeOverride = value; }
|
||||||
}
|
}
|
||||||
|
|
||||||
private sealed class CommentsPropertyDescriptor : IPropertyDescriptor
|
public int Order { get; set; }
|
||||||
|
|
||||||
|
public ScalarStyle ScalarStyle {
|
||||||
|
get { return baseDescriptor.ScalarStyle; }
|
||||||
|
set { baseDescriptor.ScalarStyle = value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool CanWrite { get { return baseDescriptor.CanWrite; } }
|
||||||
|
|
||||||
|
public void Write(object target, object value)
|
||||||
{
|
{
|
||||||
private readonly IPropertyDescriptor baseDescriptor;
|
baseDescriptor.Write(target, value);
|
||||||
|
}
|
||||||
|
|
||||||
public CommentsPropertyDescriptor(IPropertyDescriptor baseDescriptor)
|
public T GetCustomAttribute<T>() where T : Attribute
|
||||||
{
|
{
|
||||||
this.baseDescriptor = baseDescriptor;
|
return baseDescriptor.GetCustomAttribute<T>();
|
||||||
Name = baseDescriptor.Name;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public string Name { get; set; }
|
public IObjectDescriptor Read(object target)
|
||||||
|
{
|
||||||
public Type Type { get { return baseDescriptor.Type; } }
|
var comment = baseDescriptor.GetCustomAttribute<CommentAttribute>();
|
||||||
|
return comment != null
|
||||||
public Type TypeOverride {
|
? new CommentsObjectDescriptor(baseDescriptor.Read(target), comment.Comment)
|
||||||
get { return baseDescriptor.TypeOverride; }
|
: baseDescriptor.Read(target);
|
||||||
set { baseDescriptor.TypeOverride = value; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public int Order { get; set; }
|
|
||||||
|
|
||||||
public ScalarStyle ScalarStyle {
|
|
||||||
get { return baseDescriptor.ScalarStyle; }
|
|
||||||
set { baseDescriptor.ScalarStyle = value; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool CanWrite { get { return baseDescriptor.CanWrite; } }
|
|
||||||
|
|
||||||
public void Write(object target, object value)
|
|
||||||
{
|
|
||||||
baseDescriptor.Write(target, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public T GetCustomAttribute<T>() where T : Attribute
|
|
||||||
{
|
|
||||||
return baseDescriptor.GetCustomAttribute<T>();
|
|
||||||
}
|
|
||||||
|
|
||||||
public IObjectDescriptor Read(object target)
|
|
||||||
{
|
|
||||||
var comment = baseDescriptor.GetCustomAttribute<CommentAttribute>();
|
|
||||||
return comment != null
|
|
||||||
? new CommentsObjectDescriptor(baseDescriptor.Read(target), comment.Comment)
|
|
||||||
: baseDescriptor.Read(target);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,24 +1,22 @@
|
|||||||
using System;
|
using YamlDotNet.Core;
|
||||||
using YamlDotNet.Core;
|
|
||||||
using YamlDotNet.Serialization;
|
using YamlDotNet.Serialization;
|
||||||
|
|
||||||
namespace NadekoBot.Common.Yml
|
namespace NadekoBot.Common.Yml;
|
||||||
|
|
||||||
|
public sealed class CommentsObjectDescriptor : IObjectDescriptor
|
||||||
{
|
{
|
||||||
public sealed class CommentsObjectDescriptor : IObjectDescriptor
|
private readonly IObjectDescriptor innerDescriptor;
|
||||||
|
|
||||||
|
public CommentsObjectDescriptor(IObjectDescriptor innerDescriptor, string comment)
|
||||||
{
|
{
|
||||||
private readonly IObjectDescriptor innerDescriptor;
|
this.innerDescriptor = innerDescriptor;
|
||||||
|
this.Comment = comment;
|
||||||
public CommentsObjectDescriptor(IObjectDescriptor innerDescriptor, string comment)
|
|
||||||
{
|
|
||||||
this.innerDescriptor = innerDescriptor;
|
|
||||||
this.Comment = comment;
|
|
||||||
}
|
|
||||||
|
|
||||||
public string Comment { get; private set; }
|
|
||||||
|
|
||||||
public object Value { get { return innerDescriptor.Value; } }
|
|
||||||
public Type Type { get { return innerDescriptor.Type; } }
|
|
||||||
public Type StaticType { get { return innerDescriptor.StaticType; } }
|
|
||||||
public ScalarStyle ScalarStyle { get { return innerDescriptor.ScalarStyle; } }
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
public string Comment { get; private set; }
|
||||||
|
|
||||||
|
public object Value { get { return innerDescriptor.Value; } }
|
||||||
|
public Type Type { get { return innerDescriptor.Type; } }
|
||||||
|
public Type StaticType { get { return innerDescriptor.StaticType; } }
|
||||||
|
public ScalarStyle ScalarStyle { get { return innerDescriptor.ScalarStyle; } }
|
||||||
|
}
|
@@ -3,24 +3,23 @@ using YamlDotNet.Core.Events;
|
|||||||
using YamlDotNet.Serialization;
|
using YamlDotNet.Serialization;
|
||||||
using YamlDotNet.Serialization.ObjectGraphVisitors;
|
using YamlDotNet.Serialization.ObjectGraphVisitors;
|
||||||
|
|
||||||
namespace NadekoBot.Common.Yml
|
namespace NadekoBot.Common.Yml;
|
||||||
|
|
||||||
|
public class CommentsObjectGraphVisitor : ChainedObjectGraphVisitor
|
||||||
{
|
{
|
||||||
public class CommentsObjectGraphVisitor : ChainedObjectGraphVisitor
|
public CommentsObjectGraphVisitor(IObjectGraphVisitor<IEmitter> nextVisitor)
|
||||||
|
: base(nextVisitor)
|
||||||
{
|
{
|
||||||
public CommentsObjectGraphVisitor(IObjectGraphVisitor<IEmitter> nextVisitor)
|
|
||||||
: base(nextVisitor)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool EnterMapping(IPropertyDescriptor key, IObjectDescriptor value, IEmitter context)
|
|
||||||
{
|
|
||||||
var commentsDescriptor = value as CommentsObjectDescriptor;
|
|
||||||
if (commentsDescriptor != null && !string.IsNullOrWhiteSpace(commentsDescriptor.Comment))
|
|
||||||
{
|
|
||||||
context.Emit(new Comment(commentsDescriptor.Comment.Replace("\n", "\n# "), false));
|
|
||||||
}
|
|
||||||
|
|
||||||
return base.EnterMapping(key, value, context);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
public override bool EnterMapping(IPropertyDescriptor key, IObjectDescriptor value, IEmitter context)
|
||||||
|
{
|
||||||
|
var commentsDescriptor = value as CommentsObjectDescriptor;
|
||||||
|
if (commentsDescriptor != null && !string.IsNullOrWhiteSpace(commentsDescriptor.Comment))
|
||||||
|
{
|
||||||
|
context.Emit(new Comment(commentsDescriptor.Comment.Replace("\n", "\n# "), false));
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.EnterMapping(key, value, context);
|
||||||
|
}
|
||||||
|
}
|
@@ -2,31 +2,30 @@
|
|||||||
using YamlDotNet.Serialization;
|
using YamlDotNet.Serialization;
|
||||||
using YamlDotNet.Serialization.EventEmitters;
|
using YamlDotNet.Serialization.EventEmitters;
|
||||||
|
|
||||||
namespace NadekoBot.Common.Yml
|
namespace NadekoBot.Common.Yml;
|
||||||
|
|
||||||
|
public class MultilineScalarFlowStyleEmitter : ChainedEventEmitter
|
||||||
{
|
{
|
||||||
public class MultilineScalarFlowStyleEmitter : ChainedEventEmitter
|
public MultilineScalarFlowStyleEmitter(IEventEmitter nextEmitter)
|
||||||
|
: base(nextEmitter) { }
|
||||||
|
|
||||||
|
public override void Emit(ScalarEventInfo eventInfo, IEmitter emitter)
|
||||||
{
|
{
|
||||||
public MultilineScalarFlowStyleEmitter(IEventEmitter nextEmitter)
|
|
||||||
: base(nextEmitter) { }
|
|
||||||
|
|
||||||
public override void Emit(ScalarEventInfo eventInfo, IEmitter emitter)
|
if (typeof(string).IsAssignableFrom(eventInfo.Source.Type))
|
||||||
{
|
{
|
||||||
|
string value = eventInfo.Source.Value as string;
|
||||||
if (typeof(string).IsAssignableFrom(eventInfo.Source.Type))
|
if (!string.IsNullOrEmpty(value))
|
||||||
{
|
{
|
||||||
string value = eventInfo.Source.Value as string;
|
bool isMultiLine = value.IndexOfAny(new char[] { '\r', '\n', '\x85', '\x2028', '\x2029' }) >= 0;
|
||||||
if (!string.IsNullOrEmpty(value))
|
if (isMultiLine)
|
||||||
{
|
eventInfo = new ScalarEventInfo(eventInfo.Source)
|
||||||
bool isMultiLine = value.IndexOfAny(new char[] { '\r', '\n', '\x85', '\x2028', '\x2029' }) >= 0;
|
{
|
||||||
if (isMultiLine)
|
Style = ScalarStyle.Literal,
|
||||||
eventInfo = new ScalarEventInfo(eventInfo.Source)
|
};
|
||||||
{
|
|
||||||
Style = ScalarStyle.Literal,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
nextEmitter.Emit(eventInfo, emitter);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nextEmitter.Emit(eventInfo, emitter);
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,52 +1,50 @@
|
|||||||
using System;
|
using System.Globalization;
|
||||||
using System.Globalization;
|
|
||||||
using SixLabors.ImageSharp.PixelFormats;
|
using SixLabors.ImageSharp.PixelFormats;
|
||||||
using YamlDotNet.Core;
|
using YamlDotNet.Core;
|
||||||
using YamlDotNet.Core.Events;
|
using YamlDotNet.Core.Events;
|
||||||
using YamlDotNet.Serialization;
|
using YamlDotNet.Serialization;
|
||||||
|
|
||||||
namespace NadekoBot.Common.Yml
|
namespace NadekoBot.Common.Yml;
|
||||||
|
|
||||||
|
public class Rgba32Converter : IYamlTypeConverter
|
||||||
{
|
{
|
||||||
public class Rgba32Converter : IYamlTypeConverter
|
public bool Accepts(Type type)
|
||||||
{
|
{
|
||||||
public bool Accepts(Type type)
|
return type == typeof(Rgba32);
|
||||||
{
|
|
||||||
return type == typeof(Rgba32);
|
|
||||||
}
|
|
||||||
|
|
||||||
public object ReadYaml(IParser parser, Type type)
|
|
||||||
{
|
|
||||||
var scalar = parser.Consume<Scalar>();
|
|
||||||
var result = Rgba32.ParseHex(scalar.Value);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void WriteYaml(IEmitter emitter, object value, Type type)
|
|
||||||
{
|
|
||||||
var color = (Rgba32)value;
|
|
||||||
var val = (uint) (color.B << 0 | color.G << 8 | color.R << 16);
|
|
||||||
emitter.Emit(new Scalar(val.ToString("X6").ToLower()));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class CultureInfoConverter : IYamlTypeConverter
|
public object ReadYaml(IParser parser, Type type)
|
||||||
{
|
{
|
||||||
public bool Accepts(Type type)
|
var scalar = parser.Consume<Scalar>();
|
||||||
{
|
var result = Rgba32.ParseHex(scalar.Value);
|
||||||
return type == typeof(CultureInfo);
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public object ReadYaml(IParser parser, Type type)
|
public void WriteYaml(IEmitter emitter, object value, Type type)
|
||||||
{
|
{
|
||||||
var scalar = parser.Consume<Scalar>();
|
var color = (Rgba32)value;
|
||||||
var result = new CultureInfo(scalar.Value);
|
var val = (uint) (color.B << 0 | color.G << 8 | color.R << 16);
|
||||||
return result;
|
emitter.Emit(new Scalar(val.ToString("X6").ToLower()));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class CultureInfoConverter : IYamlTypeConverter
|
||||||
|
{
|
||||||
|
public bool Accepts(Type type)
|
||||||
|
{
|
||||||
|
return type == typeof(CultureInfo);
|
||||||
|
}
|
||||||
|
|
||||||
public void WriteYaml(IEmitter emitter, object value, Type type)
|
public object ReadYaml(IParser parser, Type type)
|
||||||
{
|
{
|
||||||
var ci = (CultureInfo)value;
|
var scalar = parser.Consume<Scalar>();
|
||||||
emitter.Emit(new Scalar(ci.Name));
|
var result = new CultureInfo(scalar.Value);
|
||||||
}
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void WriteYaml(IEmitter emitter, object value, Type type)
|
||||||
|
{
|
||||||
|
var ci = (CultureInfo)value;
|
||||||
|
emitter.Emit(new Scalar(ci.Name));
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,28 +1,26 @@
|
|||||||
using System;
|
using YamlDotNet.Core;
|
||||||
using YamlDotNet.Core;
|
|
||||||
using YamlDotNet.Core.Events;
|
using YamlDotNet.Core.Events;
|
||||||
using YamlDotNet.Serialization;
|
using YamlDotNet.Serialization;
|
||||||
|
|
||||||
namespace NadekoBot.Common.Yml
|
namespace NadekoBot.Common.Yml;
|
||||||
|
|
||||||
|
public class UriConverter : IYamlTypeConverter
|
||||||
{
|
{
|
||||||
public class UriConverter : IYamlTypeConverter
|
public bool Accepts(Type type)
|
||||||
{
|
{
|
||||||
public bool Accepts(Type type)
|
return type == typeof(Uri);
|
||||||
{
|
}
|
||||||
return type == typeof(Uri);
|
|
||||||
}
|
|
||||||
|
|
||||||
public object ReadYaml(IParser parser, Type type)
|
public object ReadYaml(IParser parser, Type type)
|
||||||
{
|
{
|
||||||
var scalar = parser.Consume<Scalar>();
|
var scalar = parser.Consume<Scalar>();
|
||||||
var result = new Uri(scalar.Value);
|
var result = new Uri(scalar.Value);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void WriteYaml(IEmitter emitter, object value, Type type)
|
public void WriteYaml(IEmitter emitter, object value, Type type)
|
||||||
{
|
{
|
||||||
var uri = (Uri)value;
|
var uri = (Uri)value;
|
||||||
emitter.Emit(new Scalar(uri.ToString()));
|
emitter.Emit(new Scalar(uri.ToString()));
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,26 +1,25 @@
|
|||||||
using YamlDotNet.Serialization;
|
using YamlDotNet.Serialization;
|
||||||
|
|
||||||
namespace NadekoBot.Common.Yml
|
namespace NadekoBot.Common.Yml;
|
||||||
{
|
|
||||||
public class Yaml
|
|
||||||
{
|
|
||||||
public static ISerializer Serializer => new SerializerBuilder()
|
|
||||||
.WithTypeInspector(inner => new CommentGatheringTypeInspector(inner))
|
|
||||||
.WithEmissionPhaseObjectGraphVisitor(args => new CommentsObjectGraphVisitor(args.InnerVisitor))
|
|
||||||
.WithEventEmitter(args => new MultilineScalarFlowStyleEmitter(args))
|
|
||||||
.WithNamingConvention(YamlDotNet.Serialization.NamingConventions.CamelCaseNamingConvention.Instance)
|
|
||||||
.WithIndentedSequences()
|
|
||||||
.WithTypeConverter(new Rgba32Converter())
|
|
||||||
.WithTypeConverter(new CultureInfoConverter())
|
|
||||||
.WithTypeConverter(new UriConverter())
|
|
||||||
.Build();
|
|
||||||
|
|
||||||
public static IDeserializer Deserializer => new DeserializerBuilder()
|
public class Yaml
|
||||||
.WithNamingConvention(YamlDotNet.Serialization.NamingConventions.CamelCaseNamingConvention.Instance)
|
{
|
||||||
.WithTypeConverter(new Rgba32Converter())
|
public static ISerializer Serializer => new SerializerBuilder()
|
||||||
.WithTypeConverter(new CultureInfoConverter())
|
.WithTypeInspector(inner => new CommentGatheringTypeInspector(inner))
|
||||||
.WithTypeConverter(new UriConverter())
|
.WithEmissionPhaseObjectGraphVisitor(args => new CommentsObjectGraphVisitor(args.InnerVisitor))
|
||||||
.IgnoreUnmatchedProperties()
|
.WithEventEmitter(args => new MultilineScalarFlowStyleEmitter(args))
|
||||||
.Build();
|
.WithNamingConvention(YamlDotNet.Serialization.NamingConventions.CamelCaseNamingConvention.Instance)
|
||||||
}
|
.WithIndentedSequences()
|
||||||
|
.WithTypeConverter(new Rgba32Converter())
|
||||||
|
.WithTypeConverter(new CultureInfoConverter())
|
||||||
|
.WithTypeConverter(new UriConverter())
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
public static IDeserializer Deserializer => new DeserializerBuilder()
|
||||||
|
.WithNamingConvention(YamlDotNet.Serialization.NamingConventions.CamelCaseNamingConvention.Instance)
|
||||||
|
.WithTypeConverter(new Rgba32Converter())
|
||||||
|
.WithTypeConverter(new CultureInfoConverter())
|
||||||
|
.WithTypeConverter(new UriConverter())
|
||||||
|
.IgnoreUnmatchedProperties()
|
||||||
|
.Build();
|
||||||
}
|
}
|
@@ -1,58 +1,57 @@
|
|||||||
namespace NadekoBot.Common.Yml
|
namespace NadekoBot.Common.Yml;
|
||||||
|
|
||||||
|
public class YamlHelper
|
||||||
{
|
{
|
||||||
public class YamlHelper
|
// https://github.com/aaubry/YamlDotNet/blob/0f4cc205e8b2dd8ef6589d96de32bf608a687c6f/YamlDotNet/Core/Scanner.cs#L1687
|
||||||
|
/// <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
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="point">Unicode code point</param>
|
||||||
|
/// <returns>Actual character</returns>
|
||||||
|
public static string UnescapeUnicodeCodePoint(string point)
|
||||||
{
|
{
|
||||||
// https://github.com/aaubry/YamlDotNet/blob/0f4cc205e8b2dd8ef6589d96de32bf608a687c6f/YamlDotNet/Core/Scanner.cs#L1687
|
var character = 0;
|
||||||
/// <summary>
|
|
||||||
/// This is modified code from yamldotnet's repo which handles parsing unicode code points
|
// Scan the character value.
|
||||||
/// it is needed as yamldotnet doesn't support unescaped unicode characters
|
|
||||||
/// </summary>
|
foreach(var c in point)
|
||||||
/// <param name="point">Unicode code point</param>
|
|
||||||
/// <returns>Actual character</returns>
|
|
||||||
public static string UnescapeUnicodeCodePoint(string point)
|
|
||||||
{
|
{
|
||||||
var character = 0;
|
if (!IsHex(c))
|
||||||
|
|
||||||
// Scan the character value.
|
|
||||||
|
|
||||||
foreach(var c in point)
|
|
||||||
{
|
|
||||||
if (!IsHex(c))
|
|
||||||
{
|
|
||||||
return point;
|
|
||||||
}
|
|
||||||
character = (character << 4) + AsHex(c);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check the value and write the character.
|
|
||||||
|
|
||||||
if (character >= 0xD800 && character <= 0xDFFF || character > 0x10FFFF)
|
|
||||||
{
|
{
|
||||||
return point;
|
return point;
|
||||||
}
|
}
|
||||||
|
character = (character << 4) + AsHex(c);
|
||||||
|
}
|
||||||
|
|
||||||
return char.ConvertFromUtf32(character);
|
// Check the value and write the character.
|
||||||
}
|
|
||||||
|
if (character >= 0xD800 && character <= 0xDFFF || character > 0x10FFFF)
|
||||||
public static bool IsHex(char c)
|
|
||||||
{
|
{
|
||||||
return
|
return point;
|
||||||
(c >= '0' && c <= '9') ||
|
|
||||||
(c >= 'A' && c <= 'F') ||
|
|
||||||
(c >= 'a' && c <= 'f');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return char.ConvertFromUtf32(character);
|
||||||
|
}
|
||||||
|
|
||||||
public static int AsHex(char c)
|
public static bool IsHex(char c)
|
||||||
|
{
|
||||||
|
return
|
||||||
|
(c >= '0' && c <= '9') ||
|
||||||
|
(c >= 'A' && c <= 'F') ||
|
||||||
|
(c >= 'a' && c <= 'f');
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int AsHex(char c)
|
||||||
|
{
|
||||||
|
if (c <= '9')
|
||||||
{
|
{
|
||||||
if (c <= '9')
|
return c - '0';
|
||||||
{
|
|
||||||
return c - '0';
|
|
||||||
}
|
|
||||||
if (c <= 'F')
|
|
||||||
{
|
|
||||||
return c - 'A' + 10;
|
|
||||||
}
|
|
||||||
return c - 'a' + 10;
|
|
||||||
}
|
}
|
||||||
|
if (c <= 'F')
|
||||||
|
{
|
||||||
|
return c - 'A' + 10;
|
||||||
|
}
|
||||||
|
return c - 'a' + 10;
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,48 +1,45 @@
|
|||||||
using System.Linq;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using NadekoBot.Db.Models;
|
using NadekoBot.Db.Models;
|
||||||
|
|
||||||
namespace NadekoBot.Db
|
namespace NadekoBot.Db;
|
||||||
|
|
||||||
|
public static class ClubExtensions
|
||||||
{
|
{
|
||||||
public static class ClubExtensions
|
private static IQueryable<ClubInfo> Include(this DbSet<ClubInfo> clubs)
|
||||||
{
|
=> clubs.Include(x => x.Owner)
|
||||||
private static IQueryable<ClubInfo> Include(this DbSet<ClubInfo> clubs)
|
.Include(x => x.Applicants)
|
||||||
=> clubs.Include(x => x.Owner)
|
.ThenInclude(x => x.User)
|
||||||
.Include(x => x.Applicants)
|
.Include(x => x.Bans)
|
||||||
.ThenInclude(x => x.User)
|
.ThenInclude(x => x.User)
|
||||||
.Include(x => x.Bans)
|
.Include(x => x.Users)
|
||||||
.ThenInclude(x => x.User)
|
.AsQueryable();
|
||||||
.Include(x => x.Users)
|
public static ClubInfo GetByOwner(this DbSet<ClubInfo> clubs, ulong userId)
|
||||||
.AsQueryable();
|
=> Include(clubs).FirstOrDefault(c => c.Owner.UserId == userId);
|
||||||
public static ClubInfo GetByOwner(this DbSet<ClubInfo> clubs, ulong userId)
|
|
||||||
=> Include(clubs).FirstOrDefault(c => c.Owner.UserId == userId);
|
|
||||||
|
|
||||||
public static ClubInfo GetByOwnerOrAdmin(this DbSet<ClubInfo> clubs, ulong userId)
|
public static ClubInfo GetByOwnerOrAdmin(this DbSet<ClubInfo> clubs, ulong userId)
|
||||||
=> Include(clubs).FirstOrDefault(c => c.Owner.UserId == userId
|
=> Include(clubs).FirstOrDefault(c => c.Owner.UserId == userId
|
||||||
|| c.Users.Any(u => u.UserId == userId && u.IsClubAdmin));
|
|| c.Users.Any(u => u.UserId == userId && u.IsClubAdmin));
|
||||||
|
|
||||||
public static ClubInfo GetByMember(this DbSet<ClubInfo> clubs, ulong userId)
|
public static ClubInfo GetByMember(this DbSet<ClubInfo> clubs, ulong userId)
|
||||||
=> Include(clubs).FirstOrDefault(c => c.Users.Any(u => u.UserId == userId));
|
=> Include(clubs).FirstOrDefault(c => c.Users.Any(u => u.UserId == userId));
|
||||||
|
|
||||||
public static ClubInfo GetByName(this DbSet<ClubInfo> clubs, string name, int discrim)
|
public static ClubInfo GetByName(this DbSet<ClubInfo> clubs, string name, int discrim)
|
||||||
=> Include(clubs).FirstOrDefault(c => c.Name.ToUpper() == name.ToUpper() && c.Discrim == discrim);
|
=> Include(clubs).FirstOrDefault(c => c.Name.ToUpper() == name.ToUpper() && c.Discrim == discrim);
|
||||||
|
|
||||||
public static int GetNextDiscrim(this DbSet<ClubInfo> clubs, string name)
|
public static int GetNextDiscrim(this DbSet<ClubInfo> clubs, string name)
|
||||||
=> Include(clubs)
|
=> Include(clubs)
|
||||||
.Where(x => x.Name.ToUpper() == name.ToUpper())
|
.Where(x => x.Name.ToUpper() == name.ToUpper())
|
||||||
.Select(x => x.Discrim)
|
.Select(x => x.Discrim)
|
||||||
.DefaultIfEmpty()
|
.DefaultIfEmpty()
|
||||||
.Max() + 1;
|
.Max() + 1;
|
||||||
|
|
||||||
public static List<ClubInfo> GetClubLeaderboardPage(this DbSet<ClubInfo> clubs, int page)
|
public static List<ClubInfo> GetClubLeaderboardPage(this DbSet<ClubInfo> clubs, int page)
|
||||||
{
|
{
|
||||||
return clubs
|
return clubs
|
||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
.OrderByDescending(x => x.Xp)
|
.OrderByDescending(x => x.Xp)
|
||||||
.Skip(page * 9)
|
.Skip(page * 9)
|
||||||
.Take(9)
|
.Take(9)
|
||||||
.ToList();
|
.ToList();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,21 +1,18 @@
|
|||||||
using System.Collections.Generic;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using System.Linq;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using NadekoBot.Services.Database.Models;
|
using NadekoBot.Services.Database.Models;
|
||||||
|
|
||||||
namespace NadekoBot.Db
|
namespace NadekoBot.Db;
|
||||||
|
|
||||||
|
public static class CurrencyTransactionExtensions
|
||||||
{
|
{
|
||||||
public static class CurrencyTransactionExtensions
|
public static List<CurrencyTransaction> GetPageFor(this DbSet<CurrencyTransaction> set, ulong userId, int page)
|
||||||
{
|
{
|
||||||
public static List<CurrencyTransaction> GetPageFor(this DbSet<CurrencyTransaction> set, ulong userId, int page)
|
return set.AsQueryable()
|
||||||
{
|
.AsNoTracking()
|
||||||
return set.AsQueryable()
|
.Where(x => x.UserId == userId)
|
||||||
.AsNoTracking()
|
.OrderByDescending(x => x.DateAdded)
|
||||||
.Where(x => x.UserId == userId)
|
.Skip(15 * page)
|
||||||
.OrderByDescending(x => x.DateAdded)
|
.Take(15)
|
||||||
.Skip(15 * page)
|
.ToList();
|
||||||
.Take(15)
|
|
||||||
.ToList();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,30 +1,27 @@
|
|||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using LinqToDB;
|
using LinqToDB;
|
||||||
using NadekoBot.Services.Database.Models;
|
using NadekoBot.Services.Database.Models;
|
||||||
|
|
||||||
namespace NadekoBot.Db
|
namespace NadekoBot.Db;
|
||||||
|
|
||||||
|
public static class CustomReactionsExtensions
|
||||||
{
|
{
|
||||||
public static class CustomReactionsExtensions
|
public static int ClearFromGuild(this DbSet<CustomReaction> crs, ulong guildId)
|
||||||
{
|
{
|
||||||
public static int ClearFromGuild(this DbSet<CustomReaction> crs, ulong guildId)
|
return crs.Delete(x => x.GuildId == guildId);
|
||||||
{
|
|
||||||
return crs.Delete(x => x.GuildId == guildId);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static IEnumerable<CustomReaction> ForId(this DbSet<CustomReaction> crs, ulong id)
|
|
||||||
{
|
|
||||||
return crs
|
|
||||||
.AsNoTracking()
|
|
||||||
.AsQueryable()
|
|
||||||
.Where(x => x.GuildId == id)
|
|
||||||
.ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static CustomReaction GetByGuildIdAndInput(this DbSet<CustomReaction> crs, ulong? guildId, string input)
|
|
||||||
{
|
|
||||||
return crs.FirstOrDefault(x => x.GuildId == guildId && x.Trigger.ToUpper() == input);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
public static IEnumerable<CustomReaction> ForId(this DbSet<CustomReaction> crs, ulong id)
|
||||||
|
{
|
||||||
|
return crs
|
||||||
|
.AsNoTracking()
|
||||||
|
.AsQueryable()
|
||||||
|
.Where(x => x.GuildId == id)
|
||||||
|
.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static CustomReaction GetByGuildIdAndInput(this DbSet<CustomReaction> crs, ulong? guildId, string input)
|
||||||
|
{
|
||||||
|
return crs.FirstOrDefault(x => x.GuildId == guildId && x.Trigger.ToUpper() == input);
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user