diff --git a/DisCatSharp/Entities/Guild/DiscordClient.AuditLog.cs b/DisCatSharp/Entities/Guild/DiscordClient.AuditLog.cs new file mode 100644 index 000000000..8e2917e4e --- /dev/null +++ b/DisCatSharp/Entities/Guild/DiscordClient.AuditLog.cs @@ -0,0 +1,1294 @@ +// This file is part of the DisCatSharp project, based off DSharpPlus. +// +// Copyright (c) 2021-2022 AITSYS +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Globalization; +using System.Linq; +using System.Threading.Tasks; + +using DisCatSharp.Enums; +using DisCatSharp.Net; +using DisCatSharp.Net.Abstractions; + +using Microsoft.Extensions.Logging; + +using Newtonsoft.Json.Linq; + +namespace DisCatSharp.Entities +{ + public partial class DiscordGuild { + // TODO: Rework audit logs! + /// + /// 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 byMember = null, AuditLogActionType? actionType = 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, byMember?.Id, (int?)actionType).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 atgse = alrs.SelectMany(xa => xa.ScheduledEvents) + .GroupBy(xse => xse.Id) + .Select(xgse => xgse.First()); + + var ath = alrs.SelectMany(xa => xa.Threads) + .GroupBy(xt => xt.Id) + .Select(xgt => xgt.First()); + + var aig = alrs.SelectMany(xa => xa.Integrations) + .GroupBy(xi => xi.Id) + .Select(xgi => xgi.First()); + + var ahr = alrs.SelectMany(xa => xa.Webhooks) + .GroupBy(xh => xh.Id) + .Select(xgh => xgh.First()); + + var ams = amr.Select(xau => this.MembersInternal != null && this.MembersInternal.TryGetValue(xau.Id, out var member) ? member : new DiscordMember { Discord = this.Discord, Id = xau.Id, GuildId = this.Id }); + var amd = ams.ToDictionary(xm => xm.Id, xm => xm); + +#pragma warning disable CS0219 + Dictionary dtc = null; + Dictionary di = null; + Dictionary dse = null; +#pragma warning restore + + 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.Invalid: + break; + + case AuditLogActionType.GuildUpdate: + entry = new DiscordAuditLogGuildEntry + { + Target = this + }; + + var entrygld = entry as DiscordAuditLogGuildEntry; + foreach (var xc in xac.Changes) + { + PropertyChange GetChannelChange() + { + ulong.TryParse(xc.NewValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t1); + ulong.TryParse(xc.OldValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t2); + + return 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 } + }; + } + + 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.MembersInternal != null && this.MembersInternal.TryGetValue(xc.OldValueUlong, out var oldMember) ? oldMember : await this.GetMemberAsync(xc.OldValueUlong).ConfigureAwait(false), + After = this.MembersInternal != null && this.MembersInternal.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": + entrygld.AfkChannelChange = GetChannelChange(); + break; + + case "system_channel_flags": + entrygld.SystemChannelFlagsChange = new PropertyChange() + { + Before = (SystemChannelFlags)(long)xc.OldValue, + After = (SystemChannelFlags)(long)xc.NewValue + }; + break; + + case "widget_channel_id": + entrygld.WidgetChannelChange = GetChannelChange(); + break; + + case "rules_channel_id": + entrygld.RulesChannelChange = GetChannelChange(); + break; + + case "public_updates_channel_id": + entrygld.PublicUpdatesChannelChange = GetChannelChange(); + 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": + entrygld.SystemChannelChange = GetChannelChange(); + 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 "vanity_url_code": + entrygld.VanityUrlCodeChange = 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.OldValueString, + After = xc.NewValueString + }; + 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 "rtc_region": + entrychn.RtcRegionIdChange = new PropertyChange + { + Before = xc.OldValueString, + After = xc.NewValueString + }; + break; + + case "bitrate": + entrychn.BitrateChange = new PropertyChange + { + Before = (int?)(long?)xc.OldValue, + After = (int?)(long?)xc.NewValue + }; + break; + + case "user_limit": + entrychn.UserLimitChange = 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; + + case "default_auto_archive_duration": + entrychn.DefaultAutoArchiveDurationChange = new PropertyChange + { + Before = (ThreadAutoArchiveDuration?)(long?)xc.OldValue, + After = (ThreadAutoArchiveDuration?)(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.OldValue != null ? (OverwriteType)(long)xc.OldValue : null, + After = xc.NewValue != null ? (OverwriteType)(long)xc.NewValue : null + }; + 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, GuildId = 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, GuildId = 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, GuildId = 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 "communication_disabled_until": + entrymbu.CommunicationDisabledUntilChange = new PropertyChange + { + Before = (DateTime?)xc.OldValue, + After = (DateTime?)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; + + case "icon_hash": + entryrol.IconHashChange = new PropertyChange + { + Before = xc.OldValueString, + After = xc.NewValueString + }; + 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, GuildId = this.Id }, + After = amd.TryGetValue(t2, out var propAfterMember) ? propAfterMember : new DiscordMember { Id = t1, Discord = this.Discord, GuildId = 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; + + // TODO: Add changes for target application + + 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 "application_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.IdChange = new PropertyChange + { + Before = p1 ? t1 : null, + After = p2 ? t2 : null + }; + break; + + 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.EmojisInternal.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.StageInstancesInternal.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.StickersInternal.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 "type": + integentry.Type = new PropertyChange() + { + Before = xc.OldValueString, + After = xc.NewValueString + }; + break; + 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.ThreadsInternal.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.OldValueString, + After = xc.NewValueString + }; + 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 "invitable": + entrythr.InvitableChange = 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.ScheduledEventsInternal.TryGetValue(xac.TargetId.Value, out var scheduledEvent) ? scheduledEvent : 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 "name": + entryse.NameChange = new PropertyChange + { + Before = xc.OldValueString, + After = xc.NewValueString + }; + break; + + case "description": + entryse.DescriptionChange = new PropertyChange + { + Before = xc.OldValueString, + After = xc.NewValueString + }; + break; + + case "location": + entryse.LocationChange = new PropertyChange + { + Before = xc.OldValueString, + After = xc.NewValueString + }; + 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 + { + Before = p1 ? (ScheduledEventStatus?)t5 : null, + After = p2 ? (ScheduledEventStatus?)t6 : null + }; + break; + + default: + this.Discord.Logger.LogWarning(LoggerEvents.AuditLog, "Unknown key in scheduled event update: {0} - this should be reported to library developers", xc.Key); + break; + } + } + break; + + default: + this.Discord.Logger.LogWarning(LoggerEvents.AuditLog, "Unknown audit log action type: {0} - this should be reported to library developers", (int)xac.ActionType); + break; + } + + if (entry == null) + continue; + + entry.ActionCategory = xac.ActionType switch + { + AuditLogActionType.ChannelCreate or AuditLogActionType.EmojiCreate or AuditLogActionType.InviteCreate or AuditLogActionType.OverwriteCreate or AuditLogActionType.RoleCreate or AuditLogActionType.WebhookCreate or AuditLogActionType.IntegrationCreate or AuditLogActionType.StickerCreate or AuditLogActionType.StageInstanceCreate or AuditLogActionType.ThreadCreate or AuditLogActionType.GuildScheduledEventCreate => AuditLogActionCategory.Create, + AuditLogActionType.ChannelDelete or AuditLogActionType.EmojiDelete or AuditLogActionType.InviteDelete or AuditLogActionType.MessageDelete or AuditLogActionType.MessageBulkDelete or AuditLogActionType.OverwriteDelete or AuditLogActionType.RoleDelete or AuditLogActionType.WebhookDelete or AuditLogActionType.IntegrationDelete or AuditLogActionType.StickerDelete or AuditLogActionType.StageInstanceDelete or AuditLogActionType.ThreadDelete or AuditLogActionType.GuildScheduledEventDelete => AuditLogActionCategory.Delete, + AuditLogActionType.ChannelUpdate or AuditLogActionType.EmojiUpdate or AuditLogActionType.InviteUpdate or AuditLogActionType.MemberRoleUpdate or AuditLogActionType.MemberUpdate or AuditLogActionType.OverwriteUpdate or AuditLogActionType.RoleUpdate or AuditLogActionType.WebhookUpdate or AuditLogActionType.IntegrationUpdate or AuditLogActionType.StickerUpdate or AuditLogActionType.StageInstanceUpdate or AuditLogActionType.ThreadUpdate or AuditLogActionType.GuildScheduledEventUpdate => AuditLogActionCategory.Update, + _ => AuditLogActionCategory.Other, + }; + entry.Discord = this.Discord; + entry.ActionType = xac.ActionType; + entry.Id = xac.Id; + entry.Reason = xac.Reason; + entry.UserResponsible = amd[xac.UserId]; + entries.Add(entry); + } + + return new ReadOnlyCollection(entries); + } + } +} diff --git a/DisCatSharp/Entities/Guild/DiscordGuild.Features.cs b/DisCatSharp/Entities/Guild/DiscordGuild.Features.cs new file mode 100644 index 000000000..097f8f918 --- /dev/null +++ b/DisCatSharp/Entities/Guild/DiscordGuild.Features.cs @@ -0,0 +1,296 @@ +// This file is part of the DisCatSharp project, based off DSharpPlus. +// +// Copyright (c) 2021-2022 AITSYS +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +using System.Linq; + +namespace DisCatSharp.Entities +{ + + /// + /// Represents the guild features. + /// + public class GuildFeatures + { + /// + /// Guild has access to set an animated guild icon. + /// + public bool CanSetAnimatedIcon { get; } + + /// + /// Guild has access to set a guild banner image. + /// + public bool CanSetBanner { get; } + + /// + /// Guild has access to use commerce features (i.e. create store channels) + /// + public bool CanCreateStoreChannels { get; } + + /// + /// Guild can enable Welcome Screen, Membership Screening, Stage Channels, News Channels and receives community updates. + /// Furthermore the guild can apply as a partner and for the discovery (if the prerequisites are given). + /// and is usable. + /// + public bool HasCommunityEnabled { get; } + + /// + /// Guild is able to be discovered in the discovery. + /// + public bool IsDiscoverable { get; } + + /// + /// Guild is able to be featured in the discovery. + /// + public bool IsFeatureable { get; } + + /// + /// Guild has access to set an invite splash background. + /// + public bool CanSetInviteSplash { get; } + + /// + /// Guild has enabled Membership Screening. + /// + public bool HasMembershipScreeningEnabled { get; } + + /// + /// Guild has access to create news channels. + /// is usable. + /// + public bool CanCreateNewsChannels { get; } + + /// + /// Guild is partnered. + /// + public bool IsPartnered { get; } + + /// + /// Guild has increased custom emoji slots. + /// + public bool CanUploadMoreEmojis { get; } + + /// + /// Guild can be previewed before joining via Membership Screening or the discovery. + /// + public bool HasPreviewEnabled { get; } + + /// + /// Guild has access to set a vanity URL. + /// + public bool CanSetVanityUrl { get; } + + /// + /// Guild is verified. + /// + public bool IsVerified { get; } + + /// + /// Guild has access to set 384kbps bitrate in voice (previously VIP voice servers). + /// + public bool CanAccessVipRegions { get; } + + /// + /// Guild has enabled the welcome screen. + /// + public bool HasWelcomeScreenEnabled { get; } + + /// + /// Guild has enabled ticketed events. + /// + public bool HasTicketedEventsEnabled { get; } + + /// + /// Guild has enabled monetization. + /// + public bool HasMonetizationEnabled { get; } + + /// + /// Guild has increased custom sticker slots. + /// + public bool CanUploadMoreStickers { get; } + + /// + /// Guild has access to the three day archive time for threads. + /// Needs Premium Tier 1 (). + /// + public bool CanSetThreadArchiveDurationThreeDays { get; } + + /// + /// Guild has access to the seven day archive time for threads. + /// Needs Premium Tier 2 (). + /// + public bool CanSetThreadArchiveDurationSevenDays { get; } + + /// + /// Guild has access to create private threads. + /// Needs Premium Tier 2 (). + /// + public bool CanCreatePrivateThreads { get; } + + /// + /// Guild is a hub. + /// is usable. + /// + public bool IsHub { get; } + + /// + /// Guild is in a hub. + /// https://github.com/discord/discord-api-docs/pull/3757/commits/4932d92c9d0c783861bc715bf7ebbabb15114e34 + /// + public bool HasDirectoryEntry { get; } + + /// + /// Guild is linked to a hub. + /// + public bool IsLinkedToHub { get; } + + /// + /// Guild has full access to threads. + /// Old Feature. + /// + public bool HasThreadTestingEnabled { get; } + + /// + /// Guild has access to threads. + /// + public bool HasThreadsEnabled { get; } + + /// + /// Guild can set role icons. + /// + public bool CanSetRoleIcons { get; } + + /// + /// Guild has the new thread permissions. + /// Old Feature. + /// + public bool HasNewThreadPermissions { get; } + + /// + /// Guild can set thread default auto archive duration. + /// Old Feature. + /// + public bool CanSetThreadDefaultAutoArchiveDuration { get; } + + /// + /// Guild has enabled role subsriptions. + /// + public bool HasRoleSubscriptionsEnabled { get; } + + /// + /// Guild role subsriptions as purchaseable. + /// + public bool RoleSubscriptionsIsAvaiableForPurchase { get; } + + /// + /// Guild has premium tier 3 override. + /// + public bool PremiumTierThreeOverride { get; } + + /// + /// Guild has access to text in voice. + /// Restricted to . + /// + public bool TextInVoiceEnabled { get; } + + /// + /// Guild can set an animated banner. + /// Needs Premium Tier 3 (). + /// + public bool CanSetAnimatedBanner { get; } + + /// + /// Guild can set an animated banner. + /// Needs Premium Tier 3 (). + /// + public bool CanSetChannelBanner { get; } + + /// + /// Allows members to customize their avatar, banner and bio for that server. + /// + public bool HasMemberProfiles { get; } + + /// + /// Guild is restricted to users with the badge. + /// + public bool IsStaffOnly { get; } + + /// + /// String of guild features. + /// + public string FeatureString { get; } + + /// + /// Checks the guild features and constructs a new object. + /// + /// Guild to check + public GuildFeatures(DiscordGuild guild) + { + this.CanSetAnimatedIcon = guild.RawFeatures.Contains("ANIMATED_ICON"); + this.CanSetAnimatedBanner = guild.RawFeatures.Contains("ANIMATED_BANNER"); + this.CanSetBanner = guild.RawFeatures.Contains("BANNER"); + this.CanSetChannelBanner = guild.RawFeatures.Contains("CHANNEL_BANNER"); + this.CanCreateStoreChannels = guild.RawFeatures.Contains("COMMERCE"); + this.HasCommunityEnabled = guild.RawFeatures.Contains("COMMUNITY"); + this.IsDiscoverable = !guild.RawFeatures.Contains("DISCOVERABLE_DISABLED") && guild.RawFeatures.Contains("DISCOVERABLE"); + this.IsFeatureable = guild.RawFeatures.Contains("FEATURABLE"); + this.CanSetInviteSplash = guild.RawFeatures.Contains("INVITE_SPLASH"); + this.HasMembershipScreeningEnabled = guild.RawFeatures.Contains("MEMBER_VERIFICATION_GATE_ENABLED"); + this.CanCreateNewsChannels = guild.RawFeatures.Contains("NEWS"); + this.IsPartnered = guild.RawFeatures.Contains("PARTNERED"); + this.CanUploadMoreEmojis = guild.RawFeatures.Contains("MORE_EMOJI"); + this.HasPreviewEnabled = guild.RawFeatures.Contains("PREVIEW_ENABLED"); + this.CanSetVanityUrl = guild.RawFeatures.Contains("VANITY_URL"); + this.IsVerified = guild.RawFeatures.Contains("VERIFIED"); + this.CanAccessVipRegions = guild.RawFeatures.Contains("VIP_REGIONS"); + this.HasWelcomeScreenEnabled = guild.RawFeatures.Contains("WELCOME_SCREEN_ENABLED"); + this.HasTicketedEventsEnabled = guild.RawFeatures.Contains("TICKETED_EVENTS_ENABLED"); + this.HasMonetizationEnabled = guild.RawFeatures.Contains("MONETIZATION_ENABLED"); + this.CanUploadMoreStickers = guild.RawFeatures.Contains("MORE_STICKERS"); + this.CanSetThreadArchiveDurationThreeDays = guild.RawFeatures.Contains("THREE_DAY_THREAD_ARCHIVE"); + this.CanSetThreadArchiveDurationSevenDays = guild.RawFeatures.Contains("SEVEN_DAY_THREAD_ARCHIVE"); + this.CanCreatePrivateThreads = guild.RawFeatures.Contains("PRIVATE_THREADS"); + this.IsHub = guild.RawFeatures.Contains("HUB"); + this.HasThreadTestingEnabled = guild.RawFeatures.Contains("THREADS_ENABLED_TESTING"); + this.HasThreadsEnabled = guild.RawFeatures.Contains("THREADS_ENABLED"); + this.CanSetRoleIcons = guild.RawFeatures.Contains("ROLE_ICONS"); + this.HasNewThreadPermissions = guild.RawFeatures.Contains("NEW_THREAD_PERMISSIONS"); + this.HasRoleSubscriptionsEnabled = guild.RawFeatures.Contains("ROLE_SUBSCRIPTIONS_ENABLED"); + this.PremiumTierThreeOverride = guild.RawFeatures.Contains("PREMIUM_TIER_3_OVERRIDE"); + this.CanSetThreadDefaultAutoArchiveDuration = guild.RawFeatures.Contains("THREAD_DEFAULT_AUTO_ARCHIVE_DURATION"); + this.TextInVoiceEnabled = guild.RawFeatures.Contains("TEXT_IN_VOICE_ENABLED"); + this.HasDirectoryEntry = guild.RawFeatures.Contains("HAS_DIRECTORY_ENTRY"); + this.IsLinkedToHub = guild.RawFeatures.Contains("LINKED_TO_HUB"); + this.HasMemberProfiles = guild.RawFeatures.Contains("MEMBER_PROFILES"); + this.IsStaffOnly = guild.RawFeatures.Contains("INTERNAL_EMPLOYEE_ONLY"); + this.RoleSubscriptionsIsAvaiableForPurchase = guild.RawFeatures.Contains("ROLE_SUBSCRIPTIONS_AVAILABLE_FOR_PURCHASE"); + + var features = guild.RawFeatures.Any() ? "" : "None"; + foreach (var feature in guild.RawFeatures) + { + features += feature + " "; + } + this.FeatureString = features; + + } + } +} diff --git a/DisCatSharp/Entities/Guild/DiscordGuild.cs b/DisCatSharp/Entities/Guild/DiscordGuild.cs index 9c55563e4..55b61c047 100644 --- a/DisCatSharp/Entities/Guild/DiscordGuild.cs +++ b/DisCatSharp/Entities/Guild/DiscordGuild.cs @@ -1,3607 +1,2082 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021-2022 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System; using System.Collections.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.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 + public partial 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, interactions 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; } /// /// Gets the guild's AFK voice channel. /// [JsonIgnore] public DiscordChannel AfkChannel => this.GetChannel(this.AfkChannelId); /// /// List of . /// Null if DisCatSharp.ApplicationCommands is not used or no guild commands are registered. /// [JsonIgnore] public ReadOnlyCollection RegisteredApplicationCommands => new(this.InternalRegisteredApplicationCommands); [JsonIgnore] internal List InternalRegisteredApplicationCommands { get; set; } = null; /// /// List of . /// Null if DisCatSharp.ApplicationCommands is not used or no guild commands or permissions are registered. /// [JsonIgnore] public ReadOnlyCollection GuildApplicationCommandPermissions => new(this.InternalGuildApplicationCommandPermissions); [JsonIgnore] internal List InternalGuildApplicationCommandPermissions { get; set; } = null; /// /// 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.RolesInternal); [JsonProperty("roles", NullValueHandling = NullValueHandling.Ignore)] [JsonConverter(typeof(SnowflakeArrayAsDictionaryJsonConverter))] internal ConcurrentDictionary RolesInternal; /// /// Gets a collection of this guild's stickers. /// [JsonIgnore] public IReadOnlyDictionary Stickers => new ReadOnlyConcurrentDictionary(this.StickersInternal); [JsonProperty("stickers", NullValueHandling = NullValueHandling.Ignore)] [JsonConverter(typeof(SnowflakeArrayAsDictionaryJsonConverter))] internal ConcurrentDictionary StickersInternal; /// /// Gets a collection of this guild's emojis. /// [JsonIgnore] public IReadOnlyDictionary Emojis => new ReadOnlyConcurrentDictionary(this.EmojisInternal); [JsonProperty("emojis", NullValueHandling = NullValueHandling.Ignore)] [JsonConverter(typeof(SnowflakeArrayAsDictionaryJsonConverter))] internal ConcurrentDictionary EmojisInternal; /// /// 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; } /// /// Gets the approximate number of members in this guild, when using and having withCounts 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 withCounts set to true. /// [JsonProperty("approximate_presence_count", NullValueHandling = NullValueHandling.Ignore)] public int? ApproximatePresenceCount { get; internal set; } /// /// 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.VoiceStatesInternal); [JsonProperty("voice_states", NullValueHandling = NullValueHandling.Ignore)] [JsonConverter(typeof(SnowflakeArrayAsDictionaryJsonConverter))] internal ConcurrentDictionary VoiceStatesInternal; /// /// Gets a dictionary of all the members that belong to this guild. The dictionary's key is the member ID. /// [JsonIgnore] public IReadOnlyDictionary Members => new ReadOnlyConcurrentDictionary(this.MembersInternal); [JsonProperty("members", NullValueHandling = NullValueHandling.Ignore)] [JsonConverter(typeof(SnowflakeArrayAsDictionaryJsonConverter))] internal ConcurrentDictionary MembersInternal; /// /// 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.ChannelsInternal); [JsonProperty("channels", NullValueHandling = NullValueHandling.Ignore)] [JsonConverter(typeof(SnowflakeArrayAsDictionaryJsonConverter))] internal ConcurrentDictionary ChannelsInternal; 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 ThreadsInternal = 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 StageInstancesInternal = 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 ScheduledEventsInternal = new(); /// /// Gets the guild member for current user. /// [JsonIgnore] public DiscordMember CurrentMember => this._currentMemberLazy.Value; [JsonIgnore] private readonly Lazy _currentMemberLazy; /// /// Gets the @everyone role for this guild. /// [JsonIgnore] public DiscordRole EveryoneRole => this.GetRole(this.Id); [JsonIgnore] internal bool IsOwnerInternal; /// /// Gets whether the current user is the guild's owner. /// [JsonProperty("owner", NullValueHandling = NullValueHandling.Ignore)] public bool IsOwner { get => this.IsOwnerInternal || this.OwnerId == this.Discord.CurrentUser.Id; internal set => this.IsOwnerInternal = 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 this guild's hub type, if applicable. /// [JsonProperty("hub_type", NullValueHandling = NullValueHandling.Ignore)] public HubType HubType { 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 rawChannels = this.ChannelsInternal.Values.ToList(); Dictionary> orderedChannels = new(); orderedChannels.Add(0, new List()); foreach (var channel in rawChannels.Where(c => c.Type == ChannelType.Category).OrderBy(c => c.Position)) { orderedChannels.Add(channel.Id, new List()); } foreach (var channel in rawChannels.Where(c => c.ParentId.HasValue && (c.Type == ChannelType.Text || c.Type == ChannelType.News)).OrderBy(c => c.Position)) { orderedChannels[channel.ParentId.Value].Add(channel); } foreach (var channel in rawChannels.Where(c => c.ParentId.HasValue && (c.Type == ChannelType.Voice || c.Type == ChannelType.Stage)).OrderBy(c => c.Position)) { orderedChannels[channel.ParentId.Value].Add(channel); } foreach (var channel in rawChannels.Where(c => !c.ParentId.HasValue && c.Type != ChannelType.Category && (c.Type == ChannelType.Text || c.Type == ChannelType.News)).OrderBy(c => c.Position)) { orderedChannels[0].Add(channel); } foreach (var channel in rawChannels.Where(c => !c.ParentId.HasValue && c.Type != ChannelType.Category && (c.Type == ChannelType.Voice || c.Type == ChannelType.Stage)).OrderBy(c => c.Position)) { orderedChannels[0].Add(channel); } return orderedChannels; } /// /// 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 rawChannels = await this.Discord.ApiClient.GetGuildChannelsAsync(this.Id); Dictionary> orderedChannels = new(); orderedChannels.Add(0, new List()); foreach (var channel in rawChannels.Where(c => c.Type == ChannelType.Category).OrderBy(c => c.Position)) { orderedChannels.Add(channel.Id, new List()); } foreach (var channel in rawChannels.Where(c => c.ParentId.HasValue && (c.Type == ChannelType.Text || c.Type == ChannelType.News)).OrderBy(c => c.Position)) { orderedChannels[channel.ParentId.Value].Add(channel); } foreach (var channel in rawChannels.Where(c => c.ParentId.HasValue && (c.Type == ChannelType.Voice || c.Type == ChannelType.Stage)).OrderBy(c => c.Position)) { orderedChannels[channel.ParentId.Value].Add(channel); } foreach (var channel in rawChannels.Where(c => !c.ParentId.HasValue && c.Type != ChannelType.Category && (c.Type == ChannelType.Text || c.Type == ChannelType.News)).OrderBy(c => c.Position)) { orderedChannels[0].Add(channel); } foreach (var channel in rawChannels.Where(c => !c.ParentId.HasValue && c.Type != ChannelType.Category && (c.Type == ChannelType.Voice || c.Type == ChannelType.Stage)).OrderBy(c => c.Position)) { orderedChannels[0].Add(channel); } return orderedChannels; } /// /// Whether it is synced. /// [JsonIgnore] internal bool IsSynced { get; set; } /// /// Initializes a new instance of the class. /// internal DiscordGuild() { this._currentMemberLazy = new Lazy(() => this.MembersInternal != null && this.MembersInternal.TryGetValue(this.Discord.CurrentUser.Id, out var member) ? member : null); this.Invites = new ConcurrentDictionary(); this.Threads = new ReadOnlyConcurrentDictionary(this.ThreadsInternal); this.StageInstances = new ReadOnlyConcurrentDictionary(this.StageInstancesInternal); this.ScheduledEvents = new ReadOnlyConcurrentDictionary(this.ScheduledEventsInternal); } #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 accessToken, string nickname = null, IEnumerable roles = null, bool muted = false, bool deaf = false) => this.Discord.ApiClient.AddGuildMemberAsync(this.Id, user.Id, accessToken, 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 = mdl.PublicUpdatesChannel .MapOrNull(c => c.Type != ChannelType.Voice ? throw new ArgumentException("AFK channel needs to be a text channel.") : c.Id); static Optional ChannelToId(Optional ch, string name) => ch.MapOrNull(c => c.Type != ChannelType.Text && c.Type != ChannelType.News ? throw new ArgumentException($"{name} channel needs to be a text channel.") : c.Id); var rulesChannelId = ChannelToId(mdl.RulesChannel, "Rules"); var publicUpdatesChannelId = ChannelToId(mdl.PublicUpdatesChannel, "Public updates"); var systemChannelId = ChannelToId(mdl.SystemChannel, "System"); var iconb64 = ImageTool.Base64FromStream(mdl.Icon); var splashb64 = ImageTool.Base64FromStream(mdl.Splash); var bannerb64 = ImageTool.Base64FromStream(mdl.Banner); var discoverySplash64 = ImageTool.Base64FromStream(mdl.DiscoverySplash); return await this.Discord.ApiClient.ModifyGuildAsync(this.Id, mdl.Name, mdl.VerificationLevel, mdl.DefaultMessageNotifications, mdl.MfaLevel, mdl.ExplicitContentFilter, afkChannelId, mdl.AfkTimeout, iconb64, mdl.Owner.Map(e => e.Id), splashb64, systemChannelId, mdl.SystemChannelFlags, publicUpdatesChannelId, rulesChannelId, mdl.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; static Optional ChannelToId(DiscordChannel ch, string name) => ch == null ? null : ch.Type != ChannelType.Text && ch.Type != ChannelType.News ? throw new ArgumentException($"{name} channel needs to be a text channel.") : ch.Id; var rulesChannelId = ChannelToId(rulesChannel, "Rules"); var publicUpdatesChannelId = ChannelToId(publicUpdatesChannel, "Public updates"); 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); } /// /// Timeout a specified member in this guild. /// /// Member to timeout. /// The datetime offset to time out the user. Up to 28 days. /// 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 TimeoutAsync(ulong memberId, DateTimeOffset until, string reason = null) => until.Subtract(DateTimeOffset.UtcNow).Days > 28 ? throw new ArgumentException("Timeout can not be longer than 28 days") : this.Discord.ApiClient.ModifyTimeoutAsync(this.Id, memberId, until, reason); /// /// Timeout a specified member in this guild. /// /// Member to timeout. /// The timespan to time out the user. Up to 28 days. /// 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 TimeoutAsync(ulong memberId, TimeSpan until, string reason = null) => this.TimeoutAsync(memberId, DateTimeOffset.UtcNow + until, reason); /// /// Timeout a specified member in this guild. /// /// Member to timeout. /// The datetime to time out the user. Up to 28 days. /// 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 TimeoutAsync(ulong memberId, DateTime until, string reason = null) => this.TimeoutAsync(memberId, until.ToUniversalTime() - DateTime.UtcNow, reason); /// /// Removes the timeout from a specified member in this guild. /// /// Member to remove the timeout 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 RemoveTimeoutAsync(ulong memberId, string reason = null) => this.Discord.ApiClient.ModifyTimeoutAsync(this.Id, memberId, null, reason); /// /// 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 deleteMessageDays = 0, string reason = null) => this.Discord.ApiClient.CreateGuildBanAsync(this.Id, member.Id, deleteMessageDays, 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 userId, int deleteMessageDays = 0, string reason = null) => this.Discord.ApiClient.CreateGuildBanAsync(this.Id, userId, deleteMessageDays, 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 userId, string reason = null) => this.Discord.ApiClient.RemoveGuildBanAsync(this.Id, userId, 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, allowing for pagination. /// /// Maximum number of bans to fetch. Max 1000. Defaults to 1000. /// The Id of the user before which to fetch the bans. Overrides if both are present. /// The Id of the user after which to fetch the bans. /// Collection of bans in this guild in ascending order by user id. /// Thrown when the client does not have the permission. /// Thrown when Discord is unable to process the request. public Task> GetBansAsync(int? limit = null, ulong? before = null, ulong? after = null) => this.Discord.ApiClient.GetGuildBansAsync(this.Id, limit, before, after); /// /// Gets a ban for a specific user. /// /// The Id of the user to get the ban for. /// The requested ban object. /// Thrown when the specified user is not banned. 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. /// The requested ban object. /// Thrown when the specified user is not banned. 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 cover image. /// 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, Optional coverImage = default, string reason = null) { var coverb64 = ImageTool.Base64FromStream(coverImage); return 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, coverb64, 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 cover image. /// 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, Optional coverImage = default, string reason = null) { var coverb64 = ImageTool.Base64FromStream(coverImage); return await this.Discord.ApiClient.CreateGuildScheduledEventAsync(this.Id, null, new DiscordScheduledEventEntityMetadata(location), name, scheduledStartTime, scheduledEndTime, description, ScheduledEventEntityType.External, coverb64, 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.ScheduledEventsInternal.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.None, null, null, overwrites, null, Optional.None, 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.None, null, null, overwrites, null, Optional.None, 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.None, null, null, overwrites, null, Optional.None, 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? userLimit = null, IEnumerable overwrites = null, VideoQualityMode? qualityMode = null, string reason = null) => this.CreateChannelAsync(name, ChannelType.Voice, parent, Optional.None, bitrate, userLimit, overwrites, null, Optional.None, 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 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); /// /// 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.RolesInternal.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.RolesInternal.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 expireBehaviour, int expireGracePeriod, bool enableEmoticons) => this.Discord.ApiClient.ModifyGuildIntegrationAsync(this.Id, integration.Id, expireBehaviour, expireGracePeriod, enableEmoticons); /// /// 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.MembersInternal != null && this.MembersInternal.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.MembersInternal != null) { this.MembersInternal[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, GuildId = 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.RolesInternal.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.ChannelsInternal != null && this.ChannelsInternal.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.ThreadsInternal != null && this.ThreadsInternal.TryGetValue(id, out var thread) ? thread : null; - // TODO: Rework audit logs! - /// - /// 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 byMember = null, AuditLogActionType? actionType = 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, byMember?.Id, (int?)actionType).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 atgse = alrs.SelectMany(xa => xa.ScheduledEvents) - .GroupBy(xse => xse.Id) - .Select(xgse => xgse.First()); - - var ath = alrs.SelectMany(xa => xa.Threads) - .GroupBy(xt => xt.Id) - .Select(xgt => xgt.First()); - - var aig = alrs.SelectMany(xa => xa.Integrations) - .GroupBy(xi => xi.Id) - .Select(xgi => xgi.First()); - - var ahr = alrs.SelectMany(xa => xa.Webhooks) - .GroupBy(xh => xh.Id) - .Select(xgh => xgh.First()); - - var ams = amr.Select(xau => this.MembersInternal != null && this.MembersInternal.TryGetValue(xau.Id, out var member) ? member : new DiscordMember { Discord = this.Discord, Id = xau.Id, GuildId = this.Id }); - var amd = ams.ToDictionary(xm => xm.Id, xm => xm); - -#pragma warning disable CS0219 - Dictionary dtc = null; - Dictionary di = null; - Dictionary dse = null; -#pragma warning restore - - 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.Invalid: - break; - - case AuditLogActionType.GuildUpdate: - entry = new DiscordAuditLogGuildEntry - { - Target = this - }; - - var entrygld = entry as DiscordAuditLogGuildEntry; - foreach (var xc in xac.Changes) - { - PropertyChange GetChannelChange() - { - ulong.TryParse(xc.NewValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t1); - ulong.TryParse(xc.OldValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t2); - - return 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 } - }; - } - - 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.MembersInternal != null && this.MembersInternal.TryGetValue(xc.OldValueUlong, out var oldMember) ? oldMember : await this.GetMemberAsync(xc.OldValueUlong).ConfigureAwait(false), - After = this.MembersInternal != null && this.MembersInternal.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": - entrygld.AfkChannelChange = GetChannelChange(); - break; - - case "system_channel_flags": - entrygld.SystemChannelFlagsChange = new PropertyChange() - { - Before = (SystemChannelFlags)(long)xc.OldValue, - After = (SystemChannelFlags)(long)xc.NewValue - }; - break; - - case "widget_channel_id": - entrygld.WidgetChannelChange = GetChannelChange(); - break; - - case "rules_channel_id": - entrygld.RulesChannelChange = GetChannelChange(); - break; - - case "public_updates_channel_id": - entrygld.PublicUpdatesChannelChange = GetChannelChange(); - 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": - entrygld.SystemChannelChange = GetChannelChange(); - 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 "vanity_url_code": - entrygld.VanityUrlCodeChange = 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.OldValueString, - After = xc.NewValueString - }; - 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 "rtc_region": - entrychn.RtcRegionIdChange = new PropertyChange - { - Before = xc.OldValueString, - After = xc.NewValueString - }; - break; - - case "bitrate": - entrychn.BitrateChange = new PropertyChange - { - Before = (int?)(long?)xc.OldValue, - After = (int?)(long?)xc.NewValue - }; - break; - - case "user_limit": - entrychn.UserLimitChange = 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; - - case "default_auto_archive_duration": - entrychn.DefaultAutoArchiveDurationChange = new PropertyChange - { - Before = (ThreadAutoArchiveDuration?)(long?)xc.OldValue, - After = (ThreadAutoArchiveDuration?)(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.OldValue != null ? (OverwriteType)(long)xc.OldValue : null, - After = xc.NewValue != null ? (OverwriteType)(long)xc.NewValue : null - }; - 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, GuildId = 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, GuildId = 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, GuildId = 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 "communication_disabled_until": - entrymbu.CommunicationDisabledUntilChange = new PropertyChange - { - Before = (DateTime?)xc.OldValue, - After = (DateTime?)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; - - case "icon_hash": - entryrol.IconHashChange = new PropertyChange - { - Before = xc.OldValueString, - After = xc.NewValueString - }; - 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, GuildId = this.Id }, - After = amd.TryGetValue(t2, out var propAfterMember) ? propAfterMember : new DiscordMember { Id = t1, Discord = this.Discord, GuildId = 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; - - // TODO: Add changes for target application - - 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 "application_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.IdChange = new PropertyChange - { - Before = p1 ? t1 : null, - After = p2 ? t2 : null - }; - break; - - 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.EmojisInternal.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.StageInstancesInternal.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.StickersInternal.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 "type": - integentry.Type = new PropertyChange() - { - Before = xc.OldValueString, - After = xc.NewValueString - }; - break; - 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.ThreadsInternal.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.OldValueString, - After = xc.NewValueString - }; - 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 "invitable": - entrythr.InvitableChange = 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.ScheduledEventsInternal.TryGetValue(xac.TargetId.Value, out var scheduledEvent) ? scheduledEvent : 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 "name": - entryse.NameChange = new PropertyChange - { - Before = xc.OldValueString, - After = xc.NewValueString - }; - break; - - case "description": - entryse.DescriptionChange = new PropertyChange - { - Before = xc.OldValueString, - After = xc.NewValueString - }; - break; - - case "location": - entryse.LocationChange = new PropertyChange - { - Before = xc.OldValueString, - After = xc.NewValueString - }; - 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 - { - Before = p1 ? (ScheduledEventStatus?)t5 : null, - After = p2 ? (ScheduledEventStatus?)t6 : null - }; - break; - - default: - this.Discord.Logger.LogWarning(LoggerEvents.AuditLog, "Unknown key in scheduled event update: {0} - this should be reported to library developers", xc.Key); - break; - } - } - break; - - default: - this.Discord.Logger.LogWarning(LoggerEvents.AuditLog, "Unknown audit log action type: {0} - this should be reported to library developers", (int)xac.ActionType); - break; - } - - if (entry == null) - continue; - - entry.ActionCategory = xac.ActionType switch - { - AuditLogActionType.ChannelCreate or AuditLogActionType.EmojiCreate or AuditLogActionType.InviteCreate or AuditLogActionType.OverwriteCreate or AuditLogActionType.RoleCreate or AuditLogActionType.WebhookCreate or AuditLogActionType.IntegrationCreate or AuditLogActionType.StickerCreate or AuditLogActionType.StageInstanceCreate or AuditLogActionType.ThreadCreate or AuditLogActionType.GuildScheduledEventCreate => AuditLogActionCategory.Create, - AuditLogActionType.ChannelDelete or AuditLogActionType.EmojiDelete or AuditLogActionType.InviteDelete or AuditLogActionType.MessageDelete or AuditLogActionType.MessageBulkDelete or AuditLogActionType.OverwriteDelete or AuditLogActionType.RoleDelete or AuditLogActionType.WebhookDelete or AuditLogActionType.IntegrationDelete or AuditLogActionType.StickerDelete or AuditLogActionType.StageInstanceDelete or AuditLogActionType.ThreadDelete or AuditLogActionType.GuildScheduledEventDelete => AuditLogActionCategory.Delete, - AuditLogActionType.ChannelUpdate or AuditLogActionType.EmojiUpdate or AuditLogActionType.InviteUpdate or AuditLogActionType.MemberRoleUpdate or AuditLogActionType.MemberUpdate or AuditLogActionType.OverwriteUpdate or AuditLogActionType.RoleUpdate or AuditLogActionType.WebhookUpdate or AuditLogActionType.IntegrationUpdate or AuditLogActionType.StickerUpdate or AuditLogActionType.StageInstanceUpdate or AuditLogActionType.ThreadUpdate or AuditLogActionType.GuildScheduledEventUpdate => AuditLogActionCategory.Update, - _ => AuditLogActionCategory.Other, - }; - entry.Discord = this.Discord; - entry.ActionType = xac.ActionType; - entry.Id = xac.Id; - entry.Reason = xac.Reason; - entry.UserResponsible = amd[xac.UserId]; - entries.Add(entry); - } - - return new ReadOnlyCollection(entries); - } - /// /// Gets all of this guild's custom emojis. /// /// All of this guild's custom emojis. /// Thrown when Discord is unable to process the request. public Task> GetEmojisAsync() => this.Discord.ApiClient.GetGuildEmojisAsync(this.Id); /// /// Gets this guild's specified custom emoji. /// /// ID of the emoji to get. /// The requested custom emoji. /// Thrown when Discord is unable to process the request. public Task GetEmojiAsync(ulong id) => this.Discord.ApiClient.GetGuildEmojiAsync(this.Id, id); /// /// Creates a new custom emoji for this guild. /// /// Name of the new emoji. /// Image to use as the emoji. /// Roles for which the emoji will be available. This works only if your application is whitelisted as integration. /// Reason for audit log. /// The newly-created emoji. /// Thrown when the client does not have the permission. /// Thrown when Discord is unable to process the request. public Task CreateEmojiAsync(string name, Stream image, IEnumerable roles = null, string reason = null) { if (string.IsNullOrWhiteSpace(name)) throw new ArgumentNullException(nameof(name)); name = name.Trim(); if (name.Length < 2 || name.Length > 50) throw new ArgumentException("Emoji name needs to be between 2 and 50 characters long."); if (image == null) throw new ArgumentNullException(nameof(image)); var image64 = ImageTool.Base64FromStream(image); return this.Discord.ApiClient.CreateGuildEmojiAsync(this.Id, name, image64, roles?.Select(xr => xr.Id), reason); } /// /// Modifies a this guild's custom emoji. /// /// Emoji to modify. /// New name for the emoji. /// Roles for which the emoji will be available. This works only if your application is whitelisted as integration. /// Reason for audit log. /// The modified emoji. /// Thrown when the client does not have the permission. /// Thrown when Discord is unable to process the request. public Task ModifyEmojiAsync(DiscordGuildEmoji emoji, string name, IEnumerable roles = null, string reason = null) { if (emoji == null) throw new ArgumentNullException(nameof(emoji)); if (emoji.Guild.Id != this.Id) throw new ArgumentException("This emoji does not belong to this guild."); if (string.IsNullOrWhiteSpace(name)) throw new ArgumentNullException(nameof(name)); name = name.Trim(); return name.Length < 2 || name.Length > 50 ? throw new ArgumentException("Emoji name needs to be between 2 and 50 characters long.") : this.Discord.ApiClient.ModifyGuildEmojiAsync(this.Id, emoji.Id, name, roles?.Select(xr => xr.Id), reason); } /// /// Deletes this guild's custom emoji. /// /// Emoji to delete. /// Reason for audit log. /// Thrown when the client does not have the permission. /// Thrown when Discord is unable to process the request. public Task DeleteEmojiAsync(DiscordGuildEmoji emoji, string reason = null) => emoji == null ? throw new ArgumentNullException(nameof(emoji)) : emoji.Guild.Id != this.Id ? throw new ArgumentException("This emoji does not belong to this guild.") : this.Discord.ApiClient.DeleteGuildEmojiAsync(this.Id, emoji.Id, reason); /// /// Gets all of this guild's custom stickers. /// /// All of this guild's custom stickers. /// Thrown when Discord is unable to process the request. public async Task> GetStickersAsync() { var stickers = await this.Discord.ApiClient.GetGuildStickersAsync(this.Id); foreach (var xstr in stickers) { this.StickersInternal.AddOrUpdate(xstr.Id, xstr, (id, old) => { old.Name = xstr.Name; old.Description = xstr.Description; old.InternalTags = xstr.InternalTags; return old; }); } return stickers; } /// /// Gets a sticker /// /// Thrown when the sticker could not be found. /// Thrown when the client does not have the permission. /// Thrown when Discord is unable to process the request. /// Sticker does not belong to a guild. public Task GetStickerAsync(ulong stickerId) => this.Discord.ApiClient.GetGuildStickerAsync(this.Id, stickerId); /// /// Creates a sticker /// /// The name of the sticker. /// The optional description of the sticker. /// The emoji to associate the sticker with. /// The file format the sticker is written in. /// The sticker. /// Audit log reason /// Thrown when the client does not have the permission. /// Thrown when Discord is unable to process the request. public Task CreateStickerAsync(string name, string description, DiscordEmoji emoji, Stream file, StickerFormat format, string reason = null) { var fileExt = format switch { StickerFormat.Png => "png", StickerFormat.Apng => "png", StickerFormat.Lottie => "json", _ => throw new InvalidOperationException("This format is not supported.") }; var contentType = format switch { StickerFormat.Png => "image/png", StickerFormat.Apng => "image/png", StickerFormat.Lottie => "application/json", _ => throw new InvalidOperationException("This format is not supported.") }; return emoji.Id is not 0 ? throw new InvalidOperationException("Only unicode emoji can be used for stickers.") : name.Length < 2 || name.Length > 30 ? throw new ArgumentOutOfRangeException(nameof(name), "Sticker name needs to be between 2 and 30 characters long.") : description.Length < 1 || description.Length > 100 ? throw new ArgumentOutOfRangeException(nameof(description), "Sticker description needs to be between 1 and 100 characters long.") : this.Discord.ApiClient.CreateGuildStickerAsync(this.Id, name, description, emoji.GetDiscordName().Replace(":", ""), new DiscordMessageFile("sticker", file, null, fileExt, contentType), reason); } /// /// Modifies a sticker /// /// The id of the sticker to modify /// The name of the sticker /// The description of the sticker /// The emoji to associate with this sticker. /// Audit log reason /// A sticker object /// Thrown when the sticker could not be found. /// Thrown when the client does not have the permission. /// Thrown when Discord is unable to process the request. /// Sticker does not belong to a guild. public async Task ModifyStickerAsync(ulong sticker, Optional name, Optional description, Optional emoji, string reason = null) { if (!this.StickersInternal.TryGetValue(sticker, out var stickerobj) || stickerobj.Guild.Id != this.Id) throw new ArgumentException("This sticker does not belong to this guild."); if (name.HasValue && (name.Value.Length < 2 || name.Value.Length > 30)) throw new ArgumentException("Sticker name needs to be between 2 and 30 characters long."); if (description.HasValue && (description.Value.Length < 1 || description.Value.Length > 100)) throw new ArgumentException("Sticker description needs to be between 1 and 100 characters long."); if (emoji.HasValue && emoji.Value.Id > 0) throw new ArgumentException("Only unicode emojis can be used with stickers."); string uemoji = null; if (emoji.HasValue) uemoji = emoji.Value.GetDiscordName().Replace(":", ""); var usticker = await this.Discord.ApiClient.ModifyGuildStickerAsync(this.Id, sticker, name, description, uemoji, reason).ConfigureAwait(false); if (this.StickersInternal.TryGetValue(usticker.Id, out var old)) this.StickersInternal.TryUpdate(usticker.Id, usticker, old); return usticker; } /// /// Modifies a sticker /// /// The sticker to modify /// The name of the sticker /// The description of the sticker /// The emoji to associate with this sticker. /// Audit log reason /// A sticker object /// Thrown when the sticker could not be found. /// Thrown when the client does not have the permission. /// Thrown when Discord is unable to process the request. /// Sticker does not belong to a guild. public Task ModifyStickerAsync(DiscordSticker sticker, Optional name, Optional description, Optional emoji, string reason = null) => this.ModifyStickerAsync(sticker.Id, name, description, emoji, reason); /// /// Deletes a sticker /// /// Id of sticker to delete /// Audit log reason /// Thrown when the sticker could not be found. /// Thrown when the client does not have the permission. /// Thrown when Discord is unable to process the request. /// Sticker does not belong to a guild. public Task DeleteStickerAsync(ulong sticker, string reason = null) => !this.StickersInternal.TryGetValue(sticker, out var stickerobj) ? throw new ArgumentNullException(nameof(sticker)) : stickerobj.Guild.Id != this.Id ? throw new ArgumentException("This sticker does not belong to this guild.") : this.Discord.ApiClient.DeleteGuildStickerAsync(this.Id, sticker, reason); /// /// Deletes a sticker /// /// Sticker to delete /// Audit log reason /// Thrown when the sticker could not be found. /// Thrown when the client does not have the permission. /// Thrown when Discord is unable to process the request. /// Sticker does not belong to a guild. public Task DeleteStickerAsync(DiscordSticker sticker, string reason = null) => this.DeleteStickerAsync(sticker.Id, reason); /// /// Gets the default channel for this guild. /// Default channel is the first channel current member can see. /// /// This member's default guild. /// Thrown when Discord is unable to process the request. public DiscordChannel GetDefaultChannel() => this.ChannelsInternal?.Values.Where(xc => xc.Type == ChannelType.Text) .OrderBy(xc => xc.Position) .FirstOrDefault(xc => (xc.PermissionsFor(this.CurrentMember) & DisCatSharp.Permissions.AccessChannels) == DisCatSharp.Permissions.AccessChannels); /// /// Gets the guild's widget /// /// The guild's widget public Task GetWidgetAsync() => this.Discord.ApiClient.GetGuildWidgetAsync(this.Id); /// /// Gets the guild's widget settings /// /// The guild's widget settings public Task GetWidgetSettingsAsync() => this.Discord.ApiClient.GetGuildWidgetSettingsAsync(this.Id); /// /// Modifies the guild's widget settings /// /// If the widget is enabled or not /// Widget channel /// Reason the widget settings were modified /// The newly modified widget settings public Task ModifyWidgetSettingsAsync(bool? isEnabled = null, DiscordChannel channel = null, string reason = null) => this.Discord.ApiClient.ModifyGuildWidgetSettingsAsync(this.Id, isEnabled, channel?.Id, reason); /// /// Gets all of this guild's templates. /// /// All of the guild's templates. /// Throws when the client does not have the permission. /// Thrown when Discord is unable to process the request. public Task> GetTemplatesAsync() => this.Discord.ApiClient.GetGuildTemplatesAsync(this.Id); /// /// Creates a guild template. /// /// Name of the template. /// Description of the template. /// The template created. /// Throws when a template already exists for the guild or a null parameter is provided for the name. /// Throws when the client does not have the permission. /// Thrown when Discord is unable to process the request. public Task CreateTemplateAsync(string name, string description = null) => this.Discord.ApiClient.CreateGuildTemplateAsync(this.Id, name, description); /// /// Syncs the template to the current guild's state. /// /// The code of the template to sync. /// The template synced. /// Throws when the template for the code cannot be found /// Throws when the client does not have the permission. /// Thrown when Discord is unable to process the request. public Task SyncTemplateAsync(string code) => this.Discord.ApiClient.SyncGuildTemplateAsync(this.Id, code); /// /// Modifies the template's metadata. /// /// The template's code. /// Name of the template. /// Description of the template. /// The template modified. /// Throws when the template for the code cannot be found /// Throws when the client does not have the permission. /// Thrown when Discord is unable to process the request. public Task ModifyTemplateAsync(string code, string name = null, string description = null) => this.Discord.ApiClient.ModifyGuildTemplateAsync(this.Id, code, name, description); /// /// Deletes the template. /// /// The code of the template to delete. /// The deleted template. /// Throws when the template for the code cannot be found /// Throws when the client does not have the permission. /// Thrown when Discord is unable to process the request. public Task DeleteTemplateAsync(string code) => this.Discord.ApiClient.DeleteGuildTemplateAsync(this.Id, code); /// /// Gets this guild's membership screening form. /// /// This guild's membership screening form. /// Thrown when Discord is unable to process the request. public Task GetMembershipScreeningFormAsync() => this.Discord.ApiClient.GetGuildMembershipScreeningFormAsync(this.Id); /// /// Modifies this guild's membership screening form. /// /// Action to perform /// The modified screening form. /// Thrown when the client doesn't have the permission, or community is not enabled on this guild. /// Thrown when Discord is unable to process the request. public async Task ModifyMembershipScreeningFormAsync(Action action) { var mdl = new MembershipScreeningEditModel(); action(mdl); return await this.Discord.ApiClient.ModifyGuildMembershipScreeningFormAsync(this.Id, mdl.Enabled, mdl.Fields, mdl.Description); } /// /// Gets all the application commands in this guild. /// /// A list of application commands in this guild. public Task> GetApplicationCommandsAsync() => this.Discord.ApiClient.GetGuildApplicationCommandsAsync(this.Discord.CurrentApplication.Id, this.Id); /// /// Overwrites the existing application commands in this guild. New commands are automatically created and missing commands are automatically delete /// /// The list of commands to overwrite with. /// The list of guild commands public Task> BulkOverwriteApplicationCommandsAsync(IEnumerable commands) => this.Discord.ApiClient.BulkOverwriteGuildApplicationCommandsAsync(this.Discord.CurrentApplication.Id, this.Id, commands); /// /// Creates or overwrites a application command in this guild. /// /// The command to create. /// The created command. public Task CreateApplicationCommandAsync(DiscordApplicationCommand command) => this.Discord.ApiClient.CreateGuildApplicationCommandAsync(this.Discord.CurrentApplication.Id, this.Id, command); /// /// Edits a application command in this guild. /// /// The id of the command to edit. /// Action to perform. /// The edit command. public async Task EditApplicationCommandAsync(ulong commandId, Action action) { var mdl = new ApplicationCommandEditModel(); action(mdl); return await this.Discord.ApiClient.EditGuildApplicationCommandAsync(this.Discord.CurrentApplication.Id, this.Id, commandId, mdl.Name, mdl.Description, mdl.Options, mdl.DefaultPermission, mdl.NameLocalizations, mdl.DescriptionLocalizations).ConfigureAwait(false); } /// /// Gets this guild's welcome screen. /// /// This guild's welcome screen object. /// Thrown when Discord is unable to process the request. public Task GetWelcomeScreenAsync() => this.Discord.ApiClient.GetGuildWelcomeScreenAsync(this.Id); /// /// Modifies this guild's welcome screen. /// /// Action to perform. /// The modified welcome screen. /// Thrown when the client doesn't have the permission, or community is not enabled on this guild. /// Thrown when Discord is unable to process the request. public async Task ModifyWelcomeScreenAsync(Action action) { var mdl = new WelcomeScreenEditModel(); action(mdl); return await this.Discord.ApiClient.ModifyGuildWelcomeScreenAsync(this.Id, mdl.Enabled, mdl.WelcomeChannels, mdl.Description).ConfigureAwait(false); } #endregion /// /// Returns a string representation of this guild. /// /// String representation of this guild. public override string ToString() => $"Guild {this.Id}; {this.Name}"; /// /// 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 DiscordGuild); /// /// Checks whether this is equal to another . /// /// to compare to. /// Whether the is equal to this . public bool Equals(DiscordGuild 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 guild to compare. /// Second guild to compare. /// Whether the two guilds are equal. public static bool operator ==(DiscordGuild e1, DiscordGuild 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 guild to compare. /// Second guild to compare. /// Whether the two guilds are not equal. public static bool operator !=(DiscordGuild e1, DiscordGuild e2) => !(e1 == e2); } /// /// Represents guild verification level. /// public enum VerificationLevel : int { /// /// No verification. Anyone can join and chat right away. /// None = 0, /// /// Low verification level. Users are required to have a verified email attached to their account in order to be able to chat. /// Low = 1, /// /// Medium verification level. Users are required to have a verified email attached to their account, and account age need to be at least 5 minutes in order to be able to chat. /// Medium = 2, /// /// High verification level. Users are required to have a verified email attached to their account, account age need to be at least 5 minutes, and they need to be in the server for at least 10 minutes in order to be able to chat. /// High = 3, /// /// Highest verification level. Users are required to have a verified phone number attached to their account. /// Highest = 4 } /// /// Represents default notification level for a guild. /// public enum DefaultMessageNotifications : int { /// /// All messages will trigger push notifications. /// AllMessages = 0, /// /// Only messages that mention the user (or a role he's in) will trigger push notifications. /// MentionsOnly = 1 } /// /// Represents multi-factor authentication level required by a guild to use administrator functionality. /// public enum MfaLevel : int { /// /// Multi-factor authentication is not required to use administrator functionality. /// Disabled = 0, /// /// Multi-factor authentication is required to use administrator functionality. /// Enabled = 1 } /// /// Represents the value of explicit content filter in a guild. /// public enum ExplicitContentFilter : int { /// /// Explicit content filter is disabled. /// Disabled = 0, /// /// Only messages from members without any roles are scanned. /// MembersWithoutRoles = 1, /// /// Messages from all members are scanned. /// AllMembers = 2 } /// /// Represents the formats for a guild widget. /// public enum WidgetType : int { /// /// The widget is represented in shield format. /// This is the default widget type. /// Shield = 0, /// /// The widget is represented as the first banner type. /// Banner1 = 1, /// /// The widget is represented as the second banner type. /// Banner2 = 2, /// /// The widget is represented as the third banner type. /// Banner3 = 3, /// /// The widget is represented in the fourth banner type. /// Banner4 = 4 } - - /// - /// Represents the guild features. - /// - public class GuildFeatures - { - /// - /// Guild has access to set an animated guild icon. - /// - public bool CanSetAnimatedIcon { get; } - - /// - /// Guild has access to set a guild banner image. - /// - public bool CanSetBanner { get; } - - /// - /// Guild has access to use commerce features (i.e. create store channels) - /// - public bool CanCreateStoreChannels { get; } - - /// - /// Guild can enable Welcome Screen, Membership Screening, Stage Channels, News Channels and receives community updates. - /// Furthermore the guild can apply as a partner and for the discovery (if the prerequisites are given). - /// and is usable. - /// - public bool HasCommunityEnabled { get; } - - /// - /// Guild is able to be discovered in the discovery. - /// - public bool IsDiscoverable { get; } - - /// - /// Guild is able to be featured in the discovery. - /// - public bool IsFeatureable { get; } - - /// - /// Guild has access to set an invite splash background. - /// - public bool CanSetInviteSplash { get; } - - /// - /// Guild has enabled Membership Screening. - /// - public bool HasMembershipScreeningEnabled { get; } - - /// - /// Guild has access to create news channels. - /// is usable. - /// - public bool CanCreateNewsChannels { get; } - - /// - /// Guild is partnered. - /// - public bool IsPartnered { get; } - - /// - /// Guild has increased custom emoji slots. - /// - public bool CanUploadMoreEmojis { get; } - - /// - /// Guild can be previewed before joining via Membership Screening or the discovery. - /// - public bool HasPreviewEnabled { get; } - - /// - /// Guild has access to set a vanity URL. - /// - public bool CanSetVanityUrl { get; } - - /// - /// Guild is verified. - /// - public bool IsVerified { get; } - - /// - /// Guild has access to set 384kbps bitrate in voice (previously VIP voice servers). - /// - public bool CanAccessVipRegions { get; } - - /// - /// Guild has enabled the welcome screen. - /// - public bool HasWelcomeScreenEnabled { get; } - - /// - /// Guild has enabled ticketed events. - /// - public bool HasTicketedEventsEnabled { get; } - - /// - /// Guild has enabled monetization. - /// - public bool HasMonetizationEnabled { get; } - - /// - /// Guild has increased custom sticker slots. - /// - public bool CanUploadMoreStickers { get; } - - /// - /// Guild has access to the three day archive time for threads. - /// Needs Premium Tier 1 (). - /// - public bool CanSetThreadArchiveDurationThreeDays { get; } - - /// - /// Guild has access to the seven day archive time for threads. - /// Needs Premium Tier 2 (). - /// - public bool CanSetThreadArchiveDurationSevenDays { get; } - - /// - /// Guild has access to create private threads. - /// Needs Premium Tier 2 (). - /// - public bool CanCreatePrivateThreads { get; } - - /// - /// Guild is a hub. - /// is usable. - /// - public bool IsHub { get; } - - /// - /// Guild is in a hub. - /// https://github.com/discord/discord-api-docs/pull/3757/commits/4932d92c9d0c783861bc715bf7ebbabb15114e34 - /// - public bool HasDirectoryEntry { get; } - - /// - /// Guild is linked to a hub. - /// - public bool IsLinkedToHub { get; } - - /// - /// Guild has full access to threads. - /// Old Feature. - /// - public bool HasThreadTestingEnabled { get; } - - /// - /// Guild has access to threads. - /// - public bool HasThreadsEnabled { get; } - - /// - /// Guild can set role icons. - /// - public bool CanSetRoleIcons { get; } - - /// - /// Guild has the new thread permissions. - /// Old Feature. - /// - public bool HasNewThreadPermissions { get; } - - /// - /// Guild can set thread default auto archive duration. - /// Old Feature. - /// - public bool CanSetThreadDefaultAutoArchiveDuration { get; } - - /// - /// Guild has enabled role subsriptions. - /// - public bool HasRoleSubscriptionsEnabled { get; } - - /// - /// Guild role subsriptions as purchaseable. - /// - public bool RoleSubscriptionsIsAvaiableForPurchase { get; } - - /// - /// Guild has premium tier 3 override. - /// - public bool PremiumTierThreeOverride { get; } - - /// - /// Guild has access to text in voice. - /// Restricted to . - /// - public bool TextInVoiceEnabled { get; } - - /// - /// Guild can set an animated banner. - /// Needs Premium Tier 3 (). - /// - public bool CanSetAnimatedBanner { get; } - - /// - /// Guild can set an animated banner. - /// Needs Premium Tier 3 (). - /// - public bool CanSetChannelBanner { get; } - - /// - /// Allows members to customize their avatar, banner and bio for that server. - /// - public bool HasMemberProfiles { get; } - - /// - /// Guild is restricted to users with the badge. - /// - public bool IsStaffOnly { get; } - - /// - /// String of guild features. - /// - public string FeatureString { get; } - - /// - /// Checks the guild features and constructs a new object. - /// - /// Guild to check - public GuildFeatures(DiscordGuild guild) - { - this.CanSetAnimatedIcon = guild.RawFeatures.Contains("ANIMATED_ICON"); - this.CanSetAnimatedBanner = guild.RawFeatures.Contains("ANIMATED_BANNER"); - this.CanSetBanner = guild.RawFeatures.Contains("BANNER"); - this.CanSetChannelBanner = guild.RawFeatures.Contains("CHANNEL_BANNER"); - this.CanCreateStoreChannels = guild.RawFeatures.Contains("COMMERCE"); - this.HasCommunityEnabled = guild.RawFeatures.Contains("COMMUNITY"); - this.IsDiscoverable = !guild.RawFeatures.Contains("DISCOVERABLE_DISABLED") && guild.RawFeatures.Contains("DISCOVERABLE"); - this.IsFeatureable = guild.RawFeatures.Contains("FEATURABLE"); - this.CanSetInviteSplash = guild.RawFeatures.Contains("INVITE_SPLASH"); - this.HasMembershipScreeningEnabled = guild.RawFeatures.Contains("MEMBER_VERIFICATION_GATE_ENABLED"); - this.CanCreateNewsChannels = guild.RawFeatures.Contains("NEWS"); - this.IsPartnered = guild.RawFeatures.Contains("PARTNERED"); - this.CanUploadMoreEmojis = guild.RawFeatures.Contains("MORE_EMOJI"); - this.HasPreviewEnabled = guild.RawFeatures.Contains("PREVIEW_ENABLED"); - this.CanSetVanityUrl = guild.RawFeatures.Contains("VANITY_URL"); - this.IsVerified = guild.RawFeatures.Contains("VERIFIED"); - this.CanAccessVipRegions = guild.RawFeatures.Contains("VIP_REGIONS"); - this.HasWelcomeScreenEnabled = guild.RawFeatures.Contains("WELCOME_SCREEN_ENABLED"); - this.HasTicketedEventsEnabled = guild.RawFeatures.Contains("TICKETED_EVENTS_ENABLED"); - this.HasMonetizationEnabled = guild.RawFeatures.Contains("MONETIZATION_ENABLED"); - this.CanUploadMoreStickers = guild.RawFeatures.Contains("MORE_STICKERS"); - this.CanSetThreadArchiveDurationThreeDays = guild.RawFeatures.Contains("THREE_DAY_THREAD_ARCHIVE"); - this.CanSetThreadArchiveDurationSevenDays = guild.RawFeatures.Contains("SEVEN_DAY_THREAD_ARCHIVE"); - this.CanCreatePrivateThreads = guild.RawFeatures.Contains("PRIVATE_THREADS"); - this.IsHub = guild.RawFeatures.Contains("HUB"); - this.HasThreadTestingEnabled = guild.RawFeatures.Contains("THREADS_ENABLED_TESTING"); - this.HasThreadsEnabled = guild.RawFeatures.Contains("THREADS_ENABLED"); - this.CanSetRoleIcons = guild.RawFeatures.Contains("ROLE_ICONS"); - this.HasNewThreadPermissions = guild.RawFeatures.Contains("NEW_THREAD_PERMISSIONS"); - this.HasRoleSubscriptionsEnabled = guild.RawFeatures.Contains("ROLE_SUBSCRIPTIONS_ENABLED"); - this.PremiumTierThreeOverride = guild.RawFeatures.Contains("PREMIUM_TIER_3_OVERRIDE"); - this.CanSetThreadDefaultAutoArchiveDuration = guild.RawFeatures.Contains("THREAD_DEFAULT_AUTO_ARCHIVE_DURATION"); - this.TextInVoiceEnabled = guild.RawFeatures.Contains("TEXT_IN_VOICE_ENABLED"); - this.HasDirectoryEntry = guild.RawFeatures.Contains("HAS_DIRECTORY_ENTRY"); - this.IsLinkedToHub = guild.RawFeatures.Contains("LINKED_TO_HUB"); - this.HasMemberProfiles = guild.RawFeatures.Contains("MEMBER_PROFILES"); - this.IsStaffOnly = guild.RawFeatures.Contains("INTERNAL_EMPLOYEE_ONLY"); - this.RoleSubscriptionsIsAvaiableForPurchase = guild.RawFeatures.Contains("ROLE_SUBSCRIPTIONS_AVAILABLE_FOR_PURCHASE"); - - var features = guild.RawFeatures.Any() ? "" : "None"; - foreach (var feature in guild.RawFeatures) - { - features += feature + " "; - } - this.FeatureString = features; - - } - } }