diff --git a/DisCatSharp/Entities/Message/DiscordMessage.cs b/DisCatSharp/Entities/Message/DiscordMessage.cs
index 5ecddffaa..d39a17b0f 100644
--- a/DisCatSharp/Entities/Message/DiscordMessage.cs
+++ b/DisCatSharp/Entities/Message/DiscordMessage.cs
@@ -1,881 +1,891 @@
// This file is part of the DisCatSharp project.
//
// Copyright (c) 2021 AITSYS
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Globalization;
using System.Linq;
+using System.Net.Mail;
using System.Threading.Tasks;
using Newtonsoft.Json;
namespace DisCatSharp.Entities
{
///
/// Represents a Discord text message.
///
public class DiscordMessage : SnowflakeObject, IEquatable
{
///
/// Initializes a new instance of the class.
///
internal DiscordMessage()
{
this._attachmentsLazy = new Lazy>(() => new ReadOnlyCollection(this._attachments));
this._embedsLazy = new Lazy>(() => new ReadOnlyCollection(this._embeds));
this._mentionedChannelsLazy = new Lazy>(() => this._mentionedChannels != null
? new ReadOnlyCollection(this._mentionedChannels)
: Array.Empty());
this._mentionedRolesLazy = new Lazy>(() => this._mentionedRoles != null ? new ReadOnlyCollection(this._mentionedRoles) : Array.Empty());
this._mentionedUsersLazy = new Lazy>(() => new ReadOnlyCollection(this._mentionedUsers));
this._reactionsLazy = new Lazy>(() => new ReadOnlyCollection(this._reactions));
this._stickersLazy = new Lazy>(() => new ReadOnlyCollection(this._stickers));
this._jumpLink = new Lazy(() =>
{
var gid = this.Channel != null
? this.Channel is DiscordDmChannel ? "@me" : this.Channel.GuildId.Value.ToString(CultureInfo.InvariantCulture)
: this.InternalThread.GuildId.Value.ToString(CultureInfo.InvariantCulture);
var cid = this.ChannelId.ToString(CultureInfo.InvariantCulture);
var mid = this.Id.ToString(CultureInfo.InvariantCulture);
return new Uri($"https://{(this.Discord.Configuration.UseCanary ? "canary.discord.com" : "discord.com")}/channels/{gid}/{cid}/{mid}");
});
}
///
/// Initializes a new instance of the class.
///
/// The other.
internal DiscordMessage(DiscordMessage other)
: this()
{
this.Discord = other.Discord;
this._attachments = other._attachments; // the attachments cannot change, thus no need to copy and reallocate.
this._embeds = new List(other._embeds);
if (other._mentionedChannels != null)
this._mentionedChannels = new List(other._mentionedChannels);
if (other._mentionedRoles != null)
this._mentionedRoles = new List(other._mentionedRoles);
if (other._mentionedRoleIds != null)
this._mentionedRoleIds = new List(other._mentionedRoleIds);
this._mentionedUsers = new List(other._mentionedUsers);
this._reactions = new List(other._reactions);
this._stickers = new List(other._stickers);
this.Author = other.Author;
this.ChannelId = other.ChannelId;
this.Content = other.Content;
this.EditedTimestampRaw = other.EditedTimestampRaw;
this.Id = other.Id;
this.IsTTS = other.IsTTS;
this.MessageType = other.MessageType;
this.Pinned = other.Pinned;
this.TimestampRaw = other.TimestampRaw;
this.WebhookId = other.WebhookId;
}
///
/// Gets the channel in which the message was sent.
///
[JsonIgnore]
public DiscordChannel Channel
{
get => (this.Discord as DiscordClient)?.InternalGetCachedChannel(this.ChannelId) ?? this._channel;
internal set => this._channel = value;
}
private DiscordChannel _channel;
///
/// Gets the thread in which the message was sent.
///
[JsonIgnore]
private DiscordThreadChannel InternalThread
{
get => (this.Discord as DiscordClient)?.InternalGetCachedThread(this.ChannelId) ?? this._thread;
set => this._thread = value;
}
private DiscordThreadChannel _thread;
///
/// Gets the ID of the channel in which the message was sent.
///
[JsonProperty("channel_id", NullValueHandling = NullValueHandling.Ignore)]
public ulong ChannelId { get; internal set; }
///
/// Gets the components this message was sent with.
///
[JsonProperty("components", NullValueHandling = NullValueHandling.Ignore)]
public IReadOnlyCollection Components { get; internal set; }
///
/// Gets the user or member that sent the message.
///
[JsonProperty("author", NullValueHandling = NullValueHandling.Ignore)]
public DiscordUser Author { get; internal set; }
///
/// Gets the message's content.
///
[JsonProperty("content", NullValueHandling = NullValueHandling.Ignore)]
public string Content { get; internal set; }
///
/// Gets the message's creation timestamp.
///
[JsonIgnore]
public DateTimeOffset Timestamp
=> !string.IsNullOrWhiteSpace(this.TimestampRaw) && DateTimeOffset.TryParse(this.TimestampRaw, CultureInfo.InvariantCulture, DateTimeStyles.None, out var dto) ?
dto : this.CreationTimestamp;
///
/// Gets the message's creation timestamp as raw string.
///
[JsonProperty("timestamp", NullValueHandling = NullValueHandling.Ignore)]
internal string TimestampRaw { get; set; }
///
/// Gets the message's edit timestamp. Will be null if the message was not edited.
///
[JsonIgnore]
public DateTimeOffset? EditedTimestamp
=> !string.IsNullOrWhiteSpace(this.EditedTimestampRaw) && DateTimeOffset.TryParse(this.EditedTimestampRaw, CultureInfo.InvariantCulture, DateTimeStyles.None, out var dto) ?
(DateTimeOffset?)dto : null;
///
/// Gets the message's edit timestamp as raw string. Will be null if the message was not edited.
///
[JsonProperty("edited_timestamp", NullValueHandling = NullValueHandling.Ignore)]
internal string EditedTimestampRaw { get; set; }
///
/// Gets whether this message was edited.
///
[JsonIgnore]
public bool IsEdited
=> !string.IsNullOrWhiteSpace(this.EditedTimestampRaw);
///
/// Gets whether the message is a text-to-speech message.
///
[JsonProperty("tts", NullValueHandling = NullValueHandling.Ignore)]
public bool IsTTS { get; internal set; }
///
/// Gets whether the message mentions everyone.
///
[JsonProperty("mention_everyone", NullValueHandling = NullValueHandling.Ignore)]
public bool MentionEveryone { get; internal set; }
///
/// Gets users or members mentioned by this message.
///
[JsonIgnore]
public IReadOnlyList MentionedUsers
=> this._mentionedUsersLazy.Value;
[JsonProperty("mentions", NullValueHandling = NullValueHandling.Ignore)]
internal List _mentionedUsers;
[JsonIgnore]
internal readonly Lazy> _mentionedUsersLazy;
// TODO this will probably throw an exception in DMs since it tries to wrap around a null List...
// this is probably low priority but need to find out a clean way to solve it...
///
/// Gets roles mentioned by this message.
///
[JsonIgnore]
public IReadOnlyList MentionedRoles
=> this._mentionedRolesLazy.Value;
[JsonIgnore]
internal List _mentionedRoles;
[JsonProperty("mention_roles")]
internal List _mentionedRoleIds;
[JsonIgnore]
private readonly Lazy> _mentionedRolesLazy;
///
/// Gets channels mentioned by this message.
///
[JsonIgnore]
public IReadOnlyList MentionedChannels
=> this._mentionedChannelsLazy.Value;
[JsonIgnore]
internal List _mentionedChannels;
[JsonIgnore]
private readonly Lazy> _mentionedChannelsLazy;
///
/// Gets files attached to this message.
///
[JsonIgnore]
public IReadOnlyList Attachments
=> this._attachmentsLazy.Value;
[JsonProperty("attachments", NullValueHandling = NullValueHandling.Ignore)]
internal List _attachments = new();
[JsonIgnore]
private readonly Lazy> _attachmentsLazy;
///
/// Gets embeds attached to this message.
///
[JsonIgnore]
public IReadOnlyList Embeds
=> this._embedsLazy.Value;
[JsonProperty("embeds", NullValueHandling = NullValueHandling.Ignore)]
internal List _embeds = new();
[JsonIgnore]
private readonly Lazy> _embedsLazy;
///
/// Gets reactions used on this message.
///
[JsonIgnore]
public IReadOnlyList Reactions
=> this._reactionsLazy.Value;
[JsonProperty("reactions", NullValueHandling = NullValueHandling.Ignore)]
internal List _reactions = new();
[JsonIgnore]
private readonly Lazy> _reactionsLazy;
/*
///
/// Gets the nonce sent with the message, if the message was sent by the client.
///
[JsonProperty("nonce", NullValueHandling = NullValueHandling.Ignore)]
public ulong? Nonce { get; internal set; }
*/
///
/// Gets whether the message is pinned.
///
[JsonProperty("pinned", NullValueHandling = NullValueHandling.Ignore)]
public bool Pinned { get; internal set; }
///
/// Gets the id of the webhook that generated this message.
///
[JsonProperty("webhook_id", NullValueHandling = NullValueHandling.Ignore)]
public ulong? WebhookId { get; internal set; }
///
/// Gets the type of the message.
///
[JsonProperty("type", NullValueHandling = NullValueHandling.Ignore)]
public MessageType? MessageType { get; internal set; }
///
/// Gets the message activity in the Rich Presence embed.
///
[JsonProperty("activity", NullValueHandling = NullValueHandling.Ignore)]
public DiscordMessageActivity Activity { get; internal set; }
///
/// Gets the message application in the Rich Presence embed.
///
[JsonProperty("application", NullValueHandling = NullValueHandling.Ignore)]
public DiscordMessageApplication Application { get; internal set; }
///
/// Gets the message application id in the Rich Presence embed.
///
[JsonProperty("application_id", NullValueHandling = NullValueHandling.Ignore)]
public ulong ApplicationId { get; internal set; }
///
/// Gets the internal reference.
///
[JsonProperty("message_reference", NullValueHandling = NullValueHandling.Ignore)]
internal InternalDiscordMessageReference? InternalReference { get; set; }
///
/// Gets the original message reference from the crossposted message.
///
[JsonIgnore]
public DiscordMessageReference Reference
=> this.InternalReference.HasValue ? this?.InternalBuildMessageReference() : null;
///
/// Gets the bitwise flags for this message.
///
[JsonProperty("flags", NullValueHandling = NullValueHandling.Ignore)]
public MessageFlags? Flags { get; internal set; }
///
/// Gets whether the message originated from a webhook.
///
[JsonIgnore]
public bool WebhookMessage
=> this.WebhookId != null;
///
/// Gets the jump link to this message.
///
[JsonIgnore]
public Uri JumpLink => this._jumpLink.Value;
private readonly Lazy _jumpLink;
///
/// Gets stickers for this message.
///
[JsonIgnore]
public IReadOnlyList Stickers
=> this._stickersLazy.Value;
[JsonProperty("sticker_items", NullValueHandling = NullValueHandling.Ignore)]
internal List _stickers = new();
[JsonIgnore]
private readonly Lazy> _stickersLazy;
///
/// Gets the guild id.
///
[JsonProperty("guild_id", NullValueHandling = NullValueHandling.Ignore)]
internal ulong? GuildId { get; set; }
///
/// Gets the message object for the referenced message
///
[JsonProperty("referenced_message", NullValueHandling = NullValueHandling.Ignore)]
public DiscordMessage ReferencedMessage { get; internal set; }
///
/// Gets whether the message is a response to an interaction.
///
[JsonProperty("interaction", NullValueHandling = NullValueHandling.Ignore)]
public DiscordMessageInteraction Interaction { get; internal set; }
///
/// Gets the thread that was started from this message.
///
[JsonProperty("thread", NullValueHandling = NullValueHandling.Ignore)]
public DiscordThreadChannel Thread { get; internal set; }
///
/// Build the message reference.
///
internal DiscordMessageReference InternalBuildMessageReference()
{
var client = this.Discord as DiscordClient;
var guildId = this.InternalReference.Value.GuildId;
var channelId = this.InternalReference.Value.ChannelId;
var messageId = this.InternalReference.Value.MessageId;
var reference = new DiscordMessageReference();
if (guildId.HasValue)
reference.Guild = client._guilds.TryGetValue(guildId.Value, out var g)
? g
: new DiscordGuild
{
Id = guildId.Value,
Discord = client
};
var channel = client.InternalGetCachedChannel(channelId.Value);
if (channel == null)
{
reference.Channel = new DiscordChannel
{
Id = channelId.Value,
Discord = client
};
if (guildId.HasValue)
reference.Channel.GuildId = guildId.Value;
}
else reference.Channel = channel;
if (client.MessageCache != null && client.MessageCache.TryGet(m => m.Id == messageId.Value && m.ChannelId == channelId, out var msg))
reference.Message = msg;
else
{
reference.Message = new DiscordMessage
{
ChannelId = this.ChannelId,
Discord = client
};
if (messageId.HasValue)
reference.Message.Id = messageId.Value;
}
return reference;
}
///
/// Gets the mentions.
///
/// An array of IMentions.
private IMention[] GetMentions()
{
var mentions = new List();
if (this.ReferencedMessage != null && this._mentionedUsers.Contains(this.ReferencedMessage.Author))
mentions.Add(new RepliedUserMention()); // Return null to allow all mentions
if (this._mentionedUsers.Any())
mentions.AddRange(this._mentionedUsers.Select(m => (IMention)new UserMention(m)));
if (this._mentionedRoleIds.Any())
mentions.AddRange(this._mentionedRoleIds.Select(r => (IMention)new RoleMention(r)));
return mentions.ToArray();
}
///
/// Populates the mentions.
///
internal void PopulateMentions()
{
var guild = this.Channel?.Guild;
this._mentionedUsers ??= new List();
this._mentionedRoles ??= new List();
this._mentionedChannels ??= new List();
var mentionedUsers = new HashSet(new DiscordUserComparer());
if (guild != null)
{
foreach (var usr in this._mentionedUsers)
{
usr.Discord = this.Discord;
this.Discord.UserCache.AddOrUpdate(usr.Id, usr, (id, old) =>
{
old.Username = usr.Username;
old.Discriminator = usr.Discriminator;
old.AvatarHash = usr.AvatarHash;
return old;
});
mentionedUsers.Add(guild._members.TryGetValue(usr.Id, out var member) ? member : usr);
}
}
if (!string.IsNullOrWhiteSpace(this.Content))
{
//mentionedUsers.UnionWith(Utilities.GetUserMentions(this).Select(this.Discord.GetCachedOrEmptyUserInternal));
if (guild != null)
{
//this._mentionedRoles = this._mentionedRoles.Union(Utilities.GetRoleMentions(this).Select(xid => guild.GetRole(xid))).ToList();
this._mentionedRoles = this._mentionedRoles.Union(this._mentionedRoleIds.Select(xid => guild.GetRole(xid))).ToList();
this._mentionedChannels = this._mentionedChannels.Union(Utilities.GetChannelMentions(this).Select(xid => guild.GetChannel(xid))).ToList();
}
}
this._mentionedUsers = mentionedUsers.ToList();
}
///
/// Edits the message.
///
/// New content.
///
/// Thrown when the client tried to modify a message not sent by them.
/// Thrown when the member does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public Task ModifyAsync(Optional content)
- => this.Discord.ApiClient.EditMessageAsync(this.ChannelId, this.Id, content, default, this.GetMentions(), default, default, Array.Empty());
+ => this.Discord.ApiClient.EditMessageAsync(this.ChannelId, this.Id, content, default, this.GetMentions(), default, default, Array.Empty(), default);
///
/// Edits the message.
///
/// New embed.
///
/// Thrown when the client tried to modify a message not sent by them.
/// Thrown when the member does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public Task ModifyAsync(Optional embed = default)
- => this.Discord.ApiClient.EditMessageAsync(this.ChannelId, this.Id, default, embed.HasValue ? new[] {embed.Value} : Array.Empty(), this.GetMentions(), default, default, Array.Empty());
+ => this.Discord.ApiClient.EditMessageAsync(this.ChannelId, this.Id, default, embed.HasValue ? new[] {embed.Value} : Array.Empty(), this.GetMentions(), default, default, Array.Empty(), default);
///
/// Edits the message.
///
/// New content.
/// New embed.
///
/// Thrown when the client tried to modify a message not sent by them.
/// Thrown when the member does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public Task ModifyAsync(Optional content, Optional embed = default)
- => this.Discord.ApiClient.EditMessageAsync(this.ChannelId, this.Id, content, embed.HasValue ? new[] {embed.Value} : Array.Empty(), this.GetMentions(), default, default, Array.Empty());
+ => this.Discord.ApiClient.EditMessageAsync(this.ChannelId, this.Id, content, embed.HasValue ? new[] {embed.Value} : Array.Empty(), this.GetMentions(), default, default, Array.Empty(), default);
///
/// Edits the message.
///
/// New content.
/// New embeds.
///
/// Thrown when the client tried to modify a message not sent by them.
/// Thrown when the member does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public Task ModifyAsync(Optional content, Optional> embeds = default)
- => this.Discord.ApiClient.EditMessageAsync(this.ChannelId, this.Id, content, embeds, this.GetMentions(), default, default, Array.Empty());
+ => this.Discord.ApiClient.EditMessageAsync(this.ChannelId, this.Id, content, embeds, this.GetMentions(), default, default, Array.Empty(), default);
///
/// Edits the message.
///
/// The builder of the message to edit.
///
/// Thrown when the client tried to modify a message not sent by them.
/// Thrown when the member does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public async Task ModifyAsync(DiscordMessageBuilder builder)
{
builder.Validate(true);
- return await this.Discord.ApiClient.EditMessageAsync(this.ChannelId, this.Id, builder.Content, new Optional>(builder.Embeds), builder.Mentions, builder.Components, builder.Suppressed, builder.Files).ConfigureAwait(false);
+ var attachments = builder._attachments;
+ return await this.Discord.ApiClient.EditMessageAsync(this.ChannelId, this.Id, builder.Content, new Optional>(builder.Embeds), builder.Mentions, builder.Components, builder.Suppressed, builder.Files, attachments?.Count > 0 ? attachments : null);
}
///
/// Edits the message embed suppression.
///
/// Suppress embeds.
///
/// Thrown when the client tried to modify a message not sent by them.
/// Thrown when the member does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public Task ModifySuppressionAsync(bool suppress = false)
- => this.Discord.ApiClient.EditMessageAsync(this.ChannelId, this.Id, default, default, this.GetMentions(), default, suppress, default);
+ => this.Discord.ApiClient.EditMessageAsync(this.ChannelId, this.Id, default, default, this.GetMentions(), default, suppress, default, default);
+
+ ///
+ /// Clears all attachments from the message.
+ ///
+ ///
+ public Task ClearAttachmentsAsync()
+ => this.Discord.ApiClient.EditMessageAsync(this.ChannelId, this.Id, default, default, this.GetMentions(), default, default, default, Array.Empty().ToList());
///
/// Edits the message.
///
/// The builder of the message to edit.
///
/// Thrown when the client tried to modify a message not sent by them.
/// Thrown when the member does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public async Task ModifyAsync(Action action)
{
var builder = new DiscordMessageBuilder();
action(builder);
builder.Validate(true);
- return await this.Discord.ApiClient.EditMessageAsync(this.ChannelId, this.Id, builder.Content, new Optional>(builder.Embeds), builder.Mentions, builder.Components, builder.Suppressed, builder.Files).ConfigureAwait(false);
+ var attachments = builder._attachments;
+ return await this.Discord.ApiClient.EditMessageAsync(this.ChannelId, this.Id, builder.Content, new Optional>(builder.Embeds), builder.Mentions, builder.Components, builder.Suppressed, builder.Files, attachments?.Count > 0 ? attachments : null);
}
///
/// Deletes the message.
///
///
/// Thrown when the client does not have the permission.
/// Thrown when the member does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public Task DeleteAsync(string reason = null)
=> this.Discord.ApiClient.DeleteMessageAsync(this.ChannelId, this.Id, reason);
///
/// Creates a thread.
/// Depending on the of the parent channel it's either a or a .
///
/// The name of the thread.
/// till it gets archived. Defaults to
/// The per user ratelimit, aka slowdown.
/// The reason.
///
/// Thrown when the client does not have the or permission.
/// Thrown when the channel does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
/// Thrown when the cannot be modified.
public async Task CreateThreadAsync(string name, ThreadAutoArchiveDuration auto_archive_duration = ThreadAutoArchiveDuration.OneHour, int? rate_limit_per_user = null, string reason = null)
{
return Utilities.CheckThreadAutoArchiveDurationFeature(this.Channel.Guild, auto_archive_duration)
? await this.Discord.ApiClient.CreateThreadWithMessageAsync(this.ChannelId, this.Id, name, auto_archive_duration, rate_limit_per_user, reason)
: throw new NotSupportedException($"Cannot modify ThreadAutoArchiveDuration. Guild needs boost tier {(auto_archive_duration == ThreadAutoArchiveDuration.ThreeDays ? "one" : "two")}.");
}
///
/// Pins the message in its channel.
///
///
/// Thrown when the client does not have the permission.
/// Thrown when the member does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public Task PinAsync()
=> this.Discord.ApiClient.PinMessageAsync(this.ChannelId, this.Id);
///
/// Unpins the message in its channel.
///
///
/// Thrown when the client does not have the permission.
/// Thrown when the member does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public Task UnpinAsync()
=> this.Discord.ApiClient.UnpinMessageAsync(this.ChannelId, this.Id);
///
/// Responds to the message. This produces a reply.
///
/// Message content to respond with.
/// The sent message.
/// Thrown when the client does not have the permission.
/// Thrown when the member does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public Task RespondAsync(string content)
=> this.Discord.ApiClient.CreateMessageAsync(this.ChannelId, content, null, sticker: null, replyMessageId: this.Id, mentionReply: false, failOnInvalidReply: false);
///
/// Responds to the message. This produces a reply.
///
/// Embed to attach to the message.
/// The sent message.
/// Thrown when the client does not have the permission.
/// Thrown when the member does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public Task RespondAsync(DiscordEmbed embed)
=> this.Discord.ApiClient.CreateMessageAsync(this.ChannelId, null, embed != null ? new[] { embed } : null, sticker: null, replyMessageId: this.Id, mentionReply: false, failOnInvalidReply: false);
///
/// Responds to the message. This produces a reply.
///
/// Message content to respond with.
/// Embed to attach to the message.
/// The sent message.
/// Thrown when the client does not have the permission.
/// Thrown when the member does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public Task RespondAsync(string content, DiscordEmbed embed)
=> this.Discord.ApiClient.CreateMessageAsync(this.ChannelId, content, embed != null ? new[] { embed } : null, sticker: null, replyMessageId: this.Id, mentionReply: false, failOnInvalidReply: false);
///
/// Responds to the message. This produces a reply.
///
/// The Discord message builder.
/// The sent message.
/// Thrown when the client does not have the permission.
/// Thrown when the member does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public Task RespondAsync(DiscordMessageBuilder builder)
=> this.Discord.ApiClient.CreateMessageAsync(this.ChannelId, builder.WithReply(this.Id, mention: false, failOnInvalidReply: false));
///
/// Responds to the message. This produces a reply.
///
/// The Discord message builder.
/// The sent message.
/// Thrown when the client does not have the permission.
/// Thrown when the member does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public Task RespondAsync(Action action)
{
var builder = new DiscordMessageBuilder();
action(builder);
return this.Discord.ApiClient.CreateMessageAsync(this.ChannelId, builder.WithReply(this.Id, mention: false, failOnInvalidReply: false));
}
///
/// Creates a reaction to this message.
///
/// The emoji you want to react with, either an emoji or name:id
///
/// Thrown when the client does not have the permission.
/// Thrown when the emoji does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public Task CreateReactionAsync(DiscordEmoji emoji)
=> this.Discord.ApiClient.CreateReactionAsync(this.ChannelId, this.Id, emoji.ToReactionString());
///
/// Deletes your own reaction
///
/// Emoji for the reaction you want to remove, either an emoji or name:id
///
/// Thrown when the emoji does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public Task DeleteOwnReactionAsync(DiscordEmoji emoji)
=> this.Discord.ApiClient.DeleteOwnReactionAsync(this.ChannelId, this.Id, emoji.ToReactionString());
///
/// Deletes another user's reaction.
///
/// Emoji for the reaction you want to remove, either an emoji or name:id.
/// Member you want to remove the reaction for
/// Reason for audit logs.
///
/// Thrown when the client does not have the permission.
/// Thrown when the emoji does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public Task DeleteReactionAsync(DiscordEmoji emoji, DiscordUser user, string reason = null)
=> this.Discord.ApiClient.DeleteUserReactionAsync(this.ChannelId, this.Id, user.Id, emoji.ToReactionString(), reason);
///
/// Gets users that reacted with this emoji.
///
/// Emoji to react with.
/// Limit of users to fetch.
/// Fetch users after this user's id.
///
/// Thrown when the emoji does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public Task> GetReactionsAsync(DiscordEmoji emoji, int limit = 25, ulong? after = null)
=> this.GetReactionsInternalAsync(emoji, limit, after);
///
/// Deletes all reactions for this message.
///
/// Reason for audit logs.
///
/// Thrown when the client does not have the permission.
/// Thrown when the emoji does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public Task DeleteAllReactionsAsync(string reason = null)
=> this.Discord.ApiClient.DeleteAllReactionsAsync(this.ChannelId, this.Id, reason);
///
/// Deletes all reactions of a specific reaction for this message.
///
/// The emoji to clear, either an emoji or name:id.
///
/// Thrown when the client does not have the permission.
/// Thrown when the emoji does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public Task DeleteReactionsEmojiAsync(DiscordEmoji emoji)
=> this.Discord.ApiClient.DeleteReactionsEmojiAsync(this.ChannelId, this.Id, emoji.ToReactionString());
///
/// Gets the reactions.
///
/// The emoji to search for.
/// The limit of results.
/// Get the reasctions after snowflake.
private async Task> GetReactionsInternalAsync(DiscordEmoji emoji, int limit = 25, ulong? after = null)
{
if (limit < 0)
throw new ArgumentException("Cannot get a negative number of reactions' users.");
if (limit == 0)
return Array.Empty();
var users = new List(limit);
var remaining = limit;
var last = after;
int lastCount;
do
{
var fetchSize = remaining > 100 ? 100 : remaining;
var fetch = await this.Discord.ApiClient.GetReactionsAsync(this.Channel.Id, this.Id, emoji.ToReactionString(), last, fetchSize).ConfigureAwait(false);
lastCount = fetch.Count;
remaining -= lastCount;
users.AddRange(fetch);
last = fetch.LastOrDefault()?.Id;
} while (remaining > 0 && lastCount > 0);
return new ReadOnlyCollection(users);
}
///
/// Returns a string representation of this message.
///
/// String representation of this message.
public override string ToString() => $"Message {this.Id}; Attachment count: {this._attachments.Count}; Embed count: {this._embeds.Count}; Contents: {this.Content}";
///
/// Checks whether this is equal to another object.
///
/// Object to compare to.
/// Whether the object is equal to this .
public override bool Equals(object obj) => this.Equals(obj as DiscordMessage);
///
/// Checks whether this is equal to another .
///
/// to compare to.
/// Whether the is equal to this .
public bool Equals(DiscordMessage e) => e is not null && (ReferenceEquals(this, e) || (this.Id == e.Id && this.ChannelId == e.ChannelId));
///
/// Gets the hash code for this .
///
/// The hash code for this .
public override int GetHashCode()
{
var hash = 13;
hash = (hash * 7) + this.Id.GetHashCode();
hash = (hash * 7) + this.ChannelId.GetHashCode();
return hash;
}
///
/// Gets whether the two objects are equal.
///
/// First message to compare.
/// Second message to compare.
/// Whether the two messages are equal.
public static bool operator ==(DiscordMessage e1, DiscordMessage e2)
{
var o1 = e1 as object;
var o2 = e2 as object;
return (o1 != null || o2 == null) && (o1 == null || o2 != null) && ((o1 == null && o2 == null) || (e1.Id == e2.Id && e1.ChannelId == e2.ChannelId));
}
///
/// Gets whether the two objects are not equal.
///
/// First message to compare.
/// Second message to compare.
/// Whether the two messages are not equal.
public static bool operator !=(DiscordMessage e1, DiscordMessage e2)
=> !(e1 == e2);
}
}
diff --git a/DisCatSharp/Entities/Message/DiscordMessageBuilder.cs b/DisCatSharp/Entities/Message/DiscordMessageBuilder.cs
index 1b382a1fe..99dd486e0 100644
--- a/DisCatSharp/Entities/Message/DiscordMessageBuilder.cs
+++ b/DisCatSharp/Entities/Message/DiscordMessageBuilder.cs
@@ -1,432 +1,445 @@
// This file is part of the DisCatSharp project.
//
// Copyright (c) 2021 AITSYS
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
namespace DisCatSharp.Entities
{
///
/// Constructs a Message to be sent.
///
public sealed class DiscordMessageBuilder
{
///
/// Gets or Sets the Message to be sent.
///
public string Content
{
get => this._content;
set
{
if (value != null && value.Length > 2000)
throw new ArgumentException("Content cannot exceed 2000 characters.", nameof(value));
this._content = value;
}
}
private string _content;
///
/// Gets or sets the embed for the builder. This will always set the builder to have one embed.
///
public DiscordEmbed Embed
{
get => this._embeds.Count > 0 ? this._embeds[0] : null;
set
{
this._embeds.Clear();
this._embeds.Add(value);
}
}
///
/// Gets the Sticker to be send.
///
public DiscordSticker Sticker { get; set; }
///
/// Gets the Embeds to be sent.
///
public IReadOnlyList Embeds => this._embeds;
private readonly List _embeds = new();
///
/// Gets or Sets if the message should be TTS.
///
public bool IsTTS { get; set; } = false;
///
/// Gets the Allowed Mentions for the message to be sent.
///
public List Mentions { get; private set; } = null;
///
/// Gets the Files to be sent in the Message.
///
public IReadOnlyCollection Files => this._files;
internal readonly List _files = new();
///
/// Gets the components that will be attached to the message.
///
public IReadOnlyList Components => this._components;
internal readonly List _components = new(5);
///
/// Gets the Attachments to be sent in the Message.
///
- internal List Attachments { get; private set; } = null;
+ public IReadOnlyList Attachments => this._attachments;
+ internal readonly List _attachments = new();
///
/// Gets the Reply Message ID.
///
public ulong? ReplyId { get; private set; } = null;
///
/// Gets if the Reply should mention the user.
///
public bool MentionOnReply { get; private set; } = false;
///
/// Gets if the embeds should be suppressed.
///
public bool Suppressed { get; private set; } = false;
///
/// Gets if the Reply will error if the Reply Message Id does not reference a valid message.
/// If set to false, invalid replies are send as a regular message.
/// Defaults to false.
///
public bool FailOnInvalidReply { get; set; }
///
/// Sets the Content of the Message.
///
/// The content to be set.
/// The current builder to be chained.
public DiscordMessageBuilder WithContent(string content)
{
this.Content = content;
return this;
}
///
/// Adds a sticker to the message. Sticker must be from current guild.
///
/// The sticker to add.
/// The current builder to be chained.
public DiscordMessageBuilder WithSticker(DiscordSticker sticker)
{
this.Sticker = sticker;
return this;
}
///
/// Adds a row of components to a message, up to 5 components per row, and up to 5 rows per message.
///
/// The components to add to the message.
/// The current builder to be chained.
/// No components were passed.
public DiscordMessageBuilder AddComponents(params DiscordComponent[] components)
=> this.AddComponents((IEnumerable)components);
///
/// Appends several rows of components to the message
///
/// The rows of components to add, holding up to five each.
///
public DiscordMessageBuilder AddComponents(IEnumerable components)
{
var ara = components.ToArray();
if (ara.Length + this._components.Count > 5)
throw new ArgumentException("ActionRow count exceeds maximum of five.");
foreach (var ar in ara)
this._components.Add(ar);
return this;
}
///
/// Adds a row of components to a message, up to 5 components per row, and up to 5 rows per message.
///
/// The components to add to the message.
/// The current builder to be chained.
/// No components were passed.
public DiscordMessageBuilder AddComponents(IEnumerable components)
{
var cmpArr = components.ToArray();
var count = cmpArr.Length;
if (!cmpArr.Any())
throw new ArgumentOutOfRangeException(nameof(components), "You must provide at least one component");
if (count > 5)
throw new ArgumentException("Cannot add more than 5 components per action row!");
var comp = new DiscordActionRowComponent(cmpArr);
this._components.Add(comp);
return this;
}
///
/// Sets if the message should be TTS.
///
/// If TTS should be set.
/// The current builder to be chained.
public DiscordMessageBuilder HasTTS(bool isTTS)
{
this.IsTTS = isTTS;
return this;
}
///
/// Sets the embed for the current builder.
///
/// The embed that should be set.
/// The current builder to be chained.
public DiscordMessageBuilder WithEmbed(DiscordEmbed embed)
{
if (embed == null)
return this;
this.Embed = embed;
return this;
}
///
/// Appends an embed to the current builder.
///
/// The embed that should be appended.
/// The current builder to be chained.
public DiscordMessageBuilder AddEmbed(DiscordEmbed embed)
{
if (embed == null)
return this; //Providing null embeds will produce a 400 response from Discord.//
this._embeds.Add(embed);
return this;
}
///
/// Appends several embeds to the current builder.
///
/// The embeds that should be appended.
/// The current builder to be chained.
public DiscordMessageBuilder AddEmbeds(IEnumerable embeds)
{
this._embeds.AddRange(embeds);
return this;
}
///
/// Sets if the message has allowed mentions.
///
/// The allowed Mention that should be sent.
/// The current builder to be chained.
public DiscordMessageBuilder WithAllowedMention(IMention allowedMention)
{
if (this.Mentions != null)
this.Mentions.Add(allowedMention);
else
this.Mentions = new List { allowedMention };
return this;
}
///
/// Sets if the message has allowed mentions.
///
/// The allowed Mentions that should be sent.
/// The current builder to be chained.
public DiscordMessageBuilder WithAllowedMentions(IEnumerable allowedMentions)
{
if (this.Mentions != null)
this.Mentions.AddRange(allowedMentions);
else
this.Mentions = allowedMentions.ToList();
return this;
}
///
/// Sets if the message has files to be sent.
///
/// The fileName that the file should be sent as.
/// The Stream to the file.
/// Tells the API Client to reset the stream position to what it was after the file is sent.
/// Description of the file.
/// The current builder to be chained.
public DiscordMessageBuilder WithFile(string fileName, Stream stream, bool resetStreamPosition = false, string description = null)
{
if (this.Files.Count > 10)
throw new ArgumentException("Cannot send more than 10 files with a single message.");
if (this._files.Any(x => x.FileName == fileName))
throw new ArgumentException("A File with that filename already exists");
if (resetStreamPosition)
this._files.Add(new DiscordMessageFile(fileName, stream, stream.Position, description: description));
else
this._files.Add(new DiscordMessageFile(fileName, stream, null, description: description));
return this;
}
///
/// Sets if the message has files to be sent.
///
/// The Stream to the file.
/// Tells the API Client to reset the stream position to what it was after the file is sent.
/// Description of the file.
/// The current builder to be chained.
public DiscordMessageBuilder WithFile(FileStream stream, bool resetStreamPosition = false, string description = null)
{
if (this.Files.Count > 10)
throw new ArgumentException("Cannot send more than 10 files with a single message.");
if (this._files.Any(x => x.FileName == stream.Name))
throw new ArgumentException("A File with that filename already exists");
if (resetStreamPosition)
this._files.Add(new DiscordMessageFile(stream.Name, stream, stream.Position, description: description));
else
this._files.Add(new DiscordMessageFile(stream.Name, stream, null, description: description));
return this;
}
///
/// Sets if the message has files to be sent.
///
/// The Files that should be sent.
/// Tells the API Client to reset the stream position to what it was after the file is sent.
/// The current builder to be chained.
public DiscordMessageBuilder WithFiles(Dictionary files, bool resetStreamPosition = false)
{
if (this.Files.Count + files.Count > 10)
throw new ArgumentException("Cannot send more than 10 files with a single message.");
foreach (var file in files)
{
if (this._files.Any(x => x.FileName == file.Key))
throw new ArgumentException("A File with that filename already exists");
if (resetStreamPosition)
this._files.Add(new DiscordMessageFile(file.Key, file.Value, file.Value.Position));
else
this._files.Add(new DiscordMessageFile(file.Key, file.Value, null));
}
return this;
}
+ ///
+ /// Keeps the given attachments on edit.
+ /// You can modify the attachments descriptions / names before inserting it here.
+ ///
+ /// Attachments to keep (on edit).
+ ///
+ public DiscordMessageBuilder KeepAttachments(IEnumerable attachments)
+ {
+ this._attachments.AddRange(attachments);
+ return this;
+ }
+
///
/// Sets if the message is a reply
///
/// The ID of the message to reply to.
/// If we should mention the user in the reply.
/// Whether sending a reply that references an invalid message should be
/// The current builder to be chained.
public DiscordMessageBuilder WithReply(ulong messageId, bool mention = false, bool failOnInvalidReply = false)
{
this.ReplyId = messageId;
this.MentionOnReply = mention;
this.FailOnInvalidReply = failOnInvalidReply;
if (mention)
{
this.Mentions ??= new List();
this.Mentions.Add(new RepliedUserMention());
}
return this;
}
///
/// Sends the Message to a specific channel
///
/// The channel the message should be sent to.
/// The current builder to be chained.
public Task SendAsync(DiscordChannel channel) => channel.SendMessageAsync(this);
///
/// Sends the modified message.
/// Note: Message replies cannot be modified. To clear the reply, simply pass to .
///
/// The original Message to modify.
/// The current builder to be chained.
public Task ModifyAsync(DiscordMessage msg) => msg.ModifyAsync(this);
///
/// Clears all message components on this builder.
///
public void ClearComponents()
=> this._components.Clear();
///
/// Allows for clearing the Message Builder so that it can be used again to send a new message.
///
public void Clear()
{
this.Content = "";
this._embeds.Clear();
this.IsTTS = false;
this.Mentions = null;
this._files.Clear();
this.ReplyId = null;
this.MentionOnReply = false;
this._components.Clear();
this.Suppressed = false;
this.Sticker = null;
- this.Attachments.Clear();
+ this._attachments.Clear();
}
///
/// Does the validation before we send a the Create/Modify request.
///
/// Tells the method to perform the Modify Validation or Create Validation.
internal void Validate(bool isModify = false)
{
if (this._embeds.Count > 10)
throw new ArgumentException("A message can only have up to 10 embeds.");
if (!isModify)
{
if (this.Files?.Count == 0 && string.IsNullOrEmpty(this.Content) && (!this.Embeds?.Any() ?? true) && this.Sticker is null)
throw new ArgumentException("You must specify content, an embed, a sticker or at least one file.");
if (this.Components.Count > 5)
throw new InvalidOperationException("You can only have 5 action rows per message.");
if (this.Components.Any(c => c.Components.Count > 5))
throw new InvalidOperationException("Action rows can only have 5 components");
}
}
}
}
diff --git a/DisCatSharp/Net/Rest/DiscordApiClient.cs b/DisCatSharp/Net/Rest/DiscordApiClient.cs
index c69155a38..85805304f 100644
--- a/DisCatSharp/Net/Rest/DiscordApiClient.cs
+++ b/DisCatSharp/Net/Rest/DiscordApiClient.cs
@@ -1,5352 +1,5379 @@
// This file is part of the DisCatSharp project.
//
// Copyright (c) 2021 AITSYS
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Globalization;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using DisCatSharp.Entities;
using DisCatSharp.Exceptions;
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.
/// If true, post.
/// A string.
private static string BuildQueryString(IDictionary values, bool post = false)
{
if (values == null || values.Count == 0)
return string.Empty;
var vals_collection = values.Select(xkvp =>
$"{WebUtility.UrlEncode(xkvp.Key)}={WebUtility.UrlEncode(xkvp.Value)}");
var vals = string.Join("&", vals_collection);
return !post ? $"?{vals}" : vals;
}
///
/// Prepares the message.
///
/// The msg_raw.
/// A DiscordMessage.
private DiscordMessage PrepareMessage(JToken msg_raw)
{
var author = msg_raw["author"].ToObject();
var ret = msg_raw.ToDiscordObject();
ret.Discord = this.Discord;
this.PopulateMessage(author, ret);
var referencedMsg = msg_raw["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 ret.
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, _guild_id = guild.Id };
ret.Author = mbr;
}
else
{
ret.Author = usr;
}
}
ret.PopulateMentions();
if (ret._reactions == null)
ret._reactions = new List();
foreach (var xr in ret._reactions)
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.
/// A Task.
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.
/// A Task.
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.
/// A Task.
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.
/// A Task.
internal async Task> SearchMembersAsync(ulong guild_id, 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 }, 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, _guild_id = guild_id });
}
return mbrs;
}
///
/// Gets the guild ban async.
///
/// The guild_id.
/// The user_id.
/// A Task.
internal async Task GetGuildBanAsync(ulong guild_id, ulong user_id)
{
var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.BANS}/:user_id";
var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new {guild_id, user_id}, 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 region_id, Optional iconb64, VerificationLevel? verification_level,
DefaultMessageNotifications? default_message_notifications, SystemChannelFlags? system_channel_flags)
{
var pld = new RestGuildCreatePayload
{
Name = name,
RegionId = region_id,
DefaultMessageNotifications = default_message_notifications,
VerificationLevel = verification_level,
IconBase64 = iconb64,
SystemChannelFlags = system_channel_flags
};
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 raw_members = (JArray)json["members"];
var guild = json.ToDiscordObject();
if (this.Discord is DiscordClient dc)
await dc.OnGuildCreateEventAsync(guild, raw_members, null).ConfigureAwait(false);
return guild;
}
///
/// Creates the guild from template async.
///
/// The template_code.
/// The name.
/// The iconb64.
internal async Task CreateGuildFromTemplateAsync(string template_code, 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 }, 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 raw_members = (JArray)json["members"];
var guild = json.ToDiscordObject();
if (this.Discord is DiscordClient dc)
await dc.OnGuildCreateEventAsync(guild, raw_members, null).ConfigureAwait(false);
return guild;
}
///
/// Deletes the guild async.
///
/// The guild_id.
internal async Task DeleteGuildAsync(ulong guild_id)
{
var route = $"{Endpoints.GUILDS}/:guild_id";
var bucket = this.Rest.GetBucket(RestRequestMethod.DELETE, route, new { guild_id }, 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._guilds[guild_id];
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 discorverySplashb64, 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 = discorverySplashb64,
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._roles.Values)
r._guild_id = 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 = description ?? Optional.FromNoValue(),
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._roles.Values)
r._guild_id = guild.Id;
if (this.Discord is DiscordClient dc)
await dc.OnGuildUpdateEventAsync(guild, rawMembers).ConfigureAwait(false);
return guild;
}
///
/// Gets the guild bans async.
///
/// The guild_id.
/// A Task.
internal async Task> GetGuildBansAsync(ulong guild_id)
{
var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.BANS}";
var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new { guild_id }, 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 bans_raw = 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(bans_raw));
return bans;
}
///
/// Creates the guild ban async.
///
/// The guild_id.
/// The user_id.
/// The delete_message_days.
/// The reason.
/// A Task.
internal Task CreateGuildBanAsync(ulong guild_id, ulong user_id, int delete_message_days, string reason)
{
if (delete_message_days < 0 || delete_message_days > 7)
throw new ArgumentException("Delete message days must be a number between 0 and 7.", nameof(delete_message_days));
var urlparams = new Dictionary
{
["delete_message_days"] = delete_message_days.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, user_id }, 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.
/// A Task.
internal Task RemoveGuildBanAsync(ulong guild_id, ulong user_id, 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, user_id }, 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.
/// A Task.
internal Task LeaveGuildAsync(ulong guild_id)
{
var route = $"{Endpoints.USERS}{Endpoints.ME}{Endpoints.GUILDS}/:guild_id";
var bucket = this.Rest.GetBucket(RestRequestMethod.DELETE, route, new { guild_id }, 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.
/// A Task.
internal async Task AddGuildMemberAsync(ulong guild_id, ulong user_id, string access_token, string nick, IEnumerable roles, bool muted, bool deafened)
{
var pld = new RestGuildMemberAddPayload
{
AccessToken = access_token,
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, user_id }, 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, _guild_id = guild_id };
}
///
/// Lists the guild members async.
///
/// The guild_id.
/// The limit.
/// The after.
/// A Task.
internal async Task> ListGuildMembersAsync(ulong guild_id, 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 }, 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 members_raw = JsonConvert.DeserializeObject>(res.Response);
return new ReadOnlyCollection(members_raw);
}
///
/// Adds the guild member role async.
///
/// The guild_id.
/// The user_id.
/// The role_id.
/// The reason.
/// A Task.
internal Task AddGuildMemberRoleAsync(ulong guild_id, ulong user_id, ulong role_id, 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, user_id, role_id }, 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.
/// A Task.
internal Task RemoveGuildMemberRoleAsync(ulong guild_id, ulong user_id, ulong role_id, 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, user_id, role_id }, 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.
/// A Task.
internal Task ModifyGuildChannelPositionAsync(ulong guild_id, 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 }, 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.
/// A Task.
internal Task ModifyGuildChannelParentAsync(ulong guild_id, 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 }, 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.
/// A Task.
internal Task DetachGuildChannelParentAsync(ulong guild_id, 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 }, 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.
/// A Task.
internal Task ModifyGuildRolePositionAsync(ulong guild_id, 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 }, 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.
/// A Task.
internal async Task GetAuditLogsAsync(ulong guild_id, int limit, ulong? after, ulong? before, ulong? responsible, int? action_type)
{
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 (action_type != null)
urlparams["action_type"] = action_type?.ToString(CultureInfo.InvariantCulture);
var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.AUDIT_LOGS}";
var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new { guild_id }, 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 audit_log_data_raw = JsonConvert.DeserializeObject(res.Response);
return audit_log_data_raw;
}
///
/// Gets the guild vanity url async.
///
/// The guild_id.
/// A Task.
internal async Task GetGuildVanityUrlAsync(ulong guild_id)
{
var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.VANITY_URL}";
var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new { guild_id }, 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.
/// A Task.
internal async Task GetGuildWidgetAsync(ulong guild_id)
{
var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.WIDGET_JSON}";
var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new { guild_id }, 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[guild_id];
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.
/// A Task.
internal async Task GetGuildWidgetSettingsAsync(ulong guild_id)
{
var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.WIDGET}";
var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new { guild_id }, 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[guild_id];
return ret;
}
///
/// Modifies the guild widget settings async.
///
/// The guild_id.
/// If true, is enabled.
/// The channel id.
/// The reason.
/// A Task.
internal async Task ModifyGuildWidgetSettingsAsync(ulong guild_id, 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 }, 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[guild_id];
return ret;
}
///
/// Gets the guild templates async.
///
/// The guild_id.
/// A Task.
internal async Task> GetGuildTemplatesAsync(ulong guild_id)
{
var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.TEMPLATES}";
var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new { guild_id }, 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 templates_raw = JsonConvert.DeserializeObject>(res.Response);
return new ReadOnlyCollection(new List(templates_raw));
}
///
/// Creates the guild template async.
///
/// The guild_id.
/// The name.
/// The description.
/// A Task.
internal async Task CreateGuildTemplateAsync(ulong guild_id, 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 }, 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.
/// A Task.
internal async Task SyncGuildTemplateAsync(ulong guild_id, string template_code)
{
var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.TEMPLATES}/:template_code";
var bucket = this.Rest.GetBucket(RestRequestMethod.PUT, route, new { guild_id, template_code }, 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 template_raw = JsonConvert.DeserializeObject(res.Response);
return template_raw;
}
///
/// Modifies the guild template async.
///
/// The guild_id.
/// The template_code.
/// The name.
/// The description.
/// A Task.
internal async Task ModifyGuildTemplateAsync(ulong guild_id, string template_code, 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, template_code }, 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 template_raw = JsonConvert.DeserializeObject(res.Response);
return template_raw;
}
///
/// Deletes the guild template async.
///
/// The guild_id.
/// The template_code.
/// A Task.
internal async Task DeleteGuildTemplateAsync(ulong guild_id, string template_code)
{
var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.TEMPLATES}/:template_code";
var bucket = this.Rest.GetBucket(RestRequestMethod.DELETE, route, new { guild_id, template_code }, 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 template_raw = JsonConvert.DeserializeObject(res.Response);
return template_raw;
}
///
/// Gets the guild membership screening form async.
///
/// The guild_id.
/// A Task.
internal async Task GetGuildMembershipScreeningFormAsync(ulong guild_id)
{
var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.MEMBER_VERIFICATION}";
var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new { guild_id }, 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 screening_raw = JsonConvert.DeserializeObject(res.Response);
return screening_raw;
}
///
/// Modifies the guild membership screening form async.
///
/// The guild_id.
/// The enabled.
/// The fields.
/// The description.
/// A Task.
internal async Task ModifyGuildMembershipScreeningFormAsync(ulong guild_id, 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 }, 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 screening_raw = JsonConvert.DeserializeObject(res.Response);
return screening_raw;
}
///
/// Gets the guild welcome screen async.
///
/// The guild_id.
/// A Task.
internal async Task GetGuildWelcomeScreenAsync(ulong guild_id)
{
var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.WELCOME_SCREEN}";
var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new { guild_id }, 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.
/// A Task.
internal async Task ModifyGuildWelcomeScreenAsync(ulong guild_id, 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 }, 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.
/// A Task.
internal async Task UpdateCurrentUserVoiceStateAsync(ulong guild_id, 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 }, 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.
/// A Task.
internal async Task UpdateUserVoiceStateAsync(ulong guild_id, ulong user_id, 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, user_id }, 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 guild_id, ulong? channel_id, DiscordScheduledEventEntityMetadata metadata, string name, DateTimeOffset scheduled_start_time, DateTimeOffset? scheduled_end_time, string description, ScheduledEventEntityType type, string reason = null)
{
var pld = new RestGuildScheduledEventCreatePayload
{
ChannelId = channel_id,
EntityMetadata = metadata,
Name = name,
ScheduledStartTime = scheduled_start_time,
ScheduledEndTime = scheduled_end_time,
Description = description,
EntityType = type
};
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 }, 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 scheduled_event = JsonConvert.DeserializeObject(res.Response);
var guild = this.Discord.Guilds[guild_id];
scheduled_event.Discord = this.Discord;
if (scheduled_event.Creator != null)
scheduled_event.Creator.Discord = this.Discord;
if (this.Discord is DiscordClient dc)
await dc.OnGuildScheduledEventCreateEventAsync(scheduled_event, guild);
return scheduled_event;
}
///
/// Modifies a scheduled event.
///
internal async Task ModifyGuildScheduledEventAsync(ulong guild_id, ulong scheduled_event_id, Optional channel_id, Optional metadata, Optional name, Optional scheduled_start_time, Optional scheduled_end_time, Optional description, Optional type, Optional status, string reason = null)
{
var pld = new RestGuildSheduledEventModifyPayload
{
ChannelId = channel_id,
EntityMetadata = metadata,
Name = name,
ScheduledStartTime = scheduled_start_time,
ScheduledEndTime = scheduled_end_time,
Description = description,
EntityType = type,
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, scheduled_event_id }, 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 scheduled_event = JsonConvert.DeserializeObject(res.Response);
var guild = this.Discord.Guilds[guild_id];
scheduled_event.Discord = this.Discord;
if (scheduled_event.Creator != null)
{
scheduled_event.Creator.Discord = this.Discord;
this.Discord.UserCache.AddOrUpdate(scheduled_event.Creator.Id, scheduled_event.Creator, (id, old) =>
{
old.Username = scheduled_event.Creator.Username;
old.Discriminator = scheduled_event.Creator.Discriminator;
old.AvatarHash = scheduled_event.Creator.AvatarHash;
old.Flags = scheduled_event.Creator.Flags;
return old;
});
}
if (this.Discord is DiscordClient dc)
await dc.OnGuildScheduledEventUpdateEventAsync(scheduled_event, guild);
return scheduled_event;
}
///
/// Modifies a scheduled event.
///
internal async Task ModifyGuildScheduledEventStatusAsync(ulong guild_id, ulong scheduled_event_id, ScheduledEventStatus status, string reason = null)
{
var pld = new RestGuildSheduledEventModifyPayload
{
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, scheduled_event_id }, 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 scheduled_event = JsonConvert.DeserializeObject(res.Response);
var guild = this.Discord.Guilds[guild_id];
scheduled_event.Discord = this.Discord;
if (scheduled_event.Creator != null)
{
scheduled_event.Creator.Discord = this.Discord;
this.Discord.UserCache.AddOrUpdate(scheduled_event.Creator.Id, scheduled_event.Creator, (id, old) =>
{
old.Username = scheduled_event.Creator.Username;
old.Discriminator = scheduled_event.Creator.Discriminator;
old.AvatarHash = scheduled_event.Creator.AvatarHash;
old.Flags = scheduled_event.Creator.Flags;
return old;
});
}
if (this.Discord is DiscordClient dc)
await dc.OnGuildScheduledEventUpdateEventAsync(scheduled_event, guild);
return scheduled_event;
}
///
/// Gets a scheduled event.
///
/// The guild_id.
/// The event id.
/// Whether to include user count.
internal async Task GetGuildScheduledEventAsync(ulong guild_id, ulong scheduled_event_id, bool? with_user_count)
{
var urlparams = new Dictionary();
if (with_user_count.HasValue)
urlparams["with_user_count"] = with_user_count?.ToString();
var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.SCHEDULED_EVENTS}/:scheduled_event_id";
var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new { guild_id, scheduled_event_id }, 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);
var scheduled_event = JsonConvert.DeserializeObject(res.Response);
var guild = this.Discord.Guilds[guild_id];
scheduled_event.Discord = this.Discord;
if (scheduled_event.Creator != null)
{
scheduled_event.Creator.Discord = this.Discord;
this.Discord.UserCache.AddOrUpdate(scheduled_event.Creator.Id, scheduled_event.Creator, (id, old) =>
{
old.Username = scheduled_event.Creator.Username;
old.Discriminator = scheduled_event.Creator.Discriminator;
old.AvatarHash = scheduled_event.Creator.AvatarHash;
old.Flags = scheduled_event.Creator.Flags;
return old;
});
}
return scheduled_event;
}
///
/// Gets the guilds scheduled events.
///
/// The guild_id.
/// Whether to include the count of users subscribed to the scheduled event.
internal async Task> ListGuildScheduledEventsAsync(ulong guild_id, bool? with_user_count)
{
var urlparams = new Dictionary();
if (with_user_count.HasValue)
urlparams["with_user_count"] = with_user_count?.ToString();
var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.SCHEDULED_EVENTS}";
var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new { guild_id }, 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);
var events = new Dictionary();
var events_raw = JsonConvert.DeserializeObject>(res.Response);
var guild = this.Discord.Guilds[guild_id];
foreach (var ev in events_raw)
{
ev.Discord = this.Discord;
if(ev.Creator != null)
{
ev.Creator.Discord = this.Discord;
this.Discord.UserCache.AddOrUpdate(ev.Creator.Id, ev.Creator, (id, old) =>
{
old.Username = ev.Creator.Username;
old.Discriminator = ev.Creator.Discriminator;
old.AvatarHash = ev.Creator.AvatarHash;
old.Flags = ev.Creator.Flags;
return old;
});
}
events.Add(ev.Id, ev);
}
return new ReadOnlyDictionary(new Dictionary(events));
}
///
/// Deletes a guild sheduled event.
///
/// The guild_id.
/// The sheduled event id.
/// The reason.
internal Task DeleteGuildScheduledEventAsync(ulong guild_id, ulong scheduled_event_id, string reason)
{
var headers = Utilities.GetBaseHeaders();
if (!string.IsNullOrWhiteSpace(reason))
headers.Add(REASON_HEADER_NAME, reason);
var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.SCHEDULED_EVENTS}/:scheduled_event_id";
var bucket = this.Rest.GetBucket(RestRequestMethod.DELETE, route, new { guild_id, scheduled_event_id }, out var path);
var url = Utilities.GetApiUriFor(path, this.Discord.Configuration);
return this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.DELETE, route, headers);
}
///
/// Gets the users who RSVP'd to a sheduled event.
/// Optional with member objects.
/// This endpoint is paginated.
///
/// The guild_id.
/// The sheduled event id.
/// The limit how many users to receive from the event.
/// Get results before the given id.
/// Get results after the given id.
/// Wether to include guild member data. attaches guild_member property to the user object.
internal async Task> GetGuildScheduledEventRSPVUsersAsync(ulong guild_id, ulong scheduled_event_id, int? limit, ulong? before, ulong? after, bool? with_member)
{
var urlparams = new Dictionary();
if (limit != null && limit > 0)
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);
if (with_member != null)
urlparams["with_member"] = with_member.Value.ToString(CultureInfo.InvariantCulture);
var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.SCHEDULED_EVENTS}/:scheduled_event_id{Endpoints.USERS}";
var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new { guild_id, scheduled_event_id }, 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 rspv_users = JsonConvert.DeserializeObject>(res.Response);
Dictionary rspv = new();
foreach (var rspv_user in rspv_users)
{
rspv_user.Discord = this.Discord;
rspv_user.GuildId = guild_id;
rspv_user.User.Discord = this.Discord;
rspv_user.User = this.Discord.UserCache.AddOrUpdate(rspv_user.User.Id, rspv_user.User, (id, old) =>
{
old.Username = rspv_user.User.Username;
old.Discriminator = rspv_user.User.Discriminator;
old.AvatarHash = rspv_user.User.AvatarHash;
old.BannerHash = rspv_user.User.BannerHash;
old._bannerColor = rspv_user.User._bannerColor;
return old;
});
/*if (with_member.HasValue && with_member.Value && rspv_user.Member != null)
{
rspv_user.Member.Discord = this.Discord;
}*/
rspv.Add(rspv_user.User.Id, rspv_user);
}
return new ReadOnlyDictionary(new Dictionary(rspv));
}
#endregion
#region Channel
///