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