diff --git a/DisCatSharp.CommandsNext/Attributes/RequireDiscordCertifiedModeratorAttribute.cs b/DisCatSharp.CommandsNext/Attributes/RequireCertifiedModeratorAttribute.cs
similarity index 91%
rename from DisCatSharp.CommandsNext/Attributes/RequireDiscordCertifiedModeratorAttribute.cs
rename to DisCatSharp.CommandsNext/Attributes/RequireCertifiedModeratorAttribute.cs
index 6e69d25dc..59a306f45 100644
--- a/DisCatSharp.CommandsNext/Attributes/RequireDiscordCertifiedModeratorAttribute.cs
+++ b/DisCatSharp.CommandsNext/Attributes/RequireCertifiedModeratorAttribute.cs
@@ -1,42 +1,42 @@
// 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.Linq;
using System.Threading.Tasks;
namespace DisCatSharp.CommandsNext.Attributes
{
///
/// Defines that usage of this command is restricted to discord certified moderators.
///
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
- public sealed class RequireDiscordCertifiedModeratorAttribute : CheckBaseAttribute
+ public sealed class RequireCertifiedModeratorAttribute : CheckBaseAttribute
{
///
/// Executes the a check.
///
/// The command context.
/// If true, help - returns true.
- public override Task ExecuteCheckAsync(CommandContext ctx, bool help) => ctx.User.Flags.HasValue ? Task.FromResult(ctx.User.Flags.Value.HasFlag(UserFlags.DiscordCertifiedModerator)) : Task.FromResult(false);
+ public override Task ExecuteCheckAsync(CommandContext ctx, bool help) => ctx.User.Flags.HasValue ? Task.FromResult(ctx.User.Flags.Value.HasFlag(UserFlags.CertifiedModerator)) : Task.FromResult(false);
}
}
diff --git a/DisCatSharp.CommandsNext/Attributes/RequireDiscordEmployeeAttribute.cs b/DisCatSharp.CommandsNext/Attributes/RequireStaffAttribute.cs
similarity index 91%
rename from DisCatSharp.CommandsNext/Attributes/RequireDiscordEmployeeAttribute.cs
rename to DisCatSharp.CommandsNext/Attributes/RequireStaffAttribute.cs
index 5cffdfccb..b7bdb4b35 100644
--- a/DisCatSharp.CommandsNext/Attributes/RequireDiscordEmployeeAttribute.cs
+++ b/DisCatSharp.CommandsNext/Attributes/RequireStaffAttribute.cs
@@ -1,42 +1,42 @@
// 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.Linq;
using System.Threading.Tasks;
namespace DisCatSharp.CommandsNext.Attributes
{
///
/// Defines that usage of this command is restricted to discord employees.
///
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
- public sealed class RequireDiscordEmployeeAttribute : CheckBaseAttribute
+ public sealed class RequireStaffAttribute : CheckBaseAttribute
{
///
/// Executes the a check.
///
/// The command context.
/// If true, help - returns true.
- public override Task ExecuteCheckAsync(CommandContext ctx, bool help) => ctx.User.Flags.HasValue ? Task.FromResult(ctx.User.Flags.Value.HasFlag(UserFlags.DiscordEmployee)) : Task.FromResult(false);
+ public override Task ExecuteCheckAsync(CommandContext ctx, bool help) => ctx.User.Flags.HasValue ? Task.FromResult(ctx.User.Flags.Value.HasFlag(UserFlags.Staff)) : Task.FromResult(false);
}
}
diff --git a/DisCatSharp/Entities/Channel/DiscordChannel.cs b/DisCatSharp/Entities/Channel/DiscordChannel.cs
index 4d13beace..721a041ac 100644
--- a/DisCatSharp/Entities/Channel/DiscordChannel.cs
+++ b/DisCatSharp/Entities/Channel/DiscordChannel.cs
@@ -1,1403 +1,1403 @@
// 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.IO;
using System.Linq;
using System.Threading.Tasks;
using DisCatSharp.Enums;
using DisCatSharp.Exceptions;
using DisCatSharp.Net;
using DisCatSharp.Net.Abstractions;
using DisCatSharp.Net.Models;
using Newtonsoft.Json;
namespace DisCatSharp.Entities
{
///
/// Represents a discord channel.
///
public class DiscordChannel : SnowflakeObject, IEquatable
{
///
/// Gets ID of the guild to which this channel belongs.
///
[JsonProperty("guild_id", NullValueHandling = NullValueHandling.Ignore)]
public ulong? GuildId { get; internal set; }
///
/// Gets ID of the category that contains this channel.
///
[JsonProperty("parent_id", NullValueHandling = NullValueHandling.Include)]
public ulong? ParentId { get; internal set; }
///
/// Gets the category that contains this channel.
///
[JsonIgnore]
public DiscordChannel Parent
=> this.ParentId.HasValue ? this.Guild.GetChannel(this.ParentId.Value) : null;
///
/// Gets the name of this channel.
///
[JsonProperty("name", NullValueHandling = NullValueHandling.Ignore)]
public string Name { get; internal set; }
///
/// Gets the type of this channel.
///
[JsonProperty("type", NullValueHandling = NullValueHandling.Ignore)]
public ChannelType Type { get; internal set; }
///
/// Gets this channels's banner hash, when applicable.
///
[JsonProperty("banner")]
public string BannerHash { get; internal set; }
///
/// Gets this channels's banner in url form.
///
[JsonIgnore]
public string BannerUrl
=> !string.IsNullOrWhiteSpace(this.BannerHash) ? $"{DiscordDomain.GetDomain(CoreDomain.DiscordCdn).Uri}{Endpoints.CHANNELS}/{this.Id.ToString(CultureInfo.InvariantCulture)}{Endpoints.BANNERS}/{this.BannerHash}.{(this.BannerHash.StartsWith("a_") ? "gif" : "png")}" : null;
///
/// Gets the position of this channel.
///
[JsonProperty("position", NullValueHandling = NullValueHandling.Ignore)]
public int Position { get; internal set; }
///
/// Gets the maximum available position to move the channel to.
/// This can contain outdated informations.
///
public int GetMaxPosition()
{
var channels = this.Guild.Channels.Values;
return this.ParentId != null
? this.Type == ChannelType.Text || this.Type == ChannelType.News
? channels.Where(xc => xc.ParentId == this.ParentId && (xc.Type == ChannelType.Text || xc.Type == ChannelType.News)).OrderBy(xc => xc.Position).ToArray().Last().Position
: this.Type == ChannelType.Voice || this.Type == ChannelType.Stage
? channels.Where(xc => xc.ParentId == this.ParentId && (xc.Type == ChannelType.Voice || xc.Type == ChannelType.Stage)).OrderBy(xc => xc.Position).ToArray().Last().Position
: channels.Where(xc => xc.ParentId == this.ParentId && xc.Type == this.Type).OrderBy(xc => xc.Position).ToArray().Last().Position
: channels.Where(xc => xc.ParentId == null && xc.Type == this.Type).OrderBy(xc => xc.Position).ToArray().Last().Position;
}
///
/// Gets the minimum available position to move the channel to.
///
public int GetMinPosition()
{
var channels = this.Guild.Channels.Values;
return this.ParentId != null
? this.Type == ChannelType.Text || this.Type == ChannelType.News
? channels.Where(xc => xc.ParentId == this.ParentId && (xc.Type == ChannelType.Text || xc.Type == ChannelType.News)).OrderBy(xc => xc.Position).ToArray().First().Position
: this.Type == ChannelType.Voice || this.Type == ChannelType.Stage
? channels.Where(xc => xc.ParentId == this.ParentId && (xc.Type == ChannelType.Voice || xc.Type == ChannelType.Stage)).OrderBy(xc => xc.Position).ToArray().First().Position
: channels.Where(xc => xc.ParentId == this.ParentId && xc.Type == this.Type).OrderBy(xc => xc.Position).ToArray().First().Position
: channels.Where(xc => xc.ParentId == null && xc.Type == this.Type).OrderBy(xc => xc.Position).ToArray().First().Position;
}
///
/// Gets whether this channel is a DM channel.
///
[JsonIgnore]
public bool IsPrivate
=> this.Type == ChannelType.Private || this.Type == ChannelType.Group;
///
/// Gets whether this channel is a channel category.
///
[JsonIgnore]
public bool IsCategory
=> this.Type == ChannelType.Category;
///
/// Gets whether this channel is a stage channel.
///
[JsonIgnore]
public bool IsStage
=> this.Type == ChannelType.Stage;
///
/// Gets the guild to which this channel belongs.
///
[JsonIgnore]
public DiscordGuild Guild
=> this.GuildId.HasValue && this.Discord.Guilds.TryGetValue(this.GuildId.Value, out var guild) ? guild : null;
///
/// Gets a collection of permission overwrites for this channel.
///
[JsonIgnore]
public IReadOnlyList PermissionOverwrites
=> this._permissionOverwritesLazy.Value;
[JsonProperty("permission_overwrites", NullValueHandling = NullValueHandling.Ignore)]
internal List _permissionOverwrites = new();
[JsonIgnore]
private readonly Lazy> _permissionOverwritesLazy;
///
/// Gets the channel's topic. This is applicable to text channels only.
///
[JsonProperty("topic", NullValueHandling = NullValueHandling.Ignore)]
public string Topic { get; internal set; }
///
/// Gets the ID of the last message sent in this channel. This is applicable to text channels only.
///
[JsonProperty("last_message_id", NullValueHandling = NullValueHandling.Ignore)]
public ulong? LastMessageId { get; internal set; }
///
/// Gets this channel's bitrate. This is applicable to voice channels only.
///
[JsonProperty("bitrate", NullValueHandling = NullValueHandling.Ignore)]
public int? Bitrate { get; internal set; }
///
/// Gets this channel's user limit. This is applicable to voice channels only.
///
[JsonProperty("user_limit", NullValueHandling = NullValueHandling.Ignore)]
public int? UserLimit { get; internal set; }
///
/// Gets the slow mode delay configured for this channel.
/// All bots, as well as users with or permissions in the channel are exempt from slow mode.
///
[JsonProperty("rate_limit_per_user")]
public int? PerUserRateLimit { get; internal set; }
///
/// Gets this channel's video quality mode. This is applicable to voice channels only.
///
[JsonProperty("video_quality_mode", NullValueHandling = NullValueHandling.Ignore)]
public VideoQualityMode? QualityMode { get; internal set; }
///
/// Gets when the last pinned message was pinned.
///
[JsonIgnore]
public DateTimeOffset? LastPinTimestamp
=> !string.IsNullOrWhiteSpace(this.LastPinTimestampRaw) && DateTimeOffset.TryParse(this.LastPinTimestampRaw, CultureInfo.InvariantCulture, DateTimeStyles.None, out var dto) ?
dto : null;
///
/// Gets when the last pinned message was pinned as raw string.
///
[JsonProperty("last_pin_timestamp", NullValueHandling = NullValueHandling.Ignore)]
internal string LastPinTimestampRaw { get; set; }
///
/// Gets this channel's default duration for newly created threads, in minutes, to automatically archive the thread after recent activity.
///
[JsonProperty("default_auto_archive_duration", NullValueHandling = NullValueHandling.Ignore)]
public ThreadAutoArchiveDuration? DefaultAutoArchiveDuration { get; internal set; }
///
/// Gets this channel's mention string.
///
[JsonIgnore]
public string Mention
=> Formatter.Mention(this);
///
/// Gets this channel's children. This applies only to channel categories.
///
[JsonIgnore]
public IReadOnlyList Children
{
get
{
return !this.IsCategory
? throw new ArgumentException("Only channel categories contain children.")
: this.Guild._channels.Values.Where(e => e.ParentId == this.Id).ToList();
}
}
///
/// Gets the list of members currently in the channel (if voice channel), or members who can see the channel (otherwise).
///
[JsonIgnore]
public virtual IReadOnlyList Users
{
get
{
return this.Guild == null
? throw new InvalidOperationException("Cannot query users outside of guild channels.")
: this.IsVoiceJoinable()
? this.Guild.Members.Values.Where(x => x.VoiceState?.ChannelId == this.Id).ToList()
: this.Guild.Members.Values.Where(x => (this.PermissionsFor(x) & Permissions.AccessChannels) == Permissions.AccessChannels).ToList();
}
}
///
/// Gets whether this channel is an NSFW channel.
///
[JsonProperty("nsfw")]
public bool IsNSFW { get; internal set; }
///
/// Gets this channel's region id (if voice channel).
///
[JsonProperty("rtc_region", NullValueHandling = NullValueHandling.Ignore)]
internal string RtcRegionId { get; set; }
///
/// Gets this channel's region override (if voice channel).
///
[JsonIgnore]
public DiscordVoiceRegion RtcRegion
=> this.RtcRegionId != null ? this.Discord.VoiceRegions[this.RtcRegionId] : null;
///
/// Only sent on the resolved channels of interaction responses for application commands. Gets the permissions of the user in this channel who invoked the command.
///
[JsonProperty("permissions")]
public Permissions? UserPermissions { get; internal set; }
///
/// Initializes a new instance of the class.
///
internal DiscordChannel()
{
this._permissionOverwritesLazy = new Lazy>(() => new ReadOnlyCollection(this._permissionOverwrites));
}
#region Methods
///
/// Sends a message to this channel.
///
/// Content of the message to send.
/// The sent message.
/// Thrown when the client does not have the permission if TTS is true and if TTS is true.
/// Thrown when the channel does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public Task SendMessageAsync(string content)
{
return !this.IsWriteable()
? throw new ArgumentException("Cannot send a text message to a non-text channel.")
: this.Discord.ApiClient.CreateMessageAsync(this.Id, content, null, sticker: null, replyMessageId: null, mentionReply: false, failOnInvalidReply: false);
}
///
/// Sends a message to this channel.
///
/// Embed to attach to the message.
/// The sent message.
/// Thrown when the client does not have the permission and if TTS is true.
/// Thrown when the channel does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public Task SendMessageAsync(DiscordEmbed embed)
{
return !this.IsWriteable()
? throw new ArgumentException("Cannot send a text message to a non-text channel.")
: this.Discord.ApiClient.CreateMessageAsync(this.Id, null, embed != null ? new[] { embed } : null, sticker: null, replyMessageId: null, mentionReply: false, failOnInvalidReply: false);
}
///
/// Sends a message to this channel.
///
/// Embed to attach to the message.
/// Content of the message to send.
/// The sent message.
/// Thrown when the client does not have the permission if TTS is true and if TTS is true.
/// Thrown when the channel does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public Task SendMessageAsync(string content, DiscordEmbed embed)
{
return !this.IsWriteable()
? throw new ArgumentException("Cannot send a text message to a non-text channel.")
: this.Discord.ApiClient.CreateMessageAsync(this.Id, content, embed != null ? new[] { embed } : null, sticker: null, replyMessageId: null, mentionReply: false, failOnInvalidReply: false);
}
///
/// Sends a message to this channel.
///
/// The builder with all the items to send.
/// The sent message.
/// Thrown when the client does not have the permission TTS is true and if TTS is true.
/// Thrown when the channel does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public Task SendMessageAsync(DiscordMessageBuilder builder)
=> this.Discord.ApiClient.CreateMessageAsync(this.Id, builder);
///
/// Sends a message to this channel.
///
/// The builder with all the items to send.
/// The sent message.
/// Thrown when the client does not have the permission TTS is true and if TTS is true.
/// Thrown when the channel does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public Task SendMessageAsync(Action action)
{
var builder = new DiscordMessageBuilder();
action(builder);
return !this.IsWriteable()
? throw new ArgumentException("Cannot send a text message to a non-text channel.")
: this.Discord.ApiClient.CreateMessageAsync(this.Id, builder);
}
///
/// Deletes a guild channel
///
/// Reason for audit logs.
/// Thrown when the client does not have the permission.
/// Thrown when the channel 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.DeleteChannelAsync(this.Id, reason);
///
/// Clones this channel. This operation will create a channel with identical settings to this one. Note that this will not copy messages.
///
/// Reason for audit logs.
/// Newly-created channel.
/// Thrown when the client does not have the permission.
/// Thrown when the channel does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public async Task CloneAsync(string reason = null)
{
if (this.Guild == null)
throw new InvalidOperationException("Non-guild channels cannot be cloned.");
var ovrs = new List();
foreach (var ovr in this._permissionOverwrites)
#pragma warning disable CS0618 // Type or member is obsolete
ovrs.Add(await new DiscordOverwriteBuilder().FromAsync(ovr).ConfigureAwait(false));
#pragma warning restore CS0618 // Type or member is obsolete
var bitrate = this.Bitrate;
var userLimit = this.UserLimit;
Optional perUserRateLimit = this.PerUserRateLimit;
if(!this.IsVoiceJoinable())
{
bitrate = null;
userLimit = null;
}
if (this.Type == ChannelType.Stage)
{
userLimit = null;
}
if (!this.IsWriteable())
{
perUserRateLimit = Optional.FromNoValue();
}
return await this.Guild.CreateChannelAsync(this.Name, this.Type, this.Parent, this.Topic, bitrate, userLimit, ovrs, this.IsNSFW, perUserRateLimit, this.QualityMode, reason).ConfigureAwait(false);
}
///
/// Returns a specific message
///
/// The id of the message
/// Thrown when the client does not have the permission.
/// Thrown when the channel does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public async Task GetMessageAsync(ulong id)
{
return this.Discord.Configuration.MessageCacheSize > 0
&& this.Discord is DiscordClient dc
&& dc.MessageCache != null
&& dc.MessageCache.TryGet(xm => xm.Id == id && xm.ChannelId == this.Id, out var msg)
? msg
: await this.Discord.ApiClient.GetMessageAsync(this.Id, id).ConfigureAwait(false);
}
///
/// Modifies the current channel.
///
/// Action to perform on this channel
/// Thrown when the client does not have the .
/// Thrown when the client does not have the correct for modifying the .
/// Thrown when the channel does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public Task ModifyAsync(Action action)
{
var mdl = new ChannelEditModel();
action(mdl);
if (mdl.DefaultAutoArchiveDuration.HasValue)
{
if (!Utilities.CheckThreadAutoArchiveDurationFeature(this.Guild, mdl.DefaultAutoArchiveDuration.Value))
throw new NotSupportedException($"Cannot modify DefaultAutoArchiveDuration. Guild needs boost tier {(mdl.DefaultAutoArchiveDuration.Value == ThreadAutoArchiveDuration.ThreeDays ? "one" : "two")}.");
}
if (mdl.Banner.HasValue)
{
if (!this.Guild.Features.CanSetChannelBanner)
throw new NotSupportedException($"Cannot modify Banner. Guild needs boost tier three.");
}
var bannerb64 = Optional.FromNoValue();
if (mdl.Banner.HasValue && mdl.Banner.Value != null)
using (var imgtool = new ImageTool(mdl.Banner.Value))
bannerb64 = imgtool.GetBase64();
else if (mdl.Banner.HasValue)
bannerb64 = null;
return this.Discord.ApiClient.ModifyChannelAsync(this.Id, mdl.Name, mdl.Position, mdl.Topic, mdl.Nsfw,
mdl.Parent.HasValue ? mdl.Parent.Value?.Id : default(Optional), mdl.Bitrate, mdl.Userlimit, mdl.PerUserRateLimit, mdl.RtcRegion.IfPresent(r => r?.Id),
mdl.QualityMode, mdl.DefaultAutoArchiveDuration, mdl.Type, mdl.PermissionOverwrites, bannerb64, mdl.AuditLogReason);
}
///
/// Updates the channel position when it doesn't have a category.
///
/// Use for moving to other categories.
/// Use to move out of a category.
/// Use for moving within a category.
///
/// Position the channel should be moved to.
/// Reason for audit logs.
/// Thrown when the client does not have the permission.
/// Thrown when the channel does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public Task ModifyPositionAsync(int position, string reason = null)
{
if (this.Guild == null)
throw new ArgumentException("Cannot modify order of non-guild channels.");
if (!this.IsMovable())
throw new NotSupportedException("You can't move this type of channel in categories.");
if (this.ParentId != null)
throw new ArgumentException("Cannot modify order of channels within a category. Use ModifyPositionInCategoryAsync instead.");
var chns = this.Guild._channels.Values.Where(xc => xc.Type == this.Type).OrderBy(xc => xc.Position).ToArray();
var pmds = new RestGuildChannelReorderPayload[chns.Length];
for (var i = 0; i < chns.Length; i++)
{
pmds[i] = new RestGuildChannelReorderPayload
{
ChannelId = chns[i].Id,
};
pmds[i].Position = chns[i].Id == this.Id ? position : chns[i].Position >= position ? chns[i].Position + 1 : chns[i].Position;
}
return this.Discord.ApiClient.ModifyGuildChannelPositionAsync(this.Guild.Id, pmds, reason);
}
///
/// Updates the channel position within it's own category.
///
/// Use for moving to other categories.
/// Use to move out of a category.
/// Use to move channels outside a category.
///
/// The position.
/// The reason.
/// Thrown when the client does not have the 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 is out of range.
/// Thrown when function is called on a channel without a parent channel.
public async Task ModifyPositionInCategoryAsync(int position, string reason = null)
{
//if (this.ParentId == null)
// throw new ArgumentException("You can call this function only on channels in categories.");
if (!this.IsMovableInParent())
throw new NotSupportedException("You can't move this type of channel in categories.");
var isUp = position > this.Position;
var channels = await this.InternalRefreshChannelsAsync();
var chns = this.ParentId != null
? this.Type == ChannelType.Text || this.Type == ChannelType.News
? channels.Where(xc => xc.ParentId == this.ParentId && (xc.Type == ChannelType.Text || xc.Type == ChannelType.News))
: this.Type == ChannelType.Voice || this.Type == ChannelType.Stage
? channels.Where(xc => xc.ParentId == this.ParentId && (xc.Type == ChannelType.Voice || xc.Type == ChannelType.Stage))
: channels.Where(xc => xc.ParentId == this.ParentId && xc.Type == this.Type)
: this.Type == ChannelType.Text || this.Type == ChannelType.News
? channels.Where(xc => xc.ParentId == null && (xc.Type == ChannelType.Text || xc.Type == ChannelType.News))
: this.Type == ChannelType.Voice || this.Type == ChannelType.Stage
? channels.Where(xc => xc.ParentId == null && (xc.Type == ChannelType.Voice || xc.Type == ChannelType.Stage))
: channels.Where(xc => xc.ParentId == null && xc.Type == this.Type);
var ochns = chns.OrderBy(xc => xc.Position).ToArray();
var min = ochns.First().Position;
var max = ochns.Last().Position;
if (position > max || position < min)
throw new IndexOutOfRangeException($"Position is not in range. {position} is {(position > max ? "greater then the maximal" : "lower then the minimal")} position.");
var pmds = new RestGuildChannelReorderPayload[ochns.Length];
for (var i = 0; i < ochns.Length; i++)
{
pmds[i] = new RestGuildChannelReorderPayload
{
ChannelId = ochns[i].Id,
};
if (ochns[i].Id == this.Id)
{
pmds[i].Position = position;
}
else
{
if (isUp)
{
if (ochns[i].Position <= position && ochns[i].Position > this.Position)
{
pmds[i].Position = ochns[i].Position - 1;
}
else if (ochns[i].Position < this.Position || ochns[i].Position > position)
{
pmds[i].Position = ochns[i].Position;
}
}
else
{
if (ochns[i].Position >= position && ochns[i].Position < this.Position)
{
pmds[i].Position = ochns[i].Position + 1;
}
else if (ochns[i].Position > this.Position || ochns[i].Position < position)
{
pmds[i].Position = ochns[i].Position;
}
}
}
}
await this.Discord.ApiClient.ModifyGuildChannelPositionAsync(this.Guild.Id, pmds, reason).ConfigureAwait(false);
}
///
/// Internaly refreshes the channel list.
///
private async Task> InternalRefreshChannelsAsync()
{
await this.RefreshPositionsAsync();
return this.Guild.Channels.Values.ToList().AsReadOnly();
}
///
/// Refreshes the positions.
///
public async Task RefreshPositionsAsync()
{
var channels = await this.Discord.ApiClient.GetGuildChannelsAsync(this.Guild.Id);
this.Guild._channels.Clear();
foreach (var channel in channels.ToList())
{
channel.Discord = this.Discord;
foreach (var xo in channel._permissionOverwrites)
{
xo.Discord = this.Discord;
xo._channel_id = channel.Id;
}
this.Guild._channels[channel.Id] = channel;
}
}
///
/// Updates the channel position within it's own category.
/// Valid modes: '+' or 'down' to move a channel down | '-' or 'up' to move a channel up.
///
/// Use for moving to other categories.
/// Use to move out of a category.
/// Use to move channels outside a category.
///
/// The mode. Valid: '+' or 'down' to move a channel down | '-' or 'up' to move a channel up
/// The position.
/// The reason.
/// Thrown when the client does not have the 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 is out of range.
/// Thrown when function is called on a channel without a parent channel, a wrong mode is givven or given position is zero.
public Task ModifyPositionInCategorySmartAsync(string mode, int position, string reason = null)
{
if (!this.IsMovableInParent())
throw new NotSupportedException("You can't move this type of channel in categories.");
if (mode != "+" && mode != "-" && mode != "down" && mode != "up")
throw new ArgumentException("Error with the selected mode: Valid is '+' or 'down' to move a channel down and '-' or 'up' to move a channel up");
var positive = mode == "+" || mode == "positive" || mode == "down";
var negative = mode == "-" || mode == "negative" || mode == "up";
return positive
? position < this.GetMaxPosition()
? this.ModifyPositionInCategoryAsync(this.Position + position, reason)
: throw new IndexOutOfRangeException($"Position is not in range of category.")
: negative
? position > this.GetMinPosition()
? this.ModifyPositionInCategoryAsync(this.Position - position, reason)
: throw new IndexOutOfRangeException($"Position is not in range of category.")
: throw new ArgumentException("You can only modify with +X or -X. 0 is not valid.");
}
///
/// Updates the channel parent, moving the channel to the bottom of the new category.
///
/// New parent for channel. Will move out of parent if null.
/// Sync permissions with parent. Defaults to null.
/// Reason for audit logs.
/// Thrown when the client does not have the permission.
/// Thrown when the channel does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
#pragma warning disable CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
public Task ModifyParentAsync(DiscordChannel? newParent = null, bool? lock_permissions = null, string reason = null)
#pragma warning restore CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
{
if (this.Guild == null)
throw new ArgumentException("Cannot modify parent of non-guild channels.");
if (!this.IsMovableInParent())
throw new NotSupportedException("You can't move this type of channel in categories.");
if (newParent.Type is not ChannelType.Category)
throw new ArgumentException("Only category type channels can be parents.");
var position = this.Guild._channels.Values.Where(xc => xc.Type == this.Type && xc.ParentId == newParent.Id) // gets list same type channels in parent
.Select(xc => xc.Position).DefaultIfEmpty(-1).Max() + 1; // returns highest position of list +1, default val: 0
var chns = this.Guild._channels.Values.Where(xc => xc.Type == this.Type)
.OrderBy(xc => xc.Position).ToArray();
var pmds = new RestGuildChannelNewParentPayload[chns.Length];
for (var i = 0; i < chns.Length; i++)
{
pmds[i] = new RestGuildChannelNewParentPayload
{
ChannelId = chns[i].Id,
Position = chns[i].Position >= position ? chns[i].Position + 1 : chns[i].Position,
};
if (chns[i].Id == this.Id)
{
pmds[i].Position = position;
pmds[i].ParentId = newParent is not null ? newParent.Id : null;
pmds[i].LockPermissions = lock_permissions;
}
}
return this.Discord.ApiClient.ModifyGuildChannelParentAsync(this.Guild.Id, pmds, reason);
}
///
/// Moves the channel out of a category.
///
/// Reason for audit logs.
/// Thrown when the client does not have the permission.
/// Thrown when the channel does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public Task RemoveParentAsync(string reason = null)
{
if (this.Guild == null)
throw new ArgumentException("Cannot modify parent of non-guild channels.");
if (!this.IsMovableInParent())
throw new NotSupportedException("You can't move this type of channel in categories.");
var position = this.Guild._channels.Values.Where(xc => xc.Type == this.Type && xc.Parent is null) //gets list of same type channels with no parent
.Select(xc => xc.Position).DefaultIfEmpty(-1).Max() + 1; // returns highest position of list +1, default val: 0
var chns = this.Guild._channels.Values.Where(xc => xc.Type == this.Type)
.OrderBy(xc => xc.Position).ToArray();
var pmds = new RestGuildChannelNoParentPayload[chns.Length];
for (var i = 0; i < chns.Length; i++)
{
pmds[i] = new RestGuildChannelNoParentPayload
{
ChannelId = chns[i].Id,
};
if (chns[i].Id == this.Id)
{
pmds[i].Position = 1;
pmds[i].ParentId = null;
}
else
{
pmds[i].Position = chns[i].Position < this.Position ? chns[i].Position + 1 : chns[i].Position;
}
}
return this.Discord.ApiClient.DetachGuildChannelParentAsync(this.Guild.Id, pmds, reason);
}
///
/// Returns a list of messages before a certain message.
/// The amount of messages to fetch.
/// Message to fetch before from.
///
/// Thrown when the client does not have the permission.
/// Thrown when the channel does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public Task> GetMessagesBeforeAsync(ulong before, int limit = 100)
=> this.GetMessagesInternalAsync(limit, before, null, null);
///
/// Returns a list of messages after a certain message.
/// The amount of messages to fetch.
/// Message to fetch after from.
///
/// Thrown when the client does not have the permission.
/// Thrown when the channel does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public Task> GetMessagesAfterAsync(ulong after, int limit = 100)
=> this.GetMessagesInternalAsync(limit, null, after, null);
///
/// Returns a list of messages around a certain message.
/// The amount of messages to fetch.
/// Message to fetch around from.
///
/// Thrown when the client does not have the permission.
/// Thrown when the channel does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public Task> GetMessagesAroundAsync(ulong around, int limit = 100)
=> this.GetMessagesInternalAsync(limit, null, null, around);
///
/// Returns a list of messages from the last message in the channel.
/// The amount of messages to fetch.
///
/// Thrown when the client does not have the permission.
/// Thrown when the channel does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public Task> GetMessagesAsync(int limit = 100) =>
this.GetMessagesInternalAsync(limit, null, null, null);
///
/// Returns a list of messages
///
/// How many messages should be returned.
/// Get messages before snowflake.
/// Get messages after snowflake.
/// Get messages around snowflake.
private async Task> GetMessagesInternalAsync(int limit = 100, ulong? before = null, ulong? after = null, ulong? around = null)
{
if (!this.IsWriteable())
throw new ArgumentException("Cannot get the messages of a non-text channel.");
if (limit < 0)
throw new ArgumentException("Cannot get a negative number of messages.");
if (limit == 0)
return Array.Empty();
//return this.Discord.ApiClient.GetChannelMessagesAsync(this.Id, limit, before, after, around);
if (limit > 100 && around != null)
throw new InvalidOperationException("Cannot get more than 100 messages around the specified ID.");
var msgs = new List(limit);
var remaining = limit;
ulong? last = null;
var isAfter = after != null;
int lastCount;
do
{
var fetchSize = remaining > 100 ? 100 : remaining;
var fetch = await this.Discord.ApiClient.GetChannelMessagesAsync(this.Id, fetchSize, !isAfter ? last ?? before : null, isAfter ? last ?? after : null, around).ConfigureAwait(false);
lastCount = fetch.Count;
remaining -= lastCount;
if (!isAfter)
{
msgs.AddRange(fetch);
last = fetch.LastOrDefault()?.Id;
}
else
{
msgs.InsertRange(0, fetch);
last = fetch.FirstOrDefault()?.Id;
}
}
while (remaining > 0 && lastCount > 0);
return new ReadOnlyCollection(msgs);
}
///
/// Deletes multiple messages if they are less than 14 days old. If they are older, none of the messages will be deleted and you will receive a error.
///
/// A collection of messages to delete.
/// Reason for audit logs.
/// Thrown when the client does not have the permission.
/// Thrown when the channel does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public async Task DeleteMessagesAsync(IEnumerable messages, string reason = null)
{
// don't enumerate more than once
var msgs = messages.Where(x => x.Channel.Id == this.Id).Select(x => x.Id).ToArray();
if (messages == null || !msgs.Any())
throw new ArgumentException("You need to specify at least one message to delete.");
if (msgs.Count() < 2)
{
await this.Discord.ApiClient.DeleteMessageAsync(this.Id, msgs.Single(), reason).ConfigureAwait(false);
return;
}
for (var i = 0; i < msgs.Count(); i += 100)
await this.Discord.ApiClient.DeleteMessagesAsync(this.Id, msgs.Skip(i).Take(100), reason).ConfigureAwait(false);
}
///
/// Deletes a message
///
/// The message to be deleted.
/// Reason for audit logs.
/// Thrown when the client does not have the permission.
/// Thrown when the channel does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public Task DeleteMessageAsync(DiscordMessage message, string reason = null)
=> this.Discord.ApiClient.DeleteMessageAsync(this.Id, message.Id, reason);
///
/// Returns a list of invite objects
///
/// Thrown when the client does not have the permission.
/// Thrown when the channel does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public Task> GetInvitesAsync()
{
return this.Guild == null
? throw new ArgumentException("Cannot get the invites of a channel that does not belong to a guild.")
: this.Discord.ApiClient.GetChannelInvitesAsync(this.Id);
}
///
/// Create a new invite object
///
/// Duration of invite in seconds before expiry, or 0 for never. Defaults to 86400.
/// Max number of uses or 0 for unlimited. Defaults to 0
/// Whether this invite should be temporary. Defaults to false.
/// Whether this invite should be unique. Defaults to false.
/// The target type. Defaults to null.
/// The target activity. Defaults to null.
/// The target user id. Defaults to null.
/// The audit log reason.
/// Thrown when the client does not have the permission.
/// Thrown when the channel does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public Task CreateInviteAsync(int max_age = 86400, int max_uses = 0, bool temporary = false, bool unique = false, TargetType? target_type = null, TargetActivity? target_application = null, ulong? target_user = null, string reason = null)
=> this.Discord.ApiClient.CreateChannelInviteAsync(this.Id, max_age, max_uses, target_type, target_application, target_user, temporary, unique, reason);
#region Stage
///
/// Opens a stage.
///
/// Topic of the stage.
/// Whether @everyone should be notified.
/// Privacy level of the stage (Defaults to .
/// Audit log reason.
/// Stage instance
/// Thrown when the client does not have the permission.
/// Thrown when the channel does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public async Task OpenStageAsync(string topic, bool send_start_notification = false, StagePrivacyLevel privacy_level = StagePrivacyLevel.GuildOnly, string reason = null)
=> await this.Discord.ApiClient.CreateStageInstanceAsync(this.Id, topic, send_start_notification, privacy_level, reason);
///
/// Modifies a stage topic.
///
/// New topic of the stage.
/// New privacy level of the stage.
/// Audit log reason.
/// Thrown when the client does not have the permission.
/// Thrown when the channel does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public async Task ModifyStageAsync(Optional topic, Optional privacy_level, string reason = null)
=> await this.Discord.ApiClient.ModifyStageInstanceAsync(this.Id, topic, privacy_level, reason);
///
/// Closes a stage.
///
/// Audit log reason.
/// Thrown when the client does not have the permission.
/// Thrown when the channel does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public async Task CloseStageAsync(string reason = null)
=> await this.Discord.ApiClient.DeleteStageInstanceAsync(this.Id, reason);
///
/// Gets a stage.
///
/// The requested stage.
/// 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.
public async Task GetStageAsync()
=> await this.Discord.ApiClient.GetStageInstanceAsync(this.Id);
#endregion
#region Threads
///
/// Creates a thread.
/// Depending on whether it is created inside an or an it is either an or an .
/// Depending on whether the is set to it is either an or an (default).
///
/// The name of the thread.
/// till it gets archived. Defaults to .
/// Can be either an , or an .
/// The per user ratelimit, aka slowdown.
/// Audit log reason.
/// The created thread.
/// Thrown when the client does not have the or or if creating a private thread the permission.
/// Thrown when the guild hasn't enabled threads atm.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
- /// Thrown when the cannot be modified. This happens, when the guild hasn't reached a certain boost . Or if is not enabled for guild. This happens, if the guild does not have
+ /// Thrown when the cannot be modified. This happens, when the guild hasn't reached a certain boost . Or if is not enabled for guild. This happens, if the guild does not have
public async Task CreateThreadAsync(string name, ThreadAutoArchiveDuration auto_archive_duration = ThreadAutoArchiveDuration.OneHour, ChannelType type = ChannelType.PublicThread, int? rate_limit_per_user = null, string reason = null)
{
return (type != ChannelType.NewsThread && type != ChannelType.PublicThread && type != ChannelType.PrivateThread)
? throw new NotSupportedException("Wrong thread type given.")
: (!this.IsThreadHolder())
? throw new NotSupportedException("Parent channel can't have threads.")
: type == ChannelType.PrivateThread
? Utilities.CheckThreadPrivateFeature(this.Guild)
? Utilities.CheckThreadAutoArchiveDurationFeature(this.Guild, auto_archive_duration)
? await this.Discord.ApiClient.CreateThreadWithoutMessageAsync(this.Id, name, auto_archive_duration, type, rate_limit_per_user, reason)
: throw new NotSupportedException($"Cannot modify ThreadAutoArchiveDuration. Guild needs boost tier {(auto_archive_duration == ThreadAutoArchiveDuration.ThreeDays ? "one" : "two")}.")
: throw new NotSupportedException($"Cannot create a private thread. Guild needs to be boost tier two.")
: Utilities.CheckThreadAutoArchiveDurationFeature(this.Guild, auto_archive_duration)
? await this.Discord.ApiClient.CreateThreadWithoutMessageAsync(this.Id, name, auto_archive_duration, this.Type == ChannelType.News ? ChannelType.NewsThread : ChannelType.PublicThread, rate_limit_per_user, reason)
: throw new NotSupportedException($"Cannot modify ThreadAutoArchiveDuration. Guild needs boost tier {(auto_archive_duration == ThreadAutoArchiveDuration.ThreeDays ? "one" : "two")}.");
}
///
/// Creates a scheduled event based on the channel type.
///
/// The name.
/// The scheduled start time.
/// The description.
/// The reason.
/// A scheduled event.
/// Thrown when the ressource does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public async Task CreateScheduledEventAsync(string name, DateTimeOffset scheduledStartTime, string description = null, string reason = null)
{
if (!this.IsVoiceJoinable())
throw new NotSupportedException("Cannot create a scheduled event for this type of channel. Channel type must be either voice or stage.");
var type = this.Type == ChannelType.Voice ? ScheduledEventEntityType.Voice : ScheduledEventEntityType.StageInstance;
return await this.Guild.CreateScheduledEventAsync(name, scheduledStartTime, null, this, null, description, type, reason);
}
///
/// Gets joined archived private threads. Can contain more threads.
/// If the result's value 'HasMore' is true, you need to recall this function to get older threads.
///
/// Get threads created before this thread id.
/// Defines the limit of returned .
/// Thrown when the client does not have the permission.
/// Thrown when the channel does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public async Task GetJoinedPrivateArchivedThreadsAsync(ulong? before, int? limit)
=> await this.Discord.ApiClient.GetJoinedPrivateArchivedThreadsAsync(this.Id, before, limit);
///
/// Gets archived public threads. Can contain more threads.
/// If the result's value 'HasMore' is true, you need to recall this function to get older threads.
///
/// Get threads created before this thread id.
/// Defines the limit of returned .
/// Thrown when the client does not have the permission.
/// Thrown when the channel does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public async Task GetPublicArchivedThreadsAsync(ulong? before, int? limit)
=> await this.Discord.ApiClient.GetPublicArchivedThreadsAsync(this.Id, before, limit);
///
/// Gets archived private threads. Can contain more threads.
/// If the result's value 'HasMore' is true, you need to recall this function to get older threads.
///
/// Get threads created before this thread id.
/// Defines the limit of returned .
/// 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.
public async Task GetPrivateArchivedThreadsAsync(ulong? before, int? limit)
=> await this.Discord.ApiClient.GetPrivateArchivedThreadsAsync(this.Id, before, limit);
#endregion
///
/// Adds a channel permission overwrite for specified role.
///
/// The role to have the permission added.
/// The permissions to allow.
/// The permissions to deny.
/// Reason for audit logs.
/// Thrown when the client does not have the permission.
/// Thrown when the channel does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public Task AddOverwriteAsync(DiscordRole role, Permissions allow = Permissions.None, Permissions deny = Permissions.None, string reason = null)
=> this.Discord.ApiClient.EditChannelPermissionsAsync(this.Id, role.Id, allow, deny, "role", reason);
///
/// Adds a channel permission overwrite for specified member.
///
/// The member to have the permission added.
/// The permissions to allow.
/// The permissions to deny.
/// Reason for audit logs.
/// Thrown when the client does not have the permission.
/// Thrown when the channel does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public Task AddOverwriteAsync(DiscordMember member, Permissions allow = Permissions.None, Permissions deny = Permissions.None, string reason = null)
=> this.Discord.ApiClient.EditChannelPermissionsAsync(this.Id, member.Id, allow, deny, "member", reason);
///
/// Deletes a channel permission overwrite for specified member.
///
/// The member to have the permission deleted.
/// Reason for audit logs.
/// Thrown when the client does not have the permission.
/// Thrown when the channel does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public Task DeleteOverwriteAsync(DiscordMember member, string reason = null)
=> this.Discord.ApiClient.DeleteChannelPermissionAsync(this.Id, member.Id, reason);
///
/// Deletes a channel permission overwrite for specified role.
///
/// The role to have the permission deleted.
/// Reason for audit logs.
/// Thrown when the client does not have the permission.
/// Thrown when the channel does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public Task DeleteOverwriteAsync(DiscordRole role, string reason = null)
=> this.Discord.ApiClient.DeleteChannelPermissionAsync(this.Id, role.Id, reason);
///
/// Post a typing indicator
///
/// Thrown when the channel does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public Task TriggerTypingAsync()
{
return !this.IsWriteable()
? throw new ArgumentException("Cannot start typing in a non-text channel.")
: this.Discord.ApiClient.TriggerTypingAsync(this.Id);
}
///
/// Returns all pinned messages
///
/// Thrown when the client does not have the permission.
/// Thrown when the channel does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public Task> GetPinnedMessagesAsync()
{
return !this.IsWriteable()
? throw new ArgumentException("A non-text channel does not have pinned messages.")
: this.Discord.ApiClient.GetPinnedMessagesAsync(this.Id);
}
///
/// Create a new webhook
///
/// The name of the webhook.
/// The image for the default webhook avatar.
/// Reason for audit logs.
/// Thrown when the client does not have the permission.
/// Thrown when the channel does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public async Task CreateWebhookAsync(string name, Optional avatar = default, string reason = null)
{
var av64 = Optional.FromNoValue();
if (avatar.HasValue && avatar.Value != null)
using (var imgtool = new ImageTool(avatar.Value))
av64 = imgtool.GetBase64();
else if (avatar.HasValue)
av64 = null;
return await this.Discord.ApiClient.CreateWebhookAsync(this.Id, name, av64, reason).ConfigureAwait(false);
}
///
/// Returns a list of webhooks
///
/// Thrown when the client does not have the permission.
/// Thrown when the channel does not exist.
/// Thrown when Discord is unable to process the request.
public Task> GetWebhooksAsync()
=> this.Discord.ApiClient.GetChannelWebhooksAsync(this.Id);
///
/// Moves a member to this voice channel
///
/// The member to be moved.
/// Thrown when the client does not have the permission.
/// Thrown when the channel does not exists or if the Member does not exists.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public async Task PlaceMemberAsync(DiscordMember member)
{
if (!this.IsVoiceJoinable())
throw new ArgumentException("Cannot place a member in a non-voice channel.");
await this.Discord.ApiClient.ModifyGuildMemberAsync(this.Guild.Id, member.Id, default, default, default,
default, this.Id, null).ConfigureAwait(false);
}
///
/// Follows a news channel
///
/// Channel to crosspost messages to
/// Thrown when trying to follow a non-news channel
/// Thrown when the current user doesn't have on the target channel
public Task FollowAsync(DiscordChannel targetChannel)
{
return this.Type != ChannelType.News
? throw new ArgumentException("Cannot follow a non-news channel.")
: this.Discord.ApiClient.FollowChannelAsync(this.Id, targetChannel.Id);
}
///
/// Publishes a message in a news channel to following channels
///
/// Message to publish
/// Thrown when the message has already been crossposted
///
/// Thrown when the current user doesn't have and/or
///
public Task CrosspostMessageAsync(DiscordMessage message)
{
return (message.Flags & MessageFlags.Crossposted) == MessageFlags.Crossposted
? throw new ArgumentException("Message is already crossposted.")
: this.Discord.ApiClient.CrosspostMessageAsync(this.Id, message.Id);
}
///
/// Updates the current user's suppress state in this channel, if stage channel.
///
/// Toggles the suppress state.
/// Sets the time the user requested to speak.
/// Thrown when the channel is not a stage channel.
public async Task UpdateCurrentUserVoiceStateAsync(bool? suppress, DateTimeOffset? requestToSpeakTimestamp = null)
{
if (this.Type != ChannelType.Stage)
throw new ArgumentException("Voice state can only be updated in a stage channel.");
await this.Discord.ApiClient.UpdateCurrentUserVoiceStateAsync(this.GuildId.Value, this.Id, suppress, requestToSpeakTimestamp).ConfigureAwait(false);
}
///
/// Calculates permissions for a given member.
///
/// Member to calculate permissions for.
/// Calculated permissions for a given member.
public Permissions PermissionsFor(DiscordMember mbr)
{
// future note: might be able to simplify @everyone role checks to just check any role ... but I'm not sure
// xoxo, ~uwx
//
// you should use a single tilde
// ~emzi
// user > role > everyone
// allow > deny > undefined
// =>
// user allow > user deny > role allow > role deny > everyone allow > everyone deny
// thanks to meew0
if (this.IsPrivate || this.Guild == null)
return Permissions.None;
if (this.Guild.OwnerId == mbr.Id)
return PermissionMethods.FULL_PERMS;
Permissions perms;
// assign @everyone permissions
var everyoneRole = this.Guild.EveryoneRole;
perms = everyoneRole.Permissions;
// roles that member is in
var mbRoles = mbr.Roles.Where(xr => xr.Id != everyoneRole.Id).ToArray();
// assign permissions from member's roles (in order)
perms |= mbRoles.Aggregate(Permissions.None, (c, role) => c | role.Permissions);
// Adminstrator grants all permissions and cannot be overridden
if ((perms & Permissions.Administrator) == Permissions.Administrator)
return PermissionMethods.FULL_PERMS;
// channel overrides for roles that member is in
var mbRoleOverrides = mbRoles
.Select(xr => this._permissionOverwrites.FirstOrDefault(xo => xo.Id == xr.Id))
.Where(xo => xo != null)
.ToList();
// assign channel permission overwrites for @everyone pseudo-role
var everyoneOverwrites = this._permissionOverwrites.FirstOrDefault(xo => xo.Id == everyoneRole.Id);
if (everyoneOverwrites != null)
{
perms &= ~everyoneOverwrites.Denied;
perms |= everyoneOverwrites.Allowed;
}
// assign channel permission overwrites for member's roles (explicit deny)
perms &= ~mbRoleOverrides.Aggregate(Permissions.None, (c, overs) => c | overs.Denied);
// assign channel permission overwrites for member's roles (explicit allow)
perms |= mbRoleOverrides.Aggregate(Permissions.None, (c, overs) => c | overs.Allowed);
// channel overrides for just this member
var mbOverrides = this._permissionOverwrites.FirstOrDefault(xo => xo.Id == mbr.Id);
if (mbOverrides == null) return perms;
// assign channel permission overwrites for just this member
perms &= ~mbOverrides.Denied;
perms |= mbOverrides.Allowed;
return perms;
}
///
/// Returns a string representation of this channel.
///
/// String representation of this channel.
public override string ToString()
{
return this.Type == ChannelType.Category
? $"Channel Category {this.Name} ({this.Id})"
: this.Type == ChannelType.Text || this.Type == ChannelType.News || this.IsThread()
? $"Channel #{this.Name} ({this.Id})"
: this.IsVoiceJoinable()
? $"Channel #!{this.Name} ({this.Id})"
: !string.IsNullOrWhiteSpace(this.Name) ? $"Channel {this.Name} ({this.Id})" : $"Channel {this.Id}";
}
#endregion
///
/// 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 DiscordChannel);
///
/// Checks whether this is equal to another .
///
/// to compare to.
/// Whether the is equal to this .
public bool Equals(DiscordChannel e) => e is not null && (ReferenceEquals(this, e) || this.Id == e.Id);
///
/// Gets the hash code for this .
///
/// The hash code for this .
public override int GetHashCode() => this.Id.GetHashCode();
///
/// Gets whether the two objects are equal.
///
/// First channel to compare.
/// Second channel to compare.
/// Whether the two channels are equal.
public static bool operator ==(DiscordChannel e1, DiscordChannel 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);
}
///
/// Gets whether the two objects are not equal.
///
/// First channel to compare.
/// Second channel to compare.
/// Whether the two channels are not equal.
public static bool operator !=(DiscordChannel e1, DiscordChannel e2)
=> !(e1 == e2);
}
}
diff --git a/DisCatSharp/Entities/Guild/DiscordGuild.cs b/DisCatSharp/Entities/Guild/DiscordGuild.cs
index 933467edc..70e8b8282 100644
--- a/DisCatSharp/Entities/Guild/DiscordGuild.cs
+++ b/DisCatSharp/Entities/Guild/DiscordGuild.cs
@@ -1,3477 +1,3477 @@
// 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.
#pragma warning disable CS0618
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using DisCatSharp.Enums;
using DisCatSharp.EventArgs;
using DisCatSharp.Exceptions;
using DisCatSharp.Net;
using DisCatSharp.Net.Abstractions;
using DisCatSharp.Net.Models;
using DisCatSharp.Net.Serialization;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace DisCatSharp.Entities
{
///
/// Represents a Discord guild.
///
public class DiscordGuild : SnowflakeObject, IEquatable
{
///
/// Gets the guild's name.
///
[JsonProperty("name", NullValueHandling = NullValueHandling.Ignore)]
public string Name { get; internal set; }
///
/// Gets the guild icon's hash.
///
[JsonProperty("icon", NullValueHandling = NullValueHandling.Ignore)]
public string IconHash { get; internal set; }
///
/// Gets the guild icon's url.
///
[JsonIgnore]
public string IconUrl
=> !string.IsNullOrWhiteSpace(this.IconHash) ? $"{DiscordDomain.GetDomain(CoreDomain.DiscordCdn).Url}{Endpoints.ICONS}/{this.Id.ToString(CultureInfo.InvariantCulture)}/{this.IconHash}.{(this.IconHash.StartsWith("a_") ? "gif" : "png")}?size=1024" : null;
///
/// Gets the guild splash's hash.
///
[JsonProperty("splash", NullValueHandling = NullValueHandling.Ignore)]
public string SplashHash { get; internal set; }
///
/// Gets the guild splash's url.
///
[JsonIgnore]
public string SplashUrl
=> !string.IsNullOrWhiteSpace(this.SplashHash) ? $"{DiscordDomain.GetDomain(CoreDomain.DiscordCdn).Url}{Endpoints.SPLASHES}/{this.Id.ToString(CultureInfo.InvariantCulture)}/{this.SplashHash}.png?size=1024" : null;
///
/// Gets the guild discovery splash's hash.
///
[JsonProperty("discovery_splash", NullValueHandling = NullValueHandling.Ignore)]
public string DiscoverySplashHash { get; internal set; }
///
/// Gets the guild discovery splash's url.
///
[JsonIgnore]
public string DiscoverySplashUrl
=> !string.IsNullOrWhiteSpace(this.DiscoverySplashHash) ? $"{DiscordDomain.GetDomain(CoreDomain.DiscordCdn).Url}{Endpoints.GUILD_DISCOVERY_SPLASHES}/{this.Id.ToString(CultureInfo.InvariantCulture)}/{this.DiscoverySplashHash}.png?size=1024" : null;
///
/// Gets the preferred locale of this guild.
/// This is used for server discovery and notices from Discord. Defaults to en-US.
///
[JsonProperty("preferred_locale", NullValueHandling = NullValueHandling.Ignore)]
public string PreferredLocale { get; internal set; }
///
/// Gets the ID of the guild's owner.
///
[JsonProperty("owner_id", NullValueHandling = NullValueHandling.Ignore)]
public ulong OwnerId { get; internal set; }
///
/// Gets the guild's owner.
///
[JsonIgnore]
public DiscordMember Owner
=> this.Members.TryGetValue(this.OwnerId, out var owner)
? owner
: this.Discord.ApiClient.GetGuildMemberAsync(this.Id, this.OwnerId).ConfigureAwait(false).GetAwaiter().GetResult();
///
/// Gets permissions for the user in the guild (does not include channel overrides)
///
[JsonProperty("permissions", NullValueHandling = NullValueHandling.Ignore)]
public Permissions? Permissions { get; set; }
///
/// Gets the guild's voice region ID.
///
[JsonProperty("region", NullValueHandling = NullValueHandling.Ignore)]
internal string VoiceRegionId { get; set; }
///
/// Gets the guild's voice region.
///
[JsonIgnore]
public DiscordVoiceRegion VoiceRegion
=> this.Discord.VoiceRegions[this.VoiceRegionId];
///
/// Gets the guild's AFK voice channel ID.
///
[JsonProperty("afk_channel_id", NullValueHandling = NullValueHandling.Ignore)]
internal ulong AfkChannelId { get; set; } = 0;
///
/// Gets the guild's AFK voice channel.
///
[JsonIgnore]
public DiscordChannel AfkChannel
=> this.GetChannel(this.AfkChannelId);
///
/// Gets the guild's AFK timeout.
///
[JsonProperty("afk_timeout", NullValueHandling = NullValueHandling.Ignore)]
public int AfkTimeout { get; internal set; }
///
/// Gets the guild's verification level.
///
[JsonProperty("verification_level", NullValueHandling = NullValueHandling.Ignore)]
public VerificationLevel VerificationLevel { get; internal set; }
///
/// Gets the guild's default notification settings.
///
[JsonProperty("default_message_notifications", NullValueHandling = NullValueHandling.Ignore)]
public DefaultMessageNotifications DefaultMessageNotifications { get; internal set; }
///
/// Gets the guild's explicit content filter settings.
///
[JsonProperty("explicit_content_filter")]
public ExplicitContentFilter ExplicitContentFilter { get; internal set; }
///
/// Gets the guild's nsfw level.
///
[JsonProperty("nsfw_level")]
public NsfwLevel NsfwLevel { get; internal set; }
///
/// Gets the system channel id.
///
[JsonProperty("system_channel_id", NullValueHandling = NullValueHandling.Include)]
internal ulong? SystemChannelId { get; set; }
///
/// Gets the channel where system messages (such as boost and welcome messages) are sent.
///
[JsonIgnore]
public DiscordChannel SystemChannel => this.SystemChannelId.HasValue
? this.GetChannel(this.SystemChannelId.Value)
: null;
///
/// Gets the settings for this guild's system channel.
///
[JsonProperty("system_channel_flags")]
public SystemChannelFlags SystemChannelFlags { get; internal set; }
///
/// Gets whether this guild's widget is enabled.
///
[JsonProperty("widget_enabled", NullValueHandling = NullValueHandling.Ignore)]
public bool? WidgetEnabled { get; internal set; }
///
/// Gets the widget channel id.
///
[JsonProperty("widget_channel_id", NullValueHandling = NullValueHandling.Ignore)]
internal ulong? WidgetChannelId { get; set; }
///
/// Gets the widget channel for this guild.
///
[JsonIgnore]
public DiscordChannel WidgetChannel => this.WidgetChannelId.HasValue
? this.GetChannel(this.WidgetChannelId.Value)
: null;
///
/// Gets the rules channel id.
///
[JsonProperty("rules_channel_id")]
internal ulong? RulesChannelId { get; set; }
///
/// Gets the rules channel for this guild.
/// This is only available if the guild is considered "discoverable".
///
[JsonIgnore]
public DiscordChannel RulesChannel => this.RulesChannelId.HasValue
? this.GetChannel(this.RulesChannelId.Value)
: null;
///
/// Gets the public updates channel id.
///
[JsonProperty("public_updates_channel_id")]
internal ulong? PublicUpdatesChannelId { get; set; }
///
/// Gets the public updates channel (where admins and moderators receive messages from Discord) for this guild.
/// This is only available if the guild is considered "discoverable".
///
[JsonIgnore]
public DiscordChannel PublicUpdatesChannel => this.PublicUpdatesChannelId.HasValue
? this.GetChannel(this.PublicUpdatesChannelId.Value)
: null;
///
/// Gets the application id of this guild if it is bot created.
///
[JsonProperty("application_id")]
public ulong? ApplicationId { get; internal set; }
///
/// Gets a collection of this guild's roles.
///
[JsonIgnore]
public IReadOnlyDictionary Roles => new ReadOnlyConcurrentDictionary(this._roles);
[JsonProperty("roles", NullValueHandling = NullValueHandling.Ignore)]
[JsonConverter(typeof(SnowflakeArrayAsDictionaryJsonConverter))]
internal ConcurrentDictionary _roles;
///
/// Gets a collection of this guild's stickers.
///
[JsonIgnore]
public IReadOnlyDictionary Stickers => new ReadOnlyConcurrentDictionary(this._stickers);
[JsonProperty("stickers", NullValueHandling = NullValueHandling.Ignore)]
[JsonConverter(typeof(SnowflakeArrayAsDictionaryJsonConverter))]
internal ConcurrentDictionary _stickers;
///
/// Gets a collection of this guild's emojis.
///
[JsonIgnore]
public IReadOnlyDictionary Emojis => new ReadOnlyConcurrentDictionary(this._emojis);
[JsonProperty("emojis", NullValueHandling = NullValueHandling.Ignore)]
[JsonConverter(typeof(SnowflakeArrayAsDictionaryJsonConverter))]
internal ConcurrentDictionary _emojis;
///
/// Gets a collection of this guild's features.
///
[JsonProperty("features", NullValueHandling = NullValueHandling.Ignore)]
public IReadOnlyList RawFeatures { get; internal set; }
///
/// Gets the guild's features.
///
[JsonIgnore]
public GuildFeatures Features => new(this);
///
/// Gets the required multi-factor authentication level for this guild.
///
[JsonProperty("mfa_level", NullValueHandling = NullValueHandling.Ignore)]
public MfaLevel MfaLevel { get; internal set; }
///
/// Gets this guild's join date.
///
[JsonProperty("joined_at", NullValueHandling = NullValueHandling.Ignore)]
public DateTimeOffset JoinedAt { get; internal set; }
///
/// Gets whether this guild is considered to be a large guild.
///
[JsonProperty("large", NullValueHandling = NullValueHandling.Ignore)]
public bool IsLarge { get; internal set; }
///
/// Gets whether this guild is unavailable.
///
[JsonProperty("unavailable", NullValueHandling = NullValueHandling.Ignore)]
public bool IsUnavailable { get; internal set; }
///
/// Gets the total number of members in this guild.
///
[JsonProperty("member_count", NullValueHandling = NullValueHandling.Ignore)]
public int MemberCount { get; internal set; }
///
/// Gets the maximum amount of members allowed for this guild.
///
[JsonProperty("max_members")]
public int? MaxMembers { get; internal set; }
///
/// Gets the maximum amount of presences allowed for this guild.
///
[JsonProperty("max_presences")]
public int? MaxPresences { get; internal set; }
#pragma warning disable CS1734
///
/// Gets the approximate number of members in this guild, when using and having set to true.
///
[JsonProperty("approximate_member_count", NullValueHandling = NullValueHandling.Ignore)]
public int? ApproximateMemberCount { get; internal set; }
///
/// Gets the approximate number of presences in this guild, when using and having set to true.
///
[JsonProperty("approximate_presence_count", NullValueHandling = NullValueHandling.Ignore)]
public int? ApproximatePresenceCount { get; internal set; }
#pragma warning restore CS1734
///
/// Gets the maximum amount of users allowed per video channel.
///
[JsonProperty("max_video_channel_users", NullValueHandling = NullValueHandling.Ignore)]
public int? MaxVideoChannelUsers { get; internal set; }
///
/// Gets a dictionary of all the voice states for this guilds. The key for this dictionary is the ID of the user
/// the voice state corresponds to.
///
[JsonIgnore]
public IReadOnlyDictionary VoiceStates => new ReadOnlyConcurrentDictionary(this._voiceStates);
[JsonProperty("voice_states", NullValueHandling = NullValueHandling.Ignore)]
[JsonConverter(typeof(SnowflakeArrayAsDictionaryJsonConverter))]
internal ConcurrentDictionary _voiceStates;
///
/// Gets a dictionary of all the members that belong to this guild. The dictionary's key is the member ID.
///
[JsonIgnore] // TODO overhead of => vs Lazy? it's a struct
public IReadOnlyDictionary Members => new ReadOnlyConcurrentDictionary(this._members);
[JsonProperty("members", NullValueHandling = NullValueHandling.Ignore)]
[JsonConverter(typeof(SnowflakeArrayAsDictionaryJsonConverter))]
internal ConcurrentDictionary _members;
///
/// Gets a dictionary of all the channels associated with this guild. The dictionary's key is the channel ID.
///
[JsonIgnore]
public IReadOnlyDictionary Channels => new ReadOnlyConcurrentDictionary(this._channels);
[JsonProperty("channels", NullValueHandling = NullValueHandling.Ignore)]
[JsonConverter(typeof(SnowflakeArrayAsDictionaryJsonConverter))]
internal ConcurrentDictionary _channels;
internal ConcurrentDictionary _invites;
///
/// Gets a dictionary of all the active threads associated with this guild the user has permission to view. The dictionary's key is the channel ID.
///
[JsonIgnore]
public IReadOnlyDictionary Threads { get; internal set; }
[JsonProperty("threads", NullValueHandling = NullValueHandling.Ignore)]
[JsonConverter(typeof(SnowflakeArrayAsDictionaryJsonConverter))]
internal ConcurrentDictionary _threads = new();
///
/// Gets a dictionary of all active stage instances. The dictionary's key is the stage ID.
///
[JsonIgnore]
public IReadOnlyDictionary StageInstances { get; internal set; }
[JsonProperty("stage_instances", NullValueHandling = NullValueHandling.Ignore)]
[JsonConverter(typeof(SnowflakeArrayAsDictionaryJsonConverter))]
internal ConcurrentDictionary _stageInstances = new();
///
/// Gets a dictionary of all scheduled events.
///
[JsonIgnore]
public IReadOnlyDictionary ScheduledEvents { get; internal set; }
[JsonProperty("guild_scheduled_events", NullValueHandling = NullValueHandling.Ignore)]
[JsonConverter(typeof(SnowflakeArrayAsDictionaryJsonConverter))]
internal ConcurrentDictionary _scheduledEvents = new();
///
/// Gets the guild member for current user.
///
[JsonIgnore]
public DiscordMember CurrentMember
=> this._current_member_lazy.Value;
[JsonIgnore]
private readonly Lazy _current_member_lazy;
///
/// Gets the @everyone role for this guild.
///
[JsonIgnore]
public DiscordRole EveryoneRole
=> this.GetRole(this.Id);
[JsonIgnore]
internal bool _isOwner;
///
/// Gets whether the current user is the guild's owner.
///
[JsonProperty("owner", NullValueHandling = NullValueHandling.Ignore)]
public bool IsOwner
{
get => this._isOwner || this.OwnerId == this.Discord.CurrentUser.Id;
internal set => this._isOwner = value;
}
///
/// Gets the vanity URL code for this guild, when applicable.
///
[JsonProperty("vanity_url_code")]
public string VanityUrlCode { get; internal set; }
///
/// Gets the guild description, when applicable.
///
[JsonProperty("description")]
public string Description { get; internal set; }
///
/// Gets this guild's banner hash, when applicable.
///
[JsonProperty("banner")]
public string BannerHash { get; internal set; }
///
/// Gets this guild's banner in url form.
///
[JsonIgnore]
public string BannerUrl
=> !string.IsNullOrWhiteSpace(this.BannerHash) ? $"{DiscordDomain.GetDomain(CoreDomain.DiscordCdn).Uri}{Endpoints.BANNERS}/{this.Id.ToString(CultureInfo.InvariantCulture)}/{this.BannerHash}.{(this.BannerHash.StartsWith("a_") ? "gif" : "png")}" : null;
///
/// Whether this guild has the community feature enabled.
///
[JsonIgnore]
public bool IsCommunity => this.Features.HasCommunityEnabled;
///
/// Whether this guild has enabled the welcome screen.
///
[JsonIgnore]
public bool HasWelcomeScreen => this.Features.HasWelcomeScreenEnabled;
///
/// Whether this guild has enabled membership screening.
///
[JsonIgnore]
public bool HasMemberVerificationGate => this.Features.HasMembershipScreeningEnabled;
///
/// Gets this guild's premium tier (Nitro boosting).
///
[JsonProperty("premium_tier")]
public PremiumTier PremiumTier { get; internal set; }
///
/// Gets the amount of members that boosted this guild.
///
[JsonProperty("premium_subscription_count", NullValueHandling = NullValueHandling.Ignore)]
public int? PremiumSubscriptionCount { get; internal set; }
///
/// Whether the premium progress bar is enabled.
///
[JsonProperty("premium_progress_bar_enabled", NullValueHandling = NullValueHandling.Ignore)]
public bool PremiumProgressBarEnabled { get; internal set; }
///
/// Gets whether this guild is designated as NSFW.
///
[JsonProperty("nsfw", NullValueHandling = NullValueHandling.Ignore)]
public bool IsNSFW { get; internal set; }
///
/// Gets a dictionary of all by position ordered channels associated with this guild. The dictionary's key is the channel ID.
///
[JsonIgnore]
public IReadOnlyDictionary OrderedChannels => new ReadOnlyDictionary(this.InternalSortChannels());
///
/// Sorts the channels.
///
private Dictionary InternalSortChannels()
{
Dictionary keyValuePairs = new();
var ochannels = this.GetOrderedChannels();
foreach (var ochan in ochannels)
{
if (ochan.Key != 0)
keyValuePairs.Add(ochan.Key, this.GetChannel(ochan.Key));
foreach (var chan in ochan.Value)
keyValuePairs.Add(chan.Id, chan);
}
return keyValuePairs;
}
///
/// Gets an ordered list out of the channel cache.
/// Returns a Dictionary where the key is an ulong and can be mapped to s.
/// Ignore the 0 key here, because that indicates that this is the "has no category" list.
/// Each value contains a ordered list of text/news and voice/stage channels as .
///
/// A ordered list of categories with its channels
public Dictionary> GetOrderedChannels()
{
IReadOnlyList raw_channels = this._channels.Values.ToList();
Dictionary> ordered_channels = new();
ordered_channels.Add(0, new List());
foreach (var channel in raw_channels.Where(c => c.Type == ChannelType.Category).OrderBy(c => c.Position))
{
ordered_channels.Add(channel.Id, new List());
}
foreach (var channel in raw_channels.Where(c => c.ParentId.HasValue && (c.Type == ChannelType.Text || c.Type == ChannelType.News)).OrderBy(c => c.Position))
{
ordered_channels[channel.ParentId.Value].Add(channel);
}
foreach (var channel in raw_channels.Where(c => c.ParentId.HasValue && (c.Type == ChannelType.Voice || c.Type == ChannelType.Stage)).OrderBy(c => c.Position))
{
ordered_channels[channel.ParentId.Value].Add(channel);
}
foreach (var channel in raw_channels.Where(c => !c.ParentId.HasValue && c.Type != ChannelType.Category && (c.Type == ChannelType.Text || c.Type == ChannelType.News)).OrderBy(c => c.Position))
{
ordered_channels[0].Add(channel);
}
foreach (var channel in raw_channels.Where(c => !c.ParentId.HasValue && c.Type != ChannelType.Category && (c.Type == ChannelType.Voice || c.Type == ChannelType.Stage)).OrderBy(c => c.Position))
{
ordered_channels[0].Add(channel);
}
return ordered_channels;
}
///
/// Gets an ordered list.
/// Returns a Dictionary where the key is an ulong and can be mapped to s.
/// Ignore the 0 key here, because that indicates that this is the "has no category" list.
/// Each value contains a ordered list of text/news and voice/stage channels as .
///
/// A ordered list of categories with its channels
public async Task>> GetOrderedChannelsAsync()
{
var raw_channels = await this.Discord.ApiClient.GetGuildChannelsAsync(this.Id);
Dictionary> ordered_channels = new();
ordered_channels.Add(0, new List());
foreach (var channel in raw_channels.Where(c => c.Type == ChannelType.Category).OrderBy(c => c.Position))
{
ordered_channels.Add(channel.Id, new List());
}
foreach (var channel in raw_channels.Where(c => c.ParentId.HasValue && (c.Type == ChannelType.Text || c.Type == ChannelType.News)).OrderBy(c => c.Position))
{
ordered_channels[channel.ParentId.Value].Add(channel);
}
foreach (var channel in raw_channels.Where(c => c.ParentId.HasValue && (c.Type == ChannelType.Voice || c.Type == ChannelType.Stage)).OrderBy(c => c.Position))
{
ordered_channels[channel.ParentId.Value].Add(channel);
}
foreach (var channel in raw_channels.Where(c => !c.ParentId.HasValue && c.Type != ChannelType.Category && (c.Type == ChannelType.Text || c.Type == ChannelType.News)).OrderBy(c => c.Position))
{
ordered_channels[0].Add(channel);
}
foreach (var channel in raw_channels.Where(c => !c.ParentId.HasValue && c.Type != ChannelType.Category && (c.Type == ChannelType.Voice || c.Type == ChannelType.Stage)).OrderBy(c => c.Position))
{
ordered_channels[0].Add(channel);
}
return ordered_channels;
}
///
/// Whether it is synced.
///
[JsonIgnore]
internal bool IsSynced { get; set; }
///
/// Initializes a new instance of the class.
///
internal DiscordGuild()
{
this._current_member_lazy = new Lazy(() => (this._members != null && this._members.TryGetValue(this.Discord.CurrentUser.Id, out var member)) ? member : null);
this._invites = new ConcurrentDictionary();
this.Threads = new ReadOnlyConcurrentDictionary(this._threads);
this.StageInstances = new ReadOnlyConcurrentDictionary(this._stageInstances);
this.ScheduledEvents = new ReadOnlyConcurrentDictionary(this._scheduledEvents);
}
#region Guild Methods
///
/// Searches the current guild for members who's display name start with the specified name.
///
/// The name to search for.
/// The maximum amount of members to return. Max 1000. Defaults to 1.
/// The members found, if any.
public Task> SearchMembersAsync(string name, int? limit = 1)
=> this.Discord.ApiClient.SearchMembersAsync(this.Id, name, limit);
///
/// Adds a new member to this guild
///
/// User to add
/// User's access token (OAuth2)
/// new nickname
/// new roles
/// whether this user has to be muted
/// whether this user has to be deafened
///
/// Thrown when the client does not have the permission.
/// Thrown when the or is not found.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public Task AddMemberAsync(DiscordUser user, string access_token, string nickname = null, IEnumerable roles = null,
bool muted = false, bool deaf = false)
=> this.Discord.ApiClient.AddGuildMemberAsync(this.Id, user.Id, access_token, nickname, roles, muted, deaf);
///
/// Deletes this guild. Requires the caller to be the owner of the guild.
///
///
/// Thrown when the client is not the owner of the guild.
/// Thrown when Discord is unable to process the request.
public Task DeleteAsync()
=> this.Discord.ApiClient.DeleteGuildAsync(this.Id);
///
/// Modifies this guild.
///
/// Action to perform on this guild..
/// The modified guild object.
/// Thrown when the client does not have the permission.
/// Thrown when the guild 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 mdl = new GuildEditModel();
action(mdl);
var afkChannelId = Optional.FromNoValue();
if (mdl.AfkChannel.HasValue && mdl.AfkChannel.Value.Type != ChannelType.Voice && mdl.AfkChannel.Value != null)
throw new ArgumentException("AFK channel needs to be a voice channel.");
else if (mdl.AfkChannel.HasValue && mdl.AfkChannel.Value != null)
afkChannelId = mdl.AfkChannel.Value.Id;
else if (mdl.AfkChannel.HasValue)
afkChannelId = null;
var rulesChannelId = Optional.FromNoValue();
if (mdl.RulesChannel.HasValue && mdl.RulesChannel.Value != null && mdl.RulesChannel.Value.Type != ChannelType.Text && mdl.RulesChannel.Value.Type != ChannelType.News )
throw new ArgumentException("Rules channel needs to be a text channel.");
else if (mdl.RulesChannel.HasValue && mdl.RulesChannel.Value != null)
rulesChannelId = mdl.RulesChannel.Value.Id;
else if (mdl.RulesChannel.HasValue)
rulesChannelId = null;
var publicUpdatesChannelId = Optional.FromNoValue();
if (mdl.PublicUpdatesChannel.HasValue && mdl.PublicUpdatesChannel.Value != null && mdl.PublicUpdatesChannel.Value.Type != ChannelType.Text && mdl.PublicUpdatesChannel.Value.Type != ChannelType.News)
throw new ArgumentException("Public updates channel needs to be a text channel.");
else if (mdl.PublicUpdatesChannel.HasValue && mdl.PublicUpdatesChannel.Value != null)
publicUpdatesChannelId = mdl.PublicUpdatesChannel.Value.Id;
else if (mdl.PublicUpdatesChannel.HasValue)
publicUpdatesChannelId = null;
var systemChannelId = Optional.FromNoValue();
if (mdl.SystemChannel.HasValue && mdl.SystemChannel.Value != null && mdl.SystemChannel.Value.Type != ChannelType.Text && mdl.SystemChannel.Value.Type != ChannelType.News)
throw new ArgumentException("Public updates channel needs to be a text channel.");
else if (mdl.SystemChannel.HasValue && mdl.SystemChannel.Value != null)
systemChannelId = mdl.SystemChannel.Value.Id;
else if (mdl.SystemChannel.HasValue)
systemChannelId = null;
var iconb64 = Optional.FromNoValue();
if (mdl.Icon.HasValue && mdl.Icon.Value != null)
using (var imgtool = new ImageTool(mdl.Icon.Value))
iconb64 = imgtool.GetBase64();
else if (mdl.Icon.HasValue)
iconb64 = null;
var splashb64 = Optional.FromNoValue();
if (mdl.Splash.HasValue && mdl.Splash.Value != null)
using (var imgtool = new ImageTool(mdl.Splash.Value))
splashb64 = imgtool.GetBase64();
else if (mdl.Splash.HasValue)
splashb64 = null;
var bannerb64 = Optional.FromNoValue();
if (mdl.Banner.HasValue && mdl.Banner.Value != null)
using (var imgtool = new ImageTool(mdl.Banner.Value))
bannerb64 = imgtool.GetBase64();
else if (mdl.Banner.HasValue)
bannerb64 = null;
var discoverySplash64 = Optional.FromNoValue();
if (mdl.DiscoverySplash.HasValue && mdl.DiscoverySplash.Value != null)
using (var imgtool = new ImageTool(mdl.DiscoverySplash.Value))
discoverySplash64 = imgtool.GetBase64();
else if (mdl.DiscoverySplash.HasValue)
discoverySplash64 = null;
var description = Optional.FromNoValue();
if (mdl.Description.HasValue && mdl.Description.Value != null)
description = mdl.Description;
else if (mdl.Description.HasValue)
description = null;
return await this.Discord.ApiClient.ModifyGuildAsync(this.Id, mdl.Name,
mdl.VerificationLevel, mdl.DefaultMessageNotifications, mdl.MfaLevel, mdl.ExplicitContentFilter,
afkChannelId, mdl.AfkTimeout, iconb64, mdl.Owner.IfPresent(e => e.Id), splashb64,
systemChannelId, mdl.SystemChannelFlags, publicUpdatesChannelId, rulesChannelId,
description, bannerb64, discoverySplash64, mdl.PreferredLocale, mdl.PremiumProgressBarEnabled, mdl.AuditLogReason).ConfigureAwait(false);
}
///
/// Modifies the community settings async.
/// This sets if not highest and .
///
/// If true, enable .
/// The rules channel.
/// The public updates channel.
/// The preferred locale. Defaults to en-US.
/// The description.
/// The default message notifications. Defaults to
/// The auditlog reason.
/// Thrown when the client does not have the permission.
/// Thrown when the guild does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public async Task ModifyCommunitySettingsAsync(bool enabled, DiscordChannel rulesChannel = null, DiscordChannel publicUpdatesChannel = null, string preferredLocale = "en-US", string description = null, DefaultMessageNotifications defaultMessageNotifications = DefaultMessageNotifications.MentionsOnly, string reason = null)
{
var verificationLevel = this.VerificationLevel;
if(this.VerificationLevel != VerificationLevel.Highest)
{
verificationLevel = VerificationLevel.High;
}
var explicitContentFilter = ExplicitContentFilter.AllMembers;
var rulesChannelId = Optional.FromNoValue();
if (rulesChannel != null && rulesChannel.Type != ChannelType.Text && rulesChannel.Type != ChannelType.News)
throw new ArgumentException("Rules channel needs to be a text channel.");
else if (rulesChannel != null)
rulesChannelId = rulesChannel.Id;
else if (rulesChannel == null)
rulesChannelId = null;
var publicUpdatesChannelId = Optional.FromNoValue();
if (publicUpdatesChannel != null && publicUpdatesChannel.Type != ChannelType.Text && publicUpdatesChannel.Type != ChannelType.News)
throw new ArgumentException("Public updates channel needs to be a text channel.");
else if (publicUpdatesChannel != null)
publicUpdatesChannelId = publicUpdatesChannel.Id;
else if (publicUpdatesChannel == null)
publicUpdatesChannelId = null;
List features = new();
var rfeatures = this.RawFeatures.ToList();
if (this.RawFeatures.Contains("COMMUNITY") && enabled)
{
features = rfeatures;
} else if(!this.RawFeatures.Contains("COMMUNITY") && enabled)
{
rfeatures.Add("COMMUNITY");
features = rfeatures;
}
else if (this.RawFeatures.Contains("COMMUNITY") && !enabled)
{
rfeatures.Remove("COMMUNITY");
features = rfeatures;
} else if(!this.RawFeatures.Contains("COMMUNITY") && !enabled)
{
features = rfeatures;
}
return await this.Discord.ApiClient.ModifyGuildCommunitySettingsAsync(this.Id, features, rulesChannelId, publicUpdatesChannelId, preferredLocale, description, defaultMessageNotifications, explicitContentFilter, verificationLevel, reason).ConfigureAwait(false);
}
///
/// Bans a specified member from this guild.
///
/// Member to ban.
/// How many days to remove messages from.
/// Reason for audit logs.
///
/// 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 BanMemberAsync(DiscordMember member, int delete_message_days = 0, string reason = null)
=> this.Discord.ApiClient.CreateGuildBanAsync(this.Id, member.Id, delete_message_days, reason);
///
/// Bans a specified user by ID. This doesn't require the user to be in this guild.
///
/// ID of the user to ban.
/// How many days to remove messages from.
/// Reason for audit logs.
///
/// 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 BanMemberAsync(ulong user_id, int delete_message_days = 0, string reason = null)
=> this.Discord.ApiClient.CreateGuildBanAsync(this.Id, user_id, delete_message_days, reason);
///
/// Unbans a user from this guild.
///
/// User to unban.
/// Reason for audit logs.
///
/// Thrown when the client does not have the permission.
/// Thrown when the user does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public Task UnbanMemberAsync(DiscordUser user, string reason = null)
=> this.Discord.ApiClient.RemoveGuildBanAsync(this.Id, user.Id, reason);
///
/// Unbans a user by ID.
///
/// ID of the user to unban.
/// Reason for audit logs.
///
/// Thrown when the client does not have the permission.
/// Thrown when the user does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public Task UnbanMemberAsync(ulong user_id, string reason = null)
=> this.Discord.ApiClient.RemoveGuildBanAsync(this.Id, user_id, reason);
///
/// Leaves this guild.
///
///
/// Thrown when Discord is unable to process the request.
public Task LeaveAsync()
=> this.Discord.ApiClient.LeaveGuildAsync(this.Id);
///
/// Gets the bans for this guild.
///
/// Collection of bans in this guild.
/// Thrown when the client does not have the permission.
/// Thrown when Discord is unable to process the request.
public Task> GetBansAsync()
=> this.Discord.ApiClient.GetGuildBansAsync(this.Id);
///
/// Gets a ban for a specific user.
///
/// The Id of the user to get the ban for.
/// Thrown when the specified user is not banned.
/// The requested ban object.
public Task GetBanAsync(ulong userId)
=> this.Discord.ApiClient.GetGuildBanAsync(this.Id, userId);
///
/// Gets a ban for a specific user.
///
/// The user to get the ban for.
/// Thrown when the specified user is not banned.
/// The requested ban object.
public Task GetBanAsync(DiscordUser user)
=> this.GetBanAsync(user.Id);
#region Sheduled Events
///
/// Creates a scheduled event.
///
/// The name.
/// The scheduled start time.
/// The scheduled end time.
/// The channel.
/// The metadata.
/// The description.
/// The type.
/// The reason.
/// A scheduled event.
/// Thrown when the guild does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public async Task CreateScheduledEventAsync(string name, DateTimeOffset scheduledStartTime, DateTimeOffset? scheduledEndTime = null, DiscordChannel channel = null, DiscordScheduledEventEntityMetadata metadata = null, string description = null, ScheduledEventEntityType type = ScheduledEventEntityType.StageInstance, string reason = null)
=> await this.Discord.ApiClient.CreateGuildScheduledEventAsync(this.Id, type == ScheduledEventEntityType.External ? null : channel?.Id, type == ScheduledEventEntityType.External ? metadata : null, name, scheduledStartTime, scheduledEndTime.HasValue && type == ScheduledEventEntityType.External ? scheduledEndTime.Value : null, description, type, reason);
///
/// Creates a scheduled event with type .
///
/// The name.
/// The scheduled start time.
/// The scheduled end time.
/// The location of the external event.
/// The description.
/// The reason.
/// A scheduled event.
/// Thrown when the guild does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public async Task CreateExternalScheduledEventAsync(string name, DateTimeOffset scheduledStartTime, DateTimeOffset scheduledEndTime, string location, string description = null, string reason = null)
=> await this.Discord.ApiClient.CreateGuildScheduledEventAsync(this.Id, null, new DiscordScheduledEventEntityMetadata(location), name, scheduledStartTime, scheduledEndTime, description, ScheduledEventEntityType.External, reason);
///
/// Gets a specific scheduled events.
///
/// The Id of the event to get.
/// Whether to include user count.
/// A scheduled event.
/// Thrown when the guild does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public async Task GetScheduledEventAsync(ulong scheduledEventId, bool? withUserCount = null)
=> this._scheduledEvents.TryGetValue(scheduledEventId, out var ev) ? ev : await this.Discord.ApiClient.GetGuildScheduledEventAsync(this.Id, scheduledEventId, withUserCount);
///
/// Gets a specific scheduled events.
///
/// The event to get.
/// Whether to include user count.
/// A sheduled event.
/// Thrown when the guild does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public async Task GetScheduledEventAsync(DiscordScheduledEvent scheduledEvent, bool? withUserCount = null)
=> await this.GetScheduledEventAsync(scheduledEvent.Id, withUserCount);
///
/// Gets the guilds scheduled events.
///
/// Whether to include user count.
/// A list of the guilds scheduled events.
/// Thrown when the guild does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public async Task> GetScheduledEventsAsync(bool? withUserCount = null)
=> await this.Discord.ApiClient.ListGuildScheduledEventsAsync(this.Id, withUserCount);
#endregion
///
/// Creates a new text channel in this guild.
///
/// Name of the new channel.
/// Category to put this channel in.
/// Topic of the channel.
/// Permission overwrites for this channel.
/// Whether the channel is to be flagged as not safe for work.
/// Reason for audit logs.
/// Slow mode timeout for users.
/// The newly-created channel.
/// Thrown when the client does not have the permission.
/// Thrown when the guild does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public Task CreateTextChannelAsync(string name, DiscordChannel parent = null, Optional topic = default, IEnumerable overwrites = null, bool? nsfw = null, Optional perUserRateLimit = default, string reason = null)
=> this.CreateChannelAsync(name, ChannelType.Text, parent, topic, null, null, overwrites, nsfw, perUserRateLimit, null, reason);
///
/// Creates a new channel category in this guild.
///
/// Name of the new category.
/// Permission overwrites for this category.
/// Reason for audit logs.
/// The newly-created channel category.
/// Thrown when the client does not have the permission.
/// Thrown when the guild does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public Task CreateChannelCategoryAsync(string name, IEnumerable overwrites = null, string reason = null)
=> this.CreateChannelAsync(name, ChannelType.Category, null, Optional.FromNoValue(), null, null, overwrites, null, Optional.FromNoValue(), null, reason);
///
/// Creates a new stage channel in this guild.
///
/// Name of the new stage channel.
/// Permission overwrites for this stage channel.
/// Reason for audit logs.
/// The newly-created stage channel.
/// Thrown when the client does not have the .
/// Thrown when the guild does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
/// Thrown when the guilds has not enabled community.
public Task CreateStageChannelAsync(string name, IEnumerable overwrites = null, string reason = null)
=> this.Features.HasCommunityEnabled ? this.CreateChannelAsync(name, ChannelType.Stage, null, Optional.FromNoValue(), null, null, overwrites, null, Optional.FromNoValue(), null, reason) : throw new NotSupportedException("Guild has not enabled community. Can not create a stage channel.");
///
/// Creates a new news channel in this guild.
///
/// Name of the new stage channel.
/// Permission overwrites for this news channel.
/// Reason for audit logs.
/// The newly-created news channel.
/// Thrown when the client does not have the .
/// Thrown when the guild does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
/// Thrown when the guilds has not enabled community.
public Task CreateNewsChannelAsync(string name, IEnumerable overwrites = null, string reason = null)
=> this.Features.HasCommunityEnabled ? this.CreateChannelAsync(name, ChannelType.News, null, Optional.FromNoValue(), null, null, overwrites, null, Optional.FromNoValue(), null, reason) : throw new NotSupportedException("Guild has not enabled community. Can not create a news channel.");
///
/// Creates a new voice channel in this guild.
///
/// Name of the new channel.
/// Category to put this channel in.
/// Bitrate of the channel.
/// Maximum number of users in the channel.
/// Permission overwrites for this channel.
/// Video quality mode of the channel.
/// Reason for audit logs.
/// The newly-created channel.
/// Thrown when the client does not have the permission.
/// Thrown when the guild does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public Task CreateVoiceChannelAsync(string name, DiscordChannel parent = null, int? bitrate = null, int? user_limit = null, IEnumerable overwrites = null, VideoQualityMode? qualityMode = null, string reason = null)
=> this.CreateChannelAsync(name, ChannelType.Voice, parent, Optional.FromNoValue(), bitrate, user_limit, overwrites, null, Optional.FromNoValue(), qualityMode, reason);
///
/// Creates a new channel in this guild.
///
/// Name of the new channel.
/// Type of the new channel.
/// Category to put this channel in.
/// Topic of the channel.
/// Bitrate of the channel. Applies to voice only.
/// Maximum number of users in the channel. Applies to voice only.
/// Permission overwrites for this channel.
/// Whether the channel is to be flagged as not safe for work. Applies to text only.
/// Slow mode timeout for users.
/// Video quality mode of the channel. Applies to voice only.
/// Reason for audit logs.
/// The newly-created channel.
/// Thrown when the client does not have the permission.
/// Thrown when the guild does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public Task CreateChannelAsync(string name, ChannelType type, DiscordChannel parent = null, Optional topic = default, int? bitrate = null, int? userLimit = null, IEnumerable overwrites = null, bool? nsfw = null, Optional perUserRateLimit = default, VideoQualityMode? qualityMode = null, string reason = null)
{
// technically you can create news/store channels but not always
return type != ChannelType.Text && type != ChannelType.Voice && type != ChannelType.Category && type != ChannelType.News && type != ChannelType.Store && type != ChannelType.Stage
? throw new ArgumentException("Channel type must be text, voice, stage, or category.", nameof(type))
: type == ChannelType.Category && parent != null
? throw new ArgumentException("Cannot specify parent of a channel category.", nameof(parent))
: this.Discord.ApiClient.CreateGuildChannelAsync(this.Id, name, type, parent?.Id, topic, bitrate, userLimit, overwrites, nsfw, perUserRateLimit, qualityMode, reason);
}
///
/// Gets active threads. Can contain more threads.
/// If the result's value 'HasMore' is true, you need to recall this function to get older threads.
///
/// Thrown when the thread does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public Task GetActiveThreadsAsync()
=> this.Discord.ApiClient.GetActiveThreadsAsync(this.Id);
// this is to commemorate the Great DAPI Channel Massacre of 2017-11-19.
///
/// Deletes all channels in this guild.
/// Note that this is irreversible. Use carefully!
///
///
public Task DeleteAllChannelsAsync()
{
var tasks = this.Channels.Values.Select(xc => xc.DeleteAsync());
return Task.WhenAll(tasks);
}
///
/// Estimates the number of users to be pruned.
///
/// Minimum number of inactivity days required for users to be pruned. Defaults to 7.
/// The roles to be included in the prune.
/// Number of users that will be pruned.
/// Thrown when the client does not have the permission.
/// Thrown when the guild does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public Task GetPruneCountAsync(int days = 7, IEnumerable includedRoles = null)
{
if (includedRoles != null)
{
includedRoles = includedRoles.Where(r => r != null);
var roleCount = includedRoles.Count();
var roleArr = includedRoles.ToArray();
var rawRoleIds = new List();
for (var i = 0; i < roleCount; i++)
{
if (this._roles.ContainsKey(roleArr[i].Id))
rawRoleIds.Add(roleArr[i].Id);
}
return this.Discord.ApiClient.GetGuildPruneCountAsync(this.Id, days, rawRoleIds);
}
return this.Discord.ApiClient.GetGuildPruneCountAsync(this.Id, days, null);
}
///
/// Prunes inactive users from this guild.
///
/// Minimum number of inactivity days required for users to be pruned. Defaults to 7.
/// Whether to return the prune count after this method completes. This is discouraged for larger guilds.
/// The roles to be included in the prune.
/// Reason for audit logs.
/// Number of users pruned.
/// Thrown when the client does not have the permission.
/// Thrown when the guild does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public Task PruneAsync(int days = 7, bool computePruneCount = true, IEnumerable includedRoles = null, string reason = null)
{
if (includedRoles != null)
{
includedRoles = includedRoles.Where(r => r != null);
var roleCount = includedRoles.Count();
var roleArr = includedRoles.ToArray();
var rawRoleIds = new List();
for (var i = 0; i < roleCount; i++)
{
if (this._roles.ContainsKey(roleArr[i].Id))
rawRoleIds.Add(roleArr[i].Id);
}
return this.Discord.ApiClient.BeginGuildPruneAsync(this.Id, days, computePruneCount, rawRoleIds, reason);
}
return this.Discord.ApiClient.BeginGuildPruneAsync(this.Id, days, computePruneCount, null, reason);
}
///
/// Gets integrations attached to this guild.
///
/// Collection of integrations attached to this guild.
/// Thrown when the client does not have the permission.
/// Thrown when the guild does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public Task> GetIntegrationsAsync()
=> this.Discord.ApiClient.GetGuildIntegrationsAsync(this.Id);
///
/// Attaches an integration from current user to this guild.
///
/// Integration to attach.
/// The integration after being attached to the guild.
/// Thrown when the client does not have the permission.
/// Thrown when the guild does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public Task AttachUserIntegrationAsync(DiscordIntegration integration)
=> this.Discord.ApiClient.CreateGuildIntegrationAsync(this.Id, integration.Type, integration.Id);
///
/// Modifies an integration in this guild.
///
/// Integration to modify.
/// Number of days after which the integration expires.
/// Length of grace period which allows for renewing the integration.
/// Whether emotes should be synced from this integration.
/// The modified integration.
/// Thrown when the client does not have the permission.
/// Thrown when the guild does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public Task ModifyIntegrationAsync(DiscordIntegration integration, int expire_behaviour, int expire_grace_period, bool enable_emoticons)
=> this.Discord.ApiClient.ModifyGuildIntegrationAsync(this.Id, integration.Id, expire_behaviour, expire_grace_period, enable_emoticons);
///
/// Removes an integration from this guild.
///
/// Integration to remove.
///
/// Thrown when the client does not have the permission.
/// Thrown when the guild does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public Task DeleteIntegrationAsync(DiscordIntegration integration)
=> this.Discord.ApiClient.DeleteGuildIntegrationAsync(this.Id, integration);
///
/// Forces re-synchronization of an integration for this guild.
///
/// Integration to synchronize.
///
/// Thrown when the client does not have the permission.
/// Thrown when the guild does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public Task SyncIntegrationAsync(DiscordIntegration integration)
=> this.Discord.ApiClient.SyncGuildIntegrationAsync(this.Id, integration.Id);
///
/// Gets the voice regions for this guild.
///
/// Voice regions available for this guild.
/// Thrown when Discord is unable to process the request.
public async Task> ListVoiceRegionsAsync()
{
var vrs = await this.Discord.ApiClient.GetGuildVoiceRegionsAsync(this.Id).ConfigureAwait(false);
foreach (var xvr in vrs)
this.Discord.InternalVoiceRegions.TryAdd(xvr.Id, xvr);
return vrs;
}
///
/// Gets an invite from this guild from an invite code.
///
/// The invite code
/// An invite, or null if not in cache.
public DiscordInvite GetInvite(string code)
=> this._invites.TryGetValue(code, out var invite) ? invite : null;
///
/// Gets all the invites created for all the channels in this guild.
///
/// A collection of invites.
/// Thrown when Discord is unable to process the request.
public async Task> GetInvitesAsync()
{
var res = await this.Discord.ApiClient.GetGuildInvitesAsync(this.Id).ConfigureAwait(false);
var intents = this.Discord.Configuration.Intents;
if (!intents.HasIntent(DiscordIntents.GuildInvites))
{
for (var i = 0; i < res.Count; i++)
this._invites[res[i].Code] = res[i];
}
return res;
}
///
/// Gets the vanity invite for this guild.
///
/// A partial vanity invite.
/// Thrown when the client does not have the permission.
/// Thrown when Discord is unable to process the request.
public Task GetVanityInviteAsync()
=> this.Discord.ApiClient.GetGuildVanityUrlAsync(this.Id);
///
/// Gets all the webhooks created for all the channels in this guild.
///
/// A collection of webhooks this guild has.
/// Thrown when the client does not have the permission.
/// Thrown when Discord is unable to process the request.
public Task> GetWebhooksAsync()
=> this.Discord.ApiClient.GetGuildWebhooksAsync(this.Id);
///
/// Gets this guild's widget image.
///
/// The format of the widget.
/// The URL of the widget image.
public string GetWidgetImage(WidgetType bannerType = WidgetType.Shield)
{
var param = bannerType switch
{
WidgetType.Banner1 => "banner1",
WidgetType.Banner2 => "banner2",
WidgetType.Banner3 => "banner3",
WidgetType.Banner4 => "banner4",
_ => "shield",
};
return $"{Endpoints.BASE_URI}{Endpoints.GUILDS}/{this.Id}{Endpoints.WIDGET_PNG}?style={param}";
}
///
/// Gets a member of this guild by their user ID.
///
/// ID of the member to get.
/// The requested member.
/// Thrown when Discord is unable to process the request.
public async Task GetMemberAsync(ulong userId)
{
if (this._members != null && this._members.TryGetValue(userId, out var mbr))
return mbr;
mbr = await this.Discord.ApiClient.GetGuildMemberAsync(this.Id, userId).ConfigureAwait(false);
var intents = this.Discord.Configuration.Intents;
if (intents.HasIntent(DiscordIntents.GuildMembers))
{
if (this._members != null)
{
this._members[userId] = mbr;
}
}
return mbr;
}
///
/// Retrieves a full list of members from Discord. This method will bypass cache.
///
/// A collection of all members in this guild.
/// Thrown when Discord is unable to process the request.
public async Task> GetAllMembersAsync()
{
var recmbr = new HashSet();
var recd = 1000;
var last = 0ul;
while (recd > 0)
{
var tms = await this.Discord.ApiClient.ListGuildMembersAsync(this.Id, 1000, last == 0 ? null : (ulong?)last).ConfigureAwait(false);
recd = tms.Count;
foreach (var xtm in tms)
{
var usr = new DiscordUser(xtm.User) { Discord = this.Discord };
usr = this.Discord.UserCache.AddOrUpdate(xtm.User.Id, usr, (id, old) =>
{
old.Username = usr.Username;
old.Discord = usr.Discord;
old.AvatarHash = usr.AvatarHash;
return old;
});
recmbr.Add(new DiscordMember(xtm) { Discord = this.Discord, _guild_id = this.Id });
}
var tm = tms.LastOrDefault();
last = tm?.User.Id ?? 0;
}
return new ReadOnlySet(recmbr);
}
///
/// Requests that Discord send a list of guild members based on the specified arguments. This method will fire the event.
/// If no arguments aside from and are specified, this will request all guild members.
///
/// Filters the returned members based on what the username starts with. Either this or must not be null.
/// The must also be greater than 0 if this is specified.
/// Total number of members to request. This must be greater than 0 if is specified.
/// Whether to include the associated with the fetched members.
/// Whether to limit the request to the specified user ids. Either this or must not be null.
/// The unique string to identify the response.
public async Task RequestMembersAsync(string query = "", int limit = 0, bool? presences = null, IEnumerable userIds = null, string nonce = null)
{
if (this.Discord is not DiscordClient client)
throw new InvalidOperationException("This operation is only valid for regular Discord clients.");
if (query == null && userIds == null)
throw new ArgumentException("The query and user IDs cannot both be null.");
if (query != null && userIds != null)
query = null;
var grgm = new GatewayRequestGuildMembers(this)
{
Query = query,
Limit = limit >= 0 ? limit : 0,
Presences = presences,
UserIds = userIds,
Nonce = nonce
};
var payload = new GatewayPayload
{
OpCode = GatewayOpCode.RequestGuildMembers,
Data = grgm
};
var payloadStr = JsonConvert.SerializeObject(payload, Formatting.None);
await client.WsSendAsync(payloadStr).ConfigureAwait(false);
}
///
/// Gets all the channels this guild has.
///
/// A collection of this guild's channels.
/// Thrown when Discord is unable to process the request.
public Task> GetChannelsAsync()
=> this.Discord.ApiClient.GetGuildChannelsAsync(this.Id);
///
/// Creates a new role in this guild.
///
/// Name of the role.
/// Permissions for the role.
/// Color for the role.
/// Whether the role is to be hoisted.
/// Whether the role is to be mentionable.
/// Reason for audit logs.
/// The newly-created role.
/// Thrown when the client does not have the permission.
/// Thrown when Discord is unable to process the request.
public Task CreateRoleAsync(string name = null, Permissions? permissions = null, DiscordColor? color = null, bool? hoist = null, bool? mentionable = null, string reason = null)
=> this.Discord.ApiClient.CreateGuildRoleAsync(this.Id, name, permissions, color?.Value, hoist, mentionable, reason);
///
/// Gets a role from this guild by its ID.
///
/// ID of the role to get.
/// Requested role.
/// Thrown when Discord is unable to process the request.
public DiscordRole GetRole(ulong id)
=> this._roles.TryGetValue(id, out var role) ? role : null;
///
/// Gets a channel from this guild by its ID.
///
/// ID of the channel to get.
/// Requested channel.
/// Thrown when Discord is unable to process the request.
public DiscordChannel GetChannel(ulong id)
=> (this._channels != null && this._channels.TryGetValue(id, out var channel)) ? channel : null;
///
/// Gets a thread from this guild by its ID.
///
/// ID of the thread to get.
/// Requested thread.
/// Thrown when Discord is unable to process the request.
public DiscordThreadChannel GetThread(ulong id)
=> (this._threads != null && this._threads.TryGetValue(id, out var thread)) ? thread : null;
///
/// Gets audit log entries for this guild.
///
/// Maximum number of entries to fetch.
/// Filter by member responsible.
/// Filter by action type.
/// A collection of requested audit log entries.
/// Thrown when the client does not have the permission.
/// Thrown when Discord is unable to process the request.
public async Task> GetAuditLogsAsync(int? limit = null, DiscordMember by_member = null, AuditLogActionType? action_type = null)
{
var alrs = new List();
int ac = 1, tc = 0, rmn = 100;
var last = 0ul;
while (ac > 0)
{
rmn = limit != null ? limit.Value - tc : 100;
rmn = Math.Min(100, rmn);
if (rmn <= 0) break;
var alr = await this.Discord.ApiClient.GetAuditLogsAsync(this.Id, rmn, null, last == 0 ? null : (ulong?)last, by_member?.Id, (int?)action_type).ConfigureAwait(false);
ac = alr.Entries.Count();
tc += ac;
if (ac > 0)
{
last = alr.Entries.Last().Id;
alrs.Add(alr);
}
}
var amr = alrs.SelectMany(xa => xa.Users)
.GroupBy(xu => xu.Id)
.Select(xgu => xgu.First());
foreach (var xau in amr)
{
if (this.Discord.UserCache.ContainsKey(xau.Id))
continue;
var xtu = new TransportUser
{
Id = xau.Id,
Username = xau.Username,
Discriminator = xau.Discriminator,
AvatarHash = xau.AvatarHash
};
var xu = new DiscordUser(xtu) { Discord = this.Discord };
xu = this.Discord.UserCache.AddOrUpdate(xu.Id, xu, (id, old) =>
{
old.Username = xu.Username;
old.Discriminator = xu.Discriminator;
old.AvatarHash = xu.AvatarHash;
return old;
});
}
var ahr = alrs.SelectMany(xa => xa.Webhooks)
.GroupBy(xh => xh.Id)
.Select(xgh => xgh.First());
var ams = amr.Select(xau => (this._members != null && this._members.TryGetValue(xau.Id, out var member)) ? member : new DiscordMember { Discord = this.Discord, Id = xau.Id, _guild_id = this.Id });
var amd = ams.ToDictionary(xm => xm.Id, xm => xm);
Dictionary ahd = null;
if (ahr.Any())
{
var whr = await this.GetWebhooksAsync().ConfigureAwait(false);
var whs = whr.ToDictionary(xh => xh.Id, xh => xh);
var amh = ahr.Select(xah => whs.TryGetValue(xah.Id, out var webhook) ? webhook : new DiscordWebhook { Discord = this.Discord, Name = xah.Name, Id = xah.Id, AvatarHash = xah.AvatarHash, ChannelId = xah.ChannelId, GuildId = xah.GuildId, Token = xah.Token });
ahd = amh.ToDictionary(xh => xh.Id, xh => xh);
}
var acs = alrs.SelectMany(xa => xa.Entries).OrderByDescending(xa => xa.Id);
var entries = new List();
foreach (var xac in acs)
{
DiscordAuditLogEntry entry = null;
ulong t1, t2;
int t3, t4;
long t5, t6;
bool p1, p2;
switch (xac.ActionType)
{
case AuditLogActionType.GuildUpdate:
entry = new DiscordAuditLogGuildEntry
{
Target = this
};
var entrygld = entry as DiscordAuditLogGuildEntry;
foreach (var xc in xac.Changes)
{
switch (xc.Key.ToLowerInvariant())
{
case "name":
entrygld.NameChange = new PropertyChange
{
Before = xc.OldValueString,
After = xc.NewValueString
};
break;
case "owner_id":
entrygld.OwnerChange = new PropertyChange
{
Before = (this._members != null && this._members.TryGetValue(xc.OldValueUlong, out var oldMember)) ? oldMember : await this.GetMemberAsync(xc.OldValueUlong).ConfigureAwait(false),
After = (this._members != null && this._members.TryGetValue(xc.NewValueUlong, out var newMember)) ? newMember : await this.GetMemberAsync(xc.NewValueUlong).ConfigureAwait(false)
};
break;
case "icon_hash":
entrygld.IconChange = new PropertyChange
{
Before = xc.OldValueString != null ? $"{DiscordDomain.GetDomain(CoreDomain.DiscordCdn).Url}{Endpoints.ICONS}/{this.Id}/{xc.OldValueString}.webp" : null,
After = xc.OldValueString != null ? $"{DiscordDomain.GetDomain(CoreDomain.DiscordCdn).Url}{Endpoints.ICONS}/{this.Id}/{xc.NewValueString}.webp" : null
};
break;
case "verification_level":
entrygld.VerificationLevelChange = new PropertyChange
{
Before = (VerificationLevel)(long)xc.OldValue,
After = (VerificationLevel)(long)xc.NewValue
};
break;
case "afk_channel_id":
ulong.TryParse(xc.NewValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t1);
ulong.TryParse(xc.OldValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t2);
entrygld.AfkChannelChange = new PropertyChange
{
Before = this.GetChannel(t1) ?? new DiscordChannel { Id = t1, Discord = this.Discord, GuildId = this.Id },
After = this.GetChannel(t2) ?? new DiscordChannel { Id = t1, Discord = this.Discord, GuildId = this.Id }
};
break;
case "widget_channel_id":
ulong.TryParse(xc.NewValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t1);
ulong.TryParse(xc.OldValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t2);
entrygld.EmbedChannelChange = new PropertyChange
{
Before = this.GetChannel(t1) ?? new DiscordChannel { Id = t1, Discord = this.Discord, GuildId = this.Id },
After = this.GetChannel(t2) ?? new DiscordChannel { Id = t1, Discord = this.Discord, GuildId = this.Id }
};
break;
case "splash_hash":
entrygld.SplashChange = new PropertyChange
{
Before = xc.OldValueString != null ? $"{DiscordDomain.GetDomain(CoreDomain.DiscordCdn).Url}{Endpoints.SPLASHES}/{this.Id}/{xc.OldValueString}.webp?size=2048" : null,
After = xc.NewValueString != null ? $"{DiscordDomain.GetDomain(CoreDomain.DiscordCdn).Url}{Endpoints.SPLASHES}/{this.Id}/{xc.NewValueString}.webp?size=2048" : null
};
break;
case "default_message_notifications":
entrygld.NotificationSettingsChange = new PropertyChange
{
Before = (DefaultMessageNotifications)(long)xc.OldValue,
After = (DefaultMessageNotifications)(long)xc.NewValue
};
break;
case "system_channel_id":
ulong.TryParse(xc.NewValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t1);
ulong.TryParse(xc.OldValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t2);
entrygld.SystemChannelChange = new PropertyChange
{
Before = this.GetChannel(t1) ?? new DiscordChannel { Id = t1, Discord = this.Discord, GuildId = this.Id },
After = this.GetChannel(t2) ?? new DiscordChannel { Id = t1, Discord = this.Discord, GuildId = this.Id }
};
break;
case "explicit_content_filter":
entrygld.ExplicitContentFilterChange = new PropertyChange
{
Before = (ExplicitContentFilter)(long)xc.OldValue,
After = (ExplicitContentFilter)(long)xc.NewValue
};
break;
case "mfa_level":
entrygld.MfaLevelChange = new PropertyChange
{
Before = (MfaLevel)(long)xc.OldValue,
After = (MfaLevel)(long)xc.NewValue
};
break;
case "region":
entrygld.RegionChange = new PropertyChange
{
Before = xc.OldValueString,
After = xc.NewValueString
};
break;
case "premium_progress_bar_enabled":
entrygld.PremiumProgressBarChange = new PropertyChange
{
Before = (bool)xc.OldValue,
After = (bool)xc.NewValue
};
break;
default:
this.Discord.Logger.LogWarning(LoggerEvents.AuditLog, "Unknown key in guild update: {0} - this should be reported to library developers", xc.Key);
break;
}
}
break;
case AuditLogActionType.ChannelCreate:
case AuditLogActionType.ChannelDelete:
case AuditLogActionType.ChannelUpdate:
entry = new DiscordAuditLogChannelEntry
{
Target = this.GetChannel(xac.TargetId.Value) ?? new DiscordChannel { Id = xac.TargetId.Value, Discord = this.Discord, GuildId = this.Id }
};
var entrychn = entry as DiscordAuditLogChannelEntry;
foreach (var xc in xac.Changes)
{
switch (xc.Key.ToLowerInvariant())
{
case "name":
entrychn.NameChange = new PropertyChange
{
Before = xc.OldValue != null ? xc.OldValueString : null,
After = xc.NewValue != null ? xc.NewValueString : null
};
break;
case "type":
p1 = ulong.TryParse(xc.NewValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t1);
p2 = ulong.TryParse(xc.OldValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t2);
entrychn.TypeChange = new PropertyChange
{
Before = p1 ? (ChannelType?)t1 : null,
After = p2 ? (ChannelType?)t2 : null
};
break;
case "permission_overwrites":
var olds = xc.OldValues?.OfType()
?.Select(xjo => xjo.ToObject())
?.Select(xo => { xo.Discord = this.Discord; return xo; });
var news = xc.NewValues?.OfType()
?.Select(xjo => xjo.ToObject())
?.Select(xo => { xo.Discord = this.Discord; return xo; });
entrychn.OverwriteChange = new PropertyChange>
{
Before = olds != null ? new ReadOnlyCollection(new List(olds)) : null,
After = news != null ? new ReadOnlyCollection(new List(news)) : null
};
break;
case "topic":
entrychn.TopicChange = new PropertyChange
{
Before = xc.OldValueString,
After = xc.NewValueString
};
break;
case "nsfw":
entrychn.NsfwChange = new PropertyChange
{
Before = (bool?)xc.OldValue,
After = (bool?)xc.NewValue
};
break;
case "bitrate":
entrychn.BitrateChange = new PropertyChange
{
Before = (int?)(long?)xc.OldValue,
After = (int?)(long?)xc.NewValue
};
break;
case "rate_limit_per_user":
entrychn.PerUserRateLimitChange = new PropertyChange
{
Before = (int?)(long?)xc.OldValue,
After = (int?)(long?)xc.NewValue
};
break;
default:
this.Discord.Logger.LogWarning(LoggerEvents.AuditLog, "Unknown key in channel update: {0} - this should be reported to library developers", xc.Key);
break;
}
}
break;
case AuditLogActionType.OverwriteCreate:
case AuditLogActionType.OverwriteDelete:
case AuditLogActionType.OverwriteUpdate:
entry = new DiscordAuditLogOverwriteEntry
{
Target = this.GetChannel(xac.TargetId.Value)?.PermissionOverwrites.FirstOrDefault(xo => xo.Id == xac.Options.Id),
Channel = this.GetChannel(xac.TargetId.Value)
};
var entryovr = entry as DiscordAuditLogOverwriteEntry;
foreach (var xc in xac.Changes)
{
switch (xc.Key.ToLowerInvariant())
{
case "deny":
p1 = ulong.TryParse(xc.OldValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t1);
p2 = ulong.TryParse(xc.OldValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t2);
entryovr.DenyChange = new PropertyChange
{
Before = p1 ? (Permissions?)t1 : null,
After = p2 ? (Permissions?)t2 : null
};
break;
case "allow":
p1 = ulong.TryParse(xc.OldValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t1);
p2 = ulong.TryParse(xc.OldValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t2);
entryovr.AllowChange = new PropertyChange
{
Before = p1 ? (Permissions?)t1 : null,
After = p2 ? (Permissions?)t2 : null
};
break;
case "type":
entryovr.TypeChange = new PropertyChange
{
Before = xc.OldValueString,
After = xc.NewValueString
};
break;
case "id":
p1 = ulong.TryParse(xc.OldValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t1);
p2 = ulong.TryParse(xc.NewValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t2);
entryovr.TargetIdChange = new PropertyChange
{
Before = p1 ? (ulong?)t1 : null,
After = p2 ? (ulong?)t2 : null
};
break;
default:
this.Discord.Logger.LogWarning(LoggerEvents.AuditLog, "Unknown key in overwrite update: {0} - this should be reported to library developers", xc.Key);
break;
}
}
break;
case AuditLogActionType.Kick:
entry = new DiscordAuditLogKickEntry
{
Target = amd.TryGetValue(xac.TargetId.Value, out var kickMember) ? kickMember : new DiscordMember { Id = xac.TargetId.Value, Discord = this.Discord, _guild_id = this.Id }
};
break;
case AuditLogActionType.Prune:
entry = new DiscordAuditLogPruneEntry
{
Days = xac.Options.DeleteMemberDays,
Toll = xac.Options.MembersRemoved
};
break;
case AuditLogActionType.Ban:
case AuditLogActionType.Unban:
entry = new DiscordAuditLogBanEntry
{
Target = amd.TryGetValue(xac.TargetId.Value, out var unbanMember) ? unbanMember : new DiscordMember { Id = xac.TargetId.Value, Discord = this.Discord, _guild_id = this.Id }
};
break;
case AuditLogActionType.MemberUpdate:
case AuditLogActionType.MemberRoleUpdate:
entry = new DiscordAuditLogMemberUpdateEntry
{
Target = amd.TryGetValue(xac.TargetId.Value, out var roleUpdMember) ? roleUpdMember : new DiscordMember { Id = xac.TargetId.Value, Discord = this.Discord, _guild_id = this.Id }
};
var entrymbu = entry as DiscordAuditLogMemberUpdateEntry;
foreach (var xc in xac.Changes)
{
switch (xc.Key.ToLowerInvariant())
{
case "nick":
entrymbu.NicknameChange = new PropertyChange
{
Before = xc.OldValueString,
After = xc.NewValueString
};
break;
case "deaf":
entrymbu.DeafenChange = new PropertyChange
{
Before = (bool?)xc.OldValue,
After = (bool?)xc.NewValue
};
break;
case "mute":
entrymbu.MuteChange = new PropertyChange
{
Before = (bool?)xc.OldValue,
After = (bool?)xc.NewValue
};
break;
case "$add":
entrymbu.AddedRoles = new ReadOnlyCollection(xc.NewValues.Select(xo => (ulong)xo["id"]).Select(this.GetRole).ToList());
break;
case "$remove":
entrymbu.RemovedRoles = new ReadOnlyCollection(xc.NewValues.Select(xo => (ulong)xo["id"]).Select(this.GetRole).ToList());
break;
default:
this.Discord.Logger.LogWarning(LoggerEvents.AuditLog, "Unknown key in member update: {0} - this should be reported to library developers", xc.Key);
break;
}
}
break;
case AuditLogActionType.RoleCreate:
case AuditLogActionType.RoleDelete:
case AuditLogActionType.RoleUpdate:
entry = new DiscordAuditLogRoleUpdateEntry
{
Target = this.GetRole(xac.TargetId.Value) ?? new DiscordRole { Id = xac.TargetId.Value, Discord = this.Discord }
};
var entryrol = entry as DiscordAuditLogRoleUpdateEntry;
foreach (var xc in xac.Changes)
{
switch (xc.Key.ToLowerInvariant())
{
case "name":
entryrol.NameChange = new PropertyChange
{
Before = xc.OldValueString,
After = xc.NewValueString
};
break;
case "color":
p1 = int.TryParse(xc.OldValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t3);
p2 = int.TryParse(xc.NewValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t4);
entryrol.ColorChange = new PropertyChange
{
Before = p1 ? (int?)t3 : null,
After = p2 ? (int?)t4 : null
};
break;
case "permissions":
entryrol.PermissionChange = new PropertyChange
{
Before = xc.OldValue != null ? (Permissions?)long.Parse((string)xc.OldValue) : null,
After = xc.NewValue != null ? (Permissions?)long.Parse((string)xc.NewValue) : null
};
break;
case "position":
entryrol.PositionChange = new PropertyChange
{
Before = xc.OldValue != null ? (int?)(long)xc.OldValue : null,
After = xc.NewValue != null ? (int?)(long)xc.NewValue : null,
};
break;
case "mentionable":
entryrol.MentionableChange = new PropertyChange
{
Before = xc.OldValue != null ? (bool?)xc.OldValue : null,
After = xc.NewValue != null ? (bool?)xc.NewValue : null
};
break;
case "hoist":
entryrol.HoistChange = new PropertyChange
{
Before = (bool?)xc.OldValue,
After = (bool?)xc.NewValue
};
break;
default:
this.Discord.Logger.LogWarning(LoggerEvents.AuditLog, "Unknown key in role update: {0} - this should be reported to library developers", xc.Key);
break;
}
}
break;
case AuditLogActionType.InviteCreate:
case AuditLogActionType.InviteDelete:
case AuditLogActionType.InviteUpdate:
entry = new DiscordAuditLogInviteEntry();
var inv = new DiscordInvite
{
Discord = this.Discord,
Guild = new DiscordInviteGuild
{
Discord = this.Discord,
Id = this.Id,
Name = this.Name,
SplashHash = this.SplashHash
}
};
var entryinv = entry as DiscordAuditLogInviteEntry;
foreach (var xc in xac.Changes)
{
switch (xc.Key.ToLowerInvariant())
{
case "max_age":
p1 = int.TryParse(xc.OldValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t3);
p2 = int.TryParse(xc.OldValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t4);
entryinv.MaxAgeChange = new PropertyChange
{
Before = p1 ? (int?)t3 : null,
After = p2 ? (int?)t4 : null
};
break;
case "code":
inv.Code = xc.OldValueString ?? xc.NewValueString;
entryinv.CodeChange = new PropertyChange
{
Before = xc.OldValueString,
After = xc.NewValueString
};
break;
case "temporary":
entryinv.TemporaryChange = new PropertyChange
{
Before = xc.OldValue != null ? (bool?)xc.OldValue : null,
After = xc.NewValue != null ? (bool?)xc.NewValue : null
};
break;
case "inviter_id":
p1 = ulong.TryParse(xc.OldValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t1);
p2 = ulong.TryParse(xc.NewValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t2);
entryinv.InviterChange = new PropertyChange
{
Before = amd.TryGetValue(t1, out var propBeforeMember) ? propBeforeMember : new DiscordMember { Id = t1, Discord = this.Discord, _guild_id = this.Id },
After = amd.TryGetValue(t2, out var propAfterMember) ? propAfterMember : new DiscordMember { Id = t1, Discord = this.Discord, _guild_id = this.Id },
};
break;
case "channel_id":
p1 = ulong.TryParse(xc.OldValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t1);
p2 = ulong.TryParse(xc.NewValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t2);
entryinv.ChannelChange = new PropertyChange
{
Before = p1 ? this.GetChannel(t1) ?? new DiscordChannel { Id = t1, Discord = this.Discord, GuildId = this.Id } : null,
After = p2 ? this.GetChannel(t2) ?? new DiscordChannel { Id = t1, Discord = this.Discord, GuildId = this.Id } : null
};
var ch = entryinv.ChannelChange.Before ?? entryinv.ChannelChange.After;
var cht = ch?.Type;
inv.Channel = new DiscordInviteChannel
{
Discord = this.Discord,
Id = p1 ? t1 : t2,
Name = ch?.Name,
Type = cht != null ? cht.Value : ChannelType.Unknown
};
break;
case "uses":
p1 = int.TryParse(xc.OldValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t3);
p2 = int.TryParse(xc.OldValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t4);
entryinv.UsesChange = new PropertyChange
{
Before = p1 ? (int?)t3 : null,
After = p2 ? (int?)t4 : null
};
break;
case "max_uses":
p1 = int.TryParse(xc.OldValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t3);
p2 = int.TryParse(xc.OldValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t4);
entryinv.MaxUsesChange = new PropertyChange
{
Before = p1 ? (int?)t3 : null,
After = p2 ? (int?)t4 : null
};
break;
default:
this.Discord.Logger.LogWarning(LoggerEvents.AuditLog, "Unknown key in invite update: {0} - this should be reported to library developers", xc.Key);
break;
}
}
entryinv.Target = inv;
break;
case AuditLogActionType.WebhookCreate:
case AuditLogActionType.WebhookDelete:
case AuditLogActionType.WebhookUpdate:
entry = new DiscordAuditLogWebhookEntry
{
Target = ahd.TryGetValue(xac.TargetId.Value, out var webhook) ? webhook : new DiscordWebhook { Id = xac.TargetId.Value, Discord = this.Discord }
};
var entrywhk = entry as DiscordAuditLogWebhookEntry;
foreach (var xc in xac.Changes)
{
switch (xc.Key.ToLowerInvariant())
{
case "name":
entrywhk.NameChange = new PropertyChange
{
Before = xc.OldValueString,
After = xc.NewValueString
};
break;
case "channel_id":
p1 = ulong.TryParse(xc.OldValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t1);
p2 = ulong.TryParse(xc.NewValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t2);
entrywhk.ChannelChange = new PropertyChange
{
Before = p1 ? this.GetChannel(t1) ?? new DiscordChannel { Id = t1, Discord = this.Discord, GuildId = this.Id } : null,
After = p2 ? this.GetChannel(t2) ?? new DiscordChannel { Id = t1, Discord = this.Discord, GuildId = this.Id } : null
};
break;
case "type": // ???
p1 = int.TryParse(xc.OldValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t3);
p2 = int.TryParse(xc.NewValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t4);
entrywhk.TypeChange = new PropertyChange
{
Before = p1 ? (int?)t3 : null,
After = p2 ? (int?)t4 : null
};
break;
case "avatar_hash":
entrywhk.AvatarHashChange = new PropertyChange
{
Before = xc.OldValueString,
After = xc.NewValueString
};
break;
default:
this.Discord.Logger.LogWarning(LoggerEvents.AuditLog, "Unknown key in webhook update: {0} - this should be reported to library developers", xc.Key);
break;
}
}
break;
case AuditLogActionType.EmojiCreate:
case AuditLogActionType.EmojiDelete:
case AuditLogActionType.EmojiUpdate:
entry = new DiscordAuditLogEmojiEntry
{
Target = this._emojis.TryGetValue(xac.TargetId.Value, out var target) ? target : new DiscordEmoji { Id = xac.TargetId.Value, Discord = this.Discord }
};
var entryemo = entry as DiscordAuditLogEmojiEntry;
foreach (var xc in xac.Changes)
{
switch (xc.Key.ToLowerInvariant())
{
case "name":
entryemo.NameChange = new PropertyChange
{
Before = xc.OldValueString,
After = xc.NewValueString
};
break;
default:
this.Discord.Logger.LogWarning(LoggerEvents.AuditLog, "Unknown key in emote update: {0} - this should be reported to library developers", xc.Key);
break;
}
}
break;
case AuditLogActionType.StageInstanceCreate:
case AuditLogActionType.StageInstanceDelete:
case AuditLogActionType.StageInstanceUpdate:
entry = new DiscordAuditLogStageEntry
{
Target = this._stageInstances.TryGetValue(xac.TargetId.Value, out var stage) ? stage : new DiscordStageInstance { Id = xac.TargetId.Value, Discord = this.Discord }
};
var entrysta = entry as DiscordAuditLogStageEntry;
foreach (var xc in xac.Changes)
{
switch (xc.Key.ToLowerInvariant())
{
case "topic":
entrysta.TopicChange = new PropertyChange
{
Before = xc.OldValueString,
After = xc.NewValueString
};
break;
case "privacy_level":
entrysta.PrivacyLevelChange = new PropertyChange
{
Before = long.TryParse(xc.OldValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t5) ? (StagePrivacyLevel?)t5 : null,
After = long.TryParse(xc.NewValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t6) ? (StagePrivacyLevel?)t6 : null,
};
break;
default:
this.Discord.Logger.LogWarning(LoggerEvents.AuditLog, "Unknown key in stage instance update: {0} - this should be reported to library developers", xc.Key);
break;
}
}
break;
case AuditLogActionType.StickerCreate:
case AuditLogActionType.StickerDelete:
case AuditLogActionType.StickerUpdate:
entry = new DiscordAuditLogStickerEntry
{
Target = this._stickers.TryGetValue(xac.TargetId.Value, out var sticker) ? sticker : new DiscordSticker { Id = xac.TargetId.Value, Discord = this.Discord }
};
var entrysti = entry as DiscordAuditLogStickerEntry;
foreach (var xc in xac.Changes)
{
switch (xc.Key.ToLowerInvariant())
{
case "name":
entrysti.NameChange = new PropertyChange
{
Before = xc.OldValueString,
After = xc.NewValueString
};
break;
case "description":
entrysti.DescriptionChange = new PropertyChange
{
Before = xc.OldValueString,
After = xc.NewValueString
};
break;
case "tags":
entrysti.TagsChange = new PropertyChange
{
Before = xc.OldValueString,
After = xc.NewValueString
};
break;
case "guild_id":
entrysti.GuildIdChange = new PropertyChange
{
Before = ulong.TryParse(xc.OldValueString, out var ogid) ? ogid : null,
After = ulong.TryParse(xc.NewValueString, out var ngid) ? ngid : null
};
break;
case "available":
entrysti.AvailabilityChange = new PropertyChange
{
Before = (bool?)xc.OldValue,
After = (bool?)xc.NewValue,
};
break;
case "asset":
entrysti.AssetChange = new PropertyChange
{
Before = xc.OldValueString,
After = xc.NewValueString
};
break;
case "id":
entrysti.IdChange = new PropertyChange
{
Before = ulong.TryParse(xc.OldValueString, out var oid) ? oid : null,
After = ulong.TryParse(xc.NewValueString, out var nid) ? nid : null
};
break;
case "type":
p1 = long.TryParse(xc.OldValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t5);
p2 = long.TryParse(xc.NewValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t6);
entrysti.TypeChange = new PropertyChange
{
Before = p1 ? (StickerType?)t5 : null,
After = p2 ? (StickerType?)t6 : null
};
break;
case "format_type":
p1 = long.TryParse(xc.OldValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t5);
p2 = long.TryParse(xc.NewValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t6);
entrysti.FormatChange = new PropertyChange
{
Before = p1 ? (StickerFormat?)t5 : null,
After = p2 ? (StickerFormat?)t6 : null
};
break;
default:
this.Discord.Logger.LogWarning(LoggerEvents.AuditLog, "Unknown key in sticker update: {0} - this should be reported to library developers", xc.Key);
break;
}
}
break;
case AuditLogActionType.MessageDelete:
case AuditLogActionType.MessageBulkDelete:
{
entry = new DiscordAuditLogMessageEntry();
var entrymsg = entry as DiscordAuditLogMessageEntry;
if (xac.Options != null)
{
entrymsg.Channel = this.GetChannel(xac.Options.ChannelId) ?? new DiscordChannel { Id = xac.Options.ChannelId, Discord = this.Discord, GuildId = this.Id };
entrymsg.MessageCount = xac.Options.Count;
}
if (entrymsg.Channel != null)
{
entrymsg.Target = this.Discord is DiscordClient dc
&& dc.MessageCache != null
&& dc.MessageCache.TryGet(xm => xm.Id == xac.TargetId.Value && xm.ChannelId == entrymsg.Channel.Id, out var msg)
? msg
: new DiscordMessage { Discord = this.Discord, Id = xac.TargetId.Value };
}
break;
}
case AuditLogActionType.MessagePin:
case AuditLogActionType.MessageUnpin:
{
entry = new DiscordAuditLogMessagePinEntry();
var entrypin = entry as DiscordAuditLogMessagePinEntry;
if (this.Discord is not DiscordClient dc)
{
break;
}
if (xac.Options != null)
{
DiscordMessage message = default;
dc.MessageCache?.TryGet(x => x.Id == xac.Options.MessageId && x.ChannelId == xac.Options.ChannelId, out message);
entrypin.Channel = this.GetChannel(xac.Options.ChannelId) ?? new DiscordChannel { Id = xac.Options.ChannelId, Discord = this.Discord, GuildId = this.Id };
entrypin.Message = message ?? new DiscordMessage { Id = xac.Options.MessageId, Discord = this.Discord };
}
if (xac.TargetId.HasValue)
{
dc.UserCache.TryGetValue(xac.TargetId.Value, out var user);
entrypin.Target = user ?? new DiscordUser { Id = user.Id, Discord = this.Discord };
}
break;
}
case AuditLogActionType.BotAdd:
{
entry = new DiscordAuditLogBotAddEntry();
if (!(this.Discord is DiscordClient dc && xac.TargetId.HasValue))
{
break;
}
dc.UserCache.TryGetValue(xac.TargetId.Value, out var bot);
(entry as DiscordAuditLogBotAddEntry).TargetBot = bot ?? new DiscordUser { Id = xac.TargetId.Value, Discord = this.Discord };
break;
}
case AuditLogActionType.MemberMove:
entry = new DiscordAuditLogMemberMoveEntry();
if (xac.Options == null)
{
break;
}
var moveentry = entry as DiscordAuditLogMemberMoveEntry;
moveentry.UserCount = xac.Options.Count;
moveentry.Channel = this.GetChannel(xac.Options.ChannelId) ?? new DiscordChannel { Id = xac.Options.ChannelId, Discord = this.Discord, GuildId = this.Id };
break;
case AuditLogActionType.MemberDisconnect:
entry = new DiscordAuditLogMemberDisconnectEntry
{
UserCount = xac.Options?.Count ?? 0
};
break;
case AuditLogActionType.IntegrationCreate:
case AuditLogActionType.IntegrationDelete:
case AuditLogActionType.IntegrationUpdate:
entry = new DiscordAuditLogIntegrationEntry();
var integentry = entry as DiscordAuditLogIntegrationEntry;
foreach (var xc in xac.Changes)
{
switch (xc.Key.ToLowerInvariant())
{
case "enable_emoticons":
integentry.EnableEmoticons = new PropertyChange
{
Before = (bool?)xc.OldValue,
After = (bool?)xc.NewValue
};
break;
case "expire_behavior":
integentry.ExpireBehavior = new PropertyChange
{
Before = (int?)xc.OldValue,
After = (int?)xc.NewValue
};
break;
case "expire_grace_period":
integentry.ExpireBehavior = new PropertyChange
{
Before = (int?)xc.OldValue,
After = (int?)xc.NewValue
};
break;
default:
this.Discord.Logger.LogWarning(LoggerEvents.AuditLog, "Unknown key in integration update: {0} - this should be reported to library developers", xc.Key);
break;
}
}
break;
case AuditLogActionType.ThreadCreate:
case AuditLogActionType.ThreadDelete:
case AuditLogActionType.ThreadUpdate:
entry = new DiscordAuditLogThreadEntry
{
Target = this._threads.TryGetValue(xac.TargetId.Value, out var thread) ? thread : new DiscordThreadChannel { Id = xac.TargetId.Value, Discord = this.Discord }
};
var entrythr = entry as DiscordAuditLogThreadEntry;
foreach (var xc in xac.Changes)
{
switch (xc.Key.ToLowerInvariant())
{
case "name":
entrythr.NameChange = new PropertyChange
{
Before = xc.OldValue != null ? xc.OldValueString : null,
After = xc.NewValue != null ? xc.NewValueString : null
};
break;
case "type":
p1 = ulong.TryParse(xc.NewValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t1);
p2 = ulong.TryParse(xc.OldValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t2);
entrythr.TypeChange = new PropertyChange
{
Before = p1 ? (ChannelType?)t1 : null,
After = p2 ? (ChannelType?)t2 : null
};
break;
case "archived":
entrythr.ArchivedChange = new PropertyChange
{
Before = xc.OldValue != null ? (bool?)xc.OldValue : null,
After = xc.NewValue != null ? (bool?)xc.NewValue : null
};
break;
case "locked":
entrythr.LockedChange = new PropertyChange
{
Before = xc.OldValue != null ? (bool?)xc.OldValue : null,
After = xc.NewValue != null ? (bool?)xc.NewValue : null
};
break;
case "auto_archive_duration":
p1 = long.TryParse(xc.OldValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t5);
p2 = long.TryParse(xc.NewValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t6);
entrythr.AutoArchiveDurationChange = new PropertyChange
{
Before = p1 ? (ThreadAutoArchiveDuration?)t5 : null,
After = p2 ? (ThreadAutoArchiveDuration?)t6 : null
};
break;
case "rate_limit_per_user":
entrythr.PerUserRateLimitChange = new PropertyChange
{
Before = (int?)(long?)xc.OldValue,
After = (int?)(long?)xc.NewValue
};
break;
default:
this.Discord.Logger.LogWarning(LoggerEvents.AuditLog, "Unknown key in thread update: {0} - this should be reported to library developers", xc.Key);
break;
}
}
break;
case AuditLogActionType.GuildScheduledEventCreate:
case AuditLogActionType.GuildScheduledEventDelete:
case AuditLogActionType.GuildScheduledEventUpdate:
entry = new DiscordAuditLogGuildScheduledEventEntry
{
Target = this._scheduledEvents.TryGetValue(xac.TargetId.Value, out var scheduled_event) ? scheduled_event : new DiscordScheduledEvent { Id = xac.TargetId.Value, Discord = this.Discord }
};
var entryse = entry as DiscordAuditLogGuildScheduledEventEntry;
foreach (var xc in xac.Changes)
{
switch (xc.Key.ToLowerInvariant())
{
case "channel_id":
entryse.ChannelIdChange = new PropertyChange
{
Before = ulong.TryParse(xc.OldValueString, out var ogid) ? ogid : null,
After = ulong.TryParse(xc.NewValueString, out var ngid) ? ngid : null
};
break;
case "description":
entryse.DescriptionChange = new PropertyChange
{
Before = xc.OldValue != null ? xc.OldValueString : null,
After = xc.NewValue != null ? xc.NewValueString : null
};
break;
case "location":
entryse.LocationChange = new PropertyChange
{
Before = xc.OldValue != null ? xc.OldValueString : null,
After = xc.NewValue != null ? xc.NewValueString : null
};
break;
case "privacy_level":
p1 = long.TryParse(xc.OldValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t5);
p2 = long.TryParse(xc.NewValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t6);
entryse.PrivacyLevelChange = new PropertyChange
{
Before = p1 ? (ScheduledEventPrivacyLevel?)t5 : null,
After = p2 ? (ScheduledEventPrivacyLevel?)t6 : null
};
break;
case "entity_type":
p1 = long.TryParse(xc.OldValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t5);
p2 = long.TryParse(xc.NewValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t6);
entryse.EntityTypeChange = new PropertyChange
{
Before = p1 ? (ScheduledEventEntityType?)t5 : null,
After = p2 ? (ScheduledEventEntityType?)t6 : null
};
break;
case "status":
p1 = long.TryParse(xc.OldValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t5);
p2 = long.TryParse(xc.NewValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t6);
entryse.StatusChange = new PropertyChange