mirror of
				https://gitlab.com/Kwoth/nadekobot.git
				synced 2025-11-04 00:34:26 -05:00 
			
		
		
		
	Moving to NInject, reimplemented most things. Some services aren't discovered properly yet
This commit is contained in:
		@@ -1,13 +1,16 @@
 | 
			
		||||
#nullable disable
 | 
			
		||||
using Microsoft.Extensions.Caching.Memory;
 | 
			
		||||
using Microsoft.Extensions.DependencyInjection;
 | 
			
		||||
using NadekoBot.Common.Configs;
 | 
			
		||||
using NadekoBot.Common.ModuleBehaviors;
 | 
			
		||||
using NadekoBot.Db;
 | 
			
		||||
using NadekoBot.Modules.Utility;
 | 
			
		||||
using NadekoBot.Services.Database.Models;
 | 
			
		||||
using Ninject;
 | 
			
		||||
using Ninject.Extensions.Conventions;
 | 
			
		||||
using Ninject.Infrastructure.Language;
 | 
			
		||||
using System.Collections.Immutable;
 | 
			
		||||
using System.Diagnostics;
 | 
			
		||||
using System.Net;
 | 
			
		||||
using System.Reflection;
 | 
			
		||||
using RunMode = Discord.Commands.RunMode;
 | 
			
		||||
 | 
			
		||||
@@ -20,7 +23,7 @@ public sealed class Bot
 | 
			
		||||
    public DiscordSocketClient Client { get; }
 | 
			
		||||
    public ImmutableArray<GuildConfig> AllGuildConfigs { get; private set; }
 | 
			
		||||
 | 
			
		||||
    private IServiceProvider Services { get; set; }
 | 
			
		||||
    private IKernel Services { get; set; }
 | 
			
		||||
 | 
			
		||||
    public string Mention { get; private set; }
 | 
			
		||||
    public bool IsReady { get; private set; }
 | 
			
		||||
@@ -51,9 +54,9 @@ public sealed class Bot
 | 
			
		||||
            50;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
        if(!_creds.UsePrivilegedIntents)
 | 
			
		||||
        if (!_creds.UsePrivilegedIntents)
 | 
			
		||||
            Log.Warning("You are not using privileged intents. Some features will not work properly");
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        Client = new(new()
 | 
			
		||||
        {
 | 
			
		||||
            MessageCacheSize = messageCacheSize,
 | 
			
		||||
@@ -99,67 +102,69 @@ public sealed class Bot
 | 
			
		||||
            AllGuildConfigs = uow.GuildConfigs.GetAllGuildConfigs(startingGuildIdList).ToImmutableArray();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var svcs = new ServiceCollection().AddTransient(_ => _credsProvider.GetCreds()) // bot creds
 | 
			
		||||
                                          .AddSingleton(_credsProvider)
 | 
			
		||||
                                          .AddSingleton(_db) // database
 | 
			
		||||
                                          .AddSingleton(Client) // discord socket client
 | 
			
		||||
                                          .AddSingleton(_commandService)
 | 
			
		||||
                                          // .AddSingleton(_interactionService)
 | 
			
		||||
                                          .AddSingleton(this)
 | 
			
		||||
                                          .AddSingleton<ISeria, JsonSeria>()
 | 
			
		||||
                                          .AddSingleton<IConfigSeria, YamlSeria>()
 | 
			
		||||
                                          .AddConfigServices()
 | 
			
		||||
                                          .AddConfigMigrators()
 | 
			
		||||
                                          .AddMemoryCache()
 | 
			
		||||
                                          // music
 | 
			
		||||
                                          .AddMusic()
 | 
			
		||||
                                          // cache
 | 
			
		||||
                                          .AddCache(_creds);
 | 
			
		||||
        
 | 
			
		||||
        var kernel = new StandardKernel();
 | 
			
		||||
 | 
			
		||||
        kernel.Bind<IBotCredentials>().ToMethod(_ => _credsProvider.GetCreds()).InTransientScope();
 | 
			
		||||
 | 
			
		||||
        kernel.Bind<IBotCredsProvider>().ToConstant(_credsProvider).InSingletonScope();
 | 
			
		||||
        kernel.Bind<DbService>().ToConstant(_db).InSingletonScope();
 | 
			
		||||
        kernel.Bind<DiscordSocketClient>().ToConstant(Client).InSingletonScope();
 | 
			
		||||
        kernel.Bind<CommandService>().ToConstant(_commandService).InSingletonScope();
 | 
			
		||||
        kernel.Bind<Bot>().ToConstant(this).InSingletonScope();
 | 
			
		||||
 | 
			
		||||
        kernel.Bind<ISeria>().To<JsonSeria>().InSingletonScope();
 | 
			
		||||
        kernel.Bind<IConfigSeria>().To<YamlSeria>().InSingletonScope();
 | 
			
		||||
        kernel.Bind<IMemoryCache>().ToConstant(new MemoryCache(new MemoryCacheOptions())).InSingletonScope();
 | 
			
		||||
 | 
			
		||||
        kernel.AddConfigServices()
 | 
			
		||||
              .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")
 | 
			
		||||
            svcs.AddSingleton<ICoordinator, SingleProcessCoordinator>();
 | 
			
		||||
        {
 | 
			
		||||
            kernel.Bind<ICoordinator>().To<SingleProcessCoordinator>().InSingletonScope();
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            svcs.AddSingleton<RemoteGrpcCoordinator>()
 | 
			
		||||
                .AddSingleton<ICoordinator>(x => x.GetRequiredService<RemoteGrpcCoordinator>())
 | 
			
		||||
                .AddSingleton<IReadyExecutor>(x => x.GetRequiredService<RemoteGrpcCoordinator>());
 | 
			
		||||
            kernel.Bind<ICoordinator, IReadyExecutor>().To<RemoteGrpcCoordinator>().InSingletonScope();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        svcs.Scan(scan => scan.FromAssemblyOf<IReadyExecutor>()
 | 
			
		||||
                              .AddClasses(classes => classes.AssignableToAny(
 | 
			
		||||
                                      // services
 | 
			
		||||
                                      typeof(INService),
 | 
			
		||||
 | 
			
		||||
                                      // behaviours
 | 
			
		||||
                                      typeof(IExecOnMessage),
 | 
			
		||||
                                      typeof(IInputTransformer),
 | 
			
		||||
                                      typeof(IExecPreCommand),
 | 
			
		||||
                                      typeof(IExecPostCommand),
 | 
			
		||||
                                      typeof(IExecNoCommand))
 | 
			
		||||
                                                .WithoutAttribute<DontAddToIocContainerAttribute>()
 | 
			
		||||
        kernel.Bind(scan =>
 | 
			
		||||
        {
 | 
			
		||||
            var classes = scan.FromThisAssembly()
 | 
			
		||||
                              .SelectAllClasses()
 | 
			
		||||
                              .Where(c => (c.IsAssignableTo(typeof(INService))
 | 
			
		||||
                                           || c.IsAssignableTo(typeof(IExecOnMessage))
 | 
			
		||||
                                           || c.IsAssignableTo(typeof(IInputTransformer))
 | 
			
		||||
                                           || c.IsAssignableTo(typeof(IExecPreCommand))
 | 
			
		||||
                                           || c.IsAssignableTo(typeof(IExecPostCommand))
 | 
			
		||||
                                           || c.IsAssignableTo(typeof(IExecNoCommand)))
 | 
			
		||||
                                          && !c.HasAttribute<DontAddToIocContainerAttribute>()
 | 
			
		||||
#if GLOBAL_NADEKO
 | 
			
		||||
                                                .WithoutAttribute<NoPublicBotAttribute>()
 | 
			
		||||
                                                && !c.HasAttribute<NoPublicBotAttribute>()
 | 
			
		||||
#endif
 | 
			
		||||
                              )
 | 
			
		||||
                              .AsSelfWithInterfaces()
 | 
			
		||||
                              .WithSingletonLifetime());
 | 
			
		||||
                              );
 | 
			
		||||
            classes
 | 
			
		||||
                .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
 | 
			
		||||
        Services = svcs.BuildServiceProvider();
 | 
			
		||||
        Services = kernel;
 | 
			
		||||
        Services.GetRequiredService<IBehaviorHandler>().Initialize();
 | 
			
		||||
        Services.GetRequiredService<CurrencyRewardService>();
 | 
			
		||||
 | 
			
		||||
@@ -169,7 +174,7 @@ public sealed class Bot
 | 
			
		||||
        _ = LoadTypeReaders(typeof(Bot).Assembly);
 | 
			
		||||
 | 
			
		||||
        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()
 | 
			
		||||
@@ -249,10 +254,10 @@ public sealed class Bot
 | 
			
		||||
            LoginErrorHandler.Handle(ex);
 | 
			
		||||
            Helpers.ReadErrorAndExit(4);
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        await clientReady.Task.ConfigureAwait(false);
 | 
			
		||||
        Client.Ready -= SetClientReady;
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        Client.JoinedGuild += Client_JoinedGuild;
 | 
			
		||||
        Client.LeftGuild += Client_LeftGuild;
 | 
			
		||||
 | 
			
		||||
@@ -286,7 +291,7 @@ public sealed class Bot
 | 
			
		||||
    {
 | 
			
		||||
        if (ShardId == 0)
 | 
			
		||||
            await _db.SetupAsync();
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        var sw = Stopwatch.StartNew();
 | 
			
		||||
 | 
			
		||||
        await LoginAsync(_creds.Token);
 | 
			
		||||
@@ -387,7 +392,7 @@ public sealed class Bot
 | 
			
		||||
                """);
 | 
			
		||||
            return Task.CompletedTask;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
#if GLOBAL_NADEKO || DEBUG
 | 
			
		||||
        if (arg.Exception is not null)
 | 
			
		||||
            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 Nadeko.Medusa.Adapters;
 | 
			
		||||
using NadekoBot.Common.ModuleBehaviors;
 | 
			
		||||
using Ninject;
 | 
			
		||||
using Ninject.Modules;
 | 
			
		||||
using System.Collections.Immutable;
 | 
			
		||||
using System.Diagnostics.CodeAnalysis;
 | 
			
		||||
using System.Globalization;
 | 
			
		||||
@@ -18,29 +20,31 @@ public sealed class MedusaLoaderService : IMedusaLoaderService, IReadyExecutor,
 | 
			
		||||
    private readonly IBehaviorHandler _behHandler;
 | 
			
		||||
    private readonly IPubSub _pubSub;
 | 
			
		||||
    private readonly IMedusaConfigService _medusaConfig;
 | 
			
		||||
    
 | 
			
		||||
    private readonly IKernel _kernel;
 | 
			
		||||
 | 
			
		||||
    private readonly ConcurrentDictionary<string, ResolvedMedusa> _resolved = new();
 | 
			
		||||
    private readonly SemaphoreSlim _lock = new SemaphoreSlim(1, 1);
 | 
			
		||||
 | 
			
		||||
    private readonly TypedKey<string> _loadKey = new("medusa:load");
 | 
			
		||||
    private readonly TypedKey<string> _unloadKey = new("medusa:unload");
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    private readonly TypedKey<bool> _stringsReload = new("medusa:reload_strings");
 | 
			
		||||
 | 
			
		||||
    private const string BASE_DIR = "data/medusae";
 | 
			
		||||
 | 
			
		||||
    public MedusaLoaderService(CommandService cmdService,
 | 
			
		||||
        IServiceProvider botServices,
 | 
			
		||||
    public MedusaLoaderService(
 | 
			
		||||
        CommandService cmdService,
 | 
			
		||||
        IKernel kernel,
 | 
			
		||||
        IBehaviorHandler behHandler,
 | 
			
		||||
        IPubSub pubSub,
 | 
			
		||||
        IMedusaConfigService medusaConfig)
 | 
			
		||||
    {
 | 
			
		||||
        _cmdService = cmdService;
 | 
			
		||||
        _botServices = botServices;
 | 
			
		||||
        _behHandler = behHandler;
 | 
			
		||||
        _pubSub = pubSub;
 | 
			
		||||
        _medusaConfig = medusaConfig;
 | 
			
		||||
        
 | 
			
		||||
        _kernel = kernel;
 | 
			
		||||
 | 
			
		||||
        // has to be done this way to support this feature on sharded bots
 | 
			
		||||
        _pubSub.Sub(_loadKey, async name => await InternalLoadAsync(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()));
 | 
			
		||||
                }
 | 
			
		||||
                
 | 
			
		||||
 | 
			
		||||
                sneks.Add(new SnekStats(snekInfos.Name, commands));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            toReturn.Add(new MedusaStats(name, resolvedData.Strings.GetDescription(culture), sneks));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return toReturn;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -88,9 +93,9 @@ public sealed class MedusaLoaderService : IMedusaLoaderService, IReadyExecutor,
 | 
			
		||||
        foreach (var name in _medusaConfig.GetLoadedMedusae())
 | 
			
		||||
        {
 | 
			
		||||
            var result = await InternalLoadAsync(name);
 | 
			
		||||
            if(result != MedusaLoadResult.Success)
 | 
			
		||||
            if (result != MedusaLoadResult.Success)
 | 
			
		||||
                Log.Warning("Unable to load '{MedusaName}' medusa", name);
 | 
			
		||||
            else 
 | 
			
		||||
            else
 | 
			
		||||
                Log.Warning("Loaded medusa '{MedusaName}'", name);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@@ -110,7 +115,7 @@ public sealed class MedusaLoaderService : IMedusaLoaderService, IReadyExecutor,
 | 
			
		||||
 | 
			
		||||
        return res;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    [MethodImpl(MethodImplOptions.NoInlining)]
 | 
			
		||||
    public async Task<MedusaUnloadResult> UnloadMedusaAsync(string medusaName)
 | 
			
		||||
    {
 | 
			
		||||
@@ -150,7 +155,7 @@ public sealed class MedusaLoaderService : IMedusaLoaderService, IReadyExecutor,
 | 
			
		||||
            resolved.Strings.Reload();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    private async Task ReloadStringsInternal()
 | 
			
		||||
    {
 | 
			
		||||
        await _lock.WaitAsync();
 | 
			
		||||
@@ -179,13 +184,13 @@ public sealed class MedusaLoaderService : IMedusaLoaderService, IReadyExecutor,
 | 
			
		||||
                      .Desc
 | 
			
		||||
               ?? string.Empty;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    [MethodImpl(MethodImplOptions.NoInlining)]
 | 
			
		||||
    private async ValueTask<MedusaLoadResult> InternalLoadAsync(string name)
 | 
			
		||||
    {
 | 
			
		||||
        if (_resolved.ContainsKey(name))
 | 
			
		||||
            return MedusaLoadResult.AlreadyLoaded;
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        var safeName = Uri.EscapeDataString(name);
 | 
			
		||||
 | 
			
		||||
        await _lock.WaitAsync();
 | 
			
		||||
@@ -194,7 +199,7 @@ public sealed class MedusaLoaderService : IMedusaLoaderService, IReadyExecutor,
 | 
			
		||||
            if (LoadAssemblyInternal(safeName,
 | 
			
		||||
                    out var ctx,
 | 
			
		||||
                    out var snekData,
 | 
			
		||||
                    out var services,
 | 
			
		||||
                    out var kernelModule,
 | 
			
		||||
                    out var strings,
 | 
			
		||||
                    out var typeReaders))
 | 
			
		||||
            {
 | 
			
		||||
@@ -213,7 +218,7 @@ public sealed class MedusaLoaderService : IMedusaLoaderService, IReadyExecutor,
 | 
			
		||||
                            await sub.Instance.InitializeAsync();
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        var module = await LoadModuleInternalAsync(name, point, strings, services);
 | 
			
		||||
                        var module = await LoadModuleInternalAsync(name, point, strings, kernelModule);
 | 
			
		||||
                        moduleInfos.Add(module);
 | 
			
		||||
                    }
 | 
			
		||||
                    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);
 | 
			
		||||
 | 
			
		||||
                _resolved[name] = new(LoadContext: ctx,
 | 
			
		||||
@@ -232,13 +237,10 @@ public sealed class MedusaLoaderService : IMedusaLoaderService, IReadyExecutor,
 | 
			
		||||
                    SnekInfos: snekData.ToImmutableArray(),
 | 
			
		||||
                    strings,
 | 
			
		||||
                    typeReaders,
 | 
			
		||||
                    execs)
 | 
			
		||||
                {
 | 
			
		||||
                    Services = services
 | 
			
		||||
                };
 | 
			
		||||
                    execs,
 | 
			
		||||
                    kernelModule);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
                
 | 
			
		||||
                services = null;
 | 
			
		||||
                _medusaConfig.AddLoadedMedusa(safeName);
 | 
			
		||||
                return MedusaLoadResult.Success;
 | 
			
		||||
            }
 | 
			
		||||
@@ -261,20 +263,22 @@ public sealed class MedusaLoaderService : IMedusaLoaderService, IReadyExecutor,
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    [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>();
 | 
			
		||||
        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)
 | 
			
		||||
            {
 | 
			
		||||
                behs.Add(new BehaviorAdapter(new(sub.Instance), strings, services));
 | 
			
		||||
                behs.Add(new BehaviorAdapter(new(sub.Instance), strings, _kernel));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        return behs;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -290,10 +294,10 @@ public sealed class MedusaLoaderService : IMedusaLoaderService, IReadyExecutor,
 | 
			
		||||
                notAddedTypeReaders.Add(type);
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
            _cmdService.AddTypeReader(type, typeReader);
 | 
			
		||||
        }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
        // remove the ones that were not added
 | 
			
		||||
        // to prevent them from being unloaded later
 | 
			
		||||
        // as they didn't come from this medusa
 | 
			
		||||
@@ -308,56 +312,61 @@ public sealed class MedusaLoaderService : IMedusaLoaderService, IReadyExecutor,
 | 
			
		||||
        string safeName,
 | 
			
		||||
        [NotNullWhen(true)] out WeakReference<MedusaAssemblyLoadContext>? ctxWr,
 | 
			
		||||
        [NotNullWhen(true)] out IReadOnlyCollection<SnekInfo>? snekData,
 | 
			
		||||
        out IServiceProvider services,
 | 
			
		||||
        [NotNullWhen(true)] out INinjectModule? ninjectModule,
 | 
			
		||||
        out IMedusaStrings strings,
 | 
			
		||||
        out Dictionary<Type, TypeReader> typeReaders)
 | 
			
		||||
    {
 | 
			
		||||
        ctxWr = null;
 | 
			
		||||
        snekData = null;
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        var path = $"{BASE_DIR}/{safeName}/{safeName}.dll";
 | 
			
		||||
        strings = MedusaStrings.CreateDefault($"{BASE_DIR}/{safeName}");
 | 
			
		||||
        var ctx = new MedusaAssemblyLoadContext(Path.GetDirectoryName(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)
 | 
			
		||||
        {
 | 
			
		||||
            _kernel.Unload(safeName);
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        ctxWr = new(ctx);
 | 
			
		||||
        snekData = sis;
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static readonly Type _paramParserType = typeof(ParamParser<>);
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    [MethodImpl(MethodImplOptions.NoInlining)]
 | 
			
		||||
    private Dictionary<Type, TypeReader> LoadTypeReadersFromAssembly(
 | 
			
		||||
        Assembly assembly,
 | 
			
		||||
        IMedusaStrings strings,
 | 
			
		||||
        IServiceProvider services)
 | 
			
		||||
        IMedusaStrings strings)
 | 
			
		||||
    {
 | 
			
		||||
        var paramParsers = assembly.GetExportedTypes()
 | 
			
		||||
                .Where(x => x.IsClass
 | 
			
		||||
                            && !x.IsAbstract
 | 
			
		||||
                            && x.BaseType is not null
 | 
			
		||||
                            && x.BaseType.IsGenericType
 | 
			
		||||
                            && x.BaseType.GetGenericTypeDefinition() == _paramParserType);
 | 
			
		||||
                                   .Where(x => x.IsClass
 | 
			
		||||
                                               && !x.IsAbstract
 | 
			
		||||
                                               && x.BaseType is not null
 | 
			
		||||
                                               && x.BaseType.IsGenericType
 | 
			
		||||
                                               && x.BaseType.GetGenericTypeDefinition() == _paramParserType);
 | 
			
		||||
 | 
			
		||||
        var typeReaders = new Dictionary<Type, TypeReader>();
 | 
			
		||||
        foreach (var parserType in paramParsers)
 | 
			
		||||
        {
 | 
			
		||||
            var parserObj = ActivatorUtilities.CreateInstance(services, parserType);
 | 
			
		||||
            var parserObj = ActivatorUtilities.CreateInstance(_kernel, parserType);
 | 
			
		||||
 | 
			
		||||
            var targetType = parserType.BaseType!.GetGenericArguments()[0];
 | 
			
		||||
            var typeReaderInstance = (TypeReader)Activator.CreateInstance(
 | 
			
		||||
                typeof(ParamParserAdapter<>).MakeGenericType(targetType),
 | 
			
		||||
                args: new[] { parserObj, strings, services })!;
 | 
			
		||||
            
 | 
			
		||||
                args: new[] { parserObj, strings, _kernel })!;
 | 
			
		||||
 | 
			
		||||
            typeReaders.Add(targetType, typeReaderInstance);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -365,11 +374,15 @@ public sealed class MedusaLoaderService : IMedusaLoaderService, IReadyExecutor,
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    [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,
 | 
			
		||||
            CreateModuleFactory(medusaName, snekInfo, strings, services));
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        return module;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -378,7 +391,7 @@ public sealed class MedusaLoaderService : IMedusaLoaderService, IReadyExecutor,
 | 
			
		||||
        string medusaName,
 | 
			
		||||
        SnekInfo snekInfo,
 | 
			
		||||
        IMedusaStrings strings,
 | 
			
		||||
        IServiceProvider medusaServices)
 | 
			
		||||
        INinjectModule kernelModule)
 | 
			
		||||
        => mb =>
 | 
			
		||||
        {
 | 
			
		||||
            var m = mb.WithName(snekInfo.Name);
 | 
			
		||||
@@ -394,17 +407,17 @@ public sealed class MedusaLoaderService : IMedusaLoaderService, IReadyExecutor,
 | 
			
		||||
                    CreateCallback(cmd.ContextType,
 | 
			
		||||
                        new(snekInfo),
 | 
			
		||||
                        new(cmd),
 | 
			
		||||
                        new(medusaServices),
 | 
			
		||||
                        strings),
 | 
			
		||||
                    CreateCommandFactory(medusaName, cmd, strings));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            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 _reqDm = new RequireContextAttribute(ContextType.DM);
 | 
			
		||||
 | 
			
		||||
    private Action<CommandBuilder> CreateCommandFactory(string medusaName, SnekCommandData cmd, IMedusaStrings strings)
 | 
			
		||||
        => (cb) =>
 | 
			
		||||
        {
 | 
			
		||||
@@ -414,7 +427,7 @@ public sealed class MedusaLoaderService : IMedusaLoaderService, IReadyExecutor,
 | 
			
		||||
                cb.AddPrecondition(_reqGuild);
 | 
			
		||||
            else if (cmd.ContextType == CommandContextType.Dm)
 | 
			
		||||
                cb.AddPrecondition(_reqDm);
 | 
			
		||||
            
 | 
			
		||||
 | 
			
		||||
            foreach (var f in cmd.Filters)
 | 
			
		||||
                cb.AddPrecondition(new FilterAdapter(f, strings));
 | 
			
		||||
 | 
			
		||||
@@ -441,12 +454,12 @@ public sealed class MedusaLoaderService : IMedusaLoaderService, IReadyExecutor,
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            cb.WithPriority(cmd.Priority);
 | 
			
		||||
            
 | 
			
		||||
 | 
			
		||||
            // using summary to save method name
 | 
			
		||||
            // method name is used to retrieve desc/usages
 | 
			
		||||
            cb.WithRemarks($"medusa///{medusaName}");
 | 
			
		||||
            cb.WithSummary(cmd.MethodInfo.Name.ToLowerInvariant());
 | 
			
		||||
            
 | 
			
		||||
 | 
			
		||||
            foreach (var param in cmd.Parameters)
 | 
			
		||||
            {
 | 
			
		||||
                cb.AddParameter(param.Name, param.Type, CreateParamFactory(param));
 | 
			
		||||
@@ -466,19 +479,21 @@ public sealed class MedusaLoaderService : IMedusaLoaderService, IReadyExecutor,
 | 
			
		||||
        CommandContextType contextType,
 | 
			
		||||
        WeakReference<SnekInfo> snekDataWr,
 | 
			
		||||
        WeakReference<SnekCommandData> snekCommandDataWr,
 | 
			
		||||
        WeakReference<IServiceProvider> medusaServicesWr,
 | 
			
		||||
        IMedusaStrings strings)
 | 
			
		||||
        => async (context, parameters, svcs, _) =>
 | 
			
		||||
        => async (
 | 
			
		||||
            context,
 | 
			
		||||
            parameters,
 | 
			
		||||
            svcs,
 | 
			
		||||
            _) =>
 | 
			
		||||
        {
 | 
			
		||||
            if (!snekCommandDataWr.TryGetTarget(out var cmdData)
 | 
			
		||||
                || !snekDataWr.TryGetTarget(out var snekData)
 | 
			
		||||
                || !medusaServicesWr.TryGetTarget(out var medusaServices))
 | 
			
		||||
                || !snekDataWr.TryGetTarget(out var snekData))
 | 
			
		||||
            {
 | 
			
		||||
                Log.Warning("Attempted to run an unloaded snek's command");
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
                
 | 
			
		||||
            var paramObjs = ParamObjs(contextType, cmdData, parameters, context, svcs, medusaServices, strings);
 | 
			
		||||
 | 
			
		||||
            var paramObjs = ParamObjs(contextType, cmdData, parameters, context, svcs, _kernel, strings);
 | 
			
		||||
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
@@ -502,9 +517,8 @@ public sealed class MedusaLoaderService : IMedusaLoaderService, IReadyExecutor,
 | 
			
		||||
            {
 | 
			
		||||
                paramObjs = null;
 | 
			
		||||
                cmdData = null;
 | 
			
		||||
                
 | 
			
		||||
 | 
			
		||||
                snekData = null;
 | 
			
		||||
                medusaServices = null;
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
@@ -548,7 +562,7 @@ public sealed class MedusaLoaderService : IMedusaLoaderService, IReadyExecutor,
 | 
			
		||||
 | 
			
		||||
        for (var i = 0; i < parameters.Length; i++)
 | 
			
		||||
            paramObjs[startAt + i] = parameters[i];
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        return paramObjs;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -569,17 +583,14 @@ public sealed class MedusaLoaderService : IMedusaLoaderService, IReadyExecutor,
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            await _behHandler.RemoveRangeAsync(lsi.Execs);
 | 
			
		||||
            
 | 
			
		||||
 | 
			
		||||
            await DisposeSnekInstances(lsi);
 | 
			
		||||
 | 
			
		||||
            var lc = lsi.LoadContext;
 | 
			
		||||
 | 
			
		||||
            // removing this line will prevent assembly from being unloaded quickly
 | 
			
		||||
            // as this local variable will be held for a long time potentially
 | 
			
		||||
            // due to how async works
 | 
			
		||||
            lsi.Services = null!;
 | 
			
		||||
            // lsi.KernelModule = null!;
 | 
			
		||||
            lsi = null;
 | 
			
		||||
            
 | 
			
		||||
 | 
			
		||||
            _medusaConfig.RemoveLoadedMedusa(name);
 | 
			
		||||
            return UnloadInternal(lc)
 | 
			
		||||
                ? MedusaUnloadResult.Success
 | 
			
		||||
@@ -610,7 +621,7 @@ public sealed class MedusaLoaderService : IMedusaLoaderService, IReadyExecutor,
 | 
			
		||||
                foreach (var sub in si.Subsneks)
 | 
			
		||||
                {
 | 
			
		||||
                    await sub.Instance.DisposeAsync();
 | 
			
		||||
                }  
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            catch (Exception ex)
 | 
			
		||||
            {
 | 
			
		||||
@@ -635,10 +646,10 @@ public sealed class MedusaLoaderService : IMedusaLoaderService, IReadyExecutor,
 | 
			
		||||
    [MethodImpl(MethodImplOptions.NoInlining)]
 | 
			
		||||
    private void UnloadContext(WeakReference<MedusaAssemblyLoadContext> lsiLoadContext)
 | 
			
		||||
    {
 | 
			
		||||
        if(lsiLoadContext.TryGetTarget(out var ctx))
 | 
			
		||||
        if (lsiLoadContext.TryGetTarget(out var ctx))
 | 
			
		||||
            ctx.Unload();
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    private void GcCleanup()
 | 
			
		||||
    {
 | 
			
		||||
        // cleanup
 | 
			
		||||
@@ -653,25 +664,14 @@ public sealed class MedusaLoaderService : IMedusaLoaderService, IReadyExecutor,
 | 
			
		||||
 | 
			
		||||
    private static readonly Type _snekType = typeof(Snek);
 | 
			
		||||
 | 
			
		||||
    [MethodImpl(MethodImplOptions.NoInlining)]
 | 
			
		||||
    private IServiceProvider LoadMedusaServicesInternal(Assembly a)
 | 
			
		||||
        => new ServiceCollection()
 | 
			
		||||
           .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)]
 | 
			
		||||
    // private MedusaIoCKernelModule LoadMedusaServicesInternal(string name, Assembly a)
 | 
			
		||||
    //     => new MedusaIoCKernelModule(name, a);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    [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
 | 
			
		||||
        var types = a.GetExportedTypes();
 | 
			
		||||
        // snek is always a public non abstract class
 | 
			
		||||
@@ -683,14 +683,14 @@ public sealed class MedusaLoaderService : IMedusaLoaderService, IReadyExecutor,
 | 
			
		||||
                           .ToList();
 | 
			
		||||
 | 
			
		||||
        var topModules = new Dictionary<Type, SnekInfo>();
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        foreach (var cl in classes)
 | 
			
		||||
        {
 | 
			
		||||
            if (cl.DeclaringType is not null)
 | 
			
		||||
                continue;
 | 
			
		||||
            
 | 
			
		||||
 | 
			
		||||
            // get module data, and add it to the topModules dictionary
 | 
			
		||||
            var module = GetModuleData(cl, services);
 | 
			
		||||
            var module = GetModuleData(cl);
 | 
			
		||||
            topModules.Add(cl, module);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -708,21 +708,21 @@ public sealed class MedusaLoaderService : IMedusaLoaderService, IReadyExecutor,
 | 
			
		||||
                    dt.Name);
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            GetModuleData(c, services, parentData);
 | 
			
		||||
 | 
			
		||||
            GetModuleData(c, parentData);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return topModules.Values.ToArray();
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    [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)
 | 
			
		||||
                          .ToArray();
 | 
			
		||||
        
 | 
			
		||||
        var instance = (Snek)ActivatorUtilities.CreateInstance(services, type);
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        var instance = (Snek)ActivatorUtilities.CreateInstance(_kernel, type);
 | 
			
		||||
 | 
			
		||||
        var module = new SnekInfo(instance.Name,
 | 
			
		||||
            parentData,
 | 
			
		||||
            instance,
 | 
			
		||||
@@ -744,7 +744,7 @@ public sealed class MedusaLoaderService : IMedusaLoaderService, IReadyExecutor,
 | 
			
		||||
                                      | BindingFlags.Public)
 | 
			
		||||
                          .Where(static x =>
 | 
			
		||||
                          {
 | 
			
		||||
                              if(x.GetCustomAttribute<cmdAttribute>(true) is null)
 | 
			
		||||
                              if (x.GetCustomAttribute<cmdAttribute>(true) is null)
 | 
			
		||||
                                  return false;
 | 
			
		||||
 | 
			
		||||
                              if (x.ReturnType.IsGenericType)
 | 
			
		||||
@@ -752,14 +752,14 @@ public sealed class MedusaLoaderService : IMedusaLoaderService, IReadyExecutor,
 | 
			
		||||
                                  var genericType = x.ReturnType.GetGenericTypeDefinition();
 | 
			
		||||
                                  if (genericType == typeof(Task<>))
 | 
			
		||||
                                      return true;
 | 
			
		||||
                              
 | 
			
		||||
 | 
			
		||||
                                  // if (genericType == typeof(ValueTask<>))
 | 
			
		||||
                                  //     return true;
 | 
			
		||||
 | 
			
		||||
                                  Log.Warning("Method {MethodName} has an invalid return type: {ReturnType}",
 | 
			
		||||
                                      x.Name,
 | 
			
		||||
                                      x.ReturnType);
 | 
			
		||||
                                  
 | 
			
		||||
 | 
			
		||||
                                  return false;
 | 
			
		||||
                              }
 | 
			
		||||
 | 
			
		||||
@@ -776,8 +776,8 @@ public sealed class MedusaLoaderService : IMedusaLoaderService, IReadyExecutor,
 | 
			
		||||
 | 
			
		||||
                              return succ;
 | 
			
		||||
                          });
 | 
			
		||||
        
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        var cmds = new List<SnekCommandData>();
 | 
			
		||||
        foreach (var method in methodInfos)
 | 
			
		||||
        {
 | 
			
		||||
@@ -808,20 +808,22 @@ public sealed class MedusaLoaderService : IMedusaLoaderService, IReadyExecutor,
 | 
			
		||||
                if (isContext)
 | 
			
		||||
                {
 | 
			
		||||
                    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)
 | 
			
		||||
                        throw new ArgumentException($"IContext parameter has to be first. {GetErrorPath(method, pi)}");
 | 
			
		||||
 | 
			
		||||
                    canInject = true;
 | 
			
		||||
                    
 | 
			
		||||
 | 
			
		||||
                    if (paramType.IsAssignableTo(typeof(GuildContext)))
 | 
			
		||||
                        cmdContext = CommandContextType.Guild;
 | 
			
		||||
                    else if (paramType.IsAssignableTo(typeof(DmContext)))
 | 
			
		||||
                        cmdContext = CommandContextType.Dm;
 | 
			
		||||
                    else
 | 
			
		||||
                        cmdContext = CommandContextType.Any;
 | 
			
		||||
                    
 | 
			
		||||
 | 
			
		||||
                    continue;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
@@ -831,7 +833,7 @@ public sealed class MedusaLoaderService : IMedusaLoaderService, IReadyExecutor,
 | 
			
		||||
                        throw new ArgumentException($"Parameters marked as [Injected] have to come after IContext");
 | 
			
		||||
 | 
			
		||||
                    canInject = true;
 | 
			
		||||
                    
 | 
			
		||||
 | 
			
		||||
                    diParams.Add(paramType);
 | 
			
		||||
                    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;
 | 
			
		||||
            if (aliases.Length == 0)
 | 
			
		||||
                aliases = new[] { method.Name.ToLowerInvariant() };
 | 
			
		||||
            
 | 
			
		||||
 | 
			
		||||
            cmds.Add(new(
 | 
			
		||||
                aliases,
 | 
			
		||||
                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;
 | 
			
		||||
 | 
			
		||||
@@ -8,7 +9,7 @@ public sealed record ResolvedMedusa(
 | 
			
		||||
    IImmutableList<SnekInfo> SnekInfos,
 | 
			
		||||
    IMedusaStrings Strings,
 | 
			
		||||
    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="MorseCode.ITask" Version="2.0.3" />
 | 
			
		||||
        <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="Scrutor" Version="4.2.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.Debug" Version="7.0.0" />
 | 
			
		||||
@@ -56,7 +62,6 @@
 | 
			
		||||
        <PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
 | 
			
		||||
        <PackageReference Include="NonBlocking" Version="2.1.1" />
 | 
			
		||||
        <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.Seq" Version="5.2.2" />
 | 
			
		||||
        <PackageReference Include="SixLabors.Fonts" Version="1.0.0-beta17" />
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,7 @@
 | 
			
		||||
#nullable disable
 | 
			
		||||
using Microsoft.Extensions.DependencyInjection;
 | 
			
		||||
using NadekoBot.Common.ModuleBehaviors;
 | 
			
		||||
using Ninject;
 | 
			
		||||
 | 
			
		||||
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.Options;
 | 
			
		||||
using NadekoBot.Modules.Music;
 | 
			
		||||
using NadekoBot.Modules.Music.Resolvers;
 | 
			
		||||
using NadekoBot.Modules.Music.Services;
 | 
			
		||||
using Ninject;
 | 
			
		||||
using Ninject.Extensions.Conventions;
 | 
			
		||||
using StackExchange.Redis;
 | 
			
		||||
using System.Net;
 | 
			
		||||
using System.Reflection;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Extensions;
 | 
			
		||||
 | 
			
		||||
public static class ServiceCollectionExtensions
 | 
			
		||||
{
 | 
			
		||||
    public static IServiceCollection AddBotStringsServices(this IServiceCollection services, 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)
 | 
			
		||||
    public static IKernel AddBotStringsServices(this IKernel kernel, BotCacheImplemenation botCache)
 | 
			
		||||
    {
 | 
			
		||||
        services.Scan(x => x.FromCallingAssembly()
 | 
			
		||||
                            .AddClasses(f => f.AssignableTo(typeof(ConfigServiceBase<>)))
 | 
			
		||||
                            .AsSelfWithInterfaces());
 | 
			
		||||
        if (botCache == BotCacheImplemenation.Memory)
 | 
			
		||||
        {
 | 
			
		||||
            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)
 | 
			
		||||
        => 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)
 | 
			
		||||
    public static IKernel AddConfigServices(this IKernel kernel)
 | 
			
		||||
    {
 | 
			
		||||
        var subTypes = Assembly.GetCallingAssembly()
 | 
			
		||||
                               .ExportedTypes.Where(type => type.IsSealed && baseType.IsAssignableFrom(type));
 | 
			
		||||
        kernel.Bind(x =>
 | 
			
		||||
        {
 | 
			
		||||
            var configs = x.FromThisAssembly()
 | 
			
		||||
                           .SelectAllClasses()
 | 
			
		||||
                           .Where(f => f.IsAssignableToGenericType(typeof(ConfigServiceBase<>)));
 | 
			
		||||
 | 
			
		||||
        foreach (var subType in subTypes)
 | 
			
		||||
            services.AddSingleton(baseType, subType);
 | 
			
		||||
            // todo check for duplicates
 | 
			
		||||
            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)
 | 
			
		||||
        {
 | 
			
		||||
            var conf = ConfigurationOptions.Parse(creds.RedisOptions);
 | 
			
		||||
            services.AddSingleton(ConnectionMultiplexer.Connect(conf))
 | 
			
		||||
                    .AddSingleton<IBotCache, RedisBotCache>()
 | 
			
		||||
                    .AddSingleton<IPubSub, RedisPubSub>();
 | 
			
		||||
            kernel.Bind<ConnectionMultiplexer>().ToConstant(ConnectionMultiplexer.Connect(conf)).InSingletonScope();
 | 
			
		||||
            kernel.Bind<IBotCache>().To<RedisBotCache>().InSingletonScope();
 | 
			
		||||
            kernel.Bind<IPubSub>().To<RedisPubSub>().InSingletonScope();
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            services.AddSingleton<IBotCache, MemoryBotCache>()
 | 
			
		||||
                    .AddSingleton<IPubSub, EventPubSub>();
 | 
			
		||||
 | 
			
		||||
            kernel.Bind<IBotCache>().To<MemoryBotCache>().InSingletonScope();
 | 
			
		||||
            kernel.Bind<IPubSub>().To<EventPubSub>().InSingletonScope();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return services
 | 
			
		||||
        return kernel
 | 
			
		||||
            .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
 | 
			
		||||
# List of patron only features and relevant quota data
 | 
			
		||||
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:
 | 
			
		||||
    timely:extra_percent:
 | 
			
		||||
      V: 10
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user