diff --git a/DisCatSharp.Hosting.DependencyInjection/ServiceCollectionExtensions.cs b/DisCatSharp.Hosting.DependencyInjection/ServiceCollectionExtensions.cs
index c7cec0eb6..2f4d1aed7 100644
--- a/DisCatSharp.Hosting.DependencyInjection/ServiceCollectionExtensions.cs
+++ b/DisCatSharp.Hosting.DependencyInjection/ServiceCollectionExtensions.cs
@@ -1,49 +1,88 @@
using Microsoft.Extensions.DependencyInjection;
namespace DisCatSharp.Hosting.DependencyInjection
{
///
/// The service collection extensions.
///
public static class ServiceCollectionExtensions
{
///
/// Adds your bot as a BackgroundService, registered in Dependency Injection as
///
///
/// is scoped to ServiceLifetime.Singleton.
/// Maps to Implementation of
///
///
///
- ///
+ /// Reference to for chaining purposes
public static IServiceCollection AddDiscordHostedService(this IServiceCollection services)
where TService : class, IDiscordHostedService
{
services.AddSingleton();
services.AddHostedService(provider => provider.GetRequiredService());
return services;
}
+ ///
+ /// Adds your bot as a BackgroundService, registered in Dependency Injection as
+ ///
+ ///
+ /// is scoped to ServiceLifetime.Singleton.
+ /// Maps to Implementation of
+ ///
+ ///
+ ///
+ /// Reference to for chaining purposes
+ public static IServiceCollection AddDiscordHostedShardService(this IServiceCollection services)
+ where TService : class, IDiscordHostedShardService
+ {
+ services.AddSingleton();
+ services.AddHostedService(provider => provider.GetRequiredService());
+ return services;
+ }
+
///
/// Add as a background service which derives from
/// and
///
///
/// To retrieve your bot via Dependency Injection you can reference it via
///
///
/// Interface which inherits from
/// Your custom bot
- ///
+ /// Reference to for chaining purposes
public static IServiceCollection AddDiscordHostedService(this IServiceCollection services)
where TInterface : class, IDiscordHostedService
where TService : class, TInterface, IDiscordHostedService
{
services.AddSingleton();
services.AddHostedService(provider => provider.GetRequiredService());
return services;
}
+
+ ///
+ /// Add as a background service which derives from
+ /// and
+ ///
+ ///
+ /// To retrieve your bot via Dependency Injection you can reference it via
+ ///
+ ///
+ /// Interface which inherits from
+ /// Your custom bot
+ /// Reference to for chaining purposes
+ public static IServiceCollection AddDiscordHostedShardService(
+ this IServiceCollection services)
+ where TInterface : class, IDiscordHostedShardService
+ where TService : class, TInterface, IDiscordHostedShardService
+ {
+ services.AddSingleton();
+ services.AddHostedService(provider => provider.GetRequiredService());
+ return services;
+ }
}
}
diff --git a/DisCatSharp.Hosting.Tests/HostTests.cs b/DisCatSharp.Hosting.Tests/HostTests.cs
index 6a967793a..f513fcc18 100644
--- a/DisCatSharp.Hosting.Tests/HostTests.cs
+++ b/DisCatSharp.Hosting.Tests/HostTests.cs
@@ -1,254 +1,256 @@
// 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 DisCatSharp.Interactivity;
using DisCatSharp.Lavalink;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Xunit;
namespace DisCatSharp.Hosting.Tests
{
public sealed class Bot : DiscordHostedService
{
public Bot(IConfiguration config, ILogger logger, IServiceProvider provider, IHostApplicationLifetime lifetime) : base(config, logger, provider, lifetime)
{
- this.InitializeExtensions();
+ this.PreConnectAsync().GetAwaiter().GetResult();
+ this.PostConnectAsync().GetAwaiter().GetResult();
}
}
public sealed class MyCustomBot : DiscordHostedService
{
public MyCustomBot(IConfiguration config, ILogger logger, IServiceProvider provider, IHostApplicationLifetime lifetime) : base(config, logger, provider, lifetime, "MyCustomBot")
{
- this.InitializeExtensions();
+ this.PreConnectAsync().GetAwaiter().GetResult();
+ this.PostConnectAsync().GetAwaiter().GetResult();
}
}
public interface IBotTwoService : IDiscordHostedService
{
string GiveMeAResponse();
}
public sealed class BotTwoService : DiscordHostedService, IBotTwoService
{
public BotTwoService(IConfiguration config, ILogger logger, IServiceProvider provider, IHostApplicationLifetime lifetime) : base(config, logger, provider, lifetime, "BotTwo")
{
- this.InitializeExtensions();
+ this.PreConnectAsync().Wait();
}
public string GiveMeAResponse() => "I'm working";
}
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:Using","[\"DisCatSharp.Interactivity\"]"},
};
public Dictionary DiscordInteractivityAndLavalink() => new (this.DefaultDiscord())
{
{"DisCatSharp:Using","[\"DisCatSharp.Interactivity\", \"DisCatSharp.Lavalink\"]"},
};
IHostBuilder Create(Dictionary configValues) =>
Host.CreateDefaultBuilder()
.ConfigureServices(services => services.AddSingleton())
.ConfigureHostConfiguration(builder => builder.AddInMemoryCollection(configValues));
IHostBuilder Create(string filename) =>
Host.CreateDefaultBuilder()
.ConfigureServices(services => services.AddSingleton())
.ConfigureHostConfiguration(builder => builder.AddJsonFile(filename));
IHostBuilder Create(string filename)
where TInterface : class, IDiscordHostedService
where TBot : class, TInterface, IDiscordHostedService =>
Host.CreateDefaultBuilder()
.ConfigureServices(services => services.AddSingleton())
.ConfigureHostConfiguration(builder => builder.AddJsonFile(filename));
[Fact]
public void TestBotCustomInterface()
{
IHost? host = null;
try
{
host = this.Create("BotTwo.json").Build();
var service = host.Services.GetRequiredService();
Assert.NotNull(service);
var response = service.GiveMeAResponse();
Assert.Equal("I'm working", response);
}
finally
{
host?.Dispose();
}
}
[Fact]
public void TestDifferentSection_InteractivityOnly()
{
IHost? host = null;
try
{
host = this.Create("interactivity-different-section.json").Build();
var service = host.Services.GetRequiredService();
Assert.NotNull(service);
Assert.NotNull(service.Client);
Assert.Null(service.Client.GetExtension());
var intents = DiscordIntents.GuildEmojisAndStickers | DiscordIntents.GuildMembers |
DiscordIntents.Guilds;
Assert.Equal(intents, service.Client.Intents);
var interactivity = service.Client.GetExtension();
Assert.NotNull(interactivity);
}
finally
{
host?.Dispose();
}
}
[Fact]
public void TestDifferentSection_LavalinkOnly()
{
IHost? host = null;
try
{
host = this.Create("lavalink-different-section.json").Build();
var service = host.Services.GetRequiredService();
Assert.NotNull(service);
Assert.NotNull(service.Client);
Assert.NotNull(service.Client.GetExtension());
Assert.Null(service.Client.GetExtension());
var intents = DiscordIntents.Guilds;
Assert.Equal(intents, service.Client.Intents);
}
finally
{
host?.Dispose();
}
}
[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());
}
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());
}
finally
{
host?.Dispose();
}
}
}
}
diff --git a/DisCatSharp.Hosting/DiscordHostedService.cs b/DisCatSharp.Hosting/BaseHostedService.cs
similarity index 62%
copy from DisCatSharp.Hosting/DiscordHostedService.cs
copy to DisCatSharp.Hosting/BaseHostedService.cs
index 7d1758b3b..78cdc2de3 100644
--- a/DisCatSharp.Hosting/DiscordHostedService.cs
+++ b/DisCatSharp.Hosting/BaseHostedService.cs
@@ -1,206 +1,182 @@
// 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.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
{
///
- /// Simple implementation for to work as a
+ /// Contains the common logic between having a or
+ /// as a Hosted Service
///
- public abstract class DiscordHostedService : BackgroundService, IDiscordHostedService
+ public abstract class BaseHostedService : BackgroundService
{
- ///
- public DiscordClient Client { get; private set; }
-
- protected readonly ILogger Logger;
+ protected readonly ILogger Logger;
protected readonly IHostApplicationLifetime ApplicationLifetime;
protected readonly IConfiguration Configuration;
protected readonly IServiceProvider ServiceProvider;
- private readonly string _botSection;
+ protected readonly string BotSection;
- #pragma warning disable 8618
- ///
- /// Initializes a new instance of the class.
- ///
- /// The config.
- /// The logger.
- /// The provider.
- /// Current hosting environment. This will be used for shutting down the application on error
- /// Name within the configuration which contains the config info for our bot. Default is DisCatSharp
- protected DiscordHostedService(IConfiguration config, ILogger logger, IServiceProvider provider, IHostApplicationLifetime applicationLifetime, string configBotSection = DisCatSharp.Configuration.ConfigurationExtensions.DefaultRootLib)
+ internal BaseHostedService(IConfiguration config,
+ ILogger logger,
+ IServiceProvider serviceProvider,
+ IHostApplicationLifetime applicationLifetime,
+ string configBotSection = DisCatSharp.Configuration.ConfigurationExtensions.DefaultRootLib)
{
+ this.Configuration = config;
this.Logger = logger;
this.ApplicationLifetime = applicationLifetime;
- this.Configuration = config;
- this._botSection = configBotSection;
- this.ServiceProvider = provider;
- this.Initialize();
+ this.ServiceProvider = serviceProvider;
+ this.BotSection = configBotSection;
}
- #pragma warning restore 8618
+ ///
+ /// When the bot(s) fail to start, this method will be invoked. (Default behavior is to shutdown)
+ ///
+ /// The exception/reason for not starting
+ protected virtual void OnInitializationError(Exception ex) => this.ApplicationLifetime.StopApplication();
///
- /// When the bot fails to start, this method will be invoked. (Default behavior is to shutdown)
+ /// Connect your client(s) to Discord
///
- /// The exception/reason the bot couldn't start
- protected virtual void OnInitializationError(Exception ex)
- {
- this.ApplicationLifetime.StopApplication();
- }
+ /// Task
+ protected abstract Task ConnectAsync();
///
- /// Dynamically loads extensions by using , and
+ /// Default behavior is to dynamically load extensions by using and
///
///
- protected virtual void InitializeExtensions()
+ /// Client to add extension method(s) to
+ /// Task
+ protected virtual Task InitializeExtensions(DiscordClient client)
{
- var typeMap = this.Configuration.FindImplementedExtensions(this._botSection);
+ var typeMap = this.Configuration.FindImplementedExtensions(this.BotSection);
this.Logger.LogDebug($"Found the following config types: {string.Join("\n\t", typeMap.Keys)}");
foreach (var typePair in typeMap)
try
{
/*
If section is null --> utilize the default constructor
This means the extension was explicitly added in the 'Using' array,
but user did not wish to override any value(s) in the extension's config
*/
var configInstance = typePair.Value.Section.HasValue
? typePair.Value.Section.Value.ExtractConfig(() =>
ActivatorUtilities.CreateInstance(this.ServiceProvider, typePair.Value.ConfigType))
: ActivatorUtilities.CreateInstance(this.ServiceProvider, typePair.Value.ConfigType);
/*
Explanation for bindings
Internal Constructors --> NonPublic
Public Constructors --> Public
Constructors --> Instance
*/
var flags = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance;
var ctors = typePair.Value.ImplementationType.GetConstructors(flags);
var instance = ctors.Any(x => x.GetParameters().Length == 1 && x.GetParameters().First().ParameterType == typePair.Value.ConfigType)
? Activator.CreateInstance(typePair.Value.ImplementationType, flags, null,
new[] { configInstance }, null)
: Activator.CreateInstance(typePair.Value.ImplementationType, true);
/*
Certain extensions do not require a configuration argument
Those who do -- pass config instance in,
Those who don't -- simply instantiate
ActivatorUtilities requires a public constructor, anything with internal breaks
*/
if (instance == null)
{
this.Logger.LogError($"Unable to instantiate '{typePair.Value.ImplementationType.Name}'");
continue;
}
// Add an easy reference to our extensions for later use
- this.Client.AddExtension((BaseExtension)instance);
+ client.AddExtension((BaseExtension)instance);
}
catch (Exception ex)
{
this.Logger.LogError($"Unable to register '{typePair.Value.ImplementationType.Name}': \n\t{ex.Message}");
this.OnInitializationError(ex);
}
+
+ return Task.CompletedTask;
}
///
- /// Automatically search for and configure
+ /// Runs just prior to . Should initialize your
+ /// or here
///
- private void Initialize()
- {
- try
- {
- this.Client = this.Configuration.BuildClient(this._botSection);
- this.Client.ServiceProvider = this.ServiceProvider;
- }
- catch (Exception ex)
- {
- this.Logger.LogError($"Was unable to build {nameof(DiscordClient)} for {this.GetType().Name}");
- this.OnInitializationError(ex);
- }
- }
+ ///
+ protected abstract Task PreConnectAsync();
///
- /// Executes the bot.
+ /// Runs immediately after .
+ /// Recommended to initialize your extensions here
///
- /// The stopping token.
- /// A Task.
+ /// Task
+ protected abstract Task PostConnectAsync();
+
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
try
{
- if (this.Client == null)
- throw new NullReferenceException("Discord Client cannot be null");
-
- await this.PreConnect();
- await this.Client.ConnectAsync();
- this.InitializeExtensions();
- await this.PostConnect();
+ await this.PreConnectAsync();
+ await this.ConnectAsync();
+ await this.PostConnectAsync();
}
catch (Exception ex)
{
/*
* Anything before DOTNET 6 will
* fail silently despite throwing an exception in this method
- * So to overcome this obstacle we need to log what happened and manually exit
+ * So to overcome this obstacle we need to log what happens and
+ * manually exit
*/
+ this.Logger.LogError(($"Was unable to start {this.GetType().Name} Bot as a Hosted Service"));
- this.Logger.LogError($"Was unable to start {this.GetType().Name} Bot as a hosted service.");
+ // Power given to developer for handling exception
this.OnInitializationError(ex);
}
// Wait indefinitely -- but use stopping token so we can properly cancel if needed
await Task.Delay(-1, stoppingToken);
}
-
- ///
- /// Runs just prior to the bot connecting
- ///
- protected virtual Task PreConnect() => Task.CompletedTask;
-
- ///
- /// Runs immediately after the bot connects
- ///
- protected virtual Task PostConnect() => Task.CompletedTask;
-
}
}
diff --git a/DisCatSharp.Hosting/DiscordHostedService.cs b/DisCatSharp.Hosting/DiscordHostedService.cs
index 7d1758b3b..7201ce17b 100644
--- a/DisCatSharp.Hosting/DiscordHostedService.cs
+++ b/DisCatSharp.Hosting/DiscordHostedService.cs
@@ -1,206 +1,80 @@
// 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.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
{
///
/// Simple implementation for to work as a
///
- public abstract class DiscordHostedService : BackgroundService, IDiscordHostedService
+ public abstract class DiscordHostedService : BaseHostedService, IDiscordHostedService
{
///
- public DiscordClient Client { get; private set; }
-
- protected readonly ILogger Logger;
- protected readonly IHostApplicationLifetime ApplicationLifetime;
- protected readonly IConfiguration Configuration;
- protected readonly IServiceProvider ServiceProvider;
- private readonly string _botSection;
+ public DiscordClient Client { get; protected set; }
#pragma warning disable 8618
- ///
- /// Initializes a new instance of the class.
- ///
- /// The config.
- /// The logger.
- /// The provider.
- /// Current hosting environment. This will be used for shutting down the application on error
- /// Name within the configuration which contains the config info for our bot. Default is DisCatSharp
- protected DiscordHostedService(IConfiguration config, ILogger logger, IServiceProvider provider, IHostApplicationLifetime applicationLifetime, string configBotSection = DisCatSharp.Configuration.ConfigurationExtensions.DefaultRootLib)
- {
- this.Logger = logger;
- this.ApplicationLifetime = applicationLifetime;
- this.Configuration = config;
- this._botSection = configBotSection;
- this.ServiceProvider = provider;
- this.Initialize();
- }
-
- #pragma warning restore 8618
-
- ///
- /// When the bot fails to start, this method will be invoked. (Default behavior is to shutdown)
- ///
- /// The exception/reason the bot couldn't start
- protected virtual void OnInitializationError(Exception ex)
+ /// IConfiguration provided via Dependency Injection. Aggregate method to access configuration files
+ /// An ILogger to work with, provided via Dependency Injection
+ /// ServiceProvider reference which contains all items currently registered for Dependency Injection
+ /// Contains the appropriate methods for disposing / stopping BackgroundServices during runtime
+ /// The name of the JSON/Config Key which contains the configuration for this Discord Service
+ protected DiscordHostedService(IConfiguration config,
+ ILogger logger,
+ IServiceProvider serviceProvider,
+ IHostApplicationLifetime applicationLifetime,
+ string configBotSection = DisCatSharp.Configuration.ConfigurationExtensions.DefaultRootLib)
+ : base(config, logger, serviceProvider, applicationLifetime, configBotSection)
{
- this.ApplicationLifetime.StopApplication();
- }
-
- ///
- /// Dynamically loads extensions by using , and
- ///
- ///
- protected virtual void InitializeExtensions()
- {
- var typeMap = this.Configuration.FindImplementedExtensions(this._botSection);
-
- this.Logger.LogDebug($"Found the following config types: {string.Join("\n\t", typeMap.Keys)}");
-
- foreach (var typePair in typeMap)
- try
- {
- /*
- If section is null --> utilize the default constructor
- This means the extension was explicitly added in the 'Using' array,
- but user did not wish to override any value(s) in the extension's config
- */
-
- var configInstance = typePair.Value.Section.HasValue
- ? typePair.Value.Section.Value.ExtractConfig(() =>
- ActivatorUtilities.CreateInstance(this.ServiceProvider, typePair.Value.ConfigType))
- : ActivatorUtilities.CreateInstance(this.ServiceProvider, typePair.Value.ConfigType);
-
- /*
- Explanation for bindings
-
- Internal Constructors --> NonPublic
- Public Constructors --> Public
- Constructors --> Instance
- */
-
- var flags = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance;
- var ctors = typePair.Value.ImplementationType.GetConstructors(flags);
-
- var instance = ctors.Any(x => x.GetParameters().Length == 1 && x.GetParameters().First().ParameterType == typePair.Value.ConfigType)
- ? Activator.CreateInstance(typePair.Value.ImplementationType, flags, null,
- new[] { configInstance }, null)
- : Activator.CreateInstance(typePair.Value.ImplementationType, true);
-
- /*
- Certain extensions do not require a configuration argument
- Those who do -- pass config instance in,
- Those who don't -- simply instantiate
-
- ActivatorUtilities requires a public constructor, anything with internal breaks
- */
-
- if (instance == null)
- {
- this.Logger.LogError($"Unable to instantiate '{typePair.Value.ImplementationType.Name}'");
- continue;
- }
-
- // Add an easy reference to our extensions for later use
- this.Client.AddExtension((BaseExtension)instance);
- }
- catch (Exception ex)
- {
- this.Logger.LogError($"Unable to register '{typePair.Value.ImplementationType.Name}': \n\t{ex.Message}");
- this.OnInitializationError(ex);
- }
}
- ///
- /// Automatically search for and configure
- ///
- private void Initialize()
+ protected override Task PreConnectAsync()
{
try
{
- this.Client = this.Configuration.BuildClient(this._botSection);
+ this.Client = this.Configuration.BuildClient(this.BotSection);
this.Client.ServiceProvider = this.ServiceProvider;
}
catch (Exception ex)
{
this.Logger.LogError($"Was unable to build {nameof(DiscordClient)} for {this.GetType().Name}");
this.OnInitializationError(ex);
}
- }
- ///
- /// Executes the bot.
- ///
- /// The stopping token.
- /// A Task.
- protected override async Task ExecuteAsync(CancellationToken stoppingToken)
- {
- try
- {
- if (this.Client == null)
- throw new NullReferenceException("Discord Client cannot be null");
-
- await this.PreConnect();
- await this.Client.ConnectAsync();
- this.InitializeExtensions();
- await this.PostConnect();
- }
- catch (Exception ex)
- {
- /*
- * Anything before DOTNET 6 will
- * fail silently despite throwing an exception in this method
- * So to overcome this obstacle we need to log what happened and manually exit
- */
-
- this.Logger.LogError($"Was unable to start {this.GetType().Name} Bot as a hosted service.");
- this.OnInitializationError(ex);
- }
-
- // Wait indefinitely -- but use stopping token so we can properly cancel if needed
- await Task.Delay(-1, stoppingToken);
+ return Task.CompletedTask;
}
- ///
- /// Runs just prior to the bot connecting
- ///
- protected virtual Task PreConnect() => Task.CompletedTask;
-
- ///
- /// Runs immediately after the bot connects
- ///
- protected virtual Task PostConnect() => Task.CompletedTask;
+ protected override async Task ConnectAsync() => await this.Client.ConnectAsync();
+ protected override Task PostConnectAsync()
+ {
+ this.InitializeExtensions(this.Client);
+ return Task.CompletedTask;
+ }
}
}
diff --git a/DisCatSharp.Hosting/DiscordSharedHostedService.cs b/DisCatSharp.Hosting/DiscordSharedHostedService.cs
new file mode 100644
index 000000000..e5fa0665b
--- /dev/null
+++ b/DisCatSharp.Hosting/DiscordSharedHostedService.cs
@@ -0,0 +1,84 @@
+// 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.Threading.Tasks;
+using DisCatSharp.Configuration;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Logging;
+
+namespace DisCatSharp.Hosting
+{
+ ///
+ /// Simple Implementation for to work as a
+ ///
+ public abstract class DiscordShardedHostedService : BaseHostedService, IDiscordHostedShardService
+ {
+ public DiscordShardedClient ShardedClient { get; protected set; }
+
+#pragma warning disable 8618
+ protected DiscordShardedHostedService(IConfiguration config,
+ ILogger logger,
+ IServiceProvider serviceProvider,
+ IHostApplicationLifetime applicationLifetime,
+ string configBotSection = DisCatSharp.Configuration.ConfigurationExtensions.DefaultRootLib)
+ : base(config, logger, serviceProvider, applicationLifetime, configBotSection)
+ {
+
+ }
+#pragma warning restore 8618
+
+ protected override Task PreConnectAsync()
+ {
+ try
+ {
+ var config = this.Configuration.ExtractConfig("Discord", this.BotSection);
+ this.ShardedClient = new DiscordShardedClient(config);
+ }
+ catch (Exception ex)
+ {
+ this.Logger.LogError($"Was unable to build {nameof(DiscordShardedClient)} for {this.GetType().Name}");
+ this.OnInitializationError(ex);
+ }
+
+ return Task.CompletedTask;
+ }
+
+ protected override async Task ConnectAsync()
+ {
+ await this.ShardedClient.InitializeShardsAsync();
+ await this.ShardedClient.StartAsync();
+ }
+
+ protected override Task PostConnectAsync()
+ {
+ foreach (var client in this.ShardedClient.ShardClients.Values)
+ {
+ client.ServiceProvider = this.ServiceProvider;
+ this.InitializeExtensions(client);
+ }
+
+ return Task.CompletedTask;
+ }
+ }
+}
diff --git a/DisCatSharp.Hosting/IDiscordHostedService.cs b/DisCatSharp.Hosting/IDiscordHostedService.cs
index 22c5ee59f..826f276bc 100644
--- a/DisCatSharp.Hosting/IDiscordHostedService.cs
+++ b/DisCatSharp.Hosting/IDiscordHostedService.cs
@@ -1,36 +1,44 @@
// 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.
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; }
}
+
+ ///
+ /// Contract required for to work in a web hosting environment
+ ///
+ public interface IDiscordHostedShardService : Microsoft.Extensions.Hosting.IHostedService
+ {
+ DiscordShardedClient ShardedClient { get; }
+ }
}