diff --git a/DisCatSharp/Entities/Guild/DiscordGuild.Features.cs b/DisCatSharp/Entities/Guild/DiscordGuild.Features.cs
index b3d8f5cef..2ebd593ac 100644
--- a/DisCatSharp/Entities/Guild/DiscordGuild.Features.cs
+++ b/DisCatSharp/Entities/Guild/DiscordGuild.Features.cs
@@ -1,672 +1,672 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
// Copyright (c) 2021-2022 AITSYS
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using DisCatSharp.Attributes;
using DisCatSharp.Enums;
namespace DisCatSharp.Entities;
///
/// Represents the guild features.
///
public class GuildFeatures
{
///
/// List of all guild features.
///
public List Features { get; }
///
/// Checks the guild features and constructs a new object.
///
/// Guild to check
public GuildFeatures(DiscordGuild guild)
{
this.Features = new List();
if (guild.RawFeatures.Contains("APPLICATION_COMMAND_PERMISSIONS_V2")) this.Features.Add(GuildFeaturesEnum.UsesApplicationCommandsPermissionsV2);
if (guild.RawFeatures.Contains("RAID_ALERTS_ENABLED")) this.Features.Add(GuildFeaturesEnum.RaidAlertsEnabled);
if (guild.RawFeatures.Contains("CREATOR_MONETIZABLE_RESTRICTED")) this.Features.Add(GuildFeaturesEnum.CreatorMonetizableRestricted);
if (guild.RawFeatures.Contains("VOICE_IN_THREADS")) this.Features.Add(GuildFeaturesEnum.VoiceInThreadsEnabled);
if (guild.RawFeatures.Contains("CHANNEL_HIGHLIGHTS_DISABLED")) this.Features.Add(GuildFeaturesEnum.ChannelHighlightsDisabled);
if (guild.RawFeatures.Contains("CHANNEL_HIGHLIGHTS")) this.Features.Add(GuildFeaturesEnum.ChannelHighlights);
if (guild.RawFeatures.Contains("GUILD_ONBOARDING_EVER_ENABLED")) this.Features.Add(GuildFeaturesEnum.HadGuildOnBoardingEverEnabled);
if (guild.RawFeatures.Contains("BURST_REACTIONS")) this.Features.Add(GuildFeaturesEnum.CanUseBurstReactions);
if (guild.RawFeatures.Contains("CREATOR_STORE_PAGE")) this.Features.Add(GuildFeaturesEnum.CanUseCreatorStorePage);
if (guild.RawFeatures.Contains("ANIMATED_ICON")) this.Features.Add(GuildFeaturesEnum.CanSetAnimatedIcon);
if (guild.RawFeatures.Contains("ANIMATED_BANNER")) this.Features.Add(GuildFeaturesEnum.CanSetAnimatedBanner);
if (guild.RawFeatures.Contains("BANNER")) this.Features.Add(GuildFeaturesEnum.CanSetBanner);
if (guild.RawFeatures.Contains("COMMUNITY")) this.Features.Add(GuildFeaturesEnum.HasCommunityEnabled);
if (!guild.RawFeatures.Contains("DISCOVERABLE_DISABLED") && guild.RawFeatures.Contains("DISCOVERABLE")) this.Features.Add(GuildFeaturesEnum.IsDiscoverable);
if (guild.RawFeatures.Contains("FEATUREABLE")) this.Features.Add(GuildFeaturesEnum.IsFeatureable);
if (guild.RawFeatures.Contains("INVITE_SPLASH")) this.Features.Add(GuildFeaturesEnum.CanSetInviteSplash);
if (guild.RawFeatures.Contains("MEMBER_VERIFICATION_GATE_ENABLED")) this.Features.Add(GuildFeaturesEnum.HasMembershipScreeningEnabled);
if (guild.RawFeatures.Contains("NEWS")) this.Features.Add(GuildFeaturesEnum.CanCreateNewsChannels);
if (guild.RawFeatures.Contains("PARTNERED")) this.Features.Add(GuildFeaturesEnum.IsPartnered);
if (guild.RawFeatures.Contains("MORE_EMOJI")) this.Features.Add(GuildFeaturesEnum.CanUploadMoreEmojis);
if (guild.RawFeatures.Contains("PREVIEW_ENABLED")) this.Features.Add(GuildFeaturesEnum.HasPreviewEnabled);
if (guild.RawFeatures.Contains("VANITY_URL")) this.Features.Add(GuildFeaturesEnum.CanSetVanityUrl);
if (guild.RawFeatures.Contains("VERIFIED")) this.Features.Add(GuildFeaturesEnum.IsVerified);
if (guild.RawFeatures.Contains("VIP_REGIONS")) this.Features.Add(GuildFeaturesEnum.CanAccessVipRegions);
if (guild.RawFeatures.Contains("WELCOME_SCREEN_ENABLED")) this.Features.Add(GuildFeaturesEnum.HasWelcomeScreenEnabled);
if (guild.RawFeatures.Contains("TICKETED_EVENTS_ENABLED")) this.Features.Add(GuildFeaturesEnum.HasTicketedEventsEnabled);
if (guild.RawFeatures.Contains("MONETIZATION_ENABLED")) this.Features.Add(GuildFeaturesEnum.HasMonetizationEnabled);
if (guild.RawFeatures.Contains("MORE_STICKERS")) this.Features.Add(GuildFeaturesEnum.CanUploadMoreStickers);
if (guild.RawFeatures.Contains("HUB")) this.Features.Add(GuildFeaturesEnum.IsHub);
if (guild.RawFeatures.Contains("THREADS_ENABLED_TESTING")) this.Features.Add(GuildFeaturesEnum.HasThreadTestingEnabled);
if (guild.RawFeatures.Contains("THREADS_ENABLED")) this.Features.Add(GuildFeaturesEnum.HasThreadsEnabled);
if (guild.RawFeatures.Contains("ROLE_ICONS")) this.Features.Add(GuildFeaturesEnum.CanSetRoleIcons);
if (guild.RawFeatures.Contains("NEW_THREAD_PERMISSIONS")) this.Features.Add(GuildFeaturesEnum.HasNewThreadPermissions);
if (guild.RawFeatures.Contains("ROLE_SUBSCRIPTIONS_ENABLED")) this.Features.Add(GuildFeaturesEnum.HasRoleSubscriptionsEnabled);
if (guild.RawFeatures.Contains("PREMIUM_TIER_3_OVERRIDE")) this.Features.Add(GuildFeaturesEnum.PremiumTierThreeOverride);
if (guild.RawFeatures.Contains("THREAD_DEFAULT_AUTO_ARCHIVE_DURATION")) this.Features.Add(GuildFeaturesEnum.CanSetThreadDefaultAutoArchiveDuration);
if (guild.RawFeatures.Contains("TEXT_IN_VOICE_ENABLED")) this.Features.Add(GuildFeaturesEnum.TextInVoiceEnabled);
if (guild.RawFeatures.Contains("HAS_DIRECTORY_ENTRY")) this.Features.Add(GuildFeaturesEnum.HasDirectoryEntry);
if (guild.RawFeatures.Contains("LINKED_TO_HUB")) this.Features.Add(GuildFeaturesEnum.IsLinkedToHub);
if (guild.RawFeatures.Contains("MEMBER_PROFILES")) this.Features.Add(GuildFeaturesEnum.HasMemberProfiles);
if (guild.RawFeatures.Contains("INTERNAL_EMPLOYEE_ONLY")) this.Features.Add(GuildFeaturesEnum.IsStaffOnly);
if (guild.RawFeatures.Contains("ROLE_SUBSCRIPTIONS_AVAILABLE_FOR_PURCHASE")) this.Features.Add(GuildFeaturesEnum.RoleSubscriptionsIsAvailableForPurchase);
if (guild.RawFeatures.Contains("AUTO_MODERATION")) this.Features.Add(GuildFeaturesEnum.CanSetupAutoModeration);
if (guild.RawFeatures.Contains("GUILD_HOME_TEST")) this.Features.Add(GuildFeaturesEnum.GuildHomeTest);
if (guild.RawFeatures.Contains("INVITES_DISABLED")) this.Features.Add(GuildFeaturesEnum.InvitesDisabled);
if (guild.RawFeatures.Contains("ACTIVITIES_ALPHA")) this.Features.Add(GuildFeaturesEnum.ActivitiesAlpha);
if (guild.RawFeatures.Contains("ACTIVITIES_EMPLOYEE")) this.Features.Add(GuildFeaturesEnum.ActivitiesEmployee);
if (guild.RawFeatures.Contains("ACTIVITIES_INTERNAL_DEV")) this.Features.Add(GuildFeaturesEnum.ActivitiesInternalDev);
if (guild.RawFeatures.Contains("AUTOMOD_TRIGGER_KEYWORD_FILTER")) this.Features.Add(GuildFeaturesEnum.AutomodTriggerKeywordFilter);
if (guild.RawFeatures.Contains("AUTOMOD_TRIGGER_ML_SPAM_FILTER")) this.Features.Add(GuildFeaturesEnum.AutomodTriggerMlSpamFilter);
if (guild.RawFeatures.Contains("AUTOMOD_TRIGGER_SPAM_LINK_FILTERGuild")) this.Features.Add(GuildFeaturesEnum.AutomodTriggerSpamLinkFilterGuild);
if (guild.RawFeatures.Contains("AUTOMOD_DEFAULT_LIST")) this.Features.Add(GuildFeaturesEnum.AutomodDefaultList);
if (guild.RawFeatures.Contains("BFG")) this.Features.Add(GuildFeaturesEnum.Bfg);
if (guild.RawFeatures.Contains("BOOSTING_TIERS_EXPERIMENT_MEDIUM_GUILD")) this.Features.Add(GuildFeaturesEnum.BoostingTiersExperimentMediumGuild);
if (guild.RawFeatures.Contains("BOOSTING_TIERS_EXPERIMENT_SMALL_GUILD")) this.Features.Add(GuildFeaturesEnum.BoostingTiersExperimentSmallGuild);
if (guild.RawFeatures.Contains("BOT_DEVELOPER_EARLY_ACCESS")) this.Features.Add(GuildFeaturesEnum.BotDeveloperEarlyAccess);
if (guild.RawFeatures.Contains("CREATOR_MONETIZABLE")) this.Features.Add(GuildFeaturesEnum.CreatorMonetizable);
if (guild.RawFeatures.Contains("CREATOR_MONETIZABLE_DISABLED")) this.Features.Add(GuildFeaturesEnum.CreatorMonetizableDisabled);
if (guild.RawFeatures.Contains("CREATOR_MONETIZABLE_PROVISIONAL")) this.Features.Add(GuildFeaturesEnum.CreatorMonetizableProvisional);
if (guild.RawFeatures.Contains("CREATOR_MONETIZABLE_WHITEGLOVE")) this.Features.Add(GuildFeaturesEnum.CreatorMonetizableWhiteGlove);
if (guild.RawFeatures.Contains("CREATOR_MONETIZATION_APPLICATION_ALLOWLIST")) this.Features.Add(GuildFeaturesEnum.CreatorMonetizationApplicationAllowlist);
if (guild.RawFeatures.Contains("DEVELOPER_SUPPORT_SERVER")) this.Features.Add(GuildFeaturesEnum.DeveloperSupportServer);
if (guild.RawFeatures.Contains("EXPOSED_TO_ACTIVITIES_WTP_EXPERIMENT")) this.Features.Add(GuildFeaturesEnum.ExposedToActivitiesWtpExperiment);
if (guild.RawFeatures.Contains("GUILD_COMMUNICATION_DISABLED_GUILDS")) this.Features.Add(GuildFeaturesEnum.GuildCommunicationDisabledGuilds);
if (guild.RawFeatures.Contains("DISABLE_GUILD_COMMUNICATION")) this.Features.Add(GuildFeaturesEnum.DisableGuildCommunication);
if (guild.RawFeatures.Contains("GUILD_HOME_OVERRIDE")) this.Features.Add(GuildFeaturesEnum.GuildHomeOverride);
if (guild.RawFeatures.Contains("GUILD_AUTOMOD_DEFAULT_LIST")) this.Features.Add(GuildFeaturesEnum.GuildAutomodDefaultList);
if (guild.RawFeatures.Contains("GUILD_MEMBER_VERIFICATION_EXPERIMENT")) this.Features.Add(GuildFeaturesEnum.GuildMemberVerificationExperiment);
if (guild.RawFeatures.Contains("GUILD_ROLE_SUBSCRIPTION_PURCHASE_FEEDBACK_LOOP")) this.Features.Add(GuildFeaturesEnum.GuildRoleSubscriptionPurchaseFeedbackLoop);
if (guild.RawFeatures.Contains("GUILD_ROLE_SUBSCRIPTION_TRIALS")) this.Features.Add(GuildFeaturesEnum.GuildRoleSubscriptionTrials);
if (guild.RawFeatures.Contains("HAD_EARLY_ACTIVITIES_ACCESS")) this.Features.Add(GuildFeaturesEnum.HadEarlyActivitiesAccess);
if (guild.RawFeatures.Contains("INCREASED_THREAD_LIMIT")) this.Features.Add(GuildFeaturesEnum.IncreasedThreadLimit);
if (guild.RawFeatures.Contains("MOBILE_WEB_ROLE_SUBSCRIPTION_PURCHASE_PAGE")) this.Features.Add(GuildFeaturesEnum.MobileWebRoleSubscriptionPurchasePage);
if (guild.RawFeatures.Contains("RELAY_ENABLED")) this.Features.Add(GuildFeaturesEnum.RelayEnabled);
if (guild.RawFeatures.Contains("RESTRICT_SPAM_RISK_GUILDS")) this.Features.Add(GuildFeaturesEnum.RestrictSpamRiskGuilds);
if (guild.RawFeatures.Contains("ROLE_SUBSCRIPTIONS_AVAILABLE_FOR_PURCHASE")) this.Features.Add(GuildFeaturesEnum.RoleSubscriptionsAvailableForPurchase);
if (guild.RawFeatures.Contains("THREADS_ENABLED_TESTING")) this.Features.Add(GuildFeaturesEnum.ThreadsEnabledTesting);
if (guild.RawFeatures.Contains("VOICE_CHANNEL_EFFECTS")) this.Features.Add(GuildFeaturesEnum.VoiceChannelEffects);
if (guild.RawFeatures.Contains("SOUNDBOARD")) this.Features.Add(GuildFeaturesEnum.Soundboard);
if (guild.RawFeatures.Contains("COMMERCE")) this.Features.Add(GuildFeaturesEnum.Commerce);
if (guild.RawFeatures.Contains("EXPOSED_TO_BOOSTING_TIERS_EXPERIMENT")) this.Features.Add(GuildFeaturesEnum.ExposedToBoostingTiersExperiment);
if (guild.RawFeatures.Contains("PUBLIC_DISABLED")) this.Features.Add(GuildFeaturesEnum.PublicDisabled);
if (guild.RawFeatures.Contains("PUBLIC")) this.Features.Add(GuildFeaturesEnum.Public);
if (guild.RawFeatures.Contains("SEVEN_DAY_THREAD_ARCHIVE")) this.Features.Add(GuildFeaturesEnum.SevenDayThreadArchive);
if (guild.RawFeatures.Contains("THREE_DAY_THREAD_ARCHIVE")) this.Features.Add(GuildFeaturesEnum.ThreeDayThreadArchive);
if (guild.RawFeatures.Contains("FEATURABLE")) this.Features.Add(GuildFeaturesEnum.Featurable);
if (guild.RawFeatures.Contains("FORCE_RELAY")) this.Features.Add(GuildFeaturesEnum.ForceRelay);
if (guild.RawFeatures.Contains("LURKABLE")) this.Features.Add(GuildFeaturesEnum.Lurkable);
if (guild.RawFeatures.Contains("MEMBER_LIST_DISABLED")) this.Features.Add(GuildFeaturesEnum.MemberListDisabled);
if (guild.RawFeatures.Contains("CHANNEL_BANNER")) this.Features.Add(GuildFeaturesEnum.CanSetChannelBanner);
if (guild.RawFeatures.Contains("PRIVATE_THREADS")) this.Features.Add(GuildFeaturesEnum.CanCreatePrivateThreads);
}
///
/// Checks whether the guild has a feature enabled.
///
/// The feature you'd like to check for.
/// Whether the guild has the requested feature.
public bool HasFeature(GuildFeaturesEnum flag)
=> this.Features.Contains(flag);
public string ToString(string separator, bool humanReadable)
{
if (!humanReadable) return string.Join(separator, this.Features);
else
{
var humanReadableFeatures = this.Features.Select(x => AddSpacesToWord(x.ToString()));
return string.Join(separator, humanReadableFeatures);
}
}
///
/// Converts a string of characters (here: enum) into a string of characters separated by spaces after a capital letter.
///
/// String of text to convert
/// String separated by a space after every capital letter.
private static string AddSpacesToWord(string text)
{
if (string.IsNullOrWhiteSpace(text))
return "";
var newText = new StringBuilder(text.Length * 2);
newText.Append(text[0]);
for (var i = 1; i < text.Length; i++)
{
if (char.IsUpper(text[i]) && text[i - 1] != ' ')
newText.Append(' ');
newText.Append(text[i]);
}
return newText.ToString();
}
}
///
/// Represents the guild features.
///
public enum GuildFeaturesEnum
{
///
/// Guild has access to set an animated guild icon.
///
CanSetAnimatedIcon,
///
/// Guild has access to set a guild banner image.
///
CanSetBanner,
///
/// Guild has access to use commerce features (i.e. create store channels)
///
[Obsolete("Store applications are EOL.")]
CanCreateStoreChannels,
///
/// Guild can enable Welcome Screen, Membership Screening, Stage Channels, News Channels and receives community updates.
/// Furthermore the guild can apply as a partner and for the discovery (if the prerequisites are given).
/// and is usable.
///
HasCommunityEnabled,
///
/// Guild is able to be discovered in the discovery.
///
IsDiscoverable,
///
/// Guild is able to be featured in the discovery.
///
IsFeatureable,
///
/// Guild has access to set an invite splash background.
///
CanSetInviteSplash,
///
/// Guild has enabled Membership Screening.
///
HasMembershipScreeningEnabled,
///
/// Guild has access to create news channels.
/// is usable.
///
CanCreateNewsChannels,
///
/// Guild is partnered.
///
IsPartnered,
///
/// Guild has increased custom emoji slots.
///
CanUploadMoreEmojis,
///
/// Guild can be previewed before joining via Membership Screening or the discovery.
///
HasPreviewEnabled,
///
/// Guild has access to set a vanity URL.
///
CanSetVanityUrl,
///
/// Guild is verified.
///
IsVerified,
///
/// Guild has access to set 384kbps bitrate in voice (previously VIP voice servers).
///
CanAccessVipRegions,
///
/// Guild has enabled the welcome screen.
///
HasWelcomeScreenEnabled,
///
/// Guild has enabled ticketed events.
///
HasTicketedEventsEnabled,
///
/// Guild has enabled monetization.
///
HasMonetizationEnabled,
///
/// Guild has increased custom sticker slots.
///
CanUploadMoreStickers,
///
/// Guild has access to the three day archive time for threads.
/// Needs Premium Tier 1 ().
///
[DiscordDeprecated("Auto archive duration isn't locked to boosts anymore."), Obsolete]
CanSetThreadArchiveDurationThreeDays,
///
/// Guild has access to the seven day archive time for threads.
/// Needs Premium Tier 2 ().
///
[DiscordDeprecated("Auto archive duration isn't locked to boosts anymore."), Obsolete]
CanSetThreadArchiveDurationSevenDays,
///
/// Guild has access to create private threads.
/// Needs Premium Tier 2 ().
///
[DiscordDeprecated("Private threads aren't bound to the server boost level anymore and can be used by everyone."), Obsolete]
CanCreatePrivateThreads,
///
/// Guild is a hub.
/// is usable.
///
IsHub,
///
/// Guild is in a hub.
/// https://github.com/discord/discord-api-docs/pull/3757/commits/4932d92c9d0c783861bc715bf7ebbabb15114e34
///
HasDirectoryEntry,
///
/// Guild is linked to a hub.
///
IsLinkedToHub,
///
/// Guild has full access to threads.
/// Old Feature.
///
HasThreadTestingEnabled,
///
/// Guild has access to threads.
///
HasThreadsEnabled,
///
/// Guild can set role icons.
///
CanSetRoleIcons,
///
/// Guild has the new thread permissions.
/// Old Feature.
///
HasNewThreadPermissions,
///
/// Guild can set thread default auto archive duration.
/// Old Feature.
///
CanSetThreadDefaultAutoArchiveDuration,
///
/// Guild has enabled role subscriptions.
///
HasRoleSubscriptionsEnabled,
///
/// Guild role subscriptions as purchaseable.
///
RoleSubscriptionsIsAvailableForPurchase,
///
/// Guild has premium tier 3 override.
///
PremiumTierThreeOverride,
///
/// Guild has access to text in voice.
/// Restricted to .
///
TextInVoiceEnabled,
///
/// Guild can set an animated banner.
/// Needs Premium Tier 3 ().
///
CanSetAnimatedBanner,
///
/// Guild can set an animated banner.
/// Needs Premium Tier 3 ().
///
[DiscordDeprecated("Feature was removed"), Obsolete]
CanSetChannelBanner,
///
/// Allows members to customize their avatar, banner and bio for that server.
///
HasMemberProfiles,
///
/// Guild is restricted to users with the badge.
///
IsStaffOnly,
///
/// Guild can use and setup the experimental auto moderation feature.
///
CanSetupAutoModeration,
///
/// Guild has access to home.
///
GuildHomeTest,
///
/// Guild has disabled invites.
///
InvitesDisabled,
///
/// Currently unknown.
///
ActivitiesAlpha,
///
/// Currently unknown.
///
ActivitiesEmployee,
///
/// Currently unknown.
///
ActivitiesInternalDev,
///
/// Currently unknown.
///
AutomodTriggerKeywordFilter,
///
/// Currently unknown.
///
AutomodTriggerMlSpamFilter,
///
/// Currently unknown.
///
AutomodTriggerSpamLinkFilterGuild,
///
/// Currently unknown.
///
AutomodDefaultList,
///
/// Currently unknown.
///
Bfg,
///
/// Currently unknown.
///
BoostingTiersExperimentMediumGuild,
///
/// Currently unknown.
///
BoostingTiersExperimentSmallGuild,
///
/// Guild has early access features for bot and library developers.
///
BotDeveloperEarlyAccess,
///
/// Currently unknown.
///
CreatorMonetizable,
///
/// Currently unknown.
///
CreatorMonetizableDisabled,
///
/// Currently unknown.
///
CreatorMonetizableProvisional,
///
/// Currently unknown.
///
CreatorMonetizableWhiteGlove,
///
/// Currently unknown.
///
CreatorMonetizationApplicationAllowlist,
///
/// Guild is set as a support server for an app in App Directory.
///
DeveloperSupportServer,
///
/// Guild was previously in the 2021-11_activities_baseline_engagement_bundle experiment.
///
ExposedToActivitiesWtpExperiment,
///
/// Guild had early access to the user timeouts.
///
GuildCommunicationDisabledGuilds,
///
/// Currently unknown.
///
DisableGuildCommunication,
///
/// Guild has access to the Home feature.
///
GuildHomeOverride,
///
/// Guild had early access to the Automod Default List.
///
GuildAutomodDefaultList,
///
/// Guild had early access to approving membership manually.
///
GuildMemberVerificationExperiment,
///
/// Guilds was previously in the 2022-05_mobile_web_role_subscription_purchase_page experiment.
///
GuildRoleSubscriptionPurchaseFeedbackLoop,
///
/// Guild was previously in the 2022-01_guild_role_subscription_trials experiment.
///
GuildRoleSubscriptionTrials,
///
/// Guild previously had access to voice channel activities and can bypass the boost level requirement.
///
HadEarlyActivitiesAccess,
///
/// Allows the guild to have 1,000+ active threads.
///
IncreasedThreadLimit,
///
/// Guild was previously in the 2022-05_mobile_web_role_subscription_purchase_page experiment.
///
MobileWebRoleSubscriptionPurchasePage,
///
/// Shards connections to the guild to different nodes that relay information between each other.
///
RelayEnabled,
///
/// Currently unknown.
///
RestrictSpamRiskGuilds,
///
/// Allows guild's members to purchase role subscriptions.
///
RoleSubscriptionsAvailableForPurchase,
///
/// Used by bot developers to test their bots with threads in guilds with 5 or less members and a bot.
///
ThreadsEnabledTesting,
///
/// Guild had early access to the voice channel effects.
///
VoiceChannelEffects,
///
/// Guild had early access to the soundboard feature.
///
Soundboard,
///
/// Ability to create and use store channels.
///
[DiscordDeprecated("This feature is depcreated"), Obsolete]
Commerce,
///
/// Currently unknown.
///
[DiscordDeprecated("This feature is depcreated"), Obsolete]
ExposedToBoostingTiersExperiment,
///
/// Deprecated in favor of Community.
///
[DiscordDeprecated("This feature is depcreated"), Obsolete]
PublicDisabled,
///
/// Deprecated in favor of Community.
///
[DiscordDeprecated("This feature is depcreated"), Obsolete]
Public,
///
/// The guild can use the seven-day archive time for threads.
///
[DiscordDeprecated("This feature is depcreated"), Obsolete]
SevenDayThreadArchive,
///
/// The guild can use the three-day archive time for threads.
///
[DiscordDeprecated("This feature is depcreated"), Obsolete]
ThreeDayThreadArchive,
///
/// Previously used to control which servers were displayed under the "Featured" category in Discovery.
///
[DiscordDeprecated("This feature is depcreated"), Obsolete]
Featurable,
///
/// Shards connections to the guild to different nodes that relay information between each other.
///
[DiscordDeprecated("This feature is depcreated"), Obsolete]
ForceRelay,
///
/// Currently unknown.
///
[DiscordDeprecated("This feature is depcreated"), Obsolete]
Lurkable,
///
/// Created for the Fortnite server blackout event on Oct 13, 2019, when viewing the member list it would show "There's nothing to see here.".
///
[DiscordDeprecated("This feature is depcreated"), Obsolete]
MemberListDisabled,
[DiscordInExperiment]
CanUseCreatorStorePage,
[DiscordInExperiment]
CanUseBurstReactions,
[DiscordInExperiment]
HadGuildOnBoardingEverEnabled,
[DiscordInExperiment]
ChannelHighlightsDisabled,
[DiscordInExperiment]
ChannelHighlights,
[DiscordInExperiment]
CreatorMonetizableRestricted,
[DiscordUnreleased]
VoiceInThreadsEnabled,
- [DiscordUnreleased("Feature related to automod.")]
+ [DiscordInExperiment("Feature related to automod.")]
RaidAlertsEnabled,
[DiscordInExperiment("Was recently added to determine whether guilds uses the newer permission system for application commands.")]
UsesApplicationCommandsPermissionsV2,
}
diff --git a/DisCatSharp/Entities/Guild/DiscordGuild.cs b/DisCatSharp/Entities/Guild/DiscordGuild.cs
index 021c1f7c3..8aaf06087 100644
--- a/DisCatSharp/Entities/Guild/DiscordGuild.cs
+++ b/DisCatSharp/Entities/Guild/DiscordGuild.cs
@@ -1,2153 +1,2194 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
// Copyright (c) 2021-2022 AITSYS
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using DisCatSharp.Enums;
using DisCatSharp.Exceptions;
using DisCatSharp.Net;
using DisCatSharp.Net.Abstractions;
using DisCatSharp.Net.Models;
using DisCatSharp.Net.Serialization;
using Newtonsoft.Json;
namespace DisCatSharp.Entities;
///
/// Represents a Discord guild.
///
public partial class DiscordGuild : SnowflakeObject, IEquatable
{
///
/// Gets the guild's name.
///
[JsonProperty("name", NullValueHandling = NullValueHandling.Ignore)]
public string Name { get; internal set; }
///
/// Gets the guild icon's hash.
///
[JsonProperty("icon", NullValueHandling = NullValueHandling.Ignore)]
public string IconHash { get; internal set; }
///
/// Gets the guild icon's url.
///
[JsonIgnore]
public string IconUrl
=> !string.IsNullOrWhiteSpace(this.IconHash) ? $"{DiscordDomain.GetDomain(CoreDomain.DiscordCdn).Url}{Endpoints.ICONS}/{this.Id.ToString(CultureInfo.InvariantCulture)}/{this.IconHash}.{(this.IconHash.StartsWith("a_") ? "gif" : "png")}?size=1024" : null;
///
/// Gets the guild splash's hash.
///
[JsonProperty("splash", NullValueHandling = NullValueHandling.Ignore)]
public string SplashHash { get; internal set; }
///
/// Gets the guild splash's url.
///
[JsonIgnore]
public string SplashUrl
=> !string.IsNullOrWhiteSpace(this.SplashHash) ? $"{DiscordDomain.GetDomain(CoreDomain.DiscordCdn).Url}{Endpoints.SPLASHES}/{this.Id.ToString(CultureInfo.InvariantCulture)}/{this.SplashHash}.png?size=1024" : null;
///
/// Gets the guild discovery splash's hash.
///
[JsonProperty("discovery_splash", NullValueHandling = NullValueHandling.Ignore)]
public string DiscoverySplashHash { get; internal set; }
///
/// Gets the guild discovery splash's url.
///
[JsonIgnore]
public string DiscoverySplashUrl
=> !string.IsNullOrWhiteSpace(this.DiscoverySplashHash) ? $"{DiscordDomain.GetDomain(CoreDomain.DiscordCdn).Url}{Endpoints.GUILD_DISCOVERY_SPLASHES}/{this.Id.ToString(CultureInfo.InvariantCulture)}/{this.DiscoverySplashHash}.png?size=1024" : null;
///
/// Gets the preferred locale of this guild.
/// This is used for server discovery, interactions and notices from Discord. Defaults to en-US.
///
[JsonProperty("preferred_locale", NullValueHandling = NullValueHandling.Ignore)]
public string PreferredLocale { get; internal set; }
///
/// Gets the ID of the guild's owner.
///
[JsonProperty("owner_id", NullValueHandling = NullValueHandling.Ignore)]
public ulong OwnerId { get; internal set; }
///
/// Gets the guild's owner.
///
[JsonIgnore]
public DiscordMember Owner
=> this.Members.TryGetValue(this.OwnerId, out var owner)
? owner
: this.Discord.ApiClient.GetGuildMemberAsync(this.Id, this.OwnerId).ConfigureAwait(false).GetAwaiter().GetResult();
///
/// Gets permissions for the user in the guild (does not include channel overrides)
///
[JsonProperty("permissions", NullValueHandling = NullValueHandling.Ignore)]
public Permissions? Permissions { get; set; }
///
/// Gets the guild's voice region ID.
///
[JsonProperty("region", NullValueHandling = NullValueHandling.Ignore)]
internal string VoiceRegionId { get; set; }
///
/// Gets the guild's voice region.
///
[JsonIgnore]
public DiscordVoiceRegion VoiceRegion
=> this.Discord.VoiceRegions[this.VoiceRegionId];
///
/// Gets the guild's AFK voice channel ID.
///
[JsonProperty("afk_channel_id", NullValueHandling = NullValueHandling.Ignore)]
internal ulong AfkChannelId { get; set; }
///
/// Gets the guild's AFK voice channel.
///
[JsonIgnore]
public DiscordChannel AfkChannel
=> this.GetChannel(this.AfkChannelId);
///
/// List of .
/// Null if DisCatSharp.ApplicationCommands is not used or no guild commands are registered.
///
[JsonIgnore]
public ReadOnlyCollection RegisteredApplicationCommands
=> new(this.InternalRegisteredApplicationCommands);
[JsonIgnore]
internal List InternalRegisteredApplicationCommands { get; set; } = new();
///
/// 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 safety alerts channel id.
+ ///
+ [JsonProperty("safety_alerts_channel_id", NullValueHandling = NullValueHandling.Ignore)]
+ internal ulong? SafetyAlertsChannelId { get; set; }
+
///
/// Gets the widget channel for this guild.
///
[JsonIgnore]
public DiscordChannel WidgetChannel => this.WidgetChannelId.HasValue
? this.GetChannel(this.WidgetChannelId.Value)
: null;
///
/// Gets the rules channel id.
///
[JsonProperty("rules_channel_id")]
internal ulong? RulesChannelId { get; set; }
///
/// Gets the rules channel for this guild.
/// This is only available if the guild is considered "discoverable".
///
[JsonIgnore]
public DiscordChannel RulesChannel => this.RulesChannelId.HasValue
? this.GetChannel(this.RulesChannelId.Value)
: null;
///
/// Gets the public updates channel id.
///
[JsonProperty("public_updates_channel_id")]
internal ulong? PublicUpdatesChannelId { get; set; }
///
/// Gets the public updates channel (where admins and moderators receive messages from Discord) for this guild.
/// This is only available if the guild is considered "discoverable".
///
[JsonIgnore]
public DiscordChannel PublicUpdatesChannel => this.PublicUpdatesChannelId.HasValue
? this.GetChannel(this.PublicUpdatesChannelId.Value)
: null;
///
/// Gets the application id of this guild if it is bot created.
///
[JsonProperty("application_id")]
public ulong? ApplicationId { get; internal set; }
///
/// Gets a collection of this guild's roles.
///
[JsonIgnore]
public IReadOnlyDictionary Roles => new ReadOnlyConcurrentDictionary(this.RolesInternal);
[JsonProperty("roles", NullValueHandling = NullValueHandling.Ignore)]
[JsonConverter(typeof(SnowflakeArrayAsDictionaryJsonConverter))]
internal ConcurrentDictionary RolesInternal;
///
/// Gets a collection of this guild's stickers.
///
[JsonIgnore]
public IReadOnlyDictionary Stickers => new ReadOnlyConcurrentDictionary(this.StickersInternal);
[JsonProperty("stickers", NullValueHandling = NullValueHandling.Ignore)]
[JsonConverter(typeof(SnowflakeArrayAsDictionaryJsonConverter))]
internal ConcurrentDictionary StickersInternal;
///
/// Gets a collection of this guild's emojis.
///
[JsonIgnore]
public IReadOnlyDictionary Emojis => new ReadOnlyConcurrentDictionary(this.EmojisInternal);
[JsonProperty("emojis", NullValueHandling = NullValueHandling.Ignore)]
[JsonConverter(typeof(SnowflakeArrayAsDictionaryJsonConverter))]
internal ConcurrentDictionary EmojisInternal;
///
/// Gets a collection of this guild's features.
///
[JsonProperty("features", NullValueHandling = NullValueHandling.Ignore)]
public IReadOnlyList RawFeatures { get; internal set; }
///
/// Gets the guild's features.
///
[JsonIgnore]
public GuildFeatures Features => new(this);
///
/// Gets the required multi-factor authentication level for this guild.
///
[JsonProperty("mfa_level", NullValueHandling = NullValueHandling.Ignore)]
public MfaLevel MfaLevel { get; internal set; }
///
/// Gets this guild's join date.
///
[JsonProperty("joined_at", NullValueHandling = NullValueHandling.Ignore)]
public DateTimeOffset JoinedAt { get; internal set; }
///
/// Gets whether this guild is considered to be a large guild.
///
[JsonProperty("large", NullValueHandling = NullValueHandling.Ignore)]
public bool IsLarge { get; internal set; }
///
/// Gets whether this guild is unavailable.
///
[JsonProperty("unavailable", NullValueHandling = NullValueHandling.Ignore)]
public bool IsUnavailable { get; internal set; }
///
/// Gets the total number of members in this guild.
///
[JsonProperty("member_count", NullValueHandling = NullValueHandling.Ignore)]
public int MemberCount { get; internal set; }
///
/// Gets the maximum amount of members allowed for this guild.
///
[JsonProperty("max_members")]
public int? MaxMembers { get; internal set; }
///
/// Gets the maximum amount of presences allowed for this guild.
///
[JsonProperty("max_presences")]
public int? MaxPresences { get; internal set; }
///
/// Gets the approximate number of members in this guild, when using and having withCounts set to true.
///
[JsonProperty("approximate_member_count", NullValueHandling = NullValueHandling.Ignore)]
public int? ApproximateMemberCount { get; internal set; }
///
/// Gets the approximate number of presences in this guild, when using and having withCounts set to true.
///
[JsonProperty("approximate_presence_count", NullValueHandling = NullValueHandling.Ignore)]
public int? ApproximatePresenceCount { get; internal set; }
///
/// Gets the maximum amount of users allowed per video channel.
///
[JsonProperty("max_video_channel_users", NullValueHandling = NullValueHandling.Ignore)]
public int? MaxVideoChannelUsers { get; internal set; }
///
/// Gets the maximum amount of users allowed per video stage channel.
///
[JsonProperty("max_stage_video_channel_users", NullValueHandling = NullValueHandling.Ignore)]
public int? MaxStageVideoChannelUsers { get; internal set; }
///
/// Gets a dictionary of all the voice states for this guilds. The key for this dictionary is the ID of the user
/// the voice state corresponds to.
///
[JsonIgnore]
public IReadOnlyDictionary VoiceStates => new ReadOnlyConcurrentDictionary(this.VoiceStatesInternal);
[JsonProperty("voice_states", NullValueHandling = NullValueHandling.Ignore)]
[JsonConverter(typeof(SnowflakeArrayAsDictionaryJsonConverter))]
internal ConcurrentDictionary VoiceStatesInternal;
///
/// Gets a dictionary of all the members that belong to this guild. The dictionary's key is the member ID.
///
[JsonIgnore]
public IReadOnlyDictionary Members => new ReadOnlyConcurrentDictionary(this.MembersInternal);
[JsonProperty("members", NullValueHandling = NullValueHandling.Ignore)]
[JsonConverter(typeof(SnowflakeArrayAsDictionaryJsonConverter))]
internal ConcurrentDictionary MembersInternal;
///
/// Gets a dictionary of all the channels associated with this guild. The dictionary's key is the channel ID.
///
[JsonIgnore]
public IReadOnlyDictionary Channels => new ReadOnlyConcurrentDictionary(this.ChannelsInternal);
[JsonProperty("channels", NullValueHandling = NullValueHandling.Ignore)]
[JsonConverter(typeof(SnowflakeArrayAsDictionaryJsonConverter))]
internal ConcurrentDictionary ChannelsInternal;
internal ConcurrentDictionary Invites;
///
/// Gets a dictionary of all the active threads associated with this guild the user has permission to view. The dictionary's key is the channel ID.
///
[JsonIgnore]
public IReadOnlyDictionary Threads { get; internal set; }
[JsonProperty("threads", NullValueHandling = NullValueHandling.Ignore)]
[JsonConverter(typeof(SnowflakeArrayAsDictionaryJsonConverter))]
internal ConcurrentDictionary ThreadsInternal = new();
///
/// Gets a dictionary of all active stage instances. The dictionary's key is the stage ID.
///
[JsonIgnore]
public IReadOnlyDictionary StageInstances { get; internal set; }
[JsonProperty("stage_instances", NullValueHandling = NullValueHandling.Ignore)]
[JsonConverter(typeof(SnowflakeArrayAsDictionaryJsonConverter))]
internal ConcurrentDictionary StageInstancesInternal = new();
///
/// Gets a dictionary of all scheduled events.
///
[JsonIgnore]
public IReadOnlyDictionary ScheduledEvents { get; internal set; }
[JsonProperty("guild_scheduled_events", NullValueHandling = NullValueHandling.Ignore)]
[JsonConverter(typeof(SnowflakeArrayAsDictionaryJsonConverter))]
internal ConcurrentDictionary ScheduledEventsInternal = new();
///
/// Gets the guild member for current user.
///
[JsonIgnore]
public DiscordMember CurrentMember
=> this._currentMemberLazy.Value;
[JsonIgnore]
private readonly Lazy _currentMemberLazy;
///
/// Gets the @everyone role for this guild.
///
[JsonIgnore]
public DiscordRole EveryoneRole
=> this.GetRole(this.Id);
[JsonIgnore]
internal bool IsOwnerInternal;
///
/// Gets whether the current user is the guild's owner.
///
[JsonProperty("owner", NullValueHandling = NullValueHandling.Ignore)]
public bool IsOwner
{
get => this.IsOwnerInternal || this.OwnerId == this.Discord.CurrentUser.Id;
internal set => this.IsOwnerInternal = value;
}
///
/// Gets the vanity URL code for this guild, when applicable.
///
[JsonProperty("vanity_url_code")]
public string VanityUrlCode { get; internal set; }
///
/// Gets the guild description, when applicable.
///
[JsonProperty("description")]
public string Description { get; internal set; }
///
/// Gets this guild's banner hash, when applicable.
///
[JsonProperty("banner")]
public string BannerHash { get; internal set; }
///
/// Gets this guild's banner in url form.
///
[JsonIgnore]
public string BannerUrl
=> !string.IsNullOrWhiteSpace(this.BannerHash) ? $"{DiscordDomain.GetDomain(CoreDomain.DiscordCdn).Uri}{Endpoints.BANNERS}/{this.Id.ToString(CultureInfo.InvariantCulture)}/{this.BannerHash}.{(this.BannerHash.StartsWith("a_") ? "gif" : "png")}" : null;
///
/// Whether this guild has the community feature enabled.
///
[JsonIgnore]
public bool IsCommunity => this.Features.HasFeature(GuildFeaturesEnum.HasCommunityEnabled);
///
/// Whether this guild has enabled the welcome screen.
///
[JsonIgnore]
public bool HasWelcomeScreen => this.Features.HasFeature(GuildFeaturesEnum.HasWelcomeScreenEnabled);
///
/// Whether this guild has enabled membership screening.
///
[JsonIgnore]
public bool HasMemberVerificationGate => this.Features.HasFeature(GuildFeaturesEnum.HasMembershipScreeningEnabled);
///
/// Gets this guild's premium tier (Nitro boosting).
///
[JsonProperty("premium_tier")]
public PremiumTier PremiumTier { get; internal set; }
///
/// Gets the amount of members that boosted this guild.
///
[JsonProperty("premium_subscription_count", NullValueHandling = NullValueHandling.Ignore)]
public int? PremiumSubscriptionCount { get; internal set; }
///
/// Whether the premium progress bar is enabled.
///
[JsonProperty("premium_progress_bar_enabled", NullValueHandling = NullValueHandling.Ignore)]
public bool PremiumProgressBarEnabled { get; internal set; }
///
/// Gets whether this guild is designated as NSFW.
///
[JsonProperty("nsfw", NullValueHandling = NullValueHandling.Ignore)]
public bool IsNsfw { get; internal set; }
///
/// Gets this guild's hub type, if applicable.
///
[JsonProperty("hub_type", NullValueHandling = NullValueHandling.Ignore)]
public HubType HubType { get; internal set; }
///
/// Gets a dictionary of all by position ordered channels associated with this guild. The dictionary's key is the channel ID.
///
[JsonIgnore]
public IReadOnlyDictionary OrderedChannels => new ReadOnlyDictionary(this.InternalSortChannels());
///
/// Sorts the channels.
///
private Dictionary InternalSortChannels()
{
Dictionary keyValuePairs = new();
var ochannels = this.GetOrderedChannels();
foreach (var ochan in ochannels)
{
if (ochan.Key != 0)
keyValuePairs.Add(ochan.Key, this.GetChannel(ochan.Key));
foreach (var chan in ochan.Value)
keyValuePairs.Add(chan.Id, chan);
}
return keyValuePairs;
}
///
/// Gets an ordered list out of the channel cache.
/// Returns a Dictionary where the key is an ulong and can be mapped to s.
/// Ignore the 0 key here, because that indicates that this is the "has no category" list.
/// Each value contains a ordered list of text/news and voice/stage channels as .
///
/// A ordered list of categories with its channels
public Dictionary> GetOrderedChannels()
{
IReadOnlyList rawChannels = this.ChannelsInternal.Values.ToList();
Dictionary> orderedChannels = new()
{
{ 0, new List() }
};
foreach (var channel in rawChannels.Where(c => c.Type == ChannelType.Category).OrderBy(c => c.Position))
{
orderedChannels.Add(channel.Id, new List());
}
foreach (var channel in rawChannels.Where(c => c.ParentId.HasValue && (c.Type == ChannelType.Text || c.Type == ChannelType.News || c.Type == ChannelType.Forum)).OrderBy(c => c.Position))
{
orderedChannels[channel.ParentId.Value].Add(channel);
}
foreach (var channel in rawChannels.Where(c => c.ParentId.HasValue && (c.Type == ChannelType.Voice || c.Type == ChannelType.Stage)).OrderBy(c => c.Position))
{
orderedChannels[channel.ParentId.Value].Add(channel);
}
foreach (var channel in rawChannels.Where(c => !c.ParentId.HasValue && c.Type != ChannelType.Category && (c.Type == ChannelType.Text || c.Type == ChannelType.News || c.Type == ChannelType.Forum)).OrderBy(c => c.Position))
{
orderedChannels[0].Add(channel);
}
foreach (var channel in rawChannels.Where(c => !c.ParentId.HasValue && c.Type != ChannelType.Category && (c.Type == ChannelType.Voice || c.Type == ChannelType.Stage)).OrderBy(c => c.Position))
{
orderedChannels[0].Add(channel);
}
return orderedChannels;
}
///
/// Gets an ordered list.
/// Returns a Dictionary where the key is an ulong and can be mapped to s.
/// Ignore the 0 key here, because that indicates that this is the "has no category" list.
/// Each value contains a ordered list of text/news and voice/stage channels as .
///
/// A ordered list of categories with its channels
public async Task>> GetOrderedChannelsAsync()
{
var rawChannels = await this.Discord.ApiClient.GetGuildChannelsAsync(this.Id);
Dictionary> orderedChannels = new()
{
{ 0, new List() }
};
foreach (var channel in rawChannels.Where(c => c.Type == ChannelType.Category).OrderBy(c => c.Position))
{
orderedChannels.Add(channel.Id, new List());
}
foreach (var channel in rawChannels.Where(c => c.ParentId.HasValue && (c.Type == ChannelType.Text || c.Type == ChannelType.News || c.Type == ChannelType.Forum)).OrderBy(c => c.Position))
{
orderedChannels[channel.ParentId.Value].Add(channel);
}
foreach (var channel in rawChannels.Where(c => c.ParentId.HasValue && (c.Type == ChannelType.Voice || c.Type == ChannelType.Stage)).OrderBy(c => c.Position))
{
orderedChannels[channel.ParentId.Value].Add(channel);
}
foreach (var channel in rawChannels.Where(c => !c.ParentId.HasValue && c.Type != ChannelType.Category && (c.Type == ChannelType.Text || c.Type == ChannelType.News || c.Type == ChannelType.Forum)).OrderBy(c => c.Position))
{
orderedChannels[0].Add(channel);
}
foreach (var channel in rawChannels.Where(c => !c.ParentId.HasValue && c.Type != ChannelType.Category && (c.Type == ChannelType.Voice || c.Type == ChannelType.Stage)).OrderBy(c => c.Position))
{
orderedChannels[0].Add(channel);
}
return orderedChannels;
}
///
/// Whether it is synced.
///
[JsonIgnore]
internal bool IsSynced { get; set; }
///
/// Initializes a new instance of the class.
///
internal DiscordGuild()
{
this._currentMemberLazy = new Lazy(() => this.MembersInternal != null && this.MembersInternal.TryGetValue(this.Discord.CurrentUser.Id, out var member) ? member : null);
this.Invites = new ConcurrentDictionary();
this.Threads = new ReadOnlyConcurrentDictionary(this.ThreadsInternal);
this.StageInstances = new ReadOnlyConcurrentDictionary(this.StageInstancesInternal);
this.ScheduledEvents = new ReadOnlyConcurrentDictionary(this.ScheduledEventsInternal);
}
#region Guild Methods
///
/// Searches the current guild for members who's display name start with the specified name.
///
/// The name to search for.
/// The maximum amount of members to return. Max 1000. Defaults to 1.
/// The members found, if any.
public Task> SearchMembersAsync(string name, int? limit = 1)
=> this.Discord.ApiClient.SearchMembersAsync(this.Id, name, limit);
///
/// Adds a new member to this guild
///
/// User to add
/// User's access token (OAuth2)
/// new nickname
/// new roles
/// whether this user has to be muted
/// whether this user has to be deafened
/// Thrown when the client does not have the permission.
/// Thrown when the or is not found.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public Task AddMemberAsync(DiscordUser user, string accessToken, string nickname = null, IEnumerable roles = null,
bool muted = false, bool deaf = false)
=> this.Discord.ApiClient.AddGuildMemberAsync(this.Id, user.Id, accessToken, nickname, roles, muted, deaf);
///
/// Deletes this guild. Requires the caller to be the owner of the guild.
///
/// Thrown when the client is not the owner of the guild.
/// Thrown when Discord is unable to process the request.
public Task DeleteAsync()
=> this.Discord.ApiClient.DeleteGuildAsync(this.Id);
///
/// Enables the mfa requirement for this guild.
///
/// The audit log reason.
/// Thrown when the current user is not the guilds owner.
/// Thrown when the guild does not exist.
/// Thrown when Discord is unable to process the request.
public Task EnableMfaAsync(string reason = null)
=> this.IsOwner ? this.Discord.ApiClient.EnableGuildMfaAsync(this.Id, reason) : throw new Exception("The current user does not own the guild.");
///
/// Disables the mfa requirement for this guild.
///
/// The audit log reason.
/// Thrown when the current user is not the guilds owner.
/// Thrown when the guild does not exist.
/// Thrown when Discord is unable to process the request.
public Task DisableMfaAsync(string reason = null)
=> this.IsOwner ? this.Discord.ApiClient.DisableGuildMfaAsync(this.Id, reason) : throw new Exception("The current user does not own the guild.");
///
/// Modifies this guild.
///
/// Action to perform on this guild.
/// The modified guild object.
/// Thrown when the client does not have the permission.
/// Thrown when the guild does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public async Task ModifyAsync(Action action)
{
var mdl = new GuildEditModel();
action(mdl);
var afkChannelId = mdl.PublicUpdatesChannel
.MapOrNull(c => c.Type != ChannelType.Voice
? throw new ArgumentException("AFK channel needs to be a text channel.")
: c.Id);
static Optional ChannelToId(Optional ch, string name)
=> ch.MapOrNull(c => c.Type != ChannelType.Text && c.Type != ChannelType.News
? throw new ArgumentException($"{name} channel needs to be a text channel.")
: c.Id);
var rulesChannelId = ChannelToId(mdl.RulesChannel, "Rules");
var publicUpdatesChannelId = ChannelToId(mdl.PublicUpdatesChannel, "Public updates");
var systemChannelId = ChannelToId(mdl.SystemChannel, "System");
var iconb64 = ImageTool.Base64FromStream(mdl.Icon);
var splashb64 = ImageTool.Base64FromStream(mdl.Splash);
var bannerb64 = ImageTool.Base64FromStream(mdl.Banner);
var discoverySplash64 = ImageTool.Base64FromStream(mdl.DiscoverySplash);
return await this.Discord.ApiClient.ModifyGuildAsync(this.Id, mdl.Name,
mdl.VerificationLevel, mdl.DefaultMessageNotifications, mdl.MfaLevel, mdl.ExplicitContentFilter,
afkChannelId, mdl.AfkTimeout, iconb64, mdl.Owner.Map(e => e.Id), splashb64,
systemChannelId, mdl.SystemChannelFlags, publicUpdatesChannelId, rulesChannelId,
mdl.Description, bannerb64, discoverySplash64, mdl.PreferredLocale, mdl.PremiumProgressBarEnabled, mdl.AuditLogReason).ConfigureAwait(false);
}
///
/// Modifies the community settings async.
/// This sets if not highest and .
///
/// If true, enables .
/// The rules channel.
/// The public updates channel.
/// The preferred locale. Defaults to en-US.
/// The description.
/// The default message notifications. Defaults to
/// The audit log 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, DiscordChannel publicUpdatesChannel, string preferredLocale = "en-US", string description = null, DefaultMessageNotifications defaultMessageNotifications = DefaultMessageNotifications.MentionsOnly, string reason = null)
{
var verificationLevel = this.VerificationLevel;
if (this.VerificationLevel != VerificationLevel.Highest)
{
verificationLevel = VerificationLevel.High;
}
var explicitContentFilter = ExplicitContentFilter.AllMembers;
static Optional ChannelToId(DiscordChannel ch, string name)
=> ch == null ? null :
ch.Type != ChannelType.Text && ch.Type != ChannelType.News
? throw new ArgumentException($"{name} channel needs to be a text channel.")
: ch.Id;
var rulesChannelId = ChannelToId(rulesChannel, "Rules");
var publicUpdatesChannelId = ChannelToId(publicUpdatesChannel, "Public updates");
List features = new();
var rfeatures = this.RawFeatures.ToList();
if (!this.RawFeatures.Contains("COMMUNITY") && enabled)
{
rfeatures.Add("COMMUNITY");
}
else if (this.RawFeatures.Contains("COMMUNITY") && !enabled)
{
rfeatures.Remove("COMMUNITY");
}
features = rfeatures;
return await this.Discord.ApiClient.ModifyGuildCommunitySettingsAsync(this.Id, features, rulesChannelId, publicUpdatesChannelId, preferredLocale, description, defaultMessageNotifications, explicitContentFilter, verificationLevel, reason).ConfigureAwait(false);
}
+ ///
+ /// Modifies the safety alerts settings async.
+ ///
+ /// If true, enables .
+ /// The safety alerts channel.
+ /// The audit log 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 ModifySafetyAlertsSettingsAsync(bool enabled, DiscordChannel safetyAlertsChannel, string reason = null)
+ {
+ static Optional ChannelToId(DiscordChannel ch, string name)
+ => ch == null ? null :
+ ch.Type != ChannelType.Text && ch.Type != ChannelType.News
+ ? throw new ArgumentException($"{name} channel needs to be a text channel.")
+ : ch.Id;
+
+ var safetyAlertsChannelId = ChannelToId(safetyAlertsChannel, "Safety Alerts");
+
+ List features = new();
+ var rfeatures = this.RawFeatures.ToList();
+ if (!this.RawFeatures.Contains("RAID_ALERTS_ENABLED") && enabled)
+ {
+ rfeatures.Add("RAID_ALERTS_ENABLED");
+ }
+ else if (this.RawFeatures.Contains("RAID_ALERTS_ENABLED") && !enabled)
+ {
+ rfeatures.Remove("RAID_ALERTS_ENABLED");
+ }
+ features = rfeatures;
+
+ return await this.Discord.ApiClient.ModifyGuildSafetyAlertsSettingsAsync(this.Id, features, safetyAlertsChannelId, reason).ConfigureAwait(false);
+ }
+
///
/// Enables invites for the guild.
///
/// The audit log 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 EnableInvitesAsync(string reason = null)
{
List features = new();
var rfeatures = this.RawFeatures.ToList();
if (this.Features.HasFeature(GuildFeaturesEnum.InvitesDisabled))
rfeatures.Remove("INVITES_DISABLED");
features = rfeatures;
return await this.Discord.ApiClient.ModifyGuildFeaturesAsync(this.Id, features, reason);
}
///
/// Disables invites for the guild.
///
///
///
/// Thrown when the client does not have the permission.
/// Thrown when the guild does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public async Task DisableInvitesAsync(string reason = null)
{
List features = new();
var rfeatures = this.RawFeatures.ToList();
if (!this.Features.HasFeature(GuildFeaturesEnum.InvitesDisabled))
rfeatures.Add("INVITES_DISABLED");
features = rfeatures;
return await this.Discord.ApiClient.ModifyGuildFeaturesAsync(this.Id, features, reason);
}
///
/// Timeout a specified member in this guild.
///
/// Member to timeout.
/// The datetime offset to time out the user. Up to 28 days.
/// Reason for audit logs.
/// Thrown when the client does not have the permission.
/// Thrown when the member does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public Task TimeoutAsync(ulong memberId, DateTimeOffset until, string reason = null)
=> until.Subtract(DateTimeOffset.UtcNow).Days > 28
? throw new ArgumentException("Timeout can not be longer than 28 days")
: this.Discord.ApiClient.ModifyTimeoutAsync(this.Id, memberId, until, reason);
///
/// Timeout a specified member in this guild.
///
/// Member to timeout.
/// The timespan to time out the user. Up to 28 days.
/// Reason for audit logs.
/// Thrown when the client does not have the permission.
/// Thrown when the member does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public Task TimeoutAsync(ulong memberId, TimeSpan until, string reason = null)
=> this.TimeoutAsync(memberId, DateTimeOffset.UtcNow + until, reason);
///
/// Timeout a specified member in this guild.
///
/// Member to timeout.
/// The datetime to time out the user. Up to 28 days.
/// Reason for audit logs.
/// Thrown when the client does not have the permission.
/// Thrown when the member does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public Task TimeoutAsync(ulong memberId, DateTime until, string reason = null)
=> this.TimeoutAsync(memberId, until.ToUniversalTime() - DateTime.UtcNow, reason);
///
/// Removes the timeout from a specified member in this guild.
///
/// Member to remove the timeout from.
/// Reason for audit logs.
/// Thrown when the client does not have the permission.
/// Thrown when the member does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public Task RemoveTimeoutAsync(ulong memberId, string reason = null)
=> this.Discord.ApiClient.ModifyTimeoutAsync(this.Id, memberId, null, reason);
///
/// Bans a specified member from this guild.
///
/// Member to ban.
/// How many days to remove messages from.
/// Reason for audit logs.
/// Thrown when the client does not have the permission.
/// Thrown when the member does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public Task BanMemberAsync(DiscordMember member, int deleteMessageDays = 0, string reason = null)
=> this.Discord.ApiClient.CreateGuildBanAsync(this.Id, member.Id, deleteMessageDays, reason);
///
/// Bans a specified user by ID. This doesn't require the user to be in this guild.
///
/// ID of the user to ban.
/// How many days to remove messages from.
/// Reason for audit logs.
/// Thrown when the client does not have the permission.
/// Thrown when the member does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public Task BanMemberAsync(ulong userId, int deleteMessageDays = 0, string reason = null)
=> this.Discord.ApiClient.CreateGuildBanAsync(this.Id, userId, deleteMessageDays, reason);
///
/// Unbans a user from this guild.
///
/// User to unban.
/// Reason for audit logs.
/// Thrown when the client does not have the permission.
/// Thrown when the user does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public Task UnbanMemberAsync(DiscordUser user, string reason = null)
=> this.Discord.ApiClient.RemoveGuildBanAsync(this.Id, user.Id, reason);
///
/// Unbans a user by ID.
///
/// ID of the user to unban.
/// Reason for audit logs.
/// Thrown when the client does not have the permission.
/// Thrown when the user does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public Task UnbanMemberAsync(ulong userId, string reason = null)
=> this.Discord.ApiClient.RemoveGuildBanAsync(this.Id, userId, reason);
///
/// Leaves this guild.
///
/// Thrown when Discord is unable to process the request.
public Task LeaveAsync()
=> this.Discord.ApiClient.LeaveGuildAsync(this.Id);
///
/// Gets the bans for this guild, allowing for pagination.
///
/// Maximum number of bans to fetch. Max 1000. Defaults to 1000.
/// The Id of the user before which to fetch the bans. Overrides if both are present.
/// The Id of the user after which to fetch the bans.
/// Collection of bans in this guild in ascending order by user id.
/// Thrown when the client does not have the permission.
/// Thrown when Discord is unable to process the request.
public Task> GetBansAsync(int? limit = null, ulong? before = null, ulong? after = null)
=> this.Discord.ApiClient.GetGuildBansAsync(this.Id, limit, before, after);
///
/// Gets a ban for a specific user.
///
/// The Id of the user to get the ban for.
/// The requested ban object.
/// Thrown when the specified user is not banned.
public Task GetBanAsync(ulong userId)
=> this.Discord.ApiClient.GetGuildBanAsync(this.Id, userId);
///
/// Tries to get a ban for a specific user.
///
/// The Id of the user to get the ban for.
/// The requested ban object or null if not found.
#pragma warning disable CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
public async Task TryGetBanAsync(ulong userId)
#pragma warning restore CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
{
try
{
return await this.GetBanAsync(userId).ConfigureAwait(false);
}
catch (NotFoundException)
{
return null;
}
}
///
/// Gets a ban for a specific user.
///
/// The user to get the ban for.
/// The requested ban object.
/// Thrown when the specified user is not banned.
public Task GetBanAsync(DiscordUser user)
=> this.GetBanAsync(user.Id);
///
/// Tries to get a ban for a specific user.
///
/// The user to get the ban for.
/// The requested ban object or null if not found.
#pragma warning disable CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
public async Task TryGetBanAsync(DiscordUser user)
#pragma warning restore CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
{
try
{
return await this.GetBanAsync(user).ConfigureAwait(false);
}
catch (NotFoundException)
{
return null;
}
}
///
/// Gets all auto mod rules for a guild.
///
/// A collection of all rules in the guild.
public Task> GetAutomodRulesAsync()
=> this.Discord.ApiClient.GetAutomodRulesAsync(this.Id);
///
/// Gets a specific auto mod rule.
///
/// The rule id to get.
/// The auto mod rule.
public Task GetAutomodRuleAsync(ulong ruleId)
=> this.Discord.ApiClient.GetAutomodRuleAsync(this.Id, ruleId);
///
/// Creates a new auto mod rule in a guild.
///
/// The name of the rule.
/// The event type of the rule.
/// The trigger type of the rule.
/// The actions of the rule.
/// The meta data of the rule.
/// Whether this rule is enabled.
/// The exempt roles of the rule.
/// The exempt channels of the rule.
/// The reason for this addition
/// The created rule.
/// Thrown when the client does not have the permission.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public async Task CreateAutomodRuleAsync(string name, AutomodEventType eventType, AutomodTriggerType triggerType, IEnumerable actions,
AutomodTriggerMetadata triggerMetadata = null, bool enabled = false, IEnumerable exemptRoles = null, IEnumerable exemptChannels = null, string reason = null)
=> await this.Discord.ApiClient.CreateAutomodRuleAsync(this.Id, name, eventType, triggerType, actions, triggerMetadata, enabled, exemptRoles, exemptChannels, reason);
#region Scheduled Events
///
/// Creates a scheduled event.
///
/// The name.
/// The scheduled start time.
/// The scheduled end time.
/// The channel.
/// The metadata.
/// The description.
/// The type.
/// The cover image.
/// The reason.
/// A scheduled event.
/// Thrown when the guild does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public async Task CreateScheduledEventAsync(string name, DateTimeOffset scheduledStartTime, DateTimeOffset? scheduledEndTime = null, DiscordChannel channel = null, DiscordScheduledEventEntityMetadata metadata = null, string description = null, ScheduledEventEntityType type = ScheduledEventEntityType.StageInstance, Optional coverImage = default, string reason = null)
{
var coverb64 = ImageTool.Base64FromStream(coverImage);
return await this.Discord.ApiClient.CreateGuildScheduledEventAsync(this.Id, type == ScheduledEventEntityType.External ? null : channel?.Id, type == ScheduledEventEntityType.External ? metadata : null, name, scheduledStartTime, scheduledEndTime.HasValue && type == ScheduledEventEntityType.External ? scheduledEndTime.Value : null, description, type, coverb64, reason);
}
///
/// Creates a scheduled event with type .
///
/// The name.
/// The scheduled start time.
/// The scheduled end time.
/// The location of the external event.
/// The description.
/// The cover image.
/// The reason.
/// A scheduled event.
/// Thrown when the guild does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public async Task CreateExternalScheduledEventAsync(string name, DateTimeOffset scheduledStartTime, DateTimeOffset scheduledEndTime, string location, string description = null, Optional coverImage = default, string reason = null)
{
var coverb64 = ImageTool.Base64FromStream(coverImage);
return await this.Discord.ApiClient.CreateGuildScheduledEventAsync(this.Id, null, new DiscordScheduledEventEntityMetadata(location), name, scheduledStartTime, scheduledEndTime, description, ScheduledEventEntityType.External, coverb64, reason);
}
///
/// Gets a specific scheduled events.
///
/// The Id of the event to get.
/// Whether to include user count.
/// A scheduled event.
/// Thrown when the guild does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public async Task GetScheduledEventAsync(ulong scheduledEventId, bool? withUserCount = null)
=> this.ScheduledEventsInternal.TryGetValue(scheduledEventId, out var ev) ? ev : await this.Discord.ApiClient.GetGuildScheduledEventAsync(this.Id, scheduledEventId, withUserCount);
///
/// Tries to get a specific scheduled events.
///
/// The Id of the event to get.
/// Whether to include user count.
/// A scheduled event or null if not found.
/// 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 async Task TryGetScheduledEventAsync(ulong scheduledEventId, bool? withUserCount = null)
#pragma warning restore CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
{
try
{
return await this.GetScheduledEventAsync(scheduledEventId, withUserCount).ConfigureAwait(false);
}
catch (NotFoundException)
{
return null;
}
}
///
/// Gets a specific scheduled events.
///
/// The event to get.
/// Whether to include user count.
/// A scheduled event.
/// Thrown when the guild does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public async Task GetScheduledEventAsync(DiscordScheduledEvent scheduledEvent, bool? withUserCount = null)
=> await this.GetScheduledEventAsync(scheduledEvent.Id, withUserCount);
///
/// Tries to get a specific scheduled events.
///
/// The event to get.
/// Whether to include user count.
/// A scheduled event or null if not found.
/// 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 async Task TryGetScheduledEventAsync(DiscordScheduledEvent scheduledEvent, bool? withUserCount = null)
#pragma warning restore CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
{
try
{
return await this.GetScheduledEventAsync(scheduledEvent, withUserCount).ConfigureAwait(false);
}
catch (NotFoundException)
{
return null;
}
}
///
/// Gets the guilds scheduled events.
///
/// Whether to include user count.
/// A list of the guilds scheduled events.
/// Thrown when the guild does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public async Task> GetScheduledEventsAsync(bool? withUserCount = null)
=> await this.Discord.ApiClient.ListGuildScheduledEventsAsync(this.Id, withUserCount);
#endregion
///
/// Creates a new text channel in this guild.
///
/// Name of the new channel.
/// Category to put this channel in.
/// Topic of the channel.
/// Permission overwrites for this channel.
/// Whether the channel is to be flagged as not safe for work.
/// Slow mode timeout for users.
/// The default auto archive duration for new threads.
/// The flags of the new channel.
/// Reason for audit logs.
/// The newly-created channel.
/// Thrown when the client does not have the permission.
/// Thrown when the guild does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public Task CreateTextChannelAsync(string name, DiscordChannel parent = null, Optional topic = default, IEnumerable overwrites = null, bool? nsfw = null, Optional perUserRateLimit = default, ThreadAutoArchiveDuration defaultAutoArchiveDuration = ThreadAutoArchiveDuration.OneDay, Optional flags = default, string reason = null)
=> this.CreateChannelAsync(name, ChannelType.Text, parent, topic, null, null, overwrites, nsfw, perUserRateLimit, null, defaultAutoArchiveDuration, flags, reason);
///
/// Creates a new forum channel in this guild.
/// The field template is not yet released, so it won't applied.
///
/// Name of the new channel.
/// Category to put this channel in.
/// Topic of the channel.
/// Permission overwrites for this channel.
/// Whether the channel is to be flagged as not safe for work.
/// The default reaction emoji for posts.
/// Slow mode timeout for users.
/// Slow mode timeout for user post creations.
/// The default auto archive duration for new threads.
/// The default sort order for posts in the new channel.
/// The flags of the new channel.
/// Reason for audit logs.
/// The newly-created channel.
/// Thrown when the client does not have the permission or the guild does not have the forum channel feature.
/// Thrown when the guild does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public Task CreateForumChannelAsync(string name, DiscordChannel parent = null, Optional topic = default, IEnumerable overwrites = null, bool? nsfw = null, Optional defaultReactionEmoji = default, Optional perUserRateLimit = default, Optional postCreateUserRateLimit = default, ThreadAutoArchiveDuration defaultAutoArchiveDuration = ThreadAutoArchiveDuration.OneDay, Optional defaultSortOrder = default, Optional flags = default, string reason = null)
=> this.Discord.ApiClient.CreateForumChannelAsync(this.Id, name, parent?.Id, topic, null, nsfw, defaultReactionEmoji, perUserRateLimit, postCreateUserRateLimit, defaultSortOrder, defaultAutoArchiveDuration, overwrites, flags, reason);
///
/// Creates a new channel category in this guild.
///
/// Name of the new category.
/// Permission overwrites for this category.
/// Reason for audit logs.
/// The newly-created channel category.
/// Thrown when the client does not have the permission.
/// Thrown when the guild does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public Task CreateChannelCategoryAsync(string name, IEnumerable overwrites = null, string reason = null)
=> this.CreateChannelAsync(name, ChannelType.Category, null, Optional.None, null, null, overwrites, null, Optional.None, null, null, Optional.None, reason);
///
/// Creates a new stage channel in this guild.
///
/// Name of the new stage channel.
/// Permission overwrites for this stage channel.
/// Reason for audit logs.
/// The newly-created stage channel.
/// Thrown when the client does not have the .
/// Thrown when the guild does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
/// Thrown when the guilds has not enabled community.
public Task CreateStageChannelAsync(string name, IEnumerable overwrites = null, string reason = null)
=> this.Features.HasFeature(GuildFeaturesEnum.HasCommunityEnabled) ? this.CreateChannelAsync(name, ChannelType.Stage, null, Optional.None, null, null, overwrites, null, Optional.None, null, null, Optional.None, reason) : throw new NotSupportedException("Guild has not enabled community. Can not create a stage channel.");
///
/// Creates a new news channel in this guild.
///
/// Name of the new news channel.
/// Permission overwrites for this news channel.
/// The default auto archive duration for new threads.
/// The flags of the new channel.
/// Reason for audit logs.
/// The newly-created news channel.
/// Thrown when the client does not have the .
/// Thrown when the guild does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
/// Thrown when the guilds has not enabled community.
public Task CreateNewsChannelAsync(string name, IEnumerable overwrites = null, string reason = null, ThreadAutoArchiveDuration defaultAutoArchiveDuration = ThreadAutoArchiveDuration.OneDay, Optional flags = default)
=> this.Features.HasFeature(GuildFeaturesEnum.HasCommunityEnabled) ? this.CreateChannelAsync(name, ChannelType.News, null, Optional.None, null, null, overwrites, null, Optional.None, null, defaultAutoArchiveDuration, flags, reason) : throw new NotSupportedException("Guild has not enabled community. Can not create a news channel.");
///
/// Creates a new voice channel in this guild.
///
/// Name of the new channel.
/// Category to put this channel in.
/// Bitrate of the channel.
/// Maximum number of users in the channel.
/// Permission overwrites for this channel.
/// The flags of the new channel.
/// Video quality mode of the channel.
/// Reason for audit logs.
/// The newly-created channel.
/// Thrown when the client does not have the permission.
/// Thrown when the guild does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public Task CreateVoiceChannelAsync(string name, DiscordChannel parent = null, int? bitrate = null, int? userLimit = null, IEnumerable overwrites = null, VideoQualityMode? qualityMode = null, Optional flags = default, string reason = null)
=> this.CreateChannelAsync(name, ChannelType.Voice, parent, Optional.None, bitrate, userLimit, overwrites, null, Optional.None, qualityMode, null, flags, reason);
///
/// Creates a new channel in this guild.
///
/// Name of the new channel.
/// Type of the new channel.
/// Category to put this channel in.
/// Topic of the channel.
/// Bitrate of the channel. Applies to voice only.
/// Maximum number of users in the channel. Applies to voice only.
/// Permission overwrites for this channel.
/// Whether the channel is to be flagged as not safe for work. Applies to text only.
/// Slow mode timeout for users.
/// Video quality mode of the channel. Applies to voice only.
/// The default auto archive duration for new threads.
/// The flags of the new channel.
/// Reason for audit logs.
/// The newly-created channel.
/// Thrown when the client does not have the permission.
/// Thrown when the guild does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public Task CreateChannelAsync(string name, ChannelType type, DiscordChannel parent = null, Optional topic = default, int? bitrate = null, int? userLimit = null, IEnumerable overwrites = null, bool? nsfw = null, Optional perUserRateLimit = default, VideoQualityMode? qualityMode = null, ThreadAutoArchiveDuration? defaultAutoArchiveDuration = null, Optional flags = default, string reason = null) =>
// technically you can create news/store channels but not always
type != ChannelType.Text && type != ChannelType.Voice && type != ChannelType.Category && type != ChannelType.News && type != ChannelType.Store && type != ChannelType.Stage
? throw new ArgumentException("Channel type must be text, voice, stage, or category.", nameof(type))
: type == ChannelType.Category && parent != null
? throw new ArgumentException("Cannot specify parent of a channel category.", nameof(parent))
: this.Discord.ApiClient.CreateGuildChannelAsync(this.Id, name, type, parent?.Id, topic, bitrate, userLimit, overwrites, nsfw, perUserRateLimit, qualityMode, defaultAutoArchiveDuration, flags, reason);
///
/// Gets active threads. Can contain more threads.
/// If the result's value 'HasMore' is true, you need to recall this function to get older threads.
///
/// Thrown when the thread does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public Task GetActiveThreadsAsync()
=> this.Discord.ApiClient.GetActiveThreadsAsync(this.Id);
///
/// Deletes all channels in this guild.
/// Note that this is irreversible. Use carefully!
///
///
public Task DeleteAllChannelsAsync()
{
var tasks = this.Channels.Values.Select(xc => xc.DeleteAsync());
return Task.WhenAll(tasks);
}
///
/// Estimates the number of users to be pruned.
///
/// Minimum number of inactivity days required for users to be pruned. Defaults to 7.
/// The roles to be included in the prune.
/// Number of users that will be pruned.
/// Thrown when the client does not have the permission.
/// Thrown when the guild does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public Task GetPruneCountAsync(int days = 7, IEnumerable includedRoles = null)
{
if (includedRoles != null)
{
includedRoles = includedRoles.Where(r => r != null);
var rawRoleIds = includedRoles
.Where(x => this.RolesInternal.ContainsKey(x.Id))
.Select(x => x.Id);
return this.Discord.ApiClient.GetGuildPruneCountAsync(this.Id, days, rawRoleIds);
}
return this.Discord.ApiClient.GetGuildPruneCountAsync(this.Id, days, null);
}
///
/// Prunes inactive users from this guild.
///
/// Minimum number of inactivity days required for users to be pruned. Defaults to 7.
/// Whether to return the prune count after this method completes. This is discouraged for larger guilds.
/// The roles to be included in the prune.
/// Reason for audit logs.
/// Number of users pruned.
/// Thrown when the client does not have the permission.
/// Thrown when the guild does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public Task PruneAsync(int days = 7, bool computePruneCount = true, IEnumerable includedRoles = null, string reason = null)
{
if (includedRoles != null)
{
includedRoles = includedRoles.Where(r => r != null);
var rawRoleIds = includedRoles
.Where(x => this.RolesInternal.ContainsKey(x.Id))
.Select(x => x.Id);
return this.Discord.ApiClient.BeginGuildPruneAsync(this.Id, days, computePruneCount, rawRoleIds, reason);
}
return this.Discord.ApiClient.BeginGuildPruneAsync(this.Id, days, computePruneCount, null, reason);
}
///
/// Gets integrations attached to this guild.
///
/// Collection of integrations attached to this guild.
/// Thrown when the client does not have the permission.
/// Thrown when the guild does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public Task> GetIntegrationsAsync()
=> this.Discord.ApiClient.GetGuildIntegrationsAsync(this.Id);
///
/// Attaches an integration from current user to this guild.
///
/// Integration to attach.
/// The integration after being attached to the guild.
/// Thrown when the client does not have the permission.
/// Thrown when the guild does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public Task AttachUserIntegrationAsync(DiscordIntegration integration)
=> this.Discord.ApiClient.CreateGuildIntegrationAsync(this.Id, integration.Type, integration.Id);
///
/// Modifies an integration in this guild.
///
/// Integration to modify.
/// Number of days after which the integration expires.
/// Length of grace period which allows for renewing the integration.
/// Whether emotes should be synced from this integration.
/// The modified integration.
/// Thrown when the client does not have the permission.
/// Thrown when the guild does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public Task ModifyIntegrationAsync(DiscordIntegration integration, int expireBehaviour, int expireGracePeriod, bool enableEmoticons)
=> this.Discord.ApiClient.ModifyGuildIntegrationAsync(this.Id, integration.Id, expireBehaviour, expireGracePeriod, enableEmoticons);
///
/// Removes an integration from this guild.
///
/// Integration to remove.
/// Thrown when the client does not have the permission.
/// Thrown when the guild does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public Task DeleteIntegrationAsync(DiscordIntegration integration)
=> this.Discord.ApiClient.DeleteGuildIntegrationAsync(this.Id, integration);
///
/// Forces re-synchronization of an integration for this guild.
///
/// Integration to synchronize.
/// Thrown when the client does not have the permission.
/// Thrown when the guild does not exist.
/// Thrown when an invalid parameter was provided.
/// Thrown when Discord is unable to process the request.
public Task SyncIntegrationAsync(DiscordIntegration integration)
=> this.Discord.ApiClient.SyncGuildIntegrationAsync(this.Id, integration.Id);
///
/// Gets the voice regions for this guild.
///
/// Voice regions available for this guild.
/// Thrown when Discord is unable to process the request.
public async Task> ListVoiceRegionsAsync()
{
var vrs = await this.Discord.ApiClient.GetGuildVoiceRegionsAsync(this.Id).ConfigureAwait(false);
foreach (var xvr in vrs)
this.Discord.InternalVoiceRegions.TryAdd(xvr.Id, xvr);
return vrs;
}
///
/// Gets an invite from this guild from an invite code.
///
/// The invite code
/// An invite, or null if not in cache.
public DiscordInvite GetInvite(string code)
=> this.Invites.TryGetValue(code, out var invite) ? invite : null;
///
/// Gets all the invites created for all the channels in this guild.
///
/// A collection of invites.
/// Thrown when Discord is unable to process the request.
public async Task> GetInvitesAsync()
{
var res = await this.Discord.ApiClient.GetGuildInvitesAsync(this.Id).ConfigureAwait(false);
var intents = this.Discord.Configuration.Intents;
if (!intents.HasIntent(DiscordIntents.GuildInvites))
{
foreach (var r in res)
this.Invites[r.Code] = r;
}
return res;
}
///
/// Gets the vanity invite for this guild.
///
/// A partial vanity invite.
/// Thrown when the client does not have the permission.
/// Thrown when Discord is unable to process the request.
public Task GetVanityInviteAsync()
=> this.Discord.ApiClient.GetGuildVanityUrlAsync(this.Id);
///
/// Gets all the webhooks created for all the channels in this guild.
///
/// A collection of webhooks this guild has.
/// Thrown when the client does not have the permission.
/// Thrown when Discord is unable to process the request.
public Task> GetWebhooksAsync()
=> this.Discord.ApiClient.GetGuildWebhooksAsync(this.Id);
///
/// Gets this guild's widget image.
///
/// The format of the widget.
/// The URL of the widget image.
public string GetWidgetImage(WidgetType bannerType = WidgetType.Shield)
{
var param = bannerType switch
{
WidgetType.Banner1 => "banner1",
WidgetType.Banner2 => "banner2",
WidgetType.Banner3 => "banner3",
WidgetType.Banner4 => "banner4",
_ => "shield",
};
return $"{Endpoints.BASE_URI}{Endpoints.GUILDS}/{this.Id}{Endpoints.WIDGET_PNG}?style={param}";
}
///
/// Gets a member of this guild by their user ID.
///
/// ID of the member to get.
/// Whether to fetch the member from the api prior to cache.
/// The requested member.
/// Thrown when Discord is unable to process the request.
public async Task GetMemberAsync(ulong userId, bool fetch = false)
{
if (!fetch && this.MembersInternal != null && this.MembersInternal.TryGetValue(userId, out var mbr))
return mbr;
mbr = await this.Discord.ApiClient.GetGuildMemberAsync(this.Id, userId).ConfigureAwait(false);
var intents = this.Discord.Configuration.Intents;
if (intents.HasIntent(DiscordIntents.GuildMembers))
{
if (this.MembersInternal != null)
{
this.MembersInternal[userId] = mbr;
}
}
return mbr;
}
///
/// Retrieves a full list of members from Discord. This method will bypass cache.
///
/// A collection of all members in this guild.
/// Thrown when Discord is unable to process the request.
public async Task> GetAllMembersAsync()
{
var recmbr = new HashSet();
var recd = 1000;
var last = 0ul;
while (recd > 0)
{
var tms = await this.Discord.ApiClient.ListGuildMembersAsync(this.Id, 1000, last == 0 ? null : last).ConfigureAwait(false);
recd = tms.Count;
foreach (var xtm in tms)
{
var usr = new DiscordUser(xtm.User) { Discord = this.Discord };
usr = this.Discord.UserCache.AddOrUpdate(xtm.User.Id, usr, (id, old) =>
{
old.Username = usr.Username;
old.Discord = usr.Discord;
old.AvatarHash = usr.AvatarHash;
return old;
});
recmbr.Add(new DiscordMember(xtm) { Discord = this.Discord, GuildId = this.Id });
}
var tm = tms.LastOrDefault();
last = tm?.User.Id ?? 0;
}
return new ReadOnlySet(recmbr);
}
///
/// Requests that Discord send a list of guild members based on the specified arguments. This method will fire the event.
/// If no arguments aside from and are specified, this will request all guild members.
///
/// Filters the returned members based on what the username starts with. Either this or must not be null.
/// The must also be greater than 0 if this is specified.
/// Total number of members to request. This must be greater than 0 if is specified.
/// Whether to include the associated with the fetched members.
/// Whether to limit the request to the specified user ids. Either this or must not be null.
/// The unique string to identify the response.
public async Task RequestMembersAsync(string query = "", int limit = 0, bool? presences = null, IEnumerable userIds = null, string nonce = null)
{
if (this.Discord is not DiscordClient client)
throw new InvalidOperationException("This operation is only valid for regular Discord clients.");
if (query == null && userIds == null)
throw new ArgumentException("The query and user IDs cannot both be null.");
if (query != null && userIds != null)
query = null;
var grgm = new GatewayRequestGuildMembers(this)
{
Query = query,
Limit = limit >= 0 ? limit : 0,
Presences = presences,
UserIds = userIds,
Nonce = nonce
};
var payload = new GatewayPayload
{
OpCode = GatewayOpCode.RequestGuildMembers,
Data = grgm
};
var payloadStr = JsonConvert.SerializeObject(payload, Formatting.None);
await client.WsSendAsync(payloadStr).ConfigureAwait(false);
}
///
/// Gets all the channels this guild has.
///
/// A collection of this guild's channels.
/// Thrown when Discord is unable to process the request.
public Task> GetChannelsAsync()
=> this.Discord.ApiClient.GetGuildChannelsAsync(this.Id);
///
/// Creates a new role in this guild.
///
/// Name of the role.
/// Permissions for the role.
/// Color for the role.
/// Whether the role is to be hoisted.
/// Whether the role is to be mentionable.
/// Reason for audit logs.
/// The newly-created role.
/// Thrown when the client does not have the permission.
/// Thrown when Discord is unable to process the request.
public Task CreateRoleAsync(string name = null, Permissions? permissions = null, DiscordColor? color = null, bool? hoist = null, bool? mentionable = null, string reason = null)
=> this.Discord.ApiClient.CreateGuildRoleAsync(this.Id, name, permissions, color?.Value, hoist, mentionable, reason);
///
/// Gets a role from this guild by its ID.
///
/// ID of the role to get.
/// Requested role.
/// Thrown when Discord is unable to process the request.
public DiscordRole GetRole(ulong id)
=> this.RolesInternal.TryGetValue(id, out var role) ? role : null;
///
/// Gets a channel from this guild by its ID.
///
/// ID of the channel to get.
/// Requested channel.
/// Thrown when Discord is unable to process the request.
public DiscordChannel GetChannel(ulong id)
=> this.ChannelsInternal != null && this.ChannelsInternal.TryGetValue(id, out var channel) ? channel : null;
///
/// Gets a thread from this guild by its ID.
///
/// ID of the thread to get.
/// Requested thread.
/// Thrown when Discord is unable to process the request.
public DiscordThreadChannel GetThread(ulong id)
=> this.ThreadsInternal != null && this.ThreadsInternal.TryGetValue(id, out var thread) ? thread : null;
///
/// Gets all of this guild's custom emojis.
///
/// All of this guild's custom emojis.
/// Thrown when Discord is unable to process the request.
public Task> GetEmojisAsync()
=> this.Discord.ApiClient.GetGuildEmojisAsync(this.Id);
///
/// Gets this guild's specified custom emoji.
///
/// ID of the emoji to get.
/// The requested custom emoji.
/// Thrown when Discord is unable to process the request.
public Task GetEmojiAsync(ulong id)
=> this.Discord.ApiClient.GetGuildEmojiAsync(this.Id, id);
///
/// Creates a new custom emoji for this guild.
///
/// Name of the new emoji.
/// Image to use as the emoji.
/// Roles for which the emoji will be available. This works only if your application is whitelisted as integration.
/// Reason for audit log.
/// The newly-created emoji.
/// Thrown when the client does not have the permission.
/// Thrown when Discord is unable to process the request.
public Task CreateEmojiAsync(string name, Stream image, IEnumerable roles = null, string reason = null)
{
if (string.IsNullOrWhiteSpace(name))
throw new ArgumentNullException(nameof(name));
name = name.Trim();
if (name.Length < 2 || name.Length > 50)
throw new ArgumentException("Emoji name needs to be between 2 and 50 characters long.");
if (image == null)
throw new ArgumentNullException(nameof(image));
var image64 = ImageTool.Base64FromStream(image);
return this.Discord.ApiClient.CreateGuildEmojiAsync(this.Id, name, image64, roles?.Select(xr => xr.Id), reason);
}
///
/// Modifies a this guild's custom emoji.
///
/// Emoji to modify.
/// New name for the emoji.
/// Roles for which the emoji will be available. This works only if your application is whitelisted as integration.
/// Reason for audit log.
/// The modified emoji.
/// Thrown when the client does not have the permission.
/// Thrown when Discord is unable to process the request.
public Task ModifyEmojiAsync(DiscordGuildEmoji emoji, string name, IEnumerable roles = null, string reason = null)
{
if (emoji == null)
throw new ArgumentNullException(nameof(emoji));
if (emoji.Guild.Id != this.Id)
throw new ArgumentException("This emoji does not belong to this guild.");
if (string.IsNullOrWhiteSpace(name))
throw new ArgumentNullException(nameof(name));
name = name.Trim();
return name.Length < 2 || name.Length > 50
? throw new ArgumentException("Emoji name needs to be between 2 and 50 characters long.")
: this.Discord.ApiClient.ModifyGuildEmojiAsync(this.Id, emoji.Id, name, roles?.Select(xr => xr.Id), reason);
}
///
/// Deletes this guild's custom emoji.
///
/// Emoji to delete.
/// Reason for audit log.
/// Thrown when the client does not have the permission.
/// Thrown when Discord is unable to process the request.
public Task DeleteEmojiAsync(DiscordGuildEmoji emoji, string reason = null) =>
emoji == null
? throw new ArgumentNullException(nameof(emoji))
: emoji.Guild.Id != this.Id
? throw new ArgumentException("This emoji does not belong to this guild.")
: this.Discord.ApiClient.DeleteGuildEmojiAsync(this.Id, emoji.Id, reason);
///
/// Gets all of this guild's custom stickers.
///
/// All of this guild's custom stickers.
/// Thrown when Discord is unable to process the request.
public async Task> GetStickersAsync()
{
var stickers = await this.Discord.ApiClient.GetGuildStickersAsync(this.Id);
foreach (var xstr in stickers)
{
this.StickersInternal.AddOrUpdate(xstr.Id, xstr, (id, old) =>
{
old.Name = xstr.Name;
old.Description = xstr.Description;
old.InternalTags = xstr.InternalTags;
return old;
});
}
return stickers;
}
///
/// Gets a sticker
///
/// Thrown when the sticker could not be found.
/// Thrown when the client does not have the permission.
/// Thrown when Discord is unable to process the request.
/// Sticker does not belong to a guild.
public Task GetStickerAsync(ulong stickerId)
=> this.Discord.ApiClient.GetGuildStickerAsync(this.Id, stickerId);
///
/// Creates a sticker
///
/// The name of the sticker.
/// The optional description of the sticker.
/// The emoji to associate the sticker with.
/// The file format the sticker is written in.
/// The sticker.
/// Audit log reason
/// Thrown when the client does not have the permission.
/// Thrown when Discord is unable to process the request.
public Task CreateStickerAsync(string name, string description, DiscordEmoji emoji, Stream file, StickerFormat format, string reason = null)
{
var fileExt = format switch
{
StickerFormat.Png => "png",
StickerFormat.Apng => "png",
StickerFormat.Lottie => "json",
_ => throw new InvalidOperationException("This format is not supported.")
};
var contentType = format switch
{
StickerFormat.Png => "image/png",
StickerFormat.Apng => "image/png",
StickerFormat.Lottie => "application/json",
_ => throw new InvalidOperationException("This format is not supported.")
};
return emoji.Id is not 0
? throw new InvalidOperationException("Only unicode emoji can be used for stickers.")
: name.Length < 2 || name.Length > 30
? throw new ArgumentOutOfRangeException(nameof(name), "Sticker name needs to be between 2 and 30 characters long.")
: description.Length < 1 || description.Length > 100
? throw new ArgumentOutOfRangeException(nameof(description), "Sticker description needs to be between 1 and 100 characters long.")
: this.Discord.ApiClient.CreateGuildStickerAsync(this.Id, name, description, emoji.GetDiscordName().Replace(":", ""), new DiscordMessageFile("sticker", file, null, fileExt, contentType), reason);
}
///
/// Modifies a sticker
///
/// The id of the sticker to modify
/// The name of the sticker
/// The description of the sticker
/// The emoji to associate with this sticker.
/// Audit log reason
/// A sticker object
/// Thrown when the sticker could not be found.
/// Thrown when the client does not have the permission.
/// Thrown when Discord is unable to process the request.
/// Sticker does not belong to a guild.
public async Task ModifyStickerAsync(ulong sticker, Optional name, Optional description, Optional emoji, string reason = null)
{
if (!this.StickersInternal.TryGetValue(sticker, out var stickerobj) || stickerobj.Guild.Id != this.Id)
throw new ArgumentException("This sticker does not belong to this guild.");
if (name.HasValue && (name.Value.Length < 2 || name.Value.Length > 30))
throw new ArgumentException("Sticker name needs to be between 2 and 30 characters long.");
if (description.HasValue && (description.Value.Length < 1 || description.Value.Length > 100))
throw new ArgumentException("Sticker description needs to be between 1 and 100 characters long.");
if (emoji.HasValue && emoji.Value.Id > 0)
throw new ArgumentException("Only unicode emojis can be used with stickers.");
string uemoji = null;
if (emoji.HasValue)
uemoji = emoji.Value.GetDiscordName().Replace(":", "");
var usticker = await this.Discord.ApiClient.ModifyGuildStickerAsync(this.Id, sticker, name, description, uemoji, reason).ConfigureAwait(false);
if (this.StickersInternal.TryGetValue(usticker.Id, out var old))
this.StickersInternal.TryUpdate(usticker.Id, usticker, old);
return usticker;
}
///
/// Modifies a sticker
///
/// The sticker to modify
/// The name of the sticker
/// The description of the sticker
/// The emoji to associate with this sticker.
/// Audit log reason
/// A sticker object
/// Thrown when the sticker could not be found.
/// Thrown when the client does not have the permission.
/// Thrown when Discord is unable to process the request.
/// Sticker does not belong to a guild.
public Task ModifyStickerAsync(DiscordSticker sticker, Optional name, Optional description, Optional emoji, string reason = null)
=> this.ModifyStickerAsync(sticker.Id, name, description, emoji, reason);
///
/// Deletes a sticker
///
/// Id of sticker to delete
/// Audit log reason
/// Thrown when the sticker could not be found.
/// Thrown when the client does not have the permission.
/// Thrown when Discord is unable to process the request.
/// Sticker does not belong to a guild.
public Task DeleteStickerAsync(ulong sticker, string reason = null) =>
!this.StickersInternal.TryGetValue(sticker, out var stickerobj)
? throw new ArgumentNullException(nameof(sticker))
: stickerobj.Guild.Id != this.Id
? throw new ArgumentException("This sticker does not belong to this guild.")
: this.Discord.ApiClient.DeleteGuildStickerAsync(this.Id, sticker, reason);
///
/// Deletes a sticker
///
/// Sticker to delete
/// Audit log reason
/// Thrown when the sticker could not be found.
/// Thrown when the client does not have the permission.
/// Thrown when Discord is unable to process the request.
/// Sticker does not belong to a guild.
public Task DeleteStickerAsync(DiscordSticker sticker, string reason = null)
=> this.DeleteStickerAsync(sticker.Id, reason);
///
/// Gets the default channel for this guild.
/// Default channel is the first channel current member can see.
///
/// This member's default guild.
/// Thrown when Discord is unable to process the request.
public DiscordChannel GetDefaultChannel() =>
this.ChannelsInternal?.Values.Where(xc => xc.Type == ChannelType.Text)
.OrderBy(xc => xc.Position)
.FirstOrDefault(xc => (xc.PermissionsFor(this.CurrentMember) & DisCatSharp.Enums.Permissions.AccessChannels) == DisCatSharp.Enums.Permissions.AccessChannels);
///
/// Gets the guild's widget
///
/// The guild's widget
public Task GetWidgetAsync()
=> this.Discord.ApiClient.GetGuildWidgetAsync(this.Id);
///
/// Gets the guild's widget settings
///
/// The guild's widget settings
public Task GetWidgetSettingsAsync()
=> this.Discord.ApiClient.GetGuildWidgetSettingsAsync(this.Id);
///
/// Modifies the guild's widget settings
///
/// If the widget is enabled or not
/// Widget channel
/// Reason the widget settings were modified
/// The newly modified widget settings
public Task ModifyWidgetSettingsAsync(bool? isEnabled = null, DiscordChannel channel = null, string reason = null)
=> this.Discord.ApiClient.ModifyGuildWidgetSettingsAsync(this.Id, isEnabled, channel?.Id, reason);
///
/// Gets all of this guild's templates.
///
/// All of the guild's templates.
/// Throws when the client does not have the permission.
/// Thrown when Discord is unable to process the request.
public Task> GetTemplatesAsync()
=> this.Discord.ApiClient.GetGuildTemplatesAsync(this.Id);
///
/// Creates a guild template.
///
/// Name of the template.
/// Description of the template.
/// The template created.
/// Throws when a template already exists for the guild or a null parameter is provided for the name.
/// Throws when the client does not have the permission.
/// Thrown when Discord is unable to process the request.
public Task CreateTemplateAsync(string name, string description = null)
=> this.Discord.ApiClient.CreateGuildTemplateAsync(this.Id, name, description);
///
/// Syncs the template to the current guild's state.
///
/// The code of the template to sync.
/// The template synced.
/// Throws when the template for the code cannot be found
/// Throws when the client does not have the permission.
/// Thrown when Discord is unable to process the request.
public Task SyncTemplateAsync(string code)
=> this.Discord.ApiClient.SyncGuildTemplateAsync(this.Id, code);
///
/// Modifies the template's metadata.
///
/// The template's code.
/// Name of the template.
/// Description of the template.
/// The template modified.
/// Throws when the template for the code cannot be found
/// Throws when the client does not have the permission.
/// Thrown when Discord is unable to process the request.
public Task ModifyTemplateAsync(string code, string name = null, string description = null)
=> this.Discord.ApiClient.ModifyGuildTemplateAsync(this.Id, code, name, description);
///
/// Deletes the template.
///
/// The code of the template to delete.
/// The deleted template.
/// Throws when the template for the code cannot be found
/// Throws when the client does not have the permission.
/// Thrown when Discord is unable to process the request.
public Task DeleteTemplateAsync(string code)
=> this.Discord.ApiClient.DeleteGuildTemplateAsync(this.Id, code);
///
/// Gets this guild's membership screening form.
///
/// This guild's membership screening form.
/// Thrown when Discord is unable to process the request.
public Task GetMembershipScreeningFormAsync()
=> this.Discord.ApiClient.GetGuildMembershipScreeningFormAsync(this.Id);
///
/// Modifies this guild's membership screening form.
///
/// Action to perform
/// The modified screening form.
/// Thrown when the client doesn't have the permission, or community is not enabled on this guild.
/// Thrown when Discord is unable to process the request.
public async Task ModifyMembershipScreeningFormAsync(Action action)
{
var mdl = new MembershipScreeningEditModel();
action(mdl);
return await this.Discord.ApiClient.ModifyGuildMembershipScreeningFormAsync(this.Id, mdl.Enabled, mdl.Fields, mdl.Description);
}
///
/// Gets all the application commands in this guild.
///
/// A list of application commands in this guild.
public Task> GetApplicationCommandsAsync() =>
this.Discord.ApiClient.GetGuildApplicationCommandsAsync(this.Discord.CurrentApplication.Id, this.Id);
///
/// Overwrites the existing application commands in this guild. New commands are automatically created and missing commands are automatically delete
///
/// The list of commands to overwrite with.
/// The list of guild commands
public Task> BulkOverwriteApplicationCommandsAsync(IEnumerable commands) =>
this.Discord.ApiClient.BulkOverwriteGuildApplicationCommandsAsync(this.Discord.CurrentApplication.Id, this.Id, commands);
///
/// Creates or overwrites a application command in this guild.
///
/// The command to create.
/// The created command.
public Task CreateApplicationCommandAsync(DiscordApplicationCommand command) =>
this.Discord.ApiClient.CreateGuildApplicationCommandAsync(this.Discord.CurrentApplication.Id, this.Id, command);
///
/// Edits a application command in this guild.
///
/// The id of the command to edit.
/// Action to perform.
/// The edit command.
public async Task EditApplicationCommandAsync(ulong commandId, Action action)
{
var mdl = new ApplicationCommandEditModel();
action(mdl);
return await this.Discord.ApiClient.EditGuildApplicationCommandAsync(this.Discord.CurrentApplication.Id, this.Id, commandId, mdl.Name, mdl.Description, mdl.Options, mdl.NameLocalizations, mdl.DescriptionLocalizations, mdl.DefaultMemberPermissions, mdl.DmPermission, mdl.IsNsfw).ConfigureAwait(false);
}
///
/// Gets this guild's welcome screen.
///
/// This guild's welcome screen object.
/// Thrown when Discord is unable to process the request.
public Task GetWelcomeScreenAsync() =>
this.Discord.ApiClient.GetGuildWelcomeScreenAsync(this.Id);
///
/// Modifies this guild's welcome screen.
///
/// Action to perform.
/// The modified welcome screen.
/// Thrown when the client doesn't have the permission, or community is not enabled on this guild.
/// Thrown when Discord is unable to process the request.
public async Task ModifyWelcomeScreenAsync(Action action)
{
var mdl = new WelcomeScreenEditModel();
action(mdl);
return await this.Discord.ApiClient.ModifyGuildWelcomeScreenAsync(this.Id, mdl.Enabled, mdl.WelcomeChannels, mdl.Description).ConfigureAwait(false);
}
#endregion
///
/// Returns a string representation of this guild.
///
/// String representation of this guild.
public override string ToString()
=> $"Guild {this.Id}; {this.Name}";
///
/// Checks whether this is equal to another object.
///
/// Object to compare to.
/// Whether the object is equal to this .
public override bool Equals(object obj)
=> this.Equals(obj as DiscordGuild);
///
/// Checks whether this is equal to another .
///
/// to compare to.
/// Whether the is equal to this .
public bool Equals(DiscordGuild e)
=> e is not null && (ReferenceEquals(this, e) || this.Id == e.Id);
///
/// Gets the hash code for this .
///
/// The hash code for this .
public override int GetHashCode()
=> this.Id.GetHashCode();
///
/// Gets whether the two objects are equal.
///
/// First guild to compare.
/// Second guild to compare.
/// Whether the two guilds are equal.
public static bool operator ==(DiscordGuild e1, DiscordGuild e2)
{
var o1 = e1 as object;
var o2 = e2 as object;
return (o1 != null || o2 == null) && (o1 == null || o2 != null) && ((o1 == null && o2 == null) || e1.Id == e2.Id);
}
///
/// Gets whether the two objects are not equal.
///
/// First guild to compare.
/// Second guild to compare.
/// Whether the two guilds are not equal.
public static bool operator !=(DiscordGuild e1, DiscordGuild e2)
=> !(e1 == e2);
}
diff --git a/DisCatSharp/Net/Abstractions/Rest/RestGuildPayloads.cs b/DisCatSharp/Net/Abstractions/Rest/RestGuildPayloads.cs
index 20660a899..d0c62911d 100644
--- a/DisCatSharp/Net/Abstractions/Rest/RestGuildPayloads.cs
+++ b/DisCatSharp/Net/Abstractions/Rest/RestGuildPayloads.cs
@@ -1,751 +1,772 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
// Copyright (c) 2021-2022 AITSYS
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using System;
using System.Collections.Generic;
using DisCatSharp.Entities;
using DisCatSharp.Enums;
using Newtonsoft.Json;
namespace DisCatSharp.Net.Abstractions;
///
/// The reason action.
///
internal interface IReasonAction
{
///
/// Gets or sets the reason.
///
string Reason { get; set; }
//[JsonProperty("reason", NullValueHandling = NullValueHandling.Ignore)]
//public string Reason { get; set; }
}
///
/// Represents a guild create payload.
///
internal class RestGuildCreatePayload
{
///
/// Gets or sets the name.
///
[JsonProperty("name", NullValueHandling = NullValueHandling.Ignore)]
public string Name { get; set; }
///
/// Gets or sets the region id.
///
[JsonProperty("region", NullValueHandling = NullValueHandling.Ignore)]
public string RegionId { get; set; }
///
/// Gets or sets the icon base64.
///
[JsonProperty("icon", NullValueHandling = NullValueHandling.Include)]
public Optional IconBase64 { get; set; }
///
/// Gets or sets the verification level.
///
[JsonProperty("verification_level", NullValueHandling = NullValueHandling.Ignore)]
public VerificationLevel? VerificationLevel { get; set; }
///
/// Gets or sets the default message notifications.
///
[JsonProperty("default_message_notifications", NullValueHandling = NullValueHandling.Ignore)]
public DefaultMessageNotifications? DefaultMessageNotifications { get; set; }
///
/// Gets or sets the system channel flags.
///
[JsonProperty("system_channel_flags", NullValueHandling = NullValueHandling.Ignore)]
public SystemChannelFlags? SystemChannelFlags { get; set; }
///
/// Gets or sets the roles.
///
[JsonProperty("roles", NullValueHandling = NullValueHandling.Ignore)]
public IEnumerable Roles { get; set; }
///
/// Gets or sets the channels.
///
[JsonProperty("channels", NullValueHandling = NullValueHandling.Ignore)]
public IEnumerable Channels { get; set; }
}
///
/// Represents a guild create from template payload.
///
internal sealed class RestGuildCreateFromTemplatePayload
{
///
/// Gets or sets the name.
///
[JsonProperty("name", NullValueHandling = NullValueHandling.Ignore)]
public string Name { get; set; }
///
/// Gets or sets the icon base64.
///
[JsonProperty("icon", NullValueHandling = NullValueHandling.Include)]
public Optional IconBase64 { get; set; }
}
///
/// Represents a guild modify payload.
///
internal sealed class RestGuildModifyPayload
{
///
/// Gets or sets the name.
///
[JsonProperty("name")]
public Optional Name { get; set; }
///
/// Gets or sets the icon base64.
///
[JsonProperty("icon")]
public Optional IconBase64 { get; set; }
///
/// Gets or sets the verification level.
///
[JsonProperty("verification_level")]
public Optional VerificationLevel { get; set; }
///
/// Gets or sets the default message notifications.
///
[JsonProperty("default_message_notifications")]
public Optional DefaultMessageNotifications { get; set; }
///
/// Gets or sets the owner id.
///
[JsonProperty("owner_id")]
public Optional OwnerId { get; set; }
///
/// Gets or sets the splash base64.
///
[JsonProperty("splash")]
public Optional SplashBase64 { get; set; }
///
/// Gets or sets the banner base64.
///
[JsonProperty("banner")]
public Optional BannerBase64 { get; set; }
///
/// Gets or sets the discovery splash base64.
///
[JsonProperty("discovery_splash")]
public Optional DiscoverySplashBase64 { get; set; }
///
/// Gets or sets the afk channel id.
///
[JsonProperty("afk_channel_id")]
public Optional AfkChannelId { get; set; }
///
/// Gets or sets the afk timeout.
///
[JsonProperty("afk_timeout")]
public Optional AfkTimeout { get; set; }
///
/// Gets or sets the mfa level.
///
[JsonProperty("mfa_level")]
public Optional MfaLevel { get; set; }
///
/// Gets or sets the explicit content filter.
///
[JsonProperty("explicit_content_filter")]
public Optional ExplicitContentFilter { get; set; }
///
/// Gets or sets the system channel id.
///
[JsonProperty("system_channel_id", NullValueHandling = NullValueHandling.Include)]
public Optional SystemChannelId { get; set; }
+ ///
+ /// Gets or sets the safety alerts channel id.
+ ///
+ [JsonProperty("safety_alerts_channel_id", NullValueHandling = NullValueHandling.Include)]
+ internal Optional SafetyAlertsChannelId { get; set; }
+
///
/// Gets or sets the system channel flags.
///
[JsonProperty("system_channel_flags", NullValueHandling = NullValueHandling.Ignore)]
public Optional SystemChannelFlags { get; set; }
///
/// Gets or sets the rules channel id.
///
[JsonProperty("rules_channel_id")]
public Optional RulesChannelId { get; set; }
///
/// Gets or sets the public updates channel id.
///
[JsonProperty("public_updates_channel_id")]
public Optional PublicUpdatesChannelId { get; set; }
///
/// Gets or sets the preferred locale.
///
[JsonProperty("preferred_locale")]
public Optional PreferredLocale { get; set; }
///
/// Gets or sets the description.
///
[JsonProperty("description", NullValueHandling = NullValueHandling.Include)]
public Optional Description { get; set; }
///
/// Gets or sets whether the premium progress bar should be enabled.
///
[JsonProperty("premium_progress_bar_enabled", NullValueHandling = NullValueHandling.Ignore)]
public Optional PremiumProgressBarEnabled { get; set; }
}
///
/// Represents a guild mfa level modify payload.
///
internal sealed class RestGuildMfaLevelModifyPayload
{
///
/// Gets or sets the mfa level.
///
[JsonProperty("level")]
public MfaLevel Level { get; set; }
}
///
/// Represents a guild community modify payload.
///
internal sealed class RestGuildFeatureModifyPayload
{
///
/// Gets or sets the features.
///
[JsonProperty("features", NullValueHandling = NullValueHandling.Ignore)]
public List Features { get; set; }
}
+internal sealed class RestGuildSafetyModifyPayload
+{
+ ///
+ /// Gets or sets the safety alerts channel id.
+ ///
+ [JsonProperty("safety_alerts_channel_id", NullValueHandling = NullValueHandling.Include)]
+ internal Optional SafetyAlertsChannelId { get; set; }
+
+ ///
+ /// Gets or sets the features.
+ ///
+ [JsonProperty("features", NullValueHandling = NullValueHandling.Ignore)]
+ public List Features { get; set; }
+}
+
///
/// Represents a guild community modify payload.
///
internal sealed class RestGuildCommunityModifyPayload
{
///
/// Gets or sets the verification level.
///
[JsonProperty("verification_level", NullValueHandling = NullValueHandling.Ignore)]
public Optional VerificationLevel { get; set; }
///
/// Gets or sets the default message notifications.
///
[JsonProperty("default_message_notifications", NullValueHandling = NullValueHandling.Ignore)]
public Optional DefaultMessageNotifications { get; set; }
///
/// Gets or sets the explicit content filter.
///
[JsonProperty("explicit_content_filter", NullValueHandling = NullValueHandling.Ignore)]
public Optional ExplicitContentFilter { get; set; }
///
/// Gets or sets the rules channel id.
///
[JsonProperty("rules_channel_id", NullValueHandling = NullValueHandling.Ignore)]
public Optional RulesChannelId { get; set; }
///
/// Gets or sets the public updates channel id.
///
[JsonProperty("public_updates_channel_id", NullValueHandling = NullValueHandling.Ignore)]
public Optional PublicUpdatesChannelId { get; set; }
///
/// Gets or sets the preferred locale.
///
[JsonProperty("preferred_locale")]
public Optional PreferredLocale { get; set; }
///
/// Gets or sets the description.
///
[JsonProperty("description", NullValueHandling = NullValueHandling.Include)]
public Optional Description { get; set; }
///
/// Gets or sets the features.
///
[JsonProperty("features", NullValueHandling = NullValueHandling.Ignore)]
public List Features { get; set; }
}
///
/// Represents a guild member add payload.
///
internal sealed class RestGuildMemberAddPayload : IOAuth2Payload
{
///
/// Gets or sets the access token.
///
[JsonProperty("access_token")]
public string AccessToken { get; set; }
///
/// Gets or sets the nickname.
///
[JsonProperty("nick", NullValueHandling = NullValueHandling.Ignore)]
public string Nickname { get; set; }
///
/// Gets or sets the roles.
///
[JsonProperty("roles", NullValueHandling = NullValueHandling.Ignore)]
public IEnumerable Roles { get; set; }
///
/// Gets or sets a value indicating whether mute.
///
[JsonProperty("mute", NullValueHandling = NullValueHandling.Ignore)]
public bool? Mute { get; set; }
///
/// Gets or sets a value indicating whether deaf.
///
[JsonProperty("deaf", NullValueHandling = NullValueHandling.Ignore)]
public bool? Deaf { get; set; }
}
///
/// Represents a guild channel reorder payload.
///
internal sealed class RestGuildChannelReorderPayload
{
///
/// Gets or sets the channel id.
///
[JsonProperty("id", NullValueHandling = NullValueHandling.Ignore)]
public ulong ChannelId { get; set; }
///
/// Gets or sets the position.
///
[JsonProperty("position", NullValueHandling = NullValueHandling.Ignore)]
public int Position { get; set; }
}
///
/// Represents a guild channel new parent payload.
///
internal sealed class RestGuildChannelNewParentPayload
{
///
/// Gets or sets the channel id.
///
[JsonProperty("id", NullValueHandling = NullValueHandling.Ignore)]
public ulong ChannelId { get; set; }
///
/// Gets or sets the position.
///
[JsonProperty("position", NullValueHandling = NullValueHandling.Ignore)]
public int Position { get; set; }
///
/// Gets or sets the parent id.
///
[JsonProperty("parent_id", NullValueHandling = NullValueHandling.Ignore)]
public Optional ParentId { get; set; }
///
/// Gets or sets a value indicating whether lock permissions.
///
[JsonProperty("lock_permissions", NullValueHandling = NullValueHandling.Ignore)]
public bool? LockPermissions { get; set; }
}
///
/// Represents a guild channel no parent payload.
///
internal sealed class RestGuildChannelNoParentPayload
{
///
/// Gets or sets the channel id.
///
[JsonProperty("id", NullValueHandling = NullValueHandling.Ignore)]
public ulong ChannelId { get; set; }
///
/// Gets or sets the position.
///
[JsonProperty("position", NullValueHandling = NullValueHandling.Ignore)]
public int Position { get; set; }
///
/// Gets or sets the parent id.
///
[JsonProperty("parent_id", NullValueHandling = NullValueHandling.Include)]
public Optional ParentId { get; set; }
}
///
/// Represents a guild role reorder payload.
///
internal sealed class RestGuildRoleReorderPayload
{
///
/// Gets or sets the role id.
///
[JsonProperty("id", NullValueHandling = NullValueHandling.Ignore)]
public ulong RoleId { get; set; }
///
/// Gets or sets the position.
///
[JsonProperty("position", NullValueHandling = NullValueHandling.Ignore)]
public int Position { get; set; }
}
///
/// Represents a guild member modify payload.
///
internal sealed class RestGuildMemberModifyPayload
{
///
/// Gets or sets the nickname.
///
[JsonProperty("nick")]
public Optional Nickname { get; set; }
///
/// Gets or sets the role ids.
///
[JsonProperty("roles")]
public Optional> RoleIds { get; set; }
///
/// Gets or sets the mute.
///
[JsonProperty("mute")]
public Optional Mute { get; set; }
///
/// Gets or sets the deafen.
///
[JsonProperty("deaf")]
public Optional Deafen { get; set; }
///
/// Gets or sets the voice channel id.
///
[JsonProperty("channel_id")]
public Optional VoiceChannelId { get; set; }
[JsonProperty("flags")]
public Optional Flags { get; set; }
}
///
/// Represents a guild member timeout modify payload.
///
internal sealed class RestGuildMemberTimeoutModifyPayload
{
///
/// Gets or sets the date until the member can communicate again.
///
[JsonProperty("communication_disabled_until")]
public DateTimeOffset? CommunicationDisabledUntil { get; internal set; }
}
///
/// Represents a guild role payload.
///
internal sealed class RestGuildRolePayload
{
///
/// Gets or sets the name.
///
[JsonProperty("name", NullValueHandling = NullValueHandling.Ignore)]
public string Name { get; set; }
///
/// Gets or sets the permissions.
///
[JsonProperty("permissions", NullValueHandling = NullValueHandling.Ignore)]
public Permissions? Permissions { get; set; }
///
/// Gets or sets the color.
///
[JsonProperty("color", NullValueHandling = NullValueHandling.Ignore)]
public int? Color { get; set; }
///
/// Gets or sets a value indicating whether hoist.
///
[JsonProperty("hoist", NullValueHandling = NullValueHandling.Ignore)]
public bool? Hoist { get; set; }
///
/// Gets or sets a value indicating whether mentionable.
///
[JsonProperty("mentionable", NullValueHandling = NullValueHandling.Ignore)]
public bool? Mentionable { get; set; }
///
/// Gets or sets the icon base64.
///
[JsonProperty("icon")]
public Optional IconBase64 { get; set; }
///
/// Gets or sets the icon base64.
///
[JsonProperty("unicode_emoji")]
public Optional UnicodeEmoji { get; set; }
}
///
/// Represents a guild prune result payload.
///
internal sealed class RestGuildPruneResultPayload
{
///
/// Gets or sets the pruned.
///
[JsonProperty("pruned", NullValueHandling = NullValueHandling.Ignore)]
public int? Pruned { get; set; }
}
///
/// Represents a guild integration attach payload.
///
internal sealed class RestGuildIntegrationAttachPayload
{
///
/// Gets or sets the type.
///
[JsonProperty("type")]
public string Type { get; set; }
///
/// Gets or sets the id.
///
[JsonProperty("id")]
public ulong Id { get; set; }
}
///
/// Represents a guild integration modify payload.
///
internal sealed class RestGuildIntegrationModifyPayload
{
///
/// Gets or sets the expire behavior.
///
[JsonProperty("expire_behavior", NullValueHandling = NullValueHandling.Ignore)]
public int? ExpireBehavior { get; set; }
///
/// Gets or sets the expire grace period.
///
[JsonProperty("expire_grace_period", NullValueHandling = NullValueHandling.Ignore)]
public int? ExpireGracePeriod { get; set; }
///
/// Gets or sets a value indicating whether enable emoticons.
///
[JsonProperty("enable_emoticons", NullValueHandling = NullValueHandling.Ignore)]
public bool? EnableEmoticons { get; set; }
}
///
/// Represents a guild emoji modify payload.
///
internal class RestGuildEmojiModifyPayload
{
///
/// Gets or sets the name.
///
[JsonProperty("name")]
public string Name { get; set; }
///
/// Gets or sets the roles.
///
[JsonProperty("roles", NullValueHandling = NullValueHandling.Ignore)]
public ulong[] Roles { get; set; }
}
///
/// Represents a guild emoji create payload.
///
internal class RestGuildEmojiCreatePayload : RestGuildEmojiModifyPayload
{
///
/// Gets or sets the image b64.
///
[JsonProperty("image")]
public string ImageB64 { get; set; }
}
///
/// Represents a guild widget settings payload.
///
internal class RestGuildWidgetSettingsPayload
{
///
/// Gets or sets a value indicating whether enabled.
///
[JsonProperty("enabled", NullValueHandling = NullValueHandling.Ignore)]
public bool? Enabled { get; set; }
///
/// Gets or sets the channel id.
///
[JsonProperty("channel_id", NullValueHandling = NullValueHandling.Ignore)]
public ulong? ChannelId { get; set; }
}
///
/// Represents a guild template create or modify payload.
///
internal class RestGuildTemplateCreateOrModifyPayload
{
///
/// Gets or sets the name.
///
[JsonProperty("name", NullValueHandling = NullValueHandling.Include)]
public string Name { get; set; }
///
/// Gets or sets the description.
///
[JsonProperty("description", NullValueHandling = NullValueHandling.Include)]
public string Description { get; set; }
}
///
/// Represents a guild membership screening form modify payload.
///
internal class RestGuildMembershipScreeningFormModifyPayload
{
///
/// Gets or sets the enabled.
///
[JsonProperty("enabled", NullValueHandling = NullValueHandling.Ignore)]
public Optional Enabled { get; set; }
///
/// Gets or sets the fields.
///
[JsonProperty("form_fields", NullValueHandling = NullValueHandling.Ignore)]
public Optional Fields { get; set; }
///
/// Gets or sets the description.
///
[JsonProperty("description", NullValueHandling = NullValueHandling.Ignore)]
public Optional Description { get; set; }
}
///
/// Represents a guild welcome screen modify payload.
///
internal class RestGuildWelcomeScreenModifyPayload
{
///
/// Gets or sets the enabled.
///
[JsonProperty("enabled", NullValueHandling = NullValueHandling.Ignore)]
public Optional Enabled { get; set; }
///
/// Gets or sets the welcome channels.
///
[JsonProperty("welcome_channels", NullValueHandling = NullValueHandling.Ignore)]
public Optional> WelcomeChannels { get; set; }
///
/// Gets or sets the description.
///
[JsonProperty("description", NullValueHandling = NullValueHandling.Ignore)]
public Optional Description { get; set; }
}
///
/// Represents a guild update current user voice state payload.
///
internal class RestGuildUpdateCurrentUserVoiceStatePayload
{
///
/// Gets or sets the channel id.
///
[JsonProperty("channel_id")]
public ulong ChannelId { get; set; }
///
/// Gets or sets a value indicating whether suppress.
///
[JsonProperty("suppress", NullValueHandling = NullValueHandling.Ignore)]
public bool? Suppress { get; set; }
///
/// Gets or sets the request to speak timestamp.
///
[JsonProperty("request_to_speak_timestamp", NullValueHandling = NullValueHandling.Ignore)]
public DateTimeOffset? RequestToSpeakTimestamp { get; set; }
}
///
/// Represents a guild update user voice state payload.
///
internal class RestGuildUpdateUserVoiceStatePayload
{
///
/// Gets or sets the channel id.
///
[JsonProperty("channel_id")]
public ulong ChannelId { get; set; }
///
/// Gets or sets a value indicating whether suppress.
///
[JsonProperty("suppress", NullValueHandling = NullValueHandling.Ignore)]
public bool? Suppress { get; set; }
}
diff --git a/DisCatSharp/Net/Rest/DiscordApiClient.cs b/DisCatSharp/Net/Rest/DiscordApiClient.cs
index 3fab7c71f..2de0e42fc 100644
--- a/DisCatSharp/Net/Rest/DiscordApiClient.cs
+++ b/DisCatSharp/Net/Rest/DiscordApiClient.cs
@@ -1,5757 +1,5793 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
// Copyright (c) 2021-2022 AITSYS
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Globalization;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using DisCatSharp.Entities;
using DisCatSharp.Enums;
using DisCatSharp.Net.Abstractions;
using DisCatSharp.Net.Serialization;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using static System.Net.Mime.MediaTypeNames;
namespace DisCatSharp.Net;
///
/// Represents a discord api client.
///
public sealed class DiscordApiClient
{
///
/// The audit log reason header name.
///
private const string REASON_HEADER_NAME = "X-Audit-Log-Reason";
///
/// Gets the discord client.
///
internal BaseDiscordClient Discord { get; }
///
/// Gets the rest client.
///
internal RestClient Rest { get; }
///
/// Initializes a new instance of the class.
///
/// The client.
internal DiscordApiClient(BaseDiscordClient client)
{
this.Discord = client;
this.Rest = new RestClient(client);
}
///
/// Initializes a new instance of the class.
///
/// The proxy.
/// The timeout.
/// If true, use relative rate limit.
/// The logger.
internal DiscordApiClient(IWebProxy proxy, TimeSpan timeout, bool useRelativeRateLimit, ILogger logger) // This is for meta-clients, such as the webhook client
{
this.Rest = new RestClient(proxy, timeout, useRelativeRateLimit, logger);
}
///
/// Builds the query string.
///
/// The values.
/// Whether this query will be transmitted via POST.
private static string BuildQueryString(IDictionary values, bool post = false)
{
if (values == null || values.Count == 0)
return string.Empty;
var valsCollection = values.Select(xkvp =>
$"{WebUtility.UrlEncode(xkvp.Key)}={WebUtility.UrlEncode(xkvp.Value)}");
var vals = string.Join("&", valsCollection);
return !post ? $"?{vals}" : vals;
}
///
/// Prepares the message.
///
/// The msg_raw.
/// A DiscordMessage.
private DiscordMessage PrepareMessage(JToken msgRaw)
{
var author = msgRaw["author"].ToObject();
var ret = msgRaw.ToDiscordObject();
ret.Discord = this.Discord;
this.PopulateMessage(author, ret);
var referencedMsg = msgRaw["referenced_message"];
if (ret.MessageType == MessageType.Reply && !string.IsNullOrWhiteSpace(referencedMsg?.ToString()))
{
author = referencedMsg["author"].ToObject();
ret.ReferencedMessage.Discord = this.Discord;
this.PopulateMessage(author, ret.ReferencedMessage);
}
if (ret.Channel != null)
return ret;
var channel = !ret.GuildId.HasValue
? new DiscordDmChannel
{
Id = ret.ChannelId,
Discord = this.Discord,
Type = ChannelType.Private
}
: new DiscordChannel
{
Id = ret.ChannelId,
GuildId = ret.GuildId,
Discord = this.Discord
};
ret.Channel = channel;
return ret;
}
///
/// Populates the message.
///
/// The author.
/// The message.
private void PopulateMessage(TransportUser author, DiscordMessage ret)
{
var guild = ret.Channel?.Guild;
//If this is a webhook, it shouldn't be in the user cache.
if (author.IsBot && int.Parse(author.Discriminator) == 0)
{
ret.Author = new DiscordUser(author) { Discord = this.Discord };
}
else
{
if (!this.Discord.UserCache.TryGetValue(author.Id, out var usr))
{
this.Discord.UserCache[author.Id] = usr = new DiscordUser(author) { Discord = this.Discord };
}
if (guild != null)
{
if (!guild.Members.TryGetValue(author.Id, out var mbr))
mbr = new DiscordMember(usr) { Discord = this.Discord, GuildId = guild.Id };
ret.Author = mbr;
}
else
{
ret.Author = usr;
}
}
ret.PopulateMentions();
ret.ReactionsInternal ??= new List();
foreach (var xr in ret.ReactionsInternal)
xr.Emoji.Discord = this.Discord;
}
///
/// Executes a rest request.
///
/// The client.
/// The bucket.
/// The url.
/// The method.
/// The route.
/// The headers.
/// The payload.
/// The ratelimit wait override.
internal Task DoRequestAsync(BaseDiscordClient client, RateLimitBucket bucket, Uri url, RestRequestMethod method, string route, IReadOnlyDictionary headers = null, string payload = null, double? ratelimitWaitOverride = null)
{
var req = new RestRequest(client, bucket, url, method, route, headers, payload, ratelimitWaitOverride);
if (this.Discord != null)
this.Rest.ExecuteRequestAsync(req).LogTaskFault(this.Discord.Logger, LogLevel.Error, LoggerEvents.RestError, $"Error while executing request. Url: {url.AbsoluteUri}");
else
_ = this.Rest.ExecuteRequestAsync(req);
return req.WaitForCompletionAsync();
}
///
/// Executes a multipart rest request for stickers.
///
/// The client.
/// The bucket.
/// The url.
/// The method.
/// The route.
/// The headers.
/// The file.
/// The sticker name.
/// The sticker tag.
/// The sticker description.
/// The ratelimit wait override.
private Task DoStickerMultipartAsync(BaseDiscordClient client, RateLimitBucket bucket, Uri url, RestRequestMethod method, string route, IReadOnlyDictionary headers = null,
DiscordMessageFile file = null, string name = "", string tags = "", string description = "", double? ratelimitWaitOverride = null)
{
var req = new MultipartStickerWebRequest(client, bucket, url, method, route, headers, file, name, tags, description, ratelimitWaitOverride);
if (this.Discord != null)
this.Rest.ExecuteRequestAsync(req).LogTaskFault(this.Discord.Logger, LogLevel.Error, LoggerEvents.RestError, "Error while executing request");
else
_ = this.Rest.ExecuteRequestAsync(req);
return req.WaitForCompletionAsync();
}
///
/// Executes a multipart request.
///
/// The client.
/// The bucket.
/// The url.
/// The method.
/// The route.
/// The headers.
/// The values.
/// The files.
/// The ratelimit wait override.
private Task DoMultipartAsync(BaseDiscordClient client, RateLimitBucket bucket, Uri url, RestRequestMethod method, string route, IReadOnlyDictionary headers = null, IReadOnlyDictionary values = null,
IReadOnlyCollection files = null, double? ratelimitWaitOverride = null)
{
var req = new MultipartWebRequest(client, bucket, url, method, route, headers, values, files, ratelimitWaitOverride);
if (this.Discord != null)
this.Rest.ExecuteRequestAsync(req).LogTaskFault(this.Discord.Logger, LogLevel.Error, LoggerEvents.RestError, "Error while executing request");
else
_ = this.Rest.ExecuteRequestAsync(req);
return req.WaitForCompletionAsync();
}
#region Guild
///
/// Searches the members async.
///
/// The guild_id.
/// The name.
/// The limit.
internal async Task> SearchMembersAsync(ulong guildId, string name, int? limit)
{
var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.MEMBERS}{Endpoints.SEARCH}";
var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new {guild_id = guildId }, out var path);
var querydict = new Dictionary
{
["query"] = name,
["limit"] = limit.ToString()
};
var url = Utilities.GetApiUriFor(path, BuildQueryString(querydict), this.Discord.Configuration);
var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.GET, route).ConfigureAwait(false);
var json = JArray.Parse(res.Response);
var tms = json.ToObject>();
var mbrs = new List();
foreach (var xtm in tms)
{
var usr = new DiscordUser(xtm.User) { Discord = this.Discord };
this.Discord.UserCache.AddOrUpdate(xtm.User.Id, usr, (id, old) =>
{
old.Username = usr.Username;
old.Discord = usr.Discord;
old.AvatarHash = usr.AvatarHash;
return old;
});
mbrs.Add(new DiscordMember(xtm) { Discord = this.Discord, GuildId = guildId });
}
return mbrs;
}
///
/// Gets the guild ban async.
///
/// The guild_id.
/// The user_id.
internal async Task GetGuildBanAsync(ulong guildId, ulong userId)
{
var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.BANS}/:user_id";
var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new {guild_id = guildId, user_id = userId}, out var path);
var uri = Utilities.GetApiUriFor(path, this.Discord.Configuration);
var res = await this.DoRequestAsync(this.Discord, bucket, uri, RestRequestMethod.GET, route).ConfigureAwait(false);
var json = JObject.Parse(res.Response);
var ban = json.ToObject();
return ban;
}
///
/// Creates the guild async.
///
/// The name.
/// The region_id.
/// The iconb64.
/// The verification_level.
/// The default_message_notifications.
/// The system_channel_flags.
internal async Task CreateGuildAsync(string name, string regionId, Optional iconb64, VerificationLevel? verificationLevel,
DefaultMessageNotifications? defaultMessageNotifications, SystemChannelFlags? systemChannelFlags)
{
var pld = new RestGuildCreatePayload
{
Name = name,
RegionId = regionId,
DefaultMessageNotifications = defaultMessageNotifications,
VerificationLevel = verificationLevel,
IconBase64 = iconb64,
SystemChannelFlags = systemChannelFlags
};
var route = $"{Endpoints.GUILDS}";
var bucket = this.Rest.GetBucket(RestRequestMethod.POST, route, new { }, out var path);
var url = Utilities.GetApiUriFor(path, this.Discord.Configuration);
var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.POST, route, payload: DiscordJson.SerializeObject(pld)).ConfigureAwait(false);
var json = JObject.Parse(res.Response);
var rawMembers = (JArray)json["members"];
var guild = json.ToDiscordObject();
if (this.Discord is DiscordClient dc)
await dc.OnGuildCreateEventAsync(guild, rawMembers, null).ConfigureAwait(false);
return guild;
}
///
/// Creates the guild from template async.
///
/// The template_code.
/// The name.
/// The iconb64.
internal async Task CreateGuildFromTemplateAsync(string templateCode, string name, Optional iconb64)
{
var pld = new RestGuildCreateFromTemplatePayload
{
Name = name,
IconBase64 = iconb64
};
var route = $"{Endpoints.GUILDS}{Endpoints.TEMPLATES}/:template_code";
var bucket = this.Rest.GetBucket(RestRequestMethod.POST, route, new {template_code = templateCode }, out var path);
var url = Utilities.GetApiUriFor(path, this.Discord.Configuration);
var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.POST, route, payload: DiscordJson.SerializeObject(pld)).ConfigureAwait(false);
var json = JObject.Parse(res.Response);
var rawMembers = (JArray)json["members"];
var guild = json.ToDiscordObject();
if (this.Discord is DiscordClient dc)
await dc.OnGuildCreateEventAsync(guild, rawMembers, null).ConfigureAwait(false);
return guild;
}
///
/// Deletes the guild async.
///
/// The guild_id.
internal async Task DeleteGuildAsync(ulong guildId)
{
var route = $"{Endpoints.GUILDS}/:guild_id";
var bucket = this.Rest.GetBucket(RestRequestMethod.DELETE, route, new {guild_id = guildId }, out var path);
var url = Utilities.GetApiUriFor(path, this.Discord.Configuration);
await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.DELETE, route).ConfigureAwait(false);
if (this.Discord is DiscordClient dc)
{
var gld = dc.GuildsInternal[guildId];
await dc.OnGuildDeleteEventAsync(gld).ConfigureAwait(false);
}
}
///
/// Modifies the guild.
///
/// The guild id.
/// The name.
/// The verification level.
/// The default message notifications.
/// The mfa level.
/// The explicit content filter.
/// The afk channel id.
/// The afk timeout.
/// The iconb64.
/// The owner id.
/// The splashb64.
/// The system channel id.
/// The system channel flags.
/// The public updates channel id.
/// The rules channel id.
/// The description.
/// The banner base64.
/// The discovery base64.
/// The preferred locale.
/// Whether the premium progress bar should be enabled.
/// The reason.
internal async Task ModifyGuildAsync(ulong guildId, Optional name, Optional verificationLevel,
Optional defaultMessageNotifications, Optional mfaLevel,
Optional