Moving to NInject, reimplemented most things. Some services aren't discovered properly yet

This commit is contained in:
Kwoth
2023-02-03 11:39:20 +01:00
parent 6827c195ad
commit 307003e6fe
11 changed files with 357 additions and 242 deletions

View File

@@ -1,4 +1,4 @@
image: mcr.microsoft.com/dotnet/sdk:6.0 image: mcr.microsoft.com/dotnet/sdk:7.0
stages: stages:
- build - build
@@ -69,8 +69,8 @@ publish-windows:
- if: "$CI_COMMIT_TAG" - if: "$CI_COMMIT_TAG"
image: scottyhardy/docker-wine image: scottyhardy/docker-wine
before_script: before_script:
- choco install dotnet-6.0-runtime --version=6.0.4 -y - choco install dotnet-runtime --version=7.0.2 -y
- choco install dotnet-6.0-sdk --version=6.0.202 -y - choco install dotnet-sdk --version=7.0.102 -y
- choco install innosetup -y - choco install innosetup -y
artifacts: artifacts:
paths: paths:

View File

@@ -1,13 +1,16 @@
#nullable disable #nullable disable
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using NadekoBot.Common.Configs; using NadekoBot.Common.Configs;
using NadekoBot.Common.ModuleBehaviors; using NadekoBot.Common.ModuleBehaviors;
using NadekoBot.Db; using NadekoBot.Db;
using NadekoBot.Modules.Utility; using NadekoBot.Modules.Utility;
using NadekoBot.Services.Database.Models; using NadekoBot.Services.Database.Models;
using Ninject;
using Ninject.Extensions.Conventions;
using Ninject.Infrastructure.Language;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Diagnostics; using System.Diagnostics;
using System.Net;
using System.Reflection; using System.Reflection;
using RunMode = Discord.Commands.RunMode; using RunMode = Discord.Commands.RunMode;
@@ -20,7 +23,7 @@ public sealed class Bot
public DiscordSocketClient Client { get; } public DiscordSocketClient Client { get; }
public ImmutableArray<GuildConfig> AllGuildConfigs { get; private set; } public ImmutableArray<GuildConfig> AllGuildConfigs { get; private set; }
private IServiceProvider Services { get; set; } private IKernel Services { get; set; }
public string Mention { get; private set; } public string Mention { get; private set; }
public bool IsReady { get; private set; } public bool IsReady { get; private set; }
@@ -51,9 +54,9 @@ public sealed class Bot
50; 50;
#endif #endif
if(!_creds.UsePrivilegedIntents) if (!_creds.UsePrivilegedIntents)
Log.Warning("You are not using privileged intents. Some features will not work properly"); Log.Warning("You are not using privileged intents. Some features will not work properly");
Client = new(new() Client = new(new()
{ {
MessageCacheSize = messageCacheSize, MessageCacheSize = messageCacheSize,
@@ -99,67 +102,69 @@ public sealed class Bot
AllGuildConfigs = uow.GuildConfigs.GetAllGuildConfigs(startingGuildIdList).ToImmutableArray(); AllGuildConfigs = uow.GuildConfigs.GetAllGuildConfigs(startingGuildIdList).ToImmutableArray();
} }
var svcs = new ServiceCollection().AddTransient(_ => _credsProvider.GetCreds()) // bot creds var kernel = new StandardKernel();
.AddSingleton(_credsProvider)
.AddSingleton(_db) // database kernel.Bind<IBotCredentials>().ToMethod(_ => _credsProvider.GetCreds()).InTransientScope();
.AddSingleton(Client) // discord socket client
.AddSingleton(_commandService) kernel.Bind<IBotCredsProvider>().ToConstant(_credsProvider).InSingletonScope();
// .AddSingleton(_interactionService) kernel.Bind<DbService>().ToConstant(_db).InSingletonScope();
.AddSingleton(this) kernel.Bind<DiscordSocketClient>().ToConstant(Client).InSingletonScope();
.AddSingleton<ISeria, JsonSeria>() kernel.Bind<CommandService>().ToConstant(_commandService).InSingletonScope();
.AddSingleton<IConfigSeria, YamlSeria>() kernel.Bind<Bot>().ToConstant(this).InSingletonScope();
.AddConfigServices()
.AddConfigMigrators() kernel.Bind<ISeria>().To<JsonSeria>().InSingletonScope();
.AddMemoryCache() kernel.Bind<IConfigSeria>().To<YamlSeria>().InSingletonScope();
// music kernel.Bind<IMemoryCache>().ToConstant(new MemoryCache(new MemoryCacheOptions())).InSingletonScope();
.AddMusic()
// cache kernel.AddConfigServices()
.AddCache(_creds); .AddConfigMigrators()
.AddMusic()
.AddCache(_creds)
.AddHttpClients();
svcs.AddHttpClient();
svcs.AddHttpClient("memelist")
.ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler
{
AllowAutoRedirect = false
});
svcs.AddHttpClient("google:search")
.ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler()
{
AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate
});
if (Environment.GetEnvironmentVariable("NADEKOBOT_IS_COORDINATED") != "1") if (Environment.GetEnvironmentVariable("NADEKOBOT_IS_COORDINATED") != "1")
svcs.AddSingleton<ICoordinator, SingleProcessCoordinator>(); {
kernel.Bind<ICoordinator>().To<SingleProcessCoordinator>().InSingletonScope();
}
else else
{ {
svcs.AddSingleton<RemoteGrpcCoordinator>() kernel.Bind<ICoordinator, IReadyExecutor>().To<RemoteGrpcCoordinator>().InSingletonScope();
.AddSingleton<ICoordinator>(x => x.GetRequiredService<RemoteGrpcCoordinator>())
.AddSingleton<IReadyExecutor>(x => x.GetRequiredService<RemoteGrpcCoordinator>());
} }
svcs.Scan(scan => scan.FromAssemblyOf<IReadyExecutor>() kernel.Bind(scan =>
.AddClasses(classes => classes.AssignableToAny( {
// services var classes = scan.FromThisAssembly()
typeof(INService), .SelectAllClasses()
.Where(c => (c.IsAssignableTo(typeof(INService))
// behaviours || c.IsAssignableTo(typeof(IExecOnMessage))
typeof(IExecOnMessage), || c.IsAssignableTo(typeof(IInputTransformer))
typeof(IInputTransformer), || c.IsAssignableTo(typeof(IExecPreCommand))
typeof(IExecPreCommand), || c.IsAssignableTo(typeof(IExecPostCommand))
typeof(IExecPostCommand), || c.IsAssignableTo(typeof(IExecNoCommand)))
typeof(IExecNoCommand)) && !c.HasAttribute<DontAddToIocContainerAttribute>()
.WithoutAttribute<DontAddToIocContainerAttribute>()
#if GLOBAL_NADEKO #if GLOBAL_NADEKO
.WithoutAttribute<NoPublicBotAttribute>() && !c.HasAttribute<NoPublicBotAttribute>()
#endif #endif
) );
.AsSelfWithInterfaces() classes
.WithSingletonLifetime()); .BindAllInterfaces()
.Configure(c => c.InSingletonScope());
classes.BindToSelf()
.Configure(c => c.InSingletonScope());
});
kernel.Bind<IServiceProvider>().ToConstant(kernel).InSingletonScope();
var services = kernel.GetServices(typeof(INService));
foreach (var s in services)
{
Console.WriteLine(s.GetType().FullName);
}
//initialize Services //initialize Services
Services = svcs.BuildServiceProvider(); Services = kernel;
Services.GetRequiredService<IBehaviorHandler>().Initialize(); Services.GetRequiredService<IBehaviorHandler>().Initialize();
Services.GetRequiredService<CurrencyRewardService>(); Services.GetRequiredService<CurrencyRewardService>();
@@ -169,7 +174,7 @@ public sealed class Bot
_ = LoadTypeReaders(typeof(Bot).Assembly); _ = LoadTypeReaders(typeof(Bot).Assembly);
sw.Stop(); sw.Stop();
Log.Information( "All services loaded in {ServiceLoadTime:F2}s", sw.Elapsed.TotalSeconds); Log.Information("All services loaded in {ServiceLoadTime:F2}s", sw.Elapsed.TotalSeconds);
} }
private void ApplyConfigMigrations() private void ApplyConfigMigrations()
@@ -249,10 +254,10 @@ public sealed class Bot
LoginErrorHandler.Handle(ex); LoginErrorHandler.Handle(ex);
Helpers.ReadErrorAndExit(4); Helpers.ReadErrorAndExit(4);
} }
await clientReady.Task.ConfigureAwait(false); await clientReady.Task.ConfigureAwait(false);
Client.Ready -= SetClientReady; Client.Ready -= SetClientReady;
Client.JoinedGuild += Client_JoinedGuild; Client.JoinedGuild += Client_JoinedGuild;
Client.LeftGuild += Client_LeftGuild; Client.LeftGuild += Client_LeftGuild;
@@ -286,7 +291,7 @@ public sealed class Bot
{ {
if (ShardId == 0) if (ShardId == 0)
await _db.SetupAsync(); await _db.SetupAsync();
var sw = Stopwatch.StartNew(); var sw = Stopwatch.StartNew();
await LoginAsync(_creds.Token); await LoginAsync(_creds.Token);
@@ -387,7 +392,7 @@ public sealed class Bot
"""); """);
return Task.CompletedTask; return Task.CompletedTask;
} }
#if GLOBAL_NADEKO || DEBUG #if GLOBAL_NADEKO || DEBUG
if (arg.Exception is not null) if (arg.Exception is not null)
Log.Warning(arg.Exception, "{ErrorSource} | {ErrorMessage}", arg.Source, arg.Message); Log.Warning(arg.Exception, "{ErrorSource} | {ErrorMessage}", arg.Source, arg.Message);

View File

@@ -0,0 +1,50 @@
using Ninject.Modules;
using Ninject.Extensions.Conventions;
using System.Reflection;
namespace Nadeko.Medusa;
public sealed class MedusaIoCKernelModule : NinjectModule
{
private Assembly _a;
public override string Name { get; }
public MedusaIoCKernelModule(string name, Assembly a)
{
Name = name;
_a = a;
}
public override void Load()
{
// todo cehck for duplicate registrations with ninject.extensions.convention
Kernel.Bind(conf =>
{
var transient = conf.From(_a)
.SelectAllClasses()
.WithAttribute<svcAttribute>(x => x.Lifetime == Lifetime.Transient);
transient.BindAllInterfaces().Configure(x => x.InTransientScope());
transient.BindToSelf().Configure(x => x.InTransientScope());
var singleton = conf.From(_a)
.SelectAllClasses()
.WithAttribute<svcAttribute>(x => x.Lifetime == Lifetime.Singleton);
singleton.BindAllInterfaces().Configure(x => x.InSingletonScope());
singleton.BindToSelf().Configure(x => x.InSingletonScope());
});
}
public override void Unload()
{
// todo implement unload
// Kernel.Unbind();
}
public override void Dispose(bool disposing)
{
_a = null!;
base.Dispose(disposing);
}
}

View File

@@ -2,6 +2,8 @@
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Nadeko.Medusa.Adapters; using Nadeko.Medusa.Adapters;
using NadekoBot.Common.ModuleBehaviors; using NadekoBot.Common.ModuleBehaviors;
using Ninject;
using Ninject.Modules;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Globalization; using System.Globalization;
@@ -18,29 +20,31 @@ public sealed class MedusaLoaderService : IMedusaLoaderService, IReadyExecutor,
private readonly IBehaviorHandler _behHandler; private readonly IBehaviorHandler _behHandler;
private readonly IPubSub _pubSub; private readonly IPubSub _pubSub;
private readonly IMedusaConfigService _medusaConfig; private readonly IMedusaConfigService _medusaConfig;
private readonly IKernel _kernel;
private readonly ConcurrentDictionary<string, ResolvedMedusa> _resolved = new(); private readonly ConcurrentDictionary<string, ResolvedMedusa> _resolved = new();
private readonly SemaphoreSlim _lock = new SemaphoreSlim(1, 1); private readonly SemaphoreSlim _lock = new SemaphoreSlim(1, 1);
private readonly TypedKey<string> _loadKey = new("medusa:load"); private readonly TypedKey<string> _loadKey = new("medusa:load");
private readonly TypedKey<string> _unloadKey = new("medusa:unload"); private readonly TypedKey<string> _unloadKey = new("medusa:unload");
private readonly TypedKey<bool> _stringsReload = new("medusa:reload_strings"); private readonly TypedKey<bool> _stringsReload = new("medusa:reload_strings");
private const string BASE_DIR = "data/medusae"; private const string BASE_DIR = "data/medusae";
public MedusaLoaderService(CommandService cmdService, public MedusaLoaderService(
IServiceProvider botServices, CommandService cmdService,
IKernel kernel,
IBehaviorHandler behHandler, IBehaviorHandler behHandler,
IPubSub pubSub, IPubSub pubSub,
IMedusaConfigService medusaConfig) IMedusaConfigService medusaConfig)
{ {
_cmdService = cmdService; _cmdService = cmdService;
_botServices = botServices;
_behHandler = behHandler; _behHandler = behHandler;
_pubSub = pubSub; _pubSub = pubSub;
_medusaConfig = medusaConfig; _medusaConfig = medusaConfig;
_kernel = kernel;
// has to be done this way to support this feature on sharded bots // has to be done this way to support this feature on sharded bots
_pubSub.Sub(_loadKey, async name => await InternalLoadAsync(name)); _pubSub.Sub(_loadKey, async name => await InternalLoadAsync(name));
_pubSub.Sub(_unloadKey, async name => await InternalUnloadAsync(name)); _pubSub.Sub(_unloadKey, async name => await InternalUnloadAsync(name));
@@ -74,12 +78,13 @@ public sealed class MedusaLoaderService : IMedusaLoaderService, IReadyExecutor,
{ {
commands.Add(new SnekCommandStats(command.Aliases.First())); commands.Add(new SnekCommandStats(command.Aliases.First()));
} }
sneks.Add(new SnekStats(snekInfos.Name, commands)); sneks.Add(new SnekStats(snekInfos.Name, commands));
} }
toReturn.Add(new MedusaStats(name, resolvedData.Strings.GetDescription(culture), sneks)); toReturn.Add(new MedusaStats(name, resolvedData.Strings.GetDescription(culture), sneks));
} }
return toReturn; return toReturn;
} }
@@ -88,9 +93,9 @@ public sealed class MedusaLoaderService : IMedusaLoaderService, IReadyExecutor,
foreach (var name in _medusaConfig.GetLoadedMedusae()) foreach (var name in _medusaConfig.GetLoadedMedusae())
{ {
var result = await InternalLoadAsync(name); var result = await InternalLoadAsync(name);
if(result != MedusaLoadResult.Success) if (result != MedusaLoadResult.Success)
Log.Warning("Unable to load '{MedusaName}' medusa", name); Log.Warning("Unable to load '{MedusaName}' medusa", name);
else else
Log.Warning("Loaded medusa '{MedusaName}'", name); Log.Warning("Loaded medusa '{MedusaName}'", name);
} }
} }
@@ -110,7 +115,7 @@ public sealed class MedusaLoaderService : IMedusaLoaderService, IReadyExecutor,
return res; return res;
} }
[MethodImpl(MethodImplOptions.NoInlining)] [MethodImpl(MethodImplOptions.NoInlining)]
public async Task<MedusaUnloadResult> UnloadMedusaAsync(string medusaName) public async Task<MedusaUnloadResult> UnloadMedusaAsync(string medusaName)
{ {
@@ -150,7 +155,7 @@ public sealed class MedusaLoaderService : IMedusaLoaderService, IReadyExecutor,
resolved.Strings.Reload(); resolved.Strings.Reload();
} }
} }
private async Task ReloadStringsInternal() private async Task ReloadStringsInternal()
{ {
await _lock.WaitAsync(); await _lock.WaitAsync();
@@ -179,13 +184,13 @@ public sealed class MedusaLoaderService : IMedusaLoaderService, IReadyExecutor,
.Desc .Desc
?? string.Empty; ?? string.Empty;
} }
[MethodImpl(MethodImplOptions.NoInlining)] [MethodImpl(MethodImplOptions.NoInlining)]
private async ValueTask<MedusaLoadResult> InternalLoadAsync(string name) private async ValueTask<MedusaLoadResult> InternalLoadAsync(string name)
{ {
if (_resolved.ContainsKey(name)) if (_resolved.ContainsKey(name))
return MedusaLoadResult.AlreadyLoaded; return MedusaLoadResult.AlreadyLoaded;
var safeName = Uri.EscapeDataString(name); var safeName = Uri.EscapeDataString(name);
await _lock.WaitAsync(); await _lock.WaitAsync();
@@ -194,7 +199,7 @@ public sealed class MedusaLoaderService : IMedusaLoaderService, IReadyExecutor,
if (LoadAssemblyInternal(safeName, if (LoadAssemblyInternal(safeName,
out var ctx, out var ctx,
out var snekData, out var snekData,
out var services, out var kernelModule,
out var strings, out var strings,
out var typeReaders)) out var typeReaders))
{ {
@@ -213,7 +218,7 @@ public sealed class MedusaLoaderService : IMedusaLoaderService, IReadyExecutor,
await sub.Instance.InitializeAsync(); await sub.Instance.InitializeAsync();
} }
var module = await LoadModuleInternalAsync(name, point, strings, services); var module = await LoadModuleInternalAsync(name, point, strings, kernelModule);
moduleInfos.Add(module); moduleInfos.Add(module);
} }
catch (Exception ex) catch (Exception ex)
@@ -224,7 +229,7 @@ public sealed class MedusaLoaderService : IMedusaLoaderService, IReadyExecutor,
} }
} }
var execs = GetExecsInternal(snekData, strings, services); var execs = GetExecsInternal(snekData, strings);
await _behHandler.AddRangeAsync(execs); await _behHandler.AddRangeAsync(execs);
_resolved[name] = new(LoadContext: ctx, _resolved[name] = new(LoadContext: ctx,
@@ -232,13 +237,10 @@ public sealed class MedusaLoaderService : IMedusaLoaderService, IReadyExecutor,
SnekInfos: snekData.ToImmutableArray(), SnekInfos: snekData.ToImmutableArray(),
strings, strings,
typeReaders, typeReaders,
execs) execs,
{ kernelModule);
Services = services
};
services = null;
_medusaConfig.AddLoadedMedusa(safeName); _medusaConfig.AddLoadedMedusa(safeName);
return MedusaLoadResult.Success; return MedusaLoadResult.Success;
} }
@@ -261,20 +263,22 @@ public sealed class MedusaLoaderService : IMedusaLoaderService, IReadyExecutor,
} }
[MethodImpl(MethodImplOptions.NoInlining)] [MethodImpl(MethodImplOptions.NoInlining)]
private IReadOnlyCollection<ICustomBehavior> GetExecsInternal(IReadOnlyCollection<SnekInfo> snekData, IMedusaStrings strings, IServiceProvider services) private IReadOnlyCollection<ICustomBehavior> GetExecsInternal(
IReadOnlyCollection<SnekInfo> snekData,
IMedusaStrings strings)
{ {
var behs = new List<ICustomBehavior>(); var behs = new List<ICustomBehavior>();
foreach (var snek in snekData) foreach (var snek in snekData)
{ {
behs.Add(new BehaviorAdapter(new(snek.Instance), strings, services)); behs.Add(new BehaviorAdapter(new(snek.Instance), strings, _kernel));
foreach (var sub in snek.Subsneks) foreach (var sub in snek.Subsneks)
{ {
behs.Add(new BehaviorAdapter(new(sub.Instance), strings, services)); behs.Add(new BehaviorAdapter(new(sub.Instance), strings, _kernel));
} }
} }
return behs; return behs;
} }
@@ -290,10 +294,10 @@ public sealed class MedusaLoaderService : IMedusaLoaderService, IReadyExecutor,
notAddedTypeReaders.Add(type); notAddedTypeReaders.Add(type);
continue; continue;
} }
_cmdService.AddTypeReader(type, typeReader); _cmdService.AddTypeReader(type, typeReader);
} }
// remove the ones that were not added // remove the ones that were not added
// to prevent them from being unloaded later // to prevent them from being unloaded later
// as they didn't come from this medusa // as they didn't come from this medusa
@@ -308,56 +312,61 @@ public sealed class MedusaLoaderService : IMedusaLoaderService, IReadyExecutor,
string safeName, string safeName,
[NotNullWhen(true)] out WeakReference<MedusaAssemblyLoadContext>? ctxWr, [NotNullWhen(true)] out WeakReference<MedusaAssemblyLoadContext>? ctxWr,
[NotNullWhen(true)] out IReadOnlyCollection<SnekInfo>? snekData, [NotNullWhen(true)] out IReadOnlyCollection<SnekInfo>? snekData,
out IServiceProvider services, [NotNullWhen(true)] out INinjectModule? ninjectModule,
out IMedusaStrings strings, out IMedusaStrings strings,
out Dictionary<Type, TypeReader> typeReaders) out Dictionary<Type, TypeReader> typeReaders)
{ {
ctxWr = null; ctxWr = null;
snekData = null; snekData = null;
var path = $"{BASE_DIR}/{safeName}/{safeName}.dll"; var path = $"{BASE_DIR}/{safeName}/{safeName}.dll";
strings = MedusaStrings.CreateDefault($"{BASE_DIR}/{safeName}"); strings = MedusaStrings.CreateDefault($"{BASE_DIR}/{safeName}");
var ctx = new MedusaAssemblyLoadContext(Path.GetDirectoryName(path)!); var ctx = new MedusaAssemblyLoadContext(Path.GetDirectoryName(path)!);
var a = ctx.LoadFromAssemblyPath(Path.GetFullPath(path)); var a = ctx.LoadFromAssemblyPath(Path.GetFullPath(path));
var sis = LoadSneksFromAssembly(a, out services);
typeReaders = LoadTypeReadersFromAssembly(a, strings, services); // load services
ninjectModule = new MedusaIoCKernelModule(safeName, a);
_kernel.Load(ninjectModule);
var sis = LoadSneksFromAssembly(safeName, a);
typeReaders = LoadTypeReadersFromAssembly(a, strings);
if (sis.Count == 0) if (sis.Count == 0)
{ {
_kernel.Unload(safeName);
return false; return false;
} }
ctxWr = new(ctx); ctxWr = new(ctx);
snekData = sis; snekData = sis;
return true; return true;
} }
private static readonly Type _paramParserType = typeof(ParamParser<>); private static readonly Type _paramParserType = typeof(ParamParser<>);
[MethodImpl(MethodImplOptions.NoInlining)] [MethodImpl(MethodImplOptions.NoInlining)]
private Dictionary<Type, TypeReader> LoadTypeReadersFromAssembly( private Dictionary<Type, TypeReader> LoadTypeReadersFromAssembly(
Assembly assembly, Assembly assembly,
IMedusaStrings strings, IMedusaStrings strings)
IServiceProvider services)
{ {
var paramParsers = assembly.GetExportedTypes() var paramParsers = assembly.GetExportedTypes()
.Where(x => x.IsClass .Where(x => x.IsClass
&& !x.IsAbstract && !x.IsAbstract
&& x.BaseType is not null && x.BaseType is not null
&& x.BaseType.IsGenericType && x.BaseType.IsGenericType
&& x.BaseType.GetGenericTypeDefinition() == _paramParserType); && x.BaseType.GetGenericTypeDefinition() == _paramParserType);
var typeReaders = new Dictionary<Type, TypeReader>(); var typeReaders = new Dictionary<Type, TypeReader>();
foreach (var parserType in paramParsers) foreach (var parserType in paramParsers)
{ {
var parserObj = ActivatorUtilities.CreateInstance(services, parserType); var parserObj = ActivatorUtilities.CreateInstance(_kernel, parserType);
var targetType = parserType.BaseType!.GetGenericArguments()[0]; var targetType = parserType.BaseType!.GetGenericArguments()[0];
var typeReaderInstance = (TypeReader)Activator.CreateInstance( var typeReaderInstance = (TypeReader)Activator.CreateInstance(
typeof(ParamParserAdapter<>).MakeGenericType(targetType), typeof(ParamParserAdapter<>).MakeGenericType(targetType),
args: new[] { parserObj, strings, services })!; args: new[] { parserObj, strings, _kernel })!;
typeReaders.Add(targetType, typeReaderInstance); typeReaders.Add(targetType, typeReaderInstance);
} }
@@ -365,11 +374,15 @@ public sealed class MedusaLoaderService : IMedusaLoaderService, IReadyExecutor,
} }
[MethodImpl(MethodImplOptions.NoInlining)] [MethodImpl(MethodImplOptions.NoInlining)]
private async Task<ModuleInfo> LoadModuleInternalAsync(string medusaName, SnekInfo snekInfo, IMedusaStrings strings, IServiceProvider services) private async Task<ModuleInfo> LoadModuleInternalAsync(
string medusaName,
SnekInfo snekInfo,
IMedusaStrings strings,
INinjectModule services)
{ {
var module = await _cmdService.CreateModuleAsync(snekInfo.Instance.Prefix, var module = await _cmdService.CreateModuleAsync(snekInfo.Instance.Prefix,
CreateModuleFactory(medusaName, snekInfo, strings, services)); CreateModuleFactory(medusaName, snekInfo, strings, services));
return module; return module;
} }
@@ -378,7 +391,7 @@ public sealed class MedusaLoaderService : IMedusaLoaderService, IReadyExecutor,
string medusaName, string medusaName,
SnekInfo snekInfo, SnekInfo snekInfo,
IMedusaStrings strings, IMedusaStrings strings,
IServiceProvider medusaServices) INinjectModule kernelModule)
=> mb => => mb =>
{ {
var m = mb.WithName(snekInfo.Name); var m = mb.WithName(snekInfo.Name);
@@ -394,17 +407,17 @@ public sealed class MedusaLoaderService : IMedusaLoaderService, IReadyExecutor,
CreateCallback(cmd.ContextType, CreateCallback(cmd.ContextType,
new(snekInfo), new(snekInfo),
new(cmd), new(cmd),
new(medusaServices),
strings), strings),
CreateCommandFactory(medusaName, cmd, strings)); CreateCommandFactory(medusaName, cmd, strings));
} }
foreach (var subInfo in snekInfo.Subsneks) foreach (var subInfo in snekInfo.Subsneks)
m.AddModule(subInfo.Instance.Prefix, CreateModuleFactory(medusaName, subInfo, strings, medusaServices)); m.AddModule(subInfo.Instance.Prefix, CreateModuleFactory(medusaName, subInfo, strings, kernelModule));
}; };
private static readonly RequireContextAttribute _reqGuild = new RequireContextAttribute(ContextType.Guild); private static readonly RequireContextAttribute _reqGuild = new RequireContextAttribute(ContextType.Guild);
private static readonly RequireContextAttribute _reqDm = new RequireContextAttribute(ContextType.DM); private static readonly RequireContextAttribute _reqDm = new RequireContextAttribute(ContextType.DM);
private Action<CommandBuilder> CreateCommandFactory(string medusaName, SnekCommandData cmd, IMedusaStrings strings) private Action<CommandBuilder> CreateCommandFactory(string medusaName, SnekCommandData cmd, IMedusaStrings strings)
=> (cb) => => (cb) =>
{ {
@@ -414,7 +427,7 @@ public sealed class MedusaLoaderService : IMedusaLoaderService, IReadyExecutor,
cb.AddPrecondition(_reqGuild); cb.AddPrecondition(_reqGuild);
else if (cmd.ContextType == CommandContextType.Dm) else if (cmd.ContextType == CommandContextType.Dm)
cb.AddPrecondition(_reqDm); cb.AddPrecondition(_reqDm);
foreach (var f in cmd.Filters) foreach (var f in cmd.Filters)
cb.AddPrecondition(new FilterAdapter(f, strings)); cb.AddPrecondition(new FilterAdapter(f, strings));
@@ -441,12 +454,12 @@ public sealed class MedusaLoaderService : IMedusaLoaderService, IReadyExecutor,
} }
cb.WithPriority(cmd.Priority); cb.WithPriority(cmd.Priority);
// using summary to save method name // using summary to save method name
// method name is used to retrieve desc/usages // method name is used to retrieve desc/usages
cb.WithRemarks($"medusa///{medusaName}"); cb.WithRemarks($"medusa///{medusaName}");
cb.WithSummary(cmd.MethodInfo.Name.ToLowerInvariant()); cb.WithSummary(cmd.MethodInfo.Name.ToLowerInvariant());
foreach (var param in cmd.Parameters) foreach (var param in cmd.Parameters)
{ {
cb.AddParameter(param.Name, param.Type, CreateParamFactory(param)); cb.AddParameter(param.Name, param.Type, CreateParamFactory(param));
@@ -466,19 +479,21 @@ public sealed class MedusaLoaderService : IMedusaLoaderService, IReadyExecutor,
CommandContextType contextType, CommandContextType contextType,
WeakReference<SnekInfo> snekDataWr, WeakReference<SnekInfo> snekDataWr,
WeakReference<SnekCommandData> snekCommandDataWr, WeakReference<SnekCommandData> snekCommandDataWr,
WeakReference<IServiceProvider> medusaServicesWr,
IMedusaStrings strings) IMedusaStrings strings)
=> async (context, parameters, svcs, _) => => async (
context,
parameters,
svcs,
_) =>
{ {
if (!snekCommandDataWr.TryGetTarget(out var cmdData) if (!snekCommandDataWr.TryGetTarget(out var cmdData)
|| !snekDataWr.TryGetTarget(out var snekData) || !snekDataWr.TryGetTarget(out var snekData))
|| !medusaServicesWr.TryGetTarget(out var medusaServices))
{ {
Log.Warning("Attempted to run an unloaded snek's command"); Log.Warning("Attempted to run an unloaded snek's command");
return; return;
} }
var paramObjs = ParamObjs(contextType, cmdData, parameters, context, svcs, medusaServices, strings); var paramObjs = ParamObjs(contextType, cmdData, parameters, context, svcs, _kernel, strings);
try try
{ {
@@ -502,9 +517,8 @@ public sealed class MedusaLoaderService : IMedusaLoaderService, IReadyExecutor,
{ {
paramObjs = null; paramObjs = null;
cmdData = null; cmdData = null;
snekData = null; snekData = null;
medusaServices = null;
} }
}; };
@@ -548,7 +562,7 @@ public sealed class MedusaLoaderService : IMedusaLoaderService, IReadyExecutor,
for (var i = 0; i < parameters.Length; i++) for (var i = 0; i < parameters.Length; i++)
paramObjs[startAt + i] = parameters[i]; paramObjs[startAt + i] = parameters[i];
return paramObjs; return paramObjs;
} }
@@ -569,17 +583,14 @@ public sealed class MedusaLoaderService : IMedusaLoaderService, IReadyExecutor,
} }
await _behHandler.RemoveRangeAsync(lsi.Execs); await _behHandler.RemoveRangeAsync(lsi.Execs);
await DisposeSnekInstances(lsi); await DisposeSnekInstances(lsi);
var lc = lsi.LoadContext; var lc = lsi.LoadContext;
// removing this line will prevent assembly from being unloaded quickly // lsi.KernelModule = null!;
// as this local variable will be held for a long time potentially
// due to how async works
lsi.Services = null!;
lsi = null; lsi = null;
_medusaConfig.RemoveLoadedMedusa(name); _medusaConfig.RemoveLoadedMedusa(name);
return UnloadInternal(lc) return UnloadInternal(lc)
? MedusaUnloadResult.Success ? MedusaUnloadResult.Success
@@ -610,7 +621,7 @@ public sealed class MedusaLoaderService : IMedusaLoaderService, IReadyExecutor,
foreach (var sub in si.Subsneks) foreach (var sub in si.Subsneks)
{ {
await sub.Instance.DisposeAsync(); await sub.Instance.DisposeAsync();
} }
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -635,10 +646,10 @@ public sealed class MedusaLoaderService : IMedusaLoaderService, IReadyExecutor,
[MethodImpl(MethodImplOptions.NoInlining)] [MethodImpl(MethodImplOptions.NoInlining)]
private void UnloadContext(WeakReference<MedusaAssemblyLoadContext> lsiLoadContext) private void UnloadContext(WeakReference<MedusaAssemblyLoadContext> lsiLoadContext)
{ {
if(lsiLoadContext.TryGetTarget(out var ctx)) if (lsiLoadContext.TryGetTarget(out var ctx))
ctx.Unload(); ctx.Unload();
} }
private void GcCleanup() private void GcCleanup()
{ {
// cleanup // cleanup
@@ -653,25 +664,14 @@ public sealed class MedusaLoaderService : IMedusaLoaderService, IReadyExecutor,
private static readonly Type _snekType = typeof(Snek); private static readonly Type _snekType = typeof(Snek);
[MethodImpl(MethodImplOptions.NoInlining)] // [MethodImpl(MethodImplOptions.NoInlining)]
private IServiceProvider LoadMedusaServicesInternal(Assembly a) // private MedusaIoCKernelModule LoadMedusaServicesInternal(string name, Assembly a)
=> new ServiceCollection() // => new MedusaIoCKernelModule(name, a);
.Scan(x => x.FromAssemblies(a)
.AddClasses(static x => x.WithAttribute<svcAttribute>(x => x.Lifetime == Lifetime.Transient))
.AsSelfWithInterfaces()
.WithTransientLifetime()
.AddClasses(static x => x.WithAttribute<svcAttribute>(x => x.Lifetime == Lifetime.Singleton))
.AsSelfWithInterfaces()
.WithSingletonLifetime())
.BuildServiceProvider();
[MethodImpl(MethodImplOptions.NoInlining)] [MethodImpl(MethodImplOptions.NoInlining)]
public IReadOnlyCollection<SnekInfo> LoadSneksFromAssembly(Assembly a, out IServiceProvider services) public IReadOnlyCollection<SnekInfo> LoadSneksFromAssembly(string name, Assembly a)
{ {
var medusaServices = LoadMedusaServicesInternal(a);
services = new MedusaServiceProvider(_botServices, medusaServices);
// find all types in teh assembly // find all types in teh assembly
var types = a.GetExportedTypes(); var types = a.GetExportedTypes();
// snek is always a public non abstract class // snek is always a public non abstract class
@@ -683,14 +683,14 @@ public sealed class MedusaLoaderService : IMedusaLoaderService, IReadyExecutor,
.ToList(); .ToList();
var topModules = new Dictionary<Type, SnekInfo>(); var topModules = new Dictionary<Type, SnekInfo>();
foreach (var cl in classes) foreach (var cl in classes)
{ {
if (cl.DeclaringType is not null) if (cl.DeclaringType is not null)
continue; continue;
// get module data, and add it to the topModules dictionary // get module data, and add it to the topModules dictionary
var module = GetModuleData(cl, services); var module = GetModuleData(cl);
topModules.Add(cl, module); topModules.Add(cl, module);
} }
@@ -708,21 +708,21 @@ public sealed class MedusaLoaderService : IMedusaLoaderService, IReadyExecutor,
dt.Name); dt.Name);
continue; continue;
} }
GetModuleData(c, services, parentData); GetModuleData(c, parentData);
} }
return topModules.Values.ToArray(); return topModules.Values.ToArray();
} }
[MethodImpl(MethodImplOptions.NoInlining)] [MethodImpl(MethodImplOptions.NoInlining)]
private SnekInfo GetModuleData(Type type, IServiceProvider services, SnekInfo? parentData = null) private SnekInfo GetModuleData(Type type, SnekInfo? parentData = null)
{ {
var filters = type.GetCustomAttributes<FilterAttribute>(true) var filters = type.GetCustomAttributes<FilterAttribute>(true)
.ToArray(); .ToArray();
var instance = (Snek)ActivatorUtilities.CreateInstance(services, type); var instance = (Snek)ActivatorUtilities.CreateInstance(_kernel, type);
var module = new SnekInfo(instance.Name, var module = new SnekInfo(instance.Name,
parentData, parentData,
instance, instance,
@@ -744,7 +744,7 @@ public sealed class MedusaLoaderService : IMedusaLoaderService, IReadyExecutor,
| BindingFlags.Public) | BindingFlags.Public)
.Where(static x => .Where(static x =>
{ {
if(x.GetCustomAttribute<cmdAttribute>(true) is null) if (x.GetCustomAttribute<cmdAttribute>(true) is null)
return false; return false;
if (x.ReturnType.IsGenericType) if (x.ReturnType.IsGenericType)
@@ -752,14 +752,14 @@ public sealed class MedusaLoaderService : IMedusaLoaderService, IReadyExecutor,
var genericType = x.ReturnType.GetGenericTypeDefinition(); var genericType = x.ReturnType.GetGenericTypeDefinition();
if (genericType == typeof(Task<>)) if (genericType == typeof(Task<>))
return true; return true;
// if (genericType == typeof(ValueTask<>)) // if (genericType == typeof(ValueTask<>))
// return true; // return true;
Log.Warning("Method {MethodName} has an invalid return type: {ReturnType}", Log.Warning("Method {MethodName} has an invalid return type: {ReturnType}",
x.Name, x.Name,
x.ReturnType); x.ReturnType);
return false; return false;
} }
@@ -776,8 +776,8 @@ public sealed class MedusaLoaderService : IMedusaLoaderService, IReadyExecutor,
return succ; return succ;
}); });
var cmds = new List<SnekCommandData>(); var cmds = new List<SnekCommandData>();
foreach (var method in methodInfos) foreach (var method in methodInfos)
{ {
@@ -808,20 +808,22 @@ public sealed class MedusaLoaderService : IMedusaLoaderService, IReadyExecutor,
if (isContext) if (isContext)
{ {
if (hasDefaultValue || leftoverAttribute != null || isParams) if (hasDefaultValue || leftoverAttribute != null || isParams)
throw new ArgumentException("IContext parameter cannot be optional, leftover, constant or params. " + GetErrorPath(method, pi)); throw new ArgumentException(
"IContext parameter cannot be optional, leftover, constant or params. "
+ GetErrorPath(method, pi));
if (paramCounter != 0) if (paramCounter != 0)
throw new ArgumentException($"IContext parameter has to be first. {GetErrorPath(method, pi)}"); throw new ArgumentException($"IContext parameter has to be first. {GetErrorPath(method, pi)}");
canInject = true; canInject = true;
if (paramType.IsAssignableTo(typeof(GuildContext))) if (paramType.IsAssignableTo(typeof(GuildContext)))
cmdContext = CommandContextType.Guild; cmdContext = CommandContextType.Guild;
else if (paramType.IsAssignableTo(typeof(DmContext))) else if (paramType.IsAssignableTo(typeof(DmContext)))
cmdContext = CommandContextType.Dm; cmdContext = CommandContextType.Dm;
else else
cmdContext = CommandContextType.Any; cmdContext = CommandContextType.Any;
continue; continue;
} }
@@ -831,7 +833,7 @@ public sealed class MedusaLoaderService : IMedusaLoaderService, IReadyExecutor,
throw new ArgumentException($"Parameters marked as [Injected] have to come after IContext"); throw new ArgumentException($"Parameters marked as [Injected] have to come after IContext");
canInject = true; canInject = true;
diParams.Add(paramType); diParams.Add(paramType);
continue; continue;
} }
@@ -861,11 +863,11 @@ public sealed class MedusaLoaderService : IMedusaLoaderService, IReadyExecutor,
} }
var cmdAttribute = method.GetCustomAttribute<cmdAttribute>(true)!; var cmdAttribute = method.GetCustomAttribute<cmdAttribute>(true)!;
var aliases = cmdAttribute.Aliases; var aliases = cmdAttribute.Aliases;
if (aliases.Length == 0) if (aliases.Length == 0)
aliases = new[] { method.Name.ToLowerInvariant() }; aliases = new[] { method.Name.ToLowerInvariant() };
cmds.Add(new( cmds.Add(new(
aliases, aliases,
method, method,

View File

@@ -1,24 +0,0 @@
using System.Runtime.CompilerServices;
namespace Nadeko.Medusa;
public class MedusaServiceProvider : IServiceProvider
{
private readonly IServiceProvider _nadekoServices;
private readonly IServiceProvider _medusaServices;
public MedusaServiceProvider(IServiceProvider nadekoServices, IServiceProvider medusaServices)
{
_nadekoServices = nadekoServices;
_medusaServices = medusaServices;
}
[MethodImpl(MethodImplOptions.NoInlining)]
public object? GetService(Type serviceType)
{
if (!serviceType.Assembly.IsCollectible)
return _nadekoServices.GetService(serviceType);
return _medusaServices.GetService(serviceType);
}
}

View File

@@ -1,4 +1,5 @@
using System.Collections.Immutable; using Ninject.Modules;
using System.Collections.Immutable;
namespace Nadeko.Medusa; namespace Nadeko.Medusa;
@@ -8,7 +9,7 @@ public sealed record ResolvedMedusa(
IImmutableList<SnekInfo> SnekInfos, IImmutableList<SnekInfo> SnekInfos,
IMedusaStrings Strings, IMedusaStrings Strings,
Dictionary<Type, TypeReader> TypeReaders, Dictionary<Type, TypeReader> TypeReaders,
IReadOnlyCollection<ICustomBehavior> Execs) IReadOnlyCollection<ICustomBehavior> Execs,
INinjectModule KernelModule)
{ {
public IServiceProvider Services { get; set; } = null!;
} }

View File

@@ -47,8 +47,14 @@
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="7.0.0" /> <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="7.0.0" />
<PackageReference Include="MorseCode.ITask" Version="2.0.3" /> <PackageReference Include="MorseCode.ITask" Version="2.0.3" />
<PackageReference Include="NetEscapades.Configuration.Yaml" Version="2.2.0" /> <PackageReference Include="NetEscapades.Configuration.Yaml" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" />
<!-- DI -->
<PackageReference Include="Ninject" Version="3.3.6" />
<PackageReference Include="Ninject.Extensions.Conventions" Version="3.3.0" />
<!-- <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" />-->
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="7.0.0" /> <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="7.0.0" />
<!-- <PackageReference Include="Scrutor" Version="4.2.0" />-->
<PackageReference Include="Microsoft.Extensions.Http" Version="7.0.0" /> <PackageReference Include="Microsoft.Extensions.Http" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="7.0.0" /> <PackageReference Include="Microsoft.Extensions.Logging.Console" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="7.0.0" /> <PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="7.0.0" />
@@ -56,7 +62,6 @@
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
<PackageReference Include="NonBlocking" Version="2.1.1" /> <PackageReference Include="NonBlocking" Version="2.1.1" />
<PackageReference Include="OneOf" Version="3.0.223" /> <PackageReference Include="OneOf" Version="3.0.223" />
<PackageReference Include="Scrutor" Version="4.2.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="4.1.0" /> <PackageReference Include="Serilog.Sinks.Console" Version="4.1.0" />
<PackageReference Include="Serilog.Sinks.Seq" Version="5.2.2" /> <PackageReference Include="Serilog.Sinks.Seq" Version="5.2.2" />
<PackageReference Include="SixLabors.Fonts" Version="1.0.0-beta17" /> <PackageReference Include="SixLabors.Fonts" Version="1.0.0-beta17" />

View File

@@ -1,6 +1,7 @@
#nullable disable #nullable disable
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using NadekoBot.Common.ModuleBehaviors; using NadekoBot.Common.ModuleBehaviors;
using Ninject;
namespace NadekoBot.Services; namespace NadekoBot.Services;

View File

@@ -0,0 +1,23 @@
namespace NadekoBot.Extensions;
public static class ReflectionExtensions
{
public static bool IsAssignableToGenericType(this Type givenType, Type genericType)
{
var interfaceTypes = givenType.GetInterfaces();
foreach (var it in interfaceTypes)
{
if (it.IsGenericType && it.GetGenericTypeDefinition() == genericType)
return true;
}
if (givenType.IsGenericType && givenType.GetGenericTypeDefinition() == genericType)
return true;
Type baseType = givenType.BaseType;
if (baseType == null) return false;
return IsAssignableToGenericType(baseType, genericType);
}
}

View File

@@ -1,76 +1,128 @@
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using NadekoBot.Modules.Music; using NadekoBot.Modules.Music;
using NadekoBot.Modules.Music.Resolvers; using NadekoBot.Modules.Music.Resolvers;
using NadekoBot.Modules.Music.Services; using NadekoBot.Modules.Music.Services;
using Ninject;
using Ninject.Extensions.Conventions;
using StackExchange.Redis; using StackExchange.Redis;
using System.Net;
using System.Reflection; using System.Reflection;
namespace NadekoBot.Extensions; namespace NadekoBot.Extensions;
public static class ServiceCollectionExtensions public static class ServiceCollectionExtensions
{ {
public static IServiceCollection AddBotStringsServices(this IServiceCollection services, BotCacheImplemenation botCache) public static IKernel AddBotStringsServices(this IKernel kernel, BotCacheImplemenation botCache)
=> botCache == BotCacheImplemenation.Memory
? services.AddSingleton<IStringsSource, LocalFileStringsSource>()
.AddSingleton<IBotStringsProvider, MemoryBotStringsProvider>()
.AddSingleton<IBotStrings, BotStrings>()
: services.AddSingleton<IStringsSource, LocalFileStringsSource>()
.AddSingleton<IBotStringsProvider, RedisBotStringsProvider>()
.AddSingleton<IBotStrings, BotStrings>();
public static IServiceCollection AddConfigServices(this IServiceCollection services)
{ {
services.Scan(x => x.FromCallingAssembly() if (botCache == BotCacheImplemenation.Memory)
.AddClasses(f => f.AssignableTo(typeof(ConfigServiceBase<>))) {
.AsSelfWithInterfaces()); kernel.Bind<IStringsSource>().To<LocalFileStringsSource>().InSingletonScope();
kernel.Bind<IBotStringsProvider>().To<MemoryBotStringsProvider>().InSingletonScope();
kernel.Bind<IBotStrings>().To<BotStrings>().InSingletonScope();
}
else
{
kernel.Bind<IStringsSource>().To<LocalFileStringsSource>().InSingletonScope();
kernel.Bind<IBotStringsProvider>().To<RedisBotStringsProvider>().InSingletonScope();
kernel.Bind<IBotStrings>().To<BotStrings>().InSingletonScope();
}
return services; return kernel;
} }
public static IServiceCollection AddConfigMigrators(this IServiceCollection services) public static IKernel AddConfigServices(this IKernel kernel)
=> services.AddSealedSubclassesOf(typeof(IConfigMigrator));
public static IServiceCollection AddMusic(this IServiceCollection services)
=> services.AddSingleton<IMusicService, MusicService>()
.AddSingleton<ITrackResolveProvider, TrackResolveProvider>()
.AddSingleton<IYoutubeResolver, YtdlYoutubeResolver>()
.AddSingleton<ISoundcloudResolver, SoundcloudResolver>()
.AddSingleton<ILocalTrackResolver, LocalTrackResolver>()
.AddSingleton<IRadioResolver, RadioResolver>()
.AddSingleton<ITrackCacher, TrackCacher>()
.AddSingleton<YtLoader>()
.AddSingleton<IPlaceholderProvider>(svc => svc.GetRequiredService<IMusicService>());
// consider using scrutor, because slightly different versions
// of this might be needed in several different places
public static IServiceCollection AddSealedSubclassesOf(this IServiceCollection services, Type baseType)
{ {
var subTypes = Assembly.GetCallingAssembly() kernel.Bind(x =>
.ExportedTypes.Where(type => type.IsSealed && baseType.IsAssignableFrom(type)); {
var configs = x.FromThisAssembly()
.SelectAllClasses()
.Where(f => f.IsAssignableToGenericType(typeof(ConfigServiceBase<>)));
foreach (var subType in subTypes) // todo check for duplicates
services.AddSingleton(baseType, subType); configs.BindToSelf()
.Configure(c => c.InSingletonScope());
configs.BindAllInterfaces()
.Configure(c => c.InSingletonScope());
});
return services; return kernel;
} }
public static IServiceCollection AddCache(this IServiceCollection services, IBotCredentials creds) public static IKernel AddConfigMigrators(this IKernel kernel)
=> kernel.AddSealedSubclassesOf(typeof(IConfigMigrator));
public static IKernel AddMusic(this IKernel kernel)
{
kernel.Bind<IMusicService, IPlaceholderProvider>()
.To<MusicService>()
.InSingletonScope();
kernel.Bind<ITrackResolveProvider>().To<TrackResolveProvider>().InSingletonScope();
kernel.Bind<IYoutubeResolver>().To<YtdlYoutubeResolver>().InSingletonScope();
kernel.Bind<ISoundcloudResolver>().To<SoundcloudResolver>().InSingletonScope();
kernel.Bind<ILocalTrackResolver>().To<LocalTrackResolver>().InSingletonScope();
kernel.Bind<IRadioResolver>().To<RadioResolver>().InSingletonScope();
kernel.Bind<ITrackCacher>().To<TrackCacher>().InSingletonScope();
kernel.Bind<YtLoader>().ToSelf().InSingletonScope();
return kernel;
}
public static IKernel AddSealedSubclassesOf(this IKernel kernel, Type baseType)
{
kernel.Bind(x =>
{
var classes = x.FromThisAssembly()
.SelectAllClasses()
.Where(c => c.IsPublic && c.IsNested && baseType.IsAssignableFrom(baseType));
classes.BindAllInterfaces().Configure(x => x.InSingletonScope());
classes.BindToSelf().Configure(x => x.InSingletonScope());
});
return kernel;
}
public static IKernel AddCache(this IKernel kernel, IBotCredentials creds)
{ {
if (creds.BotCache == BotCacheImplemenation.Redis) if (creds.BotCache == BotCacheImplemenation.Redis)
{ {
var conf = ConfigurationOptions.Parse(creds.RedisOptions); var conf = ConfigurationOptions.Parse(creds.RedisOptions);
services.AddSingleton(ConnectionMultiplexer.Connect(conf)) kernel.Bind<ConnectionMultiplexer>().ToConstant(ConnectionMultiplexer.Connect(conf)).InSingletonScope();
.AddSingleton<IBotCache, RedisBotCache>() kernel.Bind<IBotCache>().To<RedisBotCache>().InSingletonScope();
.AddSingleton<IPubSub, RedisPubSub>(); kernel.Bind<IPubSub>().To<RedisPubSub>().InSingletonScope();
} }
else else
{ {
services.AddSingleton<IBotCache, MemoryBotCache>() kernel.Bind<IBotCache>().To<MemoryBotCache>().InSingletonScope();
.AddSingleton<IPubSub, EventPubSub>(); kernel.Bind<IPubSub>().To<EventPubSub>().InSingletonScope();
} }
return services return kernel
.AddBotStringsServices(creds.BotCache); .AddBotStringsServices(creds.BotCache);
} }
public static IKernel AddHttpClients(this IKernel kernel)
{
IServiceCollection svcs = new ServiceCollection();
svcs.AddHttpClient();
svcs.AddHttpClient("memelist")
.ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler
{
AllowAutoRedirect = false
});
svcs.AddHttpClient("google:search")
.ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler()
{
AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate
});
var prov = svcs.BuildServiceProvider();
kernel.Bind<IHttpClientFactory>().ToMethod(_ => prov.GetRequiredService<IHttpClientFactory>());
kernel.Bind<HttpClient>().ToMethod(_ => prov.GetRequiredService<HttpClient>());
return kernel;
}
} }

View File

@@ -4,7 +4,7 @@ version: 2
isEnabled: false isEnabled: false
# List of patron only features and relevant quota data # List of patron only features and relevant quota data
quotas: quotas:
# Dictionary of feature names with their respective limits. Set to null for unlimited # Dictionary of feature names with their respective limits. Set to null for unlimited
features: features:
timely:extra_percent: timely:extra_percent:
V: 10 V: 10