diff --git a/DisCatSharp/Clients/DiscordClient.Dispatch.cs b/DisCatSharp/Clients/DiscordClient.Dispatch.cs index aa091f57d..0f669fcf7 100644 --- a/DisCatSharp/Clients/DiscordClient.Dispatch.cs +++ b/DisCatSharp/Clients/DiscordClient.Dispatch.cs @@ -1,3221 +1,3404 @@ // 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.Linq; using System.Threading; using System.Threading.Tasks; using DisCatSharp.Common.Utilities; using DisCatSharp.Entities; using DisCatSharp.Enums; using DisCatSharp.EventArgs; using DisCatSharp.Exceptions; using DisCatSharp.Net.Abstractions; using DisCatSharp.Net.Serialization; using Microsoft.Extensions.Logging; using Newtonsoft.Json.Linq; namespace DisCatSharp { /// /// Represents a discord client. /// public sealed partial class DiscordClient { #region Private Fields private string _sessionId; private bool _guildDownloadCompleted; + private readonly Dictionary> _tempTimers = new(); + + /// + /// Represents a timeout handler. + /// + internal class TimeoutHandler + { + /// + /// Gets the member. + /// + internal DiscordMember _member; + + /// + /// Gets the guild. + /// + internal DiscordGuild _guild; + + /// + /// Gets the old timeout value. + /// + internal DateTime? _timeoutUntilOld; + + /// + /// Gets the new timeout value. + /// + internal DateTime? _timeoutUntilNew; + + /// + /// Constructs a new . + /// + /// The affected member. + /// The affected guild. + /// The old timeout value. + /// The new timeout value. + internal TimeoutHandler(DiscordMember mbr, DiscordGuild guild, DateTime? too, DateTime? ton) + { + this._guild = guild; + this._member = mbr; + this._timeoutUntilOld = too; + this._timeoutUntilNew = ton; + } + } + #endregion #region Dispatch Handler /// /// Handles the dispatch payloads. /// /// The payload. internal async Task HandleDispatchAsync(GatewayPayload payload) { if (payload.Data is not JObject dat) { this.Logger.LogWarning(LoggerEvents.WebSocketReceive, "Invalid payload body (this message is probably safe to ignore); opcode: {0} event: {1}; payload: {2}", payload.OpCode, payload.EventName, payload.Data); return; } await this._payloadReceived.InvokeAsync(this, new PayloadReceivedEventArgs(this.ServiceProvider) { EventName = payload.EventName, PayloadObject = dat }).ConfigureAwait(false); #region Default objects DiscordChannel chn; ulong gid; ulong cid; ulong uid; DiscordStageInstance stg = default; DiscordIntegration itg = default; DiscordThreadChannel trd = default; DiscordThreadChannelMember trdm = default; DiscordScheduledEvent gse = default; TransportUser usr = default; TransportMember mbr = default; TransportUser refUsr = default; TransportMember refMbr = default; JToken rawMbr = default; var rawRefMsg = dat["referenced_message"]; #endregion switch (payload.EventName.ToLowerInvariant()) { #region Gateway Status case "ready": var glds = (JArray)dat["guilds"]; await this.OnReadyEventAsync(dat.ToObject(), glds).ConfigureAwait(false); break; case "resumed": await this.OnResumedAsync().ConfigureAwait(false); break; #endregion #region Channel case "channel_create": chn = dat.ToObject(); await this.OnChannelCreateEventAsync(chn).ConfigureAwait(false); break; case "channel_update": await this.OnChannelUpdateEventAsync(dat.ToObject()).ConfigureAwait(false); break; case "channel_delete": chn = dat.ToObject(); await this.OnChannelDeleteEventAsync(chn.IsPrivate ? dat.ToObject() : chn).ConfigureAwait(false); break; case "channel_pins_update": cid = (ulong)dat["channel_id"]; var ts = (string)dat["last_pin_timestamp"]; await this.OnChannelPinsUpdateAsync((ulong?)dat["guild_id"], cid, ts != null ? DateTimeOffset.Parse(ts, CultureInfo.InvariantCulture) : default(DateTimeOffset?)).ConfigureAwait(false); break; #endregion #region Guild case "guild_create": await this.OnGuildCreateEventAsync(dat.ToDiscordObject(), (JArray)dat["members"], dat["presences"].ToDiscordObject>()).ConfigureAwait(false); break; case "guild_update": await this.OnGuildUpdateEventAsync(dat.ToDiscordObject(), (JArray)dat["members"]).ConfigureAwait(false); break; case "guild_delete": await this.OnGuildDeleteEventAsync(dat.ToDiscordObject()).ConfigureAwait(false); break; case "guild_sync": gid = (ulong)dat["id"]; await this.OnGuildSyncEventAsync(this.GuildsInternal[gid], (bool)dat["large"], (JArray)dat["members"], dat["presences"].ToDiscordObject>()).ConfigureAwait(false); break; case "guild_emojis_update": gid = (ulong)dat["guild_id"]; var ems = dat["emojis"].ToObject>(); await this.OnGuildEmojisUpdateEventAsync(this.GuildsInternal[gid], ems).ConfigureAwait(false); break; case "guild_stickers_update": gid = (ulong)dat["guild_id"]; var strs = dat["stickers"].ToDiscordObject>(); await this.OnStickersUpdatedAsync(strs, gid).ConfigureAwait(false); break; case "guild_integrations_update": gid = (ulong)dat["guild_id"]; // discord fires this event inconsistently if the current user leaves a guild. if (!this.GuildsInternal.ContainsKey(gid)) return; await this.OnGuildIntegrationsUpdateEventAsync(this.GuildsInternal[gid]).ConfigureAwait(false); break; case "guild_join_request_create": break; case "guild_join_request_update": break; case "guild_join_request_delete": break; #endregion #region Guild Ban case "guild_ban_add": usr = dat["user"].ToObject(); gid = (ulong)dat["guild_id"]; await this.OnGuildBanAddEventAsync(usr, this.GuildsInternal[gid]).ConfigureAwait(false); break; case "guild_ban_remove": usr = dat["user"].ToObject(); gid = (ulong)dat["guild_id"]; await this.OnGuildBanRemoveEventAsync(usr, this.GuildsInternal[gid]).ConfigureAwait(false); break; #endregion #region Guild Event case "guild_scheduled_event_create": gse = dat.ToObject(); gid = (ulong)dat["guild_id"]; await this.OnGuildScheduledEventCreateEventAsync(gse, this.GuildsInternal[gid]).ConfigureAwait(false); break; case "guild_scheduled_event_update": gse = dat.ToObject(); gid = (ulong)dat["guild_id"]; await this.OnGuildScheduledEventUpdateEventAsync(gse, this.GuildsInternal[gid]).ConfigureAwait(false); break; case "guild_scheduled_event_delete": gse = dat.ToObject(); gid = (ulong)dat["guild_id"]; await this.OnGuildScheduledEventDeleteEventAsync(gse, this.GuildsInternal[gid]).ConfigureAwait(false); break; case "guild_scheduled_event_user_add": gid = (ulong)dat["guild_id"]; uid = (ulong)dat["user_id"]; await this.OnGuildScheduledEventUserAddedEventAsync((ulong)dat["guild_scheduled_event_id"], uid, this.GuildsInternal[gid]).ConfigureAwait(false); break; case "guild_scheduled_event_user_remove": gid = (ulong)dat["guild_id"]; uid = (ulong)dat["user_id"]; await this.OnGuildScheduledEventUserRemovedEventAsync((ulong)dat["guild_scheduled_event_id"], uid, this.GuildsInternal[gid]).ConfigureAwait(false); break; #endregion #region Guild Integration case "integration_create": gid = (ulong)dat["guild_id"]; itg = dat.ToObject(); // discord fires this event inconsistently if the current user leaves a guild. if (!this.GuildsInternal.ContainsKey(gid)) return; await this.OnGuildIntegrationCreateEventAsync(this.GuildsInternal[gid], itg).ConfigureAwait(false); break; case "integration_update": gid = (ulong)dat["guild_id"]; itg = dat.ToObject(); // discord fires this event inconsistently if the current user leaves a guild. if (!this.GuildsInternal.ContainsKey(gid)) return; await this.OnGuildIntegrationUpdateEventAsync(this.GuildsInternal[gid], itg).ConfigureAwait(false); break; case "integration_delete": gid = (ulong)dat["guild_id"]; // discord fires this event inconsistently if the current user leaves a guild. if (!this.GuildsInternal.ContainsKey(gid)) return; await this.OnGuildIntegrationDeleteEventAsync(this.GuildsInternal[gid], (ulong)dat["id"], (ulong?)dat["application_id"]).ConfigureAwait(false); break; #endregion #region Guild Member case "guild_member_add": gid = (ulong)dat["guild_id"]; await this.OnGuildMemberAddEventAsync(dat.ToObject(), this.GuildsInternal[gid]).ConfigureAwait(false); break; case "guild_member_remove": gid = (ulong)dat["guild_id"]; usr = dat["user"].ToObject(); if (!this.GuildsInternal.ContainsKey(gid)) { // discord fires this event inconsistently if the current user leaves a guild. if (usr.Id != this.CurrentUser.Id) this.Logger.LogError(LoggerEvents.WebSocketReceive, "Could not find {0} in guild cache", gid); return; } await this.OnGuildMemberRemoveEventAsync(usr, this.GuildsInternal[gid]).ConfigureAwait(false); break; case "guild_member_update": gid = (ulong)dat["guild_id"]; await this.OnGuildMemberUpdateEventAsync(dat.ToDiscordObject(), this.GuildsInternal[gid], dat["roles"].ToObject>(), (string)dat["nick"], (bool?)dat["pending"]).ConfigureAwait(false); break; case "guild_members_chunk": await this.OnGuildMembersChunkEventAsync(dat).ConfigureAwait(false); break; #endregion #region Guild Role case "guild_role_create": gid = (ulong)dat["guild_id"]; await this.OnGuildRoleCreateEventAsync(dat["role"].ToObject(), this.GuildsInternal[gid]).ConfigureAwait(false); break; case "guild_role_update": gid = (ulong)dat["guild_id"]; await this.OnGuildRoleUpdateEventAsync(dat["role"].ToObject(), this.GuildsInternal[gid]).ConfigureAwait(false); break; case "guild_role_delete": gid = (ulong)dat["guild_id"]; await this.OnGuildRoleDeleteEventAsync((ulong)dat["role_id"], this.GuildsInternal[gid]).ConfigureAwait(false); break; #endregion #region Invite case "invite_create": gid = (ulong)dat["guild_id"]; cid = (ulong)dat["channel_id"]; await this.OnInviteCreateEventAsync(cid, gid, dat.ToObject()).ConfigureAwait(false); break; case "invite_delete": gid = (ulong)dat["guild_id"]; cid = (ulong)dat["channel_id"]; await this.OnInviteDeleteEventAsync(cid, gid, dat).ConfigureAwait(false); break; #endregion #region Message case "message_ack": cid = (ulong)dat["channel_id"]; var mid = (ulong)dat["message_id"]; await this.OnMessageAckEventAsync(this.InternalGetCachedChannel(cid), mid).ConfigureAwait(false); break; case "message_create": rawMbr = dat["member"]; if (rawMbr != null) mbr = rawMbr.ToObject(); if (rawRefMsg != null && rawRefMsg.HasValues) { if (rawRefMsg.SelectToken("author") != null) { refUsr = rawRefMsg.SelectToken("author").ToObject(); } if (rawRefMsg.SelectToken("member") != null) { refMbr = rawRefMsg.SelectToken("member").ToObject(); } } await this.OnMessageCreateEventAsync(dat.ToDiscordObject(), dat["author"].ToObject(), mbr, refUsr, refMbr).ConfigureAwait(false); break; case "message_update": rawMbr = dat["member"]; if (rawMbr != null) mbr = rawMbr.ToObject(); if (rawRefMsg != null && rawRefMsg.HasValues) { if (rawRefMsg.SelectToken("author") != null) { refUsr = rawRefMsg.SelectToken("author").ToObject(); } if (rawRefMsg.SelectToken("member") != null) { refMbr = rawRefMsg.SelectToken("member").ToObject(); } } await this.OnMessageUpdateEventAsync(dat.ToDiscordObject(), dat["author"]?.ToObject(), mbr, refUsr, refMbr).ConfigureAwait(false); break; // delete event does *not* include message object case "message_delete": await this.OnMessageDeleteEventAsync((ulong)dat["id"], (ulong)dat["channel_id"], (ulong?)dat["guild_id"]).ConfigureAwait(false); break; case "message_delete_bulk": await this.OnMessageBulkDeleteEventAsync(dat["ids"].ToObject(), (ulong)dat["channel_id"], (ulong?)dat["guild_id"]).ConfigureAwait(false); break; #endregion #region Message Reaction case "message_reaction_add": rawMbr = dat["member"]; if (rawMbr != null) mbr = rawMbr.ToObject(); await this.OnMessageReactionAddAsync((ulong)dat["user_id"], (ulong)dat["message_id"], (ulong)dat["channel_id"], (ulong?)dat["guild_id"], mbr, dat["emoji"].ToObject()).ConfigureAwait(false); break; case "message_reaction_remove": await this.OnMessageReactionRemoveAsync((ulong)dat["user_id"], (ulong)dat["message_id"], (ulong)dat["channel_id"], (ulong?)dat["guild_id"], dat["emoji"].ToObject()).ConfigureAwait(false); break; case "message_reaction_remove_all": await this.OnMessageReactionRemoveAllAsync((ulong)dat["message_id"], (ulong)dat["channel_id"], (ulong?)dat["guild_id"]).ConfigureAwait(false); break; case "message_reaction_remove_emoji": await this.OnMessageReactionRemoveEmojiAsync((ulong)dat["message_id"], (ulong)dat["channel_id"], (ulong)dat["guild_id"], dat["emoji"]).ConfigureAwait(false); break; #endregion #region Stage Instance case "stage_instance_create": stg = dat.ToObject(); await this.OnStageInstanceCreateEventAsync(stg).ConfigureAwait(false); break; case "stage_instance_update": stg = dat.ToObject(); await this.OnStageInstanceUpdateEventAsync(stg).ConfigureAwait(false); break; case "stage_instance_delete": stg = dat.ToObject(); await this.OnStageInstanceDeleteEventAsync(stg).ConfigureAwait(false); break; #endregion #region Thread case "thread_create": trd = dat.ToObject(); await this.OnThreadCreateEventAsync(trd).ConfigureAwait(false); break; case "thread_update": trd = dat.ToObject(); await this.OnThreadUpdateEventAsync(trd).ConfigureAwait(false); break; case "thread_delete": trd = dat.ToObject(); await this.OnThreadDeleteEventAsync(trd).ConfigureAwait(false); break; case "thread_list_sync": gid = (ulong)dat["guild_id"]; //get guild await this.OnThreadListSyncEventAsync(this.GuildsInternal[gid], dat["channel_ids"].ToObject>(), dat["threads"].ToObject>(), dat["members"].ToObject>()).ConfigureAwait(false); break; case "thread_member_update": trdm = dat.ToObject(); await this.OnThreadMemberUpdateEventAsync(trdm).ConfigureAwait(false); break; case "thread_members_update": gid = (ulong)dat["guild_id"]; await this.OnThreadMembersUpdateEventAsync(this.GuildsInternal[gid], (ulong)dat["id"], (JArray)dat["added_members"], (JArray)dat["removed_member_ids"], (int)dat["member_count"]).ConfigureAwait(false); break; #endregion #region Activities case "embedded_activity_update": gid = (ulong)dat["guild_id"]; cid = (ulong)dat["channel_id"]; await this.OnEmbeddedActivityUpdateAsync((JObject)dat["embedded_activity"], this.GuildsInternal[gid], cid, (JArray)dat["users"], (ulong)dat["embedded_activity"]["application_id"]).ConfigureAwait(false); break; #endregion #region User/Presence Update case "presence_update": await this.OnPresenceUpdateEventAsync(dat, (JObject)dat["user"]).ConfigureAwait(false); break; case "user_settings_update": await this.OnUserSettingsUpdateEventAsync(dat.ToObject()).ConfigureAwait(false); break; case "user_update": await this.OnUserUpdateEventAsync(dat.ToObject()).ConfigureAwait(false); break; #endregion #region Voice case "voice_state_update": await this.OnVoiceStateUpdateEventAsync(dat).ConfigureAwait(false); break; case "voice_server_update": gid = (ulong)dat["guild_id"]; await this.OnVoiceServerUpdateEventAsync((string)dat["endpoint"], (string)dat["token"], this.GuildsInternal[gid]).ConfigureAwait(false); break; #endregion #region Interaction/Integration/Application case "interaction_create": rawMbr = dat["member"]; if (rawMbr != null) { mbr = dat["member"].ToObject(); usr = mbr.User; } else { usr = dat["user"].ToObject(); } cid = (ulong)dat["channel_id"]; await this.OnInteractionCreateAsync((ulong?)dat["guild_id"], cid, usr, mbr, dat.ToDiscordObject()).ConfigureAwait(false); break; case "application_command_create": await this.OnApplicationCommandCreateAsync(dat.ToObject(), (ulong?)dat["guild_id"]).ConfigureAwait(false); break; case "application_command_update": await this.OnApplicationCommandUpdateAsync(dat.ToObject(), (ulong?)dat["guild_id"]).ConfigureAwait(false); break; case "application_command_delete": await this.OnApplicationCommandDeleteAsync(dat.ToObject(), (ulong?)dat["guild_id"]).ConfigureAwait(false); break; case "guild_application_command_counts_update": var counts = dat["application_command_counts"]; await this.OnGuildApplicationCommandCountsUpdateAsync((int)counts["1"], (int)counts["2"], (int)counts["3"], (ulong)dat["guild_id"]).ConfigureAwait(false); break; case "application_command_permissions_update": var aid = (ulong)dat["application_id"]; if (aid != this.CurrentApplication.Id) return; var pms = dat["permissions"].ToObject>(); gid = (ulong)dat["guild_id"]; await this.OnApplicationCommandPermissionsUpdateAsync(pms, (ulong)dat["id"], gid, aid).ConfigureAwait(false); break; #endregion #region Misc case "gift_code_update": //Not supposed to be dispatched to bots break; case "typing_start": cid = (ulong)dat["channel_id"]; rawMbr = dat["member"]; if (rawMbr != null) mbr = rawMbr.ToObject(); await this.OnTypingStartEventAsync((ulong)dat["user_id"], cid, this.InternalGetCachedChannel(cid), (ulong?)dat["guild_id"], Utilities.GetDateTimeOffset((long)dat["timestamp"]), mbr).ConfigureAwait(false); break; case "webhooks_update": gid = (ulong)dat["guild_id"]; cid = (ulong)dat["channel_id"]; await this.OnWebhooksUpdateAsync(this.GuildsInternal[gid].GetChannel(cid), this.GuildsInternal[gid]).ConfigureAwait(false); break; default: await this.OnUnknownEventAsync(payload).ConfigureAwait(false); this.Logger.LogWarning(LoggerEvents.WebSocketReceive, "Unknown event: {0}\npayload: {1}", payload.EventName, payload.Data); break; #endregion } } #endregion #region Events #region Gateway /// /// Handles the ready event. /// /// The ready payload. /// The raw guilds. internal async Task OnReadyEventAsync(ReadyPayload ready, JArray rawGuilds) { //ready.CurrentUser.Discord = this; var rusr = ready.CurrentUser; this.CurrentUser.Username = rusr.Username; this.CurrentUser.Discriminator = rusr.Discriminator; this.CurrentUser.AvatarHash = rusr.AvatarHash; this.CurrentUser.MfaEnabled = rusr.MfaEnabled; this.CurrentUser.Verified = rusr.Verified; this.CurrentUser.IsBot = rusr.IsBot; this.CurrentUser.Flags = rusr.Flags; this.GatewayVersion = ready.GatewayVersion; this._sessionId = ready.SessionId; var rawGuildIndex = rawGuilds.ToDictionary(xt => (ulong)xt["id"], xt => (JObject)xt); this.GuildsInternal.Clear(); foreach (var guild in ready.Guilds) { guild.Discord = this; if (guild.ChannelsInternal == null) guild.ChannelsInternal = new ConcurrentDictionary(); foreach (var xc in guild.Channels.Values) { xc.GuildId = guild.Id; xc.Discord = this; foreach (var xo in xc.PermissionOverwritesInternal) { xo.Discord = this; xo.ChannelId = xc.Id; } } if (guild.RolesInternal == null) guild.RolesInternal = new ConcurrentDictionary(); foreach (var xr in guild.Roles.Values) { xr.Discord = this; xr.GuildId = guild.Id; } var rawGuild = rawGuildIndex[guild.Id]; var rawMembers = (JArray)rawGuild["members"]; if (guild.MembersInternal != null) guild.MembersInternal.Clear(); else guild.MembersInternal = new ConcurrentDictionary(); if (rawMembers != null) { foreach (var xj in rawMembers) { var xtm = xj.ToObject(); var xu = new DiscordUser(xtm.User) { Discord = this }; xu = this.UserCache.AddOrUpdate(xtm.User.Id, xu, (id, old) => { old.Username = xu.Username; old.Discriminator = xu.Discriminator; old.AvatarHash = xu.AvatarHash; return old; }); guild.MembersInternal[xtm.User.Id] = new DiscordMember(xtm) { Discord = this, GuildId = guild.Id }; } } if (guild.EmojisInternal == null) guild.EmojisInternal = new ConcurrentDictionary(); foreach (var xe in guild.Emojis.Values) xe.Discord = this; if (guild.StickersInternal == null) guild.StickersInternal = new ConcurrentDictionary(); foreach (var xs in guild.Stickers.Values) xs.Discord = this; if (guild.VoiceStatesInternal == null) guild.VoiceStatesInternal = new ConcurrentDictionary(); foreach (var xvs in guild.VoiceStates.Values) xvs.Discord = this; if (guild.ThreadsInternal == null) guild.ThreadsInternal = new ConcurrentDictionary(); foreach (var xt in guild.ThreadsInternal.Values) xt.Discord = this; if (guild.StageInstancesInternal == null) guild.StageInstancesInternal = new ConcurrentDictionary(); foreach (var xsi in guild.StageInstancesInternal.Values) xsi.Discord = this; if (guild.ScheduledEventsInternal == null) guild.ScheduledEventsInternal = new ConcurrentDictionary(); foreach (var xse in guild.ScheduledEventsInternal.Values) xse.Discord = this; this.GuildsInternal[guild.Id] = guild; } await this._ready.InvokeAsync(this, new ReadyEventArgs(this.ServiceProvider)).ConfigureAwait(false); } /// /// Handles the resumed event. /// internal Task OnResumedAsync() { this.Logger.LogInformation(LoggerEvents.SessionUpdate, "Session resumed"); return this._resumed.InvokeAsync(this, new ReadyEventArgs(this.ServiceProvider)); } #endregion #region Channel /// /// Handles the channel create event. /// /// The channel. internal async Task OnChannelCreateEventAsync(DiscordChannel channel) { channel.Discord = this; foreach (var xo in channel.PermissionOverwritesInternal) { xo.Discord = this; xo.ChannelId = channel.Id; } this.GuildsInternal[channel.GuildId.Value].ChannelsInternal[channel.Id] = channel; /*if (this.Configuration.AutoRefreshChannelCache) { await this.RefreshChannelsAsync(channel.Guild.Id); }*/ await this._channelCreated.InvokeAsync(this, new ChannelCreateEventArgs(this.ServiceProvider) { Channel = channel, Guild = channel.Guild }).ConfigureAwait(false); } /// /// Handles the channel update event. /// /// The channel. internal async Task OnChannelUpdateEventAsync(DiscordChannel channel) { if (channel == null) return; channel.Discord = this; var gld = channel.Guild; var channelNew = this.InternalGetCachedChannel(channel.Id); DiscordChannel channelOld = null; if (channelNew != null) { channelOld = new DiscordChannel { Bitrate = channelNew.Bitrate, Discord = this, GuildId = channelNew.GuildId, Id = channelNew.Id, LastMessageId = channelNew.LastMessageId, Name = channelNew.Name, PermissionOverwritesInternal = new List(channelNew.PermissionOverwritesInternal), Position = channelNew.Position, Topic = channelNew.Topic, Type = channelNew.Type, UserLimit = channelNew.UserLimit, ParentId = channelNew.ParentId, IsNsfw = channelNew.IsNsfw, PerUserRateLimit = channelNew.PerUserRateLimit, RtcRegionId = channelNew.RtcRegionId, QualityMode = channelNew.QualityMode, DefaultAutoArchiveDuration = channelNew.DefaultAutoArchiveDuration }; channelNew.Bitrate = channel.Bitrate; channelNew.Name = channel.Name; channelNew.Position = channel.Position; channelNew.Topic = channel.Topic; channelNew.UserLimit = channel.UserLimit; channelNew.ParentId = channel.ParentId; channelNew.IsNsfw = channel.IsNsfw; channelNew.PerUserRateLimit = channel.PerUserRateLimit; channelNew.Type = channel.Type; channelNew.RtcRegionId = channel.RtcRegionId; channelNew.QualityMode = channel.QualityMode; channelNew.DefaultAutoArchiveDuration = channel.DefaultAutoArchiveDuration; channelNew.PermissionOverwritesInternal.Clear(); foreach (var po in channel.PermissionOverwritesInternal) { po.Discord = this; po.ChannelId = channel.Id; } channelNew.PermissionOverwritesInternal.AddRange(channel.PermissionOverwritesInternal); if (this.Configuration.AutoRefreshChannelCache && gld != null) { await this.RefreshChannelsAsync(channel.Guild.Id); } } else if (gld != null) { gld.ChannelsInternal[channel.Id] = channel; if (this.Configuration.AutoRefreshChannelCache) { await this.RefreshChannelsAsync(channel.Guild.Id); } } await this._channelUpdated.InvokeAsync(this, new ChannelUpdateEventArgs(this.ServiceProvider) { ChannelAfter = channelNew, Guild = gld, ChannelBefore = channelOld }).ConfigureAwait(false); } /// /// Handles the channel delete event. /// /// The channel. internal async Task OnChannelDeleteEventAsync(DiscordChannel channel) { if (channel == null) return; channel.Discord = this; //if (channel.IsPrivate) if (channel.Type == ChannelType.Group || channel.Type == ChannelType.Private) { var dmChannel = channel as DiscordDmChannel; await this._dmChannelDeleted.InvokeAsync(this, new DmChannelDeleteEventArgs(this.ServiceProvider) { Channel = dmChannel }).ConfigureAwait(false); } else { var gld = channel.Guild; if (gld.ChannelsInternal.TryRemove(channel.Id, out var cachedChannel)) channel = cachedChannel; if (this.Configuration.AutoRefreshChannelCache) { await this.RefreshChannelsAsync(channel.Guild.Id); } await this._channelDeleted.InvokeAsync(this, new ChannelDeleteEventArgs(this.ServiceProvider) { Channel = channel, Guild = gld }).ConfigureAwait(false); } } /// /// Refreshes the channels. /// /// The guild id. internal async Task RefreshChannelsAsync(ulong guildId) { var guild = this.InternalGetCachedGuild(guildId); var channels = await this.ApiClient.GetGuildChannelsAsync(guildId); guild.ChannelsInternal.Clear(); foreach (var channel in channels.ToList()) { channel.Discord = this; foreach (var xo in channel.PermissionOverwritesInternal) { xo.Discord = this; xo.ChannelId = channel.Id; } guild.ChannelsInternal[channel.Id] = channel; } } /// /// Handles the channel pins update event. /// /// The optional guild id. /// The channel id. /// The optional last pin timestamp. internal async Task OnChannelPinsUpdateAsync(ulong? guildId, ulong channelId, DateTimeOffset? lastPinTimestamp) { var guild = this.InternalGetCachedGuild(guildId); var channel = this.InternalGetCachedChannel(channelId) ?? this.InternalGetCachedThread(channelId); var ea = new ChannelPinsUpdateEventArgs(this.ServiceProvider) { Guild = guild, Channel = channel, LastPinTimestamp = lastPinTimestamp }; await this._channelPinsUpdated.InvokeAsync(this, ea).ConfigureAwait(false); } #endregion #region Guild /// /// Handles the guild create event. /// /// The guild. /// The raw members. /// The presences. internal async Task OnGuildCreateEventAsync(DiscordGuild guild, JArray rawMembers, IEnumerable presences) { if (presences != null) { foreach (var xp in presences) { xp.Discord = this; xp.GuildId = guild.Id; xp.Activity = new DiscordActivity(xp.RawActivity); if (xp.RawActivities != null) { xp.InternalActivities = new DiscordActivity[xp.RawActivities.Length]; for (var i = 0; i < xp.RawActivities.Length; i++) xp.InternalActivities[i] = new DiscordActivity(xp.RawActivities[i]); } this.PresencesInternal[xp.InternalUser.Id] = xp; } } var exists = this.GuildsInternal.TryGetValue(guild.Id, out var foundGuild); guild.Discord = this; guild.IsUnavailable = false; var eventGuild = guild; if (exists) guild = foundGuild; if (guild.ChannelsInternal == null) guild.ChannelsInternal = new ConcurrentDictionary(); if (guild.ThreadsInternal == null) guild.ThreadsInternal = new ConcurrentDictionary(); if (guild.RolesInternal == null) guild.RolesInternal = new ConcurrentDictionary(); if (guild.ThreadsInternal == null) guild.ThreadsInternal = new ConcurrentDictionary(); if (guild.StickersInternal == null) guild.StickersInternal = new ConcurrentDictionary(); if (guild.EmojisInternal == null) guild.EmojisInternal = new ConcurrentDictionary(); if (guild.VoiceStatesInternal == null) guild.VoiceStatesInternal = new ConcurrentDictionary(); if (guild.MembersInternal == null) guild.MembersInternal = new ConcurrentDictionary(); if (guild.ScheduledEventsInternal == null) guild.ScheduledEventsInternal = new ConcurrentDictionary(); this.UpdateCachedGuild(eventGuild, rawMembers); guild.JoinedAt = eventGuild.JoinedAt; guild.IsLarge = eventGuild.IsLarge; guild.MemberCount = Math.Max(eventGuild.MemberCount, guild.MembersInternal.Count); guild.IsUnavailable = eventGuild.IsUnavailable; guild.PremiumSubscriptionCount = eventGuild.PremiumSubscriptionCount; guild.PremiumTier = eventGuild.PremiumTier; guild.BannerHash = eventGuild.BannerHash; guild.VanityUrlCode = eventGuild.VanityUrlCode; guild.Description = eventGuild.Description; guild.IsNsfw = eventGuild.IsNsfw; foreach (var kvp in eventGuild.VoiceStatesInternal) guild.VoiceStatesInternal[kvp.Key] = kvp.Value; foreach (var kvp in eventGuild.ChannelsInternal) guild.ChannelsInternal[kvp.Key] = kvp.Value; foreach (var kvp in eventGuild.RolesInternal) guild.RolesInternal[kvp.Key] = kvp.Value; foreach (var kvp in eventGuild.EmojisInternal) guild.EmojisInternal[kvp.Key] = kvp.Value; foreach (var kvp in eventGuild.ThreadsInternal) guild.ThreadsInternal[kvp.Key] = kvp.Value; foreach (var kvp in eventGuild.StickersInternal) guild.StickersInternal[kvp.Key] = kvp.Value; foreach (var kvp in eventGuild.StageInstancesInternal) guild.StageInstancesInternal[kvp.Key] = kvp.Value; foreach (var kvp in eventGuild.ScheduledEventsInternal) guild.ScheduledEventsInternal[kvp.Key] = kvp.Value; foreach (var xc in guild.ChannelsInternal.Values) { xc.GuildId = guild.Id; xc.Discord = this; foreach (var xo in xc.PermissionOverwritesInternal) { xo.Discord = this; xo.ChannelId = xc.Id; } } foreach (var xt in guild.ThreadsInternal.Values) { xt.GuildId = guild.Id; xt.Discord = this; } foreach (var xe in guild.EmojisInternal.Values) xe.Discord = this; foreach (var xs in guild.StickersInternal.Values) xs.Discord = this; foreach (var xvs in guild.VoiceStatesInternal.Values) xvs.Discord = this; foreach (var xsi in guild.StageInstancesInternal.Values) { xsi.Discord = this; xsi.GuildId = guild.Id; } foreach (var xr in guild.RolesInternal.Values) { xr.Discord = this; xr.GuildId = guild.Id; } foreach (var xse in guild.ScheduledEventsInternal.Values) { xse.Discord = this; xse.GuildId = guild.Id; if (xse.Creator != null) xse.Creator.Discord = this; } var old = Volatile.Read(ref this._guildDownloadCompleted); var dcompl = this.GuildsInternal.Values.All(xg => !xg.IsUnavailable); Volatile.Write(ref this._guildDownloadCompleted, dcompl); if (exists) await this._guildAvailable.InvokeAsync(this, new GuildCreateEventArgs(this.ServiceProvider) { Guild = guild }).ConfigureAwait(false); else await this._guildCreated.InvokeAsync(this, new GuildCreateEventArgs(this.ServiceProvider) { Guild = guild }).ConfigureAwait(false); if (dcompl && !old) await this._guildDownloadCompletedEv.InvokeAsync(this, new GuildDownloadCompletedEventArgs(this.Guilds, this.ServiceProvider)).ConfigureAwait(false); } /// /// Handles the guild update event. /// /// The guild. /// The raw members. internal async Task OnGuildUpdateEventAsync(DiscordGuild guild, JArray rawMembers) { DiscordGuild oldGuild; if (!this.GuildsInternal.ContainsKey(guild.Id)) { this.GuildsInternal[guild.Id] = guild; oldGuild = null; } else { var gld = this.GuildsInternal[guild.Id]; oldGuild = new DiscordGuild { Discord = gld.Discord, Name = gld.Name, AfkChannelId = gld.AfkChannelId, AfkTimeout = gld.AfkTimeout, ApplicationId = gld.ApplicationId, DefaultMessageNotifications = gld.DefaultMessageNotifications, ExplicitContentFilter = gld.ExplicitContentFilter, RawFeatures = gld.RawFeatures, IconHash = gld.IconHash, Id = gld.Id, IsLarge = gld.IsLarge, IsSynced = gld.IsSynced, IsUnavailable = gld.IsUnavailable, JoinedAt = gld.JoinedAt, MemberCount = gld.MemberCount, MaxMembers = gld.MaxMembers, MaxPresences = gld.MaxPresences, ApproximateMemberCount = gld.ApproximateMemberCount, ApproximatePresenceCount = gld.ApproximatePresenceCount, MaxVideoChannelUsers = gld.MaxVideoChannelUsers, DiscoverySplashHash = gld.DiscoverySplashHash, PreferredLocale = gld.PreferredLocale, MfaLevel = gld.MfaLevel, OwnerId = gld.OwnerId, SplashHash = gld.SplashHash, SystemChannelId = gld.SystemChannelId, SystemChannelFlags = gld.SystemChannelFlags, Description = gld.Description, WidgetEnabled = gld.WidgetEnabled, WidgetChannelId = gld.WidgetChannelId, VerificationLevel = gld.VerificationLevel, RulesChannelId = gld.RulesChannelId, PublicUpdatesChannelId = gld.PublicUpdatesChannelId, VoiceRegionId = gld.VoiceRegionId, IsNsfw = gld.IsNsfw, PremiumProgressBarEnabled = gld.PremiumProgressBarEnabled, PremiumSubscriptionCount = gld.PremiumSubscriptionCount, PremiumTier = gld.PremiumTier, ChannelsInternal = new ConcurrentDictionary(), ThreadsInternal = new ConcurrentDictionary(), EmojisInternal = new ConcurrentDictionary(), StickersInternal = new ConcurrentDictionary(), MembersInternal = new ConcurrentDictionary(), RolesInternal = new ConcurrentDictionary(), StageInstancesInternal = new ConcurrentDictionary(), VoiceStatesInternal = new ConcurrentDictionary(), ScheduledEventsInternal = new ConcurrentDictionary() }; foreach (var kvp in gld.ChannelsInternal) oldGuild.ChannelsInternal[kvp.Key] = kvp.Value; foreach (var kvp in gld.ThreadsInternal) oldGuild.ThreadsInternal[kvp.Key] = kvp.Value; foreach (var kvp in gld.EmojisInternal) oldGuild.EmojisInternal[kvp.Key] = kvp.Value; foreach (var kvp in gld.StickersInternal) oldGuild.StickersInternal[kvp.Key] = kvp.Value; foreach (var kvp in gld.RolesInternal) oldGuild.RolesInternal[kvp.Key] = kvp.Value; foreach (var kvp in gld.VoiceStatesInternal) oldGuild.VoiceStatesInternal[kvp.Key] = kvp.Value; foreach (var kvp in gld.MembersInternal) oldGuild.MembersInternal[kvp.Key] = kvp.Value; foreach (var kvp in gld.StageInstancesInternal) oldGuild.StageInstancesInternal[kvp.Key] = kvp.Value; foreach (var kvp in gld.ScheduledEventsInternal) oldGuild.ScheduledEventsInternal[kvp.Key] = kvp.Value; } guild.Discord = this; guild.IsUnavailable = false; var eventGuild = guild; guild = this.GuildsInternal[eventGuild.Id]; if (guild.ChannelsInternal == null) guild.ChannelsInternal = new ConcurrentDictionary(); if (guild.ThreadsInternal == null) guild.ThreadsInternal = new ConcurrentDictionary(); if (guild.RolesInternal == null) guild.RolesInternal = new ConcurrentDictionary(); if (guild.EmojisInternal == null) guild.EmojisInternal = new ConcurrentDictionary(); if (guild.StickersInternal == null) guild.StickersInternal = new ConcurrentDictionary(); if (guild.VoiceStatesInternal == null) guild.VoiceStatesInternal = new ConcurrentDictionary(); if (guild.StageInstancesInternal == null) guild.StageInstancesInternal = new ConcurrentDictionary(); if (guild.MembersInternal == null) guild.MembersInternal = new ConcurrentDictionary(); if (guild.ScheduledEventsInternal == null) guild.ScheduledEventsInternal = new ConcurrentDictionary(); this.UpdateCachedGuild(eventGuild, rawMembers); foreach (var xc in guild.ChannelsInternal.Values) { xc.GuildId = guild.Id; xc.Discord = this; foreach (var xo in xc.PermissionOverwritesInternal) { xo.Discord = this; xo.ChannelId = xc.Id; } } foreach (var xc in guild.ThreadsInternal.Values) { xc.GuildId = guild.Id; xc.Discord = this; } foreach (var xe in guild.EmojisInternal.Values) xe.Discord = this; foreach (var xs in guild.StickersInternal.Values) xs.Discord = this; foreach (var xvs in guild.VoiceStatesInternal.Values) xvs.Discord = this; foreach (var xr in guild.RolesInternal.Values) { xr.Discord = this; xr.GuildId = guild.Id; } foreach (var xsi in guild.StageInstancesInternal.Values) { xsi.Discord = this; xsi.GuildId = guild.Id; } foreach (var xse in guild.ScheduledEventsInternal.Values) { xse.Discord = this; xse.GuildId = guild.Id; if (xse.Creator != null) xse.Creator.Discord = this; } await this._guildUpdated.InvokeAsync(this, new GuildUpdateEventArgs(this.ServiceProvider) { GuildBefore = oldGuild, GuildAfter = guild }).ConfigureAwait(false); } /// /// Handles the guild delete event. /// /// The guild. internal async Task OnGuildDeleteEventAsync(DiscordGuild guild) { if (guild.IsUnavailable) { if (!this.GuildsInternal.TryGetValue(guild.Id, out var gld)) return; gld.IsUnavailable = true; await this._guildUnavailable.InvokeAsync(this, new GuildDeleteEventArgs(this.ServiceProvider) { Guild = guild, Unavailable = true }).ConfigureAwait(false); } else { if (!this.GuildsInternal.TryRemove(guild.Id, out var gld)) return; await this._guildDeleted.InvokeAsync(this, new GuildDeleteEventArgs(this.ServiceProvider) { Guild = gld }).ConfigureAwait(false); } } /// /// Handles the guild sync event. /// /// The guild. /// Whether the guild is a large guild.. /// The raw members. /// The presences. internal async Task OnGuildSyncEventAsync(DiscordGuild guild, bool isLarge, JArray rawMembers, IEnumerable presences) { presences = presences.Select(xp => { xp.Discord = this; xp.Activity = new DiscordActivity(xp.RawActivity); return xp; }); foreach (var xp in presences) this.PresencesInternal[xp.InternalUser.Id] = xp; guild.IsSynced = true; guild.IsLarge = isLarge; this.UpdateCachedGuild(guild, rawMembers); await this._guildAvailable.InvokeAsync(this, new GuildCreateEventArgs(this.ServiceProvider) { Guild = guild }).ConfigureAwait(false); } /// /// Handles the guild emojis update event. /// /// The guild. /// The new emojis. internal async Task OnGuildEmojisUpdateEventAsync(DiscordGuild guild, IEnumerable newEmojis) { var oldEmojis = new ConcurrentDictionary(guild.EmojisInternal); guild.EmojisInternal.Clear(); foreach (var emoji in newEmojis) { emoji.Discord = this; guild.EmojisInternal[emoji.Id] = emoji; } var ea = new GuildEmojisUpdateEventArgs(this.ServiceProvider) { Guild = guild, EmojisAfter = guild.Emojis, EmojisBefore = new ReadOnlyConcurrentDictionary(oldEmojis) }; await this._guildEmojisUpdated.InvokeAsync(this, ea).ConfigureAwait(false); } /// /// Handles the stickers updated. /// /// The new stickers. /// The guild id. internal async Task OnStickersUpdatedAsync(IEnumerable newStickers, ulong guildId) { var guild = this.InternalGetCachedGuild(guildId); var oldStickers = new ConcurrentDictionary(guild.StickersInternal); guild.StickersInternal.Clear(); foreach (var nst in newStickers) { if (nst.User is not null) { nst.User.Discord = this; this.UserCache.AddOrUpdate(nst.User.Id, nst.User, (old, @new) => @new); } nst.Discord = this; guild.StickersInternal[nst.Id] = nst; } var sea = new GuildStickersUpdateEventArgs(this.ServiceProvider) { Guild = guild, StickersBefore = oldStickers, StickersAfter = guild.Stickers }; await this._guildStickersUpdated.InvokeAsync(this, sea).ConfigureAwait(false); } #endregion #region Guild Ban /// /// Handles the guild ban add event. /// /// The transport user. /// The guild. internal async Task OnGuildBanAddEventAsync(TransportUser user, DiscordGuild guild) { var usr = new DiscordUser(user) { Discord = this }; usr = this.UserCache.AddOrUpdate(user.Id, usr, (id, old) => { old.Username = usr.Username; old.Discriminator = usr.Discriminator; old.AvatarHash = usr.AvatarHash; return old; }); if (!guild.Members.TryGetValue(user.Id, out var mbr)) mbr = new DiscordMember(usr) { Discord = this, GuildId = guild.Id }; var ea = new GuildBanAddEventArgs(this.ServiceProvider) { Guild = guild, Member = mbr }; await this._guildBanAdded.InvokeAsync(this, ea).ConfigureAwait(false); } /// /// Handles the guild ban remove event. /// /// The transport user. /// The guild. internal async Task OnGuildBanRemoveEventAsync(TransportUser user, DiscordGuild guild) { var usr = new DiscordUser(user) { Discord = this }; usr = this.UserCache.AddOrUpdate(user.Id, usr, (id, old) => { old.Username = usr.Username; old.Discriminator = usr.Discriminator; old.AvatarHash = usr.AvatarHash; return old; }); if (!guild.Members.TryGetValue(user.Id, out var mbr)) mbr = new DiscordMember(usr) { Discord = this, GuildId = guild.Id }; var ea = new GuildBanRemoveEventArgs(this.ServiceProvider) { Guild = guild, Member = mbr }; await this._guildBanRemoved.InvokeAsync(this, ea).ConfigureAwait(false); } #endregion #region Guild Scheduled Event /// /// Handles the scheduled event create event. /// /// The created event. /// The guild. internal async Task OnGuildScheduledEventCreateEventAsync(DiscordScheduledEvent scheduledEvent, DiscordGuild guild) { scheduledEvent.Discord = this; guild.ScheduledEventsInternal.AddOrUpdate(scheduledEvent.Id, scheduledEvent, (old, newScheduledEvent) => newScheduledEvent); if (scheduledEvent.Creator != null) { scheduledEvent.Creator.Discord = this; this.UserCache.AddOrUpdate(scheduledEvent.Creator.Id, scheduledEvent.Creator, (id, old) => { old.Username = scheduledEvent.Creator.Username; old.Discriminator = scheduledEvent.Creator.Discriminator; old.AvatarHash = scheduledEvent.Creator.AvatarHash; old.Flags = scheduledEvent.Creator.Flags; return old; }); } await this._guildScheduledEventCreated.InvokeAsync(this, new GuildScheduledEventCreateEventArgs(this.ServiceProvider) { ScheduledEvent = scheduledEvent, Guild = scheduledEvent.Guild }).ConfigureAwait(false); } /// /// Handles the scheduled event update event. /// /// The updated event. /// The guild. internal async Task OnGuildScheduledEventUpdateEventAsync(DiscordScheduledEvent scheduledEvent, DiscordGuild guild) { if (guild == null) return; DiscordScheduledEvent oldEvent; if (!guild.ScheduledEventsInternal.ContainsKey(scheduledEvent.Id)) { oldEvent = null; } else { var ev = guild.ScheduledEventsInternal[scheduledEvent.Id]; oldEvent = new DiscordScheduledEvent { Id = ev.Id, ChannelId = ev.ChannelId, EntityId = ev.EntityId, EntityMetadata = ev.EntityMetadata, CreatorId = ev.CreatorId, Creator = ev.Creator, Discord = this, Description = ev.Description, EntityType = ev.EntityType, ScheduledStartTimeRaw = ev.ScheduledStartTimeRaw, ScheduledEndTimeRaw = ev.ScheduledEndTimeRaw, GuildId = ev.GuildId, Status = ev.Status, Name = ev.Name, UserCount = ev.UserCount }; } if (scheduledEvent.Creator != null) { scheduledEvent.Creator.Discord = this; this.UserCache.AddOrUpdate(scheduledEvent.Creator.Id, scheduledEvent.Creator, (id, old) => { old.Username = scheduledEvent.Creator.Username; old.Discriminator = scheduledEvent.Creator.Discriminator; old.AvatarHash = scheduledEvent.Creator.AvatarHash; old.Flags = scheduledEvent.Creator.Flags; return old; }); } if (scheduledEvent.Status == ScheduledEventStatus.Completed) { guild.ScheduledEventsInternal.TryRemove(scheduledEvent.Id, out var deletedEvent); await this._guildScheduledEventDeleted.InvokeAsync(this, new GuildScheduledEventDeleteEventArgs(this.ServiceProvider) { ScheduledEvent = scheduledEvent, Guild = guild, Reason = ScheduledEventStatus.Completed }).ConfigureAwait(false); } else if (scheduledEvent.Status == ScheduledEventStatus.Canceled) { guild.ScheduledEventsInternal.TryRemove(scheduledEvent.Id, out var deletedEvent); scheduledEvent.Status = ScheduledEventStatus.Canceled; await this._guildScheduledEventDeleted.InvokeAsync(this, new GuildScheduledEventDeleteEventArgs(this.ServiceProvider) { ScheduledEvent = scheduledEvent, Guild = guild, Reason = ScheduledEventStatus.Canceled }).ConfigureAwait(false); } else { this.UpdateScheduledEvent(scheduledEvent, guild); await this._guildScheduledEventUpdated.InvokeAsync(this, new GuildScheduledEventUpdateEventArgs(this.ServiceProvider) { ScheduledEventBefore = oldEvent, ScheduledEventAfter = scheduledEvent, Guild = guild }).ConfigureAwait(false); } } /// /// Handles the scheduled event delete event. /// /// The deleted event. /// The guild. internal async Task OnGuildScheduledEventDeleteEventAsync(DiscordScheduledEvent scheduledEvent, DiscordGuild guild) { scheduledEvent.Discord = this; if (scheduledEvent.Status == ScheduledEventStatus.Scheduled) scheduledEvent.Status = ScheduledEventStatus.Canceled; if (scheduledEvent.Creator != null) { scheduledEvent.Creator.Discord = this; this.UserCache.AddOrUpdate(scheduledEvent.Creator.Id, scheduledEvent.Creator, (id, old) => { old.Username = scheduledEvent.Creator.Username; old.Discriminator = scheduledEvent.Creator.Discriminator; old.AvatarHash = scheduledEvent.Creator.AvatarHash; old.Flags = scheduledEvent.Creator.Flags; return old; }); } await this._guildScheduledEventDeleted.InvokeAsync(this, new GuildScheduledEventDeleteEventArgs(this.ServiceProvider) { ScheduledEvent = scheduledEvent, Guild = scheduledEvent.Guild, Reason = scheduledEvent.Status }).ConfigureAwait(false); guild.ScheduledEventsInternal.TryRemove(scheduledEvent.Id, out var deletedEvent); } /// /// Handles the scheduled event user add event. /// The event. /// The added user id. /// The guild. /// internal async Task OnGuildScheduledEventUserAddedEventAsync(ulong guildScheduledEventId, ulong userId, DiscordGuild guild) { var scheduledEvent = this.InternalGetCachedScheduledEvent(guildScheduledEventId) ?? this.UpdateScheduledEvent(new DiscordScheduledEvent { Id = guildScheduledEventId, GuildId = guild.Id, Discord = this, UserCount = 0 }, guild); scheduledEvent.UserCount++; scheduledEvent.Discord = this; guild.Discord = this; var user = this.GetUserAsync(userId, true).Result; user.Discord = this; var member = guild.Members.TryGetValue(userId, out var mem) ? mem : guild.GetMemberAsync(userId).Result; member.Discord = this; await this._guildScheduledEventUserAdded.InvokeAsync(this, new GuildScheduledEventUserAddEventArgs(this.ServiceProvider) { ScheduledEvent = scheduledEvent, Guild = guild, User = user, Member = member }).ConfigureAwait(false); } /// /// Handles the scheduled event user remove event. /// The event. /// The removed user id. /// The guild. /// internal async Task OnGuildScheduledEventUserRemovedEventAsync(ulong guildScheduledEventId, ulong userId, DiscordGuild guild) { var scheduledEvent = this.InternalGetCachedScheduledEvent(guildScheduledEventId) ?? this.UpdateScheduledEvent(new DiscordScheduledEvent { Id = guildScheduledEventId, GuildId = guild.Id, Discord = this, UserCount = 0 }, guild); scheduledEvent.UserCount = scheduledEvent.UserCount == 0 ? 0 : scheduledEvent.UserCount - 1; scheduledEvent.Discord = this; guild.Discord = this; var user = this.GetUserAsync(userId, true).Result; user.Discord = this; var member = guild.Members.TryGetValue(userId, out var mem) ? mem : guild.GetMemberAsync(userId).Result; member.Discord = this; await this._guildScheduledEventUserRemoved.InvokeAsync(this, new GuildScheduledEventUserRemoveEventArgs(this.ServiceProvider) { ScheduledEvent = scheduledEvent, Guild = guild, User = user, Member = member }).ConfigureAwait(false); } #endregion #region Guild Integration /// /// Handles the guild integration create event. /// /// The guild. /// The integration. internal async Task OnGuildIntegrationCreateEventAsync(DiscordGuild guild, DiscordIntegration integration) { integration.Discord = this; await this._guildIntegrationCreated.InvokeAsync(this, new GuildIntegrationCreateEventArgs(this.ServiceProvider) { Integration = integration, Guild = guild }).ConfigureAwait(false); } /// /// Handles the guild integration update event. /// /// The guild. /// The integration. internal async Task OnGuildIntegrationUpdateEventAsync(DiscordGuild guild, DiscordIntegration integration) { integration.Discord = this; await this._guildIntegrationUpdated.InvokeAsync(this, new GuildIntegrationUpdateEventArgs(this.ServiceProvider) { Integration = integration, Guild = guild }).ConfigureAwait(false); } /// /// Handles the guild integrations update event. /// /// The guild. internal async Task OnGuildIntegrationsUpdateEventAsync(DiscordGuild guild) { var ea = new GuildIntegrationsUpdateEventArgs(this.ServiceProvider) { Guild = guild }; await this._guildIntegrationsUpdated.InvokeAsync(this, ea).ConfigureAwait(false); } /// /// Handles the guild integration delete event. /// /// The guild. /// The integration id. /// The optional application id. internal async Task OnGuildIntegrationDeleteEventAsync(DiscordGuild guild, ulong integrationId, ulong? applicationId) => await this._guildIntegrationDeleted.InvokeAsync(this, new GuildIntegrationDeleteEventArgs(this.ServiceProvider) { Guild = guild, IntegrationId = integrationId, ApplicationId = applicationId }).ConfigureAwait(false); #endregion #region Guild Member /// /// Handles the guild member add event. /// /// The transport member. /// The guild. internal async Task OnGuildMemberAddEventAsync(TransportMember member, DiscordGuild guild) { var usr = new DiscordUser(member.User) { Discord = this }; usr = this.UserCache.AddOrUpdate(member.User.Id, usr, (id, old) => { old.Username = usr.Username; old.Discriminator = usr.Discriminator; old.AvatarHash = usr.AvatarHash; return old; }); var mbr = new DiscordMember(member) { Discord = this, GuildId = guild.Id }; guild.MembersInternal[mbr.Id] = mbr; guild.MemberCount++; var ea = new GuildMemberAddEventArgs(this.ServiceProvider) { Guild = guild, Member = mbr }; await this._guildMemberAdded.InvokeAsync(this, ea).ConfigureAwait(false); } /// /// Handles the guild member remove event. /// /// The transport user. /// The guild. internal async Task OnGuildMemberRemoveEventAsync(TransportUser user, DiscordGuild guild) { var usr = new DiscordUser(user); if (!guild.MembersInternal.TryRemove(user.Id, out var mbr)) mbr = new DiscordMember(usr) { Discord = this, GuildId = guild.Id }; guild.MemberCount--; _ = this.UserCache.AddOrUpdate(user.Id, usr, (old, @new) => @new); var ea = new GuildMemberRemoveEventArgs(this.ServiceProvider) { Guild = guild, Member = mbr }; await this._guildMemberRemoved.InvokeAsync(this, ea).ConfigureAwait(false); } /// /// Handles the guild member update event. /// /// The transport member. /// The guild. /// The roles. /// The nick. /// Whether the member is pending. internal async Task OnGuildMemberUpdateEventAsync(TransportMember member, DiscordGuild guild, IEnumerable roles, string nick, bool? pending) { var usr = new DiscordUser(member.User) { Discord = this }; usr = this.UserCache.AddOrUpdate(usr.Id, usr, (id, old) => { old.Username = usr.Username; old.Discriminator = usr.Discriminator; old.AvatarHash = usr.AvatarHash; return old; }); if (!guild.Members.TryGetValue(member.User.Id, out var mbr)) mbr = new DiscordMember(usr) { Discord = this, GuildId = guild.Id }; + var old = mbr; var nickOld = mbr.Nickname; var pendingOld = mbr.IsPending; var rolesOld = new ReadOnlyCollection(new List(mbr.Roles)); var cduOld = mbr.CommunicationDisabledUntil; mbr.AvatarHashInternal = member.AvatarHash; mbr.GuildAvatarHash = member.GuildAvatarHash; mbr.Nickname = nick; mbr.IsPending = pending; mbr.CommunicationDisabledUntil = member.CommunicationDisabledUntil; mbr.RoleIdsInternal.Clear(); mbr.RoleIdsInternal.AddRange(roles); + if (!guild.MembersInternal.TryUpdate(member.User.Id, mbr, old)) + guild.MembersInternal.TryAdd(member.User.Id, mbr); + + var timeoutUntil = member.CommunicationDisabledUntil; + this.Logger.LogTrace($"Timeout:\nBefore - {cduOld}\nAfter - {timeoutUntil}"); + if ((timeoutUntil.HasValue && cduOld.HasValue) || (timeoutUntil == null && cduOld.HasValue) || (timeoutUntil.HasValue && cduOld == null)) + { + // We are going to add a scheduled timer to assure that we get a auditlog entry. + + var id = $"tt-{mbr.Id}-{guild.Id}-{DateTime.Now.ToLongTimeString()}"; + + this._tempTimers.Add( + id, + new( + new TimeoutHandler( + mbr, + guild, + cduOld, + timeoutUntil + ), + new Timer( + this.TimeoutTimer, + id, + 2000, + Timeout.Infinite + ) + ) + ); + + this.Logger.LogTrace("Scheduling timeout event."); + + return; + } - var ea = new GuildMemberUpdateEventArgs(this.ServiceProvider) + this.Logger.LogTrace("No timeout detected. Continuing on normal operation."); + + var eargs = new GuildMemberUpdateEventArgs(this.ServiceProvider) { Guild = guild, Member = mbr, NicknameAfter = mbr.Nickname, RolesAfter = new ReadOnlyCollection(new List(mbr.Roles)), PendingAfter = mbr.IsPending, TimeoutAfter = mbr.CommunicationDisabledUntil, NicknameBefore = nickOld, RolesBefore = rolesOld, PendingBefore = pendingOld, TimeoutBefore = cduOld }; - await this._guildMemberUpdated.InvokeAsync(this, ea).ConfigureAwait(false); + await this._guildMemberUpdated.InvokeAsync(this, eargs).ConfigureAwait(false); + } + + /// + /// Handles timeout events. + /// + /// Internally used as uid for the timer data. + private async void TimeoutTimer(object state) + { + var tid = (string)state; + var data = this._tempTimers.First(x=> x.Key == tid).Value.Key; + var timer = this._tempTimers.First(x=> x.Key == tid).Value.Value; + + IReadOnlyList auditlog = null; + DiscordAuditLogMemberUpdateEntry filtered = null; + try + { + auditlog = await data._guild.GetAuditLogsAsync(10, null, AuditLogActionType.MemberUpdate); + var pre_filtered = auditlog.Select(x => x as DiscordAuditLogMemberUpdateEntry).Where(x => x.Target.Id == data._member.Id); + filtered = pre_filtered.First(); + } + catch (UnauthorizedException) { } + catch (Exception) + { + this.Logger.LogTrace("Re-scheduling timeout event."); + timer.Change(2000, Timeout.Infinite); + return; + } + + var actor = filtered?.UserResponsible as DiscordMember; + + this.Logger.LogTrace("Trying to execute timeout event."); + + if (data._timeoutUntilOld.HasValue && data._timeoutUntilNew.HasValue) + { + // A timeout was updated. + + if (filtered != null && auditlog == null) + { + this.Logger.LogTrace("Re-scheduling timeout event."); + timer.Change(2000, Timeout.Infinite); + return; + } + + var ea = new GuildMemberTimeoutUpdateEventArgs(this.ServiceProvider) + { + Guild = data._guild, + Target = data._member, + TimeoutBefore = data._timeoutUntilOld.Value, + TimeoutAfter = data._timeoutUntilNew.Value, + Actor = actor, + AuditLogId = filtered?.Id, + AuditLogReason = filtered?.Reason + }; + await this._guildMemberTimeoutChanged.InvokeAsync(this, ea).ConfigureAwait(false); + } + else if (!data._timeoutUntilOld.HasValue && data._timeoutUntilNew.HasValue) + { + // A timeout was added. + + if (filtered != null && auditlog == null) + { + this.Logger.LogTrace("Re-scheduling timeout event."); + timer.Change(2000, Timeout.Infinite); + return; + } + + var ea = new GuildMemberTimeoutAddEventArgs(this.ServiceProvider) + { + Guild = data._guild, + Target = data._member, + Timeout = data._timeoutUntilNew.Value, + Actor = actor, + AuditLogId = filtered?.Id, + AuditLogReason = filtered?.Reason + }; + await this._guildMemberTimeoutAdded.InvokeAsync(this, ea).ConfigureAwait(false); + } + else if (data._timeoutUntilOld.HasValue && !data._timeoutUntilNew.HasValue) + { + // A timeout was removed. + + if (filtered != null && auditlog == null) + { + this.Logger.LogTrace("Re-scheduling timeout event."); + timer.Change(2000, Timeout.Infinite); + return; + } + + var ea = new GuildMemberTimeoutRemoveEventArgs(this.ServiceProvider) + { + Guild = data._guild, + Target = data._member, + TimeoutBefore = data._timeoutUntilOld.Value, + Actor = actor, + AuditLogId = filtered?.Id, + AuditLogReason = filtered?.Reason + }; + await this._guildMemberTimeoutRemoved.InvokeAsync(this, ea).ConfigureAwait(false); + } + + // Ending timer because it worked. + this.Logger.LogTrace("Removing timeout event."); + await timer.DisposeAsync(); + this._tempTimers.Remove(tid); } /// /// Handles the guild members chunk event. /// /// The raw chunk data. internal async Task OnGuildMembersChunkEventAsync(JObject dat) { var guild = this.Guilds[(ulong)dat["guild_id"]]; var chunkIndex = (int)dat["chunk_index"]; var chunkCount = (int)dat["chunk_count"]; var nonce = (string)dat["nonce"]; var mbrs = new HashSet(); var pres = new HashSet(); var members = dat["members"].ToObject(); var memCount = members.Count(); for (var i = 0; i < memCount; i++) { var mbr = new DiscordMember(members[i]) { Discord = this, GuildId = guild.Id }; if (!this.UserCache.ContainsKey(mbr.Id)) this.UserCache[mbr.Id] = new DiscordUser(members[i].User) { Discord = this }; guild.MembersInternal[mbr.Id] = mbr; mbrs.Add(mbr); } guild.MemberCount = guild.MembersInternal.Count; var ea = new GuildMembersChunkEventArgs(this.ServiceProvider) { Guild = guild, Members = new ReadOnlySet(mbrs), ChunkIndex = chunkIndex, ChunkCount = chunkCount, Nonce = nonce, }; if (dat["presences"] != null) { var presences = dat["presences"].ToObject(); var presCount = presences.Count(); for (var i = 0; i < presCount; i++) { var xp = presences[i]; xp.Discord = this; xp.Activity = new DiscordActivity(xp.RawActivity); if (xp.RawActivities != null) { xp.InternalActivities = new DiscordActivity[xp.RawActivities.Length]; for (var j = 0; j < xp.RawActivities.Length; j++) xp.InternalActivities[j] = new DiscordActivity(xp.RawActivities[j]); } pres.Add(xp); } ea.Presences = new ReadOnlySet(pres); } if (dat["not_found"] != null) { var nf = dat["not_found"].ToObject>(); ea.NotFound = new ReadOnlySet(nf); } await this._guildMembersChunked.InvokeAsync(this, ea).ConfigureAwait(false); } #endregion #region Guild Role /// /// Handles the guild role create event. /// /// The role. /// The guild. internal async Task OnGuildRoleCreateEventAsync(DiscordRole role, DiscordGuild guild) { role.Discord = this; role.GuildId = guild.Id; guild.RolesInternal[role.Id] = role; var ea = new GuildRoleCreateEventArgs(this.ServiceProvider) { Guild = guild, Role = role }; await this._guildRoleCreated.InvokeAsync(this, ea).ConfigureAwait(false); } /// /// Handles the guild role update event. /// /// The role. /// The guild. internal async Task OnGuildRoleUpdateEventAsync(DiscordRole role, DiscordGuild guild) { var newRole = guild.GetRole(role.Id); var oldRole = new DiscordRole { GuildId = guild.Id, ColorInternal = newRole.ColorInternal, Discord = this, IsHoisted = newRole.IsHoisted, Id = newRole.Id, IsManaged = newRole.IsManaged, IsMentionable = newRole.IsMentionable, Name = newRole.Name, Permissions = newRole.Permissions, Position = newRole.Position, IconHash = newRole.IconHash, Tags = newRole.Tags ?? null, UnicodeEmojiString = newRole.UnicodeEmojiString }; newRole.GuildId = guild.Id; newRole.ColorInternal = role.ColorInternal; newRole.IsHoisted = role.IsHoisted; newRole.IsManaged = role.IsManaged; newRole.IsMentionable = role.IsMentionable; newRole.Name = role.Name; newRole.Permissions = role.Permissions; newRole.Position = role.Position; newRole.IconHash = role.IconHash; newRole.Tags = role.Tags ?? null; newRole.UnicodeEmojiString = role.UnicodeEmojiString; var ea = new GuildRoleUpdateEventArgs(this.ServiceProvider) { Guild = guild, RoleAfter = newRole, RoleBefore = oldRole }; await this._guildRoleUpdated.InvokeAsync(this, ea).ConfigureAwait(false); } /// /// Handles the guild role delete event. /// /// The role id. /// The guild. internal async Task OnGuildRoleDeleteEventAsync(ulong roleId, DiscordGuild guild) { if (!guild.RolesInternal.TryRemove(roleId, out var role)) this.Logger.LogWarning($"Attempted to delete a nonexistent role ({roleId}) from guild ({guild})."); var ea = new GuildRoleDeleteEventArgs(this.ServiceProvider) { Guild = guild, Role = role }; await this._guildRoleDeleted.InvokeAsync(this, ea).ConfigureAwait(false); } #endregion #region Invite /// /// Handles the invite create event. /// /// The channel id. /// The guild id. /// The invite. internal async Task OnInviteCreateEventAsync(ulong channelId, ulong guildId, DiscordInvite invite) { var guild = this.InternalGetCachedGuild(guildId); var channel = this.InternalGetCachedChannel(channelId); invite.Discord = this; if (invite.Inviter is not null) { invite.Inviter.Discord = this; this.UserCache.AddOrUpdate(invite.Inviter.Id, invite.Inviter, (old, @new) => @new); } guild.Invites[invite.Code] = invite; var ea = new InviteCreateEventArgs(this.ServiceProvider) { Channel = channel, Guild = guild, Invite = invite }; await this._inviteCreated.InvokeAsync(this, ea).ConfigureAwait(false); } /// /// Handles the invite delete event. /// /// The channel id. /// The guild id. /// The raw invite. internal async Task OnInviteDeleteEventAsync(ulong channelId, ulong guildId, JToken dat) { var guild = this.InternalGetCachedGuild(guildId); var channel = this.InternalGetCachedChannel(channelId); if (!guild.Invites.TryRemove(dat["code"].ToString(), out var invite)) { invite = dat.ToObject(); invite.Discord = this; } invite.IsRevoked = true; var ea = new InviteDeleteEventArgs(this.ServiceProvider) { Channel = channel, Guild = guild, Invite = invite }; await this._inviteDeleted.InvokeAsync(this, ea).ConfigureAwait(false); } #endregion #region Message /// /// Handles the message acknowledge event. /// /// The channel. /// The message id. internal async Task OnMessageAckEventAsync(DiscordChannel chn, ulong messageId) { if (this.MessageCache == null || !this.MessageCache.TryGet(xm => xm.Id == messageId && xm.ChannelId == chn.Id, out var msg)) { msg = new DiscordMessage { Id = messageId, ChannelId = chn.Id, Discord = this, }; } await this._messageAcknowledged.InvokeAsync(this, new MessageAcknowledgeEventArgs(this.ServiceProvider) { Message = msg }).ConfigureAwait(false); } /// /// Handles the message create event. /// /// The message. /// The transport user (author). /// The transport member. /// The reference transport user (author). /// The reference transport member. internal async Task OnMessageCreateEventAsync(DiscordMessage message, TransportUser author, TransportMember member, TransportUser referenceAuthor, TransportMember referenceMember) { message.Discord = this; this.PopulateMessageReactionsAndCache(message, author, member); message.PopulateMentions(); if (message.Channel == null && message.ChannelId == default) this.Logger.LogWarning(LoggerEvents.WebSocketReceive, "Channel which the last message belongs to is not in cache - cache state might be invalid!"); if (message.ReferencedMessage != null) { message.ReferencedMessage.Discord = this; this.PopulateMessageReactionsAndCache(message.ReferencedMessage, referenceAuthor, referenceMember); message.ReferencedMessage.PopulateMentions(); } foreach (var sticker in message.Stickers) sticker.Discord = this; var ea = new MessageCreateEventArgs(this.ServiceProvider) { Message = message, MentionedUsers = new ReadOnlyCollection(message.MentionedUsersInternal), MentionedRoles = message.MentionedRolesInternal != null ? new ReadOnlyCollection(message.MentionedRolesInternal) : null, MentionedChannels = message.MentionedChannelsInternal != null ? new ReadOnlyCollection(message.MentionedChannelsInternal) : null }; await this._messageCreated.InvokeAsync(this, ea).ConfigureAwait(false); } /// /// Handles the message update event. /// /// The message. /// The transport user (author). /// The transport member. /// The reference transport user (author). /// The reference transport member. internal async Task OnMessageUpdateEventAsync(DiscordMessage message, TransportUser author, TransportMember member, TransportUser referenceAuthor, TransportMember referenceMember) { DiscordGuild guild; message.Discord = this; var eventMessage = message; DiscordMessage oldmsg = null; if (this.Configuration.MessageCacheSize == 0 || this.MessageCache == null || !this.MessageCache.TryGet(xm => xm.Id == eventMessage.Id && xm.ChannelId == eventMessage.ChannelId, out message)) { message = eventMessage; this.PopulateMessageReactionsAndCache(message, author, member); guild = message.Channel?.Guild; if (message.ReferencedMessage != null) { message.ReferencedMessage.Discord = this; this.PopulateMessageReactionsAndCache(message.ReferencedMessage, referenceAuthor, referenceMember); message.ReferencedMessage.PopulateMentions(); } } else { oldmsg = new DiscordMessage(message); guild = message.Channel?.Guild; message.EditedTimestampRaw = eventMessage.EditedTimestampRaw; if (eventMessage.Content != null) message.Content = eventMessage.Content; message.EmbedsInternal.Clear(); message.EmbedsInternal.AddRange(eventMessage.EmbedsInternal); message.Pinned = eventMessage.Pinned; message.IsTts = eventMessage.IsTts; } message.PopulateMentions(); var ea = new MessageUpdateEventArgs(this.ServiceProvider) { Message = message, MessageBefore = oldmsg, MentionedUsers = new ReadOnlyCollection(message.MentionedUsersInternal), MentionedRoles = message.MentionedRolesInternal != null ? new ReadOnlyCollection(message.MentionedRolesInternal) : null, MentionedChannels = message.MentionedChannelsInternal != null ? new ReadOnlyCollection(message.MentionedChannelsInternal) : null }; await this._messageUpdated.InvokeAsync(this, ea).ConfigureAwait(false); } /// /// Handles the message delete event. /// /// The message id. /// The channel id. /// The optional guild id. internal async Task OnMessageDeleteEventAsync(ulong messageId, ulong channelId, ulong? guildId) { var channel = this.InternalGetCachedChannel(channelId) ?? this.InternalGetCachedThread(channelId); var guild = this.InternalGetCachedGuild(guildId); if (channel == null || this.Configuration.MessageCacheSize == 0 || this.MessageCache == null || !this.MessageCache.TryGet(xm => xm.Id == messageId && xm.ChannelId == channelId, out var msg)) { msg = new DiscordMessage { Id = messageId, ChannelId = channelId, Discord = this, }; } if (this.Configuration.MessageCacheSize > 0) this.MessageCache?.Remove(xm => xm.Id == msg.Id && xm.ChannelId == channelId); var ea = new MessageDeleteEventArgs(this.ServiceProvider) { Channel = channel, Message = msg, Guild = guild }; await this._messageDeleted.InvokeAsync(this, ea).ConfigureAwait(false); } /// /// Handles the message bulk delete event. /// /// The message ids. /// The channel id. /// The optional guild id. internal async Task OnMessageBulkDeleteEventAsync(ulong[] messageIds, ulong channelId, ulong? guildId) { var channel = this.InternalGetCachedChannel(channelId) ?? this.InternalGetCachedThread(channelId); var msgs = new List(messageIds.Length); foreach (var messageId in messageIds) { if (channel == null || this.Configuration.MessageCacheSize == 0 || this.MessageCache == null || !this.MessageCache.TryGet(xm => xm.Id == messageId && xm.ChannelId == channelId, out var msg)) { msg = new DiscordMessage { Id = messageId, ChannelId = channelId, Discord = this, }; } if (this.Configuration.MessageCacheSize > 0) this.MessageCache?.Remove(xm => xm.Id == msg.Id && xm.ChannelId == channelId); msgs.Add(msg); } var guild = this.InternalGetCachedGuild(guildId); var ea = new MessageBulkDeleteEventArgs(this.ServiceProvider) { Channel = channel, Messages = new ReadOnlyCollection(msgs), Guild = guild }; await this._messagesBulkDeleted.InvokeAsync(this, ea).ConfigureAwait(false); } #endregion #region Message Reaction /// /// Handles the message reaction add event. /// /// The user id. /// The message id. /// The channel id. /// The optional guild id. /// The transport member. /// The emoji. internal async Task OnMessageReactionAddAsync(ulong userId, ulong messageId, ulong channelId, ulong? guildId, TransportMember mbr, DiscordEmoji emoji) { var channel = this.InternalGetCachedChannel(channelId) ?? this.InternalGetCachedThread(channelId); var guild = this.InternalGetCachedGuild(guildId); emoji.Discord = this; var usr = this.UpdateUser(new DiscordUser { Id = userId, Discord = this }, guildId, guild, mbr); if (channel == null || this.Configuration.MessageCacheSize == 0 || this.MessageCache == null || !this.MessageCache.TryGet(xm => xm.Id == messageId && xm.ChannelId == channelId, out var msg)) { msg = new DiscordMessage { Id = messageId, ChannelId = channelId, Discord = this, ReactionsInternal = new List() }; } var react = msg.ReactionsInternal.FirstOrDefault(xr => xr.Emoji == emoji); if (react == null) { msg.ReactionsInternal.Add(react = new DiscordReaction { Count = 1, Emoji = emoji, IsMe = this.CurrentUser.Id == userId }); } else { react.Count++; react.IsMe |= this.CurrentUser.Id == userId; } var ea = new MessageReactionAddEventArgs(this.ServiceProvider) { Message = msg, User = usr, Guild = guild, Emoji = emoji }; await this._messageReactionAdded.InvokeAsync(this, ea).ConfigureAwait(false); } /// /// Handles the message reaction remove event. /// /// The user id. /// The message id. /// The channel id. /// The guild id. /// The emoji. internal async Task OnMessageReactionRemoveAsync(ulong userId, ulong messageId, ulong channelId, ulong? guildId, DiscordEmoji emoji) { var channel = this.InternalGetCachedChannel(channelId) ?? this.InternalGetCachedThread(channelId); emoji.Discord = this; if (!this.UserCache.TryGetValue(userId, out var usr)) usr = new DiscordUser { Id = userId, Discord = this }; if (channel?.Guild != null) usr = channel.Guild.Members.TryGetValue(userId, out var member) ? member : new DiscordMember(usr) { Discord = this, GuildId = channel.GuildId.Value }; if (channel == null || this.Configuration.MessageCacheSize == 0 || this.MessageCache == null || !this.MessageCache.TryGet(xm => xm.Id == messageId && xm.ChannelId == channelId, out var msg)) { msg = new DiscordMessage { Id = messageId, ChannelId = channelId, Discord = this }; } var react = msg.ReactionsInternal?.FirstOrDefault(xr => xr.Emoji == emoji); if (react != null) { react.Count--; react.IsMe &= this.CurrentUser.Id != userId; if (msg.ReactionsInternal != null && react.Count <= 0) // shit happens for (var i = 0; i < msg.ReactionsInternal.Count; i++) if (msg.ReactionsInternal[i].Emoji == emoji) { msg.ReactionsInternal.RemoveAt(i); break; } } var guild = this.InternalGetCachedGuild(guildId); var ea = new MessageReactionRemoveEventArgs(this.ServiceProvider) { Message = msg, User = usr, Guild = guild, Emoji = emoji }; await this._messageReactionRemoved.InvokeAsync(this, ea).ConfigureAwait(false); } /// /// Handles the message reaction remove event. /// Fired when all message reactions were removed. /// /// The message id. /// The channel id. /// The optional guild id. internal async Task OnMessageReactionRemoveAllAsync(ulong messageId, ulong channelId, ulong? guildId) { var channel = this.InternalGetCachedChannel(channelId) ?? this.InternalGetCachedThread(channelId); if (channel == null || this.Configuration.MessageCacheSize == 0 || this.MessageCache == null || !this.MessageCache.TryGet(xm => xm.Id == messageId && xm.ChannelId == channelId, out var msg)) { msg = new DiscordMessage { Id = messageId, ChannelId = channelId, Discord = this }; } msg.ReactionsInternal?.Clear(); var guild = this.InternalGetCachedGuild(guildId); var ea = new MessageReactionsClearEventArgs(this.ServiceProvider) { Message = msg }; await this._messageReactionsCleared.InvokeAsync(this, ea).ConfigureAwait(false); } /// /// Handles the message reaction remove event. /// Fired when a emoji got removed. /// /// The message id. /// The channel id. /// The guild id. /// The raw discord emoji. internal async Task OnMessageReactionRemoveEmojiAsync(ulong messageId, ulong channelId, ulong guildId, JToken dat) { var guild = this.InternalGetCachedGuild(guildId); var channel = this.InternalGetCachedChannel(channelId) ?? this.InternalGetCachedThread(channelId); if (channel == null || this.Configuration.MessageCacheSize == 0 || this.MessageCache == null || !this.MessageCache.TryGet(xm => xm.Id == messageId && xm.ChannelId == channelId, out var msg)) { msg = new DiscordMessage { Id = messageId, ChannelId = channelId, Discord = this }; } var partialEmoji = dat.ToObject(); if (!guild.EmojisInternal.TryGetValue(partialEmoji.Id, out var emoji)) { emoji = partialEmoji; emoji.Discord = this; } msg.ReactionsInternal?.RemoveAll(r => r.Emoji.Equals(emoji)); var ea = new MessageReactionRemoveEmojiEventArgs(this.ServiceProvider) { Channel = channel, Guild = guild, Message = msg, Emoji = emoji }; await this._messageReactionRemovedEmoji.InvokeAsync(this, ea).ConfigureAwait(false); } #endregion #region Stage Instance /// /// Handles the stage instance create event. /// /// The created stage instance. internal async Task OnStageInstanceCreateEventAsync(DiscordStageInstance stage) { stage.Discord = this; var guild = this.InternalGetCachedGuild(stage.GuildId); guild.StageInstancesInternal[stage.Id] = stage; await this._stageInstanceCreated.InvokeAsync(this, new StageInstanceCreateEventArgs(this.ServiceProvider) { StageInstance = stage, Guild = stage.Guild }).ConfigureAwait(false); } /// /// Handles the stage instance update event. /// /// The updated stage instance. internal async Task OnStageInstanceUpdateEventAsync(DiscordStageInstance stage) { stage.Discord = this; var guild = this.InternalGetCachedGuild(stage.GuildId); guild.StageInstancesInternal[stage.Id] = stage; await this._stageInstanceUpdated.InvokeAsync(this, new StageInstanceUpdateEventArgs(this.ServiceProvider) { StageInstance = stage, Guild = stage.Guild }).ConfigureAwait(false); } /// /// Handles the stage instance delete event. /// /// The deleted stage instance. internal async Task OnStageInstanceDeleteEventAsync(DiscordStageInstance stage) { stage.Discord = this; var guild = this.InternalGetCachedGuild(stage.GuildId); guild.StageInstancesInternal[stage.Id] = stage; await this._stageInstanceDeleted.InvokeAsync(this, new StageInstanceDeleteEventArgs(this.ServiceProvider) { StageInstance = stage, Guild = stage.Guild }).ConfigureAwait(false); } #endregion #region Thread /// /// Handles the thread create event. /// /// The created thread. internal async Task OnThreadCreateEventAsync(DiscordThreadChannel thread) { thread.Discord = this; this.InternalGetCachedGuild(thread.GuildId).ThreadsInternal.AddOrUpdate(thread.Id, thread, (oldThread, newThread) => newThread); await this._threadCreated.InvokeAsync(this, new ThreadCreateEventArgs(this.ServiceProvider) { Thread = thread, Guild = thread.Guild, Parent = thread.Parent }).ConfigureAwait(false); } /// /// Handles the thread update event. /// /// The updated thread. internal async Task OnThreadUpdateEventAsync(DiscordThreadChannel thread) { if (thread == null) return; thread.Discord = this; var guild = thread.Guild; var threadNew = this.InternalGetCachedThread(thread.Id); DiscordThreadChannel threadOld = null; ThreadUpdateEventArgs updateEvent; if (threadNew != null) { threadOld = new DiscordThreadChannel { Discord = this, Type = threadNew.Type, ThreadMetadata = thread.ThreadMetadata, ThreadMembersInternal = threadNew.ThreadMembersInternal, ParentId = thread.ParentId, OwnerId = thread.OwnerId, Name = thread.Name, LastMessageId = threadNew.LastMessageId, MessageCount = thread.MessageCount, MemberCount = thread.MemberCount, GuildId = thread.GuildId, LastPinTimestampRaw = threadNew.LastPinTimestampRaw, PerUserRateLimit = threadNew.PerUserRateLimit, CurrentMember = threadNew.CurrentMember }; threadNew.ThreadMetadata = thread.ThreadMetadata; threadNew.ParentId = thread.ParentId; threadNew.OwnerId = thread.OwnerId; threadNew.Name = thread.Name; threadNew.LastMessageId = thread.LastMessageId.HasValue ? thread.LastMessageId : threadOld.LastMessageId; threadNew.MessageCount = thread.MessageCount; threadNew.MemberCount = thread.MemberCount; threadNew.GuildId = thread.GuildId; updateEvent = new ThreadUpdateEventArgs(this.ServiceProvider) { ThreadAfter = thread, ThreadBefore = threadOld, Guild = thread.Guild, Parent = thread.Parent }; } else { updateEvent = new ThreadUpdateEventArgs(this.ServiceProvider) { ThreadAfter = thread, Guild = thread.Guild, Parent = thread.Parent }; guild.ThreadsInternal[thread.Id] = thread; } await this._threadUpdated.InvokeAsync(this, updateEvent).ConfigureAwait(false); } /// /// Handles the thread delete event. /// /// The deleted thread. internal async Task OnThreadDeleteEventAsync(DiscordThreadChannel thread) { if (thread == null) return; thread.Discord = this; var gld = thread.Guild; if (gld.ThreadsInternal.TryRemove(thread.Id, out var cachedThread)) thread = cachedThread; await this._threadDeleted.InvokeAsync(this, new ThreadDeleteEventArgs(this.ServiceProvider) { Thread = thread, Guild = thread.Guild, Parent = thread.Parent, Type = thread.Type }).ConfigureAwait(false); } /// /// Handles the thread list sync event. /// /// The synced guild. /// The synced channel ids. /// The synced threads. /// The synced thread members. internal async Task OnThreadListSyncEventAsync(DiscordGuild guild, IReadOnlyList channelIds, IReadOnlyList threads, IReadOnlyList members) { guild.Discord = this; var channels = channelIds.Select(x => guild.GetChannel(x.Value)); //getting channel objects foreach (var chan in channels) { chan.Discord = this; } threads.Select(x => x.Discord = this); await this._threadListSynced.InvokeAsync(this, new ThreadListSyncEventArgs(this.ServiceProvider) { Guild = guild, Channels = channels.ToList().AsReadOnly(), Threads = threads, Members = members.ToList().AsReadOnly() }).ConfigureAwait(false); } /// /// Handles the thread member update event. /// /// The updated member. internal async Task OnThreadMemberUpdateEventAsync(DiscordThreadChannelMember member) { member.Discord = this; var thread = this.InternalGetCachedThread(member.Id); if (thread == null) { var tempThread = await this.ApiClient.GetThreadAsync(member.Id); thread = this.GuildsInternal[member.GuildId].ThreadsInternal.AddOrUpdate(member.Id, tempThread, (old, newThread) => newThread); } thread.CurrentMember = member; thread.Guild.ThreadsInternal.AddOrUpdate(member.Id, thread, (oldThread, newThread) => newThread); await this._threadMemberUpdated.InvokeAsync(this, new ThreadMemberUpdateEventArgs(this.ServiceProvider) { ThreadMember = member, Thread = thread }).ConfigureAwait(false); } /// /// Handles the thread members update event. /// /// The target guild. /// The thread id of the target thread this update belongs to. /// The added members. /// The ids of the removed members. /// The new member count. internal async Task OnThreadMembersUpdateEventAsync(DiscordGuild guild, ulong threadId, JArray membersAdded, JArray membersRemoved, int memberCount) { var thread = this.InternalGetCachedThread(threadId); if (thread == null) { var tempThread = await this.ApiClient.GetThreadAsync(threadId); thread = guild.ThreadsInternal.AddOrUpdate(threadId, tempThread, (old, newThread) => newThread); } thread.Discord = this; guild.Discord = this; List addedMembers = new(); List removedMemberIds = new(); if (membersAdded != null) { foreach (var xj in membersAdded) { var xtm = xj.ToDiscordObject(); xtm.Discord = this; xtm.GuildId = guild.Id; if (xtm != null) addedMembers.Add(xtm); if (xtm.Id == this.CurrentUser.Id) thread.CurrentMember = xtm; } } var removedMembers = new List(); if (membersRemoved != null) { foreach (var removedId in membersRemoved) { removedMembers.Add(guild.MembersInternal.TryGetValue((ulong)removedId, out var member) ? member : new DiscordMember { Id = (ulong)removedId, GuildId = guild.Id, Discord = this }); } } if (removedMemberIds.Contains(this.CurrentUser.Id)) //indicates the bot was removed from the thread thread.CurrentMember = null; thread.MemberCount = memberCount; var threadMembersUpdateArg = new ThreadMembersUpdateEventArgs(this.ServiceProvider) { Guild = guild, Thread = thread, AddedMembers = addedMembers, RemovedMembers = removedMembers, MemberCount = memberCount }; await this._threadMembersUpdated.InvokeAsync(this, threadMembersUpdateArg).ConfigureAwait(false); } #endregion #region Activities /// /// Dispatches the event. /// /// The transport activity. /// The guild. /// The channel id. /// The users in the activity. /// The application id. internal async Task OnEmbeddedActivityUpdateAsync(JObject trActivity, DiscordGuild guild, ulong channelId, JArray jUsers, ulong appId) => await Task.Delay(20); /*{ try { var users = j_users?.ToObject>(); DiscordActivity old = null; var uid = $"{guild.Id}_{channel_id}_{app_id}"; if (this._embeddedActivities.TryGetValue(uid, out var activity)) { old = new DiscordActivity(activity); DiscordJson.PopulateObject(tr_activity, activity); } else { activity = tr_activity.ToObject(); this._embeddedActivities[uid] = activity; } var activity_users = new List(); var channel = this.InternalGetCachedChannel(channel_id) ?? await this.ApiClient.GetChannelAsync(channel_id); if (users != null) { foreach (var user in users) { var activity_user = guild._members.TryGetValue(user, out var member) ? member : new DiscordMember { Id = user, _guild_id = guild.Id, Discord = this }; activity_users.Add(activity_user); } } else activity_users = null; var ea = new EmbeddedActivityUpdateEventArgs(this.ServiceProvider) { Guild = guild, Users = activity_users, Channel = channel }; await this._embeddedActivityUpdated.InvokeAsync(this, ea).ConfigureAwait(false); } catch (Exception ex) { this.Logger.LogError(ex, ex.Message); } }*/ #endregion #region User/Presence Update /// /// Handles the presence update event. /// /// The raw presence. /// The raw user. internal async Task OnPresenceUpdateEventAsync(JObject rawPresence, JObject rawUser) { var uid = (ulong)rawUser["id"]; DiscordPresence old = null; if (this.PresencesInternal.TryGetValue(uid, out var presence)) { old = new DiscordPresence(presence); DiscordJson.PopulateObject(rawPresence, presence); } else { presence = rawPresence.ToObject(); presence.Discord = this; presence.Activity = new DiscordActivity(presence.RawActivity); this.PresencesInternal[presence.InternalUser.Id] = presence; } // reuse arrays / avoid linq (this is a hot zone) if (presence.Activities == null || rawPresence["activities"] == null) { presence.InternalActivities = Array.Empty(); } else { if (presence.InternalActivities.Length != presence.RawActivities.Length) presence.InternalActivities = new DiscordActivity[presence.RawActivities.Length]; for (var i = 0; i < presence.InternalActivities.Length; i++) presence.InternalActivities[i] = new DiscordActivity(presence.RawActivities[i]); if (presence.InternalActivities.Length > 0) { presence.RawActivity = presence.RawActivities[0]; if (presence.Activity != null) presence.Activity.UpdateWith(presence.RawActivity); else presence.Activity = new DiscordActivity(presence.RawActivity); } } if (this.UserCache.TryGetValue(uid, out var usr)) { if (old != null) { old.InternalUser.Username = usr.Username; old.InternalUser.Discriminator = usr.Discriminator; old.InternalUser.AvatarHash = usr.AvatarHash; } if (rawUser["username"] is object) usr.Username = (string)rawUser["username"]; if (rawUser["discriminator"] is object) usr.Discriminator = (string)rawUser["discriminator"]; if (rawUser["avatar"] is object) usr.AvatarHash = (string)rawUser["avatar"]; presence.InternalUser.Username = usr.Username; presence.InternalUser.Discriminator = usr.Discriminator; presence.InternalUser.AvatarHash = usr.AvatarHash; } var usrafter = usr ?? new DiscordUser(presence.InternalUser); var ea = new PresenceUpdateEventArgs(this.ServiceProvider) { Status = presence.Status, Activity = presence.Activity, User = usr, PresenceBefore = old, PresenceAfter = presence, UserBefore = old != null ? new DiscordUser(old.InternalUser) : usrafter, UserAfter = usrafter }; await this._presenceUpdated.InvokeAsync(this, ea).ConfigureAwait(false); } /// /// Handles the user settings update event. /// /// The transport user. internal async Task OnUserSettingsUpdateEventAsync(TransportUser user) { var usr = new DiscordUser(user) { Discord = this }; var ea = new UserSettingsUpdateEventArgs(this.ServiceProvider) { User = usr }; await this._userSettingsUpdated.InvokeAsync(this, ea).ConfigureAwait(false); } /// /// Handles the user update event. /// /// The transport user. internal async Task OnUserUpdateEventAsync(TransportUser user) { var usrOld = new DiscordUser { AvatarHash = this.CurrentUser.AvatarHash, Discord = this, Discriminator = this.CurrentUser.Discriminator, Email = this.CurrentUser.Email, Id = this.CurrentUser.Id, IsBot = this.CurrentUser.IsBot, MfaEnabled = this.CurrentUser.MfaEnabled, Username = this.CurrentUser.Username, Verified = this.CurrentUser.Verified }; this.CurrentUser.AvatarHash = user.AvatarHash; this.CurrentUser.Discriminator = user.Discriminator; this.CurrentUser.Email = user.Email; this.CurrentUser.Id = user.Id; this.CurrentUser.IsBot = user.IsBot; this.CurrentUser.MfaEnabled = user.MfaEnabled; this.CurrentUser.Username = user.Username; this.CurrentUser.Verified = user.Verified; var ea = new UserUpdateEventArgs(this.ServiceProvider) { UserAfter = this.CurrentUser, UserBefore = usrOld }; await this._userUpdated.InvokeAsync(this, ea).ConfigureAwait(false); } #endregion #region Voice /// /// Handles the voice state update event. /// /// The raw voice state update object. internal async Task OnVoiceStateUpdateEventAsync(JObject raw) { var gid = (ulong)raw["guild_id"]; var uid = (ulong)raw["user_id"]; var gld = this.GuildsInternal[gid]; var vstateNew = raw.ToObject(); vstateNew.Discord = this; gld.VoiceStatesInternal.TryRemove(uid, out var vstateOld); if (vstateNew.Channel != null) { gld.VoiceStatesInternal[vstateNew.UserId] = vstateNew; } if (gld.MembersInternal.TryGetValue(uid, out var mbr)) { mbr.IsMuted = vstateNew.IsServerMuted; mbr.IsDeafened = vstateNew.IsServerDeafened; } else { var transportMbr = vstateNew.TransportMember; this.UpdateUser(new DiscordUser(transportMbr.User) { Discord = this }, gid, gld, transportMbr); } var ea = new VoiceStateUpdateEventArgs(this.ServiceProvider) { Guild = vstateNew.Guild, Channel = vstateNew.Channel, User = vstateNew.User, SessionId = vstateNew.SessionId, Before = vstateOld, After = vstateNew }; await this._voiceStateUpdated.InvokeAsync(this, ea).ConfigureAwait(false); } /// /// Handles the voice server update event. /// /// The new endpoint. /// The new token. /// The guild. internal async Task OnVoiceServerUpdateEventAsync(string endpoint, string token, DiscordGuild guild) { var ea = new VoiceServerUpdateEventArgs(this.ServiceProvider) { Endpoint = endpoint, VoiceToken = token, Guild = guild }; await this._voiceServerUpdated.InvokeAsync(this, ea).ConfigureAwait(false); } #endregion #region Commands /// /// Handles the application command create event. /// /// The application command. /// The optional guild id. internal async Task OnApplicationCommandCreateAsync(DiscordApplicationCommand cmd, ulong? guildId) { cmd.Discord = this; var guild = this.InternalGetCachedGuild(guildId); if (guild == null && guildId.HasValue) { guild = new DiscordGuild { Id = guildId.Value, Discord = this }; } var ea = new ApplicationCommandEventArgs(this.ServiceProvider) { Guild = guild, Command = cmd }; await this._applicationCommandCreated.InvokeAsync(this, ea).ConfigureAwait(false); } /// /// Handles the application command update event. /// /// The application command. /// The optional guild id. internal async Task OnApplicationCommandUpdateAsync(DiscordApplicationCommand cmd, ulong? guildId) { cmd.Discord = this; var guild = this.InternalGetCachedGuild(guildId); if (guild == null && guildId.HasValue) { guild = new DiscordGuild { Id = guildId.Value, Discord = this }; } var ea = new ApplicationCommandEventArgs(this.ServiceProvider) { Guild = guild, Command = cmd }; await this._applicationCommandUpdated.InvokeAsync(this, ea).ConfigureAwait(false); } /// /// Handles the application command delete event. /// /// The application command. /// The optional guild id. internal async Task OnApplicationCommandDeleteAsync(DiscordApplicationCommand cmd, ulong? guildId) { cmd.Discord = this; var guild = this.InternalGetCachedGuild(guildId); if (guild == null && guildId.HasValue) { guild = new DiscordGuild { Id = guildId.Value, Discord = this }; } var ea = new ApplicationCommandEventArgs(this.ServiceProvider) { Guild = guild, Command = cmd }; await this._applicationCommandDeleted.InvokeAsync(this, ea).ConfigureAwait(false); } /// /// Handles the guild application command counts update event. /// /// The count. /// The count. /// The count. /// The guild id. /// Count of application commands. internal async Task OnGuildApplicationCommandCountsUpdateAsync(int chatInputCommandCount, int userContextMenuCommandCount, int messageContextMenuCount, ulong guildId) { var guild = this.InternalGetCachedGuild(guildId); if (guild == null) { guild = new DiscordGuild { Id = guildId, Discord = this }; } var ea = new GuildApplicationCommandCountEventArgs(this.ServiceProvider) { SlashCommands = chatInputCommandCount, UserContextMenuCommands = userContextMenuCommandCount, MessageContextMenuCommands = messageContextMenuCount, Guild = guild }; await this._guildApplicationCommandCountUpdated.InvokeAsync(this, ea).ConfigureAwait(false); } /// /// Handles the application command permissions update event. /// /// The new permissions. /// The command id. /// The guild id. /// The application id. internal async Task OnApplicationCommandPermissionsUpdateAsync(IEnumerable perms, ulong channelId, ulong guildId, ulong applicationId) { if (applicationId != this.CurrentApplication.Id) return; var guild = this.InternalGetCachedGuild(guildId); DiscordApplicationCommand cmd; try { cmd = await this.GetGuildApplicationCommandAsync(guildId, channelId); } catch (NotFoundException) { cmd = await this.GetGlobalApplicationCommandAsync(channelId); } if (guild == null) { guild = new DiscordGuild { Id = guildId, Discord = this }; } var ea = new ApplicationCommandPermissionsUpdateEventArgs(this.ServiceProvider) { Permissions = perms.ToList(), Command = cmd, ApplicationId = applicationId, Guild = guild }; await this._applicationCommandPermissionsUpdated.InvokeAsync(this, ea).ConfigureAwait(false); } #endregion #region Interaction /// /// Handles the interaction create event. /// /// The guild id. /// The channel id. /// The transport user. /// The transport member. /// The interaction. internal async Task OnInteractionCreateAsync(ulong? guildId, ulong channelId, TransportUser user, TransportMember member, DiscordInteraction interaction) { var usr = new DiscordUser(user) { Discord = this }; interaction.ChannelId = channelId; interaction.GuildId = guildId; interaction.Discord = this; interaction.Data.Discord = this; if (member != null) { usr = new DiscordMember(member) { GuildId = guildId.Value, Discord = this }; this.UpdateUser(usr, guildId, interaction.Guild, member); } else { this.UserCache.AddOrUpdate(usr.Id, usr, (old, @new) => @new); } interaction.User = usr; var resolved = interaction.Data.Resolved; if (resolved != null) { if (resolved.Users != null) { foreach (var c in resolved.Users) { c.Value.Discord = this; this.UserCache.AddOrUpdate(c.Value.Id, c.Value, (old, @new) => @new); } } if (resolved.Members != null) { foreach (var c in resolved.Members) { c.Value.Discord = this; c.Value.Id = c.Key; c.Value.GuildId = guildId.Value; c.Value.User.Discord = this; this.UserCache.AddOrUpdate(c.Value.User.Id, c.Value.User, (old, @new) => @new); } } if (resolved.Channels != null) { foreach (var c in resolved.Channels) { c.Value.Discord = this; if (guildId.HasValue) c.Value.GuildId = guildId.Value; } } if (resolved.Roles != null) { foreach (var c in resolved.Roles) { c.Value.Discord = this; if (guildId.HasValue) c.Value.GuildId = guildId.Value; } } if (resolved.Messages != null) { foreach (var m in resolved.Messages) { m.Value.Discord = this; if (guildId.HasValue) m.Value.GuildId = guildId.Value; } } } if (interaction.Type is InteractionType.Component || interaction.Type is InteractionType.ModalSubmit) { if (interaction.Message != null) { interaction.Message.Discord = this; interaction.Message.ChannelId = interaction.ChannelId; } var cea = new ComponentInteractionCreateEventArgs(this.ServiceProvider) { Message = interaction.Message, Interaction = interaction }; await this._componentInteractionCreated.InvokeAsync(this, cea).ConfigureAwait(false); } else { if (interaction.Data.Target.HasValue) // Context-Menu. // { var targetId = interaction.Data.Target.Value; DiscordUser targetUser = null; DiscordMember targetMember = null; DiscordMessage targetMessage = null; interaction.Data.Resolved.Messages?.TryGetValue(targetId, out targetMessage); interaction.Data.Resolved.Members?.TryGetValue(targetId, out targetMember); interaction.Data.Resolved.Users?.TryGetValue(targetId, out targetUser); var ctea = new ContextMenuInteractionCreateEventArgs(this.ServiceProvider) { Interaction = interaction, TargetUser = targetMember ?? targetUser, TargetMessage = targetMessage, Type = interaction.Data.Type, }; await this._contextMenuInteractionCreated.InvokeAsync(this, ctea).ConfigureAwait(false); } else { var ea = new InteractionCreateEventArgs(this.ServiceProvider) { Interaction = interaction }; await this._interactionCreated.InvokeAsync(this, ea).ConfigureAwait(false); } } } #endregion #region Misc /// /// Handles the typing start event. /// /// The user id. /// The channel id. /// The channel. /// The optional guild id. /// The time when the user started typing. /// The transport member. internal async Task OnTypingStartEventAsync(ulong userId, ulong channelId, DiscordChannel channel, ulong? guildId, DateTimeOffset started, TransportMember mbr) { if (channel == null) { channel = new DiscordChannel { Discord = this, Id = channelId, GuildId = guildId ?? default, }; } var guild = this.InternalGetCachedGuild(guildId); var usr = this.UpdateUser(new DiscordUser { Id = userId, Discord = this }, guildId, guild, mbr); var ea = new TypingStartEventArgs(this.ServiceProvider) { Channel = channel, User = usr, Guild = guild, StartedAt = started }; await this._typingStarted.InvokeAsync(this, ea).ConfigureAwait(false); } /// /// Handles the webhooks update. /// /// The channel. /// The guild. internal async Task OnWebhooksUpdateAsync(DiscordChannel channel, DiscordGuild guild) { var ea = new WebhooksUpdateEventArgs(this.ServiceProvider) { Channel = channel, Guild = guild }; await this._webhooksUpdated.InvokeAsync(this, ea).ConfigureAwait(false); } /// /// Handles all unknown events. /// /// The payload. internal async Task OnUnknownEventAsync(GatewayPayload payload) { var ea = new UnknownEventArgs(this.ServiceProvider) { EventName = payload.EventName, Json = (payload.Data as JObject)?.ToString() }; await this._unknownEvent.InvokeAsync(this, ea).ConfigureAwait(false); } #endregion #endregion } } diff --git a/DisCatSharp/Clients/DiscordClient.Events.cs b/DisCatSharp/Clients/DiscordClient.Events.cs index a6648d4c7..19f5af0ca 100644 --- a/DisCatSharp/Clients/DiscordClient.Events.cs +++ b/DisCatSharp/Clients/DiscordClient.Events.cs @@ -1,978 +1,1016 @@ // 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 DisCatSharp.Common.Utilities; using DisCatSharp.EventArgs; using Microsoft.Extensions.Logging; namespace DisCatSharp { /// /// Represents a discord client. /// public sealed partial class DiscordClient { /// /// Gets the event execution limit. /// internal static TimeSpan EventExecutionLimit { get; } = TimeSpan.FromSeconds(1); #region WebSocket /// /// Fired whenever a WebSocket error occurs within the client. /// public event AsyncEventHandler SocketErrored { add => this._socketErrored.Register(value); remove => this._socketErrored.Unregister(value); } private AsyncEvent _socketErrored; /// /// Fired whenever WebSocket connection is established. /// public event AsyncEventHandler SocketOpened { add => this._socketOpened.Register(value); remove => this._socketOpened.Unregister(value); } private AsyncEvent _socketOpened; /// /// Fired whenever WebSocket connection is terminated. /// public event AsyncEventHandler SocketClosed { add => this._socketClosed.Register(value); remove => this._socketClosed.Unregister(value); } private AsyncEvent _socketClosed; /// /// Fired when the client enters ready state. /// public event AsyncEventHandler Ready { add => this._ready.Register(value); remove => this._ready.Unregister(value); } private AsyncEvent _ready; /// /// Fired whenever a session is resumed. /// public event AsyncEventHandler Resumed { add => this._resumed.Register(value); remove => this._resumed.Unregister(value); } private AsyncEvent _resumed; /// /// Fired on received heartbeat ACK. /// public event AsyncEventHandler Heartbeated { add => this._heartbeated.Register(value); remove => this._heartbeated.Unregister(value); } private AsyncEvent _heartbeated; #endregion #region Channel /// /// Fired when a new channel is created. /// For this Event you need the intent specified in /// public event AsyncEventHandler ChannelCreated { add => this._channelCreated.Register(value); remove => this._channelCreated.Unregister(value); } private AsyncEvent _channelCreated; /// /// Fired when a channel is updated. /// For this Event you need the intent specified in /// public event AsyncEventHandler ChannelUpdated { add => this._channelUpdated.Register(value); remove => this._channelUpdated.Unregister(value); } private AsyncEvent _channelUpdated; /// /// Fired when a channel is deleted /// For this Event you need the intent specified in /// public event AsyncEventHandler ChannelDeleted { add => this._channelDeleted.Register(value); remove => this._channelDeleted.Unregister(value); } private AsyncEvent _channelDeleted; /// /// Fired when a dm channel is deleted /// For this Event you need the intent specified in /// public event AsyncEventHandler DmChannelDeleted { add => this._dmChannelDeleted.Register(value); remove => this._dmChannelDeleted.Unregister(value); } private AsyncEvent _dmChannelDeleted; /// /// Fired whenever a channel's pinned message list is updated. /// For this Event you need the intent specified in /// public event AsyncEventHandler ChannelPinsUpdated { add => this._channelPinsUpdated.Register(value); remove => this._channelPinsUpdated.Unregister(value); } private AsyncEvent _channelPinsUpdated; #endregion #region Guild /// /// Fired when the user joins a new guild. /// For this Event you need the intent specified in /// /// [alias="GuildJoined"][alias="JoinedGuild"] public event AsyncEventHandler GuildCreated { add => this._guildCreated.Register(value); remove => this._guildCreated.Unregister(value); } private AsyncEvent _guildCreated; /// /// Fired when a guild is becoming available. /// For this Event you need the intent specified in /// public event AsyncEventHandler GuildAvailable { add => this._guildAvailable.Register(value); remove => this._guildAvailable.Unregister(value); } private AsyncEvent _guildAvailable; /// /// Fired when a guild is updated. /// For this Event you need the intent specified in /// public event AsyncEventHandler GuildUpdated { add => this._guildUpdated.Register(value); remove => this._guildUpdated.Unregister(value); } private AsyncEvent _guildUpdated; /// /// Fired when the user leaves or is removed from a guild. /// For this Event you need the intent specified in /// public event AsyncEventHandler GuildDeleted { add => this._guildDeleted.Register(value); remove => this._guildDeleted.Unregister(value); } private AsyncEvent _guildDeleted; /// /// Fired when a guild becomes unavailable. /// public event AsyncEventHandler GuildUnavailable { add => this._guildUnavailable.Register(value); remove => this._guildUnavailable.Unregister(value); } private AsyncEvent _guildUnavailable; /// /// Fired when all guilds finish streaming from Discord. /// public event AsyncEventHandler GuildDownloadCompleted { add => this._guildDownloadCompletedEv.Register(value); remove => this._guildDownloadCompletedEv.Unregister(value); } private AsyncEvent _guildDownloadCompletedEv; /// /// Fired when a guilds emojis get updated /// For this Event you need the intent specified in /// public event AsyncEventHandler GuildEmojisUpdated { add => this._guildEmojisUpdated.Register(value); remove => this._guildEmojisUpdated.Unregister(value); } private AsyncEvent _guildEmojisUpdated; /// /// Fired when a guilds stickers get updated /// For this Event you need the intent specified in /// public event AsyncEventHandler GuildStickersUpdated { add => this._guildStickersUpdated.Register(value); remove => this._guildStickersUpdated.Unregister(value); } private AsyncEvent _guildStickersUpdated; /// /// Fired when a guild integration is updated. /// public event AsyncEventHandler GuildIntegrationsUpdated { add => this._guildIntegrationsUpdated.Register(value); remove => this._guildIntegrationsUpdated.Unregister(value); } private AsyncEvent _guildIntegrationsUpdated; #endregion #region Guild Ban /// /// Fired when a guild ban gets added /// For this Event you need the intent specified in /// public event AsyncEventHandler GuildBanAdded { add => this._guildBanAdded.Register(value); remove => this._guildBanAdded.Unregister(value); } private AsyncEvent _guildBanAdded; /// /// Fired when a guild ban gets removed /// For this Event you need the intent specified in /// public event AsyncEventHandler GuildBanRemoved { add => this._guildBanRemoved.Register(value); remove => this._guildBanRemoved.Unregister(value); } private AsyncEvent _guildBanRemoved; #endregion + #region Guild Timeout + + /// + /// Fired when a guild member timeout gets added. + /// For this Event you need the intent specified in + /// + public event AsyncEventHandler GuildMemberTimeoutAdded + { + add => this._guildMemberTimeoutAdded.Register(value); + remove => this._guildMemberTimeoutAdded.Unregister(value); + } + private AsyncEvent _guildMemberTimeoutAdded; + + /// + /// Fired when a guild member timeout gets changed. + /// For this Event you need the intent specified in + /// + public event AsyncEventHandler GuildMemberTimeoutChanged + { + add => this._guildMemberTimeoutChanged.Register(value); + remove => this._guildMemberTimeoutChanged.Unregister(value); + } + private AsyncEvent _guildMemberTimeoutChanged; + + + /// + /// Fired when a guild member timeout gets removed. + /// For this Event you need the intent specified in + /// + public event AsyncEventHandler GuildMemberTimeoutRemoved + { + add => this._guildMemberTimeoutRemoved.Register(value); + remove => this._guildMemberTimeoutRemoved.Unregister(value); + } + private AsyncEvent _guildMemberTimeoutRemoved; + + #endregion + #region Guild Scheduled Event /// /// Fired when a scheduled Event is created. /// For this Event you need the intent specified in /// public event AsyncEventHandler GuildScheduledEventCreated { add => this._guildScheduledEventCreated.Register(value); remove => this._guildScheduledEventCreated.Unregister(value); } private AsyncEvent _guildScheduledEventCreated; /// /// Fired when a scheduled Event is updated. /// For this Event you need the intent specified in /// public event AsyncEventHandler GuildScheduledEventUpdated { add => this._guildScheduledEventUpdated.Register(value); remove => this._guildScheduledEventUpdated.Unregister(value); } private AsyncEvent _guildScheduledEventUpdated; /// /// Fired when a scheduled Event is deleted. /// For this Event you need the intent specified in /// public event AsyncEventHandler GuildScheduledEventDeleted { add => this._guildScheduledEventDeleted.Register(value); remove => this._guildScheduledEventDeleted.Unregister(value); } private AsyncEvent _guildScheduledEventDeleted; /// /// Fired when a user subscribes to a scheduled event. /// For this Event you need the intent specified in /// public event AsyncEventHandler GuildScheduledEventUserAdded { add => this._guildScheduledEventUserAdded.Register(value); remove => this._guildScheduledEventUserAdded.Unregister(value); } private AsyncEvent _guildScheduledEventUserAdded; /// /// Fired when a user unsubscribes from a scheduled event. /// For this Event you need the intent specified in /// public event AsyncEventHandler GuildScheduledEventUserRemoved { add => this._guildScheduledEventUserRemoved.Register(value); remove => this._guildScheduledEventUserRemoved.Unregister(value); } private AsyncEvent _guildScheduledEventUserRemoved; #endregion #region Guild Integration /// /// Fired when a guild integration is created. /// For this Event you need the intent specified in /// public event AsyncEventHandler GuildIntegrationCreated { add => this._guildIntegrationCreated.Register(value); remove => this._guildIntegrationCreated.Unregister(value); } private AsyncEvent _guildIntegrationCreated; /// /// Fired when a guild integration is updated. /// For this Event you need the intent specified in /// public event AsyncEventHandler GuildIntegrationUpdated { add => this._guildIntegrationUpdated.Register(value); remove => this._guildIntegrationUpdated.Unregister(value); } private AsyncEvent _guildIntegrationUpdated; /// /// Fired when a guild integration is deleted. /// For this Event you need the intent specified in /// public event AsyncEventHandler GuildIntegrationDeleted { add => this._guildIntegrationDeleted.Register(value); remove => this._guildIntegrationDeleted.Unregister(value); } private AsyncEvent _guildIntegrationDeleted; #endregion #region Guild Member /// /// Fired when a new user joins a guild. /// For this Event you need the intent specified in /// public event AsyncEventHandler GuildMemberAdded { add => this._guildMemberAdded.Register(value); remove => this._guildMemberAdded.Unregister(value); } private AsyncEvent _guildMemberAdded; /// /// Fired when a user is removed from a guild (leave/kick/ban). /// For this Event you need the intent specified in /// public event AsyncEventHandler GuildMemberRemoved { add => this._guildMemberRemoved.Register(value); remove => this._guildMemberRemoved.Unregister(value); } private AsyncEvent _guildMemberRemoved; /// /// Fired when a guild member is updated. /// For this Event you need the intent specified in /// public event AsyncEventHandler GuildMemberUpdated { add => this._guildMemberUpdated.Register(value); remove => this._guildMemberUpdated.Unregister(value); } private AsyncEvent _guildMemberUpdated; /// /// Fired in response to Gateway Request Guild Members. /// public event AsyncEventHandler GuildMembersChunked { add => this._guildMembersChunked.Register(value); remove => this._guildMembersChunked.Unregister(value); } private AsyncEvent _guildMembersChunked; #endregion #region Guild Role /// /// Fired when a guild role is created. /// For this Event you need the intent specified in /// public event AsyncEventHandler GuildRoleCreated { add => this._guildRoleCreated.Register(value); remove => this._guildRoleCreated.Unregister(value); } private AsyncEvent _guildRoleCreated; /// /// Fired when a guild role is updated. /// For this Event you need the intent specified in /// public event AsyncEventHandler GuildRoleUpdated { add => this._guildRoleUpdated.Register(value); remove => this._guildRoleUpdated.Unregister(value); } private AsyncEvent _guildRoleUpdated; /// /// Fired when a guild role is updated. /// For this Event you need the intent specified in /// public event AsyncEventHandler GuildRoleDeleted { add => this._guildRoleDeleted.Register(value); remove => this._guildRoleDeleted.Unregister(value); } private AsyncEvent _guildRoleDeleted; #endregion #region Invite /// /// Fired when an invite is created. /// For this Event you need the intent specified in /// public event AsyncEventHandler InviteCreated { add => this._inviteCreated.Register(value); remove => this._inviteCreated.Unregister(value); } private AsyncEvent _inviteCreated; /// /// Fired when an invite is deleted. /// For this Event you need the intent specified in /// public event AsyncEventHandler InviteDeleted { add => this._inviteDeleted.Register(value); remove => this._inviteDeleted.Unregister(value); } private AsyncEvent _inviteDeleted; #endregion #region Message /// /// Fired when a message is created. /// For this Event you need the intent specified in /// public event AsyncEventHandler MessageCreated { add => this._messageCreated.Register(value); remove => this._messageCreated.Unregister(value); } private AsyncEvent _messageCreated; /// /// Fired when message is acknowledged by the user. /// For this Event you need the intent specified in /// public event AsyncEventHandler MessageAcknowledged { add => this._messageAcknowledged.Register(value); remove => this._messageAcknowledged.Unregister(value); } private AsyncEvent _messageAcknowledged; /// /// Fired when a message is updated. /// For this Event you need the intent specified in /// public event AsyncEventHandler MessageUpdated { add => this._messageUpdated.Register(value); remove => this._messageUpdated.Unregister(value); } private AsyncEvent _messageUpdated; /// /// Fired when a message is deleted. /// For this Event you need the intent specified in /// public event AsyncEventHandler MessageDeleted { add => this._messageDeleted.Register(value); remove => this._messageDeleted.Unregister(value); } private AsyncEvent _messageDeleted; /// /// Fired when multiple messages are deleted at once. /// For this Event you need the intent specified in /// public event AsyncEventHandler MessagesBulkDeleted { add => this._messagesBulkDeleted.Register(value); remove => this._messagesBulkDeleted.Unregister(value); } private AsyncEvent _messagesBulkDeleted; #endregion #region Message Reaction /// /// Fired when a reaction gets added to a message. /// For this Event you need the intent specified in /// public event AsyncEventHandler MessageReactionAdded { add => this._messageReactionAdded.Register(value); remove => this._messageReactionAdded.Unregister(value); } private AsyncEvent _messageReactionAdded; /// /// Fired when a reaction gets removed from a message. /// For this Event you need the intent specified in /// public event AsyncEventHandler MessageReactionRemoved { add => this._messageReactionRemoved.Register(value); remove => this._messageReactionRemoved.Unregister(value); } private AsyncEvent _messageReactionRemoved; /// /// Fired when all reactions get removed from a message. /// For this Event you need the intent specified in /// public event AsyncEventHandler MessageReactionsCleared { add => this._messageReactionsCleared.Register(value); remove => this._messageReactionsCleared.Unregister(value); } private AsyncEvent _messageReactionsCleared; /// /// Fired when all reactions of a specific reaction are removed from a message. /// For this Event you need the intent specified in /// public event AsyncEventHandler MessageReactionRemovedEmoji { add => this._messageReactionRemovedEmoji.Register(value); remove => this._messageReactionRemovedEmoji.Unregister(value); } private AsyncEvent _messageReactionRemovedEmoji; #endregion #region Activities /// /// Fired when a embedded activity has been updated. /// public event AsyncEventHandler EmbeddedActivityUpdated { add => this._embeddedActivityUpdated.Register(value); remove => this._embeddedActivityUpdated.Unregister(value); } private AsyncEvent _embeddedActivityUpdated; #endregion #region Presence/User Update /// /// Fired when a presence has been updated. /// For this Event you need the intent specified in /// public event AsyncEventHandler PresenceUpdated { add => this._presenceUpdated.Register(value); remove => this._presenceUpdated.Unregister(value); } private AsyncEvent _presenceUpdated; /// /// Fired when the current user updates their settings. /// For this Event you need the intent specified in /// public event AsyncEventHandler UserSettingsUpdated { add => this._userSettingsUpdated.Register(value); remove => this._userSettingsUpdated.Unregister(value); } private AsyncEvent _userSettingsUpdated; /// /// Fired when properties about the current user change. /// /// /// NB: This event only applies for changes to the current user, the client that is connected to Discord. /// For this Event you need the intent specified in /// public event AsyncEventHandler UserUpdated { add => this._userUpdated.Register(value); remove => this._userUpdated.Unregister(value); } private AsyncEvent _userUpdated; #endregion #region Stage Instance /// /// Fired when a Stage Instance is created. /// For this Event you need the intent specified in /// public event AsyncEventHandler StageInstanceCreated { add => this._stageInstanceCreated.Register(value); remove => this._stageInstanceCreated.Unregister(value); } private AsyncEvent _stageInstanceCreated; /// /// Fired when a Stage Instance is updated. /// For this Event you need the intent specified in /// public event AsyncEventHandler StageInstanceUpdated { add => this._stageInstanceUpdated.Register(value); remove => this._stageInstanceUpdated.Unregister(value); } private AsyncEvent _stageInstanceUpdated; /// /// Fired when a Stage Instance is deleted. /// For this Event you need the intent specified in /// public event AsyncEventHandler StageInstanceDeleted { add => this._stageInstanceDeleted.Register(value); remove => this._stageInstanceDeleted.Unregister(value); } private AsyncEvent _stageInstanceDeleted; #endregion #region Thread /// /// Fired when a thread is created. /// For this Event you need the intent specified in /// public event AsyncEventHandler ThreadCreated { add => this._threadCreated.Register(value); remove => this._threadCreated.Unregister(value); } private AsyncEvent _threadCreated; /// /// Fired when a thread is updated. /// For this Event you need the intent specified in /// public event AsyncEventHandler ThreadUpdated { add => this._threadUpdated.Register(value); remove => this._threadUpdated.Unregister(value); } private AsyncEvent _threadUpdated; /// /// Fired when a thread is deleted. /// For this Event you need the intent specified in /// public event AsyncEventHandler ThreadDeleted { add => this._threadDeleted.Register(value); remove => this._threadDeleted.Unregister(value); } private AsyncEvent _threadDeleted; /// /// Fired when a thread member is updated. /// For this Event you need the intent specified in /// public event AsyncEventHandler ThreadListSynced { add => this._threadListSynced.Register(value); remove => this._threadListSynced.Unregister(value); } private AsyncEvent _threadListSynced; /// /// Fired when a thread member is updated. /// For this Event you need the intent specified in /// public event AsyncEventHandler ThreadMemberUpdated { add => this._threadMemberUpdated.Register(value); remove => this._threadMemberUpdated.Unregister(value); } private AsyncEvent _threadMemberUpdated; /// /// Fired when the thread members are updated. /// For this Event you need the or intent specified in /// public event AsyncEventHandler ThreadMembersUpdated { add => this._threadMembersUpdated.Register(value); remove => this._threadMembersUpdated.Unregister(value); } private AsyncEvent _threadMembersUpdated; #endregion #region Voice /// /// Fired when someone joins/leaves/moves voice channels. /// For this Event you need the intent specified in /// public event AsyncEventHandler VoiceStateUpdated { add => this._voiceStateUpdated.Register(value); remove => this._voiceStateUpdated.Unregister(value); } private AsyncEvent _voiceStateUpdated; /// /// Fired when a guild's voice server is updated. /// For this Event you need the intent specified in /// public event AsyncEventHandler VoiceServerUpdated { add => this._voiceServerUpdated.Register(value); remove => this._voiceServerUpdated.Unregister(value); } private AsyncEvent _voiceServerUpdated; #endregion #region Application /// /// Fired when a new application command is registered. /// public event AsyncEventHandler ApplicationCommandCreated { add => this._applicationCommandCreated.Register(value); remove => this._applicationCommandCreated.Unregister(value); } private AsyncEvent _applicationCommandCreated; /// /// Fired when an application command is updated. /// public event AsyncEventHandler ApplicationCommandUpdated { add => this._applicationCommandUpdated.Register(value); remove => this._applicationCommandUpdated.Unregister(value); } private AsyncEvent _applicationCommandUpdated; /// /// Fired when an application command is deleted. /// public event AsyncEventHandler ApplicationCommandDeleted { add => this._applicationCommandDeleted.Register(value); remove => this._applicationCommandDeleted.Unregister(value); } private AsyncEvent _applicationCommandDeleted; /// /// Fired when a new application command is registered. /// public event AsyncEventHandler GuildApplicationCommandCountUpdated { add => this._guildApplicationCommandCountUpdated.Register(value); remove => this._guildApplicationCommandCountUpdated.Unregister(value); } private AsyncEvent _guildApplicationCommandCountUpdated; /// /// Fired when a user uses a context menu. /// public event AsyncEventHandler ContextMenuInteractionCreated { add => this._contextMenuInteractionCreated.Register(value); remove => this._contextMenuInteractionCreated.Unregister(value); } private AsyncEvent _contextMenuInteractionCreated; /// /// Fired when application command permissions gets updated. /// public event AsyncEventHandler ApplicationCommandPermissionsUpdated { add => this._applicationCommandPermissionsUpdated.Register(value); remove => this._applicationCommandPermissionsUpdated.Unregister(value); } private AsyncEvent _applicationCommandPermissionsUpdated; #endregion #region Misc /// /// Fired when an interaction is invoked. /// public event AsyncEventHandler InteractionCreated { add => this._interactionCreated.Register(value); remove => this._interactionCreated.Unregister(value); } private AsyncEvent _interactionCreated; /// /// Fired when a component is invoked. /// public event AsyncEventHandler ComponentInteractionCreated { add => this._componentInteractionCreated.Register(value); remove => this._componentInteractionCreated.Unregister(value); } private AsyncEvent _componentInteractionCreated; /// /// Fired when a user starts typing in a channel. /// public event AsyncEventHandler TypingStarted { add => this._typingStarted.Register(value); remove => this._typingStarted.Unregister(value); } private AsyncEvent _typingStarted; /// /// Fired when an unknown event gets received. /// public event AsyncEventHandler UnknownEvent { add => this._unknownEvent.Register(value); remove => this._unknownEvent.Unregister(value); } private AsyncEvent _unknownEvent; /// /// Fired whenever webhooks update. /// public event AsyncEventHandler WebhooksUpdated { add => this._webhooksUpdated.Register(value); remove => this._webhooksUpdated.Unregister(value); } private AsyncEvent _webhooksUpdated; /// /// Fired whenever an error occurs within an event handler. /// public event AsyncEventHandler ClientErrored { add => this._clientErrored.Register(value); remove => this._clientErrored.Unregister(value); } private AsyncEvent _clientErrored; #endregion #region Error Handling /// /// Handles event errors. /// /// The event. /// The exception. /// The event handler. /// The sender. /// The event args. internal void EventErrorHandler(AsyncEvent asyncEvent, Exception ex, AsyncEventHandler handler, TSender sender, TArgs eventArgs) where TArgs : AsyncEventArgs { if (ex is AsyncEventTimeoutException) { this.Logger.LogWarning(LoggerEvents.EventHandlerException, $"An event handler for {asyncEvent.Name} took too long to execute. Defined as \"{handler.Method.ToString().Replace(handler.Method.ReturnType.ToString(), "").TrimStart()}\" located in \"{handler.Method.DeclaringType}\"."); return; } this.Logger.LogError(LoggerEvents.EventHandlerException, ex, "Event handler exception for event {0} thrown from {1} (defined in {2})", asyncEvent.Name, handler.Method, handler.Method.DeclaringType); this._clientErrored.InvokeAsync(this, new ClientErrorEventArgs(this.ServiceProvider) { EventName = asyncEvent.Name, Exception = ex }).ConfigureAwait(false).GetAwaiter().GetResult(); } /// /// Fired on heartbeat attempt cancellation due to too many failed heartbeats. /// public event AsyncEventHandler Zombied { add => this._zombied.Register(value); remove => this._zombied.Unregister(value); } private AsyncEvent _zombied; /// /// Fired when a gateway payload is received. /// public event AsyncEventHandler PayloadReceived { add => this._payloadReceived.Register(value); remove => this._payloadReceived.Unregister(value); } private AsyncEvent _payloadReceived; /// /// Handles event handler exceptions. /// /// The event. /// The exception. /// The event handler. /// The sender. /// The event args. private void Goof(AsyncEvent asyncEvent, Exception ex, AsyncEventHandler handler, TSender sender, TArgs eventArgs) where TArgs : AsyncEventArgs => this.Logger.LogCritical(LoggerEvents.EventHandlerException, ex, "Exception event handler {0} (defined in {1}) threw an exception", handler.Method, handler.Method.DeclaringType); #endregion } } diff --git a/DisCatSharp/Clients/DiscordClient.cs b/DisCatSharp/Clients/DiscordClient.cs index fe0e53ffc..3e77b30fa 100644 --- a/DisCatSharp/Clients/DiscordClient.cs +++ b/DisCatSharp/Clients/DiscordClient.cs @@ -1,1326 +1,1329 @@ // 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; using System.Threading.Tasks; using DisCatSharp.Common.Utilities; using DisCatSharp.Entities; using DisCatSharp.Enums; using DisCatSharp.EventArgs; using DisCatSharp.Exceptions; using DisCatSharp.Net; using DisCatSharp.Net.Abstractions; using DisCatSharp.Net.Models; using DisCatSharp.Net.Serialization; using Microsoft.Extensions.Logging; using Newtonsoft.Json.Linq; namespace DisCatSharp { /// /// A Discord API wrapper. /// public sealed partial class DiscordClient : BaseDiscordClient { #region Internal Fields/Properties internal bool IsShard = false; /// /// Gets the message cache. /// internal RingBuffer MessageCache { get; } private List _extensions = new(); private StatusUpdate _status; /// /// Gets the connection lock. /// private readonly ManualResetEventSlim _connectionLock = new(true); #endregion #region Public Fields/Properties /// /// Gets the gateway protocol version. /// public int GatewayVersion { get; internal set; } /// /// Gets the gateway session information for this client. /// public GatewayInfo GatewayInfo { get; internal set; } /// /// Gets the gateway URL. /// public Uri GatewayUri { get; internal set; } /// /// Gets the total number of shards the bot is connected to. /// public int ShardCount => this.GatewayInfo != null ? this.GatewayInfo.ShardCount : this.Configuration.ShardCount; /// /// Gets the currently connected shard ID. /// public int ShardId => this.Configuration.ShardId; /// /// Gets the intents configured for this client. /// public DiscordIntents Intents => this.Configuration.Intents; /// /// Gets a dictionary of guilds that this client is in. The dictionary's key is the guild ID. Note that the /// guild objects in this dictionary will not be filled in if the specific guilds aren't available (the /// or events haven't been fired yet) /// public override IReadOnlyDictionary Guilds { get; } internal ConcurrentDictionary GuildsInternal = new(); /// /// Gets the websocket latency for this client. /// public int Ping => Volatile.Read(ref this._ping); private int _ping; /// /// Gets the collection of presences held by this client. /// public IReadOnlyDictionary Presences => this._presencesLazy.Value; internal Dictionary PresencesInternal = new(); private Lazy> _presencesLazy; /// /// Gets the collection of presences held by this client. /// public IReadOnlyDictionary EmbeddedActivities => this._embeddedActivitiesLazy.Value; internal Dictionary EmbeddedActivitiesInternal = new(); private Lazy> _embeddedActivitiesLazy; #endregion #region Constructor/Internal Setup /// /// Initializes a new instance of . /// /// Specifies configuration parameters. public DiscordClient(DiscordConfiguration config) : base(config) { if (this.Configuration.MessageCacheSize > 0) { var intents = this.Configuration.Intents; this.MessageCache = intents.HasIntent(DiscordIntents.GuildMessages) || intents.HasIntent(DiscordIntents.DirectMessages) ? new RingBuffer(this.Configuration.MessageCacheSize) : null; } this.InternalSetup(); this.Guilds = new ReadOnlyConcurrentDictionary(this.GuildsInternal); } /// /// Internal setup of the Client. /// internal void InternalSetup() { this._clientErrored = new AsyncEvent("CLIENT_ERRORED", EventExecutionLimit, this.Goof); this._socketErrored = new AsyncEvent("SOCKET_ERRORED", EventExecutionLimit, this.Goof); this._socketOpened = new AsyncEvent("SOCKET_OPENED", EventExecutionLimit, this.EventErrorHandler); this._socketClosed = new AsyncEvent("SOCKET_CLOSED", EventExecutionLimit, this.EventErrorHandler); this._ready = new AsyncEvent("READY", EventExecutionLimit, this.EventErrorHandler); this._resumed = new AsyncEvent("RESUMED", EventExecutionLimit, this.EventErrorHandler); this._channelCreated = new AsyncEvent("CHANNEL_CREATED", EventExecutionLimit, this.EventErrorHandler); this._channelUpdated = new AsyncEvent("CHANNEL_UPDATED", EventExecutionLimit, this.EventErrorHandler); this._channelDeleted = new AsyncEvent("CHANNEL_DELETED", EventExecutionLimit, this.EventErrorHandler); this._dmChannelDeleted = new AsyncEvent("DM_CHANNEL_DELETED", EventExecutionLimit, this.EventErrorHandler); this._channelPinsUpdated = new AsyncEvent("CHANNEL_PINS_UPDATED", EventExecutionLimit, this.EventErrorHandler); this._guildCreated = new AsyncEvent("GUILD_CREATED", EventExecutionLimit, this.EventErrorHandler); this._guildAvailable = new AsyncEvent("GUILD_AVAILABLE", EventExecutionLimit, this.EventErrorHandler); this._guildUpdated = new AsyncEvent("GUILD_UPDATED", EventExecutionLimit, this.EventErrorHandler); this._guildDeleted = new AsyncEvent("GUILD_DELETED", EventExecutionLimit, this.EventErrorHandler); this._guildUnavailable = new AsyncEvent("GUILD_UNAVAILABLE", EventExecutionLimit, this.EventErrorHandler); this._guildDownloadCompletedEv = new AsyncEvent("GUILD_DOWNLOAD_COMPLETED", EventExecutionLimit, this.EventErrorHandler); this._inviteCreated = new AsyncEvent("INVITE_CREATED", EventExecutionLimit, this.EventErrorHandler); this._inviteDeleted = new AsyncEvent("INVITE_DELETED", EventExecutionLimit, this.EventErrorHandler); this._messageCreated = new AsyncEvent("MESSAGE_CREATED", EventExecutionLimit, this.EventErrorHandler); this._presenceUpdated = new AsyncEvent("PRESENCE_UPDATED", EventExecutionLimit, this.EventErrorHandler); this._guildBanAdded = new AsyncEvent("GUILD_BAN_ADD", EventExecutionLimit, this.EventErrorHandler); this._guildBanRemoved = new AsyncEvent("GUILD_BAN_REMOVED", EventExecutionLimit, this.EventErrorHandler); this._guildEmojisUpdated = new AsyncEvent("GUILD_EMOJI_UPDATED", EventExecutionLimit, this.EventErrorHandler); this._guildStickersUpdated = new AsyncEvent("GUILD_STICKER_UPDATED", EventExecutionLimit, this.EventErrorHandler); this._guildIntegrationsUpdated = new AsyncEvent("GUILD_INTEGRATIONS_UPDATED", EventExecutionLimit, this.EventErrorHandler); this._guildMemberAdded = new AsyncEvent("GUILD_MEMBER_ADD", EventExecutionLimit, this.EventErrorHandler); this._guildMemberRemoved = new AsyncEvent("GUILD_MEMBER_REMOVED", EventExecutionLimit, this.EventErrorHandler); this._guildMemberUpdated = new AsyncEvent("GUILD_MEMBER_UPDATED", EventExecutionLimit, this.EventErrorHandler); this._guildRoleCreated = new AsyncEvent("GUILD_ROLE_CREATED", EventExecutionLimit, this.EventErrorHandler); this._guildRoleUpdated = new AsyncEvent("GUILD_ROLE_UPDATED", EventExecutionLimit, this.EventErrorHandler); this._guildRoleDeleted = new AsyncEvent("GUILD_ROLE_DELETED", EventExecutionLimit, this.EventErrorHandler); this._messageAcknowledged = new AsyncEvent("MESSAGE_ACKNOWLEDGED", EventExecutionLimit, this.EventErrorHandler); this._messageUpdated = new AsyncEvent("MESSAGE_UPDATED", EventExecutionLimit, this.EventErrorHandler); this._messageDeleted = new AsyncEvent("MESSAGE_DELETED", EventExecutionLimit, this.EventErrorHandler); this._messagesBulkDeleted = new AsyncEvent("MESSAGE_BULK_DELETED", EventExecutionLimit, this.EventErrorHandler); this._interactionCreated = new AsyncEvent("INTERACTION_CREATED", EventExecutionLimit, this.EventErrorHandler); this._componentInteractionCreated = new AsyncEvent("COMPONENT_INTERACTED", EventExecutionLimit, this.EventErrorHandler); this._contextMenuInteractionCreated = new AsyncEvent("CONTEXT_MENU_INTERACTED", EventExecutionLimit, this.EventErrorHandler); this._typingStarted = new AsyncEvent("TYPING_STARTED", EventExecutionLimit, this.EventErrorHandler); this._userSettingsUpdated = new AsyncEvent("USER_SETTINGS_UPDATED", EventExecutionLimit, this.EventErrorHandler); this._userUpdated = new AsyncEvent("USER_UPDATED", EventExecutionLimit, this.EventErrorHandler); this._voiceStateUpdated = new AsyncEvent("VOICE_STATE_UPDATED", EventExecutionLimit, this.EventErrorHandler); this._voiceServerUpdated = new AsyncEvent("VOICE_SERVER_UPDATED", EventExecutionLimit, this.EventErrorHandler); this._guildMembersChunked = new AsyncEvent("GUILD_MEMBERS_CHUNKED", EventExecutionLimit, this.EventErrorHandler); this._unknownEvent = new AsyncEvent("UNKNOWN_EVENT", EventExecutionLimit, this.EventErrorHandler); this._messageReactionAdded = new AsyncEvent("MESSAGE_REACTION_ADDED", EventExecutionLimit, this.EventErrorHandler); this._messageReactionRemoved = new AsyncEvent("MESSAGE_REACTION_REMOVED", EventExecutionLimit, this.EventErrorHandler); this._messageReactionsCleared = new AsyncEvent("MESSAGE_REACTIONS_CLEARED", EventExecutionLimit, this.EventErrorHandler); this._messageReactionRemovedEmoji = new AsyncEvent("MESSAGE_REACTION_REMOVED_EMOJI", EventExecutionLimit, this.EventErrorHandler); this._webhooksUpdated = new AsyncEvent("WEBHOOKS_UPDATED", EventExecutionLimit, this.EventErrorHandler); this._heartbeated = new AsyncEvent("HEARTBEATED", EventExecutionLimit, this.EventErrorHandler); this._applicationCommandCreated = new AsyncEvent("APPLICATION_COMMAND_CREATED", EventExecutionLimit, this.EventErrorHandler); this._applicationCommandUpdated = new AsyncEvent("APPLICATION_COMMAND_UPDATED", EventExecutionLimit, this.EventErrorHandler); this._applicationCommandDeleted = new AsyncEvent("APPLICATION_COMMAND_DELETED", EventExecutionLimit, this.EventErrorHandler); this._guildApplicationCommandCountUpdated = new AsyncEvent("GUILD_APPLICATION_COMMAND_COUNTS_UPDATED", EventExecutionLimit, this.EventErrorHandler); this._applicationCommandPermissionsUpdated = new AsyncEvent("APPLICATION_COMMAND_PERMISSIONS_UPDATED", EventExecutionLimit, this.EventErrorHandler); this._guildIntegrationCreated = new AsyncEvent("INTEGRATION_CREATED", EventExecutionLimit, this.EventErrorHandler); this._guildIntegrationUpdated = new AsyncEvent("INTEGRATION_UPDATED", EventExecutionLimit, this.EventErrorHandler); this._guildIntegrationDeleted = new AsyncEvent("INTEGRATION_DELETED", EventExecutionLimit, this.EventErrorHandler); this._stageInstanceCreated = new AsyncEvent("STAGE_INSTANCE_CREATED", EventExecutionLimit, this.EventErrorHandler); this._stageInstanceUpdated = new AsyncEvent("STAGE_INSTANCE_UPDATED", EventExecutionLimit, this.EventErrorHandler); this._stageInstanceDeleted = new AsyncEvent("STAGE_INSTANCE_DELETED", EventExecutionLimit, this.EventErrorHandler); this._threadCreated = new AsyncEvent("THREAD_CREATED", EventExecutionLimit, this.EventErrorHandler); this._threadUpdated = new AsyncEvent("THREAD_UPDATED", EventExecutionLimit, this.EventErrorHandler); this._threadDeleted = new AsyncEvent("THREAD_DELETED", EventExecutionLimit, this.EventErrorHandler); this._threadListSynced = new AsyncEvent("THREAD_LIST_SYNCED", EventExecutionLimit, this.EventErrorHandler); this._threadMemberUpdated = new AsyncEvent("THREAD_MEMBER_UPDATED", EventExecutionLimit, this.EventErrorHandler); this._threadMembersUpdated = new AsyncEvent("THREAD_MEMBERS_UPDATED", EventExecutionLimit, this.EventErrorHandler); this._zombied = new AsyncEvent("ZOMBIED", EventExecutionLimit, this.EventErrorHandler); this._payloadReceived = new AsyncEvent("PAYLOAD_RECEIVED", EventExecutionLimit, this.EventErrorHandler); this._guildScheduledEventCreated = new AsyncEvent("GUILD_SCHEDULED_EVENT_CREATED", EventExecutionLimit, this.EventErrorHandler); this._guildScheduledEventUpdated = new AsyncEvent("GUILD_SCHEDULED_EVENT_UPDATED", EventExecutionLimit, this.EventErrorHandler); this._guildScheduledEventDeleted = new AsyncEvent("GUILD_SCHEDULED_EVENT_DELETED", EventExecutionLimit, this.EventErrorHandler); this._guildScheduledEventUserAdded = new AsyncEvent("GUILD_SCHEDULED_EVENT_USER_ADDED", EventExecutionLimit, this.EventErrorHandler); this._guildScheduledEventUserRemoved = new AsyncEvent("GUILD_SCHEDULED_EVENT_USER_REMOVED", EventExecutionLimit, this.EventErrorHandler); this._embeddedActivityUpdated = new AsyncEvent("EMBEDDED_ACTIVITY_UPDATED", EventExecutionLimit, this.EventErrorHandler); + this._guildMemberTimeoutAdded = new AsyncEvent("GUILD_MEMBER_TIMEOUT_ADDED", EventExecutionLimit, this.EventErrorHandler); + this._guildMemberTimeoutChanged = new AsyncEvent("GUILD_MEMBER_TIMEOUT_UPDATED", EventExecutionLimit, this.EventErrorHandler); + this._guildMemberTimeoutRemoved = new AsyncEvent("GUILD_MEMBER_TIMEOUT_REMOVED", EventExecutionLimit, this.EventErrorHandler); this.GuildsInternal.Clear(); this._presencesLazy = new Lazy>(() => new ReadOnlyDictionary(this.PresencesInternal)); this._embeddedActivitiesLazy = new Lazy>(() => new ReadOnlyDictionary(this.EmbeddedActivitiesInternal)); } #endregion #region Client Extension Methods /// /// Registers an extension with this client. /// /// Extension to register. public void AddExtension(BaseExtension ext) { ext.Setup(this); this._extensions.Add(ext); } /// /// Retrieves a previously registered extension from this client. /// /// The type of extension to retrieve. /// The requested extension. public T GetExtension() where T : BaseExtension => this._extensions.FirstOrDefault(x => x.GetType() == typeof(T)) as T; #endregion #region Public Connection Methods /// /// Connects to the gateway. /// /// The activity to set. Defaults to null. /// The optional status to set. Defaults to null. /// Since when is the client performing the specified activity. Defaults to null. /// Thrown when an invalid token was provided. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public async Task ConnectAsync(DiscordActivity activity = null, UserStatus? status = null, DateTimeOffset? idlesince = null) { // Check if connection lock is already set, and set it if it isn't if (!this._connectionLock.Wait(0)) throw new InvalidOperationException("This client is already connected."); this._connectionLock.Set(); var w = 7500; var i = 5; var s = false; Exception cex = null; if (activity == null && status == null && idlesince == null) this._status = null; else { var sinceUnix = idlesince != null ? (long?)Utilities.GetUnixTime(idlesince.Value) : null; this._status = new StatusUpdate() { Activity = new TransportActivity(activity), Status = status ?? UserStatus.Online, IdleSince = sinceUnix, IsAfk = idlesince != null, ActivityInternal = activity }; } if (!this.IsShard) { if (this.Configuration.TokenType != TokenType.Bot) this.Logger.LogWarning(LoggerEvents.Misc, "You are logging in with a token that is not a bot token. This is not officially supported by Discord, and can result in your account being terminated if you aren't careful."); this.Logger.LogInformation(LoggerEvents.Startup, "Lib {0}, version {1}", this.BotLibrary, this.VersionString); } while (i-- > 0 || this.Configuration.ReconnectIndefinitely) { try { await this.InternalConnectAsync().ConfigureAwait(false); s = true; break; } catch (UnauthorizedException e) { FailConnection(this._connectionLock); throw new Exception("Authentication failed. Check your token and try again.", e); } catch (PlatformNotSupportedException) { FailConnection(this._connectionLock); throw; } catch (NotImplementedException) { FailConnection(this._connectionLock); throw; } catch (Exception ex) { FailConnection(null); cex = ex; if (i <= 0 && !this.Configuration.ReconnectIndefinitely) break; this.Logger.LogError(LoggerEvents.ConnectionFailure, ex, "Connection attempt failed, retrying in {0}s", w / 1000); await Task.Delay(w).ConfigureAwait(false); if (i > 0) w *= 2; } } if (!s && cex != null) { this._connectionLock.Set(); throw new Exception("Could not connect to Discord.", cex); } // non-closure, hence args static void FailConnection(ManualResetEventSlim cl) => // unlock this (if applicable) so we can let others attempt to connect cl?.Set(); } /// /// Reconnects to the gateway. /// /// Whether to start a new session. public Task ReconnectAsync(bool startNewSession = false) => this.InternalReconnectAsync(startNewSession, code: startNewSession ? 1000 : 4002); /// /// Disconnects from the gateway. /// public async Task DisconnectAsync() { this.Configuration.AutoReconnect = false; if (this.WebSocketClient != null) await this.WebSocketClient.DisconnectAsync().ConfigureAwait(false); } #endregion #region Public REST Methods /// /// Gets a user. /// /// Id of the user /// Whether to fetch the user again. Defaults to true. /// The requested user. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public async Task GetUserAsync(ulong userId, bool fetch = true) { if (!fetch) { return this.TryGetCachedUserInternal(userId, out var usr) ? usr : new DiscordUser { Id = userId, Discord = this }; } else { var usr = await this.ApiClient.GetUserAsync(userId).ConfigureAwait(false); usr = this.UserCache.AddOrUpdate(userId, usr, (id, old) => { old.Username = usr.Username; old.Discriminator = usr.Discriminator; old.AvatarHash = usr.AvatarHash; old.BannerHash = usr.BannerHash; old.BannerColorInternal = usr.BannerColorInternal; return old; }); return usr; } } /// /// Gets a channel. /// /// The id of the channel to get. /// The requested channel. /// Thrown when the channel does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public async Task GetChannelAsync(ulong id) => this.InternalGetCachedChannel(id) ?? await this.ApiClient.GetChannelAsync(id).ConfigureAwait(false); /// /// Gets a thread. /// /// The id of the thread to get. /// The requested thread. /// Thrown when the thread does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public async Task GetThreadAsync(ulong id) => this.InternalGetCachedThread(id) ?? await this.ApiClient.GetThreadAsync(id).ConfigureAwait(false); /// /// Sends a normal message. /// /// The channel to send to. /// The message content to send. /// The message that was sent. /// Thrown when the client does not have the permission. /// Thrown when the channel does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task SendMessageAsync(DiscordChannel channel, string content) => this.ApiClient.CreateMessageAsync(channel.Id, content, embeds: null, sticker: null, replyMessageId: null, mentionReply: false, failOnInvalidReply: false); /// /// Sends a message with an embed. /// /// The channel to send to. /// The embed to attach to the message. /// The message that was sent. /// Thrown when the client does not have the permission. /// Thrown when the channel does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task SendMessageAsync(DiscordChannel channel, DiscordEmbed embed) => this.ApiClient.CreateMessageAsync(channel.Id, null, embed != null ? new[] { embed } : null, sticker: null, replyMessageId: null, mentionReply: false, failOnInvalidReply: false); /// /// Sends a message with content and an embed. /// /// Channel to send to. /// The message content to send. /// The embed to attach to the message. /// The message that was sent. /// Thrown when the client does not have the permission. /// Thrown when the channel does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task SendMessageAsync(DiscordChannel channel, string content, DiscordEmbed embed) => this.ApiClient.CreateMessageAsync(channel.Id, content, embed != null ? new[] { embed } : null, sticker: null, replyMessageId: null, mentionReply: false, failOnInvalidReply: false); /// /// Sends a message with the . /// /// The channel to send the message to. /// The message builder. /// The message that was sent. /// Thrown when the client does not have the permission if TTS is false and if TTS is true. /// Thrown when the channel does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task SendMessageAsync(DiscordChannel channel, DiscordMessageBuilder builder) => this.ApiClient.CreateMessageAsync(channel.Id, builder); /// /// Sends a message with an . /// /// The channel to send the message to. /// The message builder. /// The message that was sent. /// Thrown when the client does not have the permission if TTS is false and if TTS is true. /// Thrown when the channel does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task SendMessageAsync(DiscordChannel channel, Action action) { var builder = new DiscordMessageBuilder(); action(builder); return this.ApiClient.CreateMessageAsync(channel.Id, builder); } /// /// Creates a guild. This requires the bot to be in less than 10 guilds total. /// /// Name of the guild. /// Voice region of the guild. /// Stream containing the icon for the guild. /// Verification level for the guild. /// Default message notification settings for the guild. /// System channel flags fopr the guild. /// The created guild. /// Thrown when the channel does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task CreateGuildAsync(string name, string region = null, Optional icon = default, VerificationLevel? verificationLevel = null, DefaultMessageNotifications? defaultMessageNotifications = null, SystemChannelFlags? systemChannelFlags = null) { var iconb64 = Optional.FromNoValue(); if (icon.HasValue && icon.Value != null) using (var imgtool = new ImageTool(icon.Value)) iconb64 = imgtool.GetBase64(); else if (icon.HasValue) iconb64 = null; return this.ApiClient.CreateGuildAsync(name, region, iconb64, verificationLevel, defaultMessageNotifications, systemChannelFlags); } /// /// Creates a guild from a template. This requires the bot to be in less than 10 guilds total. /// /// The template code. /// Name of the guild. /// Stream containing the icon for the guild. /// The created guild. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task CreateGuildFromTemplateAsync(string code, string name, Optional icon = default) { var iconb64 = Optional.FromNoValue(); if (icon.HasValue && icon.Value != null) using (var imgtool = new ImageTool(icon.Value)) iconb64 = imgtool.GetBase64(); else if (icon.HasValue) iconb64 = null; return this.ApiClient.CreateGuildFromTemplateAsync(code, name, iconb64); } /// /// Executes a raw request. /// /// /// /// var request = await Client.ExecuteRawRequestAsync(RestRequestMethod.GET, $"{Endpoints.CHANNELS}/243184972190742178964/{Endpoints.INVITES}"); /// List<DiscordInvite> invites = DiscordJson.ToDiscordObject<List<DiscordInvite>>(request.Response); /// /// /// The method. /// The route. /// The route parameters. /// The json body. /// The addditional headers. /// The ratelimit wait override. /// Thrown when the ressource does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. /// A awaitable RestResponse [Obsolete("This is no longer needed. Use DiscordClient.RestClient instead.", false)] public async Task ExecuteRawRequestAsync(RestRequestMethod method, string route, object routeParams, string jsonBody = null, Dictionary additionalHeaders = null, double? ratelimitWaitOverride = null) { var bucket = this.ApiClient.Rest.GetBucket(method, route, routeParams, out var path); var url = Utilities.GetApiUriFor(path, this.Configuration); var res = await this.ApiClient.DoRequestAsync(this, bucket, url, method, route, additionalHeaders, DiscordJson.SerializeObject(jsonBody), ratelimitWaitOverride); return res; } /// /// Gets a guild. /// Setting to true will make a REST request. /// /// The guild ID to search for. /// Whether to include approximate presence and member counts in the returned guild. /// The requested Guild. /// 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 GetGuildAsync(ulong id, bool? withCounts = null) { if (this.GuildsInternal.TryGetValue(id, out var guild) && (!withCounts.HasValue || !withCounts.Value)) return guild; guild = await this.ApiClient.GetGuildAsync(id, withCounts).ConfigureAwait(false); var channels = await this.ApiClient.GetGuildChannelsAsync(guild.Id).ConfigureAwait(false); foreach (var channel in channels) guild.ChannelsInternal[channel.Id] = channel; return guild; } /// /// Gets a guild preview. /// /// The guild ID. /// /// 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 GetGuildPreviewAsync(ulong id) => this.ApiClient.GetGuildPreviewAsync(id); /// /// Gets an invite. /// /// The invite code. /// Whether to include presence and total member counts in the returned invite. /// Whether to include the expiration date in the returned invite. /// The scheduled event id. /// The requested Invite. /// Thrown when the invite does not exists. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task GetInviteByCodeAsync(string code, bool? withCounts = null, bool? withExpiration = null, ulong? scheduledEventId = null) => this.ApiClient.GetInviteAsync(code, withCounts, withExpiration, scheduledEventId); /// /// Gets a list of user connections. /// /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task> GetConnectionsAsync() => this.ApiClient.GetUserConnectionsAsync(); /// /// Gets a sticker. /// /// The requested sticker. /// The id of the sticker. /// Thrown when the sticker does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task GetStickerAsync(ulong id) => this.ApiClient.GetStickerAsync(id); /// /// Gets all nitro sticker packs. /// /// List of sticker packs. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task> GetStickerPacksAsync() => this.ApiClient.GetStickerPacksAsync(); /// /// Gets the In-App OAuth Url. /// /// Defaults to . /// Redirect Uri. /// Defaults to . /// The OAuth Url public Uri GetInAppOAuth(Permissions permissions = Permissions.None, OAuthScopes scopes = OAuthScopes.BOT_DEFAULT, string redir = null) { permissions &= PermissionMethods.FullPerms; // hey look, it's not all annoying and blue :P return new Uri(new QueryUriBuilder($"{DiscordDomain.GetDomain(CoreDomain.Discord).Url}{Endpoints.OAUTH2}{Endpoints.AUTHORIZE}") .AddParameter("client_id", this.CurrentApplication.Id.ToString(CultureInfo.InvariantCulture)) .AddParameter("scope", OAuth.ResolveScopes(scopes)) .AddParameter("permissions", ((long)permissions).ToString(CultureInfo.InvariantCulture)) .AddParameter("state", "") .AddParameter("redirect_uri", redir ?? "") .ToString()); } /// /// Gets a webhook. /// /// The target webhook id. /// The requested webhook. /// Thrown when the webhook does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task GetWebhookAsync(ulong id) => this.ApiClient.GetWebhookAsync(id); /// /// Gets a webhook. /// /// The target webhook id. /// The target webhook token. /// The requested webhook. /// Thrown when the webhook does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task GetWebhookWithTokenAsync(ulong id, string token) => this.ApiClient.GetWebhookWithTokenAsync(id, token); /// /// Updates current user's activity and status. /// /// Activity to set. /// Status of the user. /// Since when is the client performing the specified activity. /// public Task UpdateStatusAsync(DiscordActivity activity = null, UserStatus? userStatus = null, DateTimeOffset? idleSince = null) => this.InternalUpdateStatusAsync(activity, userStatus, idleSince); /// /// Edits current user. /// /// New username. /// New avatar. /// The modified user. /// Thrown when the user does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public async Task UpdateCurrentUserAsync(string username = null, Optional avatar = default) { var av64 = Optional.FromNoValue(); if (avatar.HasValue && avatar.Value != null) using (var imgtool = new ImageTool(avatar.Value)) av64 = imgtool.GetBase64(); else if (avatar.HasValue) av64 = null; var usr = await this.ApiClient.ModifyCurrentUserAsync(username, av64).ConfigureAwait(false); this.CurrentUser.Username = usr.Username; this.CurrentUser.Discriminator = usr.Discriminator; this.CurrentUser.AvatarHash = usr.AvatarHash; return this.CurrentUser; } /// /// Gets a guild template by the code. /// /// The code of the template. /// The guild template for the code. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task GetTemplateAsync(string code) => this.ApiClient.GetTemplateAsync(code); /// /// Gets all the global application commands for this application. /// /// A list of global application commands. public Task> GetGlobalApplicationCommandsAsync() => this.ApiClient.GetGlobalApplicationCommandsAsync(this.CurrentApplication.Id); /// /// Overwrites the existing global application commands. New commands are automatically created and missing commands are automatically deleted. /// /// The list of commands to overwrite with. /// The list of global commands. public Task> BulkOverwriteGlobalApplicationCommandsAsync(IEnumerable commands) => this.ApiClient.BulkOverwriteGlobalApplicationCommandsAsync(this.CurrentApplication.Id, commands); /// /// Creates or overwrites a global application command. /// /// The command to create. /// The created command. public Task CreateGlobalApplicationCommandAsync(DiscordApplicationCommand command) => this.ApiClient.CreateGlobalApplicationCommandAsync(this.CurrentApplication.Id, command); /// /// Gets a global application command by its id. /// /// The id of the command to get. /// The command with the id. public Task GetGlobalApplicationCommandAsync(ulong commandId) => this.ApiClient.GetGlobalApplicationCommandAsync(this.CurrentApplication.Id, commandId); /// /// Edits a global application command. /// /// The id of the command to edit. /// Action to perform. /// The edited command. public async Task EditGlobalApplicationCommandAsync(ulong commandId, Action action) { var mdl = new ApplicationCommandEditModel(); action(mdl); var applicationId = this.CurrentApplication?.Id ?? (await this.GetCurrentApplicationAsync().ConfigureAwait(false)).Id; return await this.ApiClient.EditGlobalApplicationCommandAsync(applicationId, commandId, mdl.Name, mdl.Description, mdl.Options, mdl.DefaultPermission, mdl.NameLocalizations, mdl.DescriptionLocalizations).ConfigureAwait(false); } /// /// Deletes a global application command. /// /// The id of the command to delete. public Task DeleteGlobalApplicationCommandAsync(ulong commandId) => this.ApiClient.DeleteGlobalApplicationCommandAsync(this.CurrentApplication.Id, commandId); /// /// Gets all the application commands for a guild. /// /// The id of the guild to get application commands for. /// A list of application commands in the guild. public Task> GetGuildApplicationCommandsAsync(ulong guildId) => this.ApiClient.GetGuildApplicationCommandsAsync(this.CurrentApplication.Id, guildId); /// /// Overwrites the existing application commands in a guild. New commands are automatically created and missing commands are automatically deleted. /// /// The id of the guild. /// The list of commands to overwrite with. /// The list of guild commands. public Task> BulkOverwriteGuildApplicationCommandsAsync(ulong guildId, IEnumerable commands) => this.ApiClient.BulkOverwriteGuildApplicationCommandsAsync(this.CurrentApplication.Id, guildId, commands); /// /// Creates or overwrites a guild application command. /// /// The id of the guild to create the application command in. /// The command to create. /// The created command. public Task CreateGuildApplicationCommandAsync(ulong guildId, DiscordApplicationCommand command) => this.ApiClient.CreateGuildApplicationCommandAsync(this.CurrentApplication.Id, guildId, command); /// /// Gets a application command in a guild by its id. /// /// The id of the guild the application command is in. /// The id of the command to get. /// The command with the id. public Task GetGuildApplicationCommandAsync(ulong guildId, ulong commandId) => this.ApiClient.GetGuildApplicationCommandAsync(this.CurrentApplication.Id, guildId, commandId); /// /// Edits a application command in a guild. /// /// The id of the guild the application command is in. /// The id of the command to edit. /// Action to perform. /// The edited command. public async Task EditGuildApplicationCommandAsync(ulong guildId, ulong commandId, Action action) { var mdl = new ApplicationCommandEditModel(); action(mdl); var applicationId = this.CurrentApplication?.Id ?? (await this.GetCurrentApplicationAsync().ConfigureAwait(false)).Id; return await this.ApiClient.EditGuildApplicationCommandAsync(applicationId, guildId, commandId, mdl.Name, mdl.Description, mdl.Options, mdl.DefaultPermission, mdl.NameLocalizations, mdl.DescriptionLocalizations).ConfigureAwait(false); } /// /// Deletes a application command in a guild. /// /// The id of the guild to delete the application command in. /// The id of the command. public Task DeleteGuildApplicationCommandAsync(ulong guildId, ulong commandId) => this.ApiClient.DeleteGuildApplicationCommandAsync(this.CurrentApplication.Id, guildId, commandId); /// /// Gets all command permissions for a guild. /// /// The target guild. public Task> GetGuildApplicationCommandPermissionsAsync(ulong guildId) => this.ApiClient.GetGuildApplicationCommandPermissionsAsync(this.CurrentApplication.Id, guildId); /// /// Gets the permissions for a guild command. /// /// The target guild. /// The target command id. public Task GetApplicationCommandPermissionAsync(ulong guildId, ulong commandId) => this.ApiClient.GetGuildApplicationCommandPermissionAsync(this.CurrentApplication.Id, guildId, commandId); /// /// Overwrites the existing permissions for a application command in a guild. New permissions are automatically created and missing permissions are deleted. /// A command takes up to 10 permission overwrites. /// /// The id of the guild. /// The id of the command. /// List of permissions. public Task OverwriteGuildApplicationCommandPermissionsAsync(ulong guildId, ulong commandId, IEnumerable permissions) => this.ApiClient.OverwriteGuildApplicationCommandPermissionsAsync(this.CurrentApplication.Id, guildId, commandId, permissions); /// /// Overwrites the existing application command permissions in a guild. New permissions are automatically created and missing permissions are deleted. /// Each command takes up to 10 permission overwrites. /// /// The id of the guild. /// The list of permissions to overwrite with. public Task> BulkOverwriteGuildApplicationCommandsAsync(ulong guildId, IEnumerable permissionsOverwrites) => this.ApiClient.BulkOverwriteGuildApplicationCommandPermissionsAsync(this.CurrentApplication.Id, guildId, permissionsOverwrites); #endregion #region Internal Caching Methods /// /// Gets the internal chached threads. /// /// The target thread id. /// The requested thread. internal DiscordThreadChannel InternalGetCachedThread(ulong threadId) { foreach (var guild in this.Guilds.Values) if (guild.Threads.TryGetValue(threadId, out var foundThread)) return foundThread; return null; } /// /// Gets the internal chached scheduled event. /// /// The target scheduled event id. /// The requested scheduled event. internal DiscordScheduledEvent InternalGetCachedScheduledEvent(ulong scheduledEventId) { foreach (var guild in this.Guilds.Values) if (guild.ScheduledEvents.TryGetValue(scheduledEventId, out var foundScheduledEvent)) return foundScheduledEvent; return null; } /// /// Gets the internal chached channel. /// /// The target channel id. /// The requested channel. internal DiscordChannel InternalGetCachedChannel(ulong channelId) { foreach (var guild in this.Guilds.Values) if (guild.Channels.TryGetValue(channelId, out var foundChannel)) return foundChannel; return null; } /// /// Gets the internal chached guild. /// /// The target guild id. /// The requested guild. internal DiscordGuild InternalGetCachedGuild(ulong? guildId) { if (this.GuildsInternal != null && guildId.HasValue) { if (this.GuildsInternal.TryGetValue(guildId.Value, out var guild)) return guild; } return null; } /// /// Updates a message. /// /// The message to update. /// The author to update. /// The guild to update. /// The member to update. private void UpdateMessage(DiscordMessage message, TransportUser author, DiscordGuild guild, TransportMember member) { if (author != null) { var usr = new DiscordUser(author) { Discord = this }; if (member != null) member.User = author; message.Author = this.UpdateUser(usr, guild?.Id, guild, member); } var channel = this.InternalGetCachedChannel(message.ChannelId); if (channel != null) return; channel = !message.GuildId.HasValue ? new DiscordDmChannel { Id = message.ChannelId, Discord = this, Type = ChannelType.Private } : new DiscordChannel { Id = message.ChannelId, Discord = this }; message.Channel = channel; } /// /// Updates a scheduled event. /// /// The scheduled event to update. /// The guild to update. /// The updated scheduled event. private DiscordScheduledEvent UpdateScheduledEvent(DiscordScheduledEvent scheduledEvent, DiscordGuild guild) { if (scheduledEvent != null) { _ = guild.ScheduledEventsInternal.AddOrUpdate(scheduledEvent.Id, scheduledEvent, (id, old) => { old.Discord = this; old.Description = scheduledEvent.Description; old.ChannelId = scheduledEvent.ChannelId; old.EntityId = scheduledEvent.EntityId; old.EntityType = scheduledEvent.EntityType; old.EntityMetadata = scheduledEvent.EntityMetadata; old.PrivacyLevel = scheduledEvent.PrivacyLevel; old.Name = scheduledEvent.Name; old.Status = scheduledEvent.Status; old.UserCount = scheduledEvent.UserCount; old.ScheduledStartTimeRaw = scheduledEvent.ScheduledStartTimeRaw; old.ScheduledEndTimeRaw = scheduledEvent.ScheduledEndTimeRaw; return old; }); } return scheduledEvent; } /// /// Updates a user. /// /// The user to update. /// The guild id to update. /// The guild to update. /// The member to update. /// The updated user. private DiscordUser UpdateUser(DiscordUser usr, ulong? guildId, DiscordGuild guild, TransportMember mbr) { if (mbr != null) { if (mbr.User != null) { usr = new DiscordUser(mbr.User) { Discord = this }; _ = this.UserCache.AddOrUpdate(usr.Id, usr, (id, old) => { old.Username = usr.Username; old.Discriminator = usr.Discriminator; old.AvatarHash = usr.AvatarHash; return old; }); usr = new DiscordMember(mbr) { Discord = this, GuildId = guildId.Value }; } var intents = this.Configuration.Intents; DiscordMember member = default; if (!intents.HasAllPrivilegedIntents() || guild.IsLarge) // we have the necessary privileged intents, no need to worry about caching here unless guild is large. { if (guild?.MembersInternal.TryGetValue(usr.Id, out member) == false) { if (intents.HasIntent(DiscordIntents.GuildMembers) || this.Configuration.AlwaysCacheMembers) // member can be updated by events, so cache it { guild.MembersInternal.TryAdd(usr.Id, (DiscordMember)usr); } } else if (intents.HasIntent(DiscordIntents.GuildPresences) || this.Configuration.AlwaysCacheMembers) // we can attempt to update it if it's already in cache. { if (!intents.HasIntent(DiscordIntents.GuildMembers)) // no need to update if we already have the member events { _ = guild.MembersInternal.TryUpdate(usr.Id, (DiscordMember)usr, member); } } } } else if (usr.Username != null) // check if not a skeleton user { _ = this.UserCache.AddOrUpdate(usr.Id, usr, (id, old) => { old.Username = usr.Username; old.Discriminator = usr.Discriminator; old.AvatarHash = usr.AvatarHash; return old; }); } return usr; } /// /// Updates the cached scheduled events in a guild. /// /// The guild. /// The raw events. private void UpdateCachedScheduledEvent(DiscordGuild guild, JArray rawEvents) { if (this._disposed) return; if (rawEvents != null) { guild.ScheduledEventsInternal.Clear(); foreach (var xj in rawEvents) { var xtm = xj.ToDiscordObject(); xtm.Discord = this; guild.ScheduledEventsInternal[xtm.Id] = xtm; } } } /// /// Updates the cached guild. /// /// The new guild. /// The raw members. private void UpdateCachedGuild(DiscordGuild newGuild, JArray rawMembers) { if (this._disposed) return; if (!this.GuildsInternal.ContainsKey(newGuild.Id)) this.GuildsInternal[newGuild.Id] = newGuild; var guild = this.GuildsInternal[newGuild.Id]; if (newGuild.ChannelsInternal != null && newGuild.ChannelsInternal.Count > 0) { foreach (var channel in newGuild.ChannelsInternal.Values) { if (guild.ChannelsInternal.TryGetValue(channel.Id, out _)) continue; foreach (var overwrite in channel.PermissionOverwritesInternal) { overwrite.Discord = this; overwrite.ChannelId = channel.Id; } guild.ChannelsInternal[channel.Id] = channel; } } if (newGuild.ThreadsInternal != null && newGuild.ThreadsInternal.Count > 0) { foreach (var thread in newGuild.ThreadsInternal.Values) { if (guild.ThreadsInternal.TryGetValue(thread.Id, out _)) continue; guild.ThreadsInternal[thread.Id] = thread; } } if (newGuild.ScheduledEventsInternal != null && newGuild.ScheduledEventsInternal.Count > 0) { foreach (var @event in newGuild.ScheduledEventsInternal.Values) { if (guild.ScheduledEventsInternal.TryGetValue(@event.Id, out _)) continue; guild.ScheduledEventsInternal[@event.Id] = @event; } } foreach (var newEmoji in newGuild.EmojisInternal.Values) _ = guild.EmojisInternal.GetOrAdd(newEmoji.Id, _ => newEmoji); foreach (var newSticker in newGuild.StickersInternal.Values) _ = guild.StickersInternal.GetOrAdd(newSticker.Id, _ => newSticker); foreach (var newStageInstance in newGuild.StageInstancesInternal.Values) _ = guild.StageInstancesInternal.GetOrAdd(newStageInstance.Id, _ => newStageInstance); if (rawMembers != null) { guild.MembersInternal.Clear(); foreach (var xj in rawMembers) { var xtm = xj.ToDiscordObject(); var xu = new DiscordUser(xtm.User) { Discord = this }; _ = this.UserCache.AddOrUpdate(xtm.User.Id, xu, (id, old) => { old.Username = xu.Username; old.Discriminator = xu.Discriminator; old.AvatarHash = xu.AvatarHash; old.PremiumType = xu.PremiumType; return old; }); guild.MembersInternal[xtm.User.Id] = new DiscordMember(xtm) { Discord = this, GuildId = guild.Id }; } } foreach (var role in newGuild.RolesInternal.Values) { if (guild.RolesInternal.TryGetValue(role.Id, out _)) continue; role.GuildId = guild.Id; guild.RolesInternal[role.Id] = role; } guild.Name = newGuild.Name; guild.AfkChannelId = newGuild.AfkChannelId; guild.AfkTimeout = newGuild.AfkTimeout; guild.DefaultMessageNotifications = newGuild.DefaultMessageNotifications; guild.RawFeatures = newGuild.RawFeatures; guild.IconHash = newGuild.IconHash; guild.MfaLevel = newGuild.MfaLevel; guild.OwnerId = newGuild.OwnerId; guild.VoiceRegionId = newGuild.VoiceRegionId; guild.SplashHash = newGuild.SplashHash; guild.VerificationLevel = newGuild.VerificationLevel; guild.WidgetEnabled = newGuild.WidgetEnabled; guild.WidgetChannelId = newGuild.WidgetChannelId; guild.ExplicitContentFilter = newGuild.ExplicitContentFilter; guild.PremiumTier = newGuild.PremiumTier; guild.PremiumSubscriptionCount = newGuild.PremiumSubscriptionCount; guild.PremiumProgressBarEnabled = newGuild.PremiumProgressBarEnabled; guild.BannerHash = newGuild.BannerHash; guild.Description = newGuild.Description; guild.VanityUrlCode = newGuild.VanityUrlCode; guild.SystemChannelId = newGuild.SystemChannelId; guild.SystemChannelFlags = newGuild.SystemChannelFlags; guild.DiscoverySplashHash = newGuild.DiscoverySplashHash; guild.MaxMembers = newGuild.MaxMembers; guild.MaxPresences = newGuild.MaxPresences; guild.ApproximateMemberCount = newGuild.ApproximateMemberCount; guild.ApproximatePresenceCount = newGuild.ApproximatePresenceCount; guild.MaxVideoChannelUsers = newGuild.MaxVideoChannelUsers; guild.PreferredLocale = newGuild.PreferredLocale; guild.RulesChannelId = newGuild.RulesChannelId; guild.PublicUpdatesChannelId = newGuild.PublicUpdatesChannelId; guild.ApplicationId = newGuild.ApplicationId; // fields not sent for update: // - guild.Channels // - voice states // - guild.JoinedAt = new_guild.JoinedAt; // - guild.Large = new_guild.Large; // - guild.MemberCount = Math.Max(new_guild.MemberCount, guild._members.Count); // - guild.Unavailable = new_guild.Unavailable; } /// /// Populates the message reactions and cache. /// /// The message. /// The author. /// The member. private void PopulateMessageReactionsAndCache(DiscordMessage message, TransportUser author, TransportMember member) { var guild = message.Channel?.Guild ?? this.InternalGetCachedGuild(message.GuildId); this.UpdateMessage(message, author, guild, member); if (message.ReactionsInternal == null) message.ReactionsInternal = new List(); foreach (var xr in message.ReactionsInternal) xr.Emoji.Discord = this; if (this.Configuration.MessageCacheSize > 0 && message.Channel != null) this.MessageCache?.Add(message); } #endregion #region Disposal ~DiscordClient() { this.Dispose(); } /// /// Whether the client is disposed. /// private bool _disposed; /// /// Disposes the client. /// public override void Dispose() { if (this._disposed) return; this._disposed = true; GC.SuppressFinalize(this); this.DisconnectAsync().ConfigureAwait(false).GetAwaiter().GetResult(); this.ApiClient.Rest.Dispose(); this.CurrentUser = null; var extensions = this._extensions; // prevent _extensions being modified during dispose this._extensions = null; foreach (var extension in extensions) if (extension is IDisposable disposable) disposable.Dispose(); try { this._cancelTokenSource?.Cancel(); this._cancelTokenSource?.Dispose(); } catch { } this.GuildsInternal = null; this._heartbeatTask = null; } #endregion } } diff --git a/DisCatSharp/Clients/DiscordShardedClient.Events.cs b/DisCatSharp/Clients/DiscordShardedClient.Events.cs index 04a431d71..6f8ce518b 100644 --- a/DisCatSharp/Clients/DiscordShardedClient.Events.cs +++ b/DisCatSharp/Clients/DiscordShardedClient.Events.cs @@ -1,1580 +1,1642 @@ // 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.Threading.Tasks; using DisCatSharp.Common.Utilities; using DisCatSharp.EventArgs; using Microsoft.Extensions.Logging; namespace DisCatSharp { /// /// Represents a discord sharded client. /// public sealed partial class DiscordShardedClient { #region WebSocket /// /// Fired whenever a WebSocket error occurs within the client. /// public event AsyncEventHandler SocketErrored { add => this._socketErrored.Register(value); remove => this._socketErrored.Unregister(value); } private AsyncEvent _socketErrored; /// /// Fired whenever WebSocket connection is established. /// public event AsyncEventHandler SocketOpened { add => this._socketOpened.Register(value); remove => this._socketOpened.Unregister(value); } private AsyncEvent _socketOpened; /// /// Fired whenever WebSocket connection is terminated. /// public event AsyncEventHandler SocketClosed { add => this._socketClosed.Register(value); remove => this._socketClosed.Unregister(value); } private AsyncEvent _socketClosed; /// /// Fired when the client enters ready state. /// public event AsyncEventHandler Ready { add => this._ready.Register(value); remove => this._ready.Unregister(value); } private AsyncEvent _ready; /// /// Fired whenever a session is resumed. /// public event AsyncEventHandler Resumed { add => this._resumed.Register(value); remove => this._resumed.Unregister(value); } private AsyncEvent _resumed; /// /// Fired on received heartbeat ACK. /// public event AsyncEventHandler Heartbeated { add => this._heartbeated.Register(value); remove => this._heartbeated.Unregister(value); } private AsyncEvent _heartbeated; #endregion #region Channel /// /// Fired when a new channel is created. /// For this Event you need the intent specified in /// public event AsyncEventHandler ChannelCreated { add => this._channelCreated.Register(value); remove => this._channelCreated.Unregister(value); } private AsyncEvent _channelCreated; /// /// Fired when a channel is updated. /// For this Event you need the intent specified in /// public event AsyncEventHandler ChannelUpdated { add => this._channelUpdated.Register(value); remove => this._channelUpdated.Unregister(value); } private AsyncEvent _channelUpdated; /// /// Fired when a channel is deleted /// For this Event you need the intent specified in /// public event AsyncEventHandler ChannelDeleted { add => this._channelDeleted.Register(value); remove => this._channelDeleted.Unregister(value); } private AsyncEvent _channelDeleted; /// /// Fired when a dm channel is deleted /// For this Event you need the intent specified in /// public event AsyncEventHandler DmChannelDeleted { add => this._dmChannelDeleted.Register(value); remove => this._dmChannelDeleted.Unregister(value); } private AsyncEvent _dmChannelDeleted; /// /// Fired whenever a channel's pinned message list is updated. /// For this Event you need the intent specified in /// public event AsyncEventHandler ChannelPinsUpdated { add => this._channelPinsUpdated.Register(value); remove => this._channelPinsUpdated.Unregister(value); } private AsyncEvent _channelPinsUpdated; #endregion #region Guild /// /// Fired when the user joins a new guild. /// For this Event you need the intent specified in /// /// [alias="GuildJoined"][alias="JoinedGuild"] public event AsyncEventHandler GuildCreated { add => this._guildCreated.Register(value); remove => this._guildCreated.Unregister(value); } private AsyncEvent _guildCreated; /// /// Fired when a guild is becoming available. /// public event AsyncEventHandler GuildAvailable { add => this._guildAvailable.Register(value); remove => this._guildAvailable.Unregister(value); } private AsyncEvent _guildAvailable; /// /// Fired when a guild is updated. /// For this Event you need the intent specified in /// public event AsyncEventHandler GuildUpdated { add => this._guildUpdated.Register(value); remove => this._guildUpdated.Unregister(value); } private AsyncEvent _guildUpdated; /// /// Fired when the user leaves or is removed from a guild. /// For this Event you need the intent specified in /// public event AsyncEventHandler GuildDeleted { add => this._guildDeleted.Register(value); remove => this._guildDeleted.Unregister(value); } private AsyncEvent _guildDeleted; /// /// Fired when a guild becomes unavailable. /// public event AsyncEventHandler GuildUnavailable { add => this._guildUnavailable.Register(value); remove => this._guildUnavailable.Unregister(value); } private AsyncEvent _guildUnavailable; /// /// Fired when all guilds finish streaming from Discord. /// public event AsyncEventHandler GuildDownloadCompleted { add => this._guildDownloadCompleted.Register(value); remove => this._guildDownloadCompleted.Unregister(value); } private AsyncEvent _guildDownloadCompleted; /// /// Fired when a guilds emojis get updated /// For this Event you need the intent specified in /// public event AsyncEventHandler GuildEmojisUpdated { add => this._guildEmojisUpdated.Register(value); remove => this._guildEmojisUpdated.Unregister(value); } private AsyncEvent _guildEmojisUpdated; /// /// Fired when a guilds stickers get updated /// For this Event you need the intent specified in /// public event AsyncEventHandler GuildStickersUpdated { add => this._guildStickersUpdated.Register(value); remove => this._guildStickersUpdated.Unregister(value); } private AsyncEvent _guildStickersUpdated; /// /// Fired when a guild integration is updated. /// public event AsyncEventHandler GuildIntegrationsUpdated { add => this._guildIntegrationsUpdated.Register(value); remove => this._guildIntegrationsUpdated.Unregister(value); } private AsyncEvent _guildIntegrationsUpdated; #endregion #region Guild Ban /// /// Fired when a guild ban gets added /// For this Event you need the intent specified in /// public event AsyncEventHandler GuildBanAdded { add => this._guildBanAdded.Register(value); remove => this._guildBanAdded.Unregister(value); } private AsyncEvent _guildBanAdded; /// /// Fired when a guild ban gets removed /// For this Event you need the intent specified in /// public event AsyncEventHandler GuildBanRemoved { add => this._guildBanRemoved.Register(value); remove => this._guildBanRemoved.Unregister(value); } private AsyncEvent _guildBanRemoved; #endregion + #region Guild Timeout + + /// + /// Fired when a guild member timeout gets added. + /// For this Event you need the intent specified in + /// + public event AsyncEventHandler GuildMemberTimeoutAdded + { + add => this._guildMemberTimeoutAdded.Register(value); + remove => this._guildMemberTimeoutAdded.Unregister(value); + } + private AsyncEvent _guildMemberTimeoutAdded; + + /// + /// Fired when a guild member timeout gets changed. + /// For this Event you need the intent specified in + /// + public event AsyncEventHandler GuildMemberTimeoutChanged + { + add => this._guildMemberTimeoutChanged.Register(value); + remove => this._guildMemberTimeoutChanged.Unregister(value); + } + private AsyncEvent _guildMemberTimeoutChanged; + + + /// + /// Fired when a guild member timeout gets removed. + /// For this Event you need the intent specified in + /// + public event AsyncEventHandler GuildMemberTimeoutRemoved + { + add => this._guildMemberTimeoutRemoved.Register(value); + remove => this._guildMemberTimeoutRemoved.Unregister(value); + } + private AsyncEvent _guildMemberTimeoutRemoved; + + #endregion + #region Guild Event /// /// Fired when a scheduled event is created. /// For this Event you need the intent specified in /// public event AsyncEventHandler GuildScheduledEventCreated { add => this._guildScheduledEventCreated.Register(value); remove => this._guildScheduledEventCreated.Unregister(value); } private AsyncEvent _guildScheduledEventCreated; /// /// Fired when a scheduled event is updated. /// For this Event you need the intent specified in /// public event AsyncEventHandler GuildScheduledEventUpdated { add => this._guildScheduledEventUpdated.Register(value); remove => this._guildScheduledEventUpdated.Unregister(value); } private AsyncEvent _guildScheduledEventUpdated; /// /// Fired when a scheduled event is deleted. /// For this Event you need the intent specified in /// public event AsyncEventHandler GuildScheduledEventDeleted { add => this._guildScheduledEventDeleted.Register(value); remove => this._guildScheduledEventDeleted.Unregister(value); } private AsyncEvent _guildScheduledEventDeleted; /// /// Fired when a user subscribes to a scheduled event. /// For this Event you need the intent specified in /// public event AsyncEventHandler GuildScheduledEventUserAdded { add => this._guildScheduledEventUserAdded.Register(value); remove => this._guildScheduledEventUserAdded.Unregister(value); } private AsyncEvent _guildScheduledEventUserAdded; /// /// Fired when a user unsubscribes from a scheduled event. /// For this Event you need the intent specified in /// public event AsyncEventHandler GuildScheduledEventUserRemoved { add => this._guildScheduledEventUserRemoved.Register(value); remove => this._guildScheduledEventUserRemoved.Unregister(value); } private AsyncEvent _guildScheduledEventUserRemoved; #endregion #region Guild Integration /// /// Fired when a guild integration is created. /// For this Event you need the intent specified in /// public event AsyncEventHandler GuildIntegrationCreated { add => this._guildIntegrationCreated.Register(value); remove => this._guildIntegrationCreated.Unregister(value); } private AsyncEvent _guildIntegrationCreated; /// /// Fired when a guild integration is updated. /// For this Event you need the intent specified in /// public event AsyncEventHandler GuildIntegrationUpdated { add => this._guildIntegrationUpdated.Register(value); remove => this._guildIntegrationUpdated.Unregister(value); } private AsyncEvent _guildIntegrationUpdated; /// /// Fired when a guild integration is deleted. /// For this Event you need the intent specified in /// public event AsyncEventHandler GuildIntegrationDeleted { add => this._guildIntegrationDeleted.Register(value); remove => this._guildIntegrationDeleted.Unregister(value); } private AsyncEvent _guildIntegrationDeleted; #endregion #region Guild Member /// /// Fired when a new user joins a guild. /// For this Event you need the intent specified in /// public event AsyncEventHandler GuildMemberAdded { add => this._guildMemberAdded.Register(value); remove => this._guildMemberAdded.Unregister(value); } private AsyncEvent _guildMemberAdded; /// /// Fired when a user is removed from a guild (leave/kick/ban). /// For this Event you need the intent specified in /// public event AsyncEventHandler GuildMemberRemoved { add => this._guildMemberRemoved.Register(value); remove => this._guildMemberRemoved.Unregister(value); } private AsyncEvent _guildMemberRemoved; /// /// Fired when a guild member is updated. /// For this Event you need the intent specified in /// public event AsyncEventHandler GuildMemberUpdated { add => this._guildMemberUpdated.Register(value); remove => this._guildMemberUpdated.Unregister(value); } private AsyncEvent _guildMemberUpdated; /// /// Fired in response to Gateway Request Guild Members. /// public event AsyncEventHandler GuildMembersChunked { add => this._guildMembersChunk.Register(value); remove => this._guildMembersChunk.Unregister(value); } private AsyncEvent _guildMembersChunk; #endregion #region Guild Role /// /// Fired when a guild role is created. /// For this Event you need the intent specified in /// public event AsyncEventHandler GuildRoleCreated { add => this._guildRoleCreated.Register(value); remove => this._guildRoleCreated.Unregister(value); } private AsyncEvent _guildRoleCreated; /// /// Fired when a guild role is updated. /// For this Event you need the intent specified in /// public event AsyncEventHandler GuildRoleUpdated { add => this._guildRoleUpdated.Register(value); remove => this._guildRoleUpdated.Unregister(value); } private AsyncEvent _guildRoleUpdated; /// /// Fired when a guild role is updated. /// For this Event you need the intent specified in /// public event AsyncEventHandler GuildRoleDeleted { add => this._guildRoleDeleted.Register(value); remove => this._guildRoleDeleted.Unregister(value); } private AsyncEvent _guildRoleDeleted; #endregion #region Invite /// /// Fired when an invite is created. /// For this Event you need the intent specified in /// public event AsyncEventHandler InviteCreated { add => this._inviteCreated.Register(value); remove => this._inviteCreated.Unregister(value); } private AsyncEvent _inviteCreated; /// /// Fired when an invite is deleted. /// For this Event you need the intent specified in /// public event AsyncEventHandler InviteDeleted { add => this._inviteDeleted.Register(value); remove => this._inviteDeleted.Unregister(value); } private AsyncEvent _inviteDeleted; #endregion #region Message /// /// Fired when a message is created. /// For this Event you need the intent specified in /// public event AsyncEventHandler MessageCreated { add => this._messageCreated.Register(value); remove => this._messageCreated.Unregister(value); } private AsyncEvent _messageCreated; /// /// Fired when a message is updated. /// For this Event you need the intent specified in /// public event AsyncEventHandler MessageUpdated { add => this._messageUpdated.Register(value); remove => this._messageUpdated.Unregister(value); } private AsyncEvent _messageUpdated; /// /// Fired when a message is deleted. /// For this Event you need the intent specified in /// public event AsyncEventHandler MessageDeleted { add => this._messageDeleted.Register(value); remove => this._messageDeleted.Unregister(value); } private AsyncEvent _messageDeleted; /// /// Fired when multiple messages are deleted at once. /// For this Event you need the intent specified in /// public event AsyncEventHandler MessagesBulkDeleted { add => this._messageBulkDeleted.Register(value); remove => this._messageBulkDeleted.Unregister(value); } private AsyncEvent _messageBulkDeleted; #endregion #region Message Reaction /// /// Fired when a reaction gets added to a message. /// For this Event you need the intent specified in /// public event AsyncEventHandler MessageReactionAdded { add => this._messageReactionAdded.Register(value); remove => this._messageReactionAdded.Unregister(value); } private AsyncEvent _messageReactionAdded; /// /// Fired when a reaction gets removed from a message. /// For this Event you need the intent specified in /// public event AsyncEventHandler MessageReactionRemoved { add => this._messageReactionRemoved.Register(value); remove => this._messageReactionRemoved.Unregister(value); } private AsyncEvent _messageReactionRemoved; /// /// Fired when all reactions get removed from a message. /// For this Event you need the intent specified in /// public event AsyncEventHandler MessageReactionsCleared { add => this._messageReactionsCleared.Register(value); remove => this._messageReactionsCleared.Unregister(value); } private AsyncEvent _messageReactionsCleared; /// /// Fired when all reactions of a specific reaction are removed from a message. /// For this Event you need the intent specified in /// public event AsyncEventHandler MessageReactionRemovedEmoji { add => this._messageReactionRemovedEmoji.Register(value); remove => this._messageReactionRemovedEmoji.Unregister(value); } private AsyncEvent _messageReactionRemovedEmoji; #endregion #region Stage Instance /// /// Fired when a Stage Instance is created. /// For this Event you need the intent specified in /// public event AsyncEventHandler StageInstanceCreated { add => this._stageInstanceCreated.Register(value); remove => this._stageInstanceCreated.Unregister(value); } private AsyncEvent _stageInstanceCreated; /// /// Fired when a Stage Instance is updated. /// For this Event you need the intent specified in /// public event AsyncEventHandler StageInstanceUpdated { add => this._stageInstanceUpdated.Register(value); remove => this._stageInstanceUpdated.Unregister(value); } private AsyncEvent _stageInstanceUpdated; /// /// Fired when a Stage Instance is deleted. /// For this Event you need the intent specified in /// public event AsyncEventHandler StageInstanceDeleted { add => this._stageInstanceDeleted.Register(value); remove => this._stageInstanceDeleted.Unregister(value); } private AsyncEvent _stageInstanceDeleted; #endregion #region Thread /// /// Fired when a thread is created. /// For this Event you need the intent specified in /// public event AsyncEventHandler ThreadCreated { add => this._threadCreated.Register(value); remove => this._threadCreated.Unregister(value); } private AsyncEvent _threadCreated; /// /// Fired when a thread is updated. /// For this Event you need the intent specified in /// public event AsyncEventHandler ThreadUpdated { add => this._threadUpdated.Register(value); remove => this._threadUpdated.Unregister(value); } private AsyncEvent _threadUpdated; /// /// Fired when a thread is deleted. /// For this Event you need the intent specified in /// public event AsyncEventHandler ThreadDeleted { add => this._threadDeleted.Register(value); remove => this._threadDeleted.Unregister(value); } private AsyncEvent _threadDeleted; /// /// Fired when a thread member is updated. /// For this Event you need the intent specified in /// public event AsyncEventHandler ThreadListSynced { add => this._threadListSynced.Register(value); remove => this._threadListSynced.Unregister(value); } private AsyncEvent _threadListSynced; /// /// Fired when a thread member is updated. /// For this Event you need the intent specified in /// public event AsyncEventHandler ThreadMemberUpdated { add => this._threadMemberUpdated.Register(value); remove => this._threadMemberUpdated.Unregister(value); } private AsyncEvent _threadMemberUpdated; /// /// Fired when the thread members are updated. /// For this Event you need the or intent specified in /// public event AsyncEventHandler ThreadMembersUpdated { add => this._threadMembersUpdated.Register(value); remove => this._threadMembersUpdated.Unregister(value); } private AsyncEvent _threadMembersUpdated; #endregion #region Activities /// /// Fired when a embedded activity has been updated. /// public event AsyncEventHandler EmbeddedActivityUpdated { add => this._embeddedActivityUpdated.Register(value); remove => this._embeddedActivityUpdated.Unregister(value); } private AsyncEvent _embeddedActivityUpdated; #endregion #region User/Presence Update /// /// Fired when a presence has been updated. /// For this Event you need the intent specified in /// public event AsyncEventHandler PresenceUpdated { add => this._presenceUpdated.Register(value); remove => this._presenceUpdated.Unregister(value); } private AsyncEvent _presenceUpdated; /// /// Fired when the current user updates their settings. /// For this Event you need the intent specified in /// public event AsyncEventHandler UserSettingsUpdated { add => this._userSettingsUpdated.Register(value); remove => this._userSettingsUpdated.Unregister(value); } private AsyncEvent _userSettingsUpdated; /// /// Fired when properties about the current user change. /// For this Event you need the intent specified in /// /// /// NB: This event only applies for changes to the current user, the client that is connected to Discord. /// public event AsyncEventHandler UserUpdated { add => this._userUpdated.Register(value); remove => this._userUpdated.Unregister(value); } private AsyncEvent _userUpdated; #endregion #region Voice /// /// Fired when someone joins/leaves/moves voice channels. /// For this Event you need the intent specified in /// public event AsyncEventHandler VoiceStateUpdated { add => this._voiceStateUpdated.Register(value); remove => this._voiceStateUpdated.Unregister(value); } private AsyncEvent _voiceStateUpdated; /// /// Fired when a guild's voice server is updated. /// For this Event you need the intent specified in /// public event AsyncEventHandler VoiceServerUpdated { add => this._voiceServerUpdated.Register(value); remove => this._voiceServerUpdated.Unregister(value); } private AsyncEvent _voiceServerUpdated; #endregion #region Application /// /// Fired when a new application command is registered. /// public event AsyncEventHandler ApplicationCommandCreated { add => this._applicationCommandCreated.Register(value); remove => this._applicationCommandCreated.Unregister(value); } private AsyncEvent _applicationCommandCreated; /// /// Fired when an application command is updated. /// public event AsyncEventHandler ApplicationCommandUpdated { add => this._applicationCommandUpdated.Register(value); remove => this._applicationCommandUpdated.Unregister(value); } private AsyncEvent _applicationCommandUpdated; /// /// Fired when an application command is deleted. /// public event AsyncEventHandler ApplicationCommandDeleted { add => this._applicationCommandDeleted.Register(value); remove => this._applicationCommandDeleted.Unregister(value); } private AsyncEvent _applicationCommandDeleted; /// /// Fired when a new application command is registered. /// public event AsyncEventHandler GuildApplicationCommandCountUpdated { add => this._guildApplicationCommandCountUpdated.Register(value); remove => this._guildApplicationCommandCountUpdated.Unregister(value); } private AsyncEvent _guildApplicationCommandCountUpdated; /// /// Fired when a user uses a context menu. /// public event AsyncEventHandler ContextMenuInteractionCreated { add => this._contextMenuInteractionCreated.Register(value); remove => this._contextMenuInteractionCreated.Unregister(value); } private AsyncEvent _contextMenuInteractionCreated; /// /// Fired when application command permissions gets updated. /// public event AsyncEventHandler ApplicationCommandPermissionsUpdated { add => this._applicationCommandPermissionsUpdated.Register(value); remove => this._applicationCommandPermissionsUpdated.Unregister(value); } private AsyncEvent _applicationCommandPermissionsUpdated; #endregion #region Misc /// /// Fired when an interaction is invoked. /// public event AsyncEventHandler InteractionCreated { add => this._interactionCreated.Register(value); remove => this._interactionCreated.Unregister(value); } private AsyncEvent _interactionCreated; /// /// Fired when a component is invoked. /// public event AsyncEventHandler ComponentInteractionCreated { add => this._componentInteractionCreated.Register(value); remove => this._componentInteractionCreated.Unregister(value); } private AsyncEvent _componentInteractionCreated; /// /// Fired when a user starts typing in a channel. /// public event AsyncEventHandler TypingStarted { add => this._typingStarted.Register(value); remove => this._typingStarted.Unregister(value); } private AsyncEvent _typingStarted; /// /// Fired when an unknown event gets received. /// public event AsyncEventHandler UnknownEvent { add => this._unknownEvent.Register(value); remove => this._unknownEvent.Unregister(value); } private AsyncEvent _unknownEvent; /// /// Fired whenever webhooks update. /// public event AsyncEventHandler WebhooksUpdated { add => this._webhooksUpdated.Register(value); remove => this._webhooksUpdated.Unregister(value); } private AsyncEvent _webhooksUpdated; /// /// Fired whenever an error occurs within an event handler. /// public event AsyncEventHandler ClientErrored { add => this._clientErrored.Register(value); remove => this._clientErrored.Unregister(value); } private AsyncEvent _clientErrored; #endregion #region Error Handling /// /// Handles event errors. /// /// The event. /// The exception. /// The event handler. /// The sender. /// The event args. internal void EventErrorHandler(AsyncEvent asyncEvent, Exception ex, AsyncEventHandler handler, DiscordClient sender, TArgs eventArgs) where TArgs : AsyncEventArgs { if (ex is AsyncEventTimeoutException) { this.Logger.LogWarning(LoggerEvents.EventHandlerException, $"An event handler for {asyncEvent.Name} took too long to execute. Defined as \"{handler.Method.ToString().Replace(handler.Method.ReturnType.ToString(), "").TrimStart()}\" located in \"{handler.Method.DeclaringType}\"."); return; } this.Logger.LogError(LoggerEvents.EventHandlerException, ex, "Event handler exception for event {0} thrown from {1} (defined in {2})", asyncEvent.Name, handler.Method, handler.Method.DeclaringType); this._clientErrored.InvokeAsync(sender, new ClientErrorEventArgs(this.ShardClients[0].ServiceProvider) { EventName = asyncEvent.Name, Exception = ex }).ConfigureAwait(false).GetAwaiter().GetResult(); } /// /// Fired on heartbeat attempt cancellation due to too many failed heartbeats. /// public event AsyncEventHandler Zombied { add => this._zombied.Register(value); remove => this._zombied.Unregister(value); } private AsyncEvent _zombied; /// /// Fired when a gateway payload is received. /// public event AsyncEventHandler PayloadReceived { add => this._payloadReceived.Register(value); remove => this._payloadReceived.Unregister(value); } private AsyncEvent _payloadReceived; /// /// Fired when a event handler throws an exception. /// /// The event. /// The exception. /// The event handler. /// The sender. /// The event args. private void Goof(AsyncEvent asyncEvent, Exception ex, AsyncEventHandler handler, DiscordClient sender, TArgs eventArgs) where TArgs : AsyncEventArgs => this.Logger.LogCritical(LoggerEvents.EventHandlerException, ex, "Exception event handler {0} (defined in {1}) threw an exception", handler.Method, handler.Method.DeclaringType); #endregion #region Event Dispatchers /// /// Handles the client zombied event. /// /// The client. /// The event args. private Task Client_Zombied(DiscordClient client, ZombiedEventArgs e) => this._zombied.InvokeAsync(client, e); + /// + /// Handles the guild member timeout removed event. + /// + /// The client. + /// The event args. + private Task Client_GuildMemberTimeoutRemoved(DiscordClient client, GuildMemberTimeoutRemoveEventArgs e) + => this._guildMemberTimeoutRemoved.InvokeAsync(client, e); + + /// + /// Handles the guild member timeout changed event. + /// + /// The client. + /// The event args. + private Task Client_GuildMemberTimeoutChanged(DiscordClient client, GuildMemberTimeoutUpdateEventArgs e) + => this._guildMemberTimeoutChanged.InvokeAsync(client, e); + + /// + /// Handles the guild member timeout added event. + /// + /// The client. + /// The event args. + private Task Client_GuildMemberTimeoutAdded(DiscordClient client, GuildMemberTimeoutAddEventArgs e) + => this._guildMemberTimeoutAdded.InvokeAsync(client, e); + /// /// Handles the embedded activity updated event. /// /// The client. /// The event args. private Task Client_EmbeddedActivityUpdated(DiscordClient client, EmbeddedActivityUpdateEventArgs e) => this._embeddedActivityUpdated.InvokeAsync(client, e); /// /// Handles the payload received event. /// /// The client. /// The event args. private Task Client_PayloadReceived(DiscordClient client, PayloadReceivedEventArgs e) => this._payloadReceived.InvokeAsync(client, e); /// /// Handles the client error event. /// /// The client. /// The event args. private Task Client_ClientError(DiscordClient client, ClientErrorEventArgs e) => this._clientErrored.InvokeAsync(client, e); /// /// Handles the socket error event. /// /// The client. /// The event args. private Task Client_SocketError(DiscordClient client, SocketErrorEventArgs e) => this._socketErrored.InvokeAsync(client, e); /// /// Handles the socket opened event. /// /// The client. /// The event args. private Task Client_SocketOpened(DiscordClient client, SocketEventArgs e) => this._socketOpened.InvokeAsync(client, e); /// /// Handles the socket closed event. /// /// The client. /// The event args. private Task Client_SocketClosed(DiscordClient client, SocketCloseEventArgs e) => this._socketClosed.InvokeAsync(client, e); /// /// Handles the ready event. /// /// The client. /// The event args. private Task Client_Ready(DiscordClient client, ReadyEventArgs e) => this._ready.InvokeAsync(client, e); /// /// Handles the resumed event. /// /// The client. /// The event args. private Task Client_Resumed(DiscordClient client, ReadyEventArgs e) => this._resumed.InvokeAsync(client, e); /// /// Handles the channel created event. /// /// The client. /// The event args. private Task Client_ChannelCreated(DiscordClient client, ChannelCreateEventArgs e) => this._channelCreated.InvokeAsync(client, e); /// /// Handles the channel updated event. /// /// The client. /// The event args. private Task Client_ChannelUpdated(DiscordClient client, ChannelUpdateEventArgs e) => this._channelUpdated.InvokeAsync(client, e); /// /// Handles the channel deleted. /// /// The client. /// The event args. private Task Client_ChannelDeleted(DiscordClient client, ChannelDeleteEventArgs e) => this._channelDeleted.InvokeAsync(client, e); /// /// Handles the dm channel deleted event. /// /// The client. /// The event args. private Task Client_DMChannelDeleted(DiscordClient client, DmChannelDeleteEventArgs e) => this._dmChannelDeleted.InvokeAsync(client, e); /// /// Handles the channel pins updated event. /// /// The client. /// The event args. private Task Client_ChannelPinsUpdated(DiscordClient client, ChannelPinsUpdateEventArgs e) => this._channelPinsUpdated.InvokeAsync(client, e); /// /// Handles the guild created event. /// /// The client. /// The event args. private Task Client_GuildCreated(DiscordClient client, GuildCreateEventArgs e) => this._guildCreated.InvokeAsync(client, e); /// /// Handles the guild available event. /// /// The client. /// The event args. private Task Client_GuildAvailable(DiscordClient client, GuildCreateEventArgs e) => this._guildAvailable.InvokeAsync(client, e); /// /// Handles the guild updated event. /// /// The client. /// The event args. private Task Client_GuildUpdated(DiscordClient client, GuildUpdateEventArgs e) => this._guildUpdated.InvokeAsync(client, e); /// /// Handles the guild deleted event. /// /// The client. /// The event args. private Task Client_GuildDeleted(DiscordClient client, GuildDeleteEventArgs e) => this._guildDeleted.InvokeAsync(client, e); /// /// Handles the guild unavailable event. /// /// The client. /// The event args. private Task Client_GuildUnavailable(DiscordClient client, GuildDeleteEventArgs e) => this._guildUnavailable.InvokeAsync(client, e); /// /// Handles the guild download completed event. /// /// The client. /// The event args. private Task Client_GuildDownloadCompleted(DiscordClient client, GuildDownloadCompletedEventArgs e) => this._guildDownloadCompleted.InvokeAsync(client, e); /// /// Handles the message created event. /// /// The client. /// The event args. private Task Client_MessageCreated(DiscordClient client, MessageCreateEventArgs e) => this._messageCreated.InvokeAsync(client, e); /// /// Handles the invite created event. /// /// The client. /// The event args. private Task Client_InviteCreated(DiscordClient client, InviteCreateEventArgs e) => this._inviteCreated.InvokeAsync(client, e); /// /// Handles the invite deleted event. /// /// The client. /// The event args. private Task Client_InviteDeleted(DiscordClient client, InviteDeleteEventArgs e) => this._inviteDeleted.InvokeAsync(client, e); /// /// Handles the presence update event. /// /// The client. /// The event args. private Task Client_PresenceUpdate(DiscordClient client, PresenceUpdateEventArgs e) => this._presenceUpdated.InvokeAsync(client, e); /// /// Handles the guild ban add event. /// /// The client. /// The event args. private Task Client_GuildBanAdd(DiscordClient client, GuildBanAddEventArgs e) => this._guildBanAdded.InvokeAsync(client, e); /// /// Handles the guild ban remove event. /// /// The client. /// The event args. private Task Client_GuildBanRemove(DiscordClient client, GuildBanRemoveEventArgs e) => this._guildBanRemoved.InvokeAsync(client, e); /// /// Handles the guild emojis update event. /// /// The client. /// The event args. private Task Client_GuildEmojisUpdate(DiscordClient client, GuildEmojisUpdateEventArgs e) => this._guildEmojisUpdated.InvokeAsync(client, e); /// /// Handles the guild stickers update event. /// /// The client. /// The event args. private Task Client_GuildStickersUpdate(DiscordClient client, GuildStickersUpdateEventArgs e) => this._guildStickersUpdated.InvokeAsync(client, e); /// /// Handles the guild integrations update event. /// /// The client. /// The event args. private Task Client_GuildIntegrationsUpdate(DiscordClient client, GuildIntegrationsUpdateEventArgs e) => this._guildIntegrationsUpdated.InvokeAsync(client, e); /// /// Handles the guild member add event. /// /// The client. /// The event args. private Task Client_GuildMemberAdd(DiscordClient client, GuildMemberAddEventArgs e) => this._guildMemberAdded.InvokeAsync(client, e); /// /// Handles the guild member remove event. /// /// The client. /// The event args. private Task Client_GuildMemberRemove(DiscordClient client, GuildMemberRemoveEventArgs e) => this._guildMemberRemoved.InvokeAsync(client, e); /// /// Handles the guild member update event. /// /// The client. /// The event args. private Task Client_GuildMemberUpdate(DiscordClient client, GuildMemberUpdateEventArgs e) => this._guildMemberUpdated.InvokeAsync(client, e); /// /// Handles the guild role creat evente. /// /// The client. /// The event args. private Task Client_GuildRoleCreate(DiscordClient client, GuildRoleCreateEventArgs e) => this._guildRoleCreated.InvokeAsync(client, e); /// /// Handles the guild role update event. /// /// The client. /// The event args. private Task Client_GuildRoleUpdate(DiscordClient client, GuildRoleUpdateEventArgs e) => this._guildRoleUpdated.InvokeAsync(client, e); /// /// Handles the guild role delete event. /// /// The client. /// The event args. private Task Client_GuildRoleDelete(DiscordClient client, GuildRoleDeleteEventArgs e) => this._guildRoleDeleted.InvokeAsync(client, e); /// /// Handles the message update event. /// /// The client. /// The event args. private Task Client_MessageUpdate(DiscordClient client, MessageUpdateEventArgs e) => this._messageUpdated.InvokeAsync(client, e); /// /// Handles the message delete event. /// /// The client. /// The event args. private Task Client_MessageDelete(DiscordClient client, MessageDeleteEventArgs e) => this._messageDeleted.InvokeAsync(client, e); /// /// Handles the message bulk delete event. /// /// The client. /// The event args. private Task Client_MessageBulkDelete(DiscordClient client, MessageBulkDeleteEventArgs e) => this._messageBulkDeleted.InvokeAsync(client, e); /// /// Handles the typing start event. /// /// The client. /// The event args. private Task Client_TypingStart(DiscordClient client, TypingStartEventArgs e) => this._typingStarted.InvokeAsync(client, e); /// /// Handles the user settings update event. /// /// The client. /// The event args. private Task Client_UserSettingsUpdate(DiscordClient client, UserSettingsUpdateEventArgs e) => this._userSettingsUpdated.InvokeAsync(client, e); /// /// Handles the user update event. /// /// The client. /// The event args. private Task Client_UserUpdate(DiscordClient client, UserUpdateEventArgs e) => this._userUpdated.InvokeAsync(client, e); /// /// Handles the voice state update event. /// /// The client. /// The event args. private Task Client_VoiceStateUpdate(DiscordClient client, VoiceStateUpdateEventArgs e) => this._voiceStateUpdated.InvokeAsync(client, e); /// /// Handles the voice server update event. /// /// The client. /// The event args. private Task Client_VoiceServerUpdate(DiscordClient client, VoiceServerUpdateEventArgs e) => this._voiceServerUpdated.InvokeAsync(client, e); /// /// Handles the guild members chunk event. /// /// The client. /// The event args. private Task Client_GuildMembersChunk(DiscordClient client, GuildMembersChunkEventArgs e) => this._guildMembersChunk.InvokeAsync(client, e); /// /// Handles the unknown events. /// /// The client. /// The event args. private Task Client_UnknownEvent(DiscordClient client, UnknownEventArgs e) => this._unknownEvent.InvokeAsync(client, e); /// /// Handles the message reaction add event. /// /// The client. /// The event args. private Task Client_MessageReactionAdd(DiscordClient client, MessageReactionAddEventArgs e) => this._messageReactionAdded.InvokeAsync(client, e); /// /// Handles the message reaction remove event. /// /// The client. /// The event args. private Task Client_MessageReactionRemove(DiscordClient client, MessageReactionRemoveEventArgs e) => this._messageReactionRemoved.InvokeAsync(client, e); /// /// Handles the message reaction remove all event. /// /// The client. /// The event args. private Task Client_MessageReactionRemoveAll(DiscordClient client, MessageReactionsClearEventArgs e) => this._messageReactionsCleared.InvokeAsync(client, e); /// /// Handles the message reaction removed emoji event. /// /// The client. /// The event args. private Task Client_MessageReactionRemovedEmoji(DiscordClient client, MessageReactionRemoveEmojiEventArgs e) => this._messageReactionRemovedEmoji.InvokeAsync(client, e); /// /// Handles the interaction create event. /// /// The client. /// The event args. private Task Client_InteractionCreate(DiscordClient client, InteractionCreateEventArgs e) => this._interactionCreated.InvokeAsync(client, e); /// /// Handles the component interaction create event. /// /// The client. /// The event args. private Task Client_ComponentInteractionCreate(DiscordClient client, ComponentInteractionCreateEventArgs e) => this._componentInteractionCreated.InvokeAsync(client, e); /// /// Handles the context menu interaction create event. /// /// The client. /// The event args. private Task Client_ContextMenuInteractionCreate(DiscordClient client, ContextMenuInteractionCreateEventArgs e) => this._contextMenuInteractionCreated.InvokeAsync(client, e); /// /// Handles the webhooks update event. /// /// The client. /// The event args. private Task Client_WebhooksUpdate(DiscordClient client, WebhooksUpdateEventArgs e) => this._webhooksUpdated.InvokeAsync(client, e); /// /// Handles the heart beated event. /// /// The client. /// The event args. private Task Client_HeartBeated(DiscordClient client, HeartbeatEventArgs e) => this._heartbeated.InvokeAsync(client, e); /// /// Handles the application command created event. /// /// The client. /// The event args. private Task Client_ApplicationCommandCreated(DiscordClient client, ApplicationCommandEventArgs e) => this._applicationCommandCreated.InvokeAsync(client, e); /// /// Handles the application command updated event. /// /// The client. /// The event args. private Task Client_ApplicationCommandUpdated(DiscordClient client, ApplicationCommandEventArgs e) => this._applicationCommandUpdated.InvokeAsync(client, e); /// /// Handles the application command deleted event. /// /// The client. /// The event args. private Task Client_ApplicationCommandDeleted(DiscordClient client, ApplicationCommandEventArgs e) => this._applicationCommandDeleted.InvokeAsync(client, e); /// /// Handles the guild application command count updated event. /// /// The client. /// The event args. private Task Client_GuildApplicationCommandCountUpdated(DiscordClient client, GuildApplicationCommandCountEventArgs e) => this._guildApplicationCommandCountUpdated.InvokeAsync(client, e); /// /// Handles the application command permissions updated event. /// /// The client. /// The event args. private Task Client_ApplicationCommandPermissionsUpdated(DiscordClient client, ApplicationCommandPermissionsUpdateEventArgs e) => this._applicationCommandPermissionsUpdated.InvokeAsync(client, e); /// /// Handles the guild integration created event. /// /// The client. /// The event args. private Task Client_GuildIntegrationCreated(DiscordClient client, GuildIntegrationCreateEventArgs e) => this._guildIntegrationCreated.InvokeAsync(client, e); /// /// Handles the guild integration updated event. /// /// The client. /// The event args. private Task Client_GuildIntegrationUpdated(DiscordClient client, GuildIntegrationUpdateEventArgs e) => this._guildIntegrationUpdated.InvokeAsync(client, e); /// /// Handles the guild integration deleted event. /// /// The client. /// The event args. private Task Client_GuildIntegrationDeleted(DiscordClient client, GuildIntegrationDeleteEventArgs e) => this._guildIntegrationDeleted.InvokeAsync(client, e); /// /// Handles the stage instance created event. /// /// The client. /// The event args. private Task Client_StageInstanceCreated(DiscordClient client, StageInstanceCreateEventArgs e) => this._stageInstanceCreated.InvokeAsync(client, e); /// /// Handles the stage instance updated event. /// /// The client. /// The event args. private Task Client_StageInstanceUpdated(DiscordClient client, StageInstanceUpdateEventArgs e) => this._stageInstanceUpdated.InvokeAsync(client, e); /// /// Handles the stage instance deleted event. /// /// The client. /// The event args. private Task Client_StageInstanceDeleted(DiscordClient client, StageInstanceDeleteEventArgs e) => this._stageInstanceDeleted.InvokeAsync(client, e); /// /// Handles the thread created event. /// /// The client. /// The event args. private Task Client_ThreadCreated(DiscordClient client, ThreadCreateEventArgs e) => this._threadCreated.InvokeAsync(client, e); /// /// Handles the thread updated event. /// /// The client. /// The event args. private Task Client_ThreadUpdated(DiscordClient client, ThreadUpdateEventArgs e) => this._threadUpdated.InvokeAsync(client, e); /// /// Handles the thread deleted event. /// /// The client. /// The event args. private Task Client_ThreadDeleted(DiscordClient client, ThreadDeleteEventArgs e) => this._threadDeleted.InvokeAsync(client, e); /// /// Handles the thread list synced event. /// /// The client. /// The event args. private Task Client_ThreadListSynced(DiscordClient client, ThreadListSyncEventArgs e) => this._threadListSynced.InvokeAsync(client, e); /// /// Handles the thread member updated event. /// /// The client. /// The event args. private Task Client_ThreadMemberUpdated(DiscordClient client, ThreadMemberUpdateEventArgs e) => this._threadMemberUpdated.InvokeAsync(client, e); /// /// Handles the thread members updated event. /// /// The client. /// The event args. private Task Client_ThreadMembersUpdated(DiscordClient client, ThreadMembersUpdateEventArgs e) => this._threadMembersUpdated.InvokeAsync(client, e); /// /// Handles the scheduled event created event. /// /// The client. /// The event args. private Task Client_GuildScheduledEventCreated(DiscordClient client, GuildScheduledEventCreateEventArgs e) => this._guildScheduledEventCreated.InvokeAsync(client, e); /// /// Handles the scheduled event updated event. /// /// The client. /// The event args. private Task Client_GuildScheduledEventUpdated(DiscordClient client, GuildScheduledEventUpdateEventArgs e) => this._guildScheduledEventUpdated.InvokeAsync(client, e); /// /// Handles the scheduled event deleted event. /// /// The client. /// The event args. private Task Client_GuildScheduledEventDeleted(DiscordClient client, GuildScheduledEventDeleteEventArgs e) => this._guildScheduledEventDeleted.InvokeAsync(client, e); /// /// Handles the scheduled event user added event. /// /// The client. /// The event args. private Task Client_GuildScheduledEventUserAdded(DiscordClient client, GuildScheduledEventUserAddEventArgs e) => this._guildScheduledEventUserAdded.InvokeAsync(client, e); /// /// Handles the scheduled event user removed event. /// /// The client. /// The event args. private Task Client_GuildScheduledEventUserRemoved(DiscordClient client, GuildScheduledEventUserRemoveEventArgs e) => this._guildScheduledEventUserRemoved.InvokeAsync(client, e); #endregion } } diff --git a/DisCatSharp/Clients/DiscordShardedClient.cs b/DisCatSharp/Clients/DiscordShardedClient.cs index a6c48b6a3..b5d20dc84 100644 --- a/DisCatSharp/Clients/DiscordShardedClient.cs +++ b/DisCatSharp/Clients/DiscordShardedClient.cs @@ -1,766 +1,775 @@ // 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. #pragma warning disable CS0618 using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; using System.Globalization; using System.Linq; using System.Net.Http; using System.Reflection; using System.Threading.Tasks; using DisCatSharp.Common.Utilities; using DisCatSharp.Entities; using DisCatSharp.EventArgs; using DisCatSharp.Net; using Microsoft.Extensions.Logging; using Newtonsoft.Json.Linq; namespace DisCatSharp { /// /// A Discord client that shards automatically. /// public sealed partial class DiscordShardedClient { #region Public Properties /// /// Gets the logger for this client. /// public ILogger Logger { get; } /// /// Gets all client shards. /// public IReadOnlyDictionary ShardClients { get; } /// /// Gets the gateway info for the client's session. /// public GatewayInfo GatewayInfo { get; private set; } /// /// Gets the current user. /// public DiscordUser CurrentUser { get; private set; } /// /// Gets the current application. /// public DiscordApplication CurrentApplication { get; private set; } /// /// Gets the library team. /// public DisCatSharpTeam LibraryDeveloperTeam => this.GetShard(0).LibraryDeveloperTeam; /// /// Gets the list of available voice regions. Note that this property will not contain VIP voice regions. /// public IReadOnlyDictionary VoiceRegions => this._voiceRegionsLazy?.Value; #endregion #region Private Properties/Fields /// /// Gets the configuration. /// private readonly DiscordConfiguration _configuration; /// /// Gets the list of available voice regions. This property is meant as a way to modify . /// private ConcurrentDictionary _internalVoiceRegions; /// /// Gets a list of shards. /// private readonly ConcurrentDictionary _shards = new(); /// /// Gets a lazy list of voice regions. /// private Lazy> _voiceRegionsLazy; /// /// Whether the shard client is started. /// private bool _isStarted; /// /// Whether manual sharding is enabled. /// private readonly bool _manuallySharding; #endregion #region Constructor /// /// Initializes a new auto-sharding Discord client. /// /// The configuration to use. public DiscordShardedClient(DiscordConfiguration config) { this.InternalSetup(); if (config.ShardCount > 1) this._manuallySharding = true; this._configuration = config; this.ShardClients = new ReadOnlyConcurrentDictionary(this._shards); if (this._configuration.LoggerFactory == null) { this._configuration.LoggerFactory = new DefaultLoggerFactory(); this._configuration.LoggerFactory.AddProvider(new DefaultLoggerProvider(this._configuration.MinimumLogLevel, this._configuration.LogTimestampFormat)); } this.Logger = this._configuration.LoggerFactory.CreateLogger(); } #endregion #region Public Methods /// /// Initializes and connects all shards. /// /// /// public async Task StartAsync() { if (this._isStarted) throw new InvalidOperationException("This client has already been started."); this._isStarted = true; try { if (this._configuration.TokenType != TokenType.Bot) this.Logger.LogWarning(LoggerEvents.Misc, "You are logging in with a token that is not a bot token. This is not officially supported by Discord, and can result in your account being terminated if you aren't careful."); this.Logger.LogInformation(LoggerEvents.Startup, "Lib {0}, version {1}", this._botLibrary, this._versionString.Value); var shardc = await this.InitializeShardsAsync().ConfigureAwait(false); var connectTasks = new List(); this.Logger.LogInformation(LoggerEvents.ShardStartup, "Booting {0} shards.", shardc); for (var i = 0; i < shardc; i++) { //This should never happen, but in case it does... if (this.GatewayInfo.SessionBucket.MaxConcurrency < 1) this.GatewayInfo.SessionBucket.MaxConcurrency = 1; if (this.GatewayInfo.SessionBucket.MaxConcurrency == 1) await this.ConnectShardAsync(i).ConfigureAwait(false); else { //Concurrent login. connectTasks.Add(this.ConnectShardAsync(i)); if (connectTasks.Count == this.GatewayInfo.SessionBucket.MaxConcurrency) { await Task.WhenAll(connectTasks).ConfigureAwait(false); connectTasks.Clear(); } } } } catch (Exception ex) { await this.InternalStopAsync(false).ConfigureAwait(false); var message = $"Shard initialization failed, check inner exceptions for details: "; this.Logger.LogCritical(LoggerEvents.ShardClientError, $"{message}\n{ex}"); throw new AggregateException(message, ex); } } /// /// Disconnects and disposes all shards. /// /// public Task StopAsync() => this.InternalStopAsync(); /// /// Gets a shard from a guild id. /// /// If automatically sharding, this will use the method. /// Otherwise if manually sharding, it will instead iterate through each shard's guild caches. /// /// /// The guild ID for the shard. /// The found shard. Otherwise null if the shard was not found for the guild id. public DiscordClient GetShard(ulong guildId) { var index = this._manuallySharding ? this.GetShardIdFromGuilds(guildId) : Utilities.GetShardId(guildId, this.ShardClients.Count); return index != -1 ? this._shards[index] : null; } /// /// Gets a shard from a guild. /// /// If automatically sharding, this will use the method. /// Otherwise if manually sharding, it will instead iterate through each shard's guild caches. /// /// /// The guild for the shard. /// The found shard. Otherwise null if the shard was not found for the guild. public DiscordClient GetShard(DiscordGuild guild) => this.GetShard(guild.Id); /// /// Updates the status on all shards. /// /// The activity to set. Defaults to null. /// The optional status to set. Defaults to null. /// Since when is the client performing the specified activity. Defaults to null. /// Asynchronous operation. public async Task UpdateStatusAsync(DiscordActivity activity = null, UserStatus? userStatus = null, DateTimeOffset? idleSince = null) { var tasks = new List(); foreach (var client in this._shards.Values) tasks.Add(client.UpdateStatusAsync(activity, userStatus, idleSince)); await Task.WhenAll(tasks).ConfigureAwait(false); } #endregion #region Internal Methods /// /// Initializes the shards. /// /// The count of initialized shards. internal async Task InitializeShardsAsync() { if (this._shards.Count != 0) return this._shards.Count; this.GatewayInfo = await this.GetGatewayInfoAsync().ConfigureAwait(false); var shardCount = this._configuration.ShardCount == 1 ? this.GatewayInfo.ShardCount : this._configuration.ShardCount; var lf = new ShardedLoggerFactory(this.Logger); for (var i = 0; i < shardCount; i++) { var cfg = new DiscordConfiguration(this._configuration) { ShardId = i, ShardCount = shardCount, LoggerFactory = lf }; var client = new DiscordClient(cfg); if (!this._shards.TryAdd(i, client)) throw new InvalidOperationException("Could not initialize shards."); } return shardCount; } #endregion #region Private Methods & Version Property /// /// Gets the gateway info. /// private async Task GetGatewayInfoAsync() { var url = $"{Utilities.GetApiBaseUri(this._configuration)}{Endpoints.GATEWAY}{Endpoints.BOT}"; var http = new HttpClient(); http.DefaultRequestHeaders.TryAddWithoutValidation("User-Agent", Utilities.GetUserAgent()); http.DefaultRequestHeaders.TryAddWithoutValidation("Authorization", Utilities.GetFormattedToken(this._configuration)); if (this._configuration != null && this._configuration.Override != null) { http.DefaultRequestHeaders.TryAddWithoutValidation("x-super-properties", this._configuration.Override); } this.Logger.LogDebug(LoggerEvents.ShardRest, $"Obtaining gateway information from GET {Endpoints.GATEWAY}{Endpoints.BOT}..."); var resp = await http.GetAsync(url).ConfigureAwait(false); http.Dispose(); if (!resp.IsSuccessStatusCode) { var ratelimited = await HandleHttpError(url, resp).ConfigureAwait(false); if (ratelimited) return await this.GetGatewayInfoAsync().ConfigureAwait(false); } var timer = new Stopwatch(); timer.Start(); var jo = JObject.Parse(await resp.Content.ReadAsStringAsync().ConfigureAwait(false)); var info = jo.ToObject(); //There is a delay from parsing here. timer.Stop(); info.SessionBucket.ResetAfterInternal -= (int)timer.ElapsedMilliseconds; info.SessionBucket.ResetAfter = DateTimeOffset.UtcNow + TimeSpan.FromMilliseconds(info.SessionBucket.ResetAfterInternal); return info; async Task HandleHttpError(string reqUrl, HttpResponseMessage msg) { var code = (int)msg.StatusCode; if (code == 401 || code == 403) { throw new Exception($"Authentication failed, check your token and try again: {code} {msg.ReasonPhrase}"); } else if (code == 429) { this.Logger.LogError(LoggerEvents.ShardClientError, $"Ratelimit hit, requeuing request to {reqUrl}"); var hs = msg.Headers.ToDictionary(xh => xh.Key, xh => string.Join("\n", xh.Value), StringComparer.OrdinalIgnoreCase); var waitInterval = 0; if (hs.TryGetValue("Retry-After", out var retryAfterRaw)) waitInterval = int.Parse(retryAfterRaw, CultureInfo.InvariantCulture); await Task.Delay(waitInterval).ConfigureAwait(false); return true; } else if (code >= 500) { throw new Exception($"Internal Server Error: {code} {msg.ReasonPhrase}"); } else { throw new Exception($"An unsuccessful HTTP status code was encountered: {code} {msg.ReasonPhrase}"); } } } /// /// Gets the version string. /// private readonly Lazy _versionString = new(() => { var a = typeof(DiscordShardedClient).GetTypeInfo().Assembly; var iv = a.GetCustomAttribute(); if (iv != null) return iv.InformationalVersion; var v = a.GetName().Version; var vs = v.ToString(3); if (v.Revision > 0) vs = $"{vs}, CI build {v.Revision}"; return vs; }); /// /// Gets the name of the used bot library. /// private readonly string _botLibrary = "DisCatSharp"; #endregion #region Private Connection Methods /// /// Connects a shard. /// /// The shard id. private async Task ConnectShardAsync(int i) { if (!this._shards.TryGetValue(i, out var client)) throw new Exception($"Could not initialize shard {i}."); if (this.GatewayInfo != null) { client.GatewayInfo = this.GatewayInfo; client.GatewayUri = new Uri(client.GatewayInfo.Url); } if (this.CurrentUser != null) client.CurrentUser = this.CurrentUser; if (this.CurrentApplication != null) client.CurrentApplication = this.CurrentApplication; if (this._internalVoiceRegions != null) { client.InternalVoiceRegions = this._internalVoiceRegions; client.VoiceRegionsLazy = new Lazy>(() => new ReadOnlyDictionary(client.InternalVoiceRegions)); } this.HookEventHandlers(client); client.IsShard = true; await client.ConnectAsync().ConfigureAwait(false); this.Logger.LogInformation(LoggerEvents.ShardStartup, "Booted shard {0}.", i); if (this.CurrentUser == null) this.CurrentUser = client.CurrentUser; if (this.CurrentApplication == null) this.CurrentApplication = client.CurrentApplication; if (this._internalVoiceRegions == null) { this._internalVoiceRegions = client.InternalVoiceRegions; this._voiceRegionsLazy = new Lazy>(() => new ReadOnlyDictionary(this._internalVoiceRegions)); } } /// /// Stops all shards. /// /// Whether to enable the logger. private Task InternalStopAsync(bool enableLogger = true) { if (!this._isStarted) throw new InvalidOperationException("This client has not been started."); if (enableLogger) this.Logger.LogInformation(LoggerEvents.ShardShutdown, "Disposing {0} shards.", this._shards.Count); this._isStarted = false; this._voiceRegionsLazy = null; this.GatewayInfo = null; this.CurrentUser = null; this.CurrentApplication = null; for (var i = 0; i < this._shards.Count; i++) { if (this._shards.TryGetValue(i, out var client)) { this.UnhookEventHandlers(client); client.Dispose(); if (enableLogger) this.Logger.LogInformation(LoggerEvents.ShardShutdown, "Disconnected shard {0}.", i); } } this._shards.Clear(); return Task.CompletedTask; } #endregion #region Event Handler Initialization/Registering /// /// Sets the shard client up internally.. /// private void InternalSetup() { this._clientErrored = new AsyncEvent("CLIENT_ERRORED", DiscordClient.EventExecutionLimit, this.Goof); this._socketErrored = new AsyncEvent("SOCKET_ERRORED", DiscordClient.EventExecutionLimit, this.Goof); this._socketOpened = new AsyncEvent("SOCKET_OPENED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._socketClosed = new AsyncEvent("SOCKET_CLOSED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._ready = new AsyncEvent("READY", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._resumed = new AsyncEvent("RESUMED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._channelCreated = new AsyncEvent("CHANNEL_CREATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._channelUpdated = new AsyncEvent("CHANNEL_UPDATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._channelDeleted = new AsyncEvent("CHANNEL_DELETED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._dmChannelDeleted = new AsyncEvent("DM_CHANNEL_DELETED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._channelPinsUpdated = new AsyncEvent("CHANNEL_PINS_UPDATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._guildCreated = new AsyncEvent("GUILD_CREATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._guildAvailable = new AsyncEvent("GUILD_AVAILABLE", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._guildUpdated = new AsyncEvent("GUILD_UPDATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._guildDeleted = new AsyncEvent("GUILD_DELETED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._guildUnavailable = new AsyncEvent("GUILD_UNAVAILABLE", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._guildDownloadCompleted = new AsyncEvent("GUILD_DOWNLOAD_COMPLETED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._inviteCreated = new AsyncEvent("INVITE_CREATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._inviteDeleted = new AsyncEvent("INVITE_DELETED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._messageCreated = new AsyncEvent("MESSAGE_CREATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._presenceUpdated = new AsyncEvent("PRESENCE_UPDATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._guildBanAdded = new AsyncEvent("GUILD_BAN_ADDED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._guildBanRemoved = new AsyncEvent("GUILD_BAN_REMOVED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._guildEmojisUpdated = new AsyncEvent("GUILD_EMOJI_UPDATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._guildStickersUpdated = new AsyncEvent("GUILD_STICKER_UPDATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._guildIntegrationsUpdated = new AsyncEvent("GUILD_INTEGRATIONS_UPDATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._guildMemberAdded = new AsyncEvent("GUILD_MEMBER_ADDED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._guildMemberRemoved = new AsyncEvent("GUILD_MEMBER_REMOVED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._guildMemberUpdated = new AsyncEvent("GUILD_MEMBER_UPDATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._guildRoleCreated = new AsyncEvent("GUILD_ROLE_CREATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._guildRoleUpdated = new AsyncEvent("GUILD_ROLE_UPDATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._guildRoleDeleted = new AsyncEvent("GUILD_ROLE_DELETED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._messageUpdated = new AsyncEvent("MESSAGE_UPDATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._messageDeleted = new AsyncEvent("MESSAGE_DELETED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._messageBulkDeleted = new AsyncEvent("MESSAGE_BULK_DELETED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._interactionCreated = new AsyncEvent("INTERACTION_CREATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._componentInteractionCreated = new AsyncEvent("COMPONENT_INTERACTED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._contextMenuInteractionCreated = new AsyncEvent("CONTEXT_MENU_INTERACTED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._typingStarted = new AsyncEvent("TYPING_STARTED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._userSettingsUpdated = new AsyncEvent("USER_SETTINGS_UPDATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._userUpdated = new AsyncEvent("USER_UPDATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._voiceStateUpdated = new AsyncEvent("VOICE_STATE_UPDATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._voiceServerUpdated = new AsyncEvent("VOICE_SERVER_UPDATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._guildMembersChunk = new AsyncEvent("GUILD_MEMBERS_CHUNKED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._unknownEvent = new AsyncEvent("UNKNOWN_EVENT", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._messageReactionAdded = new AsyncEvent("MESSAGE_REACTION_ADDED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._messageReactionRemoved = new AsyncEvent("MESSAGE_REACTION_REMOVED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._messageReactionsCleared = new AsyncEvent("MESSAGE_REACTIONS_CLEARED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._messageReactionRemovedEmoji = new AsyncEvent("MESSAGE_REACTION_REMOVED_EMOJI", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._webhooksUpdated = new AsyncEvent("WEBHOOKS_UPDATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._heartbeated = new AsyncEvent("HEARTBEATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._applicationCommandCreated = new AsyncEvent("APPLICATION_COMMAND_CREATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._applicationCommandUpdated = new AsyncEvent("APPLICATION_COMMAND_UPDATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._applicationCommandDeleted = new AsyncEvent("APPLICATION_COMMAND_DELETED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._guildApplicationCommandCountUpdated = new AsyncEvent("GUILD_APPLICATION_COMMAND_COUNTS_UPDATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._applicationCommandPermissionsUpdated = new AsyncEvent("APPLICATION_COMMAND_PERMISSIONS_UPDATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._guildIntegrationCreated = new AsyncEvent("INTEGRATION_CREATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._guildIntegrationUpdated = new AsyncEvent("INTEGRATION_UPDATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._guildIntegrationDeleted = new AsyncEvent("INTEGRATION_DELETED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._stageInstanceCreated = new AsyncEvent("STAGE_INSTANCE_CREATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._stageInstanceUpdated = new AsyncEvent("STAGE_INSTANCE_UPDATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._stageInstanceDeleted = new AsyncEvent("STAGE_INSTANCE_DELETED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._threadCreated = new AsyncEvent("THREAD_CREATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._threadUpdated = new AsyncEvent("THREAD_UPDATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._threadDeleted = new AsyncEvent("THREAD_DELETED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._threadListSynced = new AsyncEvent("THREAD_LIST_SYNCED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._threadMemberUpdated = new AsyncEvent("THREAD_MEMBER_UPDATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._threadMembersUpdated = new AsyncEvent("THREAD_MEMBERS_UPDATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._zombied = new AsyncEvent("ZOMBIED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._payloadReceived = new AsyncEvent("PAYLOAD_RECEIVED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._guildScheduledEventCreated = new AsyncEvent("GUILD_SCHEDULED_EVENT_CREATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._guildScheduledEventUpdated = new AsyncEvent("GUILD_SCHEDULED_EVENT_UPDATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._guildScheduledEventDeleted = new AsyncEvent("GUILD_SCHEDULED_EVENT_DELETED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._guildScheduledEventUserAdded = new AsyncEvent("GUILD_SCHEDULED_EVENT_USER_ADDED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._guildScheduledEventUserRemoved = new AsyncEvent("GUILD_SCHEDULED_EVENT_USER_REMOVED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._embeddedActivityUpdated = new AsyncEvent("EMBEDDED_ACTIVITY_UPDATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); + this._guildMemberTimeoutAdded = new AsyncEvent("GUILD_MEMBER_TIMEOUT_ADDED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); + this._guildMemberTimeoutChanged = new AsyncEvent("GUILD_MEMBER_TIMEOUT_UPDATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); + this._guildMemberTimeoutRemoved = new AsyncEvent("GUILD_MEMBER_TIMEOUT_REMOVED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); } /// /// Hooks the event handlers. /// /// The client. private void HookEventHandlers(DiscordClient client) { client.ClientErrored += this.Client_ClientError; client.SocketErrored += this.Client_SocketError; client.SocketOpened += this.Client_SocketOpened; client.SocketClosed += this.Client_SocketClosed; client.Ready += this.Client_Ready; client.Resumed += this.Client_Resumed; client.ChannelCreated += this.Client_ChannelCreated; client.ChannelUpdated += this.Client_ChannelUpdated; client.ChannelDeleted += this.Client_ChannelDeleted; client.DmChannelDeleted += this.Client_DMChannelDeleted; client.ChannelPinsUpdated += this.Client_ChannelPinsUpdated; client.GuildCreated += this.Client_GuildCreated; client.GuildAvailable += this.Client_GuildAvailable; client.GuildUpdated += this.Client_GuildUpdated; client.GuildDeleted += this.Client_GuildDeleted; client.GuildUnavailable += this.Client_GuildUnavailable; client.GuildDownloadCompleted += this.Client_GuildDownloadCompleted; client.InviteCreated += this.Client_InviteCreated; client.InviteDeleted += this.Client_InviteDeleted; client.MessageCreated += this.Client_MessageCreated; client.PresenceUpdated += this.Client_PresenceUpdate; client.GuildBanAdded += this.Client_GuildBanAdd; client.GuildBanRemoved += this.Client_GuildBanRemove; client.GuildEmojisUpdated += this.Client_GuildEmojisUpdate; client.GuildStickersUpdated += this.Client_GuildStickersUpdate; client.GuildIntegrationsUpdated += this.Client_GuildIntegrationsUpdate; client.GuildMemberAdded += this.Client_GuildMemberAdd; client.GuildMemberRemoved += this.Client_GuildMemberRemove; client.GuildMemberUpdated += this.Client_GuildMemberUpdate; client.GuildRoleCreated += this.Client_GuildRoleCreate; client.GuildRoleUpdated += this.Client_GuildRoleUpdate; client.GuildRoleDeleted += this.Client_GuildRoleDelete; client.MessageUpdated += this.Client_MessageUpdate; client.MessageDeleted += this.Client_MessageDelete; client.MessagesBulkDeleted += this.Client_MessageBulkDelete; client.InteractionCreated += this.Client_InteractionCreate; client.ComponentInteractionCreated += this.Client_ComponentInteractionCreate; client.ContextMenuInteractionCreated += this.Client_ContextMenuInteractionCreate; client.TypingStarted += this.Client_TypingStart; client.UserSettingsUpdated += this.Client_UserSettingsUpdate; client.UserUpdated += this.Client_UserUpdate; client.VoiceStateUpdated += this.Client_VoiceStateUpdate; client.VoiceServerUpdated += this.Client_VoiceServerUpdate; client.GuildMembersChunked += this.Client_GuildMembersChunk; client.UnknownEvent += this.Client_UnknownEvent; client.MessageReactionAdded += this.Client_MessageReactionAdd; client.MessageReactionRemoved += this.Client_MessageReactionRemove; client.MessageReactionsCleared += this.Client_MessageReactionRemoveAll; client.MessageReactionRemovedEmoji += this.Client_MessageReactionRemovedEmoji; client.WebhooksUpdated += this.Client_WebhooksUpdate; client.Heartbeated += this.Client_HeartBeated; client.ApplicationCommandCreated += this.Client_ApplicationCommandCreated; client.ApplicationCommandUpdated += this.Client_ApplicationCommandUpdated; client.ApplicationCommandDeleted += this.Client_ApplicationCommandDeleted; client.GuildApplicationCommandCountUpdated += this.Client_GuildApplicationCommandCountUpdated; client.ApplicationCommandPermissionsUpdated += this.Client_ApplicationCommandPermissionsUpdated; client.GuildIntegrationCreated += this.Client_GuildIntegrationCreated; client.GuildIntegrationUpdated += this.Client_GuildIntegrationUpdated; client.GuildIntegrationDeleted += this.Client_GuildIntegrationDeleted; client.StageInstanceCreated += this.Client_StageInstanceCreated; client.StageInstanceUpdated += this.Client_StageInstanceUpdated; client.StageInstanceDeleted += this.Client_StageInstanceDeleted; client.ThreadCreated += this.Client_ThreadCreated; client.ThreadUpdated += this.Client_ThreadUpdated; client.ThreadDeleted += this.Client_ThreadDeleted; client.ThreadListSynced += this.Client_ThreadListSynced; client.ThreadMemberUpdated += this.Client_ThreadMemberUpdated; client.ThreadMembersUpdated += this.Client_ThreadMembersUpdated; client.Zombied += this.Client_Zombied; client.PayloadReceived += this.Client_PayloadReceived; client.GuildScheduledEventCreated += this.Client_GuildScheduledEventCreated; client.GuildScheduledEventUpdated += this.Client_GuildScheduledEventUpdated; client.GuildScheduledEventDeleted += this.Client_GuildScheduledEventDeleted; client.GuildScheduledEventUserAdded += this.Client_GuildScheduledEventUserAdded; ; client.GuildScheduledEventUserRemoved += this.Client_GuildScheduledEventUserRemoved; client.EmbeddedActivityUpdated += this.Client_EmbeddedActivityUpdated; + client.GuildMemberTimeoutAdded += this.Client_GuildMemberTimeoutAdded; + client.GuildMemberTimeoutChanged += this.Client_GuildMemberTimeoutChanged; + client.GuildMemberTimeoutRemoved += this.Client_GuildMemberTimeoutRemoved; } /// /// Unhooks the event handlers. /// /// The client. private void UnhookEventHandlers(DiscordClient client) { client.ClientErrored -= this.Client_ClientError; client.SocketErrored -= this.Client_SocketError; client.SocketOpened -= this.Client_SocketOpened; client.SocketClosed -= this.Client_SocketClosed; client.Ready -= this.Client_Ready; client.Resumed -= this.Client_Resumed; client.ChannelCreated -= this.Client_ChannelCreated; client.ChannelUpdated -= this.Client_ChannelUpdated; client.ChannelDeleted -= this.Client_ChannelDeleted; client.DmChannelDeleted -= this.Client_DMChannelDeleted; client.ChannelPinsUpdated -= this.Client_ChannelPinsUpdated; client.GuildCreated -= this.Client_GuildCreated; client.GuildAvailable -= this.Client_GuildAvailable; client.GuildUpdated -= this.Client_GuildUpdated; client.GuildDeleted -= this.Client_GuildDeleted; client.GuildUnavailable -= this.Client_GuildUnavailable; client.GuildDownloadCompleted -= this.Client_GuildDownloadCompleted; client.InviteCreated -= this.Client_InviteCreated; client.InviteDeleted -= this.Client_InviteDeleted; client.MessageCreated -= this.Client_MessageCreated; client.PresenceUpdated -= this.Client_PresenceUpdate; client.GuildBanAdded -= this.Client_GuildBanAdd; client.GuildBanRemoved -= this.Client_GuildBanRemove; client.GuildEmojisUpdated -= this.Client_GuildEmojisUpdate; client.GuildStickersUpdated -= this.Client_GuildStickersUpdate; client.GuildIntegrationsUpdated -= this.Client_GuildIntegrationsUpdate; client.GuildMemberAdded -= this.Client_GuildMemberAdd; client.GuildMemberRemoved -= this.Client_GuildMemberRemove; client.GuildMemberUpdated -= this.Client_GuildMemberUpdate; client.GuildRoleCreated -= this.Client_GuildRoleCreate; client.GuildRoleUpdated -= this.Client_GuildRoleUpdate; client.GuildRoleDeleted -= this.Client_GuildRoleDelete; client.MessageUpdated -= this.Client_MessageUpdate; client.MessageDeleted -= this.Client_MessageDelete; client.MessagesBulkDeleted -= this.Client_MessageBulkDelete; client.InteractionCreated -= this.Client_InteractionCreate; client.ComponentInteractionCreated -= this.Client_ComponentInteractionCreate; client.ContextMenuInteractionCreated -= this.Client_ContextMenuInteractionCreate; client.TypingStarted -= this.Client_TypingStart; client.UserSettingsUpdated -= this.Client_UserSettingsUpdate; client.UserUpdated -= this.Client_UserUpdate; client.VoiceStateUpdated -= this.Client_VoiceStateUpdate; client.VoiceServerUpdated -= this.Client_VoiceServerUpdate; client.GuildMembersChunked -= this.Client_GuildMembersChunk; client.UnknownEvent -= this.Client_UnknownEvent; client.MessageReactionAdded -= this.Client_MessageReactionAdd; client.MessageReactionRemoved -= this.Client_MessageReactionRemove; client.MessageReactionsCleared -= this.Client_MessageReactionRemoveAll; client.MessageReactionRemovedEmoji -= this.Client_MessageReactionRemovedEmoji; client.WebhooksUpdated -= this.Client_WebhooksUpdate; client.Heartbeated -= this.Client_HeartBeated; client.ApplicationCommandCreated -= this.Client_ApplicationCommandCreated; client.ApplicationCommandUpdated -= this.Client_ApplicationCommandUpdated; client.ApplicationCommandDeleted -= this.Client_ApplicationCommandDeleted; client.GuildApplicationCommandCountUpdated -= this.Client_GuildApplicationCommandCountUpdated; client.ApplicationCommandPermissionsUpdated -= this.Client_ApplicationCommandPermissionsUpdated; client.GuildIntegrationCreated -= this.Client_GuildIntegrationCreated; client.GuildIntegrationUpdated -= this.Client_GuildIntegrationUpdated; client.GuildIntegrationDeleted -= this.Client_GuildIntegrationDeleted; client.StageInstanceCreated -= this.Client_StageInstanceCreated; client.StageInstanceUpdated -= this.Client_StageInstanceUpdated; client.StageInstanceDeleted -= this.Client_StageInstanceDeleted; client.ThreadCreated -= this.Client_ThreadCreated; client.ThreadUpdated -= this.Client_ThreadUpdated; client.ThreadDeleted -= this.Client_ThreadDeleted; client.ThreadListSynced -= this.Client_ThreadListSynced; client.ThreadMemberUpdated -= this.Client_ThreadMemberUpdated; client.ThreadMembersUpdated -= this.Client_ThreadMembersUpdated; client.Zombied -= this.Client_Zombied; client.PayloadReceived -= this.Client_PayloadReceived; client.GuildScheduledEventCreated -= this.Client_GuildScheduledEventCreated; client.GuildScheduledEventUpdated -= this.Client_GuildScheduledEventUpdated; client.GuildScheduledEventDeleted -= this.Client_GuildScheduledEventDeleted; client.GuildScheduledEventUserAdded -= this.Client_GuildScheduledEventUserAdded; ; client.GuildScheduledEventUserRemoved -= this.Client_GuildScheduledEventUserRemoved; client.EmbeddedActivityUpdated -= this.Client_EmbeddedActivityUpdated; + client.GuildMemberTimeoutAdded -= this.Client_GuildMemberTimeoutAdded; + client.GuildMemberTimeoutChanged -= this.Client_GuildMemberTimeoutChanged; + client.GuildMemberTimeoutRemoved -= this.Client_GuildMemberTimeoutRemoved; } /// /// Gets the shard id from guilds. /// /// The id. /// An int. private int GetShardIdFromGuilds(ulong id) { foreach (var s in this._shards.Values) { if (s.GuildsInternal.TryGetValue(id, out _)) { return s.ShardId; } } return -1; } #endregion #region Destructor ~DiscordShardedClient() { this.InternalStopAsync(false).GetAwaiter().GetResult(); } #endregion } } diff --git a/DisCatSharp/Entities/Guild/DiscordMember.cs b/DisCatSharp/Entities/Guild/DiscordMember.cs index 9f5587b1e..dbdb0de30 100644 --- a/DisCatSharp/Entities/Guild/DiscordMember.cs +++ b/DisCatSharp/Entities/Guild/DiscordMember.cs @@ -1,769 +1,769 @@ // 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 DisCatSharp.Net.Models; using Newtonsoft.Json; namespace DisCatSharp.Entities { /// /// Represents a Discord guild member. /// public class DiscordMember : DiscordUser, IEquatable { /// /// Initializes a new instance of the class. /// internal DiscordMember() { this._roleIdsLazy = new Lazy>(() => new ReadOnlyCollection(this.RoleIdsInternal)); } /// /// Initializes a new instance of the class. /// /// The user. internal DiscordMember(DiscordUser user) { this.Discord = user.Discord; this.Id = user.Id; this.RoleIdsInternal = new List(); this._roleIdsLazy = new Lazy>(() => new ReadOnlyCollection(this.RoleIdsInternal)); } /// /// Initializes a new instance of the class. /// /// The mbr. internal DiscordMember(TransportMember mbr) { this.Id = mbr.User.Id; this.IsDeafened = mbr.IsDeafened; this.IsMuted = mbr.IsMuted; this.JoinedAt = mbr.JoinedAt; this.Nickname = mbr.Nickname; this.PremiumSince = mbr.PremiumSince; this.IsPending = mbr.IsPending; this.GuildAvatarHash = mbr.GuildAvatarHash; this.GuildBannerHash = mbr.GuildBannerHash; this.GuildBio = mbr.GuildBio; this.CommunicationDisabledUntil = mbr.CommunicationDisabledUntil; this.AvatarHashInternal = mbr.AvatarHash; this.RoleIdsInternal = mbr.Roles ?? new List(); this._roleIdsLazy = new Lazy>(() => new ReadOnlyCollection(this.RoleIdsInternal)); } /// /// Gets the members avatar hash. /// [JsonProperty("avatar", NullValueHandling = NullValueHandling.Ignore)] public virtual string GuildAvatarHash { get; internal set; } /// /// Gets the members avatar URL. /// [JsonIgnore] public string GuildAvatarUrl => string.IsNullOrWhiteSpace(this.GuildAvatarHash) ? this.User.AvatarUrl : $"{DiscordDomain.GetDomain(CoreDomain.DiscordCdn).Url}{Endpoints.GUILDS}/{this.GuildId.ToString(CultureInfo.InvariantCulture)}{Endpoints.USERS}/{this.Id.ToString(CultureInfo.InvariantCulture)}{Endpoints.AVATARS}/{this.GuildAvatarHash}.{(this.GuildAvatarHash.StartsWith("a_") ? "gif" : "png")}?size=1024"; /// /// Gets the members banner hash. /// [JsonProperty("banner", NullValueHandling = NullValueHandling.Ignore)] public virtual string GuildBannerHash { get; internal set; } /// /// Gets the members banner URL. /// [JsonIgnore] public string GuildBannerUrl => string.IsNullOrWhiteSpace(this.GuildBannerHash) ? this.User.BannerUrl : $"{DiscordDomain.GetDomain(CoreDomain.DiscordCdn).Url}{Endpoints.GUILDS}/{this.GuildId.ToString(CultureInfo.InvariantCulture)}{Endpoints.USERS}/{this.Id.ToString(CultureInfo.InvariantCulture)}{Endpoints.BANNERS}/{this.GuildBannerHash}.{(this.GuildBannerHash.StartsWith("a_") ? "gif" : "png")}?size=1024"; /// /// The color of this member's banner. Mutually exclusive with . /// [JsonIgnore] public override DiscordColor? BannerColor => this.User.BannerColor; /// /// Gets this member's nickname. /// [JsonProperty("nick", NullValueHandling = NullValueHandling.Ignore)] public string Nickname { get; internal set; } /// /// Gets the members guild bio. /// This is not available to bots tho. /// [JsonProperty("bio", NullValueHandling = NullValueHandling.Ignore)] public string GuildBio { get; internal set; } [JsonIgnore] internal string AvatarHashInternal; /// /// Gets this member's display name. /// [JsonIgnore] public string DisplayName => this.Nickname ?? this.Username; /// /// List of role ids /// [JsonIgnore] internal IReadOnlyList RoleIds => this._roleIdsLazy.Value; [JsonProperty("roles", NullValueHandling = NullValueHandling.Ignore)] internal List RoleIdsInternal; [JsonIgnore] private readonly Lazy> _roleIdsLazy; /// /// Gets the list of roles associated with this member. /// [JsonIgnore] public IEnumerable Roles => this.RoleIds.Select(id => this.Guild.GetRole(id)).Where(x => x != null); /// /// Gets the color associated with this user's top color-giving role, otherwise 0 (no color). /// [JsonIgnore] public DiscordColor Color { get { var role = this.Roles.OrderByDescending(xr => xr.Position).FirstOrDefault(xr => xr.Color.Value != 0); return role != null ? role.Color : new DiscordColor(); } } /// /// Date the user joined the guild /// [JsonProperty("joined_at", NullValueHandling = NullValueHandling.Ignore)] public DateTimeOffset JoinedAt { get; internal set; } /// /// Date the user started boosting this server /// [JsonProperty("premium_since", NullValueHandling = NullValueHandling.Ignore)] public DateTimeOffset? PremiumSince { get; internal set; } /// /// Date until the can communicate again. /// [JsonProperty("communication_disabled_until", NullValueHandling = NullValueHandling.Include)] - public DateTimeOffset? CommunicationDisabledUntil { get; internal set; } + public DateTime? CommunicationDisabledUntil { get; internal set; } /// /// If the user is deafened /// [JsonProperty("is_deafened", NullValueHandling = NullValueHandling.Ignore)] public bool IsDeafened { get; internal set; } /// /// If the user is muted /// [JsonProperty("is_muted", NullValueHandling = NullValueHandling.Ignore)] public bool IsMuted { get; internal set; } /// /// Whether the user has not passed the guild's Membership Screening requirements yet. /// [JsonProperty("pending", NullValueHandling = NullValueHandling.Ignore)] public bool? IsPending { get; internal set; } /// /// Gets this member's voice state. /// [JsonIgnore] public DiscordVoiceState VoiceState => this.Discord.Guilds[this.GuildId].VoiceStates.TryGetValue(this.Id, out var voiceState) ? voiceState : null; [JsonIgnore] internal ulong GuildId = 0; /// /// Gets the guild of which this member is a part of. /// [JsonIgnore] public DiscordGuild Guild => this.Discord.Guilds[this.GuildId]; /// /// Gets whether this member is the Guild owner. /// [JsonIgnore] public bool IsOwner => this.Id == this.Guild.OwnerId; /// /// Gets the member's position in the role hierarchy, which is the member's highest role's position. Returns for the guild's owner. /// [JsonIgnore] public int Hierarchy => this.IsOwner ? int.MaxValue : this.RoleIds.Count == 0 ? 0 : this.Roles.Max(x => x.Position); /// /// Gets the permissions for the current member. /// [JsonIgnore] public Permissions Permissions => this.GetPermissions(); #region Overridden user properties /// /// Gets the user. /// [JsonIgnore] internal DiscordUser User => this.Discord.UserCache[this.Id]; /// /// Gets this member's username. /// public override string Username { get => this.User.Username; internal set => this.User.Username = value; } /// /// Gets the member's 4-digit discriminator. /// public override string Discriminator { get => this.User.Discriminator; internal set => this.User.Discriminator = value; } /// /// Gets the member's avatar hash. /// [JsonIgnore] public override string AvatarHash { get => this.User.AvatarHash; internal set => this.User.AvatarHash = value; } /// /// Gets the member's banner hash. /// [JsonIgnore] public override string BannerHash { get => this.User.BannerHash; internal set => this.User.BannerHash = value; } /// /// Gets whether the member is a bot. /// public override bool IsBot { get => this.User.IsBot; internal set => this.User.IsBot = value; } /// /// Gets the member's email address. /// This is only present in OAuth. /// public override string Email { get => this.User.Email; internal set => this.User.Email = value; } /// /// Gets whether the member has multi-factor authentication enabled. /// public override bool? MfaEnabled { get => this.User.MfaEnabled; internal set => this.User.MfaEnabled = value; } /// /// Gets whether the member is verified. /// This is only present in OAuth. /// public override bool? Verified { get => this.User.Verified; internal set => this.User.Verified = value; } /// /// Gets the member's chosen language /// public override string Locale { get => this.User.Locale; internal set => this.User.Locale = value; } /// /// Gets the user's flags. /// public override UserFlags? OAuthFlags { get => this.User.OAuthFlags; internal set => this.User.OAuthFlags = value; } /// /// Gets the member's flags for OAuth. /// public override UserFlags? Flags { get => this.User.Flags; internal set => this.User.Flags = value; } #endregion /// /// Creates a direct message channel to this member. /// /// Direct message channel to this member. /// Thrown when the member has the bot blocked, the member is no longer in the guild, or if the member has Allow DM from server members off. /// 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 CreateDmChannelAsync() => this.Discord.ApiClient.CreateDmAsync(this.Id); /// /// Sends a direct message to this member. Creates a direct message channel if one does not exist already. /// /// Content of the message to send. /// The sent message. /// Thrown when the member has the bot blocked, the member is no longer in the guild, or if the member has Allow DM from server members off. /// Thrown when the member does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public async Task SendMessageAsync(string content) { if (this.IsBot && this.Discord.CurrentUser.IsBot) throw new ArgumentException("Bots cannot DM each other."); var chn = await this.CreateDmChannelAsync().ConfigureAwait(false); return await chn.SendMessageAsync(content).ConfigureAwait(false); } /// /// Sends a direct message to this member. Creates a direct message channel if one does not exist already. /// /// Embed to attach to the message. /// The sent message. /// Thrown when the member has the bot blocked, the member is no longer in the guild, or if the member has Allow DM from server members off. /// Thrown when the member does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public async Task SendMessageAsync(DiscordEmbed embed) { if (this.IsBot && this.Discord.CurrentUser.IsBot) throw new ArgumentException("Bots cannot DM each other."); var chn = await this.CreateDmChannelAsync().ConfigureAwait(false); return await chn.SendMessageAsync(embed).ConfigureAwait(false); } /// /// Sends a direct message to this member. Creates a direct message channel if one does not exist already. /// /// Content of the message to send. /// Embed to attach to the message. /// The sent message. /// Thrown when the member has the bot blocked, the member is no longer in the guild, or if the member has Allow DM from server members off. /// Thrown when the member does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public async Task SendMessageAsync(string content, DiscordEmbed embed) { if (this.IsBot && this.Discord.CurrentUser.IsBot) throw new ArgumentException("Bots cannot DM each other."); var chn = await this.CreateDmChannelAsync().ConfigureAwait(false); return await chn.SendMessageAsync(content, embed).ConfigureAwait(false); } /// /// Sends a direct message to this member. Creates a direct message channel if one does not exist already. /// /// Builder to with the message. /// The sent message. /// Thrown when the member has the bot blocked, the member is no longer in the guild, or if the member has Allow DM from server members off. /// Thrown when the member does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public async Task SendMessageAsync(DiscordMessageBuilder message) { if (this.IsBot && this.Discord.CurrentUser.IsBot) throw new ArgumentException("Bots cannot DM each other."); var chn = await this.CreateDmChannelAsync().ConfigureAwait(false); return await chn.SendMessageAsync(message).ConfigureAwait(false); } /// /// Sets this member's voice mute status. /// /// Whether the member is to be muted. /// 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 SetMuteAsync(bool mute, string reason = null) => this.Discord.ApiClient.ModifyGuildMemberAsync(this.GuildId, this.Id, default, default, mute, default, default, reason); /// /// Sets this member's voice deaf status. /// /// Whether the member is to be deafened. /// 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 SetDeafAsync(bool deaf, string reason = null) => this.Discord.ApiClient.ModifyGuildMemberAsync(this.GuildId, this.Id, default, default, default, deaf, default, reason); /// /// Modifies this member. /// /// Action to perform on this member. /// 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 async Task ModifyAsync(Action action) { var mdl = new MemberEditModel(); action(mdl); if (mdl.VoiceChannel.HasValue && mdl.VoiceChannel.Value != null && mdl.VoiceChannel.Value.Type != ChannelType.Voice && mdl.VoiceChannel.Value.Type != ChannelType.Stage) throw new ArgumentException("Given channel is not a voice or stage channel.", nameof(mdl.VoiceChannel)); if (mdl.Nickname.HasValue && this.Discord.CurrentUser.Id == this.Id) { await this.Discord.ApiClient.ModifyCurrentMemberNicknameAsync(this.Guild.Id, mdl.Nickname.Value, mdl.AuditLogReason).ConfigureAwait(false); await this.Discord.ApiClient.ModifyGuildMemberAsync(this.Guild.Id, this.Id, Optional.FromNoValue(), mdl.Roles.IfPresent(e => e.Select(xr => xr.Id)), mdl.Muted, mdl.Deafened, mdl.VoiceChannel.IfPresent(e => e?.Id), mdl.AuditLogReason).ConfigureAwait(false); } else { await this.Discord.ApiClient.ModifyGuildMemberAsync(this.Guild.Id, this.Id, mdl.Nickname, mdl.Roles.IfPresent(e => e.Select(xr => xr.Id)), mdl.Muted, mdl.Deafened, mdl.VoiceChannel.IfPresent(e => e?.Id), mdl.AuditLogReason).ConfigureAwait(false); } } /// /// Adds a timeout to a member. /// /// 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(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.Guild.Id, this.Id, until, reason); /// /// Adds a timeout to a member. /// /// 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(TimeSpan until, string reason = null) => this.TimeoutAsync(DateTimeOffset.UtcNow + until, reason); /// /// Adds a timeout to a member. /// /// 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(DateTime until, string reason = null) => this.TimeoutAsync(until.ToUniversalTime() - DateTime.UtcNow, reason); /// /// Removes the timeout from a member. /// /// 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(string reason = null) => this.Discord.ApiClient.ModifyTimeoutAsync(this.Guild.Id, this.Id, null, reason); /// /// Grants a role to the member. /// /// Role to grant. /// 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 GrantRoleAsync(DiscordRole role, string reason = null) => this.Discord.ApiClient.AddGuildMemberRoleAsync(this.Guild.Id, this.Id, role.Id, reason); /// /// Revokes a role from a member. /// /// Role to revoke. /// 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 RevokeRoleAsync(DiscordRole role, string reason = null) => this.Discord.ApiClient.RemoveGuildMemberRoleAsync(this.Guild.Id, this.Id, role.Id, reason); /// /// Sets the member's roles to ones specified. /// /// Roles to set. /// 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 ReplaceRolesAsync(IEnumerable roles, string reason = null) => this.Discord.ApiClient.ModifyGuildMemberAsync(this.Guild.Id, this.Id, default, new Optional>(roles.Select(xr => xr.Id)), default, default, default, reason); /// /// Bans this member from their guild. /// /// 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 BanAsync(int deleteMessageDays = 0, string reason = null) => this.Guild.BanMemberAsync(this, deleteMessageDays, reason); /// /// Unbans this member from their guild. /// /// 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 UnbanAsync(string reason = null) => this.Guild.UnbanMemberAsync(this, reason); /// /// Kicks this member from their guild. /// /// Reason for audit logs. /// /// [alias="KickAsync"] /// 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 RemoveAsync(string reason = null) => this.Discord.ApiClient.RemoveGuildMemberAsync(this.GuildId, this.Id, reason); /// /// Moves this member to the specified voice channel /// /// /// Thrown when the client does not have the permission. /// Thrown when the member does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task PlaceInAsync(DiscordChannel channel) => channel.PlaceMemberAsync(this); /// /// Updates the member's suppress state in a stage channel. /// /// The channel the member is currently in. /// Toggles the member's suppress state. /// Thrown when the channel in not a voice channel. public async Task UpdateVoiceStateAsync(DiscordChannel channel, bool? suppress) { if (channel.Type != ChannelType.Stage) throw new ArgumentException("Voice state can only be updated in a stage channel."); await this.Discord.ApiClient.UpdateUserVoiceStateAsync(this.Guild.Id, this.Id, channel.Id, suppress).ConfigureAwait(false); } /// /// Makes the user a speaker. /// /// Thrown when the user is not inside an stage channel. /// Thrown when the client does not have the permission. /// Thrown when the member does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public async Task MakeSpeakerAsync() { var vs = this.VoiceState; if (vs == null || vs.Channel.Type != ChannelType.Stage) throw new ArgumentException("Voice state can only be updated when the user is inside an stage channel."); await this.Discord.ApiClient.UpdateUserVoiceStateAsync(this.Guild.Id, this.Id, vs.Channel.Id, false).ConfigureAwait(false); } /// /// Moves the user to audience. /// /// Thrown when the user is not inside an stage channel. /// Thrown when the client does not have the permission. /// Thrown when the member does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public async Task MoveToAudienceAsync() { var vs = this.VoiceState; if (vs == null || vs.Channel.Type != ChannelType.Stage) throw new ArgumentException("Voice state can only be updated when the user is inside an stage channel."); await this.Discord.ApiClient.UpdateUserVoiceStateAsync(this.Guild.Id, this.Id, vs.Channel.Id, true).ConfigureAwait(false); } /// /// Calculates permissions in a given channel for this member. /// /// Channel to calculate permissions for. /// Calculated permissions for this member in the channel. public Permissions PermissionsIn(DiscordChannel channel) => channel.PermissionsFor(this); /// /// Get's the current member's roles based on the sum of the permissions of their given roles. /// private Permissions GetPermissions() { if (this.Guild.OwnerId == this.Id) return PermissionMethods.FullPerms; Permissions perms; // assign @everyone permissions var everyoneRole = this.Guild.EveryoneRole; perms = everyoneRole.Permissions; // assign permissions from member's roles (in order) perms |= this.Roles.Aggregate(Permissions.None, (c, role) => c | role.Permissions); // Adminstrator grants all permissions and cannot be overridden return (perms & Permissions.Administrator) == Permissions.Administrator ? PermissionMethods.FullPerms : perms; } /// /// Returns a string representation of this member. /// /// String representation of this member. public override string ToString() => $"Member {this.Id}; {this.Username}#{this.Discriminator} ({this.DisplayName})"; /// /// Checks whether this is equal to another object. /// /// Object to compare to. /// Whether the object is equal to this . public override bool Equals(object obj) => this.Equals(obj as DiscordMember); /// /// Checks whether this is equal to another . /// /// to compare to. /// Whether the is equal to this . public bool Equals(DiscordMember e) => e is not null && (ReferenceEquals(this, e) || (this.Id == e.Id && this.GuildId == e.GuildId)); /// /// Gets the hash code for this . /// /// The hash code for this . public override int GetHashCode() { var hash = 13; hash = (hash * 7) + this.Id.GetHashCode(); hash = (hash * 7) + this.GuildId.GetHashCode(); return hash; } /// /// Gets whether the two objects are equal. /// /// First member to compare. /// Second member to compare. /// Whether the two members are equal. public static bool operator ==(DiscordMember e1, DiscordMember e2) { var o1 = e1 as object; var o2 = e2 as object; return (o1 != null || o2 == null) && (o1 == null || o2 != null) && ((o1 == null && o2 == null) || (e1.Id == e2.Id && e1.GuildId == e2.GuildId)); } /// /// Gets whether the two objects are not equal. /// /// First member to compare. /// Second member to compare. /// Whether the two members are not equal. public static bool operator !=(DiscordMember e1, DiscordMember e2) => !(e1 == e2); } } diff --git a/DisCatSharp/EventArgs/Guild/Timeout/GuildMemberTimeoutAddEventArgs.cs b/DisCatSharp/EventArgs/Guild/Timeout/GuildMemberTimeoutAddEventArgs.cs new file mode 100644 index 000000000..c67e43f57 --- /dev/null +++ b/DisCatSharp/EventArgs/Guild/Timeout/GuildMemberTimeoutAddEventArgs.cs @@ -0,0 +1,69 @@ +// 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 DisCatSharp.Entities; + +namespace DisCatSharp.EventArgs +{ + /// + /// Represents arguments for event. + /// + public class GuildMemberTimeoutAddEventArgs : DiscordEventArgs + { + /// + /// Gets the member that was timed out. + /// + public DiscordMember Target { get; internal set; } + + /// + /// Gets the member that timed out the member. + /// + public DiscordMember Actor { get; internal set; } + + /// + /// Gets the guild this member was timed out. + /// + public DiscordGuild Guild { get; internal set; } + + /// + /// Gets the audit log id. + /// + public ulong? AuditLogId { get; internal set; } + + /// + /// Gets the audit log reason. + /// + public string AuditLogReason { get; internal set; } + + /// + /// Gets the timeout time. + /// + public DateTimeOffset Timeout { get; internal set; } + + /// + /// Initializes a new instance of the class. + /// + internal GuildMemberTimeoutAddEventArgs(IServiceProvider provider) : base(provider) { } + } +} diff --git a/DisCatSharp/EventArgs/Guild/Timeout/GuildMemberTimeoutRemoveEventArgs.cs b/DisCatSharp/EventArgs/Guild/Timeout/GuildMemberTimeoutRemoveEventArgs.cs new file mode 100644 index 000000000..05252ebef --- /dev/null +++ b/DisCatSharp/EventArgs/Guild/Timeout/GuildMemberTimeoutRemoveEventArgs.cs @@ -0,0 +1,69 @@ +// 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 DisCatSharp.Entities; + +namespace DisCatSharp.EventArgs +{ + /// + /// Represents arguments for event. + /// + public class GuildMemberTimeoutRemoveEventArgs : DiscordEventArgs + { + /// + /// Gets the member that was affected by the timeout. + /// + public DiscordMember Target { get; internal set; } + + /// + /// Gets the member that removed the timeout for the member. + /// + public DiscordMember Actor { get; internal set; } + + /// + /// Gets the guild this member was timed out. + /// + public DiscordGuild Guild { get; internal set; } + + /// + /// Gets the audit log id. + /// + public ulong? AuditLogId { get; internal set; } + + /// + /// Gets the audit log reason. + /// + public string AuditLogReason { get; internal set; } + + /// + /// Gets the timeout time before the remove. + /// + public DateTimeOffset TimeoutBefore { get; internal set; } + + /// + /// Initializes a new instance of the class. + /// + internal GuildMemberTimeoutRemoveEventArgs(IServiceProvider provider) : base(provider) { } + } +} diff --git a/DisCatSharp/EventArgs/Guild/Timeout/GuildMemberTimeoutUpdateEventArgs.cs b/DisCatSharp/EventArgs/Guild/Timeout/GuildMemberTimeoutUpdateEventArgs.cs new file mode 100644 index 000000000..ac404bd48 --- /dev/null +++ b/DisCatSharp/EventArgs/Guild/Timeout/GuildMemberTimeoutUpdateEventArgs.cs @@ -0,0 +1,74 @@ +// 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 DisCatSharp.Entities; + +namespace DisCatSharp.EventArgs +{ + /// + /// Represents arguments for event. + /// + public class GuildMemberTimeoutUpdateEventArgs : DiscordEventArgs + { + /// + /// Gets the member that was timed out. + /// + public DiscordMember Target { get; internal set; } + + /// + /// Gets the member that timed out the member. + /// + public DiscordMember Actor { get; internal set; } + + /// + /// Gets the guild this member was timed out. + /// + public DiscordGuild Guild { get; internal set; } + + /// + /// Gets the audit log id. + /// + public ulong? AuditLogId { get; internal set; } + + /// + /// Gets the audit log reason. + /// + public string AuditLogReason { get; internal set; } + + /// + /// Gets the timeout time before the update. + /// + public DateTimeOffset TimeoutBefore { get; internal set; } + + /// + /// Gets the timeout time after the update. + /// + public DateTimeOffset TimeoutAfter { get; internal set; } + + /// + /// Initializes a new instance of the class. + /// + internal GuildMemberTimeoutUpdateEventArgs(IServiceProvider provider) : base(provider) { } + } +}