- Started cleanup of command handler

- Removed IUnloadableService
- Started removing INService (removed it from services which implement behavior interfaces) - wip
- Added scrutor for better service registration - wip
This commit is contained in:
Kwoth
2021-06-28 23:20:02 +02:00
parent 1e90d7f7bb
commit 3c82c1f919
30 changed files with 217 additions and 360 deletions

View File

@@ -28,32 +28,29 @@ using Serilog;
namespace NadekoBot namespace NadekoBot
{ {
// todo remove all migration code from public sealed class Bot
// todo read prev commit
public class Bot
{ {
private readonly IBotCredentials _creds; private readonly IBotCredentials _creds;
public DiscordSocketClient Client { get; } private readonly CommandService _commandService;
public CommandService CommandService { get; }
private readonly DbService _db; private readonly DbService _db;
private readonly BotCredsProvider _credsProvider;
public event Func<GuildConfig, Task> JoinedGuild = delegate { return Task.CompletedTask; };
public DiscordSocketClient Client { get; }
public ImmutableArray<GuildConfig> AllGuildConfigs { get; private set; } public ImmutableArray<GuildConfig> AllGuildConfigs { get; private set; }
// todo change configs to records
// todo remove colors from here // todo remove colors from here
public static Color OkColor { get; set; } public static Color OkColor { get; set; }
public static Color ErrorColor { get; set; } public static Color ErrorColor { get; set; }
public static Color PendingColor { get; set; } public static Color PendingColor { get; set; }
// todo remove ready prop private IServiceProvider Services { get; set; }
public TaskCompletionSource<bool> Ready { get; private set; } = new TaskCompletionSource<bool>();
public IServiceProvider Services { get; private set; }
public string Mention { get; set; } public string Mention { get; private set; }
public bool IsReady { get; private set; }
public event Func<GuildConfig, Task> JoinedGuild = delegate { return Task.CompletedTask; };
private readonly BotCredsProvider _credsProvider;
public Bot(int shardId, int? totalShards) public Bot(int shardId, int? totalShards)
{ {
if (shardId < 0) if (shardId < 0)
@@ -80,7 +77,7 @@ namespace NadekoBot
ExclusiveBulkDelete = true, ExclusiveBulkDelete = true,
}); });
CommandService = new CommandService(new CommandServiceConfig() _commandService = new CommandService(new CommandServiceConfig()
{ {
CaseSensitiveCommands = false, CaseSensitiveCommands = false,
DefaultRunMode = RunMode.Sync, DefaultRunMode = RunMode.Sync,
@@ -96,12 +93,6 @@ namespace NadekoBot
return Client.Guilds.Select(x => x.Id).ToList(); return Client.Guilds.Select(x => x.Id).ToList();
} }
public IEnumerable<GuildConfig> GetCurrentGuildConfigs()
{
using var uow = _db.GetDbContext();
return uow.GuildConfigs.GetAllGuildConfigs(GetCurrentGuildIds()).ToImmutableArray();
}
private void AddServices() private void AddServices()
{ {
var startingGuildIdList = GetCurrentGuildIds(); var startingGuildIdList = GetCurrentGuildIds();
@@ -119,7 +110,7 @@ namespace NadekoBot
.AddSingleton(_db) // database .AddSingleton(_db) // database
.AddRedis(_creds.RedisOptions) // redis .AddRedis(_creds.RedisOptions) // redis
.AddSingleton(Client) // discord socket client .AddSingleton(Client) // discord socket client
.AddSingleton(CommandService) .AddSingleton(_commandService)
.AddSingleton(this) // pepega .AddSingleton(this) // pepega
.AddSingleton<IDataCache, RedisCache>() .AddSingleton<IDataCache, RedisCache>()
.AddSingleton<ISeria, JsonSeria>() .AddSingleton<ISeria, JsonSeria>()
@@ -130,6 +121,7 @@ namespace NadekoBot
.AddConfigMigrators() // todo remove config migrators .AddConfigMigrators() // todo remove config migrators
.AddMemoryCache() .AddMemoryCache()
.AddSingleton<IShopService, ShopService>() .AddSingleton<IShopService, ShopService>()
.AddSingleton<IBehaviourExecutor, BehaviorExecutor>()
// music // music
.AddMusic() .AddMusic()
; ;
@@ -152,9 +144,27 @@ namespace NadekoBot
.AddSingleton<IReadyExecutor>(x => (IReadyExecutor)x.GetRequiredService<ICoordinator>()); .AddSingleton<IReadyExecutor>(x => (IReadyExecutor)x.GetRequiredService<ICoordinator>());
} }
svcs.AddSingleton<IReadyExecutor>(x => x.GetService<SelfService>()); svcs.Scan(scan => scan
svcs.AddSingleton<IReadyExecutor>(x => x.GetService<CustomReactionsService>()); .FromAssemblyOf<IReadyExecutor>()
svcs.AddSingleton<IReadyExecutor>(x => x.GetService<RepeaterService>()); .AddClasses(classes => classes.AssignableTo<IReadyExecutor>())
.AsSelf()
.AsImplementedInterfaces()
.WithSingletonLifetime()
// behaviours
.AddClasses(classes => classes.AssignableToAny(
typeof(IEarlyBehavior),
typeof(ILateBlocker),
typeof(IInputTransformer),
typeof(ILateExecutor)))
.AsSelf()
.AsImplementedInterfaces()
.WithSingletonLifetime()
);
// svcs.AddSingleton<IReadyExecutor>(x => x.GetService<SelfService>());
// svcs.AddSingleton<IReadyExecutor>(x => x.GetService<CustomReactionsService>());
// svcs.AddSingleton<IReadyExecutor>(x => x.GetService<RepeaterService>());
//initialize Services //initialize Services
Services = svcs.BuildServiceProvider(); Services = svcs.BuildServiceProvider();
@@ -164,9 +174,7 @@ namespace NadekoBot
{ {
ApplyConfigMigrations(); ApplyConfigMigrations();
} }
//what the fluff
commandHandler.AddServices(svcs);
_ = LoadTypeReaders(typeof(Bot).Assembly); _ = LoadTypeReaders(typeof(Bot).Assembly);
sw.Stop(); sw.Stop();
@@ -204,10 +212,10 @@ namespace NadekoBot
var toReturn = new List<object>(); var toReturn = new List<object>();
foreach (var ft in filteredTypes) foreach (var ft in filteredTypes)
{ {
var x = (TypeReader)Activator.CreateInstance(ft, Client, CommandService); var x = (TypeReader)Activator.CreateInstance(ft, Client, _commandService);
var baseType = ft.BaseType; var baseType = ft.BaseType;
var typeArgs = baseType.GetGenericArguments(); var typeArgs = baseType.GetGenericArguments();
CommandService.AddTypeReader(typeArgs[0], x); _commandService.AddTypeReader(typeArgs[0], x);
toReturn.Add(x); toReturn.Add(x);
} }
@@ -234,10 +242,6 @@ namespace NadekoBot
{ {
// ignored // ignored
} }
finally
{
}
}); });
return Task.CompletedTask; return Task.CompletedTask;
} }
@@ -325,7 +329,7 @@ namespace NadekoBot
.ConfigureAwait(false); .ConfigureAwait(false);
HandleStatusChanges(); HandleStatusChanges();
Ready.TrySetResult(true); IsReady = true;
_ = Task.Run(ExecuteReadySubscriptions); _ = Task.Run(ExecuteReadySubscriptions);
Log.Information("Shard {ShardId} ready", Client.ShardId); Log.Information("Shard {ShardId} ready", Client.ShardId);
} }

View File

@@ -1,6 +1,5 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using Discord; using Discord;
using Discord.WebSocket;
namespace NadekoBot.Common.ModuleBehaviors namespace NadekoBot.Common.ModuleBehaviors
{ {
@@ -12,7 +11,7 @@ namespace NadekoBot.Common.ModuleBehaviors
int Priority { get; } int Priority { get; }
ModuleBehaviorType BehaviorType { get; } ModuleBehaviorType BehaviorType { get; }
Task<bool> RunBehavior(DiscordSocketClient client, IGuild guild, IUserMessage msg); Task<bool> RunBehavior(IGuild guild, IUserMessage msg);
} }
public enum ModuleBehaviorType public enum ModuleBehaviorType

View File

@@ -8,7 +8,6 @@ namespace NadekoBot.Common.ModuleBehaviors
{ {
public int Priority { get; } public int Priority { get; }
Task<bool> TryBlockLate(DiscordSocketClient client, ICommandContext context, Task<bool> TryBlockLate(ICommandContext context, string moduleName, CommandInfo command);
string moduleName, CommandInfo command);
} }
} }

View File

@@ -1,6 +1,5 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using Discord; using Discord;
using Discord.WebSocket;
namespace NadekoBot.Common.ModuleBehaviors namespace NadekoBot.Common.ModuleBehaviors
{ {
@@ -9,6 +8,6 @@ namespace NadekoBot.Common.ModuleBehaviors
/// </summary> /// </summary>
public interface ILateExecutor public interface ILateExecutor
{ {
Task LateExecute(DiscordSocketClient client, IGuild guild, IUserMessage msg); Task LateExecute(IGuild guild, IUserMessage msg);
} }
} }

View File

@@ -142,8 +142,7 @@ namespace NadekoBot.Modules.Administration.Services
} }
} }
public async Task<bool> TryBlockLate(DiscordSocketClient client, ICommandContext context, string moduleName, public async Task<bool> TryBlockLate(ICommandContext context, string moduleName, CommandInfo command)
CommandInfo command)
{ {
if (TryGetOverrides(context.Guild?.Id ?? 0, command.Name, out var perm) && perm is not null) if (TryGetOverrides(context.Guild?.Id ?? 0, command.Name, out var perm) && perm is not null)
{ {

View File

@@ -18,7 +18,7 @@ using Serilog;
namespace NadekoBot.Modules.Administration.Services namespace NadekoBot.Modules.Administration.Services
{ {
public sealed class SelfService : ILateExecutor, IReadyExecutor, INService public sealed class SelfService : ILateExecutor, IReadyExecutor
{ {
private readonly ConnectionMultiplexer _redis; private readonly ConnectionMultiplexer _redis;
private readonly CommandHandler _cmdHandler; private readonly CommandHandler _cmdHandler;
@@ -54,6 +54,7 @@ namespace NadekoBot.Modules.Administration.Services
_httpFactory = factory; _httpFactory = factory;
_bss = bss; _bss = bss;
Log.Information("Self service created");
var sub = _redis.GetSubscriber(); var sub = _redis.GetSubscriber();
if (_client.ShardId == 0) if (_client.ShardId == 0)
{ {
@@ -226,7 +227,7 @@ namespace NadekoBot.Modules.Administration.Services
} }
// forwards dms // forwards dms
public async Task LateExecute(DiscordSocketClient client, IGuild guild, IUserMessage msg) public async Task LateExecute(IGuild guild, IUserMessage msg)
{ {
var bs = _bss.Data; var bs = _bss.Data;
if (msg.Channel is IDMChannel && _bss.Data.ForwardMessages && ownerChannels.Any()) if (msg.Channel is IDMChannel && _bss.Data.ForwardMessages && ownerChannels.Any())

View File

@@ -22,7 +22,7 @@ using YamlDotNet.Serialization;
namespace NadekoBot.Modules.CustomReactions.Services namespace NadekoBot.Modules.CustomReactions.Services
{ {
public sealed class CustomReactionsService : IEarlyBehavior, INService, IReadyExecutor public sealed class CustomReactionsService : IEarlyBehavior, IReadyExecutor
{ {
public enum CrField public enum CrField
{ {
@@ -77,6 +77,7 @@ namespace NadekoBot.Modules.CustomReactions.Services
_pubSub = pubSub; _pubSub = pubSub;
_rng = new NadekoRandom(); _rng = new NadekoRandom();
Log.Information("Custom reaction service created");
_pubSub.Sub(_crsReloadedKey, OnCrsShouldReload); _pubSub.Sub(_crsReloadedKey, OnCrsShouldReload);
pubSub.Sub(_gcrAddedKey, OnGcrAdded); pubSub.Sub(_gcrAddedKey, OnGcrAdded);
pubSub.Sub(_gcrDeletedkey, OnGcrDeleted); pubSub.Sub(_gcrDeletedkey, OnGcrDeleted);
@@ -380,7 +381,7 @@ namespace NadekoBot.Modules.CustomReactions.Services
return result[_rng.Next(0, result.Count)]; return result[_rng.Next(0, result.Count)];
} }
public async Task<bool> RunBehavior(DiscordSocketClient client, IGuild guild, IUserMessage msg) public async Task<bool> RunBehavior(IGuild guild, IUserMessage msg)
{ {
// maybe this message is a custom reaction // maybe this message is a custom reaction
var cr = TryGetCustomReaction(msg); var cr = TryGetCustomReaction(msg);

View File

@@ -5,17 +5,8 @@ using NadekoBot.Modules.Gambling.Common.AnimalRacing;
namespace NadekoBot.Modules.Gambling.Services namespace NadekoBot.Modules.Gambling.Services
{ {
public class AnimalRaceService : INService, IUnloadableService public class AnimalRaceService : INService
{ {
public ConcurrentDictionary<ulong, AnimalRace> AnimalRaces { get; } = new ConcurrentDictionary<ulong, AnimalRace>(); public ConcurrentDictionary<ulong, AnimalRace> AnimalRaces { get; } = new ConcurrentDictionary<ulong, AnimalRace>();
public Task Unload()
{
foreach (var kvp in AnimalRaces)
{
try { kvp.Value.Dispose(); } catch { }
}
return Task.CompletedTask;
}
} }
} }

View File

@@ -15,7 +15,7 @@ using Serilog;
namespace NadekoBot.Modules.Games.Services namespace NadekoBot.Modules.Games.Services
{ {
public class ChatterBotService : IEarlyBehavior, INService public class ChatterBotService : IEarlyBehavior
{ {
private readonly DiscordSocketClient _client; private readonly DiscordSocketClient _client;
private readonly PermissionService _perms; private readonly PermissionService _perms;
@@ -103,7 +103,7 @@ namespace NadekoBot.Modules.Games.Services
return true; return true;
} }
public async Task<bool> RunBehavior(DiscordSocketClient client, IGuild guild, IUserMessage usrMsg) public async Task<bool> RunBehavior(IGuild guild, IUserMessage usrMsg)
{ {
if (!(guild is SocketGuild sg)) if (!(guild is SocketGuild sg))
return false; return false;

View File

@@ -21,7 +21,7 @@ using Serilog;
namespace NadekoBot.Modules.Games.Services namespace NadekoBot.Modules.Games.Services
{ {
public class GamesService : INService, IUnloadableService public class GamesService : INService
{ {
private readonly GamesConfigService _gamesConfig; private readonly GamesConfigService _gamesConfig;
@@ -101,26 +101,6 @@ namespace NadekoBot.Modules.Games.Services
} }
} }
public async Task Unload()
{
_t.Change(Timeout.Infinite, Timeout.Infinite);
AcrophobiaGames.ForEach(x => x.Value.Dispose());
AcrophobiaGames.Clear();
HangmanGames.ForEach(x => x.Value.Dispose());
HangmanGames.Clear();
await Task.WhenAll(RunningTrivias.Select(x => x.Value.StopGame())).ConfigureAwait(false);
RunningTrivias.Clear();
TicTacToeGames.Clear();
await Task.WhenAll(RunningContests.Select(x => x.Value.Stop()))
.ConfigureAwait(false);
RunningContests.Clear();
NunchiGames.ForEach(x => x.Value.Dispose());
NunchiGames.Clear();
}
public void AddTypingArticle(IUser user, string text) public void AddTypingArticle(IUser user, string text)
{ {
TypingArticles.Add(new TypingArticle TypingArticles.Add(new TypingArticle

View File

@@ -3,7 +3,6 @@ using System.Collections.Concurrent;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Discord; using Discord;
using Discord.WebSocket;
using NadekoBot.Common.ModuleBehaviors; using NadekoBot.Common.ModuleBehaviors;
using NadekoBot.Modules.Games.Common; using NadekoBot.Modules.Games.Common;
using NadekoBot.Db.Models; using NadekoBot.Db.Models;
@@ -16,7 +15,7 @@ using Serilog;
namespace NadekoBot.Modules.Games.Services namespace NadekoBot.Modules.Games.Services
{ {
public class PollService : IEarlyBehavior, INService public class PollService : IEarlyBehavior
{ {
public ConcurrentDictionary<ulong, PollRunner> ActivePolls { get; } = new ConcurrentDictionary<ulong, PollRunner>(); public ConcurrentDictionary<ulong, PollRunner> ActivePolls { get; } = new ConcurrentDictionary<ulong, PollRunner>();
@@ -106,7 +105,7 @@ namespace NadekoBot.Modules.Games.Services
try { await msg.DeleteAsync().ConfigureAwait(false); } catch { } try { await msg.DeleteAsync().ConfigureAwait(false); } catch { }
} }
public async Task<bool> RunBehavior(DiscordSocketClient client, IGuild guild, IUserMessage msg) public async Task<bool> RunBehavior(IGuild guild, IUserMessage msg)
{ {
if (guild is null) if (guild is null)
return false; return false;

View File

@@ -1,6 +1,5 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using Discord; using Discord;
using Discord.WebSocket;
using System; using System;
using Discord.Commands; using Discord.Commands;
using NadekoBot.Extensions; using NadekoBot.Extensions;
@@ -40,7 +39,7 @@ namespace NadekoBot.Modules.Help.Services
.Build(); .Build();
} }
public Task LateExecute(DiscordSocketClient client, IGuild guild, IUserMessage msg) public Task LateExecute(IGuild guild, IUserMessage msg)
{ {
var settings = _bss.Data; var settings = _bss.Data;
if (guild is null) if (guild is null)

View File

@@ -1,7 +1,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Discord; using Discord;
using Discord.WebSocket;
using NadekoBot.Common.ModuleBehaviors; using NadekoBot.Common.ModuleBehaviors;
using NadekoBot.Services; using NadekoBot.Services;
using NadekoBot.Services.Database.Models; using NadekoBot.Services.Database.Models;
@@ -12,7 +11,7 @@ using NadekoBot.Db;
namespace NadekoBot.Modules.Permissions.Services namespace NadekoBot.Modules.Permissions.Services
{ {
public sealed class BlacklistService : IEarlyBehavior, INService public sealed class BlacklistService : IEarlyBehavior
{ {
private readonly DbService _db; private readonly DbService _db;
private readonly IPubSub _pubSub; private readonly IPubSub _pubSub;
@@ -39,7 +38,7 @@ namespace NadekoBot.Modules.Permissions.Services
return default; return default;
} }
public Task<bool> RunBehavior(DiscordSocketClient _, IGuild guild, IUserMessage usrMsg) public Task<bool> RunBehavior(IGuild guild, IUserMessage usrMsg)
{ {
foreach (var bl in _blacklist) foreach (var bl in _blacklist)
{ {

View File

@@ -63,7 +63,7 @@ namespace NadekoBot.Modules.Permissions.Services
return Task.FromResult(false); return Task.FromResult(false);
} }
public Task<bool> TryBlockLate(DiscordSocketClient client, ICommandContext ctx, string moduleName, CommandInfo command) public Task<bool> TryBlockLate(ICommandContext ctx, string moduleName, CommandInfo command)
{ {
var guild = ctx.Guild; var guild = ctx.Guild;
var user = ctx.User; var user = ctx.User;

View File

@@ -17,7 +17,7 @@ using Serilog;
namespace NadekoBot.Modules.Permissions.Services namespace NadekoBot.Modules.Permissions.Services
{ {
public class FilterService : IEarlyBehavior, INService public class FilterService : IEarlyBehavior
{ {
private readonly DbService _db; private readonly DbService _db;
@@ -117,13 +117,13 @@ namespace NadekoBot.Modules.Permissions.Services
if (guild is null || usrMsg is null) if (guild is null || usrMsg is null)
return Task.CompletedTask; return Task.CompletedTask;
return RunBehavior(null, guild, usrMsg); return RunBehavior(guild, usrMsg);
}); });
return Task.CompletedTask; return Task.CompletedTask;
}; };
} }
public async Task<bool> RunBehavior(DiscordSocketClient _, IGuild guild, IUserMessage msg) public async Task<bool> RunBehavior(IGuild guild, IUserMessage msg)
{ {
if (!(msg.Author is IGuildUser gu) || gu.GuildPermissions.Administrator) if (!(msg.Author is IGuildUser gu) || gu.GuildPermissions.Administrator)
return false; return false;

View File

@@ -21,7 +21,7 @@ namespace NadekoBot.Modules.Permissions.Services
} }
public Task<bool> TryBlockLate(DiscordSocketClient client, ICommandContext ctx, string moduleName, CommandInfo command) public Task<bool> TryBlockLate(ICommandContext ctx, string moduleName, CommandInfo command)
{ {
var settings = _bss.Data; var settings = _bss.Data;
var commandName = command.Name.ToLowerInvariant(); var commandName = command.Name.ToLowerInvariant();

View File

@@ -98,8 +98,7 @@ namespace NadekoBot.Modules.Permissions.Services
}); });
} }
public async Task<bool> TryBlockLate(DiscordSocketClient client, ICommandContext ctx, string moduleName, public async Task<bool> TryBlockLate(ICommandContext ctx, string moduleName, CommandInfo command)
CommandInfo command)
{ {
var guild = ctx.Guild; var guild = ctx.Guild;
var msg = ctx.Message; var msg = ctx.Message;

View File

@@ -31,7 +31,7 @@ using Image = SixLabors.ImageSharp.Image;
namespace NadekoBot.Modules.Searches.Services namespace NadekoBot.Modules.Searches.Services
{ {
public class SearchesService : INService, IUnloadableService public class SearchesService : INService
{ {
private readonly IHttpClientFactory _httpFactory; private readonly IHttpClientFactory _httpFactory;
private readonly DiscordSocketClient _client; private readonly DiscordSocketClient _client;
@@ -482,19 +482,6 @@ namespace NadekoBot.Modules.Searches.Services
} }
} }
public Task Unload()
{
AutoBoobTimers.ForEach(x => x.Value.Change(Timeout.Infinite, Timeout.Infinite));
AutoBoobTimers.Clear();
AutoButtTimers.ForEach(x => x.Value.Change(Timeout.Infinite, Timeout.Infinite));
AutoButtTimers.Clear();
AutoHentaiTimers.ForEach(x => x.Value.Change(Timeout.Infinite, Timeout.Infinite));
AutoHentaiTimers.Clear();
_imageCacher.Clear();
return Task.CompletedTask;
}
public async Task<MtgData> GetMtgCardAsync(string search) public async Task<MtgData> GetMtgCardAsync(string search)
{ {
search = search.Trim().ToLowerInvariant(); search = search.Trim().ToLowerInvariant();

View File

@@ -13,7 +13,7 @@ using System.Threading.Tasks;
namespace NadekoBot.Modules.Utility.Services namespace NadekoBot.Modules.Utility.Services
{ {
public class ConverterService : INService, IUnloadableService public class ConverterService : INService
{ {
public ConvertUnit[] Units => public ConvertUnit[] Units =>
_cache.Redis.GetDatabase() _cache.Redis.GetDatabase()
@@ -87,12 +87,6 @@ namespace NadekoBot.Modules.Utility.Services
// ignored // ignored
} }
} }
public Task Unload()
{
_currencyUpdater?.Change(Timeout.Infinite, Timeout.Infinite);
return Task.CompletedTask;
}
} }
public class Rates public class Rates

View File

@@ -16,7 +16,7 @@ using Serilog;
namespace NadekoBot.Modules.Utility.Services namespace NadekoBot.Modules.Utility.Services
{ {
public class PatreonRewardsService : INService, IUnloadableService public class PatreonRewardsService : INService
{ {
private readonly SemaphoreSlim getPledgesLocker = new SemaphoreSlim(1, 1); private readonly SemaphoreSlim getPledgesLocker = new SemaphoreSlim(1, 1);
@@ -218,11 +218,5 @@ namespace NadekoBot.Modules.Utility.Services
// ignored // ignored
} }
} }
public Task Unload()
{
_updater?.Change(Timeout.Infinite, Timeout.Infinite);
return Task.CompletedTask;
}
} }
} }

View File

@@ -20,7 +20,7 @@ using Serilog;
namespace NadekoBot.Modules.Utility.Services namespace NadekoBot.Modules.Utility.Services
{ {
public sealed class RepeaterService : IReadyExecutor, INService public sealed class RepeaterService : IReadyExecutor
{ {
public const int MAX_REPEATERS = 5; public const int MAX_REPEATERS = 5;

View File

@@ -17,7 +17,7 @@ using Serilog;
namespace NadekoBot.Modules.Utility.Services namespace NadekoBot.Modules.Utility.Services
{ {
public class StreamRoleService : INService, IUnloadableService public class StreamRoleService : INService
{ {
private readonly DbService _db; private readonly DbService _db;
private readonly DiscordSocketClient _client; private readonly DiscordSocketClient _client;
@@ -48,12 +48,6 @@ namespace NadekoBot.Modules.Utility.Services
}); });
} }
public Task Unload()
{
_client.GuildMemberUpdated -= Client_GuildMemberUpdated;
return Task.CompletedTask;
}
private Task Client_GuildMemberUpdated(SocketGuildUser before, SocketGuildUser after) private Task Client_GuildMemberUpdated(SocketGuildUser before, SocketGuildUser after)
{ {
var _ = Task.Run(async () => var _ = Task.Run(async () =>

View File

@@ -11,7 +11,7 @@ using NadekoBot.Modules.Administration;
namespace NadekoBot.Modules.Utility.Services namespace NadekoBot.Modules.Utility.Services
{ {
public class VerboseErrorsService : INService, IUnloadableService public class VerboseErrorsService : INService
{ {
private readonly ConcurrentHashSet<ulong> guildsEnabled; private readonly ConcurrentHashSet<ulong> guildsEnabled;
private readonly DbService _db; private readonly DbService _db;
@@ -32,12 +32,6 @@ namespace NadekoBot.Modules.Utility.Services
.Select(x => x.GuildId)); .Select(x => x.GuildId));
} }
public Task Unload()
{
_ch.CommandErrored -= LogVerboseError;
return Task.CompletedTask;
}
private async Task LogVerboseError(CommandInfo cmd, ITextChannel channel, string reason) private async Task LogVerboseError(CommandInfo cmd, ITextChannel channel, string reason)
{ {
if (channel is null || !guildsEnabled.Contains(channel.GuildId)) if (channel is null || !guildsEnabled.Contains(channel.GuildId))

View File

@@ -28,7 +28,7 @@ using Image = SixLabors.ImageSharp.Image;
namespace NadekoBot.Modules.Xp.Services namespace NadekoBot.Modules.Xp.Services
{ {
public class XpService : INService, IUnloadableService public class XpService : INService
{ {
private enum NotifOf private enum NotifOf
{ {
@@ -1182,14 +1182,6 @@ namespace NadekoBot.Modules.Xp.Services
} }
} }
public Task Unload()
{
_cmd.OnMessageNoTrigger -= _cmd_OnMessageNoTrigger;
_client.UserVoiceStateUpdated -= _client_OnUserVoiceStateUpdated;
_client.GuildAvailable -= _client_OnGuildAvailable;
return Task.CompletedTask;
}
public void XpReset(ulong guildId, ulong userId) public void XpReset(ulong guildId, ulong userId)
{ {
using (var uow = _db.GetDbContext()) using (var uow = _db.GetDbContext())

View File

@@ -42,6 +42,7 @@
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="5.0.0" /> <PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="5.0.0" />
<PackageReference Include="Microsoft.SyndicationFeed.ReaderWriter" Version="1.0.2" /> <PackageReference Include="Microsoft.SyndicationFeed.ReaderWriter" Version="1.0.2" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="Scrutor" Version="3.3.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="3.1.1" /> <PackageReference Include="Serilog.Sinks.Console" Version="3.1.1" />
<PackageReference Include="Serilog.Sinks.Seq" Version="5.0.1" /> <PackageReference Include="Serilog.Sinks.Seq" Version="5.0.1" />
<PackageReference Include="SixLabors.ImageSharp" Version="1.0.3" /> <PackageReference Include="SixLabors.ImageSharp" Version="1.0.3" />

View File

@@ -2,7 +2,6 @@
using Discord.Commands; using Discord.Commands;
using Discord.Net; using Discord.Net;
using Discord.WebSocket; using Discord.WebSocket;
using Microsoft.Extensions.DependencyInjection;
using NadekoBot.Common.Collections; using NadekoBot.Common.Collections;
using NadekoBot.Common.ModuleBehaviors; using NadekoBot.Common.ModuleBehaviors;
using NadekoBot.Extensions; using NadekoBot.Extensions;
@@ -10,26 +9,16 @@ using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Diagnostics;
using System.IO;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using NadekoBot.Common.Configs; using NadekoBot.Common.Configs;
using NadekoBot.Services;
using NadekoBot.Db; using NadekoBot.Db;
using NadekoBot.Modules.Administration;
using Serilog; using Serilog;
namespace NadekoBot.Services namespace NadekoBot.Services
{ {
public class GuildUserComparer : IEqualityComparer<IGuildUser>
{
public bool Equals(IGuildUser x, IGuildUser y) => x.Id == y.Id;
public int GetHashCode(IGuildUser obj) => obj.Id.GetHashCode();
}
public class CommandHandler : INService public class CommandHandler : INService
{ {
public const int GlobalCommandsCooldown = 750; public const int GlobalCommandsCooldown = 750;
@@ -38,11 +27,8 @@ namespace NadekoBot.Services
private readonly CommandService _commandService; private readonly CommandService _commandService;
private readonly BotConfigService _bss; private readonly BotConfigService _bss;
private readonly Bot _bot; private readonly Bot _bot;
private readonly IBehaviourExecutor _behaviourExecutor;
private IServiceProvider _services; private IServiceProvider _services;
private IEnumerable<IEarlyBehavior> _earlyBehaviors;
private IEnumerable<IInputTransformer> _inputTransformers;
private IEnumerable<ILateBlocker> _lateBlockers;
private IEnumerable<ILateExecutor> _lateExecutors;
private ConcurrentDictionary<ulong, string> _prefixes { get; } = new ConcurrentDictionary<ulong, string>(); private ConcurrentDictionary<ulong, string> _prefixes { get; } = new ConcurrentDictionary<ulong, string>();
@@ -56,17 +42,24 @@ namespace NadekoBot.Services
public ConcurrentHashSet<ulong> UsersOnShortCooldown { get; } = new ConcurrentHashSet<ulong>(); public ConcurrentHashSet<ulong> UsersOnShortCooldown { get; } = new ConcurrentHashSet<ulong>();
private readonly Timer _clearUsersOnShortCooldown; private readonly Timer _clearUsersOnShortCooldown;
public CommandHandler(DiscordSocketClient client, DbService db, CommandService commandService, // todo move behaviours to a separate service
BotConfigService bss, Bot bot, IServiceProvider services) public CommandHandler(
DiscordSocketClient client,
DbService db,
CommandService commandService,
BotConfigService bss,
Bot bot,
IBehaviourExecutor behaviourExecutor,
IServiceProvider services)
{ {
_client = client; _client = client;
_commandService = commandService; _commandService = commandService;
_bss = bss; _bss = bss;
_bot = bot; _bot = bot;
_behaviourExecutor = behaviourExecutor;
_db = db; _db = db;
_services = services; _services = services;
_clearUsersOnShortCooldown = new Timer(_ => _clearUsersOnShortCooldown = new Timer(_ =>
{ {
UsersOnShortCooldown.Clear(); UsersOnShortCooldown.Clear();
@@ -118,27 +111,6 @@ namespace NadekoBot.Services
return prefix; return prefix;
} }
public void AddServices(IServiceCollection services)
{
_lateBlockers = services.Where(x => x.ImplementationType?.GetInterfaces().Contains(typeof(ILateBlocker)) ?? false)
.Select(x => _services.GetService(x.ImplementationType) as ILateBlocker)
.OrderByDescending(x => x.Priority)
.ToArray();
_lateExecutors = services.Where(x => x.ImplementationType?.GetInterfaces().Contains(typeof(ILateExecutor)) ?? false)
.Select(x => _services.GetService(x.ImplementationType) as ILateExecutor)
.ToArray();
_inputTransformers = services.Where(x => x.ImplementationType?.GetInterfaces().Contains(typeof(IInputTransformer)) ?? false)
.Select(x => _services.GetService(x.ImplementationType) as IInputTransformer)
.ToArray();
_earlyBehaviors = services.Where(x => x.ImplementationType?.GetInterfaces().Contains(typeof(IEarlyBehavior)) ?? false)
.Select(x => _services.GetService(x.ImplementationType) as IEarlyBehavior)
.ToArray();
}
public async Task ExecuteExternal(ulong? guildId, ulong channelId, string commandText) public async Task ExecuteExternal(ulong? guildId, ulong channelId, string commandText)
{ {
if (guildId != null) if (guildId != null)
@@ -172,8 +144,7 @@ namespace NadekoBot.Services
private Task LogSuccessfulExecution(IUserMessage usrMsg, ITextChannel channel, params int[] execPoints) private Task LogSuccessfulExecution(IUserMessage usrMsg, ITextChannel channel, params int[] execPoints)
{ {
var bss = _services.GetService<BotConfigService>(); if (_bss.GetRawData().ConsoleOutputType == ConsoleOutputType.Normal)
if (bss.Data.ConsoleOutputType == ConsoleOutputType.Normal)
{ {
Log.Information($"Command Executed after " + string.Join("/", execPoints.Select(x => (x * _oneThousandth).ToString("F3"))) + "s\n\t" + Log.Information($"Command Executed after " + string.Join("/", execPoints.Select(x => (x * _oneThousandth).ToString("F3"))) + "s\n\t" +
"User: {0}\n\t" + "User: {0}\n\t" +
@@ -199,8 +170,7 @@ namespace NadekoBot.Services
private void LogErroredExecution(string errorMessage, IUserMessage usrMsg, ITextChannel channel, params int[] execPoints) private void LogErroredExecution(string errorMessage, IUserMessage usrMsg, ITextChannel channel, params int[] execPoints)
{ {
var bss = _services.GetService<BotConfigService>(); if (_bss.GetRawData().ConsoleOutputType == ConsoleOutputType.Normal)
if (bss.Data.ConsoleOutputType == ConsoleOutputType.Normal)
{ {
Log.Warning($"Command Errored after " + string.Join("/", execPoints.Select(x => (x * _oneThousandth).ToString("F3"))) + "s\n\t" + Log.Warning($"Command Errored after " + string.Join("/", execPoints.Select(x => (x * _oneThousandth).ToString("F3"))) + "s\n\t" +
"User: {0}\n\t" + "User: {0}\n\t" +
@@ -231,7 +201,7 @@ namespace NadekoBot.Services
{ {
try try
{ {
if (msg.Author.IsBot || !_bot.Ready.Task.IsCompleted) //no bots, wait until bot connected and initialized if (msg.Author.IsBot || !_bot.IsReady) //no bots, wait until bot connected and initialized
return; return;
if (!(msg is SocketUserMessage usrMsg)) if (!(msg is SocketUserMessage usrMsg))
@@ -258,62 +228,33 @@ namespace NadekoBot.Services
public async Task TryRunCommand(SocketGuild guild, ISocketMessageChannel channel, IUserMessage usrMsg) public async Task TryRunCommand(SocketGuild guild, ISocketMessageChannel channel, IUserMessage usrMsg)
{ {
var execTime = Environment.TickCount; var startTime = Environment.TickCount;
//its nice to have early blockers and early blocking executors separate, but var blocked = await _behaviourExecutor.RunEarlyBehavioursAsync(guild, usrMsg);
//i could also have one interface with priorities, and just put early blockers on if (blocked)
//highest priority. :thinking: return;
foreach (var beh in _earlyBehaviors)
{
if (await beh.RunBehavior(_client, guild, usrMsg).ConfigureAwait(false))
{
if (beh.BehaviorType == ModuleBehaviorType.Blocker)
{
Log.Information("Blocked User: [{0}] Message: [{1}] Service: [{2}]", usrMsg.Author,
usrMsg.Content, beh.GetType().Name);
}
else if (beh.BehaviorType == ModuleBehaviorType.Executor)
{
Log.Information("User [{0}] executed [{1}] in [{2}]", usrMsg.Author, usrMsg.Content,
beh.GetType().Name);
} var blockTime = Environment.TickCount - startTime;
return;
}
}
var exec2 = Environment.TickCount - execTime;
var messageContent = await _behaviourExecutor.RunInputTransformersAsync(guild, usrMsg);
string messageContent = usrMsg.Content;
foreach (var exec in _inputTransformers)
{
string newContent;
if ((newContent = await exec.TransformInput(guild, usrMsg.Channel, usrMsg.Author, messageContent)
.ConfigureAwait(false)) != messageContent.ToLowerInvariant())
{
messageContent = newContent;
break;
}
}
var prefix = GetPrefix(guild?.Id); var prefix = GetPrefix(guild?.Id);
var isPrefixCommand = messageContent.StartsWith(".prefix", StringComparison.InvariantCultureIgnoreCase); var isPrefixCommand = messageContent.StartsWith(".prefix", StringComparison.InvariantCultureIgnoreCase);
// execute the command and measure the time it took // execute the command and measure the time it took
if (messageContent.StartsWith(prefix, StringComparison.InvariantCulture) || isPrefixCommand) if (messageContent.StartsWith(prefix, StringComparison.InvariantCulture) || isPrefixCommand)
{ {
var (Success, Error, Info) = await ExecuteCommandAsync(new CommandContext(_client, usrMsg), messageContent, isPrefixCommand ? 1 : prefix.Length, _services, MultiMatchHandling.Best).ConfigureAwait(false); var (Success, Error, Info) = await ExecuteCommandAsync(new CommandContext(_client, usrMsg), messageContent, isPrefixCommand ? 1 : prefix.Length, _services, MultiMatchHandling.Best).ConfigureAwait(false);
execTime = Environment.TickCount - execTime; startTime = Environment.TickCount - startTime;
if (Success) if (Success)
{ {
await LogSuccessfulExecution(usrMsg, channel as ITextChannel, exec2, execTime).ConfigureAwait(false); await LogSuccessfulExecution(usrMsg, channel as ITextChannel, blockTime, startTime).ConfigureAwait(false);
await CommandExecuted(usrMsg, Info).ConfigureAwait(false); await CommandExecuted(usrMsg, Info).ConfigureAwait(false);
return; return;
} }
else if (Error != null) else if (Error != null)
{ {
LogErroredExecution(Error, usrMsg, channel as ITextChannel, exec2, execTime); LogErroredExecution(Error, usrMsg, channel as ITextChannel, blockTime, startTime);
if (guild != null) if (guild != null)
await CommandErrored(Info, channel as ITextChannel, Error).ConfigureAwait(false); await CommandErrored(Info, channel as ITextChannel, Error).ConfigureAwait(false);
} }
@@ -323,11 +264,7 @@ namespace NadekoBot.Services
await OnMessageNoTrigger(usrMsg).ConfigureAwait(false); await OnMessageNoTrigger(usrMsg).ConfigureAwait(false);
} }
foreach (var exec in _lateExecutors) await _behaviourExecutor.RunLateExecutorsAsync(guild, usrMsg);
{
await exec.LateExecute(_client, guild, usrMsg).ConfigureAwait(false);
}
} }
public Task<(bool Success, string Error, CommandInfo Info)> ExecuteCommandAsync(CommandContext context, string input, int argPos, IServiceProvider serviceProvider, MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception) public Task<(bool Success, string Error, CommandInfo Info)> ExecuteCommandAsync(CommandContext context, string input, int argPos, IServiceProvider serviceProvider, MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception)
@@ -423,17 +360,9 @@ namespace NadekoBot.Services
return (false, null, cmd); return (false, null, cmd);
//return SearchResult.FromError(CommandError.Exception, "You are on a global cooldown."); //return SearchResult.FromError(CommandError.Exception, "You are on a global cooldown.");
var commandName = cmd.Aliases.First(); var blocked = await _behaviourExecutor.RunLateBlockersAsync(context, cmd);
foreach (var exec in _lateBlockers) if (blocked)
{ return (false, null, cmd);
if (await exec.TryBlockLate(_client, context, cmd.Module.GetTopLevelModule().Name, cmd)
.ConfigureAwait(false))
{
Log.Information("Late blocking User [{0}] Command: [{1}] in [{2}]", context.User, commandName,
exec.GetType().Name);
return (false, null, cmd);
}
}
//If we get this far, at least one parse was successful. Execute the most likely overload. //If we get this far, at least one parse was successful. Execute the most likely overload.
var chosenOverload = successfulParses[0]; var chosenOverload = successfulParses[0];

View File

@@ -0,0 +1,15 @@
using System.Threading.Tasks;
using Discord;
using Discord.Commands;
using Discord.WebSocket;
namespace NadekoBot.Services
{
public interface IBehaviourExecutor
{
public Task<bool> RunEarlyBehavioursAsync(SocketGuild guild, IUserMessage usrMsg);
public Task<string> RunInputTransformersAsync(SocketGuild guild, IUserMessage usrMsg);
Task<bool> RunLateBlockersAsync(ICommandContext context, CommandInfo cmd);
Task RunLateExecutorsAsync(SocketGuild guild, IUserMessage usrMsg);
}
}

View File

@@ -1,6 +1,4 @@
using System.Threading.Tasks; namespace NadekoBot.Services
namespace NadekoBot.Services
{ {
/// <summary> /// <summary>
/// All services must implement this interface in order to be auto-discovered by the DI system /// All services must implement this interface in order to be auto-discovered by the DI system
@@ -9,12 +7,4 @@ namespace NadekoBot.Services
{ {
} }
/// <summary>
/// All services which require cleanup after they are unloaded must implement this interface
/// </summary>
public interface IUnloadableService
{
Task Unload();
}
} }

View File

@@ -0,0 +1,100 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Discord;
using Discord.Commands;
using Discord.WebSocket;
using NadekoBot.Common.ModuleBehaviors;
using NadekoBot.Extensions;
using Serilog;
namespace NadekoBot.Services
{
public sealed class BehaviorExecutor : IBehaviourExecutor
{
private readonly DiscordSocketClient _client;
private readonly IEnumerable<ILateExecutor> _lateExecutors;
private readonly IEnumerable<ILateBlocker> _lateBlockers;
private readonly IEnumerable<IEarlyBehavior> _earlyBehaviors;
private readonly IEnumerable<IInputTransformer> _transformers;
public BehaviorExecutor(
DiscordSocketClient client,
IEnumerable<ILateExecutor> lateExecutors,
IEnumerable<ILateBlocker> lateBlockers,
IEnumerable<IEarlyBehavior> earlyBehaviors,
IEnumerable<IInputTransformer> transformers)
{
_client = client;
_lateExecutors = lateExecutors;
_lateBlockers = lateBlockers;
_earlyBehaviors = earlyBehaviors;
_transformers = transformers;
}
// todo early behaviors should print for themselves
public async Task<bool> RunEarlyBehavioursAsync(SocketGuild guild, IUserMessage usrMsg)
{
foreach (var beh in _earlyBehaviors)
{
if (await beh.RunBehavior(guild, usrMsg))
{
return true;
}
}
return false;
}
public async Task<string> RunInputTransformersAsync(SocketGuild guild, IUserMessage usrMsg)
{
var messageContent = usrMsg.Content;
foreach (var exec in _transformers)
{
string newContent;
if ((newContent = await exec.TransformInput(guild, usrMsg.Channel, usrMsg.Author, messageContent))
!= messageContent.ToLowerInvariant())
{
messageContent = newContent;
break;
}
}
return messageContent;
}
public async Task<bool> RunLateBlockersAsync(ICommandContext ctx, CommandInfo cmd)
{
foreach (var exec in _lateBlockers)
{
if (await exec.TryBlockLate(ctx, cmd.Module.GetTopLevelModule().Name, cmd))
{
Log.Information("Late blocking User [{0}] Command: [{1}] in [{2}]",
ctx.User,
cmd.Aliases[0],
exec.GetType().Name);
return true;
}
}
return false;
}
public async Task RunLateExecutorsAsync(SocketGuild guild, IUserMessage usrMsg)
{
foreach (var exec in _lateExecutors)
{
try
{
await exec.LateExecute(guild, usrMsg).ConfigureAwait(false);
}
catch (Exception ex)
{
Log.Error(ex, "Error in {TypeName} late executor: {ErrorMessage}",
exec.GetType().Name,
ex.Message);
}
}
}
}
}

View File

@@ -24,7 +24,6 @@ namespace NadekoBot.Services
private IDatabase _db => _con.GetDatabase(); private IDatabase _db => _con.GetDatabase();
private const string _basePath = "data/"; private const string _basePath = "data/";
private const string _oldBasePath = "data/images/";
private const string _cardsPath = "data/images/cards"; private const string _cardsPath = "data/images/cards";
public ImageUrls ImageUrls { get; private set; } public ImageUrls ImageUrls { get; private set; }
@@ -79,112 +78,11 @@ namespace NadekoBot.Services
_con = con; _con = con;
_creds = creds; _creds = creds;
_http = new HttpClient(); _http = new HttpClient();
Migrate();
ImageUrls = JsonConvert.DeserializeObject<ImageUrls>( ImageUrls = JsonConvert.DeserializeObject<ImageUrls>(
File.ReadAllText(Path.Combine(_basePath, "images.json"))); File.ReadAllText(Path.Combine(_basePath, "images.json")));
} }
private void Migrate()
{
try
{
Migrate1();
Migrate2();
Migrate3();
}
catch (Exception ex)
{
Log.Warning(ex.Message);
Log.Error("Something has been incorrectly formatted in your 'images.json' file.\n" +
"Use the 'images_example.json' file as reference to fix it and restart the bot.");
}
}
private void Migrate1()
{
if (!File.Exists(Path.Combine(_oldBasePath, "images.json")))
return;
Log.Information("Migrating images v0 to images v1.");
// load old images
var oldUrls = JsonConvert.DeserializeObject<ImageUrls>(
File.ReadAllText(Path.Combine(_oldBasePath, "images.json")));
// load new images
var newUrls = JsonConvert.DeserializeObject<ImageUrls>(
File.ReadAllText(Path.Combine(_basePath, "images.json")));
//swap new links with old ones if set. Also update old links.
newUrls.Coins = oldUrls.Coins;
newUrls.Currency = oldUrls.Currency;
newUrls.Dice = oldUrls.Dice;
newUrls.Rategirl = oldUrls.Rategirl;
newUrls.Xp = oldUrls.Xp;
newUrls.Version = 1;
File.WriteAllText(Path.Combine(_basePath, "images.json"),
JsonConvert.SerializeObject(newUrls, Formatting.Indented));
File.Delete(Path.Combine(_oldBasePath, "images.json"));
}
private void Migrate2()
{
// load new images
var urls = JsonConvert.DeserializeObject<ImageUrls>(File.ReadAllText(Path.Combine(_basePath, "images.json")));
if (urls.Version >= 2)
return;
Log.Information("Migrating images v1 to images v2.");
urls.Version = 2;
var prefix = $"{_creds.RedisKey()}_localimg_";
_db.KeyDelete(new[] {
prefix + "heads",
prefix + "tails",
prefix + "dice",
prefix + "slot_background",
prefix + "slotnumbers",
prefix + "slotemojis",
prefix + "wife_matrix",
prefix + "rategirl_dot",
prefix + "xp_card",
prefix + "rip",
prefix + "rip_overlay" }
.Select(x => (RedisKey)x).ToArray());
File.WriteAllText(Path.Combine(_basePath, "images.json"), JsonConvert.SerializeObject(urls, Formatting.Indented));
}
private void Migrate3()
{
var urls = JsonConvert.DeserializeObject<ImageUrls>(
File.ReadAllText(Path.Combine(_basePath, "images.json")));
if (urls.Version >= 3)
return;
urls.Version = 3;
Log.Information("Migrating images v2 to images v3.");
var baseStr = "https://nadeko-pictures.nyc3.digitaloceanspaces.com/other/currency/";
var replacementTable = new Dictionary<Uri, Uri>()
{
{new Uri(baseStr + "0.jpg"), new Uri(baseStr + "0.png") },
{new Uri(baseStr + "1.jpg"), new Uri(baseStr + "1.png") },
{new Uri(baseStr + "2.jpg"), new Uri(baseStr + "2.png") }
};
if (replacementTable.Keys.Any(x => urls.Currency.Contains(x)))
{
urls.Currency = urls.Currency.Select(x => replacementTable.TryGetValue(x, out var newUri)
? newUri
: x).Append(new Uri(baseStr + "3.png"))
.ToArray();
}
File.WriteAllText(Path.Combine(_basePath, "images.json"), JsonConvert.SerializeObject(urls, Formatting.Indented));
}
public async Task<bool> AllKeysExist() public async Task<bool> AllKeysExist()
{ {
try try