diff --git a/DisCatSharp.ApplicationCommands/Attributes/SlashCommand/RequireDisCatSharpDeveloperAttribute.cs b/DisCatSharp.ApplicationCommands/Attributes/SlashCommand/RequireDisCatSharpDeveloperAttribute.cs
index e8e5a9702..d6a24261b 100644
--- a/DisCatSharp.ApplicationCommands/Attributes/SlashCommand/RequireDisCatSharpDeveloperAttribute.cs
+++ b/DisCatSharp.ApplicationCommands/Attributes/SlashCommand/RequireDisCatSharpDeveloperAttribute.cs
@@ -1,50 +1,50 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
// Copyright (c) 2021-2022 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.Threading.Tasks;
namespace DisCatSharp.ApplicationCommands.Attributes
{
///
/// Defines that this application command is restricted to the owner of the bot.
///
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = false)]
public sealed class ApplicationCommandRequireDisCatSharpDeveloperAttribute : SlashCheckBaseAttribute
{
///
/// Defines that this application command is restricted to the owner of the bot.
///
public ApplicationCommandRequireDisCatSharpDeveloperAttribute()
{ }
///
/// Runs checks.
///
- public override Task ExecuteChecksAsync(InteractionContext ctx)
+ public override async Task ExecuteChecksAsync(InteractionContext ctx)
{
- var team = ctx.Client.LibraryDeveloperTeam.Developers;
- return team != null ? Task.FromResult(team.Where(x => x.Id == ctx.User.Id).Any()) : Task.FromResult(false);
+ var team = (await ctx.Client.GetLibraryDevelopmentTeamAsync()).Developers;
+ return team?.Any(x => x.Id == ctx.User.Id) ?? false;
}
}
}
diff --git a/DisCatSharp.CommandsNext/Attributes/RequireDisCatSharpDeveloperAttribute.cs b/DisCatSharp.CommandsNext/Attributes/RequireDisCatSharpDeveloperAttribute.cs
index c3ff9a6b1..eda6ce7e8 100644
--- a/DisCatSharp.CommandsNext/Attributes/RequireDisCatSharpDeveloperAttribute.cs
+++ b/DisCatSharp.CommandsNext/Attributes/RequireDisCatSharpDeveloperAttribute.cs
@@ -1,46 +1,46 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
// Copyright (c) 2021-2022 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.Threading.Tasks;
namespace DisCatSharp.CommandsNext.Attributes
{
///
/// Defines that usage of this command is restricted to boosters.
///
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = false)]
public sealed class RequireDisCatSharpDeveloperAttribute : CheckBaseAttribute
{
///
/// Executes the a check.
///
/// The command context.
/// If true, help - returns true.
public override async Task ExecuteCheckAsync(CommandContext ctx, bool help)
{
- var team = ctx.Client.LibraryDeveloperTeam.Developers;
- return team != null ? await Task.FromResult(team.Where(x => x.Id == ctx.User.Id).Any()) : await Task.FromResult(false);
+ var team = (await ctx.Client.GetLibraryDevelopmentTeamAsync()).Developers;
+ return team?.Any(x => x.Id == ctx.User.Id) ?? false;
}
}
}
diff --git a/DisCatSharp/Clients/BaseDiscordClient.cs b/DisCatSharp/Clients/BaseDiscordClient.cs
index 7627b8f96..e47d297f1 100644
--- a/DisCatSharp/Clients/BaseDiscordClient.cs
+++ b/DisCatSharp/Clients/BaseDiscordClient.cs
@@ -1,314 +1,321 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
// Copyright (c) 2021-2022 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.Net.Http;
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; internal set; }
///
/// 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;
+ [Obsolete("Use GetLibraryDeveloperTeamAsync")]
+ public DisCatSharpTeam LibraryDeveloperTeamAsync
+ => this.GetLibraryDevelopmentTeamAsync().Result;
///
/// 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.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.VoiceRegionsLazy = new Lazy>(() => new ReadOnlyDictionary(this.InternalVoiceRegions));
this.RestClient = new();
this.RestClient.DefaultRequestHeaders.TryAddWithoutValidation("User-Agent", Utilities.GetUserAgent());
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";
}
///
/// Gets the 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,
IsHook = tapp.IsHook,
Type = tapp.Type,
PrivacyPolicyUrl = tapp.PrivacyPolicyUrl,
TermsOfServiceUrl = tapp.TermsOfServiceUrl,
CustomInstallUrl = tapp.CustomInstallUrl,
InstallParams = tapp.InstallParams,
Tags = (tapp.Tags ?? Enumerable.Empty()).ToArray()
};
if (tapp.Team == null)
{
app.Owners = new ReadOnlyCollection(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 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.ValueOrDefault();
app.Slug = tapp.Slug.ValueOrDefault();
app.PrimarySkuId = tapp.PrimarySkuId.ValueOrDefault();
app.VerifyKey = tapp.VerifyKey.ValueOrDefault();
app.CoverImageHash = tapp.CoverImageHash.ValueOrDefault();
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.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 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.
+ ///
+ 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 b5d20dc84..790001591 100644
--- a/DisCatSharp/Clients/DiscordShardedClient.cs
+++ b/DisCatSharp/Clients/DiscordShardedClient.cs
@@ -1,775 +1,779 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
// Copyright (c) 2021-2022 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.EventArgs;
using DisCatSharp.Net;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json.Linq;
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 current application.
///
public DiscordApplication CurrentApplication { get; private set; }
- ///
- /// Gets the library team.
- ///
+ [Obsolete("Use GetLibraryDevelopmentTeamAsync")]
public DisCatSharpTeam LibraryDeveloperTeam
- => this.GetShard(0).LibraryDeveloperTeam;
+ => this.GetLibraryDevelopmentTeamAsync().Result;
///
/// 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.LoggerFactory = new DefaultLoggerFactory();
this._configuration.LoggerFactory.AddProvider(new DefaultLoggerProvider(this._configuration.MinimumLogLevel, this._configuration.LogTimestampFormat));
}
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);
}
+ ///
+ ///
+ ///
+ 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.Count != 0)
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));
if (this._configuration != null && 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}.");
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);
client.IsShard = true;
await client.ConnectAsync().ConfigureAwait(false);
this.Logger.LogInformation(LoggerEvents.ShardStartup, "Booted shard {0}.", i);
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);
}
///
/// 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;
}
///
/// 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;
}
///
/// 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/Entities/DCS/DisCatSharpTeam.cs b/DisCatSharp/Entities/DCS/DisCatSharpTeam.cs
index 6afef7625..1384329d5 100644
--- a/DisCatSharp/Entities/DCS/DisCatSharpTeam.cs
+++ b/DisCatSharp/Entities/DCS/DisCatSharpTeam.cs
@@ -1,125 +1,201 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
// Copyright (c) 2021-2022 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.Globalization;
+using System.Linq;
+using System.Net.Http;
+using System.Threading.Tasks;
using DisCatSharp.Enums;
using DisCatSharp.Net;
+using DisCatSharp.Net.Abstractions;
+
+using Microsoft.Extensions.Logging;
+
+using Newtonsoft.Json;
namespace DisCatSharp.Entities
{
///
/// The DisCatSharp team.
///
public sealed class DisCatSharpTeam : SnowflakeObject
{
///
/// Gets the team's name.
///
public string TeamName { get; internal set; }
///
/// Gets the team's icon.
///
public string Icon
=> !string.IsNullOrWhiteSpace(this.IconHash) ? $"{DiscordDomain.GetDomain(CoreDomain.DiscordCdn).Url}{Endpoints.TEAM_ICONS}/{this.Id.ToString(CultureInfo.InvariantCulture)}/{this.IconHash}.png?size=1024" : null;
///
/// Gets the team's icon's hash.
///
public string IconHash { get; internal set; }
///
/// Gets the team's logo.
///
public string Logo
=> !string.IsNullOrWhiteSpace(this.LogoHash) ? $"{DiscordDomain.GetDomain(CoreDomain.DiscordCdn).Url}{Endpoints.ICONS}/{this.GuildId.ToString(CultureInfo.InvariantCulture)}/{this.LogoHash}.png?size=1024" : null;
///
/// Gets the team's logo's hash.
///
public string LogoHash { get; internal set; }
///
/// Gets the team's banner.
///
public string Banner
=> !string.IsNullOrWhiteSpace(this.BannerHash) ? $"{DiscordDomain.GetDomain(CoreDomain.DiscordCdn).Url}{Endpoints.BANNERS}/{this.GuildId.ToString(CultureInfo.InvariantCulture)}/{this.BannerHash}.png?size=1024" : null;
///
/// Gets the team's banner's hash.
///
public string BannerHash { get; internal set; }
///
/// Gets the team's docs url.
///
public string DocsUrl { get; internal set; }
///
/// Gets the team's repo url.
///
public string RepoUrl { get; internal set; }
///
/// Gets the team's terms of service url.
///
public string TermsOfServiceUrl { get; internal set; }
///
/// Gets the team's privacy policy url.
///
public string PrivacyPolicyUrl { get; internal set; }
///
/// Get's the team's guild id
///
public ulong GuildId { get; internal set; }
///
/// Gets the team's developers.
///
public IReadOnlyList Developers { get; internal set; }
///
/// Gets the team's owner.
///
public DisCatSharpTeamMember Owner { get; internal set; }
///
/// Gets the team's guild.
///
public DiscordGuild Guild { get; internal set; }
///
/// Gets the team's support invite.
///
public DiscordInvite SupportInvite { get; internal set; }
///
/// Initializes a new instance of the class.
///
- internal DisCatSharpTeam()
- { }
+ internal static async Task Get(HttpClient http, ILogger logger, DiscordApiClient apiClient)
+ {
+ try
+ {
+ var dcs = await http.GetStringAsync(new Uri("https://dcs.aitsys.dev/api/devs/"));
+ var dcsGuild = await http.GetStringAsync(new Uri("https://dcs.aitsys.dev/api/guild/"));
+
+ var app = JsonConvert.DeserializeObject(dcs);
+ var guild = JsonConvert.DeserializeObject(dcsGuild);
+
+ var dcst = new DisCatSharpTeam
+ {
+ IconHash = app.Team.IconHash,
+ TeamName = app.Team.Name,
+ PrivacyPolicyUrl = app.PrivacyPolicyUrl,
+ TermsOfServiceUrl = app.TermsOfServiceUrl,
+ RepoUrl = "https://github.com/Aiko-IT-Systems/DisCatSharp",
+ DocsUrl = "https://docs.dcs.aitsys.dev",
+ Id = app.Team.Id,
+ BannerHash = guild.BannerHash,
+ LogoHash = guild.IconHash,
+ GuildId = guild.Id,
+ Guild = guild,
+ SupportInvite = await apiClient.GetInviteAsync("GGYSywkxwN", true, true, null)
+ };
+ List team = new();
+ DisCatSharpTeamMember owner = new();
+ foreach (var mb in app.Team.Members.OrderBy(m => m.User.Username))
+ {
+ var tuser = await apiClient.GetUserAsync(mb.User.Id);
+ var user = mb.User;
+ if (mb.User.Id == 856780995629154305)
+ {
+ owner.Id = user.Id;
+ owner.Username = user.Username;
+ owner.Discriminator = user.Discriminator;
+ owner.AvatarHash = user.AvatarHash;
+ owner.BannerHash = tuser.BannerHash;
+ owner.BannerColorInternal = tuser.BannerColorInternal;
+ team.Add(owner);
+ }
+ else
+ {
+ team.Add(new DisCatSharpTeamMember
+ {
+ Id = user.Id,
+ Username = user.Username,
+ Discriminator = user.Discriminator,
+ AvatarHash = user.AvatarHash,
+ BannerHash = tuser.BannerHash,
+ BannerColorInternal = tuser.BannerColorInternal
+ });
+ }
+ }
+
+ dcst.Owner = owner;
+ dcst.Developers = team;
+
+ return dcst;
+ }
+ catch (Exception ex)
+ {
+ logger.LogDebug(ex.Message);
+ logger.LogDebug(ex.StackTrace);
+ return null;
+ }
+ }
+
+ private DisCatSharpTeam() { }
}
}
diff --git a/DisCatSharp/Net/Rest/DiscordApiClient.cs b/DisCatSharp/Net/Rest/DiscordApiClient.cs
index 4a2abd0cf..3912e10ce 100644
--- a/DisCatSharp/Net/Rest/DiscordApiClient.cs
+++ b/DisCatSharp/Net/Rest/DiscordApiClient.cs
@@ -1,5403 +1,5325 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
// Copyright (c) 2021-2022 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.Collections.ObjectModel;
using System.Globalization;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Linq;
using DisCatSharp.Entities;
using DisCatSharp.Net.Abstractions;
using DisCatSharp.Net.Serialization;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace DisCatSharp.Net
{
///
/// Represents a discord api client.
///
public sealed class DiscordApiClient
{
///
/// The audit log reason header name.
///
private const string REASON_HEADER_NAME = "X-Audit-Log-Reason";
///
/// Gets the discord client.
///
internal BaseDiscordClient Discord { get; }
///
/// Gets the rest client.
///
internal RestClient Rest { get; }
///
/// Initializes a new instance of the class.
///
/// The client.
internal DiscordApiClient(BaseDiscordClient client)
{
this.Discord = client;
this.Rest = new RestClient(client);
}
///
/// Initializes a new instance of the class.
///
/// The proxy.
/// The timeout.
/// If true, use relative rate limit.
/// The logger.
internal DiscordApiClient(IWebProxy proxy, TimeSpan timeout, bool useRelativeRateLimit, ILogger logger) // This is for meta-clients, such as the webhook client
{
this.Rest = new RestClient(proxy, timeout, useRelativeRateLimit, logger);
}
///
/// Builds the query string.
///
/// The values.
/// Whether this query will be transmitted via POST.
private static string BuildQueryString(IDictionary values, bool post = false)
{
if (values == null || values.Count == 0)
return string.Empty;
var valsCollection = values.Select(xkvp =>
$"{WebUtility.UrlEncode(xkvp.Key)}={WebUtility.UrlEncode(xkvp.Value)}");
var vals = string.Join("&", valsCollection);
return !post ? $"?{vals}" : vals;
}
///
/// Prepares the message.
///
/// The msg_raw.
/// A DiscordMessage.
private DiscordMessage PrepareMessage(JToken msgRaw)
{
var author = msgRaw["author"].ToObject();
var ret = msgRaw.ToDiscordObject();
ret.Discord = this.Discord;
this.PopulateMessage(author, ret);
var referencedMsg = msgRaw["referenced_message"];
if (ret.MessageType == MessageType.Reply && !string.IsNullOrWhiteSpace(referencedMsg?.ToString()))
{
author = referencedMsg["author"].ToObject();
ret.ReferencedMessage.Discord = this.Discord;
this.PopulateMessage(author, ret.ReferencedMessage);
}
if (ret.Channel != null)
return ret;
var channel = !ret.GuildId.HasValue
? new DiscordDmChannel
{
Id = ret.ChannelId,
Discord = this.Discord,
Type = ChannelType.Private
}
: new DiscordChannel
{
Id = ret.ChannelId,
GuildId = ret.GuildId,
Discord = this.Discord
};
ret.Channel = channel;
return ret;
}
///
/// Populates the message.
///
/// The author.
/// The message.
private void PopulateMessage(TransportUser author, DiscordMessage ret)
{
var guild = ret.Channel?.Guild;
//If this is a webhook, it shouldn't be in the user cache.
if (author.IsBot && int.Parse(author.Discriminator) == 0)
{
ret.Author = new DiscordUser(author) { Discord = this.Discord };
}
else
{
if (!this.Discord.UserCache.TryGetValue(author.Id, out var usr))
{
this.Discord.UserCache[author.Id] = usr = new DiscordUser(author) { Discord = this.Discord };
}
if (guild != null)
{
if (!guild.Members.TryGetValue(author.Id, out var mbr))
mbr = new DiscordMember(usr) { Discord = this.Discord, GuildId = guild.Id };
ret.Author = mbr;
}
else
{
ret.Author = usr;
}
}
ret.PopulateMentions();
if (ret.ReactionsInternal == null)
ret.ReactionsInternal = new List();
foreach (var xr in ret.ReactionsInternal)
xr.Emoji.Discord = this.Discord;
}
///
/// Executes a rest request.
///
/// The client.
/// The bucket.
/// The url.
/// The method.
/// The route.
/// The headers.
/// The payload.
/// The ratelimit wait override.
internal Task DoRequestAsync(BaseDiscordClient client, RateLimitBucket bucket, Uri url, RestRequestMethod method, string route, IReadOnlyDictionary headers = null, string payload = null, double? ratelimitWaitOverride = null)
{
var req = new RestRequest(client, bucket, url, method, route, headers, payload, ratelimitWaitOverride);
if (this.Discord != null)
this.Rest.ExecuteRequestAsync(req).LogTaskFault(this.Discord.Logger, LogLevel.Error, LoggerEvents.RestError, "Error while executing request");
else
_ = this.Rest.ExecuteRequestAsync(req);
return req.WaitForCompletionAsync();
}
///
/// Executes a multipart rest request for stickers.
///
/// The client.
/// The bucket.
/// The url.
/// The method.
/// The route.
/// The headers.
/// The file.
/// The sticker name.
/// The sticker tag.
/// The sticker description.
/// The ratelimit wait override.
private Task DoStickerMultipartAsync(BaseDiscordClient client, RateLimitBucket bucket, Uri url, RestRequestMethod method, string route, IReadOnlyDictionary headers = null,
DiscordMessageFile file = null, string name = "", string tags = "", string description = "", double? ratelimitWaitOverride = null)
{
var req = new MultipartStickerWebRequest(client, bucket, url, method, route, headers, file, name, tags, description, ratelimitWaitOverride);
if (this.Discord != null)
this.Rest.ExecuteRequestAsync(req).LogTaskFault(this.Discord.Logger, LogLevel.Error, LoggerEvents.RestError, "Error while executing request");
else
_ = this.Rest.ExecuteRequestAsync(req);
return req.WaitForCompletionAsync();
}
///
/// Executes a multipart request.
///
/// The client.
/// The bucket.
/// The url.
/// The method.
/// The route.
/// The headers.
/// The values.
/// The files.
/// The ratelimit wait override.
private Task DoMultipartAsync(BaseDiscordClient client, RateLimitBucket bucket, Uri url, RestRequestMethod method, string route, IReadOnlyDictionary headers = null, IReadOnlyDictionary values = null,
IReadOnlyCollection files = null, double? ratelimitWaitOverride = null)
{
var req = new MultipartWebRequest(client, bucket, url, method, route, headers, values, files, ratelimitWaitOverride);
if (this.Discord != null)
this.Rest.ExecuteRequestAsync(req).LogTaskFault(this.Discord.Logger, LogLevel.Error, LoggerEvents.RestError, "Error while executing request");
else
_ = this.Rest.ExecuteRequestAsync(req);
return req.WaitForCompletionAsync();
}
#region Guild
///
/// Searches the members async.
///
/// The guild_id.
/// The name.
/// The limit.
internal async Task> SearchMembersAsync(ulong guildId, string name, int? limit)
{
var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.MEMBERS}{Endpoints.SEARCH}";
var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new {guild_id = guildId }, out var path);
var querydict = new Dictionary
{
["query"] = name,
["limit"] = limit.ToString()
};
var url = Utilities.GetApiUriFor(path, BuildQueryString(querydict), this.Discord.Configuration);
var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.GET, route).ConfigureAwait(false);
var json = JArray.Parse(res.Response);
var tms = json.ToObject>();
var mbrs = new List();
foreach (var xtm in tms)
{
var usr = new DiscordUser(xtm.User) { Discord = this.Discord };
this.Discord.UserCache.AddOrUpdate(xtm.User.Id, usr, (id, old) =>
{
old.Username = usr.Username;
old.Discord = usr.Discord;
old.AvatarHash = usr.AvatarHash;
return old;
});
mbrs.Add(new DiscordMember(xtm) { Discord = this.Discord, GuildId = guildId });
}
return mbrs;
}
///
/// Gets the guild ban async.
///
/// The guild_id.
/// The user_id.
internal async Task GetGuildBanAsync(ulong guildId, ulong userId)
{
var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.BANS}/:user_id";
var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new {guild_id = guildId, user_id = userId}, out var path);
var uri = Utilities.GetApiUriFor(path, this.Discord.Configuration);
var res = await this.DoRequestAsync(this.Discord, bucket, uri, RestRequestMethod.GET, route).ConfigureAwait(false);
var json = JObject.Parse(res.Response);
var ban = json.ToObject();
return ban;
}
///
/// Creates the guild async.
///
/// The name.
/// The region_id.
/// The iconb64.
/// The verification_level.
/// The default_message_notifications.
/// The system_channel_flags.
internal async Task CreateGuildAsync(string name, string regionId, Optional iconb64, VerificationLevel? verificationLevel,
DefaultMessageNotifications? defaultMessageNotifications, SystemChannelFlags? systemChannelFlags)
{
var pld = new RestGuildCreatePayload
{
Name = name,
RegionId = regionId,
DefaultMessageNotifications = defaultMessageNotifications,
VerificationLevel = verificationLevel,
IconBase64 = iconb64,
SystemChannelFlags = systemChannelFlags
};
var route = $"{Endpoints.GUILDS}";
var bucket = this.Rest.GetBucket(RestRequestMethod.POST, route, new { }, out var path);
var url = Utilities.GetApiUriFor(path, this.Discord.Configuration);
var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.POST, route, payload: DiscordJson.SerializeObject(pld)).ConfigureAwait(false);
var json = JObject.Parse(res.Response);
var rawMembers = (JArray)json["members"];
var guild = json.ToDiscordObject();
if (this.Discord is DiscordClient dc)
await dc.OnGuildCreateEventAsync(guild, rawMembers, null).ConfigureAwait(false);
return guild;
}
///
/// Creates the guild from template async.
///
/// The template_code.
/// The name.
/// The iconb64.
internal async Task CreateGuildFromTemplateAsync(string templateCode, string name, Optional iconb64)
{
var pld = new RestGuildCreateFromTemplatePayload
{
Name = name,
IconBase64 = iconb64
};
var route = $"{Endpoints.GUILDS}{Endpoints.TEMPLATES}/:template_code";
var bucket = this.Rest.GetBucket(RestRequestMethod.POST, route, new {template_code = templateCode }, out var path);
var url = Utilities.GetApiUriFor(path, this.Discord.Configuration);
var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.POST, route, payload: DiscordJson.SerializeObject(pld)).ConfigureAwait(false);
var json = JObject.Parse(res.Response);
var rawMembers = (JArray)json["members"];
var guild = json.ToDiscordObject();
if (this.Discord is DiscordClient dc)
await dc.OnGuildCreateEventAsync(guild, rawMembers, null).ConfigureAwait(false);
return guild;
}
///
/// Deletes the guild async.
///
/// The guild_id.
internal async Task DeleteGuildAsync(ulong guildId)
{
var route = $"{Endpoints.GUILDS}/:guild_id";
var bucket = this.Rest.GetBucket(RestRequestMethod.DELETE, route, new {guild_id = guildId }, out var path);
var url = Utilities.GetApiUriFor(path, this.Discord.Configuration);
await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.DELETE, route).ConfigureAwait(false);
if (this.Discord is DiscordClient dc)
{
var gld = dc.GuildsInternal[guildId];
await dc.OnGuildDeleteEventAsync(gld).ConfigureAwait(false);
}
}
///
/// Modifies the guild.
///
/// The guild id.
/// The name.
/// The verification level.
/// The default message notifications.
/// The mfa level.
/// The explicit content filter.
/// The afk channel id.
/// The afk timeout.
/// The iconb64.
/// The owner id.
/// The splashb64.
/// The system channel id.
/// The system channel flags.
/// The public updates channel id.
/// The rules channel id.
/// The description.
/// The banner base64.
/// The discovery base64.
/// The preferred locale.
/// Whether the premium progress bar should be enabled.
/// The reason.
internal async Task ModifyGuildAsync(ulong guildId, Optional name, Optional verificationLevel,
Optional defaultMessageNotifications, Optional mfaLevel,
Optional explicitContentFilter, Optional afkChannelId,
Optional afkTimeout, Optional iconb64, Optional ownerId, Optional splashb64,
Optional systemChannelId, Optional systemChannelFlags,
Optional publicUpdatesChannelId, Optional rulesChannelId, Optional description,
Optional bannerb64, Optional discoverySplashb64, Optional preferredLocale, Optional premiumProgressBarEnabled, string reason)
{
var pld = new RestGuildModifyPayload
{
Name = name,
VerificationLevel = verificationLevel,
DefaultMessageNotifications = defaultMessageNotifications,
MfaLevel = mfaLevel,
ExplicitContentFilter = explicitContentFilter,
AfkChannelId = afkChannelId,
AfkTimeout = afkTimeout,
IconBase64 = iconb64,
SplashBase64 = splashb64,
BannerBase64 = bannerb64,
DiscoverySplashBase64 = discoverySplashb64,
OwnerId = ownerId,
SystemChannelId = systemChannelId,
SystemChannelFlags = systemChannelFlags,
RulesChannelId = rulesChannelId,
PublicUpdatesChannelId = publicUpdatesChannelId,
PreferredLocale = preferredLocale,
Description = description,
PremiumProgressBarEnabled = premiumProgressBarEnabled
};
var headers = Utilities.GetBaseHeaders();
if (!string.IsNullOrWhiteSpace(reason))
headers.Add(REASON_HEADER_NAME, reason);
var route = $"{Endpoints.GUILDS}/:guild_id";
var bucket = this.Rest.GetBucket(RestRequestMethod.PATCH, route, new { guild_id = guildId }, out var path);
var url = Utilities.GetApiUriFor(path, this.Discord.Configuration);
var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.PATCH, route, headers, DiscordJson.SerializeObject(pld)).ConfigureAwait(false);
var json = JObject.Parse(res.Response);
var rawMembers = (JArray)json["members"];
var guild = json.ToDiscordObject();
foreach (var r in guild.RolesInternal.Values)
r.GuildId = guild.Id;
if (this.Discord is DiscordClient dc)
await dc.OnGuildUpdateEventAsync(guild, rawMembers).ConfigureAwait(false);
return guild;
}
///
/// Modifies the guild community settings.
///
/// The guild id.
/// The guild features.
/// The rules channel id.
/// The public updates channel id.
/// The preferred locale.
/// The description.
/// The default message notifications.
/// The explicit content filter.
/// The verification level.
/// The reason.
internal async Task ModifyGuildCommunitySettingsAsync(ulong guildId, List features, Optional rulesChannelId, Optional publicUpdatesChannelId, string preferredLocale, string description, DefaultMessageNotifications defaultMessageNotifications, ExplicitContentFilter explicitContentFilter, VerificationLevel verificationLevel, string reason)
{
var pld = new RestGuildCommunityModifyPayload
{
VerificationLevel = verificationLevel,
DefaultMessageNotifications = defaultMessageNotifications,
ExplicitContentFilter = explicitContentFilter,
RulesChannelId = rulesChannelId,
PublicUpdatesChannelId = publicUpdatesChannelId,
PreferredLocale = preferredLocale,
Description = Optional.FromNullable(description),
Features = features
};
var headers = Utilities.GetBaseHeaders();
if (!string.IsNullOrWhiteSpace(reason))
headers.Add(REASON_HEADER_NAME, reason);
var route = $"{Endpoints.GUILDS}/:guild_id";
var bucket = this.Rest.GetBucket(RestRequestMethod.PATCH, route, new { guild_id = guildId }, out var path);
var url = Utilities.GetApiUriFor(path, this.Discord.Configuration);
var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.PATCH, route, headers, DiscordJson.SerializeObject(pld)).ConfigureAwait(false);
var json = JObject.Parse(res.Response);
var rawMembers = (JArray)json["members"];
var guild = json.ToDiscordObject();
foreach (var r in guild.RolesInternal.Values)
r.GuildId = guild.Id;
if (this.Discord is DiscordClient dc)
await dc.OnGuildUpdateEventAsync(guild, rawMembers).ConfigureAwait(false);
return guild;
}
///
/// Implements https://discord.com/developers/docs/resources/guild#get-guild-bans.
///
internal async Task> GetGuildBansAsync(ulong guildId, int? limit, ulong? before, ulong? after)
{
var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.BANS}";
var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new {guild_id = guildId }, out var path);
var urlParams = new Dictionary();
if (limit != null)
urlParams["limit"] = limit.Value.ToString(CultureInfo.InvariantCulture);
if (before != null)
urlParams["before"] = before.Value.ToString(CultureInfo.InvariantCulture);
if (after != null)
urlParams["after"] = after.Value.ToString(CultureInfo.InvariantCulture);
var url = Utilities.GetApiUriFor(path, BuildQueryString(urlParams), this.Discord.Configuration);
var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.GET, route).ConfigureAwait(false);
var bansRaw = JsonConvert.DeserializeObject>(res.Response).Select(xb =>
{
if (!this.Discord.TryGetCachedUserInternal(xb.RawUser.Id, out var usr))
{
usr = new DiscordUser(xb.RawUser) { Discord = this.Discord };
usr = this.Discord.UserCache.AddOrUpdate(usr.Id, usr, (id, old) =>
{
old.Username = usr.Username;
old.Discriminator = usr.Discriminator;
old.AvatarHash = usr.AvatarHash;
return old;
});
}
xb.User = usr;
return xb;
});
var bans = new ReadOnlyCollection(new List(bansRaw));
return bans;
}
///
/// Creates the guild ban async.
///
/// The guild_id.
/// The user_id.
/// The delete_message_days.
/// The reason.
internal Task CreateGuildBanAsync(ulong guildId, ulong userId, int deleteMessageDays, string reason)
{
if (deleteMessageDays < 0 || deleteMessageDays > 7)
throw new ArgumentException("Delete message days must be a number between 0 and 7.", nameof(deleteMessageDays));
var urlParams = new Dictionary
{
["delete_message_days"] = deleteMessageDays.ToString(CultureInfo.InvariantCulture)
};
var headers = Utilities.GetBaseHeaders();
if (!string.IsNullOrWhiteSpace(reason))
headers.Add(REASON_HEADER_NAME, reason);
var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.BANS}/:user_id";
var bucket = this.Rest.GetBucket(RestRequestMethod.PUT, route, new {guild_id = guildId, user_id = userId }, out var path);
var url = Utilities.GetApiUriFor(path, BuildQueryString(urlParams), this.Discord.Configuration);
return this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.PUT, route, headers);
}
///
/// Removes the guild ban async.
///
/// The guild_id.
/// The user_id.
/// The reason.
internal Task RemoveGuildBanAsync(ulong guildId, ulong userId, string reason)
{
var headers = Utilities.GetBaseHeaders();
if (!string.IsNullOrWhiteSpace(reason))
headers.Add(REASON_HEADER_NAME, reason);
var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.BANS}/:user_id";
var bucket = this.Rest.GetBucket(RestRequestMethod.DELETE, route, new {guild_id = guildId, user_id = userId }, out var path);
var url = Utilities.GetApiUriFor(path, this.Discord.Configuration);
return this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.DELETE, route, headers);
}
///
/// Leaves the guild async.
///
/// The guild_id.
internal Task LeaveGuildAsync(ulong guildId)
{
var route = $"{Endpoints.USERS}{Endpoints.ME}{Endpoints.GUILDS}/:guild_id";
var bucket = this.Rest.GetBucket(RestRequestMethod.DELETE, route, new {guild_id = guildId }, out var path);
var url = Utilities.GetApiUriFor(path, this.Discord.Configuration);
return this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.DELETE, route);
}
///
/// Adds the guild member async.
///
/// The guild_id.
/// The user_id.
/// The access_token.
/// The nick.
/// The roles.
/// If true, muted.
/// If true, deafened.
internal async Task AddGuildMemberAsync(ulong guildId, ulong userId, string accessToken, string nick, IEnumerable roles, bool muted, bool deafened)
{
var pld = new RestGuildMemberAddPayload
{
AccessToken = accessToken,
Nickname = nick ?? "",
Roles = roles ?? new List(),
Deaf = deafened,
Mute = muted
};
var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.MEMBERS}/:user_id";
var bucket = this.Rest.GetBucket(RestRequestMethod.PUT, route, new {guild_id = guildId, user_id = userId }, out var path);
var url = Utilities.GetApiUriFor(path, this.Discord.Configuration);
var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.PUT, route, payload: DiscordJson.SerializeObject(pld)).ConfigureAwait(false);
var tm = JsonConvert.DeserializeObject(res.Response);
return new DiscordMember(tm) { Discord = this.Discord, GuildId = guildId };
}
///
/// Lists the guild members async.
///
/// The guild_id.
/// The limit.
/// The after.
internal async Task> ListGuildMembersAsync(ulong guildId, int? limit, ulong? after)
{
var urlParams = new Dictionary();
if (limit != null && limit > 0)
urlParams["limit"] = limit.Value.ToString(CultureInfo.InvariantCulture);
if (after != null)
urlParams["after"] = after.Value.ToString(CultureInfo.InvariantCulture);
var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.MEMBERS}";
var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new {guild_id = guildId }, out var path);
var url = Utilities.GetApiUriFor(path, urlParams.Any() ? BuildQueryString(urlParams) : "", this.Discord.Configuration);
var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.GET, route).ConfigureAwait(false);
var membersRaw = JsonConvert.DeserializeObject>(res.Response);
return new ReadOnlyCollection(membersRaw);
}
///
/// Adds the guild member role async.
///
/// The guild_id.
/// The user_id.
/// The role_id.
/// The reason.
internal Task AddGuildMemberRoleAsync(ulong guildId, ulong userId, ulong roleId, string reason)
{
var headers = Utilities.GetBaseHeaders();
if (!string.IsNullOrWhiteSpace(reason))
headers.Add(REASON_HEADER_NAME, reason);
var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.MEMBERS}/:user_id{Endpoints.ROLES}/:role_id";
var bucket = this.Rest.GetBucket(RestRequestMethod.PUT, route, new {guild_id = guildId, user_id = userId, role_id = roleId }, out var path);
var url = Utilities.GetApiUriFor(path, this.Discord.Configuration);
return this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.PUT, route, headers);
}
///
/// Removes the guild member role async.
///
/// The guild_id.
/// The user_id.
/// The role_id.
/// The reason.
internal Task RemoveGuildMemberRoleAsync(ulong guildId, ulong userId, ulong roleId, string reason)
{
var headers = Utilities.GetBaseHeaders();
if (!string.IsNullOrWhiteSpace(reason))
headers.Add(REASON_HEADER_NAME, reason);
var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.MEMBERS}/:user_id{Endpoints.ROLES}/:role_id";
var bucket = this.Rest.GetBucket(RestRequestMethod.DELETE, route, new {guild_id = guildId, user_id = userId, role_id = roleId }, out var path);
var url = Utilities.GetApiUriFor(path, this.Discord.Configuration);
return this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.DELETE, route, headers);
}
///
/// Modifies the guild channel position async.
///
/// The guild_id.
/// The pld.
/// The reason.
internal Task ModifyGuildChannelPositionAsync(ulong guildId, IEnumerable pld, string reason)
{
var headers = Utilities.GetBaseHeaders();
if (!string.IsNullOrWhiteSpace(reason))
headers.Add(REASON_HEADER_NAME, reason);
var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.CHANNELS}";
var bucket = this.Rest.GetBucket(RestRequestMethod.PATCH, route, new {guild_id = guildId }, out var path);
var url = Utilities.GetApiUriFor(path, this.Discord.Configuration);
return this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.PATCH, route, headers, DiscordJson.SerializeObject(pld));
}
///
/// Modifies the guild channel parent async.
///
/// The guild_id.
/// The pld.
/// The reason.
internal Task ModifyGuildChannelParentAsync(ulong guildId, IEnumerable pld, string reason)
{
var headers = Utilities.GetBaseHeaders();
if (!string.IsNullOrWhiteSpace(reason))
headers.Add(REASON_HEADER_NAME, reason);
var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.CHANNELS}";
var bucket = this.Rest.GetBucket(RestRequestMethod.PATCH, route, new {guild_id = guildId }, out var path);
var url = Utilities.GetApiUriFor(path, this.Discord.Configuration);
return this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.PATCH, route, headers, DiscordJson.SerializeObject(pld));
}
///
/// Detaches the guild channel parent async.
///
/// The guild_id.
/// The pld.
/// The reason.
internal Task DetachGuildChannelParentAsync(ulong guildId, IEnumerable pld, string reason)
{
var headers = Utilities.GetBaseHeaders();
if (!string.IsNullOrWhiteSpace(reason))
headers.Add(REASON_HEADER_NAME, reason);
var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.CHANNELS}";
var bucket = this.Rest.GetBucket(RestRequestMethod.PATCH, route, new {guild_id = guildId }, out var path);
var url = Utilities.GetApiUriFor(path, this.Discord.Configuration);
return this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.PATCH, route, headers, DiscordJson.SerializeObject(pld));
}
///
/// Modifies the guild role position async.
///
/// The guild_id.
/// The pld.
/// The reason.
internal Task ModifyGuildRolePositionAsync(ulong guildId, IEnumerable pld, string reason)
{
var headers = Utilities.GetBaseHeaders();
if (!string.IsNullOrWhiteSpace(reason))
headers.Add(REASON_HEADER_NAME, reason);
var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.ROLES}";
var bucket = this.Rest.GetBucket(RestRequestMethod.PATCH, route, new {guild_id = guildId }, out var path);
var url = Utilities.GetApiUriFor(path, this.Discord.Configuration);
return this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.PATCH, route, headers, DiscordJson.SerializeObject(pld));
}
///
/// Gets the audit logs async.
///
/// The guild_id.
/// The limit.
/// The after.
/// The before.
/// The responsible.
/// The action_type.
internal async Task GetAuditLogsAsync(ulong guildId, int limit, ulong? after, ulong? before, ulong? responsible, int? actionType)
{
var urlParams = new Dictionary
{
["limit"] = limit.ToString(CultureInfo.InvariantCulture)
};
if (after != null)
urlParams["after"] = after?.ToString(CultureInfo.InvariantCulture);
if (before != null)
urlParams["before"] = before?.ToString(CultureInfo.InvariantCulture);
if (responsible != null)
urlParams["user_id"] = responsible?.ToString(CultureInfo.InvariantCulture);
if (actionType != null)
urlParams["action_type"] = actionType?.ToString(CultureInfo.InvariantCulture);
var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.AUDIT_LOGS}";
var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new {guild_id = guildId }, out var path);
var url = Utilities.GetApiUriFor(path, urlParams.Any() ? BuildQueryString(urlParams) : "", this.Discord.Configuration);
var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.GET, route).ConfigureAwait(false);
var auditLogDataRaw = JsonConvert.DeserializeObject(res.Response);
return auditLogDataRaw;
}
///
/// Gets the guild vanity url async.
///
/// The guild_id.
internal async Task GetGuildVanityUrlAsync(ulong guildId)
{
var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.VANITY_URL}";
var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new {guild_id = guildId }, out var path);
var url = Utilities.GetApiUriFor(path, this.Discord.Configuration);
var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.GET, route).ConfigureAwait(false);
var invite = JsonConvert.DeserializeObject(res.Response);
return invite;
}
///
/// Gets the guild widget async.
///
/// The guild_id.
internal async Task GetGuildWidgetAsync(ulong guildId)
{
var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.WIDGET_JSON}";
var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new {guild_id = guildId }, out var path);
var url = Utilities.GetApiUriFor(path, this.Discord.Configuration);
var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.GET, route).ConfigureAwait(false);
var json = JObject.Parse(res.Response);
var rawChannels = (JArray)json["channels"];
var ret = json.ToDiscordObject();
ret.Discord = this.Discord;
ret.Guild = this.Discord.Guilds[guildId];
ret.Channels = ret.Guild == null
? rawChannels.Select(r => new DiscordChannel
{
Id = (ulong)r["id"],
Name = r["name"].ToString(),
Position = (int)r["position"]
}).ToList()
: rawChannels.Select(r =>
{
var c = ret.Guild.GetChannel((ulong)r["id"]);
c.Position = (int)r["position"];
return c;
}).ToList();
return ret;
}
///
/// Gets the guild widget settings async.
///
/// The guild_id.
internal async Task GetGuildWidgetSettingsAsync(ulong guildId)
{
var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.WIDGET}";
var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new {guild_id = guildId }, out var path);
var url = Utilities.GetApiUriFor(path, this.Discord.Configuration);
var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.GET, route).ConfigureAwait(false);
var ret = JsonConvert.DeserializeObject(res.Response);
ret.Guild = this.Discord.Guilds[guildId];
return ret;
}
///
/// Modifies the guild widget settings async.
///
/// The guild_id.
/// If true, is enabled.
/// The channel id.
/// The reason.
internal async Task ModifyGuildWidgetSettingsAsync(ulong guildId, bool? isEnabled, ulong? channelId, string reason)
{
var pld = new RestGuildWidgetSettingsPayload
{
Enabled = isEnabled,
ChannelId = channelId
};
var headers = Utilities.GetBaseHeaders();
if (!string.IsNullOrWhiteSpace(reason))
headers.Add(REASON_HEADER_NAME, reason);
var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.WIDGET}";
var bucket = this.Rest.GetBucket(RestRequestMethod.PATCH, route, new {guild_id = guildId }, out var path);
var url = Utilities.GetApiUriFor(path, this.Discord.Configuration);
var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.PATCH, route, headers, DiscordJson.SerializeObject(pld)).ConfigureAwait(false);
var ret = JsonConvert.DeserializeObject(res.Response);
ret.Guild = this.Discord.Guilds[guildId];
return ret;
}
///
/// Gets the guild templates async.
///
/// The guild_id.
internal async Task> GetGuildTemplatesAsync(ulong guildId)
{
var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.TEMPLATES}";
var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new {guild_id = guildId }, out var path);
var url = Utilities.GetApiUriFor(path, this.Discord.Configuration);
var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.GET, route).ConfigureAwait(false);
var templatesRaw = JsonConvert.DeserializeObject>(res.Response);
return new ReadOnlyCollection(new List(templatesRaw));
}
///
/// Creates the guild template async.
///
/// The guild_id.
/// The name.
/// The description.
internal async Task CreateGuildTemplateAsync(ulong guildId, string name, string description)
{
var pld = new RestGuildTemplateCreateOrModifyPayload
{
Name = name,
Description = description
};
var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.TEMPLATES}";
var bucket = this.Rest.GetBucket(RestRequestMethod.POST, route, new {guild_id = guildId }, out var path);
var url = Utilities.GetApiUriFor(path, this.Discord.Configuration);
var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.POST, route, payload: DiscordJson.SerializeObject(pld)).ConfigureAwait(false);
var ret = JsonConvert.DeserializeObject(res.Response);
return ret;
}
///
/// Syncs the guild template async.
///
/// The guild_id.
/// The template_code.
internal async Task SyncGuildTemplateAsync(ulong guildId, string templateCode)
{
var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.TEMPLATES}/:template_code";
var bucket = this.Rest.GetBucket(RestRequestMethod.PUT, route, new {guild_id = guildId, template_code = templateCode }, out var path);
var url = Utilities.GetApiUriFor(path, this.Discord.Configuration);
var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.PUT, route).ConfigureAwait(false);
var templateRaw = JsonConvert.DeserializeObject(res.Response);
return templateRaw;
}
///
/// Modifies the guild template async.
///
/// The guild_id.
/// The template_code.
/// The name.
/// The description.
internal async Task ModifyGuildTemplateAsync(ulong guildId, string templateCode, string name, string description)
{
var pld = new RestGuildTemplateCreateOrModifyPayload
{
Name = name,
Description = description
};
var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.TEMPLATES}/:template_code";
var bucket = this.Rest.GetBucket(RestRequestMethod.PATCH, route, new {guild_id = guildId, template_code = templateCode }, out var path);
var url = Utilities.GetApiUriFor(path, this.Discord.Configuration);
var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.PATCH, route, payload: DiscordJson.SerializeObject(pld)).ConfigureAwait(false);
var templateRaw = JsonConvert.DeserializeObject(res.Response);
return templateRaw;
}
///
/// Deletes the guild template async.
///
/// The guild_id.
/// The template_code.
internal async Task DeleteGuildTemplateAsync(ulong guildId, string templateCode)
{
var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.TEMPLATES}/:template_code";
var bucket = this.Rest.GetBucket(RestRequestMethod.DELETE, route, new {guild_id = guildId, template_code = templateCode }, out var path);
var url = Utilities.GetApiUriFor(path, this.Discord.Configuration);
var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.DELETE, route).ConfigureAwait(false);
var templateRaw = JsonConvert.DeserializeObject(res.Response);
return templateRaw;
}
///
/// Gets the guild membership screening form async.
///
/// The guild_id.
internal async Task GetGuildMembershipScreeningFormAsync(ulong guildId)
{
var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.MEMBER_VERIFICATION}";
var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new {guild_id = guildId }, out var path);
var url = Utilities.GetApiUriFor(path, this.Discord.Configuration);
var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.GET, route).ConfigureAwait(false);
var screeningRaw = JsonConvert.DeserializeObject(res.Response);
return screeningRaw;
}
///
/// Modifies the guild membership screening form async.
///
/// The guild_id.
/// The enabled.
/// The fields.
/// The description.
internal async Task ModifyGuildMembershipScreeningFormAsync(ulong guildId, Optional enabled, Optional fields, Optional description)
{
var pld = new RestGuildMembershipScreeningFormModifyPayload
{
Enabled = enabled,
Description = description,
Fields = fields
};
var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.MEMBER_VERIFICATION}";
var bucket = this.Rest.GetBucket(RestRequestMethod.PATCH, route, new {guild_id = guildId }, out var path);
var url = Utilities.GetApiUriFor(path, this.Discord.Configuration);
var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.PATCH, route, payload: DiscordJson.SerializeObject(pld)).ConfigureAwait(false);
var screeningRaw = JsonConvert.DeserializeObject(res.Response);
return screeningRaw;
}
///
/// Gets the guild welcome screen async.
///
/// The guild_id.
internal async Task GetGuildWelcomeScreenAsync(ulong guildId)
{
var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.WELCOME_SCREEN}";
var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new {guild_id = guildId }, out var path);
var url = Utilities.GetApiUriFor(path, this.Discord.Configuration);
var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.GET, route);
var ret = JsonConvert.DeserializeObject(res.Response);
return ret;
}
///
/// Modifies the guild welcome screen async.
///
/// The guild_id.
/// The enabled.
/// The welcome channels.
/// The description.
internal async Task ModifyGuildWelcomeScreenAsync(ulong guildId, Optional enabled, Optional> welcomeChannels, Optional description)
{
var pld = new RestGuildWelcomeScreenModifyPayload
{
Enabled = enabled,
WelcomeChannels = welcomeChannels,
Description = description
};
var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.WELCOME_SCREEN}";
var bucket = this.Rest.GetBucket(RestRequestMethod.PATCH, route, new {guild_id = guildId }, out var path);
var url = Utilities.GetApiUriFor(path, this.Discord.Configuration);
var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.PATCH, route, payload: DiscordJson.SerializeObject(pld));
var ret = JsonConvert.DeserializeObject(res.Response);
return ret;
}
///
/// Updates the current user voice state async.
///
/// The guild_id.
/// The channel id.
/// If true, suppress.
/// The request to speak timestamp.
internal async Task UpdateCurrentUserVoiceStateAsync(ulong guildId, ulong channelId, bool? suppress, DateTimeOffset? requestToSpeakTimestamp)
{
var pld = new RestGuildUpdateCurrentUserVoiceStatePayload
{
ChannelId = channelId,
Suppress = suppress,
RequestToSpeakTimestamp = requestToSpeakTimestamp
};
var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.VOICE_STATES}/@me";
var bucket = this.Rest.GetBucket(RestRequestMethod.PATCH, route, new {guild_id = guildId }, out var path);
var url = Utilities.GetApiUriFor(path, this.Discord.Configuration);
await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.PATCH, route, payload: DiscordJson.SerializeObject(pld));
}
///
/// Updates the user voice state async.
///
/// The guild_id.
/// The user_id.
/// The channel id.
/// If true, suppress.
internal async Task UpdateUserVoiceStateAsync(ulong guildId, ulong userId, ulong channelId, bool? suppress)
{
var pld = new RestGuildUpdateUserVoiceStatePayload
{
ChannelId = channelId,
Suppress = suppress
};
var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.VOICE_STATES}/:user_id";
var bucket = this.Rest.GetBucket(RestRequestMethod.PATCH, route, new {guild_id = guildId, user_id = userId }, out var path);
var url = Utilities.GetApiUriFor(path, this.Discord.Configuration);
await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.PATCH, route, payload: DiscordJson.SerializeObject(pld));
}
#endregion
#region Guild Scheduled Events
///
/// Creates a scheduled event.
///
internal async Task CreateGuildScheduledEventAsync(ulong guildId, ulong? channelId, DiscordScheduledEventEntityMetadata metadata, string name, DateTimeOffset scheduledStartTime, DateTimeOffset? scheduledEndTime, string description, ScheduledEventEntityType type, Optional coverb64, string reason = null)
{
var pld = new RestGuildScheduledEventCreatePayload
{
ChannelId = channelId,
EntityMetadata = metadata,
Name = name,
ScheduledStartTime = scheduledStartTime,
ScheduledEndTime = scheduledEndTime,
Description = description,
EntityType = type,
CoverBase64 = coverb64
};
var headers = Utilities.GetBaseHeaders();
if (!string.IsNullOrWhiteSpace(reason))
headers[REASON_HEADER_NAME] = reason;
var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.SCHEDULED_EVENTS}";
var bucket = this.Rest.GetBucket(RestRequestMethod.POST, route, new {guild_id = guildId }, out var path);
var url = Utilities.GetApiUriFor(path, this.Discord.Configuration);
var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.POST, route, headers, DiscordJson.SerializeObject(pld));
var scheduledEvent = JsonConvert.DeserializeObject(res.Response);
var guild = this.Discord.Guilds[guildId];
scheduledEvent.Discord = this.Discord;
if (scheduledEvent.Creator != null)
scheduledEvent.Creator.Discord = this.Discord;
if (this.Discord is DiscordClient dc)
await dc.OnGuildScheduledEventCreateEventAsync(scheduledEvent, guild);
return scheduledEvent;
}
///
/// Modifies a scheduled event.
///
internal async Task ModifyGuildScheduledEventAsync(ulong guildId, ulong scheduledEventId, Optional channelId, Optional metadata, Optional name, Optional scheduledStartTime, Optional scheduledEndTime, Optional description, Optional type, Optional status, Optional coverb64, string reason = null)
{
var pld = new RestGuildScheduledEventModifyPayload
{
ChannelId = channelId,
EntityMetadata = metadata,
Name = name,
ScheduledStartTime = scheduledStartTime,
ScheduledEndTime = scheduledEndTime,
Description = description,
EntityType = type,
Status = status,
CoverBase64 = coverb64
};
var headers = Utilities.GetBaseHeaders();
if (!string.IsNullOrWhiteSpace(reason))
headers[REASON_HEADER_NAME] = reason;
var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.SCHEDULED_EVENTS}/:scheduled_event_id";
var bucket = this.Rest.GetBucket(RestRequestMethod.PATCH, route, new {guild_id = guildId, scheduled_event_id = scheduledEventId }, out var path);
var url = Utilities.GetApiUriFor(path, this.Discord.Configuration);
var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.PATCH, route, headers, DiscordJson.SerializeObject(pld));
var scheduledEvent = JsonConvert.DeserializeObject(res.Response);
var guild = this.Discord.Guilds[guildId];
scheduledEvent.Discord = this.Discord;
if (scheduledEvent.Creator != null)
{
scheduledEvent.Creator.Discord = this.Discord;
this.Discord.UserCache.AddOrUpdate(scheduledEvent.Creator.Id, scheduledEvent.Creator, (id, old) =>
{
old.Username = scheduledEvent.Creator.Username;
old.Discriminator = scheduledEvent.Creator.Discriminator;
old.AvatarHash = scheduledEvent.Creator.AvatarHash;
old.Flags = scheduledEvent.Creator.Flags;
return old;
});
}
if (this.Discord is DiscordClient dc)
await dc.OnGuildScheduledEventUpdateEventAsync(scheduledEvent, guild);
return scheduledEvent;
}
///
/// Modifies a scheduled event.
///
internal async Task ModifyGuildScheduledEventStatusAsync(ulong guildId, ulong scheduledEventId, ScheduledEventStatus status, string reason = null)
{
var pld = new RestGuildScheduledEventModifyPayload
{
Status = status
};
var headers = Utilities.GetBaseHeaders();
if (!string.IsNullOrWhiteSpace(reason))
headers[REASON_HEADER_NAME] = reason;
var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.SCHEDULED_EVENTS}/:scheduled_event_id";
var bucket = this.Rest.GetBucket(RestRequestMethod.PATCH, route, new {guild_id = guildId, scheduled_event_id = scheduledEventId }, out var path);
var url = Utilities.GetApiUriFor(path, this.Discord.Configuration);
var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.PATCH, route, headers, DiscordJson.SerializeObject(pld));
var scheduledEvent = JsonConvert.DeserializeObject(res.Response);
var guild = this.Discord.Guilds[guildId];
scheduledEvent.Discord = this.Discord;
if (scheduledEvent.Creator != null)
{
scheduledEvent.Creator.Discord = this.Discord;
this.Discord.UserCache.AddOrUpdate(scheduledEvent.Creator.Id, scheduledEvent.Creator, (id, old) =>
{
old.Username = scheduledEvent.Creator.Username;
old.Discriminator = scheduledEvent.Creator.Discriminator;
old.AvatarHash = scheduledEvent.Creator.AvatarHash;
old.Flags = scheduledEvent.Creator.Flags;
return old;
});
}
if (this.Discord is DiscordClient dc)
await dc.OnGuildScheduledEventUpdateEventAsync(scheduledEvent, guild);
return scheduledEvent;
}
///
/// Gets a scheduled event.
///
/// The guild_id.
/// The event id.
/// Whether to include user count.
internal async Task GetGuildScheduledEventAsync(ulong guildId, ulong scheduledEventId, bool? withUserCount)
{
var urlParams = new Dictionary