diff --git a/src/EllieBot/Bot.cs b/src/EllieBot/Bot.cs index d6ab91a..1ce8774 100644 --- a/src/EllieBot/Bot.cs +++ b/src/EllieBot/Bot.cs @@ -1,28 +1,26 @@ #nullable disable +using DryIoc; +using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.DependencyInjection; using EllieBot.Common.Configs; using EllieBot.Common.ModuleBehaviors; -using EllieBot.Db; -using EllieBot.Modules.Utility; -using EllieBot.Services.Database.Models; +using EllieBot.Db.Models; using System.Collections.Immutable; using System.Diagnostics; -using System.Net; using System.Reflection; using RunMode = Discord.Commands.RunMode; namespace EllieBot; -public sealed class Bot +public sealed class Bot : IBot { public event Func JoinedGuild = delegate { return Task.CompletedTask; }; public DiscordSocketClient Client { get; } - public ImmutableArray AllGuildConfigs { get; private set; } + public IReadOnlyCollection AllGuildConfigs { get; private set; } - private IServiceProvider Services { get; set; } + private IContainer Services { get; set; } - public string Mention { get; private set; } public bool IsReady { get; private set; } public int ShardId { get; set; } @@ -31,18 +29,19 @@ public sealed class Bot private readonly DbService _db; private readonly IBotCredsProvider _credsProvider; + + private readonly Assembly[] _loadedAssemblies; // private readonly InteractionService _interactionService; public Bot(int shardId, int? totalShards, string credPath = null) { - if (shardId < 0) - throw new ArgumentOutOfRangeException(nameof(shardId)); + ArgumentOutOfRangeException.ThrowIfLessThan(shardId, 0); ShardId = shardId; _credsProvider = new BotCredsProvider(totalShards, credPath); _creds = _credsProvider.GetCreds(); - _db = new(_credsProvider); + _db = new EllieDbService(_credsProvider); var messageCacheSize = #if GLOBAL_ELLIE @@ -51,9 +50,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, @@ -81,10 +80,14 @@ public sealed class Bot // _interactionService = new(Client.Rest); Client.Log += Client_Log; + _loadedAssemblies = + [ + typeof(Bot).Assembly // bot + ]; } - public List GetCurrentGuildIds() + public IReadOnlyList GetCurrentGuildIds() => Client.Guilds.Select(x => x.Id).ToList(); private void AddServices() @@ -96,120 +99,89 @@ public sealed class Bot using (var uow = _db.GetDbContext()) { uow.EnsureUserCreated(bot.Id, bot.Username, bot.Discriminator, bot.AvatarId); - AllGuildConfigs = uow.GuildConfigs.GetAllGuildConfigs(startingGuildIdList).ToImmutableArray(); + AllGuildConfigs = uow.Set().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() - .AddSingleton() - .AddConfigServices() - .AddConfigMigrators() - .AddMemoryCache() - // music - .AddMusic() - // cache - .AddCache(_creds); - + // var svcs = new StandardKernel(new NinjectSettings() + // { + // // ThrowOnGetServiceNotFound = true, + // ActivationCacheDisabled = true, + // }); - svcs.AddHttpClient(); - svcs.AddHttpClient("memelist") - .ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler - { - AllowAutoRedirect = false - }); - - svcs.AddHttpClient("google:search") - .ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler() - { - AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate - }); + var svcs = new Container(); - if (Environment.GetEnvironmentVariable("ELLIE_IS_COORDINATED") != "1") + // this is required in order for medusa unloading to work + // svcs.Components.Remove(); + // svcs.Components.Add(); + + svcs.AddSingleton(_ => _credsProvider.GetCreds()); + svcs.AddSingleton(_db); + svcs.AddSingleton(_credsProvider); + svcs.AddSingleton(Client); + svcs.AddSingleton(_commandService); + svcs.AddSingleton(this); + svcs.AddSingleton(this); + + svcs.AddSingleton(); + svcs.AddSingleton(); + svcs.AddSingleton(new MemoryCache(new MemoryCacheOptions())); + svcs.AddSingleton(); + svcs.AddSingleton(); + + + foreach (var a in _loadedAssemblies) + { + svcs.AddConfigServices(a) + .AddLifetimeServices(a); + } + + svcs.AddMusic() + .AddCache(_creds) + .AddHttpClients(); + + if (Environment.GetEnvironmentVariable("ELLIEBOT_IS_COORDINATED") != "1") + { svcs.AddSingleton(); + } else { - svcs.AddSingleton() - .AddSingleton(x => x.GetRequiredService()) - .AddSingleton(x => x.GetRequiredService()); + svcs.AddSingleton(); + svcs.AddSingleton(_ => svcs.GetRequiredService()); + svcs.AddSingleton(_ => svcs.GetRequiredService()); } - svcs.Scan(scan => scan.FromAssemblyOf() - .AddClasses(classes => classes.AssignableToAny( - // services - typeof(IEService), - - // behaviours - typeof(IExecOnMessage), - typeof(IInputTransformer), - typeof(IExecPreCommand), - typeof(IExecPostCommand), - typeof(IExecNoCommand)) - .WithoutAttribute() -#if GLOBAL_ELLIE - .WithoutAttribute() -#endif - ) - .AsSelfWithInterfaces() - .WithSingletonLifetime()); + svcs.AddSingleton(svcs); //initialize Services - Services = svcs.BuildServiceProvider(); + Services = svcs; Services.GetRequiredService().Initialize(); - Services.GetRequiredService(); - if (Client.ShardId == 0) - ApplyConfigMigrations(); - - _ = LoadTypeReaders(typeof(Bot).Assembly); + foreach (var a in _loadedAssemblies) + { + LoadTypeReaders(a); + } 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 LoadTypeReaders(Assembly assembly) { - // execute all migrators - var migrators = Services.GetServices(); - foreach (var migrator in migrators) - migrator.EnsureMigrated(); - } - - private IEnumerable LoadTypeReaders(Assembly assembly) - { - Type[] allTypes; - try - { - allTypes = assembly.GetTypes(); - } - catch (ReflectionTypeLoadException ex) - { - Log.Warning(ex.LoaderExceptions[0], "Error getting types"); - return Enumerable.Empty(); - } - - var filteredTypes = allTypes.Where(x => x.IsSubclassOf(typeof(TypeReader)) + var filteredTypes = assembly.GetExportedTypes() + .Where(x => x.IsSubclassOf(typeof(TypeReader)) && x.BaseType?.GetGenericArguments().Length > 0 && !x.IsAbstract); - var toReturn = new List(); foreach (var ft in filteredTypes) { - var x = (TypeReader)ActivatorUtilities.CreateInstance(Services, ft); var baseType = ft.BaseType; if (baseType is null) continue; - var typeArgs = baseType.GetGenericArguments(); - _commandService.AddTypeReader(typeArgs[0], x); - toReturn.Add(x); - } - return toReturn; + var typeReader = (TypeReader)ActivatorUtilities.CreateInstance(Services, ft); + var typeArgs = baseType.GetGenericArguments(); + _commandService.AddTypeReader(typeArgs[0], typeReader); + } } private async Task LoginAsync(string token) @@ -249,10 +221,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,12 +258,11 @@ public sealed class Bot { if (ShardId == 0) await _db.SetupAsync(); - + var sw = Stopwatch.StartNew(); await LoginAsync(_creds.Token); - Mention = Client.CurrentUser.Mention; Log.Information("Shard {ShardId} loading services...", Client.ShardId); try { @@ -310,7 +281,11 @@ public sealed class Bot // start handling messages received in commandhandler await commandHandler.StartHandling(); - await _commandService.AddModulesAsync(typeof(Bot).Assembly, Services); + foreach (var a in _loadedAssemblies) + { + await _commandService.AddModulesAsync(a, Services); + } + // await _interactionService.AddModulesAsync(typeof(Bot).Assembly, Services); IsReady = true; @@ -364,29 +339,30 @@ public sealed class Bot if (arg.Exception is { InnerException: WebSocketClosedException { CloseCode: 4014 } }) { - Log.Error(@" -Login failed. - -*** Please enable privileged intents *** - -Certain Ellie features require Discord's privileged gateway intents. -These include greeting and goodbye messages, as well as creating the Owner message channels for DM forwarding. - -How to enable privileged intents: -1. Head over to the Discord Developer Portal https://discord.com/developers/applications/ -2. Select your Application. -3. Click on `Bot` in the left side navigation panel, and scroll down to the intents section. -4. Enable all intents. -5. Restart your bot. - -Read this only if your bot is in 100 or more servers: - -You'll need to apply to use the intents with Discord, but for small selfhosts, all that is required is enabling the intents in the developer portal. -Yes, this is a new thing from Discord, as of October 2020. No, there's nothing we can do about it. Yes, we're aware it worked before. -While waiting for your bot to be accepted, you can change the 'usePrivilegedIntents' inside your creds.yml to 'false', although this will break many of the ellie's features"); + Log.Error(""" + Login failed. + + *** Please enable privileged intents *** + + Certain Ellie features require Discord's privileged gateway intents. + These include greeting and goodbye messages, as well as creating the Owner message channels for DM forwarding. + + How to enable privileged intents: + 1. Head over to the Discord Developer Portal https://discord.com/developers/applications/ + 2. Select your Application. + 3. Click on `Bot` in the left side navigation panel, and scroll down to the intents section. + 4. Enable all intents. + 5. Restart your bot. + + Read this only if your bot is in 100 or more servers: + + You'll need to apply to use the intents with Discord, but for small selfhosts, all that is required is enabling the intents in the developer portal. + Yes, this is a new thing from Discord, as of October 2020. No, there's nothing we can do about it. Yes, we're aware it worked before. + While waiting for your bot to be accepted, you can change the 'usePrivilegedIntents' inside your creds.yml to 'false', although this will break many of the ellie's features + """); return Task.CompletedTask; } - + #if GLOBAL_ELLIE || DEBUG if (arg.Exception is not null) Log.Warning(arg.Exception, "{ErrorSource} | {ErrorMessage}", arg.Source, arg.Message); diff --git a/src/EllieBot/Directory.Build.props b/src/EllieBot/Directory.Build.props deleted file mode 100644 index 1623cb0..0000000 --- a/src/EllieBot/Directory.Build.props +++ /dev/null @@ -1,7 +0,0 @@ - - - - all - - - \ No newline at end of file diff --git a/src/EllieBot/EllieBot.csproj b/src/EllieBot/EllieBot.csproj index 5f4dbb0..2c605d4 100644 --- a/src/EllieBot/EllieBot.csproj +++ b/src/EllieBot/EllieBot.csproj @@ -1,142 +1,143 @@  - - net6.0 - preview - enable - true - true + net8.0 + enable + true + en + 5.0.8 - - $(MSBuildProjectDirectory) - exe - ellie_icon.ico + + $(MSBuildProjectDirectory) + exe + ellie_icon.ico - - - - CS1066 - + + + + CS1066;CS8981 - - - all - True - - - - - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - + + true + embedded - - - - - - - - - - - - - - - - - - - - - - - - + - - all - True - + + + all + True + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + - + + + - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - + + - + + + + + + + - - - + + + + + + + + + + - - + + + + + + + - - - - - + - - - - - - - - + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + - - - - - - Protos\coordinator.proto - - - PreserveNewest - - - PreserveNewest - - - Always - - + - - 4.0.0 - $(VersionPrefix).$(VersionSuffix) - $(VersionPrefix) - + + + - - - false - GLOBAL_ELLIE - $(NoWarn);CS1573;CS1591 - true - portable - false - + + + + + + + + + + + + + + + + + + + + + + Protos\coordinator.proto + + + true + PreserveNewest + + + true + Always + + + true + Always + + + + + + false + GLOBAL_ELLIE + $(NoWarn);CS1573;CS1591 + true + portable + false +