//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 } }