diff --git a/DisCatSharp.Hosting.DependencyInjection/DisCatSharp.Hosting.DependencyInjection.csproj b/DisCatSharp.Hosting.DependencyInjection/DisCatSharp.Hosting.DependencyInjection.csproj
new file mode 100644
index 000000000..0a951bfe3
--- /dev/null
+++ b/DisCatSharp.Hosting.DependencyInjection/DisCatSharp.Hosting.DependencyInjection.csproj
@@ -0,0 +1,16 @@
+
+
+
+ net5.0
+ enable
+
+
+
+
+
+
+
+
+
+
+
diff --git a/DisCatSharp.Hosting.DependencyInjection/ServiceCollectionExtensions.cs b/DisCatSharp.Hosting.DependencyInjection/ServiceCollectionExtensions.cs
new file mode 100644
index 000000000..fbb58b3be
--- /dev/null
+++ b/DisCatSharp.Hosting.DependencyInjection/ServiceCollectionExtensions.cs
@@ -0,0 +1,42 @@
+using Microsoft.Extensions.DependencyInjection;
+
+namespace DisCatSharp.Hosting.DependencyInjection
+{
+ public static class ServiceCollectionExtensions
+ {
+ ///
+ /// Add as a background service
+ ///
+ ///
+ /// is scoped to .
+ /// Maps to implementation of
+ ///
+ ///
+ ///
+ public static IServiceCollection AddDiscordHostedService(this IServiceCollection services)
+ {
+ services.AddSingleton();
+ services.AddHostedService(provider => provider.GetRequiredService());
+
+ return services;
+ }
+
+ ///
+ /// Add as a background service
+ ///
+ ///
+ /// is scoped to .
+ /// Maps to Implementation of
+ ///
+ ///
+ ///
+ ///
+ public static IServiceCollection AddDiscordHostedService(this IServiceCollection services)
+ where TService : class, IDiscordHostedService
+ {
+ services.AddSingleton();
+ services.AddHostedService(provider => provider.GetRequiredService());
+ return services;
+ }
+ }
+}
diff --git a/DisCatSharp.Hosting.Tests/DisCatSharp.Hosting.Tests.csproj b/DisCatSharp.Hosting.Tests/DisCatSharp.Hosting.Tests.csproj
new file mode 100644
index 000000000..1a4e43783
--- /dev/null
+++ b/DisCatSharp.Hosting.Tests/DisCatSharp.Hosting.Tests.csproj
@@ -0,0 +1,31 @@
+
+
+
+ net5.0
+ enable
+
+ false
+
+
+
+
+
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+
+
+
+
+
+
+
+
+
diff --git a/DisCatSharp.Hosting.Tests/ExtensionTests.cs b/DisCatSharp.Hosting.Tests/ExtensionTests.cs
new file mode 100644
index 000000000..0cf6cb951
--- /dev/null
+++ b/DisCatSharp.Hosting.Tests/ExtensionTests.cs
@@ -0,0 +1,108 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.Extensions.Configuration;
+using Xunit;
+using DisCatSharp.Interactivity;
+using DisCatSharp.Lavalink;
+
+namespace DisCatSharp.Hosting.Tests
+{
+ public class UnitTest1
+ {
+ #region Reference to external assemblies - required to ensure they're loaded
+ private InteractivityConfiguration interactivityConfig = null;
+ private LavalinkConfiguration lavalinkConfig = null;
+ private DiscordConfiguration discordConfig = null;
+ #endregion
+
+ private Dictionary DefaultDiscord() =>
+ new()
+ {
+ { "DisCatSharp:Discord:Token", "1234567890" },
+ { "DisCatSharp:Discord:TokenType", "Bot" },
+ { "DisCatSharp:Discord:MinimumLogLevel", "Information" },
+ { "DisCatSharp:Discord:UseRelativeRateLimit", "true" },
+ { "DisCatSharp:Discord:LogTimestampFormat", "yyyy-MM-dd HH:mm:ss zzz" },
+ { "DisCatSharp:Discord:LargeThreshold", "250" },
+ { "DisCatSharp:Discord:AutoReconnect", "true" },
+ { "DisCatSharp:Discord:ShardId", "123123" },
+ { "DisCatSharp:Discord:GatewayCompressionLevel", "Stream" },
+ { "DisCatSharp:Discord:MessageCacheSize", "1024" },
+ { "DisCatSharp:Discord:HttpTimeout", "00:00:20" },
+ { "DisCatSharp:Discord:ReconnectIndefinitely", "false" },
+ { "DisCatSharp:Discord:AlwaysCacheMembers", "true" },
+ { "DisCatSharp:Discord:DiscordIntents", "AllUnprivileged" },
+ { "DisCatSharp:Discord:MobileStatus", "false" },
+ { "DisCatSharp:Discord:UseCanary", "false" },
+ { "DisCatSharp:Discord:AutoRefreshChannelCache", "false" },
+ { "DisCatSharp:Discord:Intents", "AllUnprivileged" }
+ };
+
+ public IConfiguration DiscordInteractivityConfiguration() => new ConfigurationBuilder()
+ .AddInMemoryCollection(new Dictionary(this.DefaultDiscord())
+ {
+ {"DisCatSharp:Interactivity", ""} // this should be enough to automatically add the extension
+ })
+ .Build();
+
+ public IConfiguration DiscordOnlyConfiguration() => new ConfigurationBuilder()
+ .AddInMemoryCollection(this.DefaultDiscord())
+ .Build();
+
+ public IConfiguration DiscordInteractivityAndLavaLinkConfiguration() => new ConfigurationBuilder()
+ .AddInMemoryCollection(new Dictionary(this.DefaultDiscord())
+ {
+ { "DisCatSharp:Interactivity", "" }, { "DisCatSharp:LavaLink", "" }
+ })
+ .Build();
+
+ [Fact]
+ public void HasSection_Tests()
+ {
+ var source = this.DiscordInteractivityConfiguration();
+
+ bool hasDisCatSharp = source.HasSection("DisCatSharp");
+ Assert.True(hasDisCatSharp);
+
+ bool hasDiscord = source.HasSection("DisCatSharp", "Discord");
+ Assert.True(hasDiscord);
+ }
+
+ [Fact]
+ public void DiscoverExtensions_Interactivity()
+ {
+ var source = this.DiscordInteractivityConfiguration();
+ var discovered = source.FindImplementedExtensions();
+
+ // Remember that DiscordConfiguration does not have an implementation type which is assignable to BaseExtension
+ Assert.Single(discovered);
+ var item = discovered.First();
+
+ Assert.Equal(typeof(InteractivityConfiguration), item.Value.ConfigType);
+ Assert.Equal(typeof(InteractivityExtension), item.Value.ImplementationType);
+ Assert.Equal("InteractivityExtension", item.Key);
+ }
+
+ [Fact]
+ public void DiscoverExtensions_InteractivityAndLavaLink()
+ {
+ var source = this.DiscordInteractivityAndLavaLinkConfiguration();
+ var discovered = source.FindImplementedExtensions();
+
+ Assert.Equal(2, discovered.Count);
+ var first = discovered.First();
+ var last = discovered.Last();
+
+ Assert.Equal(typeof(InteractivityConfiguration), first.Value.ConfigType);
+ Assert.Equal(typeof(InteractivityExtension), first.Value.ImplementationType);
+ Assert.True("InteractivityExtension".Equals(first.Key, StringComparison.OrdinalIgnoreCase));
+
+ Assert.Equal(typeof(LavalinkConfiguration), last.Value.ConfigType);
+ Assert.Equal(typeof(LavalinkExtension), last.Value.ImplementationType);
+ Assert.True("LavalinkExtension".Equals(last.Key, StringComparison.OrdinalIgnoreCase));
+ }
+ }
+}
+
+
diff --git a/DisCatSharp.Hosting.Tests/HostTests.cs b/DisCatSharp.Hosting.Tests/HostTests.cs
new file mode 100644
index 000000000..5dc5a3804
--- /dev/null
+++ b/DisCatSharp.Hosting.Tests/HostTests.cs
@@ -0,0 +1,145 @@
+// This file is part of the DisCatSharp project, a fork of DSharpPlus.
+//
+// Copyright (c) 2021 AITSYS
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+
+using System.Collections.Generic;
+using DisCatSharp.Interactivity;
+using DisCatSharp.Lavalink;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+using Xunit;
+
+namespace DisCatSharp.Hosting.Tests
+{
+ public class HostTests
+ {
+ private Dictionary DefaultDiscord() =>
+ new()
+ {
+ { "DisCatSharp:Discord:Token", "1234567890" },
+ { "DisCatSharp:Discord:TokenType", "Bot" },
+ { "DisCatSharp:Discord:MinimumLogLevel", "Information" },
+ { "DisCatSharp:Discord:UseRelativeRateLimit", "true" },
+ { "DisCatSharp:Discord:LogTimestampFormat", "yyyy-MM-dd HH:mm:ss zzz" },
+ { "DisCatSharp:Discord:LargeThreshold", "250" },
+ { "DisCatSharp:Discord:AutoReconnect", "true" },
+ { "DisCatSharp:Discord:ShardId", "123123" },
+ { "DisCatSharp:Discord:GatewayCompressionLevel", "Stream" },
+ { "DisCatSharp:Discord:MessageCacheSize", "1024" },
+ { "DisCatSharp:Discord:HttpTimeout", "00:00:20" },
+ { "DisCatSharp:Discord:ReconnectIndefinitely", "false" },
+ { "DisCatSharp:Discord:AlwaysCacheMembers", "true" },
+ { "DisCatSharp:Discord:DiscordIntents", "AllUnprivileged" },
+ { "DisCatSharp:Discord:MobileStatus", "false" },
+ { "DisCatSharp:Discord:UseCanary", "false" },
+ { "DisCatSharp:Discord:AutoRefreshChannelCache", "false" },
+ { "DisCatSharp:Discord:Intents", "AllUnprivileged" }
+ };
+
+ public Dictionary DiscordInteractivity() => new (this.DefaultDiscord())
+ {
+ { "DisCatSharp:Interactivity", "" } // this should be enough to automatically add the extension
+ };
+
+ public Dictionary DiscordInteractivityAndLavalink() => new (this.DefaultDiscord())
+ {
+ { "DisCatSharp:Interactivity", "" }, { "DisCatSharp:LavaLink", "" }
+ };
+
+ IHostBuilder Create(Dictionary configValues) =>
+ Host.CreateDefaultBuilder()
+ .ConfigureServices(services =>
+ {
+ services.AddSingleton();
+ })
+ .ConfigureHostConfiguration(builder =>
+ {
+ builder.AddInMemoryCollection(configValues);
+ });
+
+ [Fact]
+ public void TestNoExtensions()
+ {
+ IHost? host = null;
+
+ try
+ {
+ host = this.Create(this.DefaultDiscord()).Build();
+
+ var service = host.Services.GetRequiredService();
+ Assert.NotNull(service);
+ Assert.NotNull(service.Client);
+ }
+ finally
+ {
+ host?.Dispose();
+ }
+ }
+
+ [Fact]
+ public void TestInteractivityExtension()
+ {
+ IHost? host = null;
+
+ try
+ {
+ host = this.Create(this.DiscordInteractivity()).Build();
+
+ var service = host.Services.GetRequiredService();
+ Assert.NotNull(service);
+ Assert.NotNull(service.Client);
+ Assert.NotNull(service.Client.GetExtension());
+
+ // Also verify IDiscordHostedService.Extensions
+ Assert.Single(service.Extensions);
+ }
+ finally
+ {
+ host?.Dispose();
+ }
+ }
+
+ [Fact]
+ public void TestInteractivityLavalinkExtensions()
+ {
+ IHost? host = null;
+
+ try
+ {
+ host = this.Create(this.DiscordInteractivityAndLavalink()).Build();
+
+ var service = host.Services.GetRequiredService();
+
+ Assert.NotNull(service);
+ Assert.NotNull(service.Client);
+ Assert.NotNull(service.Client.GetExtension());
+ Assert.NotNull(service.Client.GetExtension());
+
+ Assert.Equal(2, service.Extensions.Count);
+ }
+ finally
+ {
+ host?.Dispose();
+ }
+ }
+ }
+}
diff --git a/DisCatSharp.Hosting/AssemblyProperties.cs b/DisCatSharp.Hosting/AssemblyProperties.cs
new file mode 100644
index 000000000..71ebab38f
--- /dev/null
+++ b/DisCatSharp.Hosting/AssemblyProperties.cs
@@ -0,0 +1,4 @@
+using System.Runtime.CompilerServices;
+
+[assembly: InternalsVisibleTo("DisCatSharp.Hosting")]
+[assembly: InternalsVisibleTo("DisCatSharp.Hosting.Tests")]
diff --git a/DisCatSharp.Hosting/ConfigurationExtensions.cs b/DisCatSharp.Hosting/ConfigurationExtensions.cs
index 4001cb01c..b846f3e04 100644
--- a/DisCatSharp.Hosting/ConfigurationExtensions.cs
+++ b/DisCatSharp.Hosting/ConfigurationExtensions.cs
@@ -1,102 +1,124 @@
// This file is part of the DisCatSharp project, a fork of DSharpPlus.
//
// Copyright (c) 2021 AITSYS
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using System;
using System.Collections.Generic;
using System.Linq;
using DisCatSharp.Configuration;
using DisCatSharp.Configuration.Models;
using Microsoft.Extensions.Configuration;
namespace DisCatSharp.Hosting
{
internal struct ExtensionConfigResult
{
- public string Name { get; set; }
public ConfigSection Section { get; set; }
public Type ConfigType { get; set; }
public Type ImplementationType { get; set; }
}
internal static class ConfigurationExtensions
{
+ public static bool HasSection(this IConfiguration config, params string[] values)
+ {
+ // We require something to be passed in
+ if (!values.Any())
+ return false;
+
+ Queue queue = new(values);
+ IConfigurationSection section = config.GetSection(queue.Dequeue());
+
+ while (section != null && queue.Any())
+ config.GetSection(queue.Dequeue());
+
+ return section != null;
+ }
+
///
/// Easily identify which configuration types have been added to the
/// This way we can dynamically load extensions without explicitly doing so
///
///
///
- /// Dictionary
- public static Dictionary FindImplementedConfigs(this IConfiguration configuration,
- string rootName = "DisCatSharp")
+ /// Dictionary where Key -> Name of implemented type
Value ->
+ public static Dictionary FindImplementedExtensions(this IConfiguration configuration,
+ string rootName = Configuration.ConfigurationExtensions.DefaultRootLib)
{
if (string.IsNullOrEmpty(rootName))
throw new ArgumentNullException(nameof(rootName), "Root name must be provided");
Dictionary results = new();
// Assemblies managed by DisCatSharp
var assemblies = AppDomain.CurrentDomain.GetAssemblies()
.Where(x => x.FullName != null && x.FullName.StartsWith(Configuration.ConfigurationExtensions.DefaultRootLib));
foreach (var assembly in assemblies)
{
ExtensionConfigResult result = new();
foreach (var type in assembly.ExportedTypes
- .Where(x => x.Name.EndsWith("Configuration") && !x.IsAbstract && !x.IsInterface))
+ .Where(x => x.Name.EndsWith(Constants.ConfigSuffix) && !x.IsAbstract && !x.IsInterface))
{
-
- // Strip name to be whatever prefix
- result.Name = type.Name
- .Replace("Configuration", "");
+ string sectionName = type.Name;
+ string prefix = type.Name.Replace(Constants.ConfigSuffix, "");
result.ConfigType = type;
- if (!string.IsNullOrEmpty(configuration[configuration.ConfigPath(rootName, type.Name)]))
+ // Does a section exist with the classname? (DiscordConfiguration - for instance)
+ if(configuration.HasSection(rootName, sectionName))
result.Section = new ConfigSection(ref configuration, type.Name, rootName);
- else if (!string.IsNullOrEmpty(configuration[configuration.ConfigPath(rootName, result.Name)]))
- result.Section = new ConfigSection(ref configuration, result.Name, rootName);
+
+ // Does a section exist with the classname minus Configuration? (Discord - for Instance)
+ else if (configuration.HasSection(rootName, prefix))
+ result.Section = new ConfigSection(ref configuration, prefix, rootName);
+
+ // We require the implemented type to exist so we'll continue onward
+ else
+ continue;
/*
+ Now we need to find the type which should consume our config
+
In the event a user has some "fluff" between prefix and suffix we'll
just check for beginning and ending values.
- Type should not be an interface or abstract
+ Type should not be an interface or abstract, should also be assignable to BaseExtension
*/
var implementationType = assembly.ExportedTypes.FirstOrDefault(x =>
- !x.IsAbstract && !x.IsInterface && x.Name.StartsWith(result.Name) &&
- x.Name.EndsWith("Extension"));
+ !x.IsAbstract && !x.IsInterface && x.Name.StartsWith(prefix) &&
+ x.Name.EndsWith(Constants.ExtensionSuffix) && x.IsAssignableTo(typeof(BaseExtension)));
+ // If the implementation type was found we can add it to our result set
if (implementationType != null)
{
- results.Add(result.Name, result);
result.ImplementationType = implementationType;
+ results.Add(implementationType.Name, result);
}
}
}
return results;
}
}
}
diff --git a/DisCatSharp.Hosting/IDiscordHostedService.cs b/DisCatSharp.Hosting/Constants.cs
similarity index 82%
copy from DisCatSharp.Hosting/IDiscordHostedService.cs
copy to DisCatSharp.Hosting/Constants.cs
index 36808f652..e7e9cbafc 100644
--- a/DisCatSharp.Hosting/IDiscordHostedService.cs
+++ b/DisCatSharp.Hosting/Constants.cs
@@ -1,32 +1,31 @@
// This file is part of the DisCatSharp project, a fork of DSharpPlus.
//
// Copyright (c) 2021 AITSYS
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
-using Microsoft.Extensions.Configuration;
-
namespace DisCatSharp.Hosting
{
-
- public interface IDiscordHostedService : Microsoft.Extensions.Hosting.IHostedService
+ internal static class Constants
{
- DiscordClient Client { get; }
+ public static string LibName => Configuration.ConfigurationExtensions.DefaultRootLib;
+ public static string ConfigSuffix => "Configuration";
+ public static string ExtensionSuffix => "Extension";
}
}
diff --git a/DisCatSharp.Hosting/DiscordHostedService.cs b/DisCatSharp.Hosting/DiscordHostedService.cs
index 59969481d..970e83800 100644
--- a/DisCatSharp.Hosting/DiscordHostedService.cs
+++ b/DisCatSharp.Hosting/DiscordHostedService.cs
@@ -1,88 +1,138 @@
// This file is part of the DisCatSharp project, a fork of DSharpPlus.
//
// Copyright (c) 2021 AITSYS
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using System;
using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
+using DisCatSharp.Configuration;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
namespace DisCatSharp.Hosting
{
- public class DiscordHostedService : BackgroundService, IDiscordHostedService
+ ///
+ /// Simple implementation for to work as a
+ ///
+ public sealed class DiscordHostedService : BackgroundService, IDiscordHostedService
{
+ ///
public DiscordClient Client { get; private set; }
private readonly ILogger _logger;
- public Dictionary Extensions { get; } = new();
+ ///
+ public Dictionary Extensions { get; } = new();
public DiscordHostedService(IConfiguration config, ILogger logger, IServiceProvider provider)
{
this._logger = logger;
this.Initialize(config, provider);
}
- internal void Initialize(IConfiguration config, IServiceProvider provider)
+ ///
+ /// Automatically search for and configure
+ ///
+ ///
+ ///
+ private void Initialize(IConfiguration config, IServiceProvider provider)
{
- var typeMap = config.FindImplementedConfigs();
+ var typeMap = config.FindImplementedExtensions();
+
+ this._logger.LogDebug($"Found the following config types: {string.Join("\n\t", typeMap.Keys)}");
+
+ var section = config.HasSection(Constants.LibName, "Discord")
+ ? "Discord"
+ : config.HasSection(config.ConfigPath(Constants.LibName, $"Discord{Constants.ConfigSuffix}"))
+ ? $"Discord{Constants.ConfigSuffix}"
+ : null;
+
+ // If not section was provided we'll still just use the default config
+ if (string.IsNullOrEmpty(section))
+ this.Client = new DiscordClient(new());
+ else
+ this.Client = new DiscordClient(config.ExtractConfig(section));
foreach (var typePair in typeMap)
try
{
- // First we must create an instance of the configuration type
- object configInstance = ActivatorUtilities.CreateInstance(provider, typePair.Value.ConfigType);
+ // First retrieve our configuration!
+ object configInstance = typePair.Value.Section.ExtractConfig(() =>
+ ActivatorUtilities.CreateInstance(provider, typePair.Value.ConfigType));
+
+ /*
+ Explanation for bindings
+
+ Internal Constructors --> NonPublic
+ Public Constructors --> Public
+ Constructors --> Instance
+ */
- // Secondly -- instantiate corresponding implemented types
- if (typePair.Value.ImplementationType == typeof(DiscordClient))
- {
- this.Client = (DiscordClient)ActivatorUtilities.CreateInstance(provider,
- typePair.Value.ImplementationType, configInstance);
+ BindingFlags flags = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance;
+ var ctors = typePair.Value.ImplementationType.GetConstructors(flags);
- continue;
- }
+ object? instance;
- object instance = ActivatorUtilities.CreateInstance(provider, typePair.Value.ImplementationType, configInstance);
+ /*
+ Certain extensions do not require a configuration argument
+ Those who do -- pass config instance in,
+ Those who don't -- simply instantiate
- this.Extensions.Add(typePair.Value.ImplementationType.Name, instance);
- this.Client.AddExtension((BaseExtension) instance);
+ ActivatorUtilities requires a public constructor, anything with internal breaks
+ */
+
+ if (ctors.Any(x => x.GetParameters().Length == 1))
+ instance = Activator.CreateInstance(typePair.Value.ImplementationType, flags, null,
+ new[] { configInstance }, null);
+ else
+ instance = Activator.CreateInstance(typePair.Value.ImplementationType, true);
+
+ // Add an easy reference to our extensions for later use
+ this.Extensions.Add(typePair.Value.ImplementationType.Name, (BaseExtension) instance);
}
catch (Exception ex)
{
this._logger.LogError($"Unable to register '{typePair.Value.ImplementationType.Name}': \n\t{ex.Message}");
}
+
+ // Add of our extensions to our client
+ foreach (var extension in this.Extensions.Values)
+ this.Client.AddExtension(extension);
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
if (this.Client == null)
throw new NullReferenceException("Discord Client cannot be null");
+ await this.Client.ConnectAsync();
+
// Wait indefinitely -- but use stopping token so we can properly cancel if needed
await Task.Delay(-1, stoppingToken);
}
}
}
diff --git a/DisCatSharp.Hosting/IDiscordHostedService.cs b/DisCatSharp.Hosting/IDiscordHostedService.cs
index 36808f652..97c114047 100644
--- a/DisCatSharp.Hosting/IDiscordHostedService.cs
+++ b/DisCatSharp.Hosting/IDiscordHostedService.cs
@@ -1,32 +1,46 @@
// This file is part of the DisCatSharp project, a fork of DSharpPlus.
//
// Copyright (c) 2021 AITSYS
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
-using Microsoft.Extensions.Configuration;
+using System.Collections.Generic;
namespace DisCatSharp.Hosting
{
+ ///
+ /// Contract required for to work in a web hosting environment
+ ///
public interface IDiscordHostedService : Microsoft.Extensions.Hosting.IHostedService
{
+ ///
+ /// Reference to connected client
+ ///
DiscordClient Client { get; }
+
+ ///
+ /// Extensions cached by their implementation type
+ ///
+ ///
+ /// Key: "InteractivityExtension" Value: Instance of InteractivityExtension
+ ///
+ Dictionary Extensions { get; }
}
}
diff --git a/DisCatSharp.sln b/DisCatSharp.sln
index b1b4c8f2e..48770a423 100644
--- a/DisCatSharp.sln
+++ b/DisCatSharp.sln
@@ -1,129 +1,141 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.29613.14
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DisCatSharp", "DisCatSharp\DisCatSharp.csproj", "{EB3D8310-DFAD-4295-97F9-82E253647583}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DisCatSharp.VoiceNext", "DisCatSharp.VoiceNext\DisCatSharp.VoiceNext.csproj", "{FB6B9EE9-65FB-4DFB-8D51-06F0BE6C1BA5}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{4255B64D-92EC-46B3-BC3B-ED2C3A8073EE}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
.gitattributes = .gitattributes
.gitignore = .gitignore
BUILDING.md = BUILDING.md
CODE_OF_CONDUCT.md = CODE_OF_CONDUCT.md
CONTRIBUTING.md = CONTRIBUTING.md
LICENSE.md = LICENSE.md
README.md = README.md
SECURITY.md = SECURITY.md
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DisCatSharp.CommandsNext", "DisCatSharp.CommandsNext\DisCatSharp.CommandsNext.csproj", "{C8ED55FB-E028-468D-955F-1534C20274EF}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DisCatSharp.Interactivity", "DisCatSharp.Interactivity\DisCatSharp.Interactivity.csproj", "{DD32BEC3-0189-479F-86DC-CCF95E5634A9}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{F953F5D0-F0C9-41E6-ADBF-60A76D295899}"
ProjectSection(SolutionItems) = preProject
.nuget\NuGet.config = .nuget\NuGet.config
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build Items", "Build Items", "{84464D70-687B-40A8-836D-C4F737698969}"
ProjectSection(SolutionItems) = preProject
appveyor.yml = appveyor.yml
.github\dependabot.yml = .github\dependabot.yml
DisCatSharp.targets = DisCatSharp.targets
docs-oneclick-rebuild.ps1 = docs-oneclick-rebuild.ps1
NuGet.targets = NuGet.targets
oneclick-rebuild.ps1 = oneclick-rebuild.ps1
Package.targets = Package.targets
rebuild-all.ps1 = rebuild-all.ps1
rebuild-docs.ps1 = rebuild-docs.ps1
rebuild-lib.ps1 = rebuild-lib.ps1
Version.targets = Version.targets
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".github", ".github", "{430C28D8-5F85-4D6E-AA68-211549435245}"
ProjectSection(SolutionItems) = preProject
.github\ISSUE_TEMPLATE\bug_report.md = .github\ISSUE_TEMPLATE\bug_report.md
.github\ISSUE_TEMPLATE\feature_request.md = .github\ISSUE_TEMPLATE\feature_request.md
.github\FUNDING.yml = .github\FUNDING.yml
.github\pull_request_template.md = .github\pull_request_template.md
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DisCatSharp.Lavalink", "DisCatSharp.Lavalink\DisCatSharp.Lavalink.csproj", "{A8B8FB09-C6AF-4F28-89B8-B53EE0DCE6E5}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DisCatSharp.VoiceNext.Natives", "DisCatSharp.VoiceNext.Natives\DisCatSharp.VoiceNext.Natives.csproj", "{BEC47B41-71E4-41D1-A4F9-BB7C56A1B82B}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DisCatSharp.Common", "DisCatSharp.Common\DisCatSharp.Common.csproj", "{CD84A5C7-C7FF-48CA-B23D-FA726CF80E09}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DisCatSharp.ApplicationCommands", "DisCatSharp.ApplicationCommands\DisCatSharp.ApplicationCommands.csproj", "{AD530FD0-523C-4DE7-9AF6-B9A3785492C2}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DisCatSharp.Configuration", "DisCatSharp.Configuration\DisCatSharp.Configuration.csproj", "{603287D3-1EF2-47F1-A611-C7F25869DE14}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DisCatSharp.Configuration.Tests", "DisCatSharp.Configuration.Tests\DisCatSharp.Configuration.Tests.csproj", "{E15E88B4-63AD-42DE-B685-D31697C62194}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DisCatSharp.Hosting", "DisCatSharp.Hosting\DisCatSharp.Hosting.csproj", "{72CCE5D5-926B-432A-876A-065FA2BC9B7B}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DisCatSharp.Hosting.Tests", "DisCatSharp.Hosting.Tests\DisCatSharp.Hosting.Tests.csproj", "{D02B598A-F0C9-4A8C-B8DE-7C0BAC8C9B94}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DisCatSharp.Hosting.DependencyInjection", "DisCatSharp.Hosting.DependencyInjection\DisCatSharp.Hosting.DependencyInjection.csproj", "{2D67D1DD-E5B2-40C7-80E2-54D63730E7F0}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{EB3D8310-DFAD-4295-97F9-82E253647583}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EB3D8310-DFAD-4295-97F9-82E253647583}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EB3D8310-DFAD-4295-97F9-82E253647583}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EB3D8310-DFAD-4295-97F9-82E253647583}.Release|Any CPU.Build.0 = Release|Any CPU
{FB6B9EE9-65FB-4DFB-8D51-06F0BE6C1BA5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FB6B9EE9-65FB-4DFB-8D51-06F0BE6C1BA5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FB6B9EE9-65FB-4DFB-8D51-06F0BE6C1BA5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FB6B9EE9-65FB-4DFB-8D51-06F0BE6C1BA5}.Release|Any CPU.Build.0 = Release|Any CPU
{C8ED55FB-E028-468D-955F-1534C20274EF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C8ED55FB-E028-468D-955F-1534C20274EF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C8ED55FB-E028-468D-955F-1534C20274EF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C8ED55FB-E028-468D-955F-1534C20274EF}.Release|Any CPU.Build.0 = Release|Any CPU
{DD32BEC3-0189-479F-86DC-CCF95E5634A9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DD32BEC3-0189-479F-86DC-CCF95E5634A9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DD32BEC3-0189-479F-86DC-CCF95E5634A9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DD32BEC3-0189-479F-86DC-CCF95E5634A9}.Release|Any CPU.Build.0 = Release|Any CPU
{A8B8FB09-C6AF-4F28-89B8-B53EE0DCE6E5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A8B8FB09-C6AF-4F28-89B8-B53EE0DCE6E5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A8B8FB09-C6AF-4F28-89B8-B53EE0DCE6E5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A8B8FB09-C6AF-4F28-89B8-B53EE0DCE6E5}.Release|Any CPU.Build.0 = Release|Any CPU
{BEC47B41-71E4-41D1-A4F9-BB7C56A1B82B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BEC47B41-71E4-41D1-A4F9-BB7C56A1B82B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BEC47B41-71E4-41D1-A4F9-BB7C56A1B82B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BEC47B41-71E4-41D1-A4F9-BB7C56A1B82B}.Release|Any CPU.Build.0 = Release|Any CPU
{CD84A5C7-C7FF-48CA-B23D-FA726CF80E09}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CD84A5C7-C7FF-48CA-B23D-FA726CF80E09}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CD84A5C7-C7FF-48CA-B23D-FA726CF80E09}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CD84A5C7-C7FF-48CA-B23D-FA726CF80E09}.Release|Any CPU.Build.0 = Release|Any CPU
{AD530FD0-523C-4DE7-9AF6-B9A3785492C2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AD530FD0-523C-4DE7-9AF6-B9A3785492C2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AD530FD0-523C-4DE7-9AF6-B9A3785492C2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AD530FD0-523C-4DE7-9AF6-B9A3785492C2}.Release|Any CPU.Build.0 = Release|Any CPU
{603287D3-1EF2-47F1-A611-C7F25869DE14}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{603287D3-1EF2-47F1-A611-C7F25869DE14}.Debug|Any CPU.Build.0 = Debug|Any CPU
{603287D3-1EF2-47F1-A611-C7F25869DE14}.Release|Any CPU.ActiveCfg = Release|Any CPU
{603287D3-1EF2-47F1-A611-C7F25869DE14}.Release|Any CPU.Build.0 = Release|Any CPU
{E15E88B4-63AD-42DE-B685-D31697C62194}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E15E88B4-63AD-42DE-B685-D31697C62194}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E15E88B4-63AD-42DE-B685-D31697C62194}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E15E88B4-63AD-42DE-B685-D31697C62194}.Release|Any CPU.Build.0 = Release|Any CPU
{72CCE5D5-926B-432A-876A-065FA2BC9B7B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{72CCE5D5-926B-432A-876A-065FA2BC9B7B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{72CCE5D5-926B-432A-876A-065FA2BC9B7B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{72CCE5D5-926B-432A-876A-065FA2BC9B7B}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D02B598A-F0C9-4A8C-B8DE-7C0BAC8C9B94}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D02B598A-F0C9-4A8C-B8DE-7C0BAC8C9B94}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D02B598A-F0C9-4A8C-B8DE-7C0BAC8C9B94}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D02B598A-F0C9-4A8C-B8DE-7C0BAC8C9B94}.Release|Any CPU.Build.0 = Release|Any CPU
+ {2D67D1DD-E5B2-40C7-80E2-54D63730E7F0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {2D67D1DD-E5B2-40C7-80E2-54D63730E7F0}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {2D67D1DD-E5B2-40C7-80E2-54D63730E7F0}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {2D67D1DD-E5B2-40C7-80E2-54D63730E7F0}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{430C28D8-5F85-4D6E-AA68-211549435245} = {4255B64D-92EC-46B3-BC3B-ED2C3A8073EE}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {23F3A981-51B8-4285-A38C-3267F1D25FE7}
EndGlobalSection
EndGlobal