//using Discord.Rest.Entities.Interactions;
using Discord.Net;
using Discord.Net.Converters;
using Discord.Net.ED25519;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
using System.Text;
using System.Threading.Tasks;
namespace Discord.Rest
{
///
/// Provides a client to send REST-based requests to Discord.
///
public class DiscordRestClient : BaseDiscordClient, IDiscordClient
{
#region DiscordRestClient
private RestApplication _applicationInfo;
internal static JsonSerializer Serializer = new JsonSerializer() { ContractResolver = new DiscordContractResolver(), NullValueHandling = NullValueHandling.Include };
///
/// Gets the logged-in user.
///
public new RestSelfUser CurrentUser { get => base.CurrentUser as RestSelfUser; internal set => base.CurrentUser = value; }
///
public DiscordRestClient() : this(new DiscordRestConfig()) { }
///
/// Initializes a new with the provided configuration.
///
/// The configuration to be used with the client.
public DiscordRestClient(DiscordRestConfig config) : base(config, CreateApiClient(config))
{
_apiOnCreation = config.APIOnRestInteractionCreation;
}
// used for socket client rest access
internal DiscordRestClient(DiscordRestConfig config, API.DiscordRestApiClient api) : base(config, api)
{
_apiOnCreation = config.APIOnRestInteractionCreation;
}
private static API.DiscordRestApiClient CreateApiClient(DiscordRestConfig config)
=> new API.DiscordRestApiClient(config.RestClientProvider, DiscordRestConfig.UserAgent, serializer: Serializer, useSystemClock: config.UseSystemClock, defaultRatelimitCallback: config.DefaultRatelimitCallback);
internal override void Dispose(bool disposing)
{
if (disposing)
ApiClient.Dispose();
base.Dispose(disposing);
}
internal override async ValueTask DisposeAsync(bool disposing)
{
if (disposing)
await ApiClient.DisposeAsync().ConfigureAwait(false);
base.Dispose(disposing);
}
///
internal override async Task OnLoginAsync(TokenType tokenType, string token)
{
var user = await ApiClient.GetMyUserAsync(new RequestOptions { RetryMode = RetryMode.AlwaysRetry }).ConfigureAwait(false);
ApiClient.CurrentUserId = user.Id;
base.CurrentUser = RestSelfUser.Create(this, user);
if(tokenType == TokenType.Bot)
{
await GetApplicationInfoAsync(new RequestOptions { RetryMode = RetryMode.AlwaysRetry }).ConfigureAwait(false);
ApiClient.CurrentApplicationId = _applicationInfo.Id;
}
}
internal void CreateRestSelfUser(API.User user)
{
base.CurrentUser = RestSelfUser.Create(this, user);
}
///
internal override Task OnLogoutAsync()
{
_applicationInfo = null;
return Task.Delay(0);
}
#region Rest interactions
private readonly bool _apiOnCreation;
public bool IsValidHttpInteraction(string publicKey, string signature, string timestamp, string body)
=> IsValidHttpInteraction(publicKey, signature, timestamp, Encoding.UTF8.GetBytes(body));
public bool IsValidHttpInteraction(string publicKey, string signature, string timestamp, byte[] body)
{
var key = HexConverter.HexToByteArray(publicKey);
var sig = HexConverter.HexToByteArray(signature);
var tsp = Encoding.UTF8.GetBytes(timestamp);
var message = new List();
message.AddRange(tsp);
message.AddRange(body);
return IsValidHttpInteraction(key, sig, message.ToArray());
}
private bool IsValidHttpInteraction(byte[] publicKey, byte[] signature, byte[] message)
{
return Ed25519.Verify(signature, message, publicKey);
}
///
/// Creates a from a http message.
///
/// The public key of your application
/// The signature sent with the interaction.
/// The timestamp sent with the interaction.
/// The body of the http message.
///
/// A that represents the incoming http interaction.
///
/// Thrown when the signature doesn't match the public key.
public Task ParseHttpInteractionAsync(string publicKey, string signature, string timestamp, string body, Func doApiCallOnCreation = null)
=> ParseHttpInteractionAsync(publicKey, signature, timestamp, Encoding.UTF8.GetBytes(body), doApiCallOnCreation);
///
/// Creates a from a http message.
///
/// The public key of your application
/// The signature sent with the interaction.
/// The timestamp sent with the interaction.
/// The body of the http message.
///
/// A that represents the incoming http interaction.
///
/// Thrown when the signature doesn't match the public key.
public async Task ParseHttpInteractionAsync(string publicKey, string signature, string timestamp, byte[] body, Func doApiCallOnCreation = null)
{
if (!IsValidHttpInteraction(publicKey, signature, timestamp, body))
{
throw new BadSignatureException();
}
using (var textReader = new StringReader(Encoding.UTF8.GetString(body)))
using (var jsonReader = new JsonTextReader(textReader))
{
var model = Serializer.Deserialize(jsonReader);
return await RestInteraction.CreateAsync(this, model, doApiCallOnCreation is not null ? doApiCallOnCreation(new InteractionProperties(model)) : _apiOnCreation);
}
}
#endregion
public async Task GetCurrentUserAsync(RequestOptions options = null)
{
var user = await ApiClient.GetMyUserAsync(options);
CurrentUser.Update(user);
return CurrentUser;
}
public async Task GetCurrentUserGuildMemberAsync(ulong guildId, RequestOptions options = null)
{
var user = await ApiClient.GetCurrentUserGuildMember(guildId, options);
return RestGuildUser.Create(this, null, user, guildId);
}
public async Task GetApplicationInfoAsync(RequestOptions options = null)
{
return _applicationInfo ??= await ClientHelper.GetApplicationInfoAsync(this, options).ConfigureAwait(false);
}
public Task GetChannelAsync(ulong id, RequestOptions options = null)
=> ClientHelper.GetChannelAsync(this, id, options);
public Task> GetPrivateChannelsAsync(RequestOptions options = null)
=> ClientHelper.GetPrivateChannelsAsync(this, options);
public Task> GetDMChannelsAsync(RequestOptions options = null)
=> ClientHelper.GetDMChannelsAsync(this, options);
public Task> GetGroupChannelsAsync(RequestOptions options = null)
=> ClientHelper.GetGroupChannelsAsync(this, options);
public Task> GetConnectionsAsync(RequestOptions options = null)
=> ClientHelper.GetConnectionsAsync(this, options);
public Task GetInviteAsync(string inviteId, RequestOptions options = null)
=> ClientHelper.GetInviteAsync(this, inviteId, options);
public Task GetGuildAsync(ulong id, RequestOptions options = null)
=> ClientHelper.GetGuildAsync(this, id, false, options);
public Task GetGuildAsync(ulong id, bool withCounts, RequestOptions options = null)
=> ClientHelper.GetGuildAsync(this, id, withCounts, options);
public Task GetGuildWidgetAsync(ulong id, RequestOptions options = null)
=> ClientHelper.GetGuildWidgetAsync(this, id, options);
public IAsyncEnumerable> GetGuildSummariesAsync(RequestOptions options = null)
=> ClientHelper.GetGuildSummariesAsync(this, null, null, options);
public IAsyncEnumerable> GetGuildSummariesAsync(ulong fromGuildId, int limit, RequestOptions options = null)
=> ClientHelper.GetGuildSummariesAsync(this, fromGuildId, limit, options);
public Task> GetGuildsAsync(RequestOptions options = null)
=> ClientHelper.GetGuildsAsync(this, false, options);
public Task> GetGuildsAsync(bool withCounts, RequestOptions options = null)
=> ClientHelper.GetGuildsAsync(this, withCounts, options);
public Task CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon = null, RequestOptions options = null)
=> ClientHelper.CreateGuildAsync(this, name, region, jpegIcon, options);
public Task GetUserAsync(ulong id, RequestOptions options = null)
=> ClientHelper.GetUserAsync(this, id, options);
public Task GetGuildUserAsync(ulong guildId, ulong id, RequestOptions options = null)
=> ClientHelper.GetGuildUserAsync(this, guildId, id, options);
public Task> GetVoiceRegionsAsync(RequestOptions options = null)
=> ClientHelper.GetVoiceRegionsAsync(this, options);
public Task GetVoiceRegionAsync(string id, RequestOptions options = null)
=> ClientHelper.GetVoiceRegionAsync(this, id, options);
public Task GetWebhookAsync(ulong id, RequestOptions options = null)
=> ClientHelper.GetWebhookAsync(this, id, options);
public Task CreateGlobalCommand(ApplicationCommandProperties properties, RequestOptions options = null)
=> ClientHelper.CreateGlobalApplicationCommandAsync(this, properties, options);
public Task CreateGuildCommand(ApplicationCommandProperties properties, ulong guildId, RequestOptions options = null)
=> ClientHelper.CreateGuildApplicationCommandAsync(this, guildId, properties, options);
public Task> GetGlobalApplicationCommands(bool withLocalizations = false, string locale = null, RequestOptions options = null)
=> ClientHelper.GetGlobalApplicationCommandsAsync(this, withLocalizations, locale, options);
public Task> GetGuildApplicationCommands(ulong guildId, bool withLocalizations = false, string locale = null, RequestOptions options = null)
=> ClientHelper.GetGuildApplicationCommandsAsync(this, guildId, withLocalizations, locale, options);
public Task> BulkOverwriteGlobalCommands(ApplicationCommandProperties[] commandProperties, RequestOptions options = null)
=> ClientHelper.BulkOverwriteGlobalApplicationCommandAsync(this, commandProperties, options);
public Task> BulkOverwriteGuildCommands(ApplicationCommandProperties[] commandProperties, ulong guildId, RequestOptions options = null)
=> ClientHelper.BulkOverwriteGuildApplicationCommandAsync(this, guildId, commandProperties, options);
public Task> BatchEditGuildCommandPermissions(ulong guildId, IDictionary permissions, RequestOptions options = null)
=> InteractionHelper.BatchEditGuildCommandPermissionsAsync(this, guildId, permissions, options);
public Task DeleteAllGlobalCommandsAsync(RequestOptions options = null)
=> InteractionHelper.DeleteAllGlobalCommandsAsync(this, options);
public Task AddRoleAsync(ulong guildId, ulong userId, ulong roleId)
=> ClientHelper.AddRoleAsync(this, guildId, userId, roleId);
public Task RemoveRoleAsync(ulong guildId, ulong userId, ulong roleId)
=> ClientHelper.RemoveRoleAsync(this, guildId, userId, roleId);
public Task AddReactionAsync(ulong channelId, ulong messageId, IEmote emote, RequestOptions options = null)
=> MessageHelper.AddReactionAsync(channelId, messageId, emote, this, options);
public Task RemoveReactionAsync(ulong channelId, ulong messageId, ulong userId, IEmote emote, RequestOptions options = null)
=> MessageHelper.RemoveReactionAsync(channelId, messageId, userId, emote, this, options);
public Task RemoveAllReactionsAsync(ulong channelId, ulong messageId, RequestOptions options = null)
=> MessageHelper.RemoveAllReactionsAsync(channelId, messageId, this, options);
public Task RemoveAllReactionsForEmoteAsync(ulong channelId, ulong messageId, IEmote emote, RequestOptions options = null)
=> MessageHelper.RemoveAllReactionsForEmoteAsync(channelId, messageId, emote, this, options);
public Task> GetRoleConnectionMetadataRecordsAsync(RequestOptions options = null)
=> ClientHelper.GetRoleConnectionMetadataRecordsAsync(this, options);
public Task> ModifyRoleConnectionMetadataRecordsAsync(ICollection metadata, RequestOptions options = null)
{
Preconditions.AtMost(metadata.Count, 5, nameof(metadata), "An application can have a maximum of 5 metadata records.");
return ClientHelper.ModifyRoleConnectionMetadataRecordsAsync(metadata, this, options);
}
public Task GetUserApplicationRoleConnectionAsync(ulong applicationId, RequestOptions options = null)
=> ClientHelper.GetUserRoleConnectionAsync(applicationId, this, options);
public Task ModifyUserApplicationRoleConnectionAsync(ulong applicationId, RoleConnectionProperties roleConnection, RequestOptions options = null)
=> ClientHelper.ModifyUserRoleConnectionAsync(applicationId, roleConnection, this, options);
#endregion
#region IDiscordClient
///
async Task IDiscordClient.GetApplicationInfoAsync(RequestOptions options)
=> await GetApplicationInfoAsync(options).ConfigureAwait(false);
///
async Task IDiscordClient.GetChannelAsync(ulong id, CacheMode mode, RequestOptions options)
{
if (mode == CacheMode.AllowDownload)
return await GetChannelAsync(id, options).ConfigureAwait(false);
else
return null;
}
///
async Task> IDiscordClient.GetPrivateChannelsAsync(CacheMode mode, RequestOptions options)
{
if (mode == CacheMode.AllowDownload)
return await GetPrivateChannelsAsync(options).ConfigureAwait(false);
else
return ImmutableArray.Create();
}
///
async Task> IDiscordClient.GetDMChannelsAsync(CacheMode mode, RequestOptions options)
{
if (mode == CacheMode.AllowDownload)
return await GetDMChannelsAsync(options).ConfigureAwait(false);
else
return ImmutableArray.Create();
}
///
async Task> IDiscordClient.GetGroupChannelsAsync(CacheMode mode, RequestOptions options)
{
if (mode == CacheMode.AllowDownload)
return await GetGroupChannelsAsync(options).ConfigureAwait(false);
else
return ImmutableArray.Create();
}
///
async Task> IDiscordClient.GetConnectionsAsync(RequestOptions options)
=> await GetConnectionsAsync(options).ConfigureAwait(false);
async Task IDiscordClient.GetInviteAsync(string inviteId, RequestOptions options)
=> await GetInviteAsync(inviteId, options).ConfigureAwait(false);
///
async Task IDiscordClient.GetGuildAsync(ulong id, CacheMode mode, RequestOptions options)
{
if (mode == CacheMode.AllowDownload)
return await GetGuildAsync(id, options).ConfigureAwait(false);
else
return null;
}
///
async Task> IDiscordClient.GetGuildsAsync(CacheMode mode, RequestOptions options)
{
if (mode == CacheMode.AllowDownload)
return await GetGuildsAsync(options).ConfigureAwait(false);
else
return ImmutableArray.Create();
}
///
async Task IDiscordClient.CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon, RequestOptions options)
=> await CreateGuildAsync(name, region, jpegIcon, options).ConfigureAwait(false);
///
async Task IDiscordClient.GetUserAsync(ulong id, CacheMode mode, RequestOptions options)
{
if (mode == CacheMode.AllowDownload)
return await GetUserAsync(id, options).ConfigureAwait(false);
else
return null;
}
///
async Task> IDiscordClient.GetVoiceRegionsAsync(RequestOptions options)
=> await GetVoiceRegionsAsync(options).ConfigureAwait(false);
///
async Task IDiscordClient.GetVoiceRegionAsync(string id, RequestOptions options)
=> await GetVoiceRegionAsync(id, options).ConfigureAwait(false);
///
async Task IDiscordClient.GetWebhookAsync(ulong id, RequestOptions options)
=> await GetWebhookAsync(id, options).ConfigureAwait(false);
///
async Task> IDiscordClient.GetGlobalApplicationCommandsAsync(bool withLocalizations, string locale, RequestOptions options)
=> await GetGlobalApplicationCommands(withLocalizations, locale, options).ConfigureAwait(false);
///
async Task IDiscordClient.GetGlobalApplicationCommandAsync(ulong id, RequestOptions options)
=> await ClientHelper.GetGlobalApplicationCommandAsync(this, id, options).ConfigureAwait(false);
#endregion
}
}