diff --git a/DisCatSharp/Clients/BaseDiscordClient.cs b/DisCatSharp/Clients/BaseDiscordClient.cs
index 28ed752e8..9d1721702 100644
--- a/DisCatSharp/Clients/BaseDiscordClient.cs
+++ b/DisCatSharp/Clients/BaseDiscordClient.cs
@@ -1,483 +1,498 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
// Copyright (c) 2021-2023 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.IO;
using System.Linq;
using System.Net.Http;
using System.Reflection;
using System.Threading.Tasks;
using DisCatSharp.Attributes;
using DisCatSharp.Entities;
using DisCatSharp.Enums;
using DisCatSharp.Exceptions;
using DisCatSharp.Net;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Console;
using Microsoft.Extensions.Options;
using Sentry;
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 sentry client.
///
internal SentryClient Sentry { get; set; }
///
/// Gets the sentry dsn.
///
internal static string SentryDsn
=> "https://1da216e26a2741b99e8ccfccea1b7ac8@o1113828.ingest.sentry.io/4504901362515968";
///
/// Gets the configuration.
///
internal protected DiscordConfiguration Configuration { get; }
///
/// Gets the instance of the logger for this client.
///
public ILogger Logger { get; internal set; }
///
/// Gets the string representing the version of bot lib.
///
public string VersionString { get; }
///
/// Gets the bot library name.
///
public string BotLibrary
=> "DisCatSharp";
///
/// Gets the current user.
///
public DiscordUser CurrentUser { get; internal set; }
///
/// Gets the current application.
///
public DiscordApplication CurrentApplication { get; internal set; }
///
/// Exposes a Http Client for custom operations.
///
public HttpClient RestClient { get; internal set; }
///
/// Gets the cached guilds for this client.
///
public abstract IReadOnlyDictionary Guilds { get; }
///
/// Gets the cached users for this client.
///
public ConcurrentDictionary UserCache { get; internal set; }
///
/// Gets the service provider.
/// This allows passing data around without resorting to static members.
/// Defaults to null.
///
internal IServiceProvider ServiceProvider { get; set; }
///
/// Gets the list of available voice regions. Note that this property will not contain VIP voice regions.
///
public IReadOnlyDictionary VoiceRegions
=> this.VoiceRegionsLazy.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> VoiceRegionsLazy;
///
/// Initializes this Discord API client.
///
/// Configuration for this client.
protected BaseDiscordClient(DiscordConfiguration config)
{
this.Configuration = new DiscordConfiguration(config);
this.ServiceProvider = config.ServiceProvider;
if (this.ServiceProvider != null)
{
this.Configuration.LoggerFactory ??= config.ServiceProvider.GetService()!;
this.Logger = config.ServiceProvider.GetService>()!;
}
if (this.Configuration.LoggerFactory == null && !this.Configuration.EnableSentry)
{
this.Configuration.LoggerFactory = new DefaultLoggerFactory();
this.Configuration.LoggerFactory.AddProvider(new DefaultLoggerProvider(this));
}
else if (this.Configuration.LoggerFactory == null && this.Configuration.EnableSentry)
{
var configureNamedOptions = new ConfigureNamedOptions(string.Empty, x =>
{
x.Format = ConsoleLoggerFormat.Default;
x.TimestampFormat = this.Configuration.LogTimestampFormat;
x.LogToStandardErrorThreshold = this.Configuration.MinimumLogLevel;
});
var optionsFactory = new OptionsFactory(new[] { configureNamedOptions }, Enumerable.Empty>());
var optionsMonitor = new OptionsMonitor(optionsFactory, Enumerable.Empty>(), new OptionsCache());
var l = new ConsoleLoggerProvider(optionsMonitor);
this.Configuration.LoggerFactory = new LoggerFactory();
this.Configuration.LoggerFactory.AddProvider(l);
}
var ass = typeof(DiscordClient).GetTypeInfo().Assembly;
var vrs = "";
var ivr = ass.GetCustomAttribute();
if (ivr != null)
vrs = ivr.InformationalVersion;
else
{
var v = ass.GetName().Version;
vrs = v?.ToString(3);
}
if (!this.Configuration.HasShardLogger)
if (this.Configuration.LoggerFactory != null && this.Configuration.EnableSentry)
{
this.Configuration.LoggerFactory.AddSentry(o =>
{
o.InitializeSdk = true;
o.Dsn = SentryDsn;
o.DetectStartupTime = StartupTimeDetectionMode.Fast;
o.DiagnosticLevel = SentryLevel.Debug;
o.Environment = "dev";
- o.IsGlobalModeEnabled = true;
+ o.IsGlobalModeEnabled = false;
o.TracesSampleRate = 1.0;
o.ReportAssembliesMode = ReportAssembliesMode.InformationalVersion;
o.AddInAppInclude("DisCatSharp");
o.AttachStacktrace = true;
o.StackTraceMode = StackTraceMode.Enhanced;
o.Release = $"{this.BotLibrary}@{vrs}";
o.SendClientReports = true;
o.IsEnvironmentUser = false;
o.UseAsyncFileIO = true;
o.EnableScopeSync = true;
+ o.AddExceptionFilter(new DisCatSharpExceptionFilter(this.Configuration));
o.BeforeSend = e =>
{
- if (e.Exception?.Message?.ToLower()?.Contains("connection") ?? false)
+ if (e.Exception != null)
+ {
+ if (!this.Configuration.TrackExceptions.Contains(e.Exception.GetType()))
+ return null;
+ }
+ else if (e.Extra.Count == 0 || !e.Extra.ContainsKey("Found Fields"))
return null;
+
if (!e.HasUser())
if (this.Configuration.AttachUserInfo && this.CurrentUser! != null!)
e.User = new()
{
Id = this.CurrentUser.Id.ToString(),
Username = this.CurrentUser.UsernameWithDiscriminator,
Other = new Dictionary()
{
{ "developer", this.Configuration.DeveloperUserId?.ToString() ?? "not_given" },
{ "email", this.Configuration.FeedbackEmail ?? "not_given" }
}
};
return e;
};
});
}
if (this.Configuration.EnableSentry)
this.Sentry = new SentryClient(new SentryOptions()
{
DetectStartupTime = StartupTimeDetectionMode.Fast,
DiagnosticLevel = SentryLevel.Debug,
Environment = "dev",
- IsGlobalModeEnabled = true,
+ IsGlobalModeEnabled = false,
TracesSampleRate = 1.0,
ReportAssembliesMode = ReportAssembliesMode.InformationalVersion,
Dsn = SentryDsn,
AttachStacktrace = true,
StackTraceMode = StackTraceMode.Enhanced,
SendClientReports = true,
Release = $"{this.BotLibrary}@{vrs}",
IsEnvironmentUser = false,
UseAsyncFileIO = true,
EnableScopeSync = true,
BeforeSend = e =>
{
+ if (e.Exception != null)
+ {
+ if (!this.Configuration.TrackExceptions.Contains(e.Exception.GetType()))
+ return null;
+ }
+ else if (e.Extra.Count == 0 || !e.Extra.ContainsKey("Found Fields"))
+ return null;
+
if (!e.HasUser())
if (this.Configuration.AttachUserInfo && this.CurrentUser! != null!)
e.User = new()
{
Id = this.CurrentUser.Id.ToString(),
Username = this.CurrentUser.UsernameWithDiscriminator,
Other = new Dictionary()
{
{ "developer", this.Configuration.DeveloperUserId?.ToString() ?? "not_given" },
{ "email", this.Configuration.FeedbackEmail ?? "not_given" }
}
};
return e;
}
});
this.Logger ??= this.Configuration.LoggerFactory!.CreateLogger();
this.ApiClient = new DiscordApiClient(this);
this.UserCache = new ConcurrentDictionary();
this.InternalVoiceRegions = new ConcurrentDictionary();
this.VoiceRegionsLazy = new Lazy>(() => new ReadOnlyDictionary(this.InternalVoiceRegions));
this.RestClient = new();
this.RestClient.DefaultRequestHeaders.TryAddWithoutValidation("User-Agent", Utilities.GetUserAgent());
this.RestClient.DefaultRequestHeaders.TryAddWithoutValidation("x-discord-locale", this.Configuration.Locale);
this.RestClient.DefaultRequestHeaders.TryAddWithoutValidation("x-discord-timezone", this.Configuration.Timezone);
if (this.Configuration.Override != null)
this.RestClient.DefaultRequestHeaders.TryAddWithoutValidation("x-super-properties", this.Configuration.Override);
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}";
}
}
///
/// Gets the current API application.
///
public async Task GetCurrentApplicationAsync()
{
var tapp = await this.ApiClient.GetCurrentApplicationOauth2InfoAsync().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,
IsHook = tapp.IsHook,
Type = tapp.Type,
PrivacyPolicyUrl = tapp.PrivacyPolicyUrl,
TermsOfServiceUrl = tapp.TermsOfServiceUrl,
CustomInstallUrl = tapp.CustomInstallUrl,
InstallParams = tapp.InstallParams,
RoleConnectionsVerificationUrl = tapp.RoleConnectionsVerificationUrl,
Tags = (tapp.Tags ?? Enumerable.Empty()).ToArray()
};
if (tapp.Team == null)
{
app.Owners = new List(new[] { new DiscordUser(tapp.Owner) });
app.Team = null;
app.TeamName = null;
}
else
{
app.Team = new DiscordTeam(tapp.Team);
var members = tapp.Team.Members
.Select(x => new DiscordTeamMember(x) { TeamId = app.Team.Id, TeamName = app.Team.Name, User = new DiscordUser(x.User) })
.ToArray();
var owners = members
.Where(x => x.MembershipStatus == DiscordTeamMembershipStatus.Accepted)
.Select(x => x.User)
.ToArray();
app.Owners = new List(owners);
app.Team.Owner = owners.FirstOrDefault(x => x.Id == tapp.Team.OwnerId);
app.Team.Members = new List(members);
app.TeamName = app.Team.Name;
}
app.GuildId = tapp.GuildId.ValueOrDefault();
app.Slug = tapp.Slug.ValueOrDefault();
app.PrimarySkuId = tapp.PrimarySkuId.ValueOrDefault();
app.VerifyKey = tapp.VerifyKey.ValueOrDefault();
app.CoverImageHash = tapp.CoverImageHash.ValueOrDefault();
app.Guild = tapp.Guild.ValueOrDefault();
app.ApproximateGuildCount = tapp.ApproximateGuildCount.ValueOrDefault();
app.RequiresCodeGrant = tapp.BotRequiresCodeGrant.ValueOrDefault();
app.IsPublic = tapp.IsPublicBot.ValueOrDefault();
app.RedirectUris = tapp.RedirectUris.ValueOrDefault();
app.InteractionsEndpointUrl = tapp.InteractionsEndpointUrl.ValueOrDefault();
return app;
}
///
/// Updates the current API application.
///
/// The new description.
/// The new interactions endpoint url.
/// The new role connections verification url.
/// The new tags.
/// The new application icon.
/// The updated application.
public async Task UpdateCurrentApplicationInfoAsync(Optional description, Optional interactionsEndpointUrl, Optional roleConnectionsVerificationUrl, Optional?> tags, Optional icon)
{
var iconb64 = ImageTool.Base64FromStream(icon);
if (tags != null && tags.HasValue && tags.Value != null)
if (tags.Value.Any(x => x.Length > 20))
throw new InvalidOperationException("Tags can not exceed 20 chars.");
_ = await this.ApiClient.ModifyCurrentApplicationInfoAsync(description, interactionsEndpointUrl, roleConnectionsVerificationUrl, tags, iconb64);
// We use GetCurrentApplicationAsync because modify returns internal data not meant for developers.
var app = await this.GetCurrentApplicationAsync();
this.CurrentApplication = app;
return app;
}
///
/// Gets a list of voice 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.IsEmpty)
{
var vrs = await this.ListVoiceRegionsAsync().ConfigureAwait(false);
foreach (var xvr in vrs)
this.InternalVoiceRegions.TryAdd(xvr.Id, xvr);
}
if (this.Configuration.EnableSentry && this.Configuration.AttachUserInfo)
SentrySdk.ConfigureScope(x => x.User = new User()
{
Id = this.CurrentUser.Id.ToString(),
Username = this.CurrentUser.UsernameWithDiscriminator,
Other = new Dictionary()
{
{ "developer", this.Configuration.DeveloperUserId?.ToString() ?? "not_given" },
{ "email", this.Configuration.FeedbackEmail ?? "not_given" }
}
});
}
///
/// 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 some information about the development team behind DisCatSharp.
/// Can be used for crediting etc.
/// Note: This call contacts servers managed by the DCS team, no information is collected.
/// The team, or null with errors being logged on failure.
///
[Deprecated("Don't use this right now, inactive")]
public async Task GetLibraryDevelopmentTeamAsync()
=> await DisCatSharpTeam.Get(this.RestClient, this.Logger, this.ApiClient).ConfigureAwait(false);
///
/// Gets a cached user.
///
/// The user id.
internal DiscordUser GetCachedOrEmptyUserInternal(ulong userId)
{
this.TryGetCachedUserInternal(userId, out var user);
return user;
}
///
/// Tries the get a cached user.
///
/// The user id.
/// The user.
internal bool TryGetCachedUserInternal(ulong userId, out DiscordUser user)
{
if (this.UserCache.TryGetValue(userId, out user))
return true;
user = new DiscordUser { Id = userId, Discord = this };
return false;
}
///
/// Disposes this client.
///
public abstract void Dispose();
}
diff --git a/DisCatSharp/Clients/DiscordShardedClient.cs b/DisCatSharp/Clients/DiscordShardedClient.cs
index b146d98f5..621ca8b6e 100644
--- a/DisCatSharp/Clients/DiscordShardedClient.cs
+++ b/DisCatSharp/Clients/DiscordShardedClient.cs
@@ -1,871 +1,880 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
// Copyright (c) 2021-2023 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.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Net.Http;
using System.Reflection;
using System.Threading.Tasks;
using DisCatSharp.Common.Utilities;
using DisCatSharp.Entities;
using DisCatSharp.Enums;
using DisCatSharp.EventArgs;
+using DisCatSharp.Exceptions;
using DisCatSharp.Net;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Console;
using Microsoft.Extensions.Options;
using Newtonsoft.Json.Linq;
using Sentry;
namespace DisCatSharp;
///
/// A Discord client that shards automatically.
///
public sealed partial class DiscordShardedClient
{
#region Public Properties
///
/// Gets the logger for this client.
///
public ILogger Logger { get; }
///
/// Gets all client shards.
///
public IReadOnlyDictionary ShardClients { get; }
///
/// Gets the gateway info for the client's session.
///
public GatewayInfo GatewayInfo { get; private set; }
///
/// Gets the current user.
///
public DiscordUser CurrentUser { get; private set; }
///
/// Gets the bot library name.
///
public string BotLibrary
=> "DisCatSharp";
///
/// Gets the current application.
///
public DiscordApplication CurrentApplication { get; private set; }
///
/// Gets the list of available voice regions. Note that this property will not contain VIP voice regions.
///
public IReadOnlyDictionary VoiceRegions
=> this._voiceRegionsLazy?.Value;
#endregion
#region Private Properties/Fields
///
/// Gets the configuration.
///
private readonly DiscordConfiguration _configuration;
///
/// Gets the list of available voice regions. This property is meant as a way to modify .
///
private ConcurrentDictionary _internalVoiceRegions;
///
/// Gets a list of shards.
///
private readonly ConcurrentDictionary _shards = new();
///
/// Gets a lazy list of voice regions.
///
private Lazy> _voiceRegionsLazy;
///
/// Whether the shard client is started.
///
private bool _isStarted;
///
/// Whether manual sharding is enabled.
///
private readonly bool _manuallySharding;
#endregion
#region Constructor
///
/// Initializes a new auto-sharding Discord client.
///
/// The configuration to use.
public DiscordShardedClient(DiscordConfiguration config)
{
this.InternalSetup();
if (config.ShardCount > 1)
this._manuallySharding = true;
this._configuration = config;
this.ShardClients = new ReadOnlyConcurrentDictionary(this._shards);
if (this._configuration.LoggerFactory == null && !this._configuration.EnableSentry)
{
this._configuration.LoggerFactory = new DefaultLoggerFactory();
this._configuration.LoggerFactory.AddProvider(new DefaultLoggerProvider(this._configuration.MinimumLogLevel, this._configuration.LogTimestampFormat));
}
else if (this._configuration.LoggerFactory == null && this._configuration.EnableSentry)
{
var configureNamedOptions = new ConfigureNamedOptions(string.Empty, x =>
{
x.Format = ConsoleLoggerFormat.Default;
x.TimestampFormat = this._configuration.LogTimestampFormat;
x.LogToStandardErrorThreshold = this._configuration.MinimumLogLevel;
}); var optionsFactory = new OptionsFactory(new[] { configureNamedOptions }, Enumerable.Empty>());
var optionsMonitor = new OptionsMonitor(optionsFactory, Enumerable.Empty>(), new OptionsCache());
var l = new ConsoleLoggerProvider(optionsMonitor);
this._configuration.LoggerFactory = new LoggerFactory();
this._configuration.LoggerFactory.AddProvider(l);
}
if (this._configuration.LoggerFactory != null && this._configuration.EnableSentry)
this._configuration.LoggerFactory.AddSentry(o =>
{
var a = typeof(DiscordClient).GetTypeInfo().Assembly;
var vs = "";
var iv = a.GetCustomAttribute();
if (iv != null)
vs = iv.InformationalVersion;
else
{
var v = a.GetName().Version;
vs = v?.ToString(3);
}
o.InitializeSdk = true;
o.Dsn = BaseDiscordClient.SentryDsn;
o.DetectStartupTime = StartupTimeDetectionMode.Fast;
o.DiagnosticLevel = SentryLevel.Debug;
o.Environment = "dev";
- o.IsGlobalModeEnabled = true;
+ o.IsGlobalModeEnabled = false;
o.TracesSampleRate = 1.0;
o.ReportAssembliesMode = ReportAssembliesMode.InformationalVersion;
o.AddInAppInclude("DisCatSharp");
o.AttachStacktrace = true;
o.StackTraceMode = StackTraceMode.Enhanced;
o.Release = $"{this.BotLibrary}@{vs}";
o.SendClientReports = true;
+ o.AddExceptionFilter(new DisCatSharpExceptionFilter(this._configuration));
o.IsEnvironmentUser = false;
o.UseAsyncFileIO = true;
+ o.Debug = true;
o.EnableScopeSync = true;
o.BeforeSend = e =>
{
- if (e.Exception?.Message?.ToLower()?.Contains("connection") ?? false)
+ if (e.Exception != null)
+ {
+ if (!this._configuration.TrackExceptions.Contains(e.Exception.GetType()))
+ return null;
+ }
+ else if (e.Extra.Count == 0 || !e.Extra.ContainsKey("Found Fields"))
return null;
+
if (!e.HasUser())
if (this._configuration.AttachUserInfo && this.CurrentUser! != null!)
e.User = new()
{
Id = this.CurrentUser.Id.ToString(),
Username = this.CurrentUser.UsernameWithDiscriminator,
Other = new Dictionary()
{
{ "developer", this._configuration.DeveloperUserId?.ToString() ?? "not_given" },
{ "email", this._configuration.FeedbackEmail ?? "not_given" }
}
};
return e;
};
});
this._configuration.HasShardLogger = true;
this.Logger ??= this._configuration.LoggerFactory!.CreateLogger();
}
#endregion
#region Public Methods
///
/// Initializes and connects all shards.
///
///
///
public async Task StartAsync()
{
if (this._isStarted)
throw new InvalidOperationException("This client has already been started.");
this._isStarted = true;
try
{
if (this._configuration.TokenType != TokenType.Bot)
this.Logger.LogWarning(LoggerEvents.Misc, "You are logging in with a token that is not a bot token. This is not officially supported by Discord, and can result in your account being terminated if you aren't careful.");
this.Logger.LogInformation(LoggerEvents.Startup, "Lib {0}, version {1}", this._botLibrary, this._versionString.Value);
var shardc = await this.InitializeShardsAsync().ConfigureAwait(false);
var connectTasks = new List();
this.Logger.LogInformation(LoggerEvents.ShardStartup, "Booting {0} shards.", shardc);
for (var i = 0; i < shardc; i++)
{
//This should never happen, but in case it does...
if (this.GatewayInfo.SessionBucket.MaxConcurrency < 1)
this.GatewayInfo.SessionBucket.MaxConcurrency = 1;
if (this.GatewayInfo.SessionBucket.MaxConcurrency == 1)
await this.ConnectShardAsync(i).ConfigureAwait(false);
else
{
//Concurrent login.
connectTasks.Add(this.ConnectShardAsync(i));
if (connectTasks.Count == this.GatewayInfo.SessionBucket.MaxConcurrency)
{
await Task.WhenAll(connectTasks).ConfigureAwait(false);
connectTasks.Clear();
}
}
}
}
catch (Exception ex)
{
await this.InternalStopAsync(false).ConfigureAwait(false);
var message = $"Shard initialization failed, check inner exceptions for details: ";
this.Logger.LogCritical(LoggerEvents.ShardClientError, $"{message}\n{ex}");
throw new AggregateException(message, ex);
}
}
///
/// Disconnects and disposes all shards.
///
///
public Task StopAsync()
=> this.InternalStopAsync();
///
/// Gets a shard from a guild id.
///
/// If automatically sharding, this will use the method.
/// Otherwise if manually sharding, it will instead iterate through each shard's guild caches.
///
///
/// The guild ID for the shard.
/// The found shard. Otherwise null if the shard was not found for the guild id.
public DiscordClient GetShard(ulong guildId)
{
var index = this._manuallySharding ? this.GetShardIdFromGuilds(guildId) : Utilities.GetShardId(guildId, this.ShardClients.Count);
return index != -1 ? this._shards[index] : null;
}
///
/// Gets a shard from a guild.
///
/// If automatically sharding, this will use the method.
/// Otherwise if manually sharding, it will instead iterate through each shard's guild caches.
///
///
/// The guild for the shard.
/// The found shard. Otherwise null if the shard was not found for the guild.
public DiscordClient GetShard(DiscordGuild guild)
=> this.GetShard(guild.Id);
///
/// Updates the status on all shards.
///
/// The activity to set. Defaults to null.
/// The optional status to set. Defaults to null.
/// Since when is the client performing the specified activity. Defaults to null.
/// Asynchronous operation.
public async Task UpdateStatusAsync(DiscordActivity activity = null, UserStatus? userStatus = null, DateTimeOffset? idleSince = null)
{
var tasks = new List();
foreach (var client in this._shards.Values)
tasks.Add(client.UpdateStatusAsync(activity, userStatus, idleSince));
await Task.WhenAll(tasks).ConfigureAwait(false);
}
///
///
///
[Obsolete("Don't use this right now, inactive")]
public async Task GetLibraryDevelopmentTeamAsync()
=> await this.GetShard(0).GetLibraryDevelopmentTeamAsync().ConfigureAwait(false);
#endregion
#region Internal Methods
///
/// Initializes the shards.
///
/// The count of initialized shards.
internal async Task InitializeShardsAsync()
{
if (!this._shards.IsEmpty)
return this._shards.Count;
this.GatewayInfo = await this.GetGatewayInfoAsync().ConfigureAwait(false);
var shardCount = this._configuration.ShardCount == 1 ? this.GatewayInfo.ShardCount : this._configuration.ShardCount;
var lf = new ShardedLoggerFactory(this.Logger);
for (var i = 0; i < shardCount; i++)
{
var cfg = new DiscordConfiguration(this._configuration)
{
ShardId = i,
ShardCount = shardCount,
LoggerFactory = lf
};
var client = new DiscordClient(cfg);
if (!this._shards.TryAdd(i, client))
throw new InvalidOperationException("Could not initialize shards.");
}
return shardCount;
}
#endregion
#region Private Methods & Version Property
///
/// Gets the gateway info.
///
private async Task GetGatewayInfoAsync()
{
var url = $"{Utilities.GetApiBaseUri(this._configuration)}{Endpoints.GATEWAY}{Endpoints.BOT}";
var http = new HttpClient();
http.DefaultRequestHeaders.TryAddWithoutValidation("User-Agent", Utilities.GetUserAgent());
http.DefaultRequestHeaders.TryAddWithoutValidation("Authorization", Utilities.GetFormattedToken(this._configuration));
http.DefaultRequestHeaders.TryAddWithoutValidation("x-discord-locale", this._configuration.Locale);
http.DefaultRequestHeaders.TryAddWithoutValidation("x-discord-timezone", this._configuration.Timezone);
if (this._configuration.Override != null)
http.DefaultRequestHeaders.TryAddWithoutValidation("x-super-properties", this._configuration.Override);
this.Logger.LogDebug(LoggerEvents.ShardRest, $"Obtaining gateway information from GET {Endpoints.GATEWAY}{Endpoints.BOT}...");
var resp = await http.GetAsync(url).ConfigureAwait(false);
http.Dispose();
if (!resp.IsSuccessStatusCode)
{
var ratelimited = await HandleHttpError(url, resp).ConfigureAwait(false);
if (ratelimited)
return await this.GetGatewayInfoAsync().ConfigureAwait(false);
}
var timer = new Stopwatch();
timer.Start();
var jo = JObject.Parse(await resp.Content.ReadAsStringAsync().ConfigureAwait(false));
var info = jo.ToObject();
//There is a delay from parsing here.
timer.Stop();
info.SessionBucket.ResetAfterInternal -= (int)timer.ElapsedMilliseconds;
info.SessionBucket.ResetAfter = DateTimeOffset.UtcNow + TimeSpan.FromMilliseconds(info.SessionBucket.ResetAfterInternal);
return info;
async Task HandleHttpError(string reqUrl, HttpResponseMessage msg)
{
var code = (int)msg.StatusCode;
if (code == 401 || code == 403)
{
throw new Exception($"Authentication failed, check your token and try again: {code} {msg.ReasonPhrase}");
}
else if (code == 429)
{
this.Logger.LogError(LoggerEvents.ShardClientError, $"Ratelimit hit, requeuing request to {reqUrl}");
var hs = msg.Headers.ToDictionary(xh => xh.Key, xh => string.Join("\n", xh.Value), StringComparer.OrdinalIgnoreCase);
var waitInterval = 0;
if (hs.TryGetValue("Retry-After", out var retryAfterRaw))
waitInterval = int.Parse(retryAfterRaw, CultureInfo.InvariantCulture);
await Task.Delay(waitInterval).ConfigureAwait(false);
return true;
}
else if (code >= 500)
{
throw new Exception($"Internal Server Error: {code} {msg.ReasonPhrase}");
}
else
{
throw new Exception($"An unsuccessful HTTP status code was encountered: {code} {msg.ReasonPhrase}");
}
}
}
///
/// Gets the version string.
///
private readonly Lazy _versionString = new(() =>
{
var a = typeof(DiscordShardedClient).GetTypeInfo().Assembly;
var iv = a.GetCustomAttribute();
if (iv != null)
return iv.InformationalVersion;
var v = a.GetName().Version;
var vs = v.ToString(3);
if (v.Revision > 0)
vs = $"{vs}, CI build {v.Revision}";
return vs;
});
///
/// Gets the name of the used bot library.
///
private readonly string _botLibrary = "DisCatSharp";
#endregion
#region Private Connection Methods
///
/// Connects a shard.
///
/// The shard id.
private async Task ConnectShardAsync(int i)
{
if (!this._shards.TryGetValue(i, out var client))
throw new Exception($"Could not initialize shard {i}.");
client.IsShard = true;
if (this.GatewayInfo != null)
{
client.GatewayInfo = this.GatewayInfo;
client.GatewayUri = new Uri(client.GatewayInfo.Url);
}
if (this.CurrentUser != null)
client.CurrentUser = this.CurrentUser;
if (this.CurrentApplication != null)
client.CurrentApplication = this.CurrentApplication;
if (this._internalVoiceRegions != null)
{
client.InternalVoiceRegions = this._internalVoiceRegions;
client.VoiceRegionsLazy = new Lazy>(() => new ReadOnlyDictionary(client.InternalVoiceRegions));
}
this.HookEventHandlers(client);
await client.ConnectAsync();
this.Logger.LogInformation(LoggerEvents.ShardStartup, "Booted shard {0}.", i);
this.GatewayInfo ??= client.GatewayInfo;
if (this.CurrentUser == null)
this.CurrentUser = client.CurrentUser;
if (this.CurrentApplication == null)
this.CurrentApplication = client.CurrentApplication;
if (this._internalVoiceRegions == null)
{
this._internalVoiceRegions = client.InternalVoiceRegions;
this._voiceRegionsLazy = new Lazy>(() => new ReadOnlyDictionary(this._internalVoiceRegions));
}
}
///
/// Stops all shards.
///
/// Whether to enable the logger.
private Task InternalStopAsync(bool enableLogger = true)
{
if (!this._isStarted)
throw new InvalidOperationException("This client has not been started.");
if (enableLogger)
this.Logger.LogInformation(LoggerEvents.ShardShutdown, "Disposing {0} shards.", this._shards.Count);
this._isStarted = false;
this._voiceRegionsLazy = null;
this.GatewayInfo = null;
this.CurrentUser = null;
this.CurrentApplication = null;
for (var i = 0; i < this._shards.Count; i++)
{
if (this._shards.TryGetValue(i, out var client))
{
this.UnhookEventHandlers(client);
client.Dispose();
if (enableLogger)
this.Logger.LogInformation(LoggerEvents.ShardShutdown, "Disconnected shard {0}.", i);
}
}
this._shards.Clear();
return Task.CompletedTask;
}
#endregion
#region Event Handler Initialization/Registering
///
/// Sets the shard client up internally..
///
private void InternalSetup()
{
this._clientErrored = new AsyncEvent("CLIENT_ERRORED", DiscordClient.EventExecutionLimit, this.Goof);
this._socketErrored = new AsyncEvent("SOCKET_ERRORED", DiscordClient.EventExecutionLimit, this.Goof);
this._socketOpened = new AsyncEvent("SOCKET_OPENED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._socketClosed = new AsyncEvent("SOCKET_CLOSED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._ready = new AsyncEvent("READY", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._resumed = new AsyncEvent("RESUMED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._channelCreated = new AsyncEvent("CHANNEL_CREATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._channelUpdated = new AsyncEvent("CHANNEL_UPDATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._channelDeleted = new AsyncEvent("CHANNEL_DELETED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._dmChannelDeleted = new AsyncEvent("DM_CHANNEL_DELETED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._channelPinsUpdated = new AsyncEvent("CHANNEL_PINS_UPDATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._guildCreated = new AsyncEvent("GUILD_CREATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._guildAvailable = new AsyncEvent("GUILD_AVAILABLE", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._guildUpdated = new AsyncEvent("GUILD_UPDATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._guildDeleted = new AsyncEvent("GUILD_DELETED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._guildUnavailable = new AsyncEvent("GUILD_UNAVAILABLE", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._guildDownloadCompleted = new AsyncEvent("GUILD_DOWNLOAD_COMPLETED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._inviteCreated = new AsyncEvent("INVITE_CREATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._inviteDeleted = new AsyncEvent("INVITE_DELETED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._messageCreated = new AsyncEvent("MESSAGE_CREATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._presenceUpdated = new AsyncEvent("PRESENCE_UPDATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._guildBanAdded = new AsyncEvent("GUILD_BAN_ADDED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._guildBanRemoved = new AsyncEvent("GUILD_BAN_REMOVED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._guildEmojisUpdated = new AsyncEvent("GUILD_EMOJI_UPDATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._guildStickersUpdated = new AsyncEvent("GUILD_STICKER_UPDATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._guildIntegrationsUpdated = new AsyncEvent("GUILD_INTEGRATIONS_UPDATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._guildMemberAdded = new AsyncEvent("GUILD_MEMBER_ADDED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._guildMemberRemoved = new AsyncEvent("GUILD_MEMBER_REMOVED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._guildMemberUpdated = new AsyncEvent("GUILD_MEMBER_UPDATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._guildRoleCreated = new AsyncEvent("GUILD_ROLE_CREATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._guildRoleUpdated = new AsyncEvent("GUILD_ROLE_UPDATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._guildRoleDeleted = new AsyncEvent("GUILD_ROLE_DELETED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._messageUpdated = new AsyncEvent("MESSAGE_UPDATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._messageDeleted = new AsyncEvent("MESSAGE_DELETED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._messageBulkDeleted = new AsyncEvent("MESSAGE_BULK_DELETED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._interactionCreated = new AsyncEvent("INTERACTION_CREATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._componentInteractionCreated = new AsyncEvent("COMPONENT_INTERACTED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._contextMenuInteractionCreated = new AsyncEvent("CONTEXT_MENU_INTERACTED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._typingStarted = new AsyncEvent("TYPING_STARTED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._userSettingsUpdated = new AsyncEvent("USER_SETTINGS_UPDATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._userUpdated = new AsyncEvent("USER_UPDATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._voiceStateUpdated = new AsyncEvent("VOICE_STATE_UPDATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._voiceServerUpdated = new AsyncEvent("VOICE_SERVER_UPDATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._guildMembersChunk = new AsyncEvent("GUILD_MEMBERS_CHUNKED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._unknownEvent = new AsyncEvent("UNKNOWN_EVENT", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._messageReactionAdded = new AsyncEvent("MESSAGE_REACTION_ADDED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._messageReactionRemoved = new AsyncEvent("MESSAGE_REACTION_REMOVED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._messageReactionsCleared = new AsyncEvent("MESSAGE_REACTIONS_CLEARED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._messageReactionRemovedEmoji = new AsyncEvent("MESSAGE_REACTION_REMOVED_EMOJI", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._webhooksUpdated = new AsyncEvent("WEBHOOKS_UPDATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._heartbeated = new AsyncEvent("HEARTBEATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._applicationCommandCreated = new AsyncEvent("APPLICATION_COMMAND_CREATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._applicationCommandUpdated = new AsyncEvent("APPLICATION_COMMAND_UPDATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._applicationCommandDeleted = new AsyncEvent("APPLICATION_COMMAND_DELETED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._guildApplicationCommandCountUpdated = new AsyncEvent("GUILD_APPLICATION_COMMAND_COUNTS_UPDATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._applicationCommandPermissionsUpdated = new AsyncEvent("APPLICATION_COMMAND_PERMISSIONS_UPDATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._guildIntegrationCreated = new AsyncEvent("INTEGRATION_CREATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._guildIntegrationUpdated = new AsyncEvent("INTEGRATION_UPDATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._guildIntegrationDeleted = new AsyncEvent("INTEGRATION_DELETED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._stageInstanceCreated = new AsyncEvent("STAGE_INSTANCE_CREATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._stageInstanceUpdated = new AsyncEvent("STAGE_INSTANCE_UPDATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._stageInstanceDeleted = new AsyncEvent("STAGE_INSTANCE_DELETED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._threadCreated = new AsyncEvent("THREAD_CREATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._threadUpdated = new AsyncEvent("THREAD_UPDATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._threadDeleted = new AsyncEvent("THREAD_DELETED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._threadListSynced = new AsyncEvent("THREAD_LIST_SYNCED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._threadMemberUpdated = new AsyncEvent("THREAD_MEMBER_UPDATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._threadMembersUpdated = new AsyncEvent("THREAD_MEMBERS_UPDATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._zombied = new AsyncEvent("ZOMBIED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._payloadReceived = new AsyncEvent("PAYLOAD_RECEIVED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._guildScheduledEventCreated = new AsyncEvent("GUILD_SCHEDULED_EVENT_CREATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._guildScheduledEventUpdated = new AsyncEvent("GUILD_SCHEDULED_EVENT_UPDATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._guildScheduledEventDeleted = new AsyncEvent("GUILD_SCHEDULED_EVENT_DELETED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._guildScheduledEventUserAdded = new AsyncEvent("GUILD_SCHEDULED_EVENT_USER_ADDED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._guildScheduledEventUserRemoved = new AsyncEvent("GUILD_SCHEDULED_EVENT_USER_REMOVED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._embeddedActivityUpdated = new AsyncEvent("EMBEDDED_ACTIVITY_UPDATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._guildMemberTimeoutAdded = new AsyncEvent("GUILD_MEMBER_TIMEOUT_ADDED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._guildMemberTimeoutChanged = new AsyncEvent("GUILD_MEMBER_TIMEOUT_UPDATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._guildMemberTimeoutRemoved = new AsyncEvent("GUILD_MEMBER_TIMEOUT_REMOVED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._automodRuleCreated = new AsyncEvent("AUTO_MODERATION_RULE_CREATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._automodRuleUpdated = new AsyncEvent("AUTO_MODERATION_RULE_UPDATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._automodRuleDeleted = new AsyncEvent("AUTO_MODERATION_RULE_DELETED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._automodActionExecuted = new AsyncEvent("AUTO_MODERATION_ACTION_EXECUTED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._guildAuditLogEntryCreated = new AsyncEvent("GUILD_AUDIT_LOG_ENTRY_CREATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
}
///
/// Hooks the event handlers.
///
/// The client.
private void HookEventHandlers(DiscordClient client)
{
client.ClientErrored += this.Client_ClientError;
client.SocketErrored += this.Client_SocketError;
client.SocketOpened += this.Client_SocketOpened;
client.SocketClosed += this.Client_SocketClosed;
client.Ready += this.Client_Ready;
client.Resumed += this.Client_Resumed;
client.ChannelCreated += this.Client_ChannelCreated;
client.ChannelUpdated += this.Client_ChannelUpdated;
client.ChannelDeleted += this.Client_ChannelDeleted;
client.DmChannelDeleted += this.Client_DMChannelDeleted;
client.ChannelPinsUpdated += this.Client_ChannelPinsUpdated;
client.GuildCreated += this.Client_GuildCreated;
client.GuildAvailable += this.Client_GuildAvailable;
client.GuildUpdated += this.Client_GuildUpdated;
client.GuildDeleted += this.Client_GuildDeleted;
client.GuildUnavailable += this.Client_GuildUnavailable;
client.GuildDownloadCompleted += this.Client_GuildDownloadCompleted;
client.InviteCreated += this.Client_InviteCreated;
client.InviteDeleted += this.Client_InviteDeleted;
client.MessageCreated += this.Client_MessageCreated;
client.PresenceUpdated += this.Client_PresenceUpdate;
client.GuildBanAdded += this.Client_GuildBanAdd;
client.GuildBanRemoved += this.Client_GuildBanRemove;
client.GuildEmojisUpdated += this.Client_GuildEmojisUpdate;
client.GuildStickersUpdated += this.Client_GuildStickersUpdate;
client.GuildIntegrationsUpdated += this.Client_GuildIntegrationsUpdate;
client.GuildMemberAdded += this.Client_GuildMemberAdd;
client.GuildMemberRemoved += this.Client_GuildMemberRemove;
client.GuildMemberUpdated += this.Client_GuildMemberUpdate;
client.GuildRoleCreated += this.Client_GuildRoleCreate;
client.GuildRoleUpdated += this.Client_GuildRoleUpdate;
client.GuildRoleDeleted += this.Client_GuildRoleDelete;
client.MessageUpdated += this.Client_MessageUpdate;
client.MessageDeleted += this.Client_MessageDelete;
client.MessagesBulkDeleted += this.Client_MessageBulkDelete;
client.InteractionCreated += this.Client_InteractionCreate;
client.ComponentInteractionCreated += this.Client_ComponentInteractionCreate;
client.ContextMenuInteractionCreated += this.Client_ContextMenuInteractionCreate;
client.TypingStarted += this.Client_TypingStart;
client.UserSettingsUpdated += this.Client_UserSettingsUpdate;
client.UserUpdated += this.Client_UserUpdate;
client.VoiceStateUpdated += this.Client_VoiceStateUpdate;
client.VoiceServerUpdated += this.Client_VoiceServerUpdate;
client.GuildMembersChunked += this.Client_GuildMembersChunk;
client.UnknownEvent += this.Client_UnknownEvent;
client.MessageReactionAdded += this.Client_MessageReactionAdd;
client.MessageReactionRemoved += this.Client_MessageReactionRemove;
client.MessageReactionsCleared += this.Client_MessageReactionRemoveAll;
client.MessageReactionRemovedEmoji += this.Client_MessageReactionRemovedEmoji;
client.WebhooksUpdated += this.Client_WebhooksUpdate;
client.Heartbeated += this.Client_HeartBeated;
client.ApplicationCommandCreated += this.Client_ApplicationCommandCreated;
client.ApplicationCommandUpdated += this.Client_ApplicationCommandUpdated;
client.ApplicationCommandDeleted += this.Client_ApplicationCommandDeleted;
client.GuildApplicationCommandCountUpdated += this.Client_GuildApplicationCommandCountUpdated;
client.ApplicationCommandPermissionsUpdated += this.Client_ApplicationCommandPermissionsUpdated;
client.GuildIntegrationCreated += this.Client_GuildIntegrationCreated;
client.GuildIntegrationUpdated += this.Client_GuildIntegrationUpdated;
client.GuildIntegrationDeleted += this.Client_GuildIntegrationDeleted;
client.StageInstanceCreated += this.Client_StageInstanceCreated;
client.StageInstanceUpdated += this.Client_StageInstanceUpdated;
client.StageInstanceDeleted += this.Client_StageInstanceDeleted;
client.ThreadCreated += this.Client_ThreadCreated;
client.ThreadUpdated += this.Client_ThreadUpdated;
client.ThreadDeleted += this.Client_ThreadDeleted;
client.ThreadListSynced += this.Client_ThreadListSynced;
client.ThreadMemberUpdated += this.Client_ThreadMemberUpdated;
client.ThreadMembersUpdated += this.Client_ThreadMembersUpdated;
client.Zombied += this.Client_Zombied;
client.PayloadReceived += this.Client_PayloadReceived;
client.GuildScheduledEventCreated += this.Client_GuildScheduledEventCreated;
client.GuildScheduledEventUpdated += this.Client_GuildScheduledEventUpdated;
client.GuildScheduledEventDeleted += this.Client_GuildScheduledEventDeleted;
client.GuildScheduledEventUserAdded += this.Client_GuildScheduledEventUserAdded; ;
client.GuildScheduledEventUserRemoved += this.Client_GuildScheduledEventUserRemoved;
client.EmbeddedActivityUpdated += this.Client_EmbeddedActivityUpdated;
client.GuildMemberTimeoutAdded += this.Client_GuildMemberTimeoutAdded;
client.GuildMemberTimeoutChanged += this.Client_GuildMemberTimeoutChanged;
client.GuildMemberTimeoutRemoved += this.Client_GuildMemberTimeoutRemoved;
client.AutomodRuleCreated += this.Client_AutomodRuleCreated;
client.AutomodRuleUpdated += this.Client_AutomodRuleUpdated;
client.AutomodRuleDeleted += this.Client_AutomodRuleDeleted;
client.AutomodActionExecuted += this.Client_AutomodActionExecuted;
client.GuildAuditLogEntryCreated += this.Client_GuildAuditLogEntryCreated;
}
///
/// Unhooks the event handlers.
///
/// The client.
private void UnhookEventHandlers(DiscordClient client)
{
client.ClientErrored -= this.Client_ClientError;
client.SocketErrored -= this.Client_SocketError;
client.SocketOpened -= this.Client_SocketOpened;
client.SocketClosed -= this.Client_SocketClosed;
client.Ready -= this.Client_Ready;
client.Resumed -= this.Client_Resumed;
client.ChannelCreated -= this.Client_ChannelCreated;
client.ChannelUpdated -= this.Client_ChannelUpdated;
client.ChannelDeleted -= this.Client_ChannelDeleted;
client.DmChannelDeleted -= this.Client_DMChannelDeleted;
client.ChannelPinsUpdated -= this.Client_ChannelPinsUpdated;
client.GuildCreated -= this.Client_GuildCreated;
client.GuildAvailable -= this.Client_GuildAvailable;
client.GuildUpdated -= this.Client_GuildUpdated;
client.GuildDeleted -= this.Client_GuildDeleted;
client.GuildUnavailable -= this.Client_GuildUnavailable;
client.GuildDownloadCompleted -= this.Client_GuildDownloadCompleted;
client.InviteCreated -= this.Client_InviteCreated;
client.InviteDeleted -= this.Client_InviteDeleted;
client.MessageCreated -= this.Client_MessageCreated;
client.PresenceUpdated -= this.Client_PresenceUpdate;
client.GuildBanAdded -= this.Client_GuildBanAdd;
client.GuildBanRemoved -= this.Client_GuildBanRemove;
client.GuildEmojisUpdated -= this.Client_GuildEmojisUpdate;
client.GuildStickersUpdated -= this.Client_GuildStickersUpdate;
client.GuildIntegrationsUpdated -= this.Client_GuildIntegrationsUpdate;
client.GuildMemberAdded -= this.Client_GuildMemberAdd;
client.GuildMemberRemoved -= this.Client_GuildMemberRemove;
client.GuildMemberUpdated -= this.Client_GuildMemberUpdate;
client.GuildRoleCreated -= this.Client_GuildRoleCreate;
client.GuildRoleUpdated -= this.Client_GuildRoleUpdate;
client.GuildRoleDeleted -= this.Client_GuildRoleDelete;
client.MessageUpdated -= this.Client_MessageUpdate;
client.MessageDeleted -= this.Client_MessageDelete;
client.MessagesBulkDeleted -= this.Client_MessageBulkDelete;
client.InteractionCreated -= this.Client_InteractionCreate;
client.ComponentInteractionCreated -= this.Client_ComponentInteractionCreate;
client.ContextMenuInteractionCreated -= this.Client_ContextMenuInteractionCreate;
client.TypingStarted -= this.Client_TypingStart;
client.UserSettingsUpdated -= this.Client_UserSettingsUpdate;
client.UserUpdated -= this.Client_UserUpdate;
client.VoiceStateUpdated -= this.Client_VoiceStateUpdate;
client.VoiceServerUpdated -= this.Client_VoiceServerUpdate;
client.GuildMembersChunked -= this.Client_GuildMembersChunk;
client.UnknownEvent -= this.Client_UnknownEvent;
client.MessageReactionAdded -= this.Client_MessageReactionAdd;
client.MessageReactionRemoved -= this.Client_MessageReactionRemove;
client.MessageReactionsCleared -= this.Client_MessageReactionRemoveAll;
client.MessageReactionRemovedEmoji -= this.Client_MessageReactionRemovedEmoji;
client.WebhooksUpdated -= this.Client_WebhooksUpdate;
client.Heartbeated -= this.Client_HeartBeated;
client.ApplicationCommandCreated -= this.Client_ApplicationCommandCreated;
client.ApplicationCommandUpdated -= this.Client_ApplicationCommandUpdated;
client.ApplicationCommandDeleted -= this.Client_ApplicationCommandDeleted;
client.GuildApplicationCommandCountUpdated -= this.Client_GuildApplicationCommandCountUpdated;
client.ApplicationCommandPermissionsUpdated -= this.Client_ApplicationCommandPermissionsUpdated;
client.GuildIntegrationCreated -= this.Client_GuildIntegrationCreated;
client.GuildIntegrationUpdated -= this.Client_GuildIntegrationUpdated;
client.GuildIntegrationDeleted -= this.Client_GuildIntegrationDeleted;
client.StageInstanceCreated -= this.Client_StageInstanceCreated;
client.StageInstanceUpdated -= this.Client_StageInstanceUpdated;
client.StageInstanceDeleted -= this.Client_StageInstanceDeleted;
client.ThreadCreated -= this.Client_ThreadCreated;
client.ThreadUpdated -= this.Client_ThreadUpdated;
client.ThreadDeleted -= this.Client_ThreadDeleted;
client.ThreadListSynced -= this.Client_ThreadListSynced;
client.ThreadMemberUpdated -= this.Client_ThreadMemberUpdated;
client.ThreadMembersUpdated -= this.Client_ThreadMembersUpdated;
client.Zombied -= this.Client_Zombied;
client.PayloadReceived -= this.Client_PayloadReceived;
client.GuildScheduledEventCreated -= this.Client_GuildScheduledEventCreated;
client.GuildScheduledEventUpdated -= this.Client_GuildScheduledEventUpdated;
client.GuildScheduledEventDeleted -= this.Client_GuildScheduledEventDeleted;
client.GuildScheduledEventUserAdded -= this.Client_GuildScheduledEventUserAdded; ;
client.GuildScheduledEventUserRemoved -= this.Client_GuildScheduledEventUserRemoved;
client.EmbeddedActivityUpdated -= this.Client_EmbeddedActivityUpdated;
client.GuildMemberTimeoutAdded -= this.Client_GuildMemberTimeoutAdded;
client.GuildMemberTimeoutChanged -= this.Client_GuildMemberTimeoutChanged;
client.GuildMemberTimeoutRemoved -= this.Client_GuildMemberTimeoutRemoved;
client.AutomodRuleCreated -= this.Client_AutomodRuleCreated;
client.AutomodRuleUpdated -= this.Client_AutomodRuleUpdated;
client.AutomodRuleDeleted -= this.Client_AutomodRuleDeleted;
client.AutomodActionExecuted -= this.Client_AutomodActionExecuted;
client.GuildAuditLogEntryCreated -= this.Client_GuildAuditLogEntryCreated;
}
///
/// Gets the shard id from guilds.
///
/// The id.
/// An int.
private int GetShardIdFromGuilds(ulong id)
{
foreach (var s in this._shards.Values)
{
if (s.GuildsInternal.TryGetValue(id, out _))
{
return s.ShardId;
}
}
return -1;
}
#endregion
#region Destructor
~DiscordShardedClient()
{
this.InternalStopAsync(false).GetAwaiter().GetResult();
}
#endregion
}
diff --git a/DisCatSharp/DiscordConfiguration.cs b/DisCatSharp/DiscordConfiguration.cs
index b8a00d92c..a14918680 100644
--- a/DisCatSharp/DiscordConfiguration.cs
+++ b/DisCatSharp/DiscordConfiguration.cs
@@ -1,365 +1,397 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
// Copyright (c) 2021-2023 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.Net;
using DisCatSharp.Attributes;
using DisCatSharp.Entities;
using DisCatSharp.Enums;
+using DisCatSharp.Exceptions;
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 10.
///
public string ApiVersion { internal get; set; } = "10";
///
/// 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 .
///
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 .
///
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 .
///
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 .
///
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 .
///
public bool AlwaysCacheMembers { internal get; set; } = true;
+ ///
+ /// Sets whether a shard logger is attached.
+ ///
+ internal bool HasShardLogger { get; set; } = false;
+
///
/// 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; }
///
/// Sets if the bot's status should show the mobile icon.
/// Defaults to .
///
public bool MobileStatus { internal get; set; } = false;
///
/// Whether to use canary. has to be false.
/// Defaults to .
///
[Deprecated("Use ApiChannel instead.")]
public bool UseCanary
{
internal get => this.ApiChannel == ApiChannel.Canary;
set
{
if (value)
this.ApiChannel = ApiChannel.Canary;
}
}
///
/// Whether to use ptb. has to be false.
/// Defaults to .
///
[Deprecated("Use ApiChannel instead.")]
public bool UsePtb
{
internal get => this.ApiChannel == ApiChannel.PTB;
set
{
if (value)
this.ApiChannel = ApiChannel.PTB;
}
}
///
/// Which api channel to use.
/// Defaults to .
///
public ApiChannel ApiChannel { internal get; set; } = ApiChannel.Stable;
///
/// Refresh full guild channel cache.
/// Defaults to .
///
public bool AutoRefreshChannelCache { internal get; set; } = false;
///
/// Do not use, this is meant for DisCatSharp Devs.
/// Defaults to .
///
public string Override { internal get; set; } = null!;
///
/// Sets your preferred API language. See for valid locales.
///
public string Locale { internal get; set; } = DiscordLocales.AMERICAN_ENGLISH;
///
/// Sets your timezone.
///
public string Timezone { internal get; set; } = "Europe/Berlin";
///
/// Whether to report missing fields for discord object.
/// Useful for library development.
/// Defaults to .
///
public bool ReportMissingFields { internal get; set; } = false;
///
/// Sets the service provider.
/// This allows passing data around without resorting to static members.
/// Defaults to an empty service provider.
///
public IServiceProvider ServiceProvider { internal get; set; } = new ServiceCollection().BuildServiceProvider(true);
///
/// Whether to report missing fields for discord object.
/// This helps us to track missing data and library bugs better.
/// Defaults to .
///
public bool EnableSentry { internal get; set; } = false;
///
/// Whether to attach the bots username and id to sentry reports.
/// This helps us to pinpoint problems.
/// Defaults to .
///
public bool AttachUserInfo { internal get; set; } = false;
///
/// Your email address we can reach out when your bot encounters library bugs.
/// Will only be transmitted if is .
/// Defaults to .
///
public string? FeedbackEmail { internal get; set; } = null;
///
/// Your discord user id we can reach out when your bot encounters library bugs.
/// Will only be transmitted if is .
/// Defaults to .
///
public ulong? DeveloperUserId { internal get; set; } = null;
- internal bool HasShardLogger { get; set; } = false;
+
+ ///
+ /// Sets which exceptions to track with sentry.
+ ///
+ /// Thrown when the base type of all exceptions is not .
+ public List TrackExceptions
+ {
+ internal get => this._exceptions;
+ set {
+ if (value == null)
+ this._exceptions.Clear();
+ else this._exceptions = value.All(val => val.BaseType == typeof(DisCatSharpException))
+ ? value
+ : throw new InvalidOperationException("Can only track exceptions who inherit from " + nameof(DisCatSharpException) + " and must be constructed with typeof(Type)");
+ }
+ }
+
+ ///
+ /// The exception we track with sentry.
+ ///
+ private List _exceptions = new()
+ {
+ typeof(ServerErrorException),
+ typeof(BadRequestException)
+ };
///
/// 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.UsePtb = other.UsePtb;
this.AutoRefreshChannelCache = other.AutoRefreshChannelCache;
this.ApiVersion = other.ApiVersion;
this.ServiceProvider = other.ServiceProvider;
this.Override = other.Override;
this.Locale = other.Locale;
this.Timezone = other.Timezone;
this.ReportMissingFields = other.ReportMissingFields;
this.EnableSentry = other.EnableSentry;
this.AttachUserInfo = other.AttachUserInfo;
this.FeedbackEmail = other.FeedbackEmail;
this.DeveloperUserId = other.DeveloperUserId;
this.HasShardLogger = other.HasShardLogger;
}
}
diff --git a/DisCatSharp/Exceptions/BadRequestException.cs b/DisCatSharp/Exceptions/BadRequestException.cs
index d5463c340..51b197475 100644
--- a/DisCatSharp/Exceptions/BadRequestException.cs
+++ b/DisCatSharp/Exceptions/BadRequestException.cs
@@ -1,86 +1,87 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
// Copyright (c) 2021-2023 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 DisCatSharp.Net;
using Newtonsoft.Json.Linq;
namespace DisCatSharp.Exceptions;
///
/// Represents an exception thrown when a malformed request is sent.
///
-public class BadRequestException : Exception
+public class BadRequestException : DisCatSharpException
{
///
/// Gets the request that caused the exception.
///
public BaseRestRequest WebRequest { get; internal set; }
///
/// Gets the response to the request.
///
public RestResponse WebResponse { get; internal set; }
///
/// Gets the error code for this exception.
///
public int Code { get; internal set; }
///
/// Gets the JSON message received.
///
public string JsonMessage { get; internal set; }
///
/// Gets the form error responses in JSON format.
///
public string Errors { get; internal set; }
///
/// Initializes a new instance of the class.
///
/// The request.
/// The response.
- internal BadRequestException(BaseRestRequest request, RestResponse response) : base("Bad request: " + response.ResponseCode)
+ internal BadRequestException(BaseRestRequest request, RestResponse response)
+ : base("Bad request: " + response.ResponseCode)
{
this.WebRequest = request;
this.WebResponse = response;
try
{
var j = JObject.Parse(response.Response);
if (j["code"] != null)
this.Code = (int)j["code"];
if (j["message"] != null)
this.JsonMessage = j["message"].ToString();
if (j["errors"] != null)
this.Errors = j["errors"].ToString();
}
catch { }
}
}
diff --git a/DisCatSharp/Exceptions/RateLimitException.cs b/DisCatSharp/Exceptions/DisCatSharpException.cs
similarity index 52%
copy from DisCatSharp/Exceptions/RateLimitException.cs
copy to DisCatSharp/Exceptions/DisCatSharpException.cs
index 6d9c8b590..ce66caadd 100644
--- a/DisCatSharp/Exceptions/RateLimitException.cs
+++ b/DisCatSharp/Exceptions/DisCatSharpException.cs
@@ -1,70 +1,32 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
// Copyright (c) 2021-2023 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 DisCatSharp.Net;
-
-using Newtonsoft.Json.Linq;
-
namespace DisCatSharp.Exceptions;
-///
-/// Represents an exception thrown when too many requests are sent.
-///
-public class RateLimitException : Exception
+public class DisCatSharpException : Exception
{
- ///
- /// Gets the request that caused the exception.
- ///
- public BaseRestRequest WebRequest { get; internal set; }
-
- ///
- /// Gets the response to the request.
- ///
- public RestResponse WebResponse { get; internal set; }
-
- ///
- /// Gets the JSON received.
- ///
- public string JsonMessage { get; internal set; }
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The request.
- /// The response.
- internal RateLimitException(BaseRestRequest request, RestResponse response) : base("Rate limited: " + response.ResponseCode)
- {
- this.WebRequest = request;
- this.WebResponse = response;
-
- try
- {
- var j = JObject.Parse(response.Response);
-
- if (j["message"] != null)
- this.JsonMessage = j["message"].ToString();
- }
- catch (Exception) { }
- }
+ internal DisCatSharpException(string message)
+ : base(message)
+ { }
}
diff --git a/DisCatSharp/Exceptions/RateLimitException.cs b/DisCatSharp/Exceptions/ExceptionFilter.cs
similarity index 52%
copy from DisCatSharp/Exceptions/RateLimitException.cs
copy to DisCatSharp/Exceptions/ExceptionFilter.cs
index 6d9c8b590..5ffbb03e1 100644
--- a/DisCatSharp/Exceptions/RateLimitException.cs
+++ b/DisCatSharp/Exceptions/ExceptionFilter.cs
@@ -1,70 +1,40 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
// Copyright (c) 2021-2023 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 DisCatSharp.Net;
-
-using Newtonsoft.Json.Linq;
+using Sentry.Extensibility;
namespace DisCatSharp.Exceptions;
-///
-/// Represents an exception thrown when too many requests are sent.
-///
-public class RateLimitException : Exception
+public class DisCatSharpExceptionFilter : IExceptionFilter
{
- ///
- /// Gets the request that caused the exception.
- ///
- public BaseRestRequest WebRequest { get; internal set; }
-
- ///
- /// Gets the response to the request.
- ///
- public RestResponse WebResponse { get; internal set; }
+ internal DiscordConfiguration config { get; set; }
- ///
- /// Gets the JSON received.
- ///
- public string JsonMessage { get; internal set; }
+ public bool Filter(Exception ex)
+ => !this.config.TrackExceptions.Contains(ex.GetType());
- ///
- /// Initializes a new instance of the class.
- ///
- /// The request.
- /// The response.
- internal RateLimitException(BaseRestRequest request, RestResponse response) : base("Rate limited: " + response.ResponseCode)
+ internal DisCatSharpExceptionFilter(DiscordConfiguration configuration)
{
- this.WebRequest = request;
- this.WebResponse = response;
-
- try
- {
- var j = JObject.Parse(response.Response);
-
- if (j["message"] != null)
- this.JsonMessage = j["message"].ToString();
- }
- catch (Exception) { }
+ this.config = configuration;
}
}
diff --git a/DisCatSharp/Exceptions/NotFoundException.cs b/DisCatSharp/Exceptions/NotFoundException.cs
index 1b48f0d30..1b6a97ed3 100644
--- a/DisCatSharp/Exceptions/NotFoundException.cs
+++ b/DisCatSharp/Exceptions/NotFoundException.cs
@@ -1,70 +1,71 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
// Copyright (c) 2021-2023 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 DisCatSharp.Net;
using Newtonsoft.Json.Linq;
namespace DisCatSharp.Exceptions;
///
/// Represents an exception thrown when a requested resource is not found.
///
-public class NotFoundException : Exception
+public class NotFoundException : DisCatSharpException
{
///
/// Gets the request that caused the exception.
///
public BaseRestRequest WebRequest { get; internal set; }
///
/// Gets the response to the request.
///
public RestResponse WebResponse { get; internal set; }
///
/// Gets the JSON received.
///
public string JsonMessage { get; internal set; }
///
/// Initializes a new instance of the class.
///
/// The request.
/// The response.
- internal NotFoundException(BaseRestRequest request, RestResponse response) : base("Not found: " + response.ResponseCode)
+ internal NotFoundException(BaseRestRequest request, RestResponse response)
+ : base("Not found: " + response.ResponseCode)
{
this.WebRequest = request;
this.WebResponse = response;
try
{
var j = JObject.Parse(response.Response);
if (j["message"] != null)
this.JsonMessage = j["message"].ToString();
}
catch (Exception) { }
}
}
diff --git a/DisCatSharp/Exceptions/RateLimitException.cs b/DisCatSharp/Exceptions/RateLimitException.cs
index 6d9c8b590..10577ac22 100644
--- a/DisCatSharp/Exceptions/RateLimitException.cs
+++ b/DisCatSharp/Exceptions/RateLimitException.cs
@@ -1,70 +1,71 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
// Copyright (c) 2021-2023 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 DisCatSharp.Net;
using Newtonsoft.Json.Linq;
namespace DisCatSharp.Exceptions;
///
/// Represents an exception thrown when too many requests are sent.
///
-public class RateLimitException : Exception
+public class RateLimitException : DisCatSharpException
{
///
/// Gets the request that caused the exception.
///
public BaseRestRequest WebRequest { get; internal set; }
///
/// Gets the response to the request.
///
public RestResponse WebResponse { get; internal set; }
///
/// Gets the JSON received.
///
public string JsonMessage { get; internal set; }
///
/// Initializes a new instance of the class.
///
/// The request.
/// The response.
- internal RateLimitException(BaseRestRequest request, RestResponse response) : base("Rate limited: " + response.ResponseCode)
+ internal RateLimitException(BaseRestRequest request, RestResponse response)
+ : base("Rate limited: " + response.ResponseCode)
{
this.WebRequest = request;
this.WebResponse = response;
try
{
var j = JObject.Parse(response.Response);
if (j["message"] != null)
this.JsonMessage = j["message"].ToString();
}
catch (Exception) { }
}
}
diff --git a/DisCatSharp/Exceptions/RequestSizeException.cs b/DisCatSharp/Exceptions/RequestSizeException.cs
index 71205e0a9..f9f3c3c42 100644
--- a/DisCatSharp/Exceptions/RequestSizeException.cs
+++ b/DisCatSharp/Exceptions/RequestSizeException.cs
@@ -1,70 +1,71 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
// Copyright (c) 2021-2023 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 DisCatSharp.Net;
using Newtonsoft.Json.Linq;
namespace DisCatSharp.Exceptions;
///
/// Represents an exception thrown when the request sent to Discord is too large.
///
-public class RequestSizeException : Exception
+public class RequestSizeException : DisCatSharpException
{
///
/// Gets the request that caused the exception.
///
public BaseRestRequest WebRequest { get; internal set; }
///
/// Gets the response to the request.
///
public RestResponse WebResponse { get; internal set; }
///
/// Gets the JSON received.
///
public string JsonMessage { get; internal set; }
///
/// Initializes a new instance of the class.
///
/// The request.
/// The response.
- internal RequestSizeException(BaseRestRequest request, RestResponse response) : base($"Request entity too large: {response.ResponseCode}. Make sure the data sent is within Discord's upload limit.")
+ internal RequestSizeException(BaseRestRequest request, RestResponse response)
+ : base($"Request entity too large: {response.ResponseCode}. Make sure the data sent is within Discord's upload limit.")
{
this.WebRequest = request;
this.WebResponse = response;
try
{
var j = JObject.Parse(response.Response);
if (j["message"] != null)
this.JsonMessage = j["message"].ToString();
}
catch (Exception) { }
}
}
diff --git a/DisCatSharp/Exceptions/ServerErrorException.cs b/DisCatSharp/Exceptions/ServerErrorException.cs
index 863f1caed..782e539ea 100644
--- a/DisCatSharp/Exceptions/ServerErrorException.cs
+++ b/DisCatSharp/Exceptions/ServerErrorException.cs
@@ -1,70 +1,71 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
// Copyright (c) 2021-2023 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 DisCatSharp.Net;
using Newtonsoft.Json.Linq;
namespace DisCatSharp.Exceptions;
///
/// Represents an exception thrown when Discord returns an Internal Server Error.
///
-public class ServerErrorException : Exception
+public class ServerErrorException : DisCatSharpException
{
///
/// Gets the request that caused the exception.
///
public BaseRestRequest WebRequest { get; internal set; }
///
/// Gets the response to the request.
///
public RestResponse WebResponse { get; internal set; }
///
/// Gets the JSON received.
///
public string JsonMessage { get; internal set; }
///
/// Initializes a new instance of the class.
///
/// The request.
/// The response.
- internal ServerErrorException(BaseRestRequest request, RestResponse response) : base("Internal Server Error: " + response.ResponseCode)
+ internal ServerErrorException(BaseRestRequest request, RestResponse response)
+ : base("Internal Server Error: " + response.ResponseCode)
{
this.WebRequest = request;
this.WebResponse = response;
try
{
var j = JObject.Parse(response.Response);
if (j["message"] != null)
this.JsonMessage = j["message"].ToString();
}
catch (Exception) { }
}
}
diff --git a/DisCatSharp/Exceptions/UnauthorizedException.cs b/DisCatSharp/Exceptions/UnauthorizedException.cs
index f25938ea5..741da47af 100644
--- a/DisCatSharp/Exceptions/UnauthorizedException.cs
+++ b/DisCatSharp/Exceptions/UnauthorizedException.cs
@@ -1,70 +1,71 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
// Copyright (c) 2021-2023 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 DisCatSharp.Net;
using Newtonsoft.Json.Linq;
namespace DisCatSharp.Exceptions;
///
/// Represents an exception thrown when requester doesn't have necessary permissions to complete the request.
///
-public class UnauthorizedException : Exception
+public class UnauthorizedException : DisCatSharpException
{
///
/// Gets the request that caused the exception.
///
public BaseRestRequest WebRequest { get; internal set; }
///
/// Gets the response to the request.
///
public RestResponse WebResponse { get; internal set; }
///
/// Gets the JSON received.
///
public string JsonMessage { get; internal set; }
///
/// Initializes a new instance of the class.
///
/// The request.
/// The response.
- internal UnauthorizedException(BaseRestRequest request, RestResponse response) : base("Unauthorized: " + response.ResponseCode)
+ internal UnauthorizedException(BaseRestRequest request, RestResponse response)
+ : base("Unauthorized: " + response.ResponseCode)
{
this.WebRequest = request;
this.WebResponse = response;
try
{
var j = JObject.Parse(response.Response);
if (j["message"] != null)
this.JsonMessage = j["message"].ToString();
}
catch (Exception) { }
}
}