mirror of
https://gitlab.com/Kwoth/nadekobot.git
synced 2025-09-10 17:28:27 -04:00
Moving to NInject, reimplemented most things. Some services aren't discovered properly yet
This commit is contained in:
@@ -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:
|
||||||
|
@@ -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);
|
||||||
|
50
src/NadekoBot/Common/Medusa/MedusaIoCKernelModule.cs
Normal file
50
src/NadekoBot/Common/Medusa/MedusaIoCKernelModule.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
@@ -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,
|
||||||
|
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
@@ -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!;
|
|
||||||
}
|
}
|
@@ -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" />
|
||||||
|
@@ -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;
|
||||||
|
|
||||||
|
23
src/NadekoBot/_Extensions/ReflectionExtensions.cs
Normal file
23
src/NadekoBot/_Extensions/ReflectionExtensions.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
@@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
@@ -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
|
||||||
|
Reference in New Issue
Block a user