diff --git a/DisCatSharp.Hosting/DiscordHostedService.cs b/DisCatSharp.Hosting/DiscordHostedService.cs
index dc3de879c..a2216f0e2 100644
--- a/DisCatSharp.Hosting/DiscordHostedService.cs
+++ b/DisCatSharp.Hosting/DiscordHostedService.cs
@@ -1,209 +1,208 @@
// 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 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;
#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)
{
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
///
///
///
/// Name within the configuration which contains the config info for our bot
private void Initialize()
{
try
{
this.Client = this.Configuration.BuildClient(this._botSection);
- this.Client.Services = 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);
}
///
/// 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/Clients/BaseDiscordClient.cs b/DisCatSharp/Clients/BaseDiscordClient.cs
index 934f16984..57395a843 100644
--- a/DisCatSharp/Clients/BaseDiscordClient.cs
+++ b/DisCatSharp/Clients/BaseDiscordClient.cs
@@ -1,301 +1,301 @@
// This file is part of the DisCatSharp project.
//
// 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.
#pragma warning disable CS0618
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using DisCatSharp.Entities;
using DisCatSharp.Net;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace DisCatSharp
{
///
/// Represents a common base for various Discord client implementations.
///
public abstract class BaseDiscordClient : IDisposable
{
///
/// Gets the api client.
///
internal protected DiscordApiClient ApiClient { get; }
///
/// Gets the configuration.
///
internal protected DiscordConfiguration Configuration { get; }
///
/// Gets the instance of the logger for this client.
///
public ILogger Logger { get; }
///
/// Gets the string representing the version of bot lib.
///
public string VersionString { get; }
///
/// Gets the bot library name.
///
public string BotLibrary { get; }
///
/// Gets the library team.
///
public DisCatSharpTeam LibraryDeveloperTeam
=> this.ApiClient.GetDisCatSharpTeamAsync().Result;
///
/// Gets the current user.
///
public DiscordUser CurrentUser { get; internal set; }
///
/// Gets the current application.
///
public DiscordApplication CurrentApplication { get; internal set; }
///
/// Gets the cached guilds for this client.
///
public abstract IReadOnlyDictionary Guilds { get; }
///
/// Gets the cached users for this client.
///
protected internal ConcurrentDictionary UserCache { get; }
///
/// Gets the service provider.
/// This allows passing data around without resorting to static members.
/// Defaults to null.
///
internal IServiceProvider Services { get; set; } = new ServiceCollection().BuildServiceProvider(true);
///
/// Gets the list of available voice regions. Note that this property will not contain VIP voice regions.
///
public IReadOnlyDictionary VoiceRegions
=> this._voice_regions_lazy.Value;
///
/// Gets the list of available voice regions. This property is meant as a way to modify .
///
protected internal ConcurrentDictionary InternalVoiceRegions { get; set; }
internal Lazy> _voice_regions_lazy;
///
/// Initializes this Discord API client.
///
/// Configuration for this client.
protected BaseDiscordClient(DiscordConfiguration config)
{
this.Configuration = new DiscordConfiguration(config);
if (this.Configuration.LoggerFactory == null)
{
this.Configuration.LoggerFactory = new DefaultLoggerFactory();
this.Configuration.LoggerFactory.AddProvider(new DefaultLoggerProvider(this));
}
this.Logger = this.Configuration.LoggerFactory.CreateLogger();
this.ApiClient = new DiscordApiClient(this);
this.UserCache = new ConcurrentDictionary();
this.InternalVoiceRegions = new ConcurrentDictionary();
this._voice_regions_lazy = new Lazy>(() => new ReadOnlyDictionary(this.InternalVoiceRegions));
var a = typeof(DiscordClient).GetTypeInfo().Assembly;
var iv = a.GetCustomAttribute();
if (iv != null)
{
this.VersionString = iv.InformationalVersion;
}
else
{
var v = a.GetName().Version;
var vs = v.ToString(3);
if (v.Revision > 0)
this.VersionString = $"{vs}, CI build {v.Revision}";
}
this.BotLibrary = "DisCatSharp";
- this.Services = config.Services;
+ this.Services = config.ServiceProvider;
}
///
/// Gets the current API application.
///
/// Current API application.
public async Task GetCurrentApplicationAsync()
{
var tapp = await this.ApiClient.GetCurrentApplicationInfoAsync().ConfigureAwait(false);
var app = new DiscordApplication
{
Discord = this,
Id = tapp.Id,
Name = tapp.Name,
Description = tapp.Description,
Summary = tapp.Summary,
IconHash = tapp.IconHash,
RpcOrigins = tapp.RpcOrigins != null ? new ReadOnlyCollection(tapp.RpcOrigins) : null,
Flags = tapp.Flags,
RequiresCodeGrant = tapp.BotRequiresCodeGrant,
IsPublic = tapp.IsPublicBot,
PrivacyPolicyUrl = tapp.PrivacyPolicyUrl,
TermsOfServiceUrl = tapp.TermsOfServiceUrl
};
// do team and owners
// tbh fuck doing this properly
if (tapp.Team == null)
{
// singular owner
app.Owners = new ReadOnlyCollection(new[] { new DiscordUser(tapp.Owner) });
app.Team = null;
app.TeamName = null;
}
else
{
// team owner
app.Team = new DiscordTeam(tapp.Team);
var members = tapp.Team.Members
.Select(x => new DiscordTeamMember(x) { Team = app.Team, User = new DiscordUser(x.User) })
.ToArray();
var owners = members
.Where(x => x.MembershipStatus == DiscordTeamMembershipStatus.Accepted)
.Select(x => x.User)
.ToArray();
app.Owners = new ReadOnlyCollection(owners);
app.Team.Owner = owners.FirstOrDefault(x => x.Id == tapp.Team.OwnerId);
app.Team.Members = new ReadOnlyCollection(members);
app.TeamName = app.Team.Name;
}
app.GuildId = tapp.GuildId.HasValue ? tapp.GuildId.Value : null;
app.Slug = tapp.Slug.HasValue ? tapp.Slug.Value : null;
app.PrimarySkuId = tapp.PrimarySkuId.HasValue ? tapp.PrimarySkuId.Value : null;
app.VerifyKey = tapp.VerifyKey.HasValue ? tapp.VerifyKey.Value : null;
app.CoverImageHash = tapp.CoverImageHash.HasValue ? tapp.CoverImageHash.Value : null;
return app;
}
///
/// Gets a list of regions
///
///
/// Thrown when Discord is unable to process the request.
public Task> ListVoiceRegionsAsync()
=> this.ApiClient.ListVoiceRegionsAsync();
///
/// Initializes this client. This method fetches information about current user, application, and voice regions.
///
///
public virtual async Task InitializeAsync()
{
if (this.CurrentUser == null)
{
this.CurrentUser = await this.ApiClient.GetCurrentUserAsync().ConfigureAwait(false);
this.UserCache.AddOrUpdate(this.CurrentUser.Id, this.CurrentUser, (id, xu) => this.CurrentUser);
}
if (this.Configuration.TokenType == TokenType.Bot && this.CurrentApplication == null)
this.CurrentApplication = await this.GetCurrentApplicationAsync().ConfigureAwait(false);
if (this.Configuration.TokenType != TokenType.Bearer && this.InternalVoiceRegions.Count == 0)
{
var vrs = await this.ListVoiceRegionsAsync().ConfigureAwait(false);
foreach (var xvr in vrs)
this.InternalVoiceRegions.TryAdd(xvr.Id, xvr);
}
}
///
/// Gets the current gateway info for the provided token.
/// If no value is provided, the configuration value will be used instead.
///
/// A gateway info object.
public async Task GetGatewayInfoAsync(string token = null)
{
if (this.Configuration.TokenType != TokenType.Bot)
throw new InvalidOperationException("Only bot tokens can access this info.");
if (string.IsNullOrEmpty(this.Configuration.Token))
{
if (string.IsNullOrEmpty(token))
throw new InvalidOperationException("Could not locate a valid token.");
this.Configuration.Token = token;
var res = await this.ApiClient.GetGatewayInfoAsync().ConfigureAwait(false);
this.Configuration.Token = null;
return res;
}
return await this.ApiClient.GetGatewayInfoAsync().ConfigureAwait(false);
}
///
/// Gets a cached user.
///
/// The user_id.
internal DiscordUser GetCachedOrEmptyUserInternal(ulong user_id)
{
this.TryGetCachedUserInternal(user_id, out var user);
return user;
}
///
/// Tries the get a cached user.
///
/// The user_id.
/// The user.
internal bool TryGetCachedUserInternal(ulong user_id, out DiscordUser user)
{
if (this.UserCache.TryGetValue(user_id, out user))
return true;
user = new DiscordUser { Id = user_id, Discord = this };
return false;
}
///
/// Disposes this client.
///
public abstract void Dispose();
}
}
diff --git a/DisCatSharp/DiscordConfiguration.cs b/DisCatSharp/DiscordConfiguration.cs
index 5ce72cae4..c47950077 100644
--- a/DisCatSharp/DiscordConfiguration.cs
+++ b/DisCatSharp/DiscordConfiguration.cs
@@ -1,258 +1,268 @@
// This file is part of the DisCatSharp project.
//
// 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.Net;
using DisCatSharp.Net.Udp;
using DisCatSharp.Net.WebSocket;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace DisCatSharp
{
///
/// Represents configuration for and .
///
public sealed class DiscordConfiguration
{
///
/// Sets the token used to identify the client.
///
public string Token
{
internal get => this._token;
set
{
if (string.IsNullOrWhiteSpace(value))
throw new ArgumentNullException(nameof(value), "Token cannot be null, empty, or all whitespace.");
this._token = value.Trim();
}
}
private string _token = "";
///
/// Sets the type of the token used to identify the client.
/// Defaults to .
///
public TokenType TokenType { internal get; set; } = TokenType.Bot;
///
/// Sets the minimum logging level for messages.
/// Typically, the default value of is ok for most uses.
///
public LogLevel MinimumLogLevel { internal get; set; } = LogLevel.Information;
///
/// Overwrites the api version.
/// Defaults to 9.
///
public string ApiVersion { internal get; set; } = "9";
///
/// Sets whether to rely on Discord for NTP (Network Time Protocol) synchronization with the "X-Ratelimit-Reset-After" header.
/// If the system clock is unsynced, setting this to true will ensure ratelimits are synced with Discord and reduce the risk of hitting one.
/// This should only be set to false if the system clock is synced with NTP.
/// Defaults to true.
///
public bool UseRelativeRatelimit { internal get; set; } = true;
///
/// Allows you to overwrite the time format used by the internal debug logger.
/// Only applicable when is set left at default value. Defaults to ISO 8601-like format.
///
public string LogTimestampFormat { internal get; set; } = "yyyy-MM-dd HH:mm:ss zzz";
///
/// Sets the member count threshold at which guilds are considered large.
/// Defaults to 250.
///
public int LargeThreshold { internal get; set; } = 250;
///
/// Sets whether to automatically reconnect in case a connection is lost.
/// Defaults to true.
///
public bool AutoReconnect { internal get; set; } = true;
///
/// Sets the ID of the shard to connect to.
/// If not sharding, or sharding automatically, this value should be left with the default value of 0.
///
public int ShardId { internal get; set; } = 0;
///
/// Sets the total number of shards the bot is on. If not sharding, this value should be left with a default value of 1.
/// If sharding automatically, this value will indicate how many shards to boot. If left default for automatic sharding, the client will determine the shard count automatically.
///
public int ShardCount { internal get; set; } = 1;
///
/// Sets the level of compression for WebSocket traffic.
/// Disabling this option will increase the amount of traffic sent via WebSocket. Setting will enable compression for READY and GUILD_CREATE payloads. Setting will enable compression for the entire WebSocket stream, drastically reducing amount of traffic.
/// Defaults to .
///
public GatewayCompressionLevel GatewayCompressionLevel { internal get; set; } = GatewayCompressionLevel.Stream;
///
/// Sets the size of the global message cache.
/// Setting this to 0 will disable message caching entirely. Defaults to 1024.
///
public int MessageCacheSize { internal get; set; } = 1024;
///
/// Sets the proxy to use for HTTP and WebSocket connections to Discord.
/// Defaults to null.
///
public IWebProxy Proxy { internal get; set; } = null;
///
/// Sets the timeout for HTTP requests.
/// Set to to disable timeouts.
/// Defaults to 20 seconds.
///
public TimeSpan HttpTimeout { internal get; set; } = TimeSpan.FromSeconds(20);
///
/// Defines that the client should attempt to reconnect indefinitely.
/// This is typically a very bad idea to set to true, as it will swallow all connection errors.
/// Defaults to false.
///
public bool ReconnectIndefinitely { internal get; set; } = false;
///
/// Sets whether the client should attempt to cache members if exclusively using unprivileged intents.
///
/// This will only take effect if there are no or
/// intents specified. Otherwise, this will always be overwritten to true.
///
/// Defaults to true.
///
public bool AlwaysCacheMembers { internal get; set; } = true;
///
/// Sets the gateway intents for this client.
/// If set, the client will only receive events that they specify with intents.
/// Defaults to .
///
public DiscordIntents Intents { internal get; set; } = DiscordIntents.AllUnprivileged;
///
/// Sets the factory method used to create instances of WebSocket clients.
/// Use and equivalents on other implementations to switch out client implementations.
/// Defaults to .
///
public WebSocketClientFactoryDelegate WebSocketClientFactory
{
internal get => this._webSocketClientFactory;
set
{
if (value == null)
throw new InvalidOperationException("You need to supply a valid WebSocket client factory method.");
this._webSocketClientFactory = value;
}
}
private WebSocketClientFactoryDelegate _webSocketClientFactory = WebSocketClient.CreateNew;
///
/// Sets the factory method used to create instances of UDP clients.
/// Use and equivalents on other implementations to switch out client implementations.
/// Defaults to .
///
public UdpClientFactoryDelegate UdpClientFactory
{
internal get => this._udpClientFactory;
set => this._udpClientFactory = value ?? throw new InvalidOperationException("You need to supply a valid UDP client factory method.");
}
private UdpClientFactoryDelegate _udpClientFactory = DCSUdpClient.CreateNew;
///
/// Sets the logger implementation to use.
/// To create your own logger, implement the instance.
/// Defaults to built-in implementation.
///
public ILoggerFactory LoggerFactory { internal get; set; } = null;
///
/// Sets if the bot's status should show the mobile icon.
/// Defaults to false.
///
public bool MobileStatus { internal get; set; } = false;
///
/// Use canary.
/// Defaults to false.
///
public bool UseCanary { internal get; set; } = false;
///
/// Refresh full guild channel cache.
/// Defaults to false.
///
public bool AutoRefreshChannelCache { internal get; set; } = false;
///
/// Sets the service provider.
/// This allows passing data around without resorting to static members.
/// Defaults to null.
///
- public IServiceProvider Services { internal get; set; } = new ServiceCollection().BuildServiceProvider(true);
+ public IServiceProvider ServiceProvider { internal get; set; } = new ServiceCollection().BuildServiceProvider(true);
///
/// Creates a new configuration with default values.
///
public DiscordConfiguration()
{ }
+ ///
+ /// Utilized via Dependency Injection Pipeline
+ ///
+ ///
+ [ActivatorUtilitiesConstructor]
+ public DiscordConfiguration(IServiceProvider provider)
+ {
+ this.ServiceProvider = provider;
+ }
+
///
/// Creates a clone of another discord configuration.
///
/// Client configuration to clone.
public DiscordConfiguration(DiscordConfiguration other)
{
this.Token = other.Token;
this.TokenType = other.TokenType;
this.MinimumLogLevel = other.MinimumLogLevel;
this.UseRelativeRatelimit = other.UseRelativeRatelimit;
this.LogTimestampFormat = other.LogTimestampFormat;
this.LargeThreshold = other.LargeThreshold;
this.AutoReconnect = other.AutoReconnect;
this.ShardId = other.ShardId;
this.ShardCount = other.ShardCount;
this.GatewayCompressionLevel = other.GatewayCompressionLevel;
this.MessageCacheSize = other.MessageCacheSize;
this.WebSocketClientFactory = other.WebSocketClientFactory;
this.UdpClientFactory = other.UdpClientFactory;
this.Proxy = other.Proxy;
this.HttpTimeout = other.HttpTimeout;
this.ReconnectIndefinitely = other.ReconnectIndefinitely;
this.Intents = other.Intents;
this.LoggerFactory = other.LoggerFactory;
this.MobileStatus = other.MobileStatus;
this.UseCanary = other.UseCanary;
this.AutoRefreshChannelCache = other.AutoRefreshChannelCache;
this.ApiVersion = other.ApiVersion;
- this.Services = other.Services;
+ this.ServiceProvider = other.ServiceProvider;
}
}
}