diff --git a/DisCatSharp/Clients/DiscordClient.Dispatch.cs b/DisCatSharp/Clients/DiscordClient.Dispatch.cs
index a9d20ad10..f78f550d2 100644
--- a/DisCatSharp/Clients/DiscordClient.Dispatch.cs
+++ b/DisCatSharp/Clients/DiscordClient.Dispatch.cs
@@ -1,3113 +1,3115 @@
// This file is part of the DisCatSharp project.
//
// Copyright (c) 2021 AITSYS
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Globalization;
using System.Linq;
using System.Security.Cryptography;
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 = false;
#endregion
#region Dispatch Handler
///
/// Handles the dispatch.
///
/// 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(this.ServiceProvider)
{
EventName = payload.EventName,
PayloadObject = dat
}).ConfigureAwait(false);
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"];
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._guilds[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._guilds[gid], ems).ConfigureAwait(false);
break;
case "guild_stickers_update":
var strs = dat["stickers"].ToDiscordObject>();
await this.OnStickersUpdatedAsync(strs, dat).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._guilds.ContainsKey(gid))
return;
await this.OnGuildIntegrationsUpdateEventAsync(this._guilds[gid]).ConfigureAwait(false);
break;
/*
Ok soooo.. this isn't documented yet
It seems to be part of the next version of membership screening (https://discord.com/channels/641574644578648068/689591708962652289/845836910991507486)
advaith said the following (https://discord.com/channels/641574644578648068/689591708962652289/845838160047112202):
> iirc it happens when a user leaves a server where they havent completed screening yet
We have to wait till it's documented, but the fields are:
{ "user_id": "snowflake_user", "guild_id": "snowflake_guild" }
We could handle it rn, but due to the fact that it isn't documented, it's not an good idea.
*/
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._guilds[gid]).ConfigureAwait(false);
break;
case "guild_ban_remove":
usr = dat["user"].ToObject();
gid = (ulong)dat["guild_id"];
await this.OnGuildBanRemoveEventAsync(usr, this._guilds[gid]).ConfigureAwait(false);
break;
#endregion
#region Guild Event
case "guild_scheduled_event_create":
gse = dat.ToObject();
await this.OnGuildScheduledEventCreateEventAsync(gse).ConfigureAwait(false);
break;
case "guild_scheduled_event_update":
gse = dat.ToObject();
await this.OnGuildScheduledEventUpdateEventAsync(gse).ConfigureAwait(false);
break;
case "guild_scheduled_event_delete":
gse = dat.ToObject();
await this.OnGuildScheduledEventDeleteEventAsync(gse).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, 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, 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._guilds.ContainsKey(gid))
return;
await this.OnGuildIntegrationCreateEventAsync(this._guilds[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._guilds.ContainsKey(gid))
return;
await this.OnGuildIntegrationUpdateEventAsync(this._guilds[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._guilds.ContainsKey(gid))
return;
await this.OnGuildIntegrationDeleteEventAsync(this._guilds[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._guilds[gid]).ConfigureAwait(false);
break;
case "guild_member_remove":
gid = (ulong)dat["guild_id"];
usr = dat["user"].ToObject();
if (!this._guilds.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._guilds[gid]).ConfigureAwait(false);
break;
case "guild_member_update":
gid = (ulong)dat["guild_id"];
await this.OnGuildMemberUpdateEventAsync(dat.ToDiscordObject(), this._guilds[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._guilds[gid]).ConfigureAwait(false);
break;
case "guild_role_update":
gid = (ulong)dat["guild_id"];
await this.OnGuildRoleUpdateEventAsync(dat["role"].ToObject(), this._guilds[gid]).ConfigureAwait(false);
break;
case "guild_role_delete":
gid = (ulong)dat["guild_id"];
await this.OnGuildRoleDeleteEventAsync((ulong)dat["role_id"], this._guilds[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._guilds[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._guilds[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._guilds[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._guilds[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._guilds[gid].GetChannel(cid), this._guilds[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.
/// 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.GatewayVersion = ready.GatewayVersion;
this._sessionId = ready.SessionId;
var raw_guild_index = rawGuilds.ToDictionary(xt => (ulong)xt["id"], xt => (JObject)xt);
this._guilds.Clear();
foreach (var guild in ready.Guilds)
{
guild.Discord = this;
if (guild._channels == null)
guild._channels = new ConcurrentDictionary();
foreach (var xc in guild.Channels.Values)
{
xc.GuildId = guild.Id;
xc.Discord = this;
foreach (var xo in xc._permissionOverwrites)
{
xo.Discord = this;
xo._channel_id = xc.Id;
}
}
if (guild._roles == null)
guild._roles = new ConcurrentDictionary();
foreach (var xr in guild.Roles.Values)
{
xr.Discord = this;
xr._guild_id = guild.Id;
}
var raw_guild = raw_guild_index[guild.Id];
var raw_members = (JArray)raw_guild["members"];
if (guild._members != null)
guild._members.Clear();
else
guild._members = new ConcurrentDictionary();
if (raw_members != null)
{
foreach (var xj in raw_members)
{
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._members[xtm.User.Id] = new DiscordMember(xtm) { Discord = this, _guild_id = guild.Id };
}
}
if (guild._emojis == null)
guild._emojis = new ConcurrentDictionary();
foreach (var xe in guild.Emojis.Values)
xe.Discord = this;
if (guild._voiceStates == null)
guild._voiceStates = new ConcurrentDictionary();
foreach (var xvs in guild.VoiceStates.Values)
xvs.Discord = this;
this._guilds[guild.Id] = guild;
}
await this._ready.InvokeAsync(this, new ReadyEventArgs(this.ServiceProvider)).ConfigureAwait(false);
}
///
/// Handles the resumed.
///
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._permissionOverwrites)
{
xo.Discord = this;
xo._channel_id = channel.Id;
}
this._guilds[channel.GuildId.Value]._channels[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 channel_new = this.InternalGetCachedChannel(channel.Id);
DiscordChannel channel_old = null;
if (channel_new != null)
{
channel_old = new DiscordChannel
{
Bitrate = channel_new.Bitrate,
Discord = this,
GuildId = channel_new.GuildId,
Id = channel_new.Id,
//IsPrivate = channel_new.IsPrivate,
LastMessageId = channel_new.LastMessageId,
Name = channel_new.Name,
_permissionOverwrites = new List(channel_new._permissionOverwrites),
Position = channel_new.Position,
Topic = channel_new.Topic,
Type = channel_new.Type,
UserLimit = channel_new.UserLimit,
ParentId = channel_new.ParentId,
IsNSFW = channel_new.IsNSFW,
PerUserRateLimit = channel_new.PerUserRateLimit,
RtcRegionId = channel_new.RtcRegionId,
QualityMode = channel_new.QualityMode,
DefaultAutoArchiveDuration = channel_new.DefaultAutoArchiveDuration
};
channel_new.Bitrate = channel.Bitrate;
channel_new.Name = channel.Name;
channel_new.Position = channel.Position;
channel_new.Topic = channel.Topic;
channel_new.UserLimit = channel.UserLimit;
channel_new.ParentId = channel.ParentId;
channel_new.IsNSFW = channel.IsNSFW;
channel_new.PerUserRateLimit = channel.PerUserRateLimit;
channel_new.Type = channel.Type;
channel_new.RtcRegionId = channel.RtcRegionId;
channel_new.QualityMode = channel.QualityMode;
channel_new.DefaultAutoArchiveDuration = channel.DefaultAutoArchiveDuration;
channel_new._permissionOverwrites.Clear();
foreach (var po in channel._permissionOverwrites)
{
po.Discord = this;
po._channel_id = channel.Id;
}
channel_new._permissionOverwrites.AddRange(channel._permissionOverwrites);
if (this.Configuration.AutoRefreshChannelCache && gld != null)
{
await this.RefreshChannelsAsync(channel.Guild.Id);
}
}
else if (gld != null)
{
gld._channels[channel.Id] = channel;
if (this.Configuration.AutoRefreshChannelCache)
{
await this.RefreshChannelsAsync(channel.Guild.Id);
}
}
await this._channelUpdated.InvokeAsync(this, new ChannelUpdateEventArgs(this.ServiceProvider) { ChannelAfter = channel_new, Guild = gld, ChannelBefore = channel_old }).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._channels.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._channels.Clear();
foreach (var channel in channels.ToList())
{
channel.Discord = this;
foreach (var xo in channel._permissionOverwrites)
{
xo.Discord = this;
xo._channel_id = channel.Id;
}
guild._channels[channel.Id] = channel;
}
}
///
/// Handles the channel pins update.
///
/// The guild id.
/// The channel id.
/// The 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._presences[xp.InternalUser.Id] = xp;
}
}
var exists = this._guilds.TryGetValue(guild.Id, out var foundGuild);
guild.Discord = this;
guild.IsUnavailable = false;
var eventGuild = guild;
if (exists)
guild = foundGuild;
if (guild._channels == null)
guild._channels = new ConcurrentDictionary();
if(guild._threads == null)
guild._threads = new ConcurrentDictionary();
if (guild._roles == null)
guild._roles = new ConcurrentDictionary();
if (guild._threads == null)
guild._threads = new ConcurrentDictionary();
if (guild._stickers == null)
guild._stickers = new ConcurrentDictionary();
if (guild._emojis == null)
guild._emojis = new ConcurrentDictionary();
if (guild._voiceStates == null)
guild._voiceStates = new ConcurrentDictionary();
if (guild._members == null)
guild._members = new ConcurrentDictionary();
if (guild._scheduledEvents == null)
guild._scheduledEvents = new ConcurrentDictionary();
this.UpdateCachedGuild(eventGuild, rawMembers);
guild.JoinedAt = eventGuild.JoinedAt;
guild.IsLarge = eventGuild.IsLarge;
guild.MemberCount = Math.Max(eventGuild.MemberCount, guild._members.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._voiceStates) guild._voiceStates[kvp.Key] = kvp.Value;
foreach (var kvp in eventGuild._channels) guild._channels[kvp.Key] = kvp.Value;
foreach (var kvp in eventGuild._roles) guild._roles[kvp.Key] = kvp.Value;
foreach (var kvp in eventGuild._emojis) guild._emojis[kvp.Key] = kvp.Value;
foreach (var kvp in eventGuild._threads) guild._threads[kvp.Key] = kvp.Value;
foreach (var kvp in eventGuild._stickers) guild._stickers[kvp.Key] = kvp.Value;
foreach (var kvp in eventGuild._stageInstances) guild._stageInstances[kvp.Key] = kvp.Value;
foreach (var kvp in eventGuild._scheduledEvents) guild._scheduledEvents[kvp.Key] = kvp.Value;
foreach (var xc in guild._channels.Values)
{
xc.GuildId = guild.Id;
xc.Discord = this;
foreach (var xo in xc._permissionOverwrites)
{
xo.Discord = this;
xo._channel_id = xc.Id;
}
}
foreach(var xt in guild._threads.Values)
{
xt.GuildId = guild.Id;
xt.Discord = this;
}
foreach (var xe in guild._emojis.Values)
xe.Discord = this;
foreach (var xs in guild._stickers.Values)
xs.Discord = this;
foreach (var xvs in guild._voiceStates.Values)
xvs.Discord = this;
foreach (var xsi in guild._stageInstances.Values)
{
xsi.Discord = this;
xsi.GuildId = guild.Id;
}
foreach (var xr in guild._roles.Values)
{
xr.Discord = this;
xr._guild_id = guild.Id;
}
foreach (var xse in guild._scheduledEvents.Values)
{
xse.Discord = this;
xse.GuildId = guild.Id;
}
var old = Volatile.Read(ref this._guildDownloadCompleted);
var dcompl = this._guilds.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._guilds.ContainsKey(guild.Id))
{
this._guilds[guild.Id] = guild;
oldGuild = null;
}
else
{
var gld = this._guilds[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,
_channels = new ConcurrentDictionary(),
_threads = new ConcurrentDictionary(),
_emojis = new ConcurrentDictionary(),
_stickers = new ConcurrentDictionary(),
_members = new ConcurrentDictionary(),
_roles = new ConcurrentDictionary(),
_stageInstances = new ConcurrentDictionary(),
_voiceStates = new ConcurrentDictionary(),
_scheduledEvents = new ConcurrentDictionary()
};
foreach (var kvp in gld._channels) oldGuild._channels[kvp.Key] = kvp.Value;
foreach (var kvp in gld._threads) oldGuild._threads[kvp.Key] = kvp.Value;
foreach (var kvp in gld._emojis) oldGuild._emojis[kvp.Key] = kvp.Value;
foreach (var kvp in gld._stickers) oldGuild._stickers[kvp.Key] = kvp.Value;
foreach (var kvp in gld._roles) oldGuild._roles[kvp.Key] = kvp.Value;
foreach (var kvp in gld._voiceStates) oldGuild._voiceStates[kvp.Key] = kvp.Value;
foreach (var kvp in gld._members) oldGuild._members[kvp.Key] = kvp.Value;
foreach (var kvp in gld._stageInstances) oldGuild._stageInstances[kvp.Key] = kvp.Value;
foreach (var kvp in gld._scheduledEvents) oldGuild._scheduledEvents[kvp.Key] = kvp.Value;
}
guild.Discord = this;
guild.IsUnavailable = false;
var eventGuild = guild;
guild = this._guilds[eventGuild.Id];
if (guild._channels == null)
guild._channels = new ConcurrentDictionary();
if (guild._threads == null)
guild._threads = new ConcurrentDictionary();
if (guild._roles == null)
guild._roles = new ConcurrentDictionary();
if (guild._emojis == null)
guild._emojis = new ConcurrentDictionary();
if (guild._stickers == null)
guild._stickers = new ConcurrentDictionary();
if (guild._voiceStates == null)
guild._voiceStates = new ConcurrentDictionary();
if (guild._stageInstances == null)
guild._stageInstances = new ConcurrentDictionary();
if (guild._members == null)
guild._members = new ConcurrentDictionary();
if (guild._scheduledEvents == null)
guild._scheduledEvents = new ConcurrentDictionary();
this.UpdateCachedGuild(eventGuild, rawMembers);
foreach (var xc in guild._channels.Values)
{
xc.GuildId = guild.Id;
xc.Discord = this;
foreach (var xo in xc._permissionOverwrites)
{
xo.Discord = this;
xo._channel_id = xc.Id;
}
}
foreach (var xc in guild._threads.Values)
{
xc.GuildId = guild.Id;
xc.Discord = this;
}
foreach (var xe in guild._emojis.Values)
xe.Discord = this;
foreach (var xs in guild._stickers.Values)
xs.Discord = this;
foreach (var xvs in guild._voiceStates.Values)
xvs.Discord = this;
foreach (var xr in guild._roles.Values)
{
xr.Discord = this;
xr._guild_id = guild.Id;
}
foreach (var xsi in guild._stageInstances.Values)
{
xsi.Discord = this;
xsi.GuildId = guild.Id;
}
foreach (var xse in guild._scheduledEvents.Values)
{
xse.Discord = this;
xse.GuildId = guild.Id;
}
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._guilds.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._guilds.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.
/// If true, is large.
/// 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._presences[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._emojis);
guild._emojis.Clear();
foreach (var emoji in newEmojis)
{
emoji.Discord = this;
guild._emojis[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 raw.
internal async Task OnStickersUpdatedAsync(IEnumerable newStickers, JObject raw)
{
var guild = this.InternalGetCachedGuild((ulong)raw["guild_id"]);
var oldStickers = new ConcurrentDictionary(guild._stickers);
guild._stickers.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._stickers[nst.Id] = nst;
}
var sea = new GuildStickersUpdateEventArgs(this.ServiceProvider)
{
Guild = guild,
StickersBefore = oldStickers,
StickersAfter = guild.Stickers
};
await this._guildStickersUpdated.InvokeAsync(this, sea).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);
}
#endregion
#region Guild Ban
///
/// Handles the guild ban add event.
///
/// The 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, _guild_id = 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 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, _guild_id = 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
///
/// Dispatches the event.
///
/// The created event.
internal async Task OnGuildScheduledEventCreateEventAsync(DiscordScheduledEvent scheduled_event)
{
scheduled_event.Discord = this;
var guild = this.InternalGetCachedGuild(scheduled_event.GuildId);
guild._scheduledEvents[scheduled_event.Id] = scheduled_event;
await this._guildScheduledEventCreated.InvokeAsync(this, new GuildScheduledEventCreateEventArgs(this.ServiceProvider) { ScheduledEvent = scheduled_event, Guild = scheduled_event.Guild }).ConfigureAwait(false);
}
///
/// Dispatches the event.
///
/// The updated event.
internal async Task OnGuildScheduledEventUpdateEventAsync(DiscordScheduledEvent scheduled_event)
{
scheduled_event.Discord = this;
var guild = this.InternalGetCachedGuild(scheduled_event.GuildId);
var old_event = guild._scheduledEvents[scheduled_event.Id] ?? null;
guild._scheduledEvents.AddOrUpdate(scheduled_event.Id, scheduled_event, (id, old) =>
{
old.Name = scheduled_event.Name;
old.Description = scheduled_event.Description;
old.ChannelId = scheduled_event.ChannelId;
old.EntityMetadata = scheduled_event.EntityMetadata;
old.EntityType = scheduled_event.EntityType;
old.Status = scheduled_event.Status;
old.ScheduledStartTimeRaw = scheduled_event.ScheduledStartTimeRaw;
old.ScheduledEndTimeRaw = scheduled_event.ScheduledEndTimeRaw;
old.PrivacyLevel = scheduled_event.PrivacyLevel;
old.UserCount = scheduled_event.UserCount;
//old.SkuIds = scheduled_event.SkuIds;
return old;
});
await this._guildScheduledEventUpdated.InvokeAsync(this, new GuildScheduledEventUpdateEventArgs(this.ServiceProvider) { ScheduledEventBefore = old_event, ScheduledEventAfter = scheduled_event, Guild = scheduled_event.Guild }).ConfigureAwait(false);
}
///
/// Dispatches the event.
///
/// The deleted event.
internal async Task OnGuildScheduledEventDeleteEventAsync(DiscordScheduledEvent scheduled_event)
{
scheduled_event.Discord = this;
var guild = this.InternalGetCachedGuild(scheduled_event.GuildId);
guild._scheduledEvents.TryRemove(scheduled_event.Id, out var deleted_event);
await this._guildScheduledEventDeleted.InvokeAsync(this, new GuildScheduledEventDeleteEventArgs(this.ServiceProvider) { ScheduledEvent = scheduled_event, Guild = scheduled_event.Guild, Status = scheduled_event.Status }).ConfigureAwait(false);
}
///
/// Dispatches the event.
///
internal async Task OnGuildScheduledEventUserAddedEventAsync(ulong guild_scheduled_event_id, ulong user_id, ulong guild_id)
{
var guild = this.InternalGetCachedGuild(guild_id);
var success = guild._scheduledEvents.TryGetValue(guild_scheduled_event_id, out var scheduled_event);
if (!success)
scheduled_event = await this.ApiClient.GetGuildScheduledEventAsync(guild_id, guild_scheduled_event_id);
var user = this.UserCache.TryGetValue(user_id, out var usr) ? usr : await this.GetUserAsync(user_id, true) ?? new DiscordUser{ Id = user_id, Discord = this };
await this._guildScheduledEventUserAdded.InvokeAsync(this, new GuildScheduledEventUserAddEventArgs(this.ServiceProvider) { ScheduledEvent = scheduled_event, Guild = guild, User = user }).ConfigureAwait(false);
}
///
/// Dispatches the event.
///
internal async Task OnGuildScheduledEventUserRemovedEventAsync(ulong guild_scheduled_event_id, ulong user_id, ulong guild_id)
{
var guild = this.InternalGetCachedGuild(guild_id);
var success = guild._scheduledEvents.TryGetValue(guild_scheduled_event_id, out var scheduled_event);
if (!success)
scheduled_event = await this.ApiClient.GetGuildScheduledEventAsync(guild_id, guild_scheduled_event_id);
var user = this.UserCache.TryGetValue(user_id, out var usr) ? usr : await this.GetUserAsync(user_id, true) ?? new DiscordUser{ Id = user_id, Discord = this };
await this._guildScheduledEventUserRemoved.InvokeAsync(this, new GuildScheduledEventUserRemoveEventArgs(this.ServiceProvider) { ScheduledEvent = scheduled_event, Guild = guild, User = user }).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 integration delete event.
///
/// The guild.
/// The integration_id.
/// The application_id.
internal async Task OnGuildIntegrationDeleteEventAsync(DiscordGuild guild, ulong integration_id, ulong? application_id)
=> await this._guildIntegrationDeleted.InvokeAsync(this, new GuildIntegrationDeleteEventArgs(this.ServiceProvider) { Guild = guild, IntegrationId = integration_id, ApplicationId = application_id }).ConfigureAwait(false);
#endregion
#region Guild Member
///
/// Handles the guild member add event.
///
/// The 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,
_guild_id = guild.Id
};
guild._members[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 user.
/// The guild.
internal async Task OnGuildMemberRemoveEventAsync(TransportUser user, DiscordGuild guild)
{
var usr = new DiscordUser(user);
if (!guild._members.TryRemove(user.Id, out var mbr))
mbr = new DiscordMember(usr) { Discord = this, _guild_id = 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 member.
/// The guild.
/// The roles.
/// The nick.
/// If true, 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, _guild_id = guild.Id };
var nick_old = mbr.Nickname;
var pending_old = mbr.IsPending;
var roles_old = new ReadOnlyCollection(new List(mbr.Roles));
var cdu_old = mbr.CommunicationDisabledUntil;
mbr._avatarHash = member.AvatarHash;
mbr.GuildAvatarHash = member.GuildAvatarHash;
mbr.Nickname = nick;
mbr.IsPending = pending;
mbr.CommunicationDisabledUntil = member.CommunicationDisabledUntil;
mbr._role_ids.Clear();
mbr._role_ids.AddRange(roles);
var ea = 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 = nick_old,
RolesBefore = roles_old,
PendingBefore = pending_old,
TimeoutBefore = cdu_old
};
await this._guildMemberUpdated.InvokeAsync(this, ea).ConfigureAwait(false);
}
///
/// Handles the guild members chunk event.
///
/// The dat.
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, _guild_id = guild.Id };
if (!this.UserCache.ContainsKey(mbr.Id))
this.UserCache[mbr.Id] = new DiscordUser(members[i].User) { Discord = this };
guild._members[mbr.Id] = mbr;
mbrs.Add(mbr);
}
guild.MemberCount = guild._members.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._guild_id = guild.Id;
guild._roles[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
{
_guild_id = guild.Id,
_color = newRole._color,
Discord = this,
IsHoisted = newRole.IsHoisted,
Id = newRole.Id,
IsManaged = newRole.IsManaged,
IsMentionable = newRole.IsMentionable,
Name = newRole.Name,
Permissions = newRole.Permissions,
Position = newRole.Position
};
newRole._guild_id = guild.Id;
newRole._color = role._color;
newRole.IsHoisted = role.IsHoisted;
newRole.IsManaged = role.IsManaged;
newRole.IsMentionable = role.IsMentionable;
newRole.Name = role.Name;
newRole.Permissions = role.Permissions;
newRole.Position = role.Position;
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._roles.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 dat.
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 ack event.
///
/// The chn.
/// 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 author.
/// The member.
/// The reference author.
/// The reference 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._mentionedUsers),
MentionedRoles = message._mentionedRoles != null ? new ReadOnlyCollection(message._mentionedRoles) : null,
MentionedChannels = message._mentionedChannels != null ? new ReadOnlyCollection(message._mentionedChannels) : null
};
await this._messageCreated.InvokeAsync(this, ea).ConfigureAwait(false);
}
///
/// Handles the message update event.
///
/// The message.
/// The author.
/// The member.
/// The reference author.
/// The reference member.
internal async Task OnMessageUpdateEventAsync(DiscordMessage message, TransportUser author, TransportMember member, TransportUser referenceAuthor, TransportMember referenceMember)
{
DiscordGuild guild;
message.Discord = this;
var event_message = message;
DiscordMessage oldmsg = null;
if (this.Configuration.MessageCacheSize == 0
|| this.MessageCache == null
|| !this.MessageCache.TryGet(xm => xm.Id == event_message.Id && xm.ChannelId == event_message.ChannelId, out message))
{
message = event_message;
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 = event_message.EditedTimestampRaw;
if (event_message.Content != null)
message.Content = event_message.Content;
message._embeds.Clear();
message._embeds.AddRange(event_message._embeds);
message.Pinned = event_message.Pinned;
message.IsTTS = event_message.IsTTS;
}
message.PopulateMentions();
var ea = new MessageUpdateEventArgs(this.ServiceProvider)
{
Message = message,
MessageBefore = oldmsg,
MentionedUsers = new ReadOnlyCollection(message._mentionedUsers),
MentionedRoles = message._mentionedRoles != null ? new ReadOnlyCollection(message._mentionedRoles) : null,
MentionedChannels = message._mentionedChannels != null ? new ReadOnlyCollection(message._mentionedChannels) : null
};
await this._messageUpdated.InvokeAsync(this, ea).ConfigureAwait(false);
}
///
/// Handles the message delete event.
///
/// The message id.
/// The channel id.
/// The 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 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.
///
/// The user id.
/// The message id.
/// The channel id.
/// The guild id.
/// The mbr.
/// 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,
_reactions = new List()
};
}
var react = msg._reactions.FirstOrDefault(xr => xr.Emoji == emoji);
if (react == null)
{
msg._reactions.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.
///
/// 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, _guild_id = 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._reactions?.FirstOrDefault(xr => xr.Emoji == emoji);
if (react != null)
{
react.Count--;
react.IsMe &= this.CurrentUser.Id != userId;
if (msg._reactions != null && react.Count <= 0) // shit happens
for (var i = 0; i < msg._reactions.Count; i++)
if (msg._reactions[i].Emoji == emoji)
{
msg._reactions.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 all.
///
/// The message id.
/// The channel id.
/// The 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._reactions?.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 emoji.
///
/// The message id.
/// The channel id.
/// The guild id.
/// The dat.
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._emojis.TryGetValue(partialEmoji.Id, out var emoji))
{
emoji = partialEmoji;
emoji.Discord = this;
}
msg._reactions?.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
///
/// Dispatches the event.
///
/// The created stage instance.
internal async Task OnStageInstanceCreateEventAsync(DiscordStageInstance stage)
{
stage.Discord = this;
var guild = this.InternalGetCachedGuild(stage.GuildId);
guild._stageInstances[stage.Id] = stage;
await this._stageInstanceCreated.InvokeAsync(this, new StageInstanceCreateEventArgs(this.ServiceProvider) { StageInstance = stage, Guild = stage.Guild }).ConfigureAwait(false);
}
///
/// Dispatches the event.
///
/// The updated stage instance.
internal async Task OnStageInstanceUpdateEventAsync(DiscordStageInstance stage)
{
stage.Discord = this;
var guild = this.InternalGetCachedGuild(stage.GuildId);
guild._stageInstances[stage.Id] = stage;
await this._stageInstanceUpdated.InvokeAsync(this, new StageInstanceUpdateEventArgs(this.ServiceProvider) { StageInstance = stage, Guild = stage.Guild }).ConfigureAwait(false);
}
///
/// Dispatches the event.
///
/// The deleted stage instance.
internal async Task OnStageInstanceDeleteEventAsync(DiscordStageInstance stage)
{
stage.Discord = this;
var guild = this.InternalGetCachedGuild(stage.GuildId);
guild._stageInstances[stage.Id] = stage;
await this._stageInstanceDeleted.InvokeAsync(this, new StageInstanceDeleteEventArgs(this.ServiceProvider) { StageInstance = stage, Guild = stage.Guild }).ConfigureAwait(false);
}
#endregion
#region Thread
///
/// Dispatches the event.
///
/// The created thread.
internal async Task OnThreadCreateEventAsync(DiscordThreadChannel thread)
{
thread.Discord = this;
this.InternalGetCachedGuild(thread.GuildId)._threads.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);
}
///
/// Dispatches the 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,
_threadMembers = threadNew._threadMembers,
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._threads[thread.Id] = thread;
}
await this._threadUpdated.InvokeAsync(this, updateEvent).ConfigureAwait(false);
}
///
/// Dispatches the event.
///
/// The deleted thread.
internal async Task OnThreadDeleteEventAsync(DiscordThreadChannel thread)
{
if (thread == null)
return;
thread.Discord = this;
var gld = thread.Guild;
if (gld._threads.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);
}
///
/// Dispatches the event.
///
/// The synced guild.
/// The synced channel ids.
/// The synced threads.
/// The synced members.
internal async Task OnThreadListSyncEventAsync(DiscordGuild guild, IReadOnlyList channel_ids, IReadOnlyList threads, IReadOnlyList members)
{
guild.Discord = this;
var channels = channel_ids.Select(x => guild.GetChannel(x.Value)); //getting channel objects
foreach (var chan in channels)
{
chan.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);
}
///
/// Dispatches the event.
///
/// The updated member.
internal async Task OnThreadMemberUpdateEventAsync(DiscordThreadChannelMember member)
{
member.Discord = this;
var thread = this.InternalGetCachedThread(member.Id);
thread.CurrentMember = member;
thread.Guild._threads.AddOrUpdate(member.Id, thread, (oldThread, newThread) => newThread);
await this._threadMemberUpdated.InvokeAsync(this, new ThreadMemberUpdateEventArgs(this.ServiceProvider) { ThreadMember = member, Thread = thread }).ConfigureAwait(false);
}
///
/// Dispatches the 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 thread_id, JArray added_members, JArray removed_members, int member_count)
{
var thread = this.InternalGetCachedThread(thread_id);
thread.Discord = this;
guild.Discord = this;
List addedMembers = new();
List removed_member_ids = new();
if (added_members != null)
{
foreach (var xj in added_members)
{
var xtm = xj.ToDiscordObject();
xtm.Discord = this;
xtm._guild_id = guild.Id;
if(xtm != null)
addedMembers.Add(xtm);
if (xtm.Id == this.CurrentUser.Id)
thread.CurrentMember = xtm;
}
}
var removedMembers = new List();
if (removed_members != null)
{
foreach (var removedId in removed_members)
{
removedMembers.Add(guild._members.TryGetValue((ulong)removedId, out var member) ? member : new DiscordMember { Id = (ulong)removedId, _guild_id = guild.Id, Discord = this });
}
}
if (removed_member_ids.Contains(this.CurrentUser.Id)) //indicates the bot was removed from the thread
thread.CurrentMember = null;
thread.MemberCount = member_count;
var threadMembersUpdateArg = new ThreadMembersUpdateEventArgs(this.ServiceProvider)
{
Guild = guild,
Thread = thread,
AddedMembers = addedMembers,
RemovedMembers = removedMembers,
MemberCount = member_count
};
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.
/// A Task.
internal async Task OnEmbeddedActivityUpdateAsync(JObject tr_activity, DiscordGuild guild, ulong channel_id, JArray j_users, ulong app_id)
=> 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);
+ var activity_users = new List();
- if (users != null)
- {
- foreach (var user in users)
+ var channel = this.InternalGetCachedChannel(channel_id) ?? await this.ApiClient.GetChannelAsync(channel_id);
+
+ if (users != null)
{
- 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);
+ 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;
+ else
+ activity_users = null;
+
+ var ea = new EmbeddedActivityUpdateEventArgs(this.ServiceProvider)
+ {
+ Guild = guild,
+ Users = activity_users,
+ Channel = channel
- var ea = new EmbeddedActivityUpdateEventArgs(this.ServiceProvider)
+ };
+ await this._embeddedActivityUpdated.InvokeAsync(this, ea).ConfigureAwait(false);
+ } catch (Exception ex)
{
- Guild = guild,
- Users = activity_users,
- Channel = channel
+ this.Logger.LogError(ex, ex.Message);
+ }
+ }*/
- };
- 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._presences.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._presences[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 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 user.
internal async Task OnUserUpdateEventAsync(TransportUser user)
{
var usr_old = 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 = usr_old
};
await this._userUpdated.InvokeAsync(this, ea).ConfigureAwait(false);
}
#endregion
#region Voice
///
/// Handles the voice state update event.
///
/// The raw.
internal async Task OnVoiceStateUpdateEventAsync(JObject raw)
{
var gid = (ulong)raw["guild_id"];
var uid = (ulong)raw["user_id"];
var gld = this._guilds[gid];
var vstateNew = raw.ToObject();
vstateNew.Discord = this;
gld._voiceStates.TryRemove(uid, out var vstateOld);
if (vstateNew.Channel != null)
{
gld._voiceStates[vstateNew.UserId] = vstateNew;
}
if (gld._members.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 endpoint.
/// The 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.
///
/// The cmd.
/// The guild_id.
internal async Task OnApplicationCommandCreateAsync(DiscordApplicationCommand cmd, ulong? guild_id)
{
cmd.Discord = this;
var guild = this.InternalGetCachedGuild(guild_id);
if (guild == null && guild_id.HasValue)
{
guild = new DiscordGuild
{
Id = guild_id.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.
///
/// The cmd.
/// The guild_id.
internal async Task OnApplicationCommandUpdateAsync(DiscordApplicationCommand cmd, ulong? guild_id)
{
cmd.Discord = this;
var guild = this.InternalGetCachedGuild(guild_id);
if (guild == null && guild_id.HasValue)
{
guild = new DiscordGuild
{
Id = guild_id.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.
///
/// The cmd.
/// The guild_id.
internal async Task OnApplicationCommandDeleteAsync(DiscordApplicationCommand cmd, ulong? guild_id)
{
cmd.Discord = this;
var guild = this.InternalGetCachedGuild(guild_id);
if (guild == null && guild_id.HasValue)
{
guild = new DiscordGuild
{
Id = guild_id.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.
///
/// The count.
/// The count.
/// The count.
/// The guild_id.
/// Count of application commands.
internal async Task OnGuildApplicationCommandCountsUpdateAsync(int sc, int ucmc, int mcmc, ulong guild_id)
{
var guild = this.InternalGetCachedGuild(guild_id);
if (guild == null)
{
guild = new DiscordGuild
{
Id = guild_id,
Discord = this
};
}
var ea = new GuildApplicationCommandCountEventArgs(this.ServiceProvider)
{
SlashCommands = sc,
UserContextMenuCommands = ucmc,
MessageContextMenuCommands = mcmc,
Guild = guild
};
await this._guildApplicationCommandCountUpdated.InvokeAsync(this, ea).ConfigureAwait(false);
}
///
/// Handles the application command permissions update.
///
/// The new permissions.
/// The command id.
/// The guild id.
/// The application id.
internal async Task OnApplicationCommandPermissionsUpdateAsync(IEnumerable perms, ulong c_id, ulong guild_id, ulong a_id)
{
if (a_id != this.CurrentApplication.Id)
return;
var guild = this.InternalGetCachedGuild(guild_id);
DiscordApplicationCommand cmd;
try
{
cmd = await this.GetGuildApplicationCommandAsync(guild_id, c_id);
}
catch(NotFoundException)
{
cmd = await this.GetGlobalApplicationCommandAsync(c_id);
}
if (guild == null)
{
guild = new DiscordGuild
{
Id = guild_id,
Discord = this
};
}
var ea = new ApplicationCommandPermissionsUpdateEventArgs(this.ServiceProvider)
{
Permissions = perms.ToList(),
Command = cmd,
ApplicationId = a_id,
Guild = guild
};
await this._applicationCommandPermissionsUpdated.InvokeAsync(this, ea).ConfigureAwait(false);
}
#endregion
#region Interaction
///
/// Handles the interaction create.
///
/// The guild id.
/// The channel id.
/// The user.
/// The 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) { _guild_id = 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._guild_id = 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._guild_id = 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.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 guild id.
/// The started.
/// The mbr.
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 the unknown event.
///
/// 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/Entities/Channel/DiscordChannel.cs b/DisCatSharp/Entities/Channel/DiscordChannel.cs
index 80068f668..ac2921048 100644
--- a/DisCatSharp/Entities/Channel/DiscordChannel.cs
+++ b/DisCatSharp/Entities/Channel/DiscordChannel.cs
@@ -1,1404 +1,1405 @@
// This file is part of the DisCatSharp project.
//
// Copyright (c) 2021 AITSYS
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using DisCatSharp.Enums;
using DisCatSharp.Exceptions;
using DisCatSharp.Net;
using DisCatSharp.Net.Abstractions;
using DisCatSharp.Net.Models;
using Newtonsoft.Json;
namespace DisCatSharp.Entities
{
///
/// Represents a discord channel.
///
public class DiscordChannel : SnowflakeObject, IEquatable
{
///
/// Gets ID of the guild to which this channel belongs.
///
[JsonProperty("guild_id", NullValueHandling = NullValueHandling.Ignore)]
public ulong? GuildId { get; internal set; }
///
/// Gets ID of the category that contains this channel.
///
[JsonProperty("parent_id", NullValueHandling = NullValueHandling.Include)]
public ulong? ParentId { get; internal set; }
///
/// Gets the category that contains this channel.
///
[JsonIgnore]
public DiscordChannel Parent
=> this.ParentId.HasValue ? this.Guild.GetChannel(this.ParentId.Value) : null;
///
/// Gets the name of this channel.
///
[JsonProperty("name", NullValueHandling = NullValueHandling.Ignore)]
public string Name { get; internal set; }
///
/// Gets the type of this channel.
///
[JsonProperty("type", NullValueHandling = NullValueHandling.Ignore)]
public ChannelType Type { get; internal set; }
///
/// Gets this channels's banner hash, when applicable.
///
[JsonProperty("banner")]
public string BannerHash { get; internal set; }
///
/// Gets this channels's banner in url form.
///
[JsonIgnore]
public string BannerUrl
=> !string.IsNullOrWhiteSpace(this.BannerHash) ? $"{DiscordDomain.GetDomain(CoreDomain.DiscordCdn).Uri}{Endpoints.CHANNELS}/{this.Id.ToString(CultureInfo.InvariantCulture)}{Endpoints.BANNERS}/{this.BannerHash}.{(this.BannerHash.StartsWith("a_") ? "gif" : "png")}" : null;
///
/// Gets the position of this channel.
///
[JsonProperty("position", NullValueHandling = NullValueHandling.Ignore)]
public int Position { get; internal set; }
///
/// Gets the maximum available position to move the channel to.
/// This can contain outdated informations.
///
public int GetMaxPosition()
{
var channels = this.Guild.Channels.Values;
return this.ParentId != null
? this.Type == ChannelType.Text || this.Type == ChannelType.News
? channels.Where(xc => xc.ParentId == this.ParentId && (xc.Type == ChannelType.Text || xc.Type == ChannelType.News)).OrderBy(xc => xc.Position).ToArray().Last().Position
: this.Type == ChannelType.Voice || this.Type == ChannelType.Stage
? channels.Where(xc => xc.ParentId == this.ParentId && (xc.Type == ChannelType.Voice || xc.Type == ChannelType.Stage)).OrderBy(xc => xc.Position).ToArray().Last().Position
: channels.Where(xc => xc.ParentId == this.ParentId && xc.Type == this.Type).OrderBy(xc => xc.Position).ToArray().Last().Position
: channels.Where(xc => xc.ParentId == null && xc.Type == this.Type).OrderBy(xc => xc.Position).ToArray().Last().Position;
}
///
/// Gets the minimum available position to move the channel to.
///
public int GetMinPosition()
{
var channels = this.Guild.Channels.Values;
return this.ParentId != null
? this.Type == ChannelType.Text || this.Type == ChannelType.News
? channels.Where(xc => xc.ParentId == this.ParentId && (xc.Type == ChannelType.Text || xc.Type == ChannelType.News)).OrderBy(xc => xc.Position).ToArray().First().Position
: this.Type == ChannelType.Voice || this.Type == ChannelType.Stage
? channels.Where(xc => xc.ParentId == this.ParentId && (xc.Type == ChannelType.Voice || xc.Type == ChannelType.Stage)).OrderBy(xc => xc.Position).ToArray().First().Position
: channels.Where(xc => xc.ParentId == this.ParentId && xc.Type == this.Type).OrderBy(xc => xc.Position).ToArray().First().Position
: channels.Where(xc => xc.ParentId == null && xc.Type == this.Type).OrderBy(xc => xc.Position).ToArray().First().Position;
}
///
/// Gets whether this channel is a DM channel.
///
[JsonIgnore]
public bool IsPrivate
=> this.Type == ChannelType.Private || this.Type == ChannelType.Group;
///
/// Gets whether this channel is a channel category.
///
[JsonIgnore]
public bool IsCategory
=> this.Type == ChannelType.Category;
///
/// Gets whether this channel is a stage channel.
///
[JsonIgnore]
public bool IsStage
=> this.Type == ChannelType.Stage;
///
/// Gets the guild to which this channel belongs.
///
[JsonIgnore]
public DiscordGuild Guild
=> this.GuildId.HasValue && this.Discord.Guilds.TryGetValue(this.GuildId.Value, out var guild) ? guild : null;
///
/// Gets a collection of permission overwrites for this channel.
///
[JsonIgnore]
public IReadOnlyList PermissionOverwrites
=> this._permissionOverwritesLazy.Value;
[JsonProperty("permission_overwrites", NullValueHandling = NullValueHandling.Ignore)]
internal List _permissionOverwrites = new();
[JsonIgnore]
private readonly Lazy> _permissionOverwritesLazy;
///
/// Gets the channel's topic. This is applicable to text channels only.
///
[JsonProperty("topic", NullValueHandling = NullValueHandling.Ignore)]
public string Topic { get; internal set; }
///
/// Gets the ID of the last message sent in this channel. This is applicable to text channels only.
///
[JsonProperty("last_message_id", NullValueHandling = NullValueHandling.Ignore)]
public ulong? LastMessageId { get; internal set; }
///
/// Gets this channel's bitrate. This is applicable to voice channels only.
///
[JsonProperty("bitrate", NullValueHandling = NullValueHandling.Ignore)]
public int? Bitrate { get; internal set; }
///
/// Gets this channel's user limit. This is applicable to voice channels only.
///
[JsonProperty("user_limit", NullValueHandling = NullValueHandling.Ignore)]
public int? UserLimit { get; internal set; }
///
/// Gets the slow mode delay configured for this channel.
/// All bots, as well as users with or permissions in the channel are exempt from slow mode.
///
[JsonProperty("rate_limit_per_user")]
public int? PerUserRateLimit { get; internal set; }
///
/// Gets this channel's video quality mode. This is applicable to voice channels only.
///
[JsonProperty("video_quality_mode", NullValueHandling = NullValueHandling.Ignore)]
public VideoQualityMode? QualityMode { get; internal set; }
///
/// Gets when the last pinned message was pinned.
///
[JsonIgnore]
public DateTimeOffset? LastPinTimestamp
=> !string.IsNullOrWhiteSpace(this.LastPinTimestampRaw) && DateTimeOffset.TryParse(this.LastPinTimestampRaw, CultureInfo.InvariantCulture, DateTimeStyles.None, out var dto) ?
dto : null;
///
/// Gets when the last pinned message was pinned as raw string.
///
[JsonProperty("last_pin_timestamp", NullValueHandling = NullValueHandling.Ignore)]
internal string LastPinTimestampRaw { get; set; }
///
/// Gets this channel's default duration for newly created threads, in minutes, to automatically archive the thread after recent activity.
///
[JsonProperty("default_auto_archive_duration", NullValueHandling = NullValueHandling.Ignore)]
public ThreadAutoArchiveDuration? DefaultAutoArchiveDuration { get; internal set; }
///
/// Gets this channel's mention string.
///
[JsonIgnore]
public string Mention
=> Formatter.Mention(this);
///
/// Gets this channel's children. This applies only to channel categories.
///
[JsonIgnore]
public IReadOnlyList Children
{
get
{
return !this.IsCategory
? throw new ArgumentException("Only channel categories contain children.")
: this.Guild._channels.Values.Where(e => e.ParentId == this.Id).ToList();
}
}
///
/// Gets the list of members currently in the channel (if voice channel), or members who can see the channel (otherwise).
///
[JsonIgnore]
public virtual IReadOnlyList Users
{
get
{
return this.Guild == null
? throw new InvalidOperationException("Cannot query users outside of guild channels.")
: this.IsVoiceJoinable()
? this.Guild.Members.Values.Where(x => x.VoiceState?.ChannelId == this.Id).ToList()
: this.Guild.Members.Values.Where(x => (this.PermissionsFor(x) & Permissions.AccessChannels) == Permissions.AccessChannels).ToList();
}
}
///
/// Gets whether this channel is an NSFW channel.
///
[JsonProperty("nsfw")]
public bool IsNSFW { get; internal set; }
///
/// Gets this channel's region id (if voice channel).
///
[JsonProperty("rtc_region", NullValueHandling = NullValueHandling.Ignore)]
internal string RtcRegionId { get; set; }
///
/// Gets this channel's region override (if voice channel).
///
[JsonIgnore]
public DiscordVoiceRegion RtcRegion
=> this.RtcRegionId != null ? this.Discord.VoiceRegions[this.RtcRegionId] : null;
///
/// Only sent on the resolved channels of interaction responses for application commands. Gets the permissions of the user in this channel who invoked the command.
///
[JsonProperty("permissions")]
public Permissions? UserPermissions { get; internal set; }
///
/// Initializes a new instance of the class.
///
internal DiscordChannel()
{
this._permissionOverwritesLazy = new Lazy>(() => new ReadOnlyCollection(this._permissionOverwrites));
}
#region Methods
///
/// Sends a message to this channel.
///
/// Content of the message to send.
/// The sent message.
/// Thrown when the client does not have the permission if TTS is true and if TTS is true.
/// Thrown when the channel does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public Task SendMessageAsync(string content)
{
return !this.IsWriteable()
? throw new ArgumentException("Cannot send a text message to a non-text channel.")
: this.Discord.ApiClient.CreateMessageAsync(this.Id, content, null, sticker: null, replyMessageId: null, mentionReply: false, failOnInvalidReply: false);
}
///
/// Sends a message to this channel.
///
/// Embed to attach to the message.
/// The sent message.
/// Thrown when the client does not have the permission and if TTS is true.
/// Thrown when the channel does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public Task SendMessageAsync(DiscordEmbed embed)
{
return !this.IsWriteable()
? throw new ArgumentException("Cannot send a text message to a non-text channel.")
: this.Discord.ApiClient.CreateMessageAsync(this.Id, null, embed != null ? new[] { embed } : null, sticker: null, replyMessageId: null, mentionReply: false, failOnInvalidReply: false);
}
///
/// Sends a message to this channel.
///
/// Embed to attach to the message.
/// Content of the message to send.
/// The sent message.
/// Thrown when the client does not have the permission if TTS is true and if TTS is true.
/// Thrown when the channel does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public Task SendMessageAsync(string content, DiscordEmbed embed)
{
return !this.IsWriteable()
? throw new ArgumentException("Cannot send a text message to a non-text channel.")
: this.Discord.ApiClient.CreateMessageAsync(this.Id, content, embed != null ? new[] { embed } : null, sticker: null, replyMessageId: null, mentionReply: false, failOnInvalidReply: false);
}
///
/// Sends a message to this channel.
///
/// The builder with all the items to send.
/// The sent message.
/// Thrown when the client does not have the permission TTS is true and if TTS is true.
/// Thrown when the channel does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public Task SendMessageAsync(DiscordMessageBuilder builder)
=> this.Discord.ApiClient.CreateMessageAsync(this.Id, builder);
///
/// Sends a message to this channel.
///
/// The builder with all the items to send.
/// The sent message.
/// Thrown when the client does not have the permission TTS is true and if TTS is true.
/// Thrown when the channel does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public Task SendMessageAsync(Action action)
{
var builder = new DiscordMessageBuilder();
action(builder);
return !this.IsWriteable()
? throw new ArgumentException("Cannot send a text message to a non-text channel.")
: this.Discord.ApiClient.CreateMessageAsync(this.Id, builder);
}
///
/// Deletes a guild channel
///
/// Reason for audit logs.
/// Thrown when the client does not have the permission.
/// Thrown when the channel does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public Task DeleteAsync(string reason = null)
=> this.Discord.ApiClient.DeleteChannelAsync(this.Id, reason);
///
/// Clones this channel. This operation will create a channel with identical settings to this one. Note that this will not copy messages.
///
/// Reason for audit logs.
/// Newly-created channel.
/// Thrown when the client does not have the permission.
/// Thrown when the channel does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public async Task CloneAsync(string reason = null)
{
if (this.Guild == null)
throw new InvalidOperationException("Non-guild channels cannot be cloned.");
var ovrs = new List();
foreach (var ovr in this._permissionOverwrites)
#pragma warning disable CS0618 // Type or member is obsolete
ovrs.Add(await new DiscordOverwriteBuilder().FromAsync(ovr).ConfigureAwait(false));
#pragma warning restore CS0618 // Type or member is obsolete
var bitrate = this.Bitrate;
var userLimit = this.UserLimit;
Optional perUserRateLimit = this.PerUserRateLimit;
if(!this.IsVoiceJoinable())
{
bitrate = null;
userLimit = null;
}
if (this.Type == ChannelType.Stage)
{
userLimit = null;
}
if (!this.IsWriteable())
{
perUserRateLimit = Optional.FromNoValue();
}
return await this.Guild.CreateChannelAsync(this.Name, this.Type, this.Parent, this.Topic, bitrate, userLimit, ovrs, this.IsNSFW, perUserRateLimit, this.QualityMode, reason).ConfigureAwait(false);
}
///
/// Returns a specific message
///
/// The id of the message
/// Thrown when the client does not have the permission.
/// Thrown when the channel does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public async Task GetMessageAsync(ulong id)
{
return this.Discord.Configuration.MessageCacheSize > 0
&& this.Discord is DiscordClient dc
&& dc.MessageCache != null
&& dc.MessageCache.TryGet(xm => xm.Id == id && xm.ChannelId == this.Id, out var msg)
? msg
: await this.Discord.ApiClient.GetMessageAsync(this.Id, id).ConfigureAwait(false);
}
///
/// Modifies the current channel.
///
/// Action to perform on this channel
/// Thrown when the client does not have the .
/// Thrown when the client does not have the correct for modifying the .
/// Thrown when the channel does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public Task ModifyAsync(Action action)
{
var mdl = new ChannelEditModel();
action(mdl);
if (mdl.DefaultAutoArchiveDuration.HasValue)
{
if (!Utilities.CheckThreadAutoArchiveDurationFeature(this.Guild, mdl.DefaultAutoArchiveDuration.Value))
throw new NotSupportedException($"Cannot modify DefaultAutoArchiveDuration. Guild needs boost tier {(mdl.DefaultAutoArchiveDuration.Value == ThreadAutoArchiveDuration.ThreeDays ? "one" : "two")}.");
}
if (mdl.Banner.HasValue)
{
if (!this.Guild.Features.CanSetChannelBanner)
throw new NotSupportedException($"Cannot modify Banner. Guild needs boost tier three.");
}
var bannerb64 = Optional.FromNoValue();
if (mdl.Banner.HasValue && mdl.Banner.Value != null)
using (var imgtool = new ImageTool(mdl.Banner.Value))
bannerb64 = imgtool.GetBase64();
else if (mdl.Banner.HasValue)
bannerb64 = null;
return this.Discord.ApiClient.ModifyChannelAsync(this.Id, mdl.Name, mdl.Position, mdl.Topic, mdl.Nsfw,
mdl.Parent.HasValue ? mdl.Parent.Value?.Id : default(Optional), mdl.Bitrate, mdl.Userlimit, mdl.PerUserRateLimit, mdl.RtcRegion.IfPresent(r => r?.Id),
mdl.QualityMode, mdl.DefaultAutoArchiveDuration, mdl.Type, mdl.PermissionOverwrites, bannerb64, mdl.AuditLogReason);
}
///
/// Updates the channel position when it doesn't have a category.
///
/// Use for moving to other categories.
/// Use to move out of a category.
/// Use for moving within a category.
///
/// Position the channel should be moved to.
/// Reason for audit logs.
/// Thrown when the client does not have the permission.
/// Thrown when the channel does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public Task ModifyPositionAsync(int position, string reason = null)
{
if (this.Guild == null)
throw new ArgumentException("Cannot modify order of non-guild channels.");
if (!this.IsMovable())
throw new NotSupportedException("You can't move this type of channel in categories.");
if (this.ParentId != null)
throw new ArgumentException("Cannot modify order of channels within a category. Use ModifyPositionInCategoryAsync instead.");
var chns = this.Guild._channels.Values.Where(xc => xc.Type == this.Type).OrderBy(xc => xc.Position).ToArray();
var pmds = new RestGuildChannelReorderPayload[chns.Length];
for (var i = 0; i < chns.Length; i++)
{
pmds[i] = new RestGuildChannelReorderPayload
{
ChannelId = chns[i].Id,
};
pmds[i].Position = chns[i].Id == this.Id ? position : chns[i].Position >= position ? chns[i].Position + 1 : chns[i].Position;
}
return this.Discord.ApiClient.ModifyGuildChannelPositionAsync(this.Guild.Id, pmds, reason);
}
///
/// Updates the channel position within it's own category.
///
/// Use for moving to other categories.
/// Use to move out of a category.
/// Use to move channels outside a category.
///
/// The position.
/// The reason.
/// Thrown when the client does not have the permission.
/// Thrown when the channel does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
/// Thrown when is out of range.
/// Thrown when function is called on a channel without a parent channel.
public async Task ModifyPositionInCategoryAsync(int position, string reason = null)
{
//if (this.ParentId == null)
// throw new ArgumentException("You can call this function only on channels in categories.");
if (!this.IsMovableInParent())
throw new NotSupportedException("You can't move this type of channel in categories.");
var isUp = position > this.Position;
var channels = await this.InternalRefreshChannelsAsync();
var chns = this.ParentId != null
? this.Type == ChannelType.Text || this.Type == ChannelType.News
? channels.Where(xc => xc.ParentId == this.ParentId && (xc.Type == ChannelType.Text || xc.Type == ChannelType.News))
: this.Type == ChannelType.Voice || this.Type == ChannelType.Stage
? channels.Where(xc => xc.ParentId == this.ParentId && (xc.Type == ChannelType.Voice || xc.Type == ChannelType.Stage))
: channels.Where(xc => xc.ParentId == this.ParentId && xc.Type == this.Type)
: this.Type == ChannelType.Text || this.Type == ChannelType.News
? channels.Where(xc => xc.ParentId == null && (xc.Type == ChannelType.Text || xc.Type == ChannelType.News))
: this.Type == ChannelType.Voice || this.Type == ChannelType.Stage
? channels.Where(xc => xc.ParentId == null && (xc.Type == ChannelType.Voice || xc.Type == ChannelType.Stage))
: channels.Where(xc => xc.ParentId == null && xc.Type == this.Type);
var ochns = chns.OrderBy(xc => xc.Position).ToArray();
var min = ochns.First().Position;
var max = ochns.Last().Position;
if (position > max || position < min)
throw new IndexOutOfRangeException($"Position is not in range. {position} is {(position > max ? "greater then the maximal" : "lower then the minimal")} position.");
var pmds = new RestGuildChannelReorderPayload[ochns.Length];
for (var i = 0; i < ochns.Length; i++)
{
pmds[i] = new RestGuildChannelReorderPayload
{
ChannelId = ochns[i].Id,
};
if (ochns[i].Id == this.Id)
{
pmds[i].Position = position;
}
else
{
if (isUp)
{
if (ochns[i].Position <= position && ochns[i].Position > this.Position)
{
pmds[i].Position = ochns[i].Position - 1;
}
else if (ochns[i].Position < this.Position || ochns[i].Position > position)
{
pmds[i].Position = ochns[i].Position;
}
}
else
{
if (ochns[i].Position >= position && ochns[i].Position < this.Position)
{
pmds[i].Position = ochns[i].Position + 1;
}
else if (ochns[i].Position > this.Position || ochns[i].Position < position)
{
pmds[i].Position = ochns[i].Position;
}
}
}
}
await this.Discord.ApiClient.ModifyGuildChannelPositionAsync(this.Guild.Id, pmds, reason).ConfigureAwait(false);
}
///
/// Internaly refreshes the channel list.
///
private async Task> InternalRefreshChannelsAsync()
{
await this.RefreshPositionsAsync();
return this.Guild.Channels.Values.ToList().AsReadOnly();
}
///
/// Refreshes the positions.
///
public async Task RefreshPositionsAsync()
{
var channels = await this.Discord.ApiClient.GetGuildChannelsAsync(this.Guild.Id);
this.Guild._channels.Clear();
foreach (var channel in channels.ToList())
{
channel.Discord = this.Discord;
foreach (var xo in channel._permissionOverwrites)
{
xo.Discord = this.Discord;
xo._channel_id = channel.Id;
}
this.Guild._channels[channel.Id] = channel;
}
}
///
/// Updates the channel position within it's own category.
/// Valid modes: '+' or 'down' to move a channel down | '-' or 'up' to move a channel up.
///
/// Use for moving to other categories.
/// Use to move out of a category.
/// Use to move channels outside a category.
///
/// The mode. Valid: '+' or 'down' to move a channel down | '-' or 'up' to move a channel up
/// The position.
/// The reason.
/// Thrown when the client does not have the permission.
/// Thrown when the channel does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
/// Thrown when is out of range.
/// Thrown when function is called on a channel without a parent channel, a wrong mode is givven or given position is zero.
public Task ModifyPositionInCategorySmartAsync(string mode, int position, string reason = null)
{
if (!this.IsMovableInParent())
throw new NotSupportedException("You can't move this type of channel in categories.");
if (mode != "+" && mode != "-" && mode != "down" && mode != "up")
throw new ArgumentException("Error with the selected mode: Valid is '+' or 'down' to move a channel down and '-' or 'up' to move a channel up");
var positive = mode == "+" || mode == "positive" || mode == "down";
var negative = mode == "-" || mode == "negative" || mode == "up";
return positive
? position < this.GetMaxPosition()
? this.ModifyPositionInCategoryAsync(this.Position + position, reason)
: throw new IndexOutOfRangeException($"Position is not in range of category.")
: negative
? position > this.GetMinPosition()
? this.ModifyPositionInCategoryAsync(this.Position - position, reason)
: throw new IndexOutOfRangeException($"Position is not in range of category.")
: throw new ArgumentException("You can only modify with +X or -X. 0 is not valid.");
}
///
/// Updates the channel parent, moving the channel to the bottom of the new category.
///
/// New parent for channel. Will move out of parent if null.
/// Sync permissions with parent. Defaults to null.
/// Reason for audit logs.
/// Thrown when the client does not have the permission.
/// Thrown when the channel does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
#pragma warning disable CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
public Task ModifyParentAsync(DiscordChannel? newParent = null, bool? lock_permissions = null, string reason = null)
#pragma warning restore CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
{
if (this.Guild == null)
throw new ArgumentException("Cannot modify parent of non-guild channels.");
if (!this.IsMovableInParent())
throw new NotSupportedException("You can't move this type of channel in categories.");
if (newParent.Type is not ChannelType.Category)
throw new ArgumentException("Only category type channels can be parents.");
var position = this.Guild._channels.Values.Where(xc => xc.Type == this.Type && xc.ParentId == newParent.Id) // gets list same type channels in parent
.Select(xc => xc.Position).DefaultIfEmpty(-1).Max() + 1; // returns highest position of list +1, default val: 0
var chns = this.Guild._channels.Values.Where(xc => xc.Type == this.Type)
.OrderBy(xc => xc.Position).ToArray();
var pmds = new RestGuildChannelNewParentPayload[chns.Length];
for (var i = 0; i < chns.Length; i++)
{
pmds[i] = new RestGuildChannelNewParentPayload
{
ChannelId = chns[i].Id,
Position = chns[i].Position >= position ? chns[i].Position + 1 : chns[i].Position,
};
if (chns[i].Id == this.Id)
{
pmds[i].Position = position;
pmds[i].ParentId = newParent is not null ? newParent.Id : null;
pmds[i].LockPermissions = lock_permissions;
}
}
return this.Discord.ApiClient.ModifyGuildChannelParentAsync(this.Guild.Id, pmds, reason);
}
///
/// Moves the channel out of a category.
///
/// Reason for audit logs.
/// Thrown when the client does not have the permission.
/// Thrown when the channel does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public Task RemoveParentAsync(string reason = null)
{
if (this.Guild == null)
throw new ArgumentException("Cannot modify parent of non-guild channels.");
if (!this.IsMovableInParent())
throw new NotSupportedException("You can't move this type of channel in categories.");
var position = this.Guild._channels.Values.Where(xc => xc.Type == this.Type && xc.Parent is null) //gets list of same type channels with no parent
.Select(xc => xc.Position).DefaultIfEmpty(-1).Max() + 1; // returns highest position of list +1, default val: 0
var chns = this.Guild._channels.Values.Where(xc => xc.Type == this.Type)
.OrderBy(xc => xc.Position).ToArray();
var pmds = new RestGuildChannelNoParentPayload[chns.Length];
for (var i = 0; i < chns.Length; i++)
{
pmds[i] = new RestGuildChannelNoParentPayload
{
ChannelId = chns[i].Id,
};
if (chns[i].Id == this.Id)
{
pmds[i].Position = 1;
pmds[i].ParentId = null;
}
else
{
pmds[i].Position = chns[i].Position < this.Position ? chns[i].Position + 1 : chns[i].Position;
}
}
return this.Discord.ApiClient.DetachGuildChannelParentAsync(this.Guild.Id, pmds, reason);
}
///
/// Returns a list of messages before a certain message.
/// The amount of messages to fetch.
/// Message to fetch before from.
///
/// Thrown when the client does not have the permission.
/// Thrown when the channel does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public Task> GetMessagesBeforeAsync(ulong before, int limit = 100)
=> this.GetMessagesInternalAsync(limit, before, null, null);
///
/// Returns a list of messages after a certain message.
/// The amount of messages to fetch.
/// Message to fetch after from.
///
/// Thrown when the client does not have the permission.
/// Thrown when the channel does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public Task> GetMessagesAfterAsync(ulong after, int limit = 100)
=> this.GetMessagesInternalAsync(limit, null, after, null);
///
/// Returns a list of messages around a certain message.
/// The amount of messages to fetch.
/// Message to fetch around from.
///
/// Thrown when the client does not have the permission.
/// Thrown when the channel does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public Task> GetMessagesAroundAsync(ulong around, int limit = 100)
=> this.GetMessagesInternalAsync(limit, null, null, around);
///
/// Returns a list of messages from the last message in the channel.
/// The amount of messages to fetch.
///
/// Thrown when the client does not have the permission.
/// Thrown when the channel does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public Task> GetMessagesAsync(int limit = 100) =>
this.GetMessagesInternalAsync(limit, null, null, null);
///
/// Returns a list of messages
///
/// How many messages should be returned.
/// Get messages before snowflake.
/// Get messages after snowflake.
/// Get messages around snowflake.
private async Task> GetMessagesInternalAsync(int limit = 100, ulong? before = null, ulong? after = null, ulong? around = null)
{
if (!this.IsWriteable())
throw new ArgumentException("Cannot get the messages of a non-text channel.");
if (limit < 0)
throw new ArgumentException("Cannot get a negative number of messages.");
if (limit == 0)
return Array.Empty();
//return this.Discord.ApiClient.GetChannelMessagesAsync(this.Id, limit, before, after, around);
if (limit > 100 && around != null)
throw new InvalidOperationException("Cannot get more than 100 messages around the specified ID.");
var msgs = new List(limit);
var remaining = limit;
ulong? last = null;
var isAfter = after != null;
int lastCount;
do
{
var fetchSize = remaining > 100 ? 100 : remaining;
var fetch = await this.Discord.ApiClient.GetChannelMessagesAsync(this.Id, fetchSize, !isAfter ? last ?? before : null, isAfter ? last ?? after : null, around).ConfigureAwait(false);
lastCount = fetch.Count;
remaining -= lastCount;
if (!isAfter)
{
msgs.AddRange(fetch);
last = fetch.LastOrDefault()?.Id;
}
else
{
msgs.InsertRange(0, fetch);
last = fetch.FirstOrDefault()?.Id;
}
}
while (remaining > 0 && lastCount > 0);
return new ReadOnlyCollection(msgs);
}
///
/// Deletes multiple messages if they are less than 14 days old. If they are older, none of the messages will be deleted and you will receive a error.
///
/// A collection of messages to delete.
/// Reason for audit logs.
/// Thrown when the client does not have the permission.
/// Thrown when the channel does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public async Task DeleteMessagesAsync(IEnumerable messages, string reason = null)
{
// don't enumerate more than once
var msgs = messages.Where(x => x.Channel.Id == this.Id).Select(x => x.Id).ToArray();
if (messages == null || !msgs.Any())
throw new ArgumentException("You need to specify at least one message to delete.");
if (msgs.Count() < 2)
{
await this.Discord.ApiClient.DeleteMessageAsync(this.Id, msgs.Single(), reason).ConfigureAwait(false);
return;
}
for (var i = 0; i < msgs.Count(); i += 100)
await this.Discord.ApiClient.DeleteMessagesAsync(this.Id, msgs.Skip(i).Take(100), reason).ConfigureAwait(false);
}
///
/// Deletes a message
///
/// The message to be deleted.
/// Reason for audit logs.
/// Thrown when the client does not have the permission.
/// Thrown when the channel does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public Task DeleteMessageAsync(DiscordMessage message, string reason = null)
=> this.Discord.ApiClient.DeleteMessageAsync(this.Id, message.Id, reason);
///
/// Returns a list of invite objects
///
/// Thrown when the client does not have the permission.
/// Thrown when the channel does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public Task> GetInvitesAsync()
{
return this.Guild == null
? throw new ArgumentException("Cannot get the invites of a channel that does not belong to a guild.")
: this.Discord.ApiClient.GetChannelInvitesAsync(this.Id);
}
///
/// Create a new invite object
///
/// Duration of invite in seconds before expiry, or 0 for never. Defaults to 86400.
/// Max number of uses or 0 for unlimited. Defaults to 0
/// Whether this invite should be temporary. Defaults to false.
/// Whether this invite should be unique. Defaults to false.
/// The target type. Defaults to null.
/// The target activity. Defaults to null.
/// The target user id. Defaults to null.
/// The audit log reason.
/// Thrown when the client does not have the permission.
/// Thrown when the channel does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public Task CreateInviteAsync(int max_age = 86400, int max_uses = 0, bool temporary = false, bool unique = false, TargetType? target_type = null, TargetActivity? target_application = null, ulong? target_user = null, string reason = null)
=> this.Discord.ApiClient.CreateChannelInviteAsync(this.Id, max_age, max_uses, target_type, target_application, target_user, temporary, unique, reason);
#region Stage
///
/// Opens a stage.
///
/// Topic of the stage.
/// Whether @everyone should be notified.
/// Privacy level of the stage (Defaults to .
/// Audit log reason.
/// Stage instance
/// Thrown when the client does not have the permission.
/// Thrown when the channel does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public async Task OpenStageAsync(string topic, bool send_start_notification = false, StagePrivacyLevel privacy_level = StagePrivacyLevel.GuildOnly, string reason = null)
=> await this.Discord.ApiClient.CreateStageInstanceAsync(this.Id, topic, send_start_notification, privacy_level, reason);
///
/// Modifies a stage topic.
///
/// New topic of the stage.
/// New privacy level of the stage.
/// Audit log reason.
/// Thrown when the client does not have the permission.
/// Thrown when the channel does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public async Task ModifyStageAsync(Optional topic, Optional privacy_level, string reason = null)
=> await this.Discord.ApiClient.ModifyStageInstanceAsync(this.Id, topic, privacy_level, reason);
///
/// Closes a stage.
///
/// Audit log reason.
/// Thrown when the client does not have the permission.
/// Thrown when the channel does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public async Task CloseStageAsync(string reason = null)
=> await this.Discord.ApiClient.DeleteStageInstanceAsync(this.Id, reason);
///
/// Gets a stage.
///
/// The requested stage.
/// Thrown when the client does not have the or permission.
/// Thrown when the channel does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public async Task GetStageAsync()
=> await this.Discord.ApiClient.GetStageInstanceAsync(this.Id);
#endregion
#region Threads
///
/// Creates a thread.
/// Depending on whether it is created inside an or an it is either an or an .
/// Depending on whether the is set to it is either an or an (default).
///
/// The name of the thread.
/// till it gets archived. Defaults to .
/// Can be either an , or an .
/// The per user ratelimit, aka slowdown.
/// Audit log reason.
/// The created thread.
/// Thrown when the client does not have the or or if creating a private thread the permission.
/// Thrown when the guild hasn't enabled threads atm.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
/// Thrown when the cannot be modified. This happens, when the guild hasn't reached a certain boost . Or if is not enabled for guild. This happens, if the guild does not have
public async Task CreateThreadAsync(string name, ThreadAutoArchiveDuration auto_archive_duration = ThreadAutoArchiveDuration.OneHour, ChannelType type = ChannelType.PublicThread, int? rate_limit_per_user = null, string reason = null)
{
return (type != ChannelType.NewsThread && type != ChannelType.PublicThread && type != ChannelType.PrivateThread)
? throw new NotSupportedException("Wrong thread type given.")
: (!this.IsThreadHolder())
? throw new NotSupportedException("Parent channel can't have threads")
: type == ChannelType.PrivateThread
? Utilities.CheckThreadPrivateFeature(this.Guild)
? Utilities.CheckThreadAutoArchiveDurationFeature(this.Guild, auto_archive_duration)
? await this.Discord.ApiClient.CreateThreadWithoutMessageAsync(this.Id, name, auto_archive_duration, type, rate_limit_per_user, reason)
: throw new NotSupportedException($"Cannot modify ThreadAutoArchiveDuration. Guild needs boost tier {(auto_archive_duration == ThreadAutoArchiveDuration.ThreeDays ? "one" : "two")}.")
: throw new NotSupportedException($"Cannot create a private thread. Guild needs to be boost tier two.")
: Utilities.CheckThreadAutoArchiveDurationFeature(this.Guild, auto_archive_duration)
? await this.Discord.ApiClient.CreateThreadWithoutMessageAsync(this.Id, name, auto_archive_duration, this.Type == ChannelType.News ? ChannelType.NewsThread : ChannelType.PublicThread, rate_limit_per_user, reason)
: throw new NotSupportedException($"Cannot modify ThreadAutoArchiveDuration. Guild needs boost tier {(auto_archive_duration == ThreadAutoArchiveDuration.ThreeDays ? "one" : "two")}.");
}
///
/// Creates a scheduled event based on the channel type.
///
/// The name.
/// The privacy level.
/// The scheduled start time.
/// The description.
+ /// The metadata.
/// The reason.
/// A scheduled event.
/// Thrown when the ressource does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
- public async Task CreateScheduledEventAsync(string name, ScheduledEventPrivacyLevel privacy_level, DateTimeOffset scheduled_start_time, Optional description = default, string reason = null)
+ public async Task CreateScheduledEventAsync(string name, ScheduledEventPrivacyLevel privacy_level, DateTimeOffset scheduled_start_time, string description = null, DiscordScheduledEventEntityMetadata metadata = null, string reason = null)
{
if (this.Type != ChannelType.Voice && this.Type != ChannelType.Stage)
throw new NotSupportedException("Cannot create a scheduled event for this type of channel. Channel type must be either voice or stage.");
var type = this.Type == ChannelType.Voice ? ScheduledEventEntityType.Voice : ScheduledEventEntityType.StageInstance;
- return await this.Guild.CreateScheduledEventAsync(name, privacy_level, scheduled_start_time, null, this, null, description, type, reason);
+ return await this.Guild.CreateScheduledEventAsync(name, privacy_level, scheduled_start_time, null, this, metadata, description, type, reason);
}
///
/// Gets joined archived private threads. Can contain more threads.
/// If the result's value 'HasMore' is true, you need to recall this function to get older threads.
///
/// Get threads created before this thread id.
/// Defines the limit of returned .
/// Thrown when the client does not have the permission.
/// Thrown when the channel does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public async Task GetJoinedPrivateArchivedThreadsAsync(ulong? before, int? limit)
=> await this.Discord.ApiClient.GetJoinedPrivateArchivedThreadsAsync(this.Id, before, limit);
///
/// Gets archived public threads. Can contain more threads.
/// If the result's value 'HasMore' is true, you need to recall this function to get older threads.
///
/// Get threads created before this thread id.
/// Defines the limit of returned .
/// Thrown when the client does not have the permission.
/// Thrown when the channel does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public async Task GetPublicArchivedThreadsAsync(ulong? before, int? limit)
=> await this.Discord.ApiClient.GetPublicArchivedThreadsAsync(this.Id, before, limit);
///
/// Gets archived private threads. Can contain more threads.
/// If the result's value 'HasMore' is true, you need to recall this function to get older threads.
///
/// Get threads created before this thread id.
/// Defines the limit of returned .
/// Thrown when the client does not have the or permission.
/// Thrown when the channel does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public async Task GetPrivateArchivedThreadsAsync(ulong? before, int? limit)
=> await this.Discord.ApiClient.GetPrivateArchivedThreadsAsync(this.Id, before, limit);
#endregion
///
/// Adds a channel permission overwrite for specified role.
///
/// The role to have the permission added.
/// The permissions to allow.
/// The permissions to deny.
/// Reason for audit logs.
/// Thrown when the client does not have the permission.
/// Thrown when the channel does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public Task AddOverwriteAsync(DiscordRole role, Permissions allow = Permissions.None, Permissions deny = Permissions.None, string reason = null)
=> this.Discord.ApiClient.EditChannelPermissionsAsync(this.Id, role.Id, allow, deny, "role", reason);
///
/// Adds a channel permission overwrite for specified member.
///
/// The member to have the permission added.
/// The permissions to allow.
/// The permissions to deny.
/// Reason for audit logs.
/// Thrown when the client does not have the permission.
/// Thrown when the channel does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public Task AddOverwriteAsync(DiscordMember member, Permissions allow = Permissions.None, Permissions deny = Permissions.None, string reason = null)
=> this.Discord.ApiClient.EditChannelPermissionsAsync(this.Id, member.Id, allow, deny, "member", reason);
///
/// Deletes a channel permission overwrite for specified member.
///
/// The member to have the permission deleted.
/// Reason for audit logs.
/// Thrown when the client does not have the permission.
/// Thrown when the channel does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public Task DeleteOverwriteAsync(DiscordMember member, string reason = null)
=> this.Discord.ApiClient.DeleteChannelPermissionAsync(this.Id, member.Id, reason);
///
/// Deletes a channel permission overwrite for specified role.
///
/// The role to have the permission deleted.
/// Reason for audit logs.
/// Thrown when the client does not have the permission.
/// Thrown when the channel does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public Task DeleteOverwriteAsync(DiscordRole role, string reason = null)
=> this.Discord.ApiClient.DeleteChannelPermissionAsync(this.Id, role.Id, reason);
///
/// Post a typing indicator
///
/// Thrown when the channel does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public Task TriggerTypingAsync()
{
return !this.IsWriteable()
? throw new ArgumentException("Cannot start typing in a non-text channel.")
: this.Discord.ApiClient.TriggerTypingAsync(this.Id);
}
///
/// Returns all pinned messages
///
/// Thrown when the client does not have the permission.
/// Thrown when the channel does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public Task> GetPinnedMessagesAsync()
{
return !this.IsWriteable()
? throw new ArgumentException("A non-text channel does not have pinned messages.")
: this.Discord.ApiClient.GetPinnedMessagesAsync(this.Id);
}
///
/// Create a new webhook
///
/// The name of the webhook.
/// The image for the default webhook avatar.
/// Reason for audit logs.
/// Thrown when the client does not have the permission.
/// Thrown when the channel does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public async Task CreateWebhookAsync(string name, Optional avatar = default, string reason = null)
{
var av64 = Optional.FromNoValue();
if (avatar.HasValue && avatar.Value != null)
using (var imgtool = new ImageTool(avatar.Value))
av64 = imgtool.GetBase64();
else if (avatar.HasValue)
av64 = null;
return await this.Discord.ApiClient.CreateWebhookAsync(this.Id, name, av64, reason).ConfigureAwait(false);
}
///
/// Returns a list of webhooks
///
/// Thrown when the client does not have the permission.
/// Thrown when the channel does not exist.
/// Thrown when Discord is unable to process the request.
public Task> GetWebhooksAsync()
=> this.Discord.ApiClient.GetChannelWebhooksAsync(this.Id);
///
/// Moves a member to this voice channel
///
/// The member to be moved.
/// Thrown when the client does not have the permission.
/// Thrown when the channel does not exists or if the Member does not exists.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public async Task PlaceMemberAsync(DiscordMember member)
{
if (!this.IsVoiceJoinable())
throw new ArgumentException("Cannot place a member in a non-voice channel.");
await this.Discord.ApiClient.ModifyGuildMemberAsync(this.Guild.Id, member.Id, default, default, default,
default, this.Id, null).ConfigureAwait(false);
}
///
/// Follows a news channel
///
/// Channel to crosspost messages to
/// Thrown when trying to follow a non-news channel
/// Thrown when the current user doesn't have on the target channel
public Task FollowAsync(DiscordChannel targetChannel)
{
return this.Type != ChannelType.News
? throw new ArgumentException("Cannot follow a non-news channel.")
: this.Discord.ApiClient.FollowChannelAsync(this.Id, targetChannel.Id);
}
///
/// Publishes a message in a news channel to following channels
///
/// Message to publish
/// Thrown when the message has already been crossposted
///
/// Thrown when the current user doesn't have and/or
///
public Task CrosspostMessageAsync(DiscordMessage message)
{
return (message.Flags & MessageFlags.Crossposted) == MessageFlags.Crossposted
? throw new ArgumentException("Message is already crossposted.")
: this.Discord.ApiClient.CrosspostMessageAsync(this.Id, message.Id);
}
///
/// Updates the current user's suppress state in this channel, if stage channel.
///
/// Toggles the suppress state.
/// Sets the time the user requested to speak.
/// Thrown when the channel is not a stage channel.
public async Task UpdateCurrentUserVoiceStateAsync(bool? suppress, DateTimeOffset? requestToSpeakTimestamp = null)
{
if (this.Type != ChannelType.Stage)
throw new ArgumentException("Voice state can only be updated in a stage channel.");
await this.Discord.ApiClient.UpdateCurrentUserVoiceStateAsync(this.GuildId.Value, this.Id, suppress, requestToSpeakTimestamp).ConfigureAwait(false);
}
///
/// Calculates permissions for a given member.
///
/// Member to calculate permissions for.
/// Calculated permissions for a given member.
public Permissions PermissionsFor(DiscordMember mbr)
{
// future note: might be able to simplify @everyone role checks to just check any role ... but I'm not sure
// xoxo, ~uwx
//
// you should use a single tilde
// ~emzi
// user > role > everyone
// allow > deny > undefined
// =>
// user allow > user deny > role allow > role deny > everyone allow > everyone deny
// thanks to meew0
if (this.IsPrivate || this.Guild == null)
return Permissions.None;
if (this.Guild.OwnerId == mbr.Id)
return PermissionMethods.FULL_PERMS;
Permissions perms;
// assign @everyone permissions
var everyoneRole = this.Guild.EveryoneRole;
perms = everyoneRole.Permissions;
// roles that member is in
var mbRoles = mbr.Roles.Where(xr => xr.Id != everyoneRole.Id).ToArray();
// assign permissions from member's roles (in order)
perms |= mbRoles.Aggregate(Permissions.None, (c, role) => c | role.Permissions);
// Adminstrator grants all permissions and cannot be overridden
if ((perms & Permissions.Administrator) == Permissions.Administrator)
return PermissionMethods.FULL_PERMS;
// channel overrides for roles that member is in
var mbRoleOverrides = mbRoles
.Select(xr => this._permissionOverwrites.FirstOrDefault(xo => xo.Id == xr.Id))
.Where(xo => xo != null)
.ToList();
// assign channel permission overwrites for @everyone pseudo-role
var everyoneOverwrites = this._permissionOverwrites.FirstOrDefault(xo => xo.Id == everyoneRole.Id);
if (everyoneOverwrites != null)
{
perms &= ~everyoneOverwrites.Denied;
perms |= everyoneOverwrites.Allowed;
}
// assign channel permission overwrites for member's roles (explicit deny)
perms &= ~mbRoleOverrides.Aggregate(Permissions.None, (c, overs) => c | overs.Denied);
// assign channel permission overwrites for member's roles (explicit allow)
perms |= mbRoleOverrides.Aggregate(Permissions.None, (c, overs) => c | overs.Allowed);
// channel overrides for just this member
var mbOverrides = this._permissionOverwrites.FirstOrDefault(xo => xo.Id == mbr.Id);
if (mbOverrides == null) return perms;
// assign channel permission overwrites for just this member
perms &= ~mbOverrides.Denied;
perms |= mbOverrides.Allowed;
return perms;
}
///
/// Returns a string representation of this channel.
///
/// String representation of this channel.
public override string ToString()
{
return this.Type == ChannelType.Category
? $"Channel Category {this.Name} ({this.Id})"
: this.Type == ChannelType.Text || this.Type == ChannelType.News || this.IsThread()
? $"Channel #{this.Name} ({this.Id})"
: this.IsVoiceJoinable()
? $"Channel #!{this.Name} ({this.Id})"
: !string.IsNullOrWhiteSpace(this.Name) ? $"Channel {this.Name} ({this.Id})" : $"Channel {this.Id}";
}
#endregion
///
/// Checks whether this is equal to another object.
///
/// Object to compare to.
/// Whether the object is equal to this .
public override bool Equals(object obj) => this.Equals(obj as DiscordChannel);
///
/// Checks whether this is equal to another .
///
/// to compare to.
/// Whether the is equal to this .
public bool Equals(DiscordChannel e) => e is not null && (ReferenceEquals(this, e) || this.Id == e.Id);
///
/// Gets the hash code for this .
///
/// The hash code for this .
public override int GetHashCode() => this.Id.GetHashCode();
///
/// Gets whether the two objects are equal.
///
/// First channel to compare.
/// Second channel to compare.
/// Whether the two channels are equal.
public static bool operator ==(DiscordChannel e1, DiscordChannel e2)
{
var o1 = e1 as object;
var o2 = e2 as object;
return (o1 != null || o2 == null) && (o1 == null || o2 != null) && ((o1 == null && o2 == null) || e1.Id == e2.Id);
}
///
/// Gets whether the two objects are not equal.
///
/// First channel to compare.
/// Second channel to compare.
/// Whether the two channels are not equal.
public static bool operator !=(DiscordChannel e1, DiscordChannel e2)
=> !(e1 == e2);
}
}
diff --git a/DisCatSharp/Entities/Guild/DiscordGuild.cs b/DisCatSharp/Entities/Guild/DiscordGuild.cs
index 7ee3d6a98..f49768205 100644
--- a/DisCatSharp/Entities/Guild/DiscordGuild.cs
+++ b/DisCatSharp/Entities/Guild/DiscordGuild.cs
@@ -1,3472 +1,3472 @@
// This file is part of the DisCatSharp project.
//
// Copyright (c) 2021 AITSYS
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#pragma warning disable CS0618
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using DisCatSharp.Enums;
using DisCatSharp.EventArgs;
using DisCatSharp.Exceptions;
using DisCatSharp.Net;
using DisCatSharp.Net.Abstractions;
using DisCatSharp.Net.Models;
using DisCatSharp.Net.Serialization;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace DisCatSharp.Entities
{
///
/// Represents a Discord guild.
///
public class DiscordGuild : SnowflakeObject, IEquatable
{
///
/// Gets the guild's name.
///
[JsonProperty("name", NullValueHandling = NullValueHandling.Ignore)]
public string Name { get; internal set; }
///
/// Gets the guild icon's hash.
///
[JsonProperty("icon", NullValueHandling = NullValueHandling.Ignore)]
public string IconHash { get; internal set; }
///
/// Gets the guild icon's url.
///
[JsonIgnore]
public string IconUrl
=> !string.IsNullOrWhiteSpace(this.IconHash) ? $"{DiscordDomain.GetDomain(CoreDomain.DiscordCdn).Url}{Endpoints.ICONS}/{this.Id.ToString(CultureInfo.InvariantCulture)}/{this.IconHash}.{(this.IconHash.StartsWith("a_") ? "gif" : "png")}?size=1024" : null;
///
/// Gets the guild splash's hash.
///
[JsonProperty("splash", NullValueHandling = NullValueHandling.Ignore)]
public string SplashHash { get; internal set; }
///
/// Gets the guild splash's url.
///
[JsonIgnore]
public string SplashUrl
=> !string.IsNullOrWhiteSpace(this.SplashHash) ? $"{DiscordDomain.GetDomain(CoreDomain.DiscordCdn).Url}{Endpoints.SPLASHES}/{this.Id.ToString(CultureInfo.InvariantCulture)}/{this.SplashHash}.png?size=1024" : null;
///
/// Gets the guild discovery splash's hash.
///
[JsonProperty("discovery_splash", NullValueHandling = NullValueHandling.Ignore)]
public string DiscoverySplashHash { get; internal set; }
///
/// Gets the guild discovery splash's url.
///
[JsonIgnore]
public string DiscoverySplashUrl
=> !string.IsNullOrWhiteSpace(this.DiscoverySplashHash) ? $"{DiscordDomain.GetDomain(CoreDomain.DiscordCdn).Url}{Endpoints.GUILD_DISCOVERY_SPLASHES}/{this.Id.ToString(CultureInfo.InvariantCulture)}/{this.DiscoverySplashHash}.png?size=1024" : null;
///
/// Gets the preferred locale of this guild.
/// This is used for server discovery and notices from Discord. Defaults to en-US.
///
[JsonProperty("preferred_locale", NullValueHandling = NullValueHandling.Ignore)]
public string PreferredLocale { get; internal set; }
///
/// Gets the ID of the guild's owner.
///
[JsonProperty("owner_id", NullValueHandling = NullValueHandling.Ignore)]
public ulong OwnerId { get; internal set; }
///
/// Gets the guild's owner.
///
[JsonIgnore]
public DiscordMember Owner
=> this.Members.TryGetValue(this.OwnerId, out var owner)
? owner
: this.Discord.ApiClient.GetGuildMemberAsync(this.Id, this.OwnerId).ConfigureAwait(false).GetAwaiter().GetResult();
///
/// Gets permissions for the user in the guild (does not include channel overrides)
///
[JsonProperty("permissions", NullValueHandling = NullValueHandling.Ignore)]
public Permissions? Permissions { get; set; }
///
/// Gets the guild's voice region ID.
///
[JsonProperty("region", NullValueHandling = NullValueHandling.Ignore)]
internal string VoiceRegionId { get; set; }
///
/// Gets the guild's voice region.
///
[JsonIgnore]
public DiscordVoiceRegion VoiceRegion
=> this.Discord.VoiceRegions[this.VoiceRegionId];
///
/// Gets the guild's AFK voice channel ID.
///
[JsonProperty("afk_channel_id", NullValueHandling = NullValueHandling.Ignore)]
internal ulong AfkChannelId { get; set; } = 0;
///
/// Gets the guild's AFK voice channel.
///
[JsonIgnore]
public DiscordChannel AfkChannel
=> this.GetChannel(this.AfkChannelId);
///
/// Gets the guild's AFK timeout.
///
[JsonProperty("afk_timeout", NullValueHandling = NullValueHandling.Ignore)]
public int AfkTimeout { get; internal set; }
///
/// Gets the guild's verification level.
///
[JsonProperty("verification_level", NullValueHandling = NullValueHandling.Ignore)]
public VerificationLevel VerificationLevel { get; internal set; }
///
/// Gets the guild's default notification settings.
///
[JsonProperty("default_message_notifications", NullValueHandling = NullValueHandling.Ignore)]
public DefaultMessageNotifications DefaultMessageNotifications { get; internal set; }
///
/// Gets the guild's explicit content filter settings.
///
[JsonProperty("explicit_content_filter")]
public ExplicitContentFilter ExplicitContentFilter { get; internal set; }
///
/// Gets the guild's nsfw level.
///
[JsonProperty("nsfw_level")]
public NsfwLevel NsfwLevel { get; internal set; }
///
/// Gets the system channel id.
///
[JsonProperty("system_channel_id", NullValueHandling = NullValueHandling.Include)]
internal ulong? SystemChannelId { get; set; }
///
/// Gets the channel where system messages (such as boost and welcome messages) are sent.
///
[JsonIgnore]
public DiscordChannel SystemChannel => this.SystemChannelId.HasValue
? this.GetChannel(this.SystemChannelId.Value)
: null;
///
/// Gets the settings for this guild's system channel.
///
[JsonProperty("system_channel_flags")]
public SystemChannelFlags SystemChannelFlags { get; internal set; }
///
/// Gets whether this guild's widget is enabled.
///
[JsonProperty("widget_enabled", NullValueHandling = NullValueHandling.Ignore)]
public bool? WidgetEnabled { get; internal set; }
///
/// Gets the widget channel id.
///
[JsonProperty("widget_channel_id", NullValueHandling = NullValueHandling.Ignore)]
internal ulong? WidgetChannelId { get; set; }
///
/// Gets the widget channel for this guild.
///
[JsonIgnore]
public DiscordChannel WidgetChannel => this.WidgetChannelId.HasValue
? this.GetChannel(this.WidgetChannelId.Value)
: null;
///
/// Gets the rules channel id.
///
[JsonProperty("rules_channel_id")]
internal ulong? RulesChannelId { get; set; }
///
/// Gets the rules channel for this guild.
/// This is only available if the guild is considered "discoverable".
///
[JsonIgnore]
public DiscordChannel RulesChannel => this.RulesChannelId.HasValue
? this.GetChannel(this.RulesChannelId.Value)
: null;
///
/// Gets the public updates channel id.
///
[JsonProperty("public_updates_channel_id")]
internal ulong? PublicUpdatesChannelId { get; set; }
///
/// Gets the public updates channel (where admins and moderators receive messages from Discord) for this guild.
/// This is only available if the guild is considered "discoverable".
///
[JsonIgnore]
public DiscordChannel PublicUpdatesChannel => this.PublicUpdatesChannelId.HasValue
? this.GetChannel(this.PublicUpdatesChannelId.Value)
: null;
///
/// Gets the application id of this guild if it is bot created.
///
[JsonProperty("application_id")]
public ulong? ApplicationId { get; internal set; }
///
/// Gets a collection of this guild's roles.
///
[JsonIgnore]
public IReadOnlyDictionary Roles => new ReadOnlyConcurrentDictionary(this._roles);
[JsonProperty("roles", NullValueHandling = NullValueHandling.Ignore)]
[JsonConverter(typeof(SnowflakeArrayAsDictionaryJsonConverter))]
internal ConcurrentDictionary _roles;
///
/// Gets a collection of this guild's stickers.
///
[JsonIgnore]
public IReadOnlyDictionary Stickers => new ReadOnlyConcurrentDictionary(this._stickers);
[JsonProperty("stickers", NullValueHandling = NullValueHandling.Ignore)]
[JsonConverter(typeof(SnowflakeArrayAsDictionaryJsonConverter))]
internal ConcurrentDictionary _stickers;
///
/// Gets a collection of this guild's emojis.
///
[JsonIgnore]
public IReadOnlyDictionary Emojis => new ReadOnlyConcurrentDictionary(this._emojis);
[JsonProperty("emojis", NullValueHandling = NullValueHandling.Ignore)]
[JsonConverter(typeof(SnowflakeArrayAsDictionaryJsonConverter))]
internal ConcurrentDictionary _emojis;
///
/// Gets a collection of this guild's features.
///
[JsonProperty("features", NullValueHandling = NullValueHandling.Ignore)]
public IReadOnlyList RawFeatures { get; internal set; }
///
/// Gets the guild's features.
///
[JsonIgnore]
public GuildFeatures Features => new(this);
///
/// Gets the required multi-factor authentication level for this guild.
///
[JsonProperty("mfa_level", NullValueHandling = NullValueHandling.Ignore)]
public MfaLevel MfaLevel { get; internal set; }
///
/// Gets this guild's join date.
///
[JsonProperty("joined_at", NullValueHandling = NullValueHandling.Ignore)]
public DateTimeOffset JoinedAt { get; internal set; }
///
/// Gets whether this guild is considered to be a large guild.
///
[JsonProperty("large", NullValueHandling = NullValueHandling.Ignore)]
public bool IsLarge { get; internal set; }
///
/// Gets whether this guild is unavailable.
///
[JsonProperty("unavailable", NullValueHandling = NullValueHandling.Ignore)]
public bool IsUnavailable { get; internal set; }
///
/// Gets the total number of members in this guild.
///
[JsonProperty("member_count", NullValueHandling = NullValueHandling.Ignore)]
public int MemberCount { get; internal set; }
///
/// Gets the maximum amount of members allowed for this guild.
///
[JsonProperty("max_members")]
public int? MaxMembers { get; internal set; }
///
/// Gets the maximum amount of presences allowed for this guild.
///
[JsonProperty("max_presences")]
public int? MaxPresences { get; internal set; }
#pragma warning disable CS1734
///
/// Gets the approximate number of members in this guild, when using and having set to true.
///
[JsonProperty("approximate_member_count", NullValueHandling = NullValueHandling.Ignore)]
public int? ApproximateMemberCount { get; internal set; }
///
/// Gets the approximate number of presences in this guild, when using and having set to true.
///
[JsonProperty("approximate_presence_count", NullValueHandling = NullValueHandling.Ignore)]
public int? ApproximatePresenceCount { get; internal set; }
#pragma warning restore CS1734
///
/// Gets the maximum amount of users allowed per video channel.
///
[JsonProperty("max_video_channel_users", NullValueHandling = NullValueHandling.Ignore)]
public int? MaxVideoChannelUsers { get; internal set; }
///
/// Gets a dictionary of all the voice states for this guilds. The key for this dictionary is the ID of the user
/// the voice state corresponds to.
///
[JsonIgnore]
public IReadOnlyDictionary VoiceStates => new ReadOnlyConcurrentDictionary(this._voiceStates);
[JsonProperty("voice_states", NullValueHandling = NullValueHandling.Ignore)]
[JsonConverter(typeof(SnowflakeArrayAsDictionaryJsonConverter))]
internal ConcurrentDictionary _voiceStates;
///
/// Gets a dictionary of all the members that belong to this guild. The dictionary's key is the member ID.
///
[JsonIgnore] // TODO overhead of => vs Lazy? it's a struct
public IReadOnlyDictionary Members => new ReadOnlyConcurrentDictionary(this._members);
[JsonProperty("members", NullValueHandling = NullValueHandling.Ignore)]
[JsonConverter(typeof(SnowflakeArrayAsDictionaryJsonConverter))]
internal ConcurrentDictionary _members;
///
/// Gets a dictionary of all the channels associated with this guild. The dictionary's key is the channel ID.
///
[JsonIgnore]
public IReadOnlyDictionary Channels => new ReadOnlyConcurrentDictionary(this._channels);
[JsonProperty("channels", NullValueHandling = NullValueHandling.Ignore)]
[JsonConverter(typeof(SnowflakeArrayAsDictionaryJsonConverter))]
internal ConcurrentDictionary _channels;
internal ConcurrentDictionary _invites;
///
/// Gets a dictionary of all the active threads associated with this guild the user has permission to view. The dictionary's key is the channel ID.
///
[JsonIgnore]
public IReadOnlyDictionary Threads { get; internal set; }
[JsonProperty("threads", NullValueHandling = NullValueHandling.Ignore)]
[JsonConverter(typeof(SnowflakeArrayAsDictionaryJsonConverter))]
internal ConcurrentDictionary _threads = new();
///
/// Gets a dictionary of all active stage instances. The dictionary's key is the stage ID.
///
[JsonIgnore]
public IReadOnlyDictionary StageInstances { get; internal set; }
[JsonProperty("stage_instances", NullValueHandling = NullValueHandling.Ignore)]
[JsonConverter(typeof(SnowflakeArrayAsDictionaryJsonConverter))]
internal ConcurrentDictionary _stageInstances = new();
///
/// Gets a dictionary of all scheduled events.
///
[JsonIgnore]
public IReadOnlyDictionary ScheduledEvents { get; internal set; }
[JsonProperty("guild_scheduled_events", NullValueHandling = NullValueHandling.Ignore)]
[JsonConverter(typeof(SnowflakeArrayAsDictionaryJsonConverter))]
internal ConcurrentDictionary _scheduledEvents = new();
///
/// Gets the guild member for current user.
///
[JsonIgnore]
public DiscordMember CurrentMember
=> this._current_member_lazy.Value;
[JsonIgnore]
private readonly Lazy _current_member_lazy;
///
/// Gets the @everyone role for this guild.
///
[JsonIgnore]
public DiscordRole EveryoneRole
=> this.GetRole(this.Id);
[JsonIgnore]
internal bool _isOwner;
///
/// Gets whether the current user is the guild's owner.
///
[JsonProperty("owner", NullValueHandling = NullValueHandling.Ignore)]
public bool IsOwner
{
get => this._isOwner || this.OwnerId == this.Discord.CurrentUser.Id;
internal set => this._isOwner = value;
}
///
/// Gets the vanity URL code for this guild, when applicable.
///
[JsonProperty("vanity_url_code")]
public string VanityUrlCode { get; internal set; }
///
/// Gets the guild description, when applicable.
///
[JsonProperty("description")]
public string Description { get; internal set; }
///
/// Gets this guild's banner hash, when applicable.
///
[JsonProperty("banner")]
public string BannerHash { get; internal set; }
///
/// Gets this guild's banner in url form.
///
[JsonIgnore]
public string BannerUrl
=> !string.IsNullOrWhiteSpace(this.BannerHash) ? $"{DiscordDomain.GetDomain(CoreDomain.DiscordCdn).Uri}{Endpoints.BANNERS}/{this.Id.ToString(CultureInfo.InvariantCulture)}/{this.BannerHash}.{(this.BannerHash.StartsWith("a_") ? "gif" : "png")}" : null;
///
/// Whether this guild has the community feature enabled.
///
[JsonIgnore]
public bool IsCommunity => this.Features.HasCommunityEnabled;
///
/// Whether this guild has enabled the welcome screen.
///
[JsonIgnore]
public bool HasWelcomeScreen => this.Features.HasWelcomeScreenEnabled;
///
/// Whether this guild has enabled membership screening.
///
[JsonIgnore]
public bool HasMemberVerificationGate => this.Features.HasMembershipScreeningEnabled;
///
/// Gets this guild's premium tier (Nitro boosting).
///
[JsonProperty("premium_tier")]
public PremiumTier PremiumTier { get; internal set; }
///
/// Gets the amount of members that boosted this guild.
///
[JsonProperty("premium_subscription_count", NullValueHandling = NullValueHandling.Ignore)]
public int? PremiumSubscriptionCount { get; internal set; }
///
/// Whether the premium progress bar is enabled.
///
[JsonProperty("premium_progress_bar_enabled", NullValueHandling = NullValueHandling.Ignore)]
public bool PremiumProgressBarEnabled { get; internal set; }
///
/// Gets whether this guild is designated as NSFW.
///
[JsonProperty("nsfw", NullValueHandling = NullValueHandling.Ignore)]
public bool IsNSFW { get; internal set; }
///
/// Gets a dictionary of all by position ordered channels associated with this guild. The dictionary's key is the channel ID.
///
[JsonIgnore]
public IReadOnlyDictionary OrderedChannels => new ReadOnlyDictionary(this.InternalSortChannels());
///
/// Sorts the channels.
///
private Dictionary InternalSortChannels()
{
Dictionary keyValuePairs = new();
var ochannels = this.GetOrderedChannels();
foreach (var ochan in ochannels)
{
if (ochan.Key != 0)
keyValuePairs.Add(ochan.Key, this.GetChannel(ochan.Key));
foreach (var chan in ochan.Value)
keyValuePairs.Add(chan.Id, chan);
}
return keyValuePairs;
}
///
/// Gets an ordered list out of the channel cache.
/// Returns a Dictionary where the key is an ulong and can be mapped to s.
/// Ignore the 0 key here, because that indicates that this is the "has no category" list.
/// Each value contains a ordered list of text/news and voice/stage channels as .
///
/// A ordered list of categories with its channels
public Dictionary> GetOrderedChannels()
{
IReadOnlyList raw_channels = this._channels.Values.ToList();
Dictionary> ordered_channels = new();
ordered_channels.Add(0, new List());
foreach (var channel in raw_channels.Where(c => c.Type == ChannelType.Category).OrderBy(c => c.Position))
{
ordered_channels.Add(channel.Id, new List());
}
foreach (var channel in raw_channels.Where(c => c.ParentId.HasValue && (c.Type == ChannelType.Text || c.Type == ChannelType.News)).OrderBy(c => c.Position))
{
ordered_channels[channel.ParentId.Value].Add(channel);
}
foreach (var channel in raw_channels.Where(c => c.ParentId.HasValue && (c.Type == ChannelType.Voice || c.Type == ChannelType.Stage)).OrderBy(c => c.Position))
{
ordered_channels[channel.ParentId.Value].Add(channel);
}
foreach (var channel in raw_channels.Where(c => !c.ParentId.HasValue && c.Type != ChannelType.Category && (c.Type == ChannelType.Text || c.Type == ChannelType.News)).OrderBy(c => c.Position))
{
ordered_channels[0].Add(channel);
}
foreach (var channel in raw_channels.Where(c => !c.ParentId.HasValue && c.Type != ChannelType.Category && (c.Type == ChannelType.Voice || c.Type == ChannelType.Stage)).OrderBy(c => c.Position))
{
ordered_channels[0].Add(channel);
}
return ordered_channels;
}
///
/// Gets an ordered list.
/// Returns a Dictionary where the key is an ulong and can be mapped to s.
/// Ignore the 0 key here, because that indicates that this is the "has no category" list.
/// Each value contains a ordered list of text/news and voice/stage channels as .
///
/// A ordered list of categories with its channels
public async Task>> GetOrderedChannelsAsync()
{
var raw_channels = await this.Discord.ApiClient.GetGuildChannelsAsync(this.Id);
Dictionary> ordered_channels = new();
ordered_channels.Add(0, new List());
foreach (var channel in raw_channels.Where(c => c.Type == ChannelType.Category).OrderBy(c => c.Position))
{
ordered_channels.Add(channel.Id, new List());
}
foreach (var channel in raw_channels.Where(c => c.ParentId.HasValue && (c.Type == ChannelType.Text || c.Type == ChannelType.News)).OrderBy(c => c.Position))
{
ordered_channels[channel.ParentId.Value].Add(channel);
}
foreach (var channel in raw_channels.Where(c => c.ParentId.HasValue && (c.Type == ChannelType.Voice || c.Type == ChannelType.Stage)).OrderBy(c => c.Position))
{
ordered_channels[channel.ParentId.Value].Add(channel);
}
foreach (var channel in raw_channels.Where(c => !c.ParentId.HasValue && c.Type != ChannelType.Category && (c.Type == ChannelType.Text || c.Type == ChannelType.News)).OrderBy(c => c.Position))
{
ordered_channels[0].Add(channel);
}
foreach (var channel in raw_channels.Where(c => !c.ParentId.HasValue && c.Type != ChannelType.Category && (c.Type == ChannelType.Voice || c.Type == ChannelType.Stage)).OrderBy(c => c.Position))
{
ordered_channels[0].Add(channel);
}
return ordered_channels;
}
///
/// Whether it is synced.
///
[JsonIgnore]
internal bool IsSynced { get; set; }
///
/// Initializes a new instance of the class.
///
internal DiscordGuild()
{
this._current_member_lazy = new Lazy(() => (this._members != null && this._members.TryGetValue(this.Discord.CurrentUser.Id, out var member)) ? member : null);
this._invites = new ConcurrentDictionary();
this.Threads = new ReadOnlyConcurrentDictionary(this._threads);
this.StageInstances = new ReadOnlyConcurrentDictionary(this._stageInstances);
this.ScheduledEvents = new ReadOnlyConcurrentDictionary(this._scheduledEvents);
}
#region Guild Methods
///
/// Searches the current guild for members who's display name start with the specified name.
///
/// The name to search for.
/// The maximum amount of members to return. Max 1000. Defaults to 1.
/// The members found, if any.
public Task> SearchMembersAsync(string name, int? limit = 1)
=> this.Discord.ApiClient.SearchMembersAsync(this.Id, name, limit);
///
/// Adds a new member to this guild
///
/// User to add
/// User's access token (OAuth2)
/// new nickname
/// new roles
/// whether this user has to be muted
/// whether this user has to be deafened
///
/// Thrown when the client does not have the permission.
/// Thrown when the or is not found.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public Task AddMemberAsync(DiscordUser user, string access_token, string nickname = null, IEnumerable roles = null,
bool muted = false, bool deaf = false)
=> this.Discord.ApiClient.AddGuildMemberAsync(this.Id, user.Id, access_token, nickname, roles, muted, deaf);
///
/// Deletes this guild. Requires the caller to be the owner of the guild.
///
///
/// Thrown when the client is not the owner of the guild.
/// Thrown when Discord is unable to process the request.
public Task DeleteAsync()
=> this.Discord.ApiClient.DeleteGuildAsync(this.Id);
///
/// Modifies this guild.
///
/// Action to perform on this guild..
/// The modified guild object.
/// Thrown when the client does not have the permission.
/// Thrown when the guild does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public async Task ModifyAsync(Action action)
{
var mdl = new GuildEditModel();
action(mdl);
var afkChannelId = Optional.FromNoValue();
if (mdl.AfkChannel.HasValue && mdl.AfkChannel.Value.Type != ChannelType.Voice && mdl.AfkChannel.Value != null)
throw new ArgumentException("AFK channel needs to be a voice channel.");
else if (mdl.AfkChannel.HasValue && mdl.AfkChannel.Value != null)
afkChannelId = mdl.AfkChannel.Value.Id;
else if (mdl.AfkChannel.HasValue)
afkChannelId = null;
var rulesChannelId = Optional.FromNoValue();
if (mdl.RulesChannel.HasValue && mdl.RulesChannel.Value != null && mdl.RulesChannel.Value.Type != ChannelType.Text && mdl.RulesChannel.Value.Type != ChannelType.News )
throw new ArgumentException("Rules channel needs to be a text channel.");
else if (mdl.RulesChannel.HasValue && mdl.RulesChannel.Value != null)
rulesChannelId = mdl.RulesChannel.Value.Id;
else if (mdl.RulesChannel.HasValue)
rulesChannelId = null;
var publicUpdatesChannelId = Optional.FromNoValue();
if (mdl.PublicUpdatesChannel.HasValue && mdl.PublicUpdatesChannel.Value != null && mdl.PublicUpdatesChannel.Value.Type != ChannelType.Text && mdl.PublicUpdatesChannel.Value.Type != ChannelType.News)
throw new ArgumentException("Public updates channel needs to be a text channel.");
else if (mdl.PublicUpdatesChannel.HasValue && mdl.PublicUpdatesChannel.Value != null)
publicUpdatesChannelId = mdl.PublicUpdatesChannel.Value.Id;
else if (mdl.PublicUpdatesChannel.HasValue)
publicUpdatesChannelId = null;
var systemChannelId = Optional.FromNoValue();
if (mdl.SystemChannel.HasValue && mdl.SystemChannel.Value != null && mdl.SystemChannel.Value.Type != ChannelType.Text && mdl.SystemChannel.Value.Type != ChannelType.News)
throw new ArgumentException("Public updates channel needs to be a text channel.");
else if (mdl.SystemChannel.HasValue && mdl.SystemChannel.Value != null)
systemChannelId = mdl.SystemChannel.Value.Id;
else if (mdl.SystemChannel.HasValue)
systemChannelId = null;
var iconb64 = Optional.FromNoValue();
if (mdl.Icon.HasValue && mdl.Icon.Value != null)
using (var imgtool = new ImageTool(mdl.Icon.Value))
iconb64 = imgtool.GetBase64();
else if (mdl.Icon.HasValue)
iconb64 = null;
var splashb64 = Optional.FromNoValue();
if (mdl.Splash.HasValue && mdl.Splash.Value != null)
using (var imgtool = new ImageTool(mdl.Splash.Value))
splashb64 = imgtool.GetBase64();
else if (mdl.Splash.HasValue)
splashb64 = null;
var bannerb64 = Optional.FromNoValue();
if (mdl.Banner.HasValue && mdl.Banner.Value != null)
using (var imgtool = new ImageTool(mdl.Banner.Value))
bannerb64 = imgtool.GetBase64();
else if (mdl.Banner.HasValue)
bannerb64 = null;
var discoverySplash64 = Optional.FromNoValue();
if (mdl.DiscoverySplash.HasValue && mdl.DiscoverySplash.Value != null)
using (var imgtool = new ImageTool(mdl.DiscoverySplash.Value))
discoverySplash64 = imgtool.GetBase64();
else if (mdl.DiscoverySplash.HasValue)
discoverySplash64 = null;
var description = Optional.FromNoValue();
if (mdl.Description.HasValue && mdl.Description.Value != null)
description = mdl.Description;
else if (mdl.Description.HasValue)
description = null;
return await this.Discord.ApiClient.ModifyGuildAsync(this.Id, mdl.Name,
mdl.VerificationLevel, mdl.DefaultMessageNotifications, mdl.MfaLevel, mdl.ExplicitContentFilter,
afkChannelId, mdl.AfkTimeout, iconb64, mdl.Owner.IfPresent(e => e.Id), splashb64,
systemChannelId, mdl.SystemChannelFlags, publicUpdatesChannelId, rulesChannelId,
description, bannerb64, discoverySplash64, mdl.PreferredLocale, mdl.PremiumProgressBarEnabled, mdl.AuditLogReason).ConfigureAwait(false);
}
///
/// Modifies the community settings async.
/// This sets if not highest and .
///
/// If true, enable .
/// The rules channel.
/// The public updates channel.
/// The preferred locale. Defaults to en-US.
/// The description.
/// The default message notifications. Defaults to
/// The auditlog reason.
/// Thrown when the client does not have the permission.
/// Thrown when the guild does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public async Task ModifyCommunitySettingsAsync(bool enabled, DiscordChannel rulesChannel = null, DiscordChannel publicUpdatesChannel = null, string preferredLocale = "en-US", string description = null, DefaultMessageNotifications defaultMessageNotifications = DefaultMessageNotifications.MentionsOnly, string reason = null)
{
var verificationLevel = this.VerificationLevel;
if(this.VerificationLevel != VerificationLevel.Highest)
{
verificationLevel = VerificationLevel.High;
}
var explicitContentFilter = ExplicitContentFilter.AllMembers;
var rulesChannelId = Optional.FromNoValue();
if (rulesChannel != null && rulesChannel.Type != ChannelType.Text && rulesChannel.Type != ChannelType.News)
throw new ArgumentException("Rules channel needs to be a text channel.");
else if (rulesChannel != null)
rulesChannelId = rulesChannel.Id;
else if (rulesChannel == null)
rulesChannelId = null;
var publicUpdatesChannelId = Optional.FromNoValue();
if (publicUpdatesChannel != null && publicUpdatesChannel.Type != ChannelType.Text && publicUpdatesChannel.Type != ChannelType.News)
throw new ArgumentException("Public updates channel needs to be a text channel.");
else if (publicUpdatesChannel != null)
publicUpdatesChannelId = publicUpdatesChannel.Id;
else if (publicUpdatesChannel == null)
publicUpdatesChannelId = null;
List