diff --git a/.editorconfig b/.editorconfig index 4865109c2..eef8b7267 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,274 +1,271 @@ # Remove the line below if you want to inherit .editorconfig settings from higher directories root = true #### Core EditorConfig Options #### # All files [*] # General charset = utf-8 trim_trailing_whitespace = true # Indentation and spacing indent_size = 4 indent_style =tab tab_width = 4 # New line preferences end_of_line = crlf insert_final_newline = true dotnet_style_readonly_field=true:suggestion # Project files [*.{csproj,targets,yml}] indent_size = 2 [NuGet.config] indent_size = 2 # Solution files [*.sln] indent_style = tab tab_width = 4 #### .NET Coding Conventions #### # C# files [*.cs] # Organize usings dotnet_separate_import_directive_groups = false dotnet_sort_system_directives_first = true file_header_template = This file is part of the DisCatSharp project, based off DSharpPlus.\n\nCopyright (c) 2021 AITSYS\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the "Software"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE. # this. and Me. preferences dotnet_style_qualification_for_event = true:warning dotnet_style_qualification_for_field = true:warning dotnet_style_qualification_for_method = true:warning dotnet_style_qualification_for_property = true:warning # Language keywords vs BCL types preferences dotnet_style_predefined_type_for_locals_parameters_members = true:error dotnet_style_predefined_type_for_member_access = true:warning # Parentheses preferences dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:warning dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:warning dotnet_style_parentheses_in_other_operators = never_if_unnecessary:suggestion dotnet_style_parentheses_in_relational_binary_operators = never_if_unnecessary:suggestion # Modifier preferences dotnet_style_require_accessibility_modifiers = for_non_interface_members # Expression-level preferences dotnet_style_coalesce_expression = true:warning dotnet_style_collection_initializer = true dotnet_style_explicit_tuple_names = true:warning dotnet_style_null_propagation = true:warning dotnet_style_object_initializer = true dotnet_style_operator_placement_when_wrapping = beginning_of_line dotnet_style_prefer_auto_properties = true:suggestion dotnet_style_prefer_compound_assignment = true:warning dotnet_style_prefer_conditional_expression_over_assignment = true:warning dotnet_style_prefer_conditional_expression_over_return = true:warning dotnet_style_prefer_inferred_anonymous_type_member_names = true:warning dotnet_style_prefer_inferred_tuple_names = true:warning dotnet_style_prefer_is_null_check_over_reference_equality_method = true:error dotnet_style_prefer_simplified_boolean_expressions = true dotnet_style_prefer_simplified_interpolation = true # Field preferences dotnet_style_readonly_field = true:warning # Parameter preferences dotnet_code_quality_unused_parameters = non_public # Suppression preferences dotnet_remove_unnecessary_suppression_exclusions = none #### C# Coding Conventions #### # var preferences csharp_style_var_elsewhere = true:suggestion csharp_style_var_for_built_in_types = true:suggestion csharp_style_var_when_type_is_apparent = true:warning # Expression-bodied members csharp_style_expression_bodied_accessors = when_on_single_line:warning csharp_style_expression_bodied_constructors = false:warning csharp_style_expression_bodied_indexers = when_on_single_line:warning csharp_style_expression_bodied_lambdas = true:warning csharp_style_expression_bodied_local_functions = when_on_single_line:warning csharp_style_expression_bodied_methods = when_on_single_line:warning csharp_style_expression_bodied_operators = when_on_single_line:warning csharp_style_expression_bodied_properties = when_on_single_line:warning # Pattern matching preferences csharp_style_pattern_matching_over_as_with_null_check = true:warning csharp_style_pattern_matching_over_is_with_cast_check = true:warning csharp_style_prefer_not_pattern = true:warning csharp_style_prefer_pattern_matching = false csharp_style_prefer_switch_expression = true # Null-checking preferences csharp_style_conditional_delegate_call = false # Modifier preferences csharp_prefer_static_local_function = true:warning csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async # Code-block preferences csharp_prefer_braces = false:suggestion csharp_prefer_simple_using_statement = true # Expression-level preferences csharp_prefer_simple_default_expression = true:warning csharp_style_deconstructed_variable_declaration = true csharp_style_implicit_object_creation_when_type_is_apparent = true csharp_style_inlined_variable_declaration = true:warning csharp_style_pattern_local_over_anonymous_function = true:warning csharp_style_prefer_index_operator = true csharp_style_prefer_range_operator = true csharp_style_throw_expression = false:warning csharp_style_unused_value_assignment_preference = discard_variable:silent csharp_style_unused_value_expression_statement_preference = discard_variable # 'using' directive preferences csharp_using_directive_placement = outside_namespace:error #### C# Formatting Rules #### # New line preferences csharp_new_line_before_catch = true csharp_new_line_before_else = true csharp_new_line_before_finally = true csharp_new_line_before_members_in_anonymous_types = true csharp_new_line_before_members_in_object_initializers = true csharp_new_line_before_open_brace = all csharp_new_line_between_query_expression_clauses = true # Indentation preferences csharp_indent_block_contents = true csharp_indent_braces = false csharp_indent_case_contents = true csharp_indent_case_contents_when_block = false csharp_indent_labels = one_less_than_current csharp_indent_switch_labels = true # Space preferences csharp_space_after_cast = false csharp_space_after_colon_in_inheritance_clause = true csharp_space_after_comma = true csharp_space_after_dot = false csharp_space_after_keywords_in_control_flow_statements = true csharp_space_after_semicolon_in_for_statement = true csharp_space_around_binary_operators = before_and_after csharp_space_around_declaration_statements = ignore csharp_space_before_colon_in_inheritance_clause = true csharp_space_before_comma = false csharp_space_before_dot = false csharp_space_before_open_square_brackets = false csharp_space_before_semicolon_in_for_statement = false csharp_space_between_empty_square_brackets = false csharp_space_between_method_call_empty_parameter_list_parentheses = false csharp_space_between_method_call_name_and_opening_parenthesis = false csharp_space_between_method_call_parameter_list_parentheses = false csharp_space_between_method_declaration_empty_parameter_list_parentheses = false csharp_space_between_method_declaration_name_and_open_parenthesis = false csharp_space_between_method_declaration_parameter_list_parentheses = false csharp_space_between_parentheses = false csharp_space_between_square_brackets = false # Wrapping preferences csharp_preserve_single_line_blocks = true csharp_preserve_single_line_statements = true #### Naming styles #### # Naming rules dotnet_naming_rule.interface_should_be_begins_with_i.severity = error dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i +dotnet_naming_rule.private_static_fields_convention.severity = error +dotnet_naming_rule.private_static_fields_convention.symbols = private_static_field_props +dotnet_naming_rule.private_static_fields_convention.style = private_static_camel_case + +dotnet_naming_rule.readonly.severity = error +dotnet_naming_rule.readonly.symbols = readonly +dotnet_naming_rule.readonly.style = underscore_prefixed_camel_case + dotnet_naming_rule.types_should_be_pascal_case.severity = error dotnet_naming_rule.types_should_be_pascal_case.symbols = types dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case -dotnet_naming_rule.async_methods_should_be_async_suffix.severity = error -dotnet_naming_rule.async_methods_should_be_async_suffix.symbols = async_methods -dotnet_naming_rule.async_methods_should_be_async_suffix.style = async_suffix - dotnet_naming_rule.const_fields_should_be_all_upper.severity = error dotnet_naming_rule.const_fields_should_be_all_upper.symbols = const_fields -dotnet_naming_rule.const_fields_should_be_all_upper.style = all_upper +dotnet_naming_rule.const_fields_should_be_all_upper.style = constant_style -dotnet_naming_rule.private_or_internal_field_should_be_underscore_prefixed_camel_case.severity = error -dotnet_naming_rule.private_or_internal_field_should_be_underscore_prefixed_camel_case.symbols = private_or_internal_field -dotnet_naming_rule.private_or_internal_field_should_be_underscore_prefixed_camel_case.style = underscore_prefixed_camel_case +dotnet_naming_rule.constants_should_be_upper_case.severity = error +dotnet_naming_rule.constants_should_be_upper_case.symbols = constants +dotnet_naming_rule.constants_should_be_upper_case.style = constant_style -dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = error -dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members -dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case +dotnet_naming_rule.private_field_should_be_underscore_prefixed_camel_case.severity = error +dotnet_naming_rule.private_field_should_be_underscore_prefixed_camel_case.symbols = private_fields +dotnet_naming_rule.private_field_should_be_underscore_prefixed_camel_case.style = underscore_prefixed_camel_case # Symbol specifications dotnet_naming_symbols.interface.applicable_kinds = interface dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected dotnet_naming_symbols.interface.required_modifiers = -dotnet_naming_symbols.private_or_internal_field.applicable_kinds = field -dotnet_naming_symbols.private_or_internal_field.applicable_accessibilities = internal, private, private_protected -dotnet_naming_symbols.private_or_internal_field.required_modifiers = +dotnet_naming_symbols.private_static_field_props.applicable_kinds = field, property +dotnet_naming_symbols.private_static_field_props.applicable_accessibilities = private +dotnet_naming_symbols.private_static_field_props.required_modifiers = static -dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum -dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected -dotnet_naming_symbols.types.required_modifiers = - -dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method -dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected -dotnet_naming_symbols.non_field_members.required_modifiers = - -dotnet_naming_symbols.async_methods.applicable_kinds = method -dotnet_naming_symbols.async_methods.applicable_accessibilities = public, internal, protected_internal, protected, private_protected -dotnet_naming_symbols.async_methods.required_modifiers = async +dotnet_naming_symbols.constants.applicable_kinds = field, local +dotnet_naming_symbols.constants.required_modifiers = const dotnet_naming_symbols.const_fields.applicable_kinds = field, property dotnet_naming_symbols.const_fields.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected dotnet_naming_symbols.const_fields.required_modifiers = const +dotnet_naming_symbols.readonly.applicable_kinds = field +dotnet_naming_symbols.readonly.applicable_accessibilities = private +dotnet_naming_symbols.readonly.required_modifiers = readonly + +dotnet_naming_symbols.private_fields.applicable_kinds = field +dotnet_naming_symbols.private_fields.applicable_accessibilities = private, private_protected +dotnet_naming_symbols.private_fields.required_modifiers = + +dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum +dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.types.required_modifiers = + # Naming styles dotnet_naming_style.pascal_case.required_prefix = dotnet_naming_style.pascal_case.required_suffix = dotnet_naming_style.pascal_case.word_separator = dotnet_naming_style.pascal_case.capitalization = pascal_case dotnet_naming_style.begins_with_i.required_prefix = I dotnet_naming_style.begins_with_i.required_suffix = dotnet_naming_style.begins_with_i.word_separator = dotnet_naming_style.begins_with_i.capitalization = pascal_case dotnet_naming_style.underscore_prefixed_camel_case.required_prefix = _ dotnet_naming_style.underscore_prefixed_camel_case.required_suffix = dotnet_naming_style.underscore_prefixed_camel_case.word_separator = dotnet_naming_style.underscore_prefixed_camel_case.capitalization = camel_case -dotnet_naming_style.async_suffix.required_prefix = -dotnet_naming_style.async_suffix.required_suffix = Async -dotnet_naming_style.async_suffix.word_separator = -dotnet_naming_style.async_suffix.capitalization = pascal_case - -# Constants are UPPER_CASE -dotnet_naming_rule.constants_should_be_upper_case.severity = error -dotnet_naming_rule.constants_should_be_upper_case.symbols = constants -dotnet_naming_rule.constants_should_be_upper_case.style = constant_style - -dotnet_naming_symbols.constants.applicable_kinds = field, local -dotnet_naming_symbols.constants.required_modifiers = const +dotnet_naming_style.private_static_camel_case.required_prefix = s_ +dotnet_naming_style.private_static_camel_case.capitalization = camel_case dotnet_naming_style.constant_style.required_prefix = dotnet_naming_style.constant_style.required_suffix = dotnet_naming_style.constant_style.word_separator = _ dotnet_naming_style.constant_style.capitalization = all_upper diff --git a/DisCatSharp.ApplicationCommands/ApplicationCommandsExtension.cs b/DisCatSharp.ApplicationCommands/ApplicationCommandsExtension.cs index 7d2b48e2c..cfff28834 100644 --- a/DisCatSharp.ApplicationCommands/ApplicationCommandsExtension.cs +++ b/DisCatSharp.ApplicationCommands/ApplicationCommandsExtension.cs @@ -1,1776 +1,1776 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Text; using System.Threading.Tasks; using DisCatSharp.ApplicationCommands.Attributes; using DisCatSharp.ApplicationCommands.EventArgs; using DisCatSharp.Common; using DisCatSharp.Common.Utilities; using DisCatSharp.Entities; using DisCatSharp.Enums; using DisCatSharp.EventArgs; using DisCatSharp.Exceptions; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Newtonsoft.Json; namespace DisCatSharp.ApplicationCommands { /// /// A class that handles slash commands for a client. /// public sealed class ApplicationCommandsExtension : BaseExtension { /// /// A list of methods for top level commands. /// - private static List _commandMethods { get; set; } = new List(); + private static List s_commandMethods { get; set; } = new List(); /// /// List of groups. /// - private static List _groupCommands { get; set; } = new List(); + private static List s_groupCommands { get; set; } = new List(); /// /// List of groups with subgroups. /// - private static List _subGroupCommands { get; set; } = new List(); + private static List s_subGroupCommands { get; set; } = new List(); /// /// List of context menus. /// - private static List _contextMenuCommands { get; set; } = new List(); + private static List s_contextMenuCommands { get; set; } = new List(); /// /// List of global commands on discords backend. /// - internal static List _globalDiscordCommands { get; set; } = null; + internal static List GlobalDiscordCommands { get; set; } = null; /// /// List of guild commands on discords backend. /// - internal static Dictionary> _guildDiscordCommands { get; set; } = null; + internal static Dictionary> GuildDiscordCommands { get; set; } = null; /// /// Singleton modules. /// - private static List _singletonModules { get; set; } = new List(); + private static List s_singletonModules { get; set; } = new List(); /// /// List of modules to register. /// - private List> _updateList { get; set; } = new List>(); + private List> UpdateList { get; set; } = new List>(); /// /// Configuration for Discord. /// - internal static ApplicationCommandsConfiguration _configuration; + internal static ApplicationCommandsConfiguration Configuration; /// /// Discord client. /// - internal static DiscordClient _client; + internal static DiscordClient ClientInternal; /// /// Set to true if anything fails when registering. /// - private static bool _errored { get; set; } = false; + private static bool s_errored { get; set; } = false; /// /// Gets a list of registered commands. The key is the guild id (null if global). /// public IReadOnlyList>> RegisteredCommands - => _registeredCommands; - private static List>> _registeredCommands = new(); + => s_registeredCommands; + private static List>> s_registeredCommands = new(); /// /// Gets a list of registered global commands. /// public IReadOnlyList GlobalCommands - => _globalCommands; - internal static readonly List _globalCommands = new(); + => GlobalCommandsInternal; + internal static readonly List GlobalCommandsInternal = new(); /// /// Gets a list of registered guild commands mapped by guild id. /// public IReadOnlyDictionary> GuildCommands - => _guildCommands; - internal static readonly Dictionary> _guildCommands = new(); + => GuildCommandsInternal; + internal static readonly Dictionary> GuildCommandsInternal = new(); /// /// Gets the registration count. /// - private static int RegistrationCount { get; set; } = 0; + private static int s_registrationCount { get; set; } = 0; /// /// Gets the expected count. /// - private static int ExpectedCount { get; set; } = 0; + private static int s_expectedCount { get; set; } = 0; /// /// Gets the guild ids where the applications.commands scope is missing. /// private IReadOnlyList MissingScopeGuildIds { get; set; } /// /// Gets whether debug is enabled. /// - private static bool DebugEnabled { get; set; } + private static bool s_debugEnabled { get; set; } /// /// Initializes a new instance of the class. /// /// The configuration. internal ApplicationCommandsExtension(ApplicationCommandsConfiguration configuration) { - _configuration = configuration; - DebugEnabled = configuration.DebugStartupCounts; + Configuration = configuration; + s_debugEnabled = configuration.DebugStartupCounts; } /// /// Runs setup. DO NOT RUN THIS MANUALLY. DO NOT DO ANYTHING WITH THIS. /// /// The client to setup on. protected internal override void Setup(DiscordClient client) { if (this.Client != null) throw new InvalidOperationException("What did I tell you?"); this.Client = client; - _client = client; + ClientInternal = client; this._slashError = new AsyncEvent("SLASHCOMMAND_ERRORED", TimeSpan.Zero, null); this._slashExecuted = new AsyncEvent("SLASHCOMMAND_EXECUTED", TimeSpan.Zero, null); this._contextMenuErrored = new AsyncEvent("CONTEXTMENU_ERRORED", TimeSpan.Zero, null); this._contextMenuExecuted = new AsyncEvent("CONTEXTMENU_EXECUTED", TimeSpan.Zero, null); this._applicationCommandsModuleReady = new AsyncEvent("APPLICATION_COMMANDS_MODULE_READY", TimeSpan.Zero, null); this._applicationCommandsModuleStartupFinished = new AsyncEvent("APPLICATION_COMMANDS_MODULE_STARTUP_FINISHED", TimeSpan.Zero, null); this._globalApplicationCommandsRegistered = new AsyncEvent("GLOBAL_COMMANDS_REGISTERED", TimeSpan.Zero, null); this._guildApplicationCommandsRegistered = new AsyncEvent("GUILD_COMMANDS_REGISTERED", TimeSpan.Zero, null); this.Client.GuildDownloadCompleted += async (c, e) => await this.UpdateAsync(); this.Client.InteractionCreated += this.CatchInteractionsOnStartup; this.Client.ContextMenuInteractionCreated += this.CatchContextMenuInteractionsOnStartup; } private async Task CatchInteractionsOnStartup(DiscordClient sender, InteractionCreateEventArgs e) => await e.Interaction.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, new DiscordInteractionResponseBuilder().AsEphemeral(true).WithContent("Attention: This application is still starting up. Application commands are unavailable for now.")); private async Task CatchContextMenuInteractionsOnStartup(DiscordClient sender, ContextMenuInteractionCreateEventArgs e) => await e.Interaction.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, new DiscordInteractionResponseBuilder().AsEphemeral(true).WithContent("Attention: This application is still starting up. Context menu commands are unavailable for now.")); private void FinishedRegistration() { this.Client.InteractionCreated -= this.CatchInteractionsOnStartup; this.Client.ContextMenuInteractionCreated -= this.CatchContextMenuInteractionsOnStartup; this.Client.InteractionCreated += this.InteractionHandler; this.Client.ContextMenuInteractionCreated += this.ContextMenuHandler; } /// /// Registers a command class. /// /// The command class to register. /// The guild id to register it on. If you want global commands, leave it null. public void RegisterCommands(ulong? guildId = null) where T : ApplicationCommandsModule { if (this.Client.ShardId == 0) - this._updateList.Add(new KeyValuePair(guildId, new ApplicationCommandsModuleConfiguration(typeof(T)))); + this.UpdateList.Add(new KeyValuePair(guildId, new ApplicationCommandsModuleConfiguration(typeof(T)))); } /// /// Registers a command class. /// /// The of the command class to register. /// The guild id to register it on. If you want global commands, leave it null. public void RegisterCommands(Type type, ulong? guildId = null) { if (!typeof(ApplicationCommandsModule).IsAssignableFrom(type)) throw new ArgumentException("Command classes have to inherit from ApplicationCommandsModule", nameof(type)); //If sharding, only register for shard 0 if (this.Client.ShardId == 0) - this._updateList.Add(new KeyValuePair(guildId, new ApplicationCommandsModuleConfiguration(type))); + this.UpdateList.Add(new KeyValuePair(guildId, new ApplicationCommandsModuleConfiguration(type))); } /// /// Cleans all guild application commands. /// public async Task CleanGuildCommandsAsync() { foreach (var guild in this.Client.Guilds.Values) { await this.Client.BulkOverwriteGuildApplicationCommandsAsync(guild.Id, Array.Empty()); } } /// /// Cleans the global application commands. /// public async Task CleanGlobalCommandsAsync() => await this.Client.BulkOverwriteGlobalApplicationCommandsAsync(Array.Empty()); /// /// Registers a command class with permission and translation setup. /// /// The command class to register. /// The guild id to register it on. /// A callback to setup permissions with. /// A callback to setup translations with. public void RegisterCommands(ulong guildId, Action permissionSetup = null, Action translationSetup = null) where T : ApplicationCommandsModule { if (this.Client.ShardId == 0) - this._updateList.Add(new KeyValuePair(guildId, new ApplicationCommandsModuleConfiguration(typeof(T), permissionSetup, translationSetup))); + this.UpdateList.Add(new KeyValuePair(guildId, new ApplicationCommandsModuleConfiguration(typeof(T), permissionSetup, translationSetup))); } /// /// Registers a command class with permission and translation setup. /// /// The of the command class to register. /// The guild id to register it on. /// A callback to setup permissions with. /// A callback to setup translations with. public void RegisterCommands(Type type, ulong guildId, Action permissionSetup = null, Action translationSetup = null) { if (!typeof(ApplicationCommandsModule).IsAssignableFrom(type)) throw new ArgumentException("Command classes have to inherit from ApplicationCommandsModule", nameof(type)); //If sharding, only register for shard 0 if (this.Client.ShardId == 0) - this._updateList.Add(new KeyValuePair(guildId, new ApplicationCommandsModuleConfiguration(type, permissionSetup, translationSetup))); + this.UpdateList.Add(new KeyValuePair(guildId, new ApplicationCommandsModuleConfiguration(type, permissionSetup, translationSetup))); } /* /// /// Registers a command class with permission setup but without a guild id. /// /// The command class to register. /// A callback to setup permissions with. public void RegisterCommands(Action permissionSetup = null) where T : ApplicationCommandsModule { if (this.Client.ShardId == 0) this._updateList.Add(new KeyValuePair(null, new ApplicationCommandsModuleConfiguration(typeof(T), permissionSetup))); } /// /// Registers a command class with permission setup but without a guild id. /// /// The of the command class to register. /// A callback to setup permissions with. public void RegisterCommands(Type type, Action permissionSetup = null) { if (!typeof(ApplicationCommandsModule).IsAssignableFrom(type)) throw new ArgumentException("Command classes have to inherit from ApplicationCommandsModule", nameof(type)); //If sharding, only register for shard 0 if (this.Client.ShardId == 0) this._updateList.Add(new KeyValuePair(null, new ApplicationCommandsModuleConfiguration(type, permissionSetup))); } */ /// /// Fired when the application commands module is ready. /// public event AsyncEventHandler ApplicationCommandsModuleReady { add { this._applicationCommandsModuleReady.Register(value); } remove { this._applicationCommandsModuleReady.Unregister(value); } } private AsyncEvent _applicationCommandsModuleReady; /// /// Fired when the application commands modules startup is finished. /// public event AsyncEventHandler ApplicationCommandsModuleStartupFinished { add { this._applicationCommandsModuleStartupFinished.Register(value); } remove { this._applicationCommandsModuleStartupFinished.Unregister(value); } } private AsyncEvent _applicationCommandsModuleStartupFinished; /// /// Fired when guild commands are registered on a guild. /// public event AsyncEventHandler GuildApplicationCommandsRegistered { add { this._guildApplicationCommandsRegistered.Register(value); } remove { this._guildApplicationCommandsRegistered.Unregister(value); } } private AsyncEvent _guildApplicationCommandsRegistered; /// /// Fired when the global commands are registered. /// public event AsyncEventHandler GlobalApplicationCommandsRegistered { add { this._globalApplicationCommandsRegistered.Register(value); } remove { this._globalApplicationCommandsRegistered.Unregister(value); } } private AsyncEvent _globalApplicationCommandsRegistered; /// /// Used for RegisterCommands and the event. /// internal async Task UpdateAsync() { //Only update for shard 0 if (this.Client.ShardId == 0) { - _globalDiscordCommands = new(); - _guildDiscordCommands = new(); + GlobalDiscordCommands = new(); + GuildDiscordCommands = new(); - var commands_pending = this._updateList.Select(x => x.Key).Distinct(); - ExpectedCount = commands_pending.Count(); + var commandsPending = this.UpdateList.Select(x => x.Key).Distinct(); + s_expectedCount = commandsPending.Count(); - if (DebugEnabled) - this.Client.Logger.LogDebug($"Expected Count: {ExpectedCount}"); + if (s_debugEnabled) + this.Client.Logger.LogDebug($"Expected Count: {s_expectedCount}"); - List FailedGuilds = new(); - IEnumerable GlobalCommands = null; - GlobalCommands = await this.Client.GetGlobalApplicationCommandsAsync() ?? null; + List failedGuilds = new(); + IEnumerable globalCommands = null; + globalCommands = await this.Client.GetGlobalApplicationCommandsAsync() ?? null; foreach (var guild in this.Client.Guilds.Keys) { - IEnumerable Commands = null; + IEnumerable commands = null; var unauthorized = false; try { - Commands = await this.Client.GetGuildApplicationCommandsAsync(guild) ?? null; + commands = await this.Client.GetGuildApplicationCommandsAsync(guild) ?? null; } catch (UnauthorizedException) { unauthorized = true; } finally { - if (!unauthorized && Commands != null && Commands.Any()) - _guildDiscordCommands.Add(guild, Commands.ToList()); + if (!unauthorized && commands != null && commands.Any()) + GuildDiscordCommands.Add(guild, commands.ToList()); else if (!unauthorized) - _guildDiscordCommands.Add(guild, null); + GuildDiscordCommands.Add(guild, null); else - FailedGuilds.Add(guild); + failedGuilds.Add(guild); } } //Default should be to add the help and slash commands can be added without setting any configuration //so this should still add the default help - if (_configuration is null || (_configuration is not null && _configuration.EnableDefaultHelp)) + if (Configuration is null || (Configuration is not null && Configuration.EnableDefaultHelp)) { - foreach (var key in commands_pending.ToList()) + foreach (var key in commandsPending.ToList()) { - this._updateList.Add(new KeyValuePair + this.UpdateList.Add(new KeyValuePair (key, new ApplicationCommandsModuleConfiguration(typeof(DefaultHelpModule)))); } } - if (GlobalCommands != null && GlobalCommands.Any()) - _globalDiscordCommands.AddRange(GlobalCommands); - foreach (var key in commands_pending.ToList()) + if (globalCommands != null && globalCommands.Any()) + GlobalDiscordCommands.AddRange(globalCommands); + foreach (var key in commandsPending.ToList()) { this.Client.Logger.LogDebug(key.HasValue ? $"Registering commands in guild {key.Value}" : "Registering global commands."); - this.RegisterCommands(this._updateList.Where(x => x.Key == key).Select(x => x.Value), key); + this.RegisterCommands(this.UpdateList.Where(x => x.Key == key).Select(x => x.Value), key); } - this.MissingScopeGuildIds = FailedGuilds; + this.MissingScopeGuildIds = failedGuilds; - await this._applicationCommandsModuleReady.InvokeAsync(this, new ApplicationCommandsModuleReadyEventArgs(_configuration?.ServiceProvider) + await this._applicationCommandsModuleReady.InvokeAsync(this, new ApplicationCommandsModuleReadyEventArgs(Configuration?.ServiceProvider) { Handled = true, - GuildsWithoutScope = FailedGuilds + GuildsWithoutScope = failedGuilds }); } } /// /// Method for registering commands for a target from modules. /// /// The types. /// The optional guild id. private void RegisterCommands(IEnumerable types, ulong? guildid) { //Initialize empty lists to be added to the global ones at the end var commandMethods = new List(); var groupCommands = new List(); var subGroupCommands = new List(); var contextMenuCommands = new List(); var updateList = new List(); var commandTypeSources = new List>(); _ = Task.Run(async () => { //Iterates over all the modules foreach (var config in types) { var type = config.Type; try { var module = type.GetTypeInfo(); var classes = new List(); var ctx = new ApplicationCommandsTranslationContext(type, module.FullName); config.Translations?.Invoke(ctx); //Add module to classes list if it's a group if (module.GetCustomAttribute() != null) { classes.Add(module); } else { //Otherwise add the nested groups classes = module.DeclaredNestedTypes.Where(x => x.GetCustomAttribute() != null).ToList(); } List groupTranslations = null; if (!string.IsNullOrEmpty(ctx.Translations)) { groupTranslations = JsonConvert.DeserializeObject>(ctx.Translations); } var slashGroupsTulpe = NestedCommandWorker.ParseSlashGroupsAsync(type, classes, guildid, groupTranslations).Result; if (slashGroupsTulpe.applicationCommands != null && slashGroupsTulpe.applicationCommands.Any()) updateList.AddRange(slashGroupsTulpe.applicationCommands); if (slashGroupsTulpe.commandTypeSources != null && slashGroupsTulpe.commandTypeSources.Any()) commandTypeSources.AddRange(slashGroupsTulpe.commandTypeSources); if (slashGroupsTulpe.singletonModules != null && slashGroupsTulpe.singletonModules.Any()) - _singletonModules.AddRange(slashGroupsTulpe.singletonModules); + s_singletonModules.AddRange(slashGroupsTulpe.singletonModules); if (slashGroupsTulpe.groupCommands != null && slashGroupsTulpe.groupCommands.Any()) groupCommands.AddRange(slashGroupsTulpe.groupCommands); if (slashGroupsTulpe.subGroupCommands != null && slashGroupsTulpe.subGroupCommands.Any()) subGroupCommands.AddRange(slashGroupsTulpe.subGroupCommands); //Handles methods and context menus, only if the module isn't a group itself if (module.GetCustomAttribute() == null) { List commandTranslations = null; if (!string.IsNullOrEmpty(ctx.Translations)) { commandTranslations = JsonConvert.DeserializeObject>(ctx.Translations); } //Slash commands var methods = module.DeclaredMethods.Where(x => x.GetCustomAttribute() != null); var slashCommands = CommandWorker.ParseBasicSlashCommandsAsync(type, methods, guildid, commandTranslations).Result; if (slashCommands.applicationCommands != null && slashCommands.applicationCommands.Any()) updateList.AddRange(slashCommands.applicationCommands); if (slashCommands.commandTypeSources != null && slashCommands.commandTypeSources.Any()) commandTypeSources.AddRange(slashCommands.commandTypeSources); if (slashCommands.commandMethods != null && slashCommands.commandMethods.Any()) commandMethods.AddRange(slashCommands.commandMethods); //Context Menus var contextMethods = module.DeclaredMethods.Where(x => x.GetCustomAttribute() != null); var contextCommands = await CommandWorker.ParseContextMenuCommands(type, contextMethods, commandTranslations); if (contextCommands.applicationCommands != null && contextCommands.applicationCommands.Any()) updateList.AddRange(contextCommands.applicationCommands); if (contextCommands.commandTypeSources != null && contextCommands.commandTypeSources.Any()) commandTypeSources.AddRange(contextCommands.commandTypeSources); if (contextCommands.contextMenuCommands != null && contextCommands.contextMenuCommands.Any()) contextMenuCommands.AddRange(contextCommands.contextMenuCommands); //Accounts for lifespans if (module.GetCustomAttribute() != null && module.GetCustomAttribute().Lifespan == ApplicationCommandModuleLifespan.Singleton) { - _singletonModules.Add(CreateInstance(module, _configuration?.ServiceProvider)); + s_singletonModules.Add(CreateInstance(module, Configuration?.ServiceProvider)); } } } catch (Exception ex) { if (ex is BadRequestException brex) this.Client.Logger.LogCritical(brex, $"There was an error registering application commands: {brex.JsonMessage}"); else this.Client.Logger.LogCritical(ex, $"There was an error registering application commands"); - _errored = true; + s_errored = true; } } - if (!_errored) + if (!s_errored) { try { - List Commands = new(); + List commands = new(); try { if (guildid == null) { if (updateList != null && updateList.Any()) { - var RegCommands = await RegistrationWorker.RegisterGlobalCommandsAsync(updateList); - var ActualCommands = RegCommands.Distinct().ToList(); - Commands.AddRange(ActualCommands); - _globalCommands.AddRange(ActualCommands); + var regCommands = await RegistrationWorker.RegisterGlobalCommandsAsync(updateList); + var actualCommands = regCommands.Distinct().ToList(); + commands.AddRange(actualCommands); + GlobalCommandsInternal.AddRange(actualCommands); } else { - foreach (var cmd in _globalDiscordCommands) + foreach (var cmd in GlobalDiscordCommands) { try { await this.Client.DeleteGlobalApplicationCommandAsync(cmd.Id); } catch (NotFoundException) { this.Client.Logger.LogError($"Could not delete global command {cmd.Id}. Please clean up manually"); } } } } else { if (updateList != null && updateList.Any()) { - var RegCommands = await RegistrationWorker.RegisterGuilldCommandsAsync(guildid.Value, updateList); - var ActualCommands = RegCommands.Distinct().ToList(); - Commands.AddRange(ActualCommands); - _guildCommands.Add(guildid.Value, ActualCommands); + var regCommands = await RegistrationWorker.RegisterGuilldCommandsAsync(guildid.Value, updateList); + var actualCommands = regCommands.Distinct().ToList(); + commands.AddRange(actualCommands); + GuildCommandsInternal.Add(guildid.Value, actualCommands); if (this.Client.Guilds.TryGetValue(guildid.Value, out var guild)) { guild.InternalRegisteredApplicationCommands = new(); - guild.InternalRegisteredApplicationCommands.AddRange(ActualCommands); + guild.InternalRegisteredApplicationCommands.AddRange(actualCommands); } } else { - foreach (var cmd in _guildDiscordCommands[guildid.Value]) + foreach (var cmd in GuildDiscordCommands[guildid.Value]) { try { await this.Client.DeleteGuildApplicationCommandAsync(guildid.Value, cmd.Id); } catch (NotFoundException) { this.Client.Logger.LogError($"Could not delete guild command {cmd.Id} in guild {guildid.Value}. Please clean up manually"); } } } } } catch (UnauthorizedException ex) { this.Client.Logger.LogError($"Could not register application commands for guild {guildid}.\nError: {ex.JsonMessage}"); return; } //Creates a guild command if a guild id is specified, otherwise global //Checks against the ids and adds them to the command method lists - foreach (var command in Commands) + foreach (var command in commands) { if (commandMethods.GetFirstValueWhere(x => x.Name == command.Name, out var com)) { com.CommandId = command.Id; var source = commandTypeSources.FirstOrDefault(f => f.Key == com.Method.DeclaringType); await PermissionWorker.UpdateCommandPermissionAsync(types, guildid, command.Id, com.Name, source.Value, source.Key); } else if (groupCommands.GetFirstValueWhere(x => x.Name == command.Name, out var groupCom)) { groupCom.CommandId = command.Id; foreach (var gCom in groupCom.Methods) { var source = commandTypeSources.FirstOrDefault(f => f.Key == gCom.Value.DeclaringType); await PermissionWorker.UpdateCommandPermissionAsync(types, guildid, groupCom.CommandId, gCom.Key, source.Key, source.Value); } } else if (subGroupCommands.GetFirstValueWhere(x => x.Name == command.Name, out var subCom)) { subCom.CommandId = command.Id; foreach (var groupComs in subCom.SubCommands) { foreach (var gCom in groupComs.Methods) { var source = commandTypeSources.FirstOrDefault(f => f.Key == gCom.Value.DeclaringType); await PermissionWorker.UpdateCommandPermissionAsync(types, guildid, subCom.CommandId, gCom.Key, source.Key, source.Value); } } } else if (contextMenuCommands.GetFirstValueWhere(x => x.Name == command.Name, out var cmCom)) { cmCom.CommandId = command.Id; var source = commandTypeSources.First(f => f.Key == cmCom.Method.DeclaringType); await PermissionWorker.UpdateCommandPermissionAsync(types, guildid, command.Id, cmCom.Name, source.Value, source.Key); } } //Adds to the global lists finally - _commandMethods.AddRange(commandMethods); - _groupCommands.AddRange(groupCommands); - _subGroupCommands.AddRange(subGroupCommands); - _contextMenuCommands.AddRange(contextMenuCommands); + s_commandMethods.AddRange(commandMethods); + s_groupCommands.AddRange(groupCommands); + s_subGroupCommands.AddRange(subGroupCommands); + s_contextMenuCommands.AddRange(contextMenuCommands); - _registeredCommands.Add(new KeyValuePair>(guildid, Commands.ToList())); + s_registeredCommands.Add(new KeyValuePair>(guildid, commands.ToList())); foreach (var command in commandMethods) { var app = types.First(t => t.Type == command.Method.DeclaringType); } - RegistrationCount++; - if (DebugEnabled) - this.Client.Logger.LogDebug($"Expected Count: {ExpectedCount}\nCurrent Count: {RegistrationCount}"); + s_registrationCount++; + if (s_debugEnabled) + this.Client.Logger.LogDebug($"Expected Count: {s_expectedCount}\nCurrent Count: {s_registrationCount}"); if (guildid.HasValue) { - await this._guildApplicationCommandsRegistered.InvokeAsync(this, new GuildApplicationCommandsRegisteredEventArgs(_configuration?.ServiceProvider) + await this._guildApplicationCommandsRegistered.InvokeAsync(this, new GuildApplicationCommandsRegisteredEventArgs(Configuration?.ServiceProvider) { Handled = true, GuildId = guildid.Value, - RegisteredCommands = _guildCommands.Any(c => c.Key == guildid.Value) ? _guildCommands.Single(c => c.Key == guildid.Value).Value : null + RegisteredCommands = GuildCommandsInternal.Any(c => c.Key == guildid.Value) ? GuildCommandsInternal.Single(c => c.Key == guildid.Value).Value : null }); } else { - await this._globalApplicationCommandsRegistered.InvokeAsync(this, new GlobalApplicationCommandsRegisteredEventArgs(_configuration?.ServiceProvider) + await this._globalApplicationCommandsRegistered.InvokeAsync(this, new GlobalApplicationCommandsRegisteredEventArgs(Configuration?.ServiceProvider) { Handled = true, - RegisteredCommands = _globalCommands + RegisteredCommands = GlobalCommandsInternal }); } this.CheckRegistrationStartup(); } catch (Exception ex) { if (ex is BadRequestException brex) this.Client.Logger.LogCritical(brex, $"There was an error registering application commands: {brex.JsonMessage}"); else this.Client.Logger.LogCritical(ex, $"There was an error registering application commands"); - _errored = true; + s_errored = true; } } }); } private async void CheckRegistrationStartup() { - if (DebugEnabled) - this.Client.Logger.LogDebug($"Checking counts...\n\nExpected Count: {ExpectedCount}\nCurrent Count: {RegistrationCount}"); - if (RegistrationCount == ExpectedCount) + if (s_debugEnabled) + this.Client.Logger.LogDebug($"Checking counts...\n\nExpected Count: {s_expectedCount}\nCurrent Count: {s_registrationCount}"); + if (s_registrationCount == s_expectedCount) { - await this._applicationCommandsModuleStartupFinished.InvokeAsync(this, new ApplicationCommandsModuleStartupFinishedEventArgs(_configuration?.ServiceProvider) + await this._applicationCommandsModuleStartupFinished.InvokeAsync(this, new ApplicationCommandsModuleStartupFinishedEventArgs(Configuration?.ServiceProvider) { - RegisteredGlobalCommands = _globalCommands, - RegisteredGuildCommands = _guildCommands, + RegisteredGlobalCommands = GlobalCommandsInternal, + RegisteredGuildCommands = GuildCommandsInternal, GuildsWithoutScope = MissingScopeGuildIds }); this.FinishedRegistration(); } } /// /// Interaction handler. /// /// The client. /// The event args. private Task InteractionHandler(DiscordClient client, InteractionCreateEventArgs e) { _ = Task.Run(async () => { if (e.Interaction.Type == InteractionType.ApplicationCommand) { //Creates the context var context = new InteractionContext { Interaction = e.Interaction, Channel = e.Interaction.Channel, Guild = e.Interaction.Guild, User = e.Interaction.User, Client = client, ApplicationCommandsExtension = this, CommandName = e.Interaction.Data.Name, InteractionId = e.Interaction.Id, Token = e.Interaction.Token, - Services = _configuration?.ServiceProvider, + Services = Configuration?.ServiceProvider, ResolvedUserMentions = e.Interaction.Data.Resolved?.Users?.Values.ToList(), ResolvedRoleMentions = e.Interaction.Data.Resolved?.Roles?.Values.ToList(), ResolvedChannelMentions = e.Interaction.Data.Resolved?.Channels?.Values.ToList(), ResolvedAttachments = e.Interaction.Data.Resolved?.Attachments?.Values.ToList(), Type = ApplicationCommandType.ChatInput, Locale = e.Interaction.Locale, GuildLocale = e.Interaction.GuildLocale }; try { - if (_errored) + if (s_errored) throw new InvalidOperationException("Slash commands failed to register properly on startup."); - var methods = _commandMethods.Where(x => x.CommandId == e.Interaction.Data.Id); - var groups = _groupCommands.Where(x => x.CommandId == e.Interaction.Data.Id); - var subgroups = _subGroupCommands.Where(x => x.CommandId == e.Interaction.Data.Id); + var methods = s_commandMethods.Where(x => x.CommandId == e.Interaction.Data.Id); + var groups = s_groupCommands.Where(x => x.CommandId == e.Interaction.Data.Id); + var subgroups = s_subGroupCommands.Where(x => x.CommandId == e.Interaction.Data.Id); if (!methods.Any() && !groups.Any() && !subgroups.Any()) throw new InvalidOperationException("A slash command was executed, but no command was registered for it."); if (methods.Any()) { var method = methods.First().Method; var args = await this.ResolveInteractionCommandParameters(e, context, method, e.Interaction.Data.Options); await this.RunCommandAsync(context, method, args); } else if (groups.Any()) { var command = e.Interaction.Data.Options.First(); var method = groups.First().Methods.First(x => x.Key == command.Name).Value; var args = await this.ResolveInteractionCommandParameters(e, context, method, e.Interaction.Data.Options.First().Options); await this.RunCommandAsync(context, method, args); } else if (subgroups.Any()) { var command = e.Interaction.Data.Options.First(); var group = subgroups.First().SubCommands.First(x => x.Name == command.Name); var method = group.Methods.First(x => x.Key == command.Options.First().Name).Value; var args = await this.ResolveInteractionCommandParameters(e, context, method, e.Interaction.Data.Options.First().Options.First().Options); await this.RunCommandAsync(context, method, args); } await this._slashExecuted.InvokeAsync(this, new SlashCommandExecutedEventArgs(this.Client.ServiceProvider) { Context = context }); } catch (Exception ex) { await this._slashError.InvokeAsync(this, new SlashCommandErrorEventArgs(this.Client.ServiceProvider) { Context = context, Exception = ex }); } } else if (e.Interaction.Type == InteractionType.AutoComplete) { - if (_errored) + if (s_errored) throw new InvalidOperationException("Slash commands failed to register properly on startup."); - var methods = _commandMethods.Where(x => x.CommandId == e.Interaction.Data.Id); - var groups = _groupCommands.Where(x => x.CommandId == e.Interaction.Data.Id); - var subgroups = _subGroupCommands.Where(x => x.CommandId == e.Interaction.Data.Id); + var methods = s_commandMethods.Where(x => x.CommandId == e.Interaction.Data.Id); + var groups = s_groupCommands.Where(x => x.CommandId == e.Interaction.Data.Id); + var subgroups = s_subGroupCommands.Where(x => x.CommandId == e.Interaction.Data.Id); if (!methods.Any() && !groups.Any() && !subgroups.Any()) throw new InvalidOperationException("An autocomplete interaction was created, but no command was registered for it."); try { if (methods.Any()) { var focusedOption = e.Interaction.Data.Options.First(o => o.Focused); var method = methods.First().Method; var option = method.GetParameters().Skip(1).First(p => p.GetCustomAttribute().Name == focusedOption.Name); var provider = option.GetCustomAttribute().ProviderType; var providerMethod = provider.GetMethod(nameof(IAutocompleteProvider.Provider)); var providerInstance = Activator.CreateInstance(provider); var context = new AutocompleteContext { Interaction = e.Interaction, Client = this.Client, - Services = _configuration?.ServiceProvider, + Services = Configuration?.ServiceProvider, ApplicationCommandsExtension = this, Guild = e.Interaction.Guild, Channel = e.Interaction.Channel, User = e.Interaction.User, Options = e.Interaction.Data.Options.ToList(), FocusedOption = focusedOption, Locale = e.Interaction.Locale, GuildLocale = e.Interaction.GuildLocale }; var choices = await (Task>) providerMethod.Invoke(providerInstance, new[] { context }); await e.Interaction.CreateResponseAsync(InteractionResponseType.AutoCompleteResult, new DiscordInteractionResponseBuilder().AddAutoCompleteChoices(choices)); } else if (groups.Any()) { var command = e.Interaction.Data.Options.First(); var group = groups.First().Methods.First(x => x.Key == command.Name).Value; var focusedOption = command.Options.First(o => o.Focused); var option = group.GetParameters().Skip(1).First(p => p.GetCustomAttribute().Name == focusedOption.Name); var provider = option.GetCustomAttribute().ProviderType; var providerMethod = provider.GetMethod(nameof(IAutocompleteProvider.Provider)); var providerInstance = Activator.CreateInstance(provider); var context = new AutocompleteContext { Interaction = e.Interaction, - Services = _configuration?.ServiceProvider, + Services = Configuration?.ServiceProvider, ApplicationCommandsExtension = this, Guild = e.Interaction.Guild, Channel = e.Interaction.Channel, User = e.Interaction.User, Options = command.Options.ToList(), FocusedOption = focusedOption, Locale = e.Interaction.Locale, GuildLocale = e.Interaction.GuildLocale }; var choices = await (Task>) providerMethod.Invoke(providerInstance, new[] { context }); await e.Interaction.CreateResponseAsync(InteractionResponseType.AutoCompleteResult, new DiscordInteractionResponseBuilder().AddAutoCompleteChoices(choices)); } /*else if (subgroups.Any()) { var command = e.Interaction.Data.Options.First(); var method = methods.First().Method; var group = subgroups.First().SubCommands.First(x => x.Name == command.Name); var focusedOption = command.Options.First(x => x.Name == group.Name).Options.First(o => o.Focused); this.Client.Logger.LogDebug("SUBGROUP::" + focusedOption.Name + ": " + focusedOption.RawValue); var option = group.Methods.First(p => p.Value.GetCustomAttribute().Name == focusedOption.Name).Value; var provider = option.GetCustomAttribute().ProviderType; var providerMethod = provider.GetMethod(nameof(IAutocompleteProvider.Provider)); var providerInstance = Activator.CreateInstance(provider); var context = new AutocompleteContext { Interaction = e.Interaction, Services = this._configuration?.Services, ApplicationCommandsExtension = this, Guild = e.Interaction.Guild, Channel = e.Interaction.Channel, User = e.Interaction.User, Options = command.Options.First(x => x.Name == group.Name).Options.ToList(), FocusedOption = focusedOption }; var choices = await (Task>) providerMethod.Invoke(providerInstance, new[] { context }); await e.Interaction.CreateResponseAsync(InteractionResponseType.AutoCompleteResult, new DiscordInteractionResponseBuilder().AddAutoCompleteChoices(choices)); }*/ } catch (Exception ex) { this.Client.Logger.LogError(ex, "Error in autocomplete interaction"); } } }); return Task.CompletedTask; } /// /// Context menu handler. /// /// The client. /// The event args. private Task ContextMenuHandler(DiscordClient client, ContextMenuInteractionCreateEventArgs e) { _ = Task.Run(async () => { //Creates the context var context = new ContextMenuContext { Interaction = e.Interaction, Channel = e.Interaction.Channel, Client = client, - Services = _configuration?.ServiceProvider, + Services = Configuration?.ServiceProvider, CommandName = e.Interaction.Data.Name, ApplicationCommandsExtension = this, Guild = e.Interaction.Guild, InteractionId = e.Interaction.Id, User = e.Interaction.User, Token = e.Interaction.Token, TargetUser = e.TargetUser, TargetMessage = e.TargetMessage, Type = e.Type, Locale = e.Interaction.Locale, GuildLocale = e.Interaction.GuildLocale }; try { - if (_errored) + if (s_errored) throw new InvalidOperationException("Context menus failed to register properly on startup."); //Gets the method for the command - var method = _contextMenuCommands.FirstOrDefault(x => x.CommandId == e.Interaction.Data.Id); + var method = s_contextMenuCommands.FirstOrDefault(x => x.CommandId == e.Interaction.Data.Id); if (method == null) throw new InvalidOperationException("A context menu was executed, but no command was registered for it."); await this.RunCommandAsync(context, method.Method, new[] { context }); await this._contextMenuExecuted.InvokeAsync(this, new ContextMenuExecutedEventArgs(this.Client.ServiceProvider) { Context = context }); } catch (Exception ex) { await this._contextMenuErrored.InvokeAsync(this, new ContextMenuErrorEventArgs(this.Client.ServiceProvider) { Context = context, Exception = ex }); } }); return Task.CompletedTask; } /// /// Runs a command. /// /// The base context. /// The method info. /// The arguments. [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0066:Convert switch statement to expression", Justification = "")] internal async Task RunCommandAsync(BaseContext context, MethodInfo method, IEnumerable args) { object classInstance; //Accounts for lifespans var moduleLifespan = (method.DeclaringType.GetCustomAttribute() != null ? method.DeclaringType.GetCustomAttribute()?.Lifespan : ApplicationCommandModuleLifespan.Transient) ?? ApplicationCommandModuleLifespan.Transient; switch (moduleLifespan) { case ApplicationCommandModuleLifespan.Scoped: //Accounts for static methods and adds DI - classInstance = method.IsStatic ? ActivatorUtilities.CreateInstance(_configuration?.ServiceProvider.CreateScope().ServiceProvider, method.DeclaringType) : CreateInstance(method.DeclaringType, _configuration?.ServiceProvider.CreateScope().ServiceProvider); + classInstance = method.IsStatic ? ActivatorUtilities.CreateInstance(Configuration?.ServiceProvider.CreateScope().ServiceProvider, method.DeclaringType) : CreateInstance(method.DeclaringType, Configuration?.ServiceProvider.CreateScope().ServiceProvider); break; case ApplicationCommandModuleLifespan.Transient: //Accounts for static methods and adds DI - classInstance = method.IsStatic ? ActivatorUtilities.CreateInstance(_configuration?.ServiceProvider, method.DeclaringType) : CreateInstance(method.DeclaringType, _configuration?.ServiceProvider); + classInstance = method.IsStatic ? ActivatorUtilities.CreateInstance(Configuration?.ServiceProvider, method.DeclaringType) : CreateInstance(method.DeclaringType, Configuration?.ServiceProvider); break; //If singleton, gets it from the singleton list case ApplicationCommandModuleLifespan.Singleton: - classInstance = _singletonModules.First(x => ReferenceEquals(x.GetType(), method.DeclaringType)); + classInstance = s_singletonModules.First(x => ReferenceEquals(x.GetType(), method.DeclaringType)); break; default: throw new Exception($"An unknown {nameof(ApplicationCommandModuleLifespanAttribute)} scope was specified on command {context.CommandName}"); } ApplicationCommandsModule module = null; if (classInstance is ApplicationCommandsModule mod) module = mod; // Slash commands if (context is InteractionContext slashContext) { await this.RunPreexecutionChecksAsync(method, slashContext); var shouldExecute = await (module?.BeforeSlashExecutionAsync(slashContext) ?? Task.FromResult(true)); if (shouldExecute) { await (Task)method.Invoke(classInstance, args.ToArray()); await (module?.AfterSlashExecutionAsync(slashContext) ?? Task.CompletedTask); } } // Context menus if (context is ContextMenuContext contextMenuContext) { await this.RunPreexecutionChecksAsync(method, contextMenuContext); var shouldExecute = await (module?.BeforeContextMenuExecutionAsync(contextMenuContext) ?? Task.FromResult(true)); if (shouldExecute) { await (Task)method.Invoke(classInstance, args.ToArray()); await (module?.AfterContextMenuExecutionAsync(contextMenuContext) ?? Task.CompletedTask); } } } /// /// Property injection /// /// The type. /// The services. internal static object CreateInstance(Type t, IServiceProvider services) { var ti = t.GetTypeInfo(); var constructors = ti.DeclaredConstructors .Where(xci => xci.IsPublic) .ToArray(); if (constructors.Length != 1) throw new ArgumentException("Specified type does not contain a public constructor or contains more than one public constructor."); var constructor = constructors[0]; var constructorArgs = constructor.GetParameters(); var args = new object[constructorArgs.Length]; if (constructorArgs.Length != 0 && services == null) throw new InvalidOperationException("Dependency collection needs to be specified for parameterized constructors."); // inject via constructor if (constructorArgs.Length != 0) for (var i = 0; i < args.Length; i++) args[i] = services.GetRequiredService(constructorArgs[i].ParameterType); var moduleInstance = Activator.CreateInstance(t, args); // inject into properties var props = t.GetRuntimeProperties().Where(xp => xp.CanWrite && xp.SetMethod != null && !xp.SetMethod.IsStatic && xp.SetMethod.IsPublic); foreach (var prop in props) { if (prop.GetCustomAttribute() != null) continue; var service = services.GetService(prop.PropertyType); if (service == null) continue; prop.SetValue(moduleInstance, service); } // inject into fields var fields = t.GetRuntimeFields().Where(xf => !xf.IsInitOnly && !xf.IsStatic && xf.IsPublic); foreach (var field in fields) { if (field.GetCustomAttribute() != null) continue; var service = services.GetService(field.FieldType); if (service == null) continue; field.SetValue(moduleInstance, service); } return moduleInstance; } /// /// Resolves the slash command parameters. /// /// The event arguments. /// The interaction context. /// The method info. /// The options. private async Task> ResolveInteractionCommandParameters(InteractionCreateEventArgs e, InteractionContext context, MethodInfo method, IEnumerable options) { var args = new List { context }; var parameters = method.GetParameters().Skip(1); for (var i = 0; i < parameters.Count(); i++) { var parameter = parameters.ElementAt(i); //Accounts for optional arguments without values given if (parameter.IsOptional && (options == null || (!options?.Any(x => x.Name == parameter.GetCustomAttribute().Name.ToLower()) ?? true))) args.Add(parameter.DefaultValue); else { var option = options.Single(x => x.Name == parameter.GetCustomAttribute().Name.ToLower()); if (parameter.ParameterType == typeof(string)) args.Add(option.Value.ToString()); else if (parameter.ParameterType.IsEnum) args.Add(Enum.Parse(parameter.ParameterType, (string)option.Value)); else if (parameter.ParameterType == typeof(long) || parameter.ParameterType == typeof(long?)) args.Add((long?)option.Value); else if (parameter.ParameterType == typeof(bool) || parameter.ParameterType == typeof(bool?)) args.Add((bool?)option.Value); else if (parameter.ParameterType == typeof(double) || parameter.ParameterType == typeof(double?)) args.Add((double?)option.Value); else if (parameter.ParameterType == typeof(int) || parameter.ParameterType == typeof(int?)) args.Add((int?)option.Value); else if (parameter.ParameterType == typeof(DiscordAttachment)) { //Checks through resolved if (e.Interaction.Data.Resolved.Attachments != null && e.Interaction.Data.Resolved.Attachments.TryGetValue((ulong)option.Value, out var attachment)) args.Add(attachment); else args.Add(new DiscordAttachment() { Id = (ulong)option.Value, Discord = this.Client.ApiClient.Discord }); } else if (parameter.ParameterType == typeof(DiscordUser)) { //Checks through resolved if (e.Interaction.Data.Resolved.Members != null && e.Interaction.Data.Resolved.Members.TryGetValue((ulong)option.Value, out var member)) args.Add(member); else if (e.Interaction.Data.Resolved.Users != null && e.Interaction.Data.Resolved.Users.TryGetValue((ulong)option.Value, out var user)) args.Add(user); else args.Add(await this.Client.GetUserAsync((ulong)option.Value)); } else if (parameter.ParameterType == typeof(DiscordChannel)) { //Checks through resolved if (e.Interaction.Data.Resolved.Channels != null && e.Interaction.Data.Resolved.Channels.TryGetValue((ulong)option.Value, out var channel)) args.Add(channel); else args.Add(e.Interaction.Guild.GetChannel((ulong)option.Value)); } else if (parameter.ParameterType == typeof(DiscordRole)) { //Checks through resolved if (e.Interaction.Data.Resolved.Roles != null && e.Interaction.Data.Resolved.Roles.TryGetValue((ulong)option.Value, out var role)) args.Add(role); else args.Add(e.Interaction.Guild.GetRole((ulong)option.Value)); } else if (parameter.ParameterType == typeof(SnowflakeObject)) { //Checks through resolved if (e.Interaction.Data.Resolved.Roles != null && e.Interaction.Data.Resolved.Roles.TryGetValue((ulong)option.Value, out var role)) args.Add(role); else if (e.Interaction.Data.Resolved.Members != null && e.Interaction.Data.Resolved.Members.TryGetValue((ulong)option.Value, out var member)) args.Add(member); else if (e.Interaction.Data.Resolved.Users != null && e.Interaction.Data.Resolved.Users.TryGetValue((ulong)option.Value, out var user)) args.Add(user); else if (e.Interaction.Data.Resolved.Attachments != null && e.Interaction.Data.Resolved.Attachments.TryGetValue((ulong)option.Value, out var attachment)) args.Add(attachment); else throw new ArgumentException("Error resolving mentionable option."); } else throw new ArgumentException($"Error resolving interaction."); } } return args; } /// /// Runs the preexecution checks. /// /// The method info. /// The base context. private async Task RunPreexecutionChecksAsync(MethodInfo method, BaseContext context) { if (context is InteractionContext ctx) { //Gets all attributes from parent classes as well and stuff var attributes = new List(); attributes.AddRange(method.GetCustomAttributes(true)); attributes.AddRange(method.DeclaringType.GetCustomAttributes()); if (method.DeclaringType.DeclaringType != null) { attributes.AddRange(method.DeclaringType.DeclaringType.GetCustomAttributes()); if (method.DeclaringType.DeclaringType.DeclaringType != null) { attributes.AddRange(method.DeclaringType.DeclaringType.DeclaringType.GetCustomAttributes()); } } var dict = new Dictionary(); foreach (var att in attributes) { //Runs the check and adds the result to a list var result = await att.ExecuteChecksAsync(ctx); dict.Add(att, result); } //Checks if any failed, and throws an exception if (dict.Any(x => x.Value == false)) throw new SlashExecutionChecksFailedException { FailedChecks = dict.Where(x => x.Value == false).Select(x => x.Key).ToList() }; } - if (context is ContextMenuContext CMctx) + if (context is ContextMenuContext cMctx) { var attributes = new List(); attributes.AddRange(method.GetCustomAttributes(true)); attributes.AddRange(method.DeclaringType.GetCustomAttributes()); if (method.DeclaringType.DeclaringType != null) { attributes.AddRange(method.DeclaringType.DeclaringType.GetCustomAttributes()); if (method.DeclaringType.DeclaringType.DeclaringType != null) { attributes.AddRange(method.DeclaringType.DeclaringType.DeclaringType.GetCustomAttributes()); } } var dict = new Dictionary(); foreach (var att in attributes) { //Runs the check and adds the result to a list - var result = await att.ExecuteChecksAsync(CMctx); + var result = await att.ExecuteChecksAsync(cMctx); dict.Add(att, result); } //Checks if any failed, and throws an exception if (dict.Any(x => x.Value == false)) throw new ContextMenuExecutionChecksFailedException { FailedChecks = dict.Where(x => x.Value == false).Select(x => x.Key).ToList() }; } } /// /// Gets the choice attributes from choice provider. /// /// The custom attributes. /// The optional guild id private static async Task> GetChoiceAttributesFromProvider(IEnumerable customAttributes, ulong? guildId = null) { var choices = new List(); foreach (var choiceProviderAttribute in customAttributes) { var method = choiceProviderAttribute.ProviderType.GetMethod(nameof(IChoiceProvider.Provider)); if (method == null) throw new ArgumentException("ChoiceProviders must inherit from IChoiceProvider."); else { var instance = Activator.CreateInstance(choiceProviderAttribute.ProviderType); // Abstract class offers more properties that can be set if (choiceProviderAttribute.ProviderType.IsSubclassOf(typeof(ChoiceProvider))) { choiceProviderAttribute.ProviderType.GetProperty(nameof(ChoiceProvider.GuildId)) ?.SetValue(instance, guildId); choiceProviderAttribute.ProviderType.GetProperty(nameof(ChoiceProvider.Services)) - ?.SetValue(instance, _configuration.ServiceProvider); + ?.SetValue(instance, Configuration.ServiceProvider); } //Gets the choices from the method var result = await (Task>)method.Invoke(instance, null); if (result.Any()) { choices.AddRange(result); } } } return choices; } /// /// Gets the choice attributes from enum parameter. /// /// The enum parameter. private static List GetChoiceAttributesFromEnumParameter(Type enumParam) { var choices = new List(); foreach (Enum enumValue in Enum.GetValues(enumParam)) { choices.Add(new DiscordApplicationCommandOptionChoice(enumValue.GetName(), enumValue.ToString())); } return choices; } /// /// Gets the parameter type. /// /// The type. private static ApplicationCommandOptionType GetParameterType(Type type) { var parametertype = type == typeof(string) ? ApplicationCommandOptionType.String : type == typeof(long) || type == typeof(long?) || type == typeof(int) || type == typeof(int?) ? ApplicationCommandOptionType.Integer : type == typeof(bool) || type == typeof(bool?) ? ApplicationCommandOptionType.Boolean : type == typeof(double) || type == typeof(double?) ? ApplicationCommandOptionType.Number : type == typeof(DiscordChannel) ? ApplicationCommandOptionType.Channel : type == typeof(DiscordUser) ? ApplicationCommandOptionType.User : type == typeof(DiscordRole) ? ApplicationCommandOptionType.Role : type == typeof(SnowflakeObject) ? ApplicationCommandOptionType.Mentionable : type == typeof(DiscordAttachment) ? ApplicationCommandOptionType.Attachment : type.IsEnum ? ApplicationCommandOptionType.String : throw new ArgumentException("Cannot convert type! Argument types must be string, int, long, bool, double, DiscordChannel, DiscordUser, DiscordRole, SnowflakeObject, DiscordAttachment or an Enum."); return parametertype; } /// /// Gets the choice attributes from parameter. /// /// The choice attributes. private static List GetChoiceAttributesFromParameter(IEnumerable choiceattributes) { return !choiceattributes.Any() ? null : choiceattributes.Select(att => new DiscordApplicationCommandOptionChoice(att.Name, att.Value)).ToList(); } /// /// Parses the parameters. /// /// The parameters. /// The optional guild id. internal static async Task> ParseParametersAsync(ParameterInfo[] parameters, ulong? guildId) { var options = new List(); foreach (var parameter in parameters) { //Gets the attribute var optionattribute = parameter.GetCustomAttribute(); if (optionattribute == null) throw new ArgumentException("Arguments must have the Option attribute!"); var minimumValue = parameter.GetCustomAttribute()?.Value ?? null; var maximumValue = parameter.GetCustomAttribute()?.Value ?? null; var autocompleteAttribute = parameter.GetCustomAttribute(); if (optionattribute.Autocomplete && autocompleteAttribute == null) throw new ArgumentException("Autocomplete options must have the Autocomplete attribute!"); if (!optionattribute.Autocomplete && autocompleteAttribute != null) throw new ArgumentException("Setting an autocomplete provider requires the option to have autocomplete set to true!"); //Sets the type var type = parameter.ParameterType; var parametertype = GetParameterType(type); //Handles choices //From attributes var choices = GetChoiceAttributesFromParameter(parameter.GetCustomAttributes()); //From enums if (parameter.ParameterType.IsEnum) { choices = GetChoiceAttributesFromEnumParameter(parameter.ParameterType); } //From choice provider var choiceProviders = parameter.GetCustomAttributes(); if (choiceProviders.Any()) { choices = await GetChoiceAttributesFromProvider(choiceProviders, guildId); } var channelTypes = parameter.GetCustomAttribute()?.ChannelTypes ?? null; options.Add(new DiscordApplicationCommandOption(optionattribute.Name, optionattribute.Description, parametertype, !parameter.IsOptional, choices, null, channelTypes, optionattribute.Autocomplete, minimumValue, maximumValue)); } return options; } /// /// Refreshes your commands, used for refreshing choice providers or applying commands registered after the ready event on the discord client. /// Should only be run on the slash command extension linked to shard 0 if sharding. /// Not recommended and should be avoided since it can make slash commands be unresponsive for a while. /// public async Task RefreshCommandsAsync() { - _commandMethods.Clear(); - _groupCommands.Clear(); - _subGroupCommands.Clear(); - _registeredCommands.Clear(); - _contextMenuCommands.Clear(); - _globalDiscordCommands.Clear(); - _guildDiscordCommands.Clear(); - _globalDiscordCommands = null; - _guildDiscordCommands = null; - _guildCommands.Clear(); - _globalCommands.Clear(); + s_commandMethods.Clear(); + s_groupCommands.Clear(); + s_subGroupCommands.Clear(); + s_registeredCommands.Clear(); + s_contextMenuCommands.Clear(); + GlobalDiscordCommands.Clear(); + GuildDiscordCommands.Clear(); + GlobalDiscordCommands = null; + GuildDiscordCommands = null; + GuildCommandsInternal.Clear(); + GlobalCommandsInternal.Clear(); await this.UpdateAsync(); } /// /// Fires when the execution of a slash command fails. /// public event AsyncEventHandler SlashCommandErrored { add { this._slashError.Register(value); } remove { this._slashError.Unregister(value); } } private AsyncEvent _slashError; /// /// Fires when the execution of a slash command is successful. /// public event AsyncEventHandler SlashCommandExecuted { add { this._slashExecuted.Register(value); } remove { this._slashExecuted.Unregister(value); } } private AsyncEvent _slashExecuted; /// /// Fires when the execution of a context menu fails. /// public event AsyncEventHandler ContextMenuErrored { add { this._contextMenuErrored.Register(value); } remove { this._contextMenuErrored.Unregister(value); } } private AsyncEvent _contextMenuErrored; /// /// Fire when the execution of a context menu is successful. /// public event AsyncEventHandler ContextMenuExecuted { add { this._contextMenuExecuted.Register(value); } remove { this._contextMenuExecuted.Unregister(value); } } private AsyncEvent _contextMenuExecuted; } /// /// Holds configuration data for setting up an application command. /// internal class ApplicationCommandsModuleConfiguration { /// /// The type of the command module. /// public Type Type { get; } /// /// The permission setup. /// public Action Setup { get; } public Action Translations { get; } /// /// Creates a new command configuration. /// /// The type of the command module. /// The permission setup callback. /// The translation setup callback. public ApplicationCommandsModuleConfiguration(Type type, Action setup = null, Action translations = null) { this.Type = type; this.Setup = setup; this.Translations = translations; } } /// /// Links a command to its original command module. /// internal class ApplicationCommandSourceLink { /// /// The command. /// public DiscordApplicationCommand ApplicationCommand { get; set; } /// /// The base/root module the command is contained in. /// public Type RootCommandContainerType { get; set; } /// /// The direct group the command is contained in. /// public Type CommandContainerType { get; set; } } /// /// The command method. /// internal class CommandMethod { /// /// Gets or sets the command id. /// public ulong CommandId { get; set; } /// /// Gets or sets the name. /// public string Name { get; set; } /// /// Gets or sets the method. /// public MethodInfo Method { get; set; } } /// /// The group command. /// internal class GroupCommand { /// /// Gets or sets the command id. /// public ulong CommandId { get; set; } /// /// Gets or sets the name. /// public string Name { get; set; } /// /// Gets or sets the methods. /// public List> Methods { get; set; } = null; } /// /// The sub group command. /// internal class SubGroupCommand { /// /// Gets or sets the command id. /// public ulong CommandId { get; set; } /// /// Gets or sets the name. /// public string Name { get; set; } /// /// Gets or sets the sub commands. /// public List SubCommands { get; set; } = new List(); } /// /// The context menu command. /// internal class ContextMenuCommand { /// /// Gets or sets the command id. /// public ulong CommandId { get; set; } /// /// Gets or sets the name. /// public string Name { get; set; } /// /// Gets or sets the method. /// public MethodInfo Method { get; set; } } #region Default Help /// /// Represents the default help module. /// public class DefaultHelpModule : ApplicationCommandsModule { public class DefaultHelpAutoCompleteProvider : IAutocompleteProvider { public async Task> Provider(AutocompleteContext context) { var options = new List(); var globalCommandsTask = context.Client.GetGlobalApplicationCommandsAsync(); var guildCommandsTask= context.Client.GetGuildApplicationCommandsAsync(context.Guild.Id); await Task.WhenAll(globalCommandsTask, guildCommandsTask); var slashCommands = globalCommandsTask.Result.Concat(guildCommandsTask.Result) .Where(ac => !ac.Name.Equals("help", StringComparison.OrdinalIgnoreCase)) .GroupBy(ac => ac.Name).Select(x => x.First()). Where(ac => ac.Name.StartsWith(context.Options[0].Value.ToString(), StringComparison.OrdinalIgnoreCase)).ToList(); foreach (var sc in slashCommands.Take(25)) { options.Add(new DiscordApplicationCommandAutocompleteChoice(sc.Name, sc.Name.Trim())); } return options.AsEnumerable(); } } public class DefaultHelpAutoCompleteLevelOneProvider : IAutocompleteProvider { public async Task> Provider(AutocompleteContext context) { var options = new List(); var globalCommandsTask = context.Client.GetGlobalApplicationCommandsAsync(); var guildCommandsTask= context.Client.GetGuildApplicationCommandsAsync(context.Guild.Id); await Task.WhenAll(globalCommandsTask, guildCommandsTask); var slashCommands = globalCommandsTask.Result.Concat(guildCommandsTask.Result) .Where(ac => !ac.Name.Equals("help", StringComparison.OrdinalIgnoreCase)) .GroupBy(ac => ac.Name).Select(x => x.First()); var command = slashCommands.FirstOrDefault(ac => ac.Name.Equals(context.Options[0].Value.ToString().Trim(),StringComparison.OrdinalIgnoreCase)); if (command is null || command.Options is null) { options.Add(new DiscordApplicationCommandAutocompleteChoice("no_options_for_this_command", "no_options_for_this_command")); } else { var opt = command.Options.Where(c => c.Type is ApplicationCommandOptionType.SubCommandGroup or ApplicationCommandOptionType.SubCommand && c.Name.StartsWith(context.Options[1].Value.ToString(), StringComparison.InvariantCultureIgnoreCase)).ToList(); foreach (var option in opt.Take(25)) { options.Add(new DiscordApplicationCommandAutocompleteChoice(option.Name, option.Name.Trim())); } } return options.AsEnumerable(); } } public class DefaultHelpAutoCompleteLevelTwoProvider : IAutocompleteProvider { public async Task> Provider(AutocompleteContext context) { var options = new List(); var globalCommandsTask = context.Client.GetGlobalApplicationCommandsAsync(); var guildCommandsTask= context.Client.GetGuildApplicationCommandsAsync(context.Guild.Id); await Task.WhenAll(globalCommandsTask, guildCommandsTask); var slashCommands = globalCommandsTask.Result.Concat(guildCommandsTask.Result) .Where(ac => !ac.Name.Equals("help", StringComparison.OrdinalIgnoreCase)) .GroupBy(ac => ac.Name).Select(x => x.First()); var command = slashCommands.FirstOrDefault(ac => ac.Name.Equals(context.Options[0].Value.ToString().Trim(), StringComparison.OrdinalIgnoreCase)); if (command.Options is null) { options.Add(new DiscordApplicationCommandAutocompleteChoice("no_options_for_this_command", "no_options_for_this_command")); return options.AsEnumerable(); } var foundCommand = command.Options.FirstOrDefault(op => op.Name.Equals(context.Options[1].Value.ToString().Trim(), StringComparison.OrdinalIgnoreCase)); if (foundCommand is null || foundCommand.Options is null) { options.Add(new DiscordApplicationCommandAutocompleteChoice("no_options_for_this_command", "no_options_for_this_command")); } else { var opt = foundCommand.Options.Where(x => x.Type == ApplicationCommandOptionType.SubCommand && x.Name.StartsWith(context.Options[2].Value.ToString(), StringComparison.OrdinalIgnoreCase)).ToList(); foreach (var option in opt.Take(25)) { options.Add(new DiscordApplicationCommandAutocompleteChoice(option.Name, option.Name.Trim())); } } return options.AsEnumerable(); } } [SlashCommand("help", "Displays command help")] public async Task DefaultHelpAsync(InteractionContext ctx, [Autocomplete(typeof(DefaultHelpAutoCompleteProvider))] [Option("option_one", "top level command to provide help for", true)] string commandName, [Autocomplete(typeof(DefaultHelpAutoCompleteLevelOneProvider))] [Option("option_two", "subgroup or command to provide help for", true)] string commandOneName = null, [Autocomplete(typeof(DefaultHelpAutoCompleteLevelTwoProvider))] [Option("option_three", "command to provide help for", true)] string commandTwoName = null) { var globalCommandsTask = ctx.Client.GetGlobalApplicationCommandsAsync(); var guildCommandsTask= ctx.Client.GetGuildApplicationCommandsAsync(ctx.Guild.Id); await Task.WhenAll(globalCommandsTask, guildCommandsTask); var applicationCommands = globalCommandsTask.Result.Concat(guildCommandsTask.Result) .Where(ac => !ac.Name.Equals("help", StringComparison.OrdinalIgnoreCase)) .GroupBy(ac => ac.Name).Select(x => x.First()) .ToList(); if (applicationCommands.Count < 1) { await ctx.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, new DiscordInteractionResponseBuilder() .WithContent($"There are no slash commands for guild {ctx.Guild.Name}").AsEphemeral(true)); return; } if (commandTwoName is not null && !commandTwoName.Equals("no_options_for_this_command")) { var commandsWithSubCommands = applicationCommands.FindAll(ac => ac.Options is not null && ac.Options.Any(op => op.Type == ApplicationCommandOptionType.SubCommandGroup)); var cmdParent = commandsWithSubCommands.FirstOrDefault(cm => cm.Options.Any(op => op.Name.Equals(commandOneName))).Options .FirstOrDefault(opt => opt.Name.Equals(commandOneName,StringComparison.OrdinalIgnoreCase)); var cmd = cmdParent.Options.FirstOrDefault(op => op.Name.Equals(commandTwoName,StringComparison.OrdinalIgnoreCase)); var discordEmbed = new DiscordEmbedBuilder { Title = "Help", Description = $"{Formatter.InlineCode(cmd.Name)}: {cmd.Description ?? "No description provided."}" }; if (cmd.Options is not null) { var commandOptions = cmd.Options.ToList(); var sb = new StringBuilder(); foreach (var option in commandOptions) sb.Append('`').Append(option.Name).Append(" (").Append(")`: ").Append(option.Description ?? "No description provided.").Append('\n'); sb.Append('\n'); discordEmbed.AddField("Arguments", sb.ToString().Trim(), false); } await ctx.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, new DiscordInteractionResponseBuilder().AddEmbed(discordEmbed).AsEphemeral(true)); } else if (commandOneName is not null && commandTwoName is null && !commandOneName.Equals("no_options_for_this_command")) { var commandsWithOptions = applicationCommands.FindAll(ac => ac.Options is not null && ac.Options.All(op => op.Type == ApplicationCommandOptionType.SubCommand)); var subCommandParent = commandsWithOptions.FirstOrDefault(cm => cm.Name.Equals(commandName,StringComparison.OrdinalIgnoreCase)); var subCommand = subCommandParent.Options.FirstOrDefault(op => op.Name.Equals(commandOneName,StringComparison.OrdinalIgnoreCase)); var discordEmbed = new DiscordEmbedBuilder { Title = "Help", Description = $"{Formatter.InlineCode(subCommand.Name)}: {subCommand.Description ?? "No description provided."}" }; if (subCommand.Options is not null) { var commandOptions = subCommand.Options.ToList(); var sb = new StringBuilder(); foreach (var option in commandOptions) sb.Append('`').Append(option.Name).Append(" (").Append(")`: ").Append(option.Description ?? "No description provided.").Append('\n'); sb.Append('\n'); discordEmbed.AddField("Arguments", sb.ToString().Trim(), false); } await ctx.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, new DiscordInteractionResponseBuilder().AddEmbed(discordEmbed).AsEphemeral(true)); } else { var command = applicationCommands.FirstOrDefault(cm => cm.Name.Equals(commandName, StringComparison.OrdinalIgnoreCase)); if (command is null) { await ctx.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, new DiscordInteractionResponseBuilder() .WithContent($"No command called {commandName} in guild {ctx.Guild.Name}").AsEphemeral(true)); return; } var discordEmbed = new DiscordEmbedBuilder { Title = "Help", Description = $"{Formatter.InlineCode(command.Name)}: {command.Description ?? "No description provided."}" }; if (command.Options is not null) { var commandOptions = command.Options.ToList(); var sb = new StringBuilder(); foreach (var option in commandOptions) sb.Append('`').Append(option.Name).Append(" (").Append(")`: ").Append(option.Description ?? "No description provided.").Append('\n'); sb.Append('\n'); discordEmbed.AddField("Arguments", sb.ToString().Trim(), false); } await ctx.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, new DiscordInteractionResponseBuilder().AddEmbed(discordEmbed).AsEphemeral(true)); } } } #endregion } diff --git a/DisCatSharp.ApplicationCommands/Attributes/SlashCommand/RequireNsfwAttribute.cs b/DisCatSharp.ApplicationCommands/Attributes/SlashCommand/RequireNsfwAttribute.cs index 8f6399645..873572f37 100644 --- a/DisCatSharp.ApplicationCommands/Attributes/SlashCommand/RequireNsfwAttribute.cs +++ b/DisCatSharp.ApplicationCommands/Attributes/SlashCommand/RequireNsfwAttribute.cs @@ -1,46 +1,46 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System; using System.Threading.Tasks; namespace DisCatSharp.ApplicationCommands.Attributes { /// /// Defines that this application command is only usable within a guild. /// [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false, Inherited = false)] public sealed class ApplicationCommandRequireNsfwAttribute : SlashCheckBaseAttribute { /// /// Defines that this command is only usable within a guild. /// public ApplicationCommandRequireNsfwAttribute() { } /// /// Runs checks. /// public override Task ExecuteChecksAsync(InteractionContext ctx) - => Task.FromResult(ctx.Channel.Guild == null || ctx.Channel.IsNSFW); + => Task.FromResult(ctx.Channel.Guild == null || ctx.Channel.IsNsfw); } } diff --git a/DisCatSharp.ApplicationCommands/Attributes/SlashCommand/RequireOwnerOrIdAttribute.cs b/DisCatSharp.ApplicationCommands/Attributes/SlashCommand/RequireOwnerOrIdAttribute.cs index 90b9db096..f6d8fd779 100644 --- a/DisCatSharp.ApplicationCommands/Attributes/SlashCommand/RequireOwnerOrIdAttribute.cs +++ b/DisCatSharp.ApplicationCommands/Attributes/SlashCommand/RequireOwnerOrIdAttribute.cs @@ -1,67 +1,67 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using System.Threading.Tasks; namespace DisCatSharp.ApplicationCommands.Attributes { /// /// Requires ownership of the bot or a whitelisted id to execute this command. /// [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false, Inherited = false)] public sealed class ApplicationCommandRequireOwnerOrIdAttribute : SlashCheckBaseAttribute { /// /// Allowed user ids /// public IReadOnlyList UserIds { get; } /// /// Defines that usage of this command is restricted to the owner or whitelisted ids of the bot. /// /// List of allowed user ids - public ApplicationCommandRequireOwnerOrIdAttribute(params ulong[] user_ids) + public ApplicationCommandRequireOwnerOrIdAttribute(params ulong[] userIds) { - this.UserIds = new ReadOnlyCollection(user_ids); + this.UserIds = new ReadOnlyCollection(userIds); } /// /// Executes the a check. /// /// The command context.s public override Task ExecuteChecksAsync(InteractionContext ctx) { var app = ctx.Client.CurrentApplication; var me = ctx.Client.CurrentUser; var owner = app != null ? Task.FromResult(app.Owners.Any(x => x.Id == ctx.User.Id)) : Task.FromResult(ctx.User.Id == me.Id); var allowed = this.UserIds.Contains(ctx.User.Id); return allowed ? Task.FromResult(true) : owner; } } } diff --git a/DisCatSharp.ApplicationCommands/Attributes/SlashCommand/SlashCommandAttribute.cs b/DisCatSharp.ApplicationCommands/Attributes/SlashCommand/SlashCommandAttribute.cs index ab5d23719..378f53d0b 100644 --- a/DisCatSharp.ApplicationCommands/Attributes/SlashCommand/SlashCommandAttribute.cs +++ b/DisCatSharp.ApplicationCommands/Attributes/SlashCommand/SlashCommandAttribute.cs @@ -1,71 +1,71 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System; namespace DisCatSharp.ApplicationCommands { /// /// Marks this method as a slash command /// [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] public class SlashCommandAttribute : Attribute { /// /// Gets the name of this command /// public string Name { get; set; } /// /// Gets the description of this command /// public string Description { get; set; } /// /// Gets the default permission of this command /// public bool DefaultPermission { get; } /// /// Gets the needed permission of this command /// public Permissions? Permission { get; set; } /// /// Gets the dm permission of this command /// public bool? DmPermission { get; set; } /// /// Marks this method as a slash command /// /// The name of this slash command. /// The description of this slash command. /// Whether everyone can execute this command. - public SlashCommandAttribute(string name, string description, bool default_permission = true) + public SlashCommandAttribute(string name, string description, bool defaultPermission = true) { this.Name = name.ToLower(); this.Description = description; - this.DefaultPermission = default_permission; + this.DefaultPermission = defaultPermission; } } } diff --git a/DisCatSharp.ApplicationCommands/Attributes/SlashCommand/SlashCommandGroupAttribute.cs b/DisCatSharp.ApplicationCommands/Attributes/SlashCommand/SlashCommandGroupAttribute.cs index 6232673c2..9febbe18a 100644 --- a/DisCatSharp.ApplicationCommands/Attributes/SlashCommand/SlashCommandGroupAttribute.cs +++ b/DisCatSharp.ApplicationCommands/Attributes/SlashCommand/SlashCommandGroupAttribute.cs @@ -1,71 +1,71 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System; namespace DisCatSharp.ApplicationCommands { /// /// Marks this class a slash command group /// [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] public class SlashCommandGroupAttribute : Attribute { /// /// Gets the name of this slash command group /// public string Name { get; set; } /// /// Gets the description of this slash command group /// public string Description { get; set; } /// /// Gets the default permission of this slash command group /// public bool DefaultPermission { get; set; } /// /// Gets the needed permission of this slash command group /// public Permissions? Permission { get; set; } /// /// Gets the dm permission of this slash command group /// public bool? DmPermission { get; set; } /// /// Marks this class as a slash command group /// /// The name of this slash command group. /// The description of this slash command group. /// Whether everyone can execute this command. - public SlashCommandGroupAttribute(string name, string description, bool default_permission = true) + public SlashCommandGroupAttribute(string name, string description, bool defaultPermission = true) { this.Name = name.ToLower(); this.Description = description; - this.DefaultPermission = default_permission; + this.DefaultPermission = defaultPermission; } } } diff --git a/DisCatSharp.ApplicationCommands/Context/ApplicationCommandsTranslationContext.cs b/DisCatSharp.ApplicationCommands/Context/ApplicationCommandsTranslationContext.cs index 949d3e96b..6947fdbda 100644 --- a/DisCatSharp.ApplicationCommands/Context/ApplicationCommandsTranslationContext.cs +++ b/DisCatSharp.ApplicationCommands/Context/ApplicationCommandsTranslationContext.cs @@ -1,61 +1,61 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System; namespace DisCatSharp.ApplicationCommands { /// /// The application commands translation context. /// public class ApplicationCommandsTranslationContext { /// /// Gets the type. /// public Type Type { get; } /// /// Gets the name. /// public string Name { get; } /// /// Gets the translation json. /// internal string Translations { get; set; } /// /// Initializes a new instance of the class. /// /// The type. /// The name. internal ApplicationCommandsTranslationContext(Type type, string name) { this.Type = type; this.Name = name; } - public void AddTranslation(string translation_json) - => this.Translations = translation_json; + public void AddTranslation(string translationJson) + => this.Translations = translationJson; } } diff --git a/DisCatSharp.ApplicationCommands/Context/ApplicationCommandsTranslationResolver.cs b/DisCatSharp.ApplicationCommands/Context/ApplicationCommandsTranslationResolver.cs index 972d63ad3..dd0914cca 100644 --- a/DisCatSharp.ApplicationCommands/Context/ApplicationCommandsTranslationResolver.cs +++ b/DisCatSharp.ApplicationCommands/Context/ApplicationCommandsTranslationResolver.cs @@ -1,153 +1,153 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System.Collections.Generic; using DisCatSharp.Entities; namespace DisCatSharp.ApplicationCommands { /// /// Represents a application commands translation resolver. /// public class ApplicationCommandsTranslationResolver { #pragma warning disable IDE1006 /// /// Gets the group translations. /// - public IReadOnlyDictionary GroupTranslations => this._groupTranslations; - private Dictionary _groupTranslations => new(); + public IReadOnlyDictionary GroupTranslations => this.GroupTranslationsInternal; + private Dictionary GroupTranslationsInternal => new(); /// /// Gets the sub group translations. /// - public IReadOnlyDictionary SubGroupTranslations => this._subGroupTranslations; - private Dictionary _subGroupTranslations => new(); + public IReadOnlyDictionary SubGroupTranslations => this.SubGroupTranslationsInternal; + private Dictionary SubGroupTranslationsInternal => new(); /// /// Gets the command translations. /// - public IReadOnlyDictionary CommandTranslations => this._commandTranslations; - private Dictionary _commandTranslations => new(); + public IReadOnlyDictionary CommandTranslations => this.CommandTranslationsInternal; + private Dictionary CommandTranslationsInternal => new(); #pragma warning restore IDE1006 } public class GroupTranslation { /// /// Gets the group name. /// public string GroupName { get; set; } public DiscordApplicationCommandLocalization GroupNameLocalizations { get; set; } public DiscordApplicationCommandLocalization GroupDescriptionLocalizations { get; set; } /// /// Gets the sub group translations. /// public Dictionary SubGroupTranslations { get; set; } /// /// Gets the command translations. /// public Dictionary CommandTranslations { get; set; } } public class SubGroupTranslation { /// /// Gets the sub group name. /// public string SubGroupName { get; set; } public DiscordApplicationCommandLocalization SubGroupNameLocalizations { get; set; } public DiscordApplicationCommandLocalization SubGroupDescriptionLocalizations { get; set; } /// /// Gets the command translations. /// public Dictionary CommandTranslations { get; set; } } public class CommandTranslation { /// /// Gets the command name. /// public string CommandName { get; set; } public DiscordApplicationCommandLocalization CommandNameLocalizations { get; set; } public DiscordApplicationCommandLocalization CommandDescriptionLocalizations { get; set; } /// /// Gets the option translations. /// public Dictionary OptionTranslations { get; set; } } public class OptionTranslation { /// /// Gets the option name. /// public string OptionName { get; set; } public DiscordApplicationCommandLocalization OptionNameLocalizations { get; set; } public DiscordApplicationCommandLocalization OptionDescriptionLocalizations { get; set; } /// /// Gets the choice translations /// public Dictionary ChoiceTranslations { get; set; } /// /// Gets the auto complete choice translations /// public Dictionary AutocompleteChoiceTranslations { get; set; } } public class AutocompleteChoiceTranslation { /// /// Gets the autocomplete choice name. /// public string AutocompleteChoiceName { get; set; } public DiscordApplicationCommandLocalization AutocompleteNameLocalizations { get; set; } } public class ChoiceTranslation { /// /// Gets the choice name /// public string ChoiceName { get; set; } public DiscordApplicationCommandLocalization ChoiceNameLocalizations { get; set; } } } diff --git a/DisCatSharp.ApplicationCommands/Entities/ChoiceTranslator.cs b/DisCatSharp.ApplicationCommands/Entities/ChoiceTranslator.cs index efa142a44..f6ac20697 100644 --- a/DisCatSharp.ApplicationCommands/Entities/ChoiceTranslator.cs +++ b/DisCatSharp.ApplicationCommands/Entities/ChoiceTranslator.cs @@ -1,46 +1,46 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System.Collections.Generic; using DisCatSharp.Entities; using Newtonsoft.Json; namespace DisCatSharp.ApplicationCommands { public class ChoiceTranslator { /// /// Gets the choice name. /// [JsonProperty("name")] public string Name { get; set; } /// /// Gets the choice name translations. /// [JsonProperty("name_translations")] - internal Dictionary NT { get; set; } + internal Dictionary NameTranslationsDictionary { get; set; } [JsonIgnore] public DiscordApplicationCommandLocalization NameTranslations - => new(this.NT); + => new(this.NameTranslationsDictionary); } } diff --git a/DisCatSharp.ApplicationCommands/Entities/CommandTranslator.cs b/DisCatSharp.ApplicationCommands/Entities/CommandTranslator.cs index e0bb7767c..860473870 100644 --- a/DisCatSharp.ApplicationCommands/Entities/CommandTranslator.cs +++ b/DisCatSharp.ApplicationCommands/Entities/CommandTranslator.cs @@ -1,72 +1,72 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System.Collections.Generic; using DisCatSharp.Entities; using DisCatSharp.Enums; using Newtonsoft.Json; namespace DisCatSharp.ApplicationCommands { /// /// Represents a command translator. /// internal class CommandTranslator { /// /// Gets the command name. /// [JsonProperty("name")] public string Name { get; set; } /// /// Gets the application command type. /// Used to determine whether it is an translator for context menu or not. /// [JsonProperty("type")] public ApplicationCommandType Type { get; set; } /// /// Gets the command name translations. /// [JsonProperty("name_translations")] - internal Dictionary NT { get; set; } + internal Dictionary NameTranslationDictionary { get; set; } [JsonIgnore] public DiscordApplicationCommandLocalization NameTranslations - => new(this.NT); + => new(this.NameTranslationDictionary); /// /// Gets the command description translations. /// [JsonProperty("description_translations")] - internal Dictionary DT { get; set; } + internal Dictionary DescriptionTranslationDictionary { get; set; } [JsonIgnore] public DiscordApplicationCommandLocalization DescriptionTranslations - => new(this.DT); + => new(this.DescriptionTranslationDictionary); /// /// Gets the option translators, if applicable. /// [JsonProperty("options")] public List Options { get; set; } } } diff --git a/DisCatSharp.ApplicationCommands/Entities/GroupTranslator.cs b/DisCatSharp.ApplicationCommands/Entities/GroupTranslator.cs index 14966c9fd..6c0f913f5 100644 --- a/DisCatSharp.ApplicationCommands/Entities/GroupTranslator.cs +++ b/DisCatSharp.ApplicationCommands/Entities/GroupTranslator.cs @@ -1,70 +1,70 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System.Collections.Generic; using DisCatSharp.Entities; using Newtonsoft.Json; namespace DisCatSharp.ApplicationCommands { /// /// Represents a group translator. /// internal class GroupTranslator { /// /// Gets the group name. /// [JsonProperty("name")] public string Name { get; set; } /// /// Gets the group name translations. /// [JsonProperty("name_translations")] - internal Dictionary NT { get; set; } + internal Dictionary NameTranslationsDictionary { get; set; } [JsonIgnore] public DiscordApplicationCommandLocalization NameTranslations - => new(this.NT); + => new(this.NameTranslationsDictionary); /// /// Gets the group description translations. /// [JsonProperty("description_translations")] - internal Dictionary DT { get; set; } + internal Dictionary DescriptionTranslationsDictionary { get; set; } [JsonIgnore] public DiscordApplicationCommandLocalization DescriptionTranslations - => new(this.DT); + => new(this.DescriptionTranslationsDictionary); /// /// Gets the sub group translators, if applicable. /// [JsonProperty("groups")] public List SubGroups { get; set; } /// /// Gets the command translators, if applicable. /// [JsonProperty("commands")] public List Commands { get; set; } } } diff --git a/DisCatSharp.ApplicationCommands/Entities/OptionTranslator.cs b/DisCatSharp.ApplicationCommands/Entities/OptionTranslator.cs index 4ee9b2655..85c9f5043 100644 --- a/DisCatSharp.ApplicationCommands/Entities/OptionTranslator.cs +++ b/DisCatSharp.ApplicationCommands/Entities/OptionTranslator.cs @@ -1,61 +1,61 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System.Collections.Generic; using DisCatSharp.Entities; using Newtonsoft.Json; namespace DisCatSharp.ApplicationCommands { public class OptionTranslator { /// /// Gets the option name. /// [JsonProperty("name")] public string Name { get; set; } /// /// Gets the option name translations. /// [JsonProperty("name_translations")] - internal Dictionary NT { get; set; } + internal Dictionary NameTranslationsDictionary { get; set; } [JsonIgnore] public DiscordApplicationCommandLocalization NameTranslations - => new(this.NT); + => new(this.NameTranslationsDictionary); /// /// Gets the option description translations. /// [JsonProperty("description_translations")] - internal Dictionary DT { get; set; } + internal Dictionary DescriptionTranslationsDictionary { get; set; } [JsonIgnore] public DiscordApplicationCommandLocalization DescriptionTranslations - => new(this.DT); + => new(this.DescriptionTranslationsDictionary); /// /// Gets the choice translators, if applicable. /// [JsonProperty("choices")] public List Choices { get; set; } } } diff --git a/DisCatSharp.ApplicationCommands/Entities/SubGroupTranslator.cs b/DisCatSharp.ApplicationCommands/Entities/SubGroupTranslator.cs index 664f9c442..639286e5d 100644 --- a/DisCatSharp.ApplicationCommands/Entities/SubGroupTranslator.cs +++ b/DisCatSharp.ApplicationCommands/Entities/SubGroupTranslator.cs @@ -1,64 +1,64 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System.Collections.Generic; using DisCatSharp.Entities; using Newtonsoft.Json; namespace DisCatSharp.ApplicationCommands { /// /// Represents a sub group translator. /// internal class SubGroupTranslator { /// /// Gets the sub group name. /// [JsonProperty("name")] public string Name { get; set; } /// /// Gets the sub group name translations. /// [JsonProperty("name_translations")] - internal Dictionary NT { get; set; } + internal Dictionary NameTranslationsDictionary { get; set; } [JsonIgnore] public DiscordApplicationCommandLocalization NameTranslations - => new(this.NT); + => new(this.NameTranslationsDictionary); /// /// Gets the sub group description translations. /// [JsonProperty("description_translations")] - internal Dictionary DT { get; set; } + internal Dictionary DescriptionTranslationsDictionary { get; set; } [JsonIgnore] public DiscordApplicationCommandLocalization DescriptionTranslations - => new(this.DT); + => new(this.DescriptionTranslationsDictionary); /// /// Gets the command translators. /// [JsonProperty("commands")] public List Commands { get; set; } } } diff --git a/DisCatSharp.ApplicationCommands/Workers/ApplicationCommandWorker.cs b/DisCatSharp.ApplicationCommands/Workers/ApplicationCommandWorker.cs index e09ce7fe7..78f8b7235 100644 --- a/DisCatSharp.ApplicationCommands/Workers/ApplicationCommandWorker.cs +++ b/DisCatSharp.ApplicationCommands/Workers/ApplicationCommandWorker.cs @@ -1,382 +1,382 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Threading.Tasks; using DisCatSharp.Entities; using DisCatSharp.Enums; namespace DisCatSharp.ApplicationCommands { /// /// Represents a . /// internal class CommandWorker { /// /// Parses context menu application commands. /// /// The type. /// List of method infos. /// The optional command translations. /// Too much. internal static Task< ( List applicationCommands, List> commandTypeSources, List contextMenuCommands ) > ParseContextMenuCommands(Type type, IEnumerable methods, List translator = null) { List commands = new(); List> commandTypeSources = new(); List contextMenuCommands = new(); foreach (var contextMethod in methods) { var contextAttribute = contextMethod.GetCustomAttribute(); - DiscordApplicationCommandLocalization NameLocalizations = null; + DiscordApplicationCommandLocalization nameLocalizations = null; - var command_translation = translator?.Single(c => c.Name == contextAttribute.Name && c.Type == contextAttribute.Type); - if (command_translation != null) - NameLocalizations = command_translation.NameTranslations; + var commandTranslation = translator?.Single(c => c.Name == contextAttribute.Name && c.Type == contextAttribute.Type); + if (commandTranslation != null) + nameLocalizations = commandTranslation.NameTranslations; - var command = new DiscordApplicationCommand(contextAttribute.Name, null, null, contextAttribute.DefaultPermission, contextAttribute.Type, NameLocalizations); + var command = new DiscordApplicationCommand(contextAttribute.Name, null, null, contextAttribute.DefaultPermission, contextAttribute.Type, nameLocalizations); var parameters = contextMethod.GetParameters(); if (parameters.Length == 0 || parameters == null || !ReferenceEquals(parameters.FirstOrDefault()?.ParameterType, typeof(ContextMenuContext))) throw new ArgumentException($"The first argument must be a ContextMenuContext!"); if (parameters.Length > 1) throw new ArgumentException($"A context menu cannot have parameters!"); contextMenuCommands.Add(new ContextMenuCommand { Method = contextMethod, Name = contextAttribute.Name }); commands.Add(command); commandTypeSources.Add(new KeyValuePair(type, type)); } return Task.FromResult((commands, commandTypeSources, contextMenuCommands)); } /// /// Parses single application commands. /// /// The type. /// List of method infos. /// The optional guild id. /// The optional command translations. /// Too much. internal static async Task< ( List applicationCommands, List> commandTypeSources, List commandMethods ) > ParseBasicSlashCommandsAsync(Type type, IEnumerable methods, ulong? guildId = null, List translator = null) { List commands = new(); List> commandTypeSources = new(); List commandMethods = new(); foreach (var method in methods) { var commandattribute = method.GetCustomAttribute(); var parameters = method.GetParameters(); if (parameters.Length == 0 || parameters == null || !ReferenceEquals(parameters.FirstOrDefault()?.ParameterType, typeof(InteractionContext))) throw new ArgumentException($"The first argument must be an InteractionContext!"); parameters = parameters.Skip(1).ToArray(); var options = await ApplicationCommandsExtension.ParseParametersAsync(parameters, guildId); commandMethods.Add(new CommandMethod { Method = method, Name = commandattribute.Name }); - DiscordApplicationCommandLocalization NameLocalizations = null; - DiscordApplicationCommandLocalization DescriptionLocalizations = null; - List LocalizisedOptions = null; + DiscordApplicationCommandLocalization nameLocalizations = null; + DiscordApplicationCommandLocalization descriptionLocalizations = null; + List localizisedOptions = null; - var command_translation = translator?.Single(c => c.Name == commandattribute.Name && c.Type == ApplicationCommandType.ChatInput); + var commandTranslation = translator?.Single(c => c.Name == commandattribute.Name && c.Type == ApplicationCommandType.ChatInput); - if (command_translation != null && command_translation.Options != null) + if (commandTranslation != null && commandTranslation.Options != null) { - LocalizisedOptions = new(options.Count); + localizisedOptions = new(options.Count); foreach (var option in options) { List choices = option.Choices != null ? new(option.Choices.Count()) : null; if (option.Choices != null) { foreach (var choice in option.Choices) { - choices.Add(new DiscordApplicationCommandOptionChoice(choice.Name, choice.Value, command_translation.Options.Single(o => o.Name == option.Name).Choices.Single(c => c.Name == choice.Name).NameTranslations)); + choices.Add(new DiscordApplicationCommandOptionChoice(choice.Name, choice.Value, commandTranslation.Options.Single(o => o.Name == option.Name).Choices.Single(c => c.Name == choice.Name).NameTranslations)); } } - LocalizisedOptions.Add(new DiscordApplicationCommandOption(option.Name, option.Description, option.Type, option.Required, + localizisedOptions.Add(new DiscordApplicationCommandOption(option.Name, option.Description, option.Type, option.Required, choices, option.Options, option.ChannelTypes, option.AutoComplete, option.MinimumValue, option.MaximumValue, - command_translation.Options.Single(o => o.Name == option.Name).NameTranslations, command_translation.Options.Single(o => o.Name == option.Name).DescriptionTranslations + commandTranslation.Options.Single(o => o.Name == option.Name).NameTranslations, commandTranslation.Options.Single(o => o.Name == option.Name).DescriptionTranslations )); } - NameLocalizations = command_translation.NameTranslations; - DescriptionLocalizations = command_translation.DescriptionTranslations; + nameLocalizations = commandTranslation.NameTranslations; + descriptionLocalizations = commandTranslation.DescriptionTranslations; } - var payload = new DiscordApplicationCommand(commandattribute.Name, commandattribute.Description, LocalizisedOptions ?? options, commandattribute.DefaultPermission, ApplicationCommandType.ChatInput, NameLocalizations, DescriptionLocalizations); + var payload = new DiscordApplicationCommand(commandattribute.Name, commandattribute.Description, localizisedOptions ?? options, commandattribute.DefaultPermission, ApplicationCommandType.ChatInput, nameLocalizations, descriptionLocalizations); commands.Add(payload); commandTypeSources.Add(new KeyValuePair(type, type)); } return (commands, commandTypeSources, commandMethods); } } /// /// Represents a . /// internal class NestedCommandWorker { /// /// Parses application command groups. /// /// The type. /// List of type infos. /// The optional guild id. /// The optional group translations. /// Too much. internal static async Task< ( List applicationCommands, List> commandTypeSources, List singletonModules, List groupCommands, List subGroupCommands ) > ParseSlashGroupsAsync(Type type, List types, ulong? guildId = null, List translator = null) { List commands = new(); List> commandTypeSources = new(); List groupCommands = new(); List subGroupCommands = new(); List singletonModules = new(); //Handles groups foreach (var subclassinfo in types) { //Gets the attribute and methods in the group var groupAttribute = subclassinfo.GetCustomAttribute(); var submethods = subclassinfo.DeclaredMethods.Where(x => x.GetCustomAttribute() != null).ToList(); var subclasses = subclassinfo.DeclaredNestedTypes.Where(x => x.GetCustomAttribute() != null).ToList(); if (subclasses.Any() && submethods.Any()) { throw new ArgumentException("Slash command groups cannot have both subcommands and subgroups!"); } - DiscordApplicationCommandLocalization NameLocalizations = null; - DiscordApplicationCommandLocalization DescriptionLocalizations = null; + DiscordApplicationCommandLocalization nameLocalizations = null; + DiscordApplicationCommandLocalization descriptionLocalizations = null; if (translator != null) { - var command_translation = translator.Single(c => c.Name == groupAttribute.Name); - if (command_translation != null) + var commandTranslation = translator.Single(c => c.Name == groupAttribute.Name); + if (commandTranslation != null) { - NameLocalizations = command_translation.NameTranslations; - DescriptionLocalizations = command_translation.DescriptionTranslations; + nameLocalizations = commandTranslation.NameTranslations; + descriptionLocalizations = commandTranslation.DescriptionTranslations; } } //Initializes the command - var payload = new DiscordApplicationCommand(groupAttribute.Name, groupAttribute.Description, default_permission: groupAttribute.DefaultPermission, nameLocalizations: NameLocalizations, descriptionLocalizations: DescriptionLocalizations); + var payload = new DiscordApplicationCommand(groupAttribute.Name, groupAttribute.Description, defaultPermission: groupAttribute.DefaultPermission, nameLocalizations: nameLocalizations, descriptionLocalizations: descriptionLocalizations); commandTypeSources.Add(new KeyValuePair(type, type)); var commandmethods = new List>(); //Handles commands in the group foreach (var submethod in submethods) { var commandAttribute = submethod.GetCustomAttribute(); //Gets the paramaters and accounts for InteractionContext var parameters = submethod.GetParameters(); if (parameters.Length == 0 || parameters == null || !ReferenceEquals(parameters.First().ParameterType, typeof(InteractionContext))) throw new ArgumentException($"The first argument must be an InteractionContext!"); parameters = parameters.Skip(1).ToArray(); var options = await ApplicationCommandsExtension.ParseParametersAsync(parameters, guildId); - DiscordApplicationCommandLocalization SubNameLocalizations = null; - DiscordApplicationCommandLocalization SubDescriptionLocalizations = null; - List LocalizisedOptions = null; + DiscordApplicationCommandLocalization subNameLocalizations = null; + DiscordApplicationCommandLocalization subDescriptionLocalizations = null; + List localizisedOptions = null; - var command_translation = translator?.Single(c => c.Name == payload.Name); + var commandTranslation = translator?.Single(c => c.Name == payload.Name); - if (command_translation?.Commands != null) + if (commandTranslation?.Commands != null) { - var sub_command_translation = command_translation.Commands.Single(sc => sc.Name == commandAttribute.Name); - if (sub_command_translation.Options != null) + var subCommandTranslation = commandTranslation.Commands.Single(sc => sc.Name == commandAttribute.Name); + if (subCommandTranslation.Options != null) { - LocalizisedOptions = new(options.Count); + localizisedOptions = new(options.Count); foreach (var option in options) { List choices = option.Choices != null ? new(option.Choices.Count()) : null; if (option.Choices != null) { foreach (var choice in option.Choices) { - choices.Add(new DiscordApplicationCommandOptionChoice(choice.Name, choice.Value, sub_command_translation.Options.Single(o => o.Name == option.Name).Choices.Single(c => c.Name == choice.Name).NameTranslations)); + choices.Add(new DiscordApplicationCommandOptionChoice(choice.Name, choice.Value, subCommandTranslation.Options.Single(o => o.Name == option.Name).Choices.Single(c => c.Name == choice.Name).NameTranslations)); } } - LocalizisedOptions.Add(new DiscordApplicationCommandOption(option.Name, option.Description, option.Type, option.Required, + localizisedOptions.Add(new DiscordApplicationCommandOption(option.Name, option.Description, option.Type, option.Required, choices, option.Options, option.ChannelTypes, option.AutoComplete, option.MinimumValue, option.MaximumValue, - sub_command_translation.Options.Single(o => o.Name == option.Name).NameTranslations, sub_command_translation.Options.Single(o => o.Name == option.Name).DescriptionTranslations + subCommandTranslation.Options.Single(o => o.Name == option.Name).NameTranslations, subCommandTranslation.Options.Single(o => o.Name == option.Name).DescriptionTranslations )); } } - SubNameLocalizations = sub_command_translation.NameTranslations; - SubDescriptionLocalizations = sub_command_translation.DescriptionTranslations; + subNameLocalizations = subCommandTranslation.NameTranslations; + subDescriptionLocalizations = subCommandTranslation.DescriptionTranslations; } //Creates the subcommand and adds it to the main command - var subpayload = new DiscordApplicationCommandOption(commandAttribute.Name, commandAttribute.Description, ApplicationCommandOptionType.SubCommand, null, null, LocalizisedOptions ?? options, nameLocalizations: SubNameLocalizations, descriptionLocalizations: SubDescriptionLocalizations); + var subpayload = new DiscordApplicationCommandOption(commandAttribute.Name, commandAttribute.Description, ApplicationCommandOptionType.SubCommand, null, null, localizisedOptions ?? options, nameLocalizations: subNameLocalizations, descriptionLocalizations: subDescriptionLocalizations); payload = new DiscordApplicationCommand(payload.Name, payload.Description, payload.Options?.Append(subpayload) ?? new[] { subpayload }, payload.DefaultPermission, nameLocalizations: payload.NameLocalizations, descriptionLocalizations: payload.DescriptionLocalizations); commandTypeSources.Add(new KeyValuePair(subclassinfo, type)); //Adds it to the method lists commandmethods.Add(new KeyValuePair(commandAttribute.Name, submethod)); groupCommands.Add(new GroupCommand { Name = groupAttribute.Name, Methods = commandmethods }); } var command = new SubGroupCommand { Name = groupAttribute.Name }; //Handles subgroups foreach (var subclass in subclasses) { var subGroupAttribute = subclass.GetCustomAttribute(); var subsubmethods = subclass.DeclaredMethods.Where(x => x.GetCustomAttribute() != null); var options = new List(); var currentMethods = new List>(); - DiscordApplicationCommandLocalization SubNameLocalizations = null; - DiscordApplicationCommandLocalization SubDescriptionLocalizations = null; + DiscordApplicationCommandLocalization subNameLocalizations = null; + DiscordApplicationCommandLocalization subDescriptionLocalizations = null; if (translator != null) { - var command_translation = translator.Single(c => c.Name == payload.Name); - if (command_translation != null && command_translation.SubGroups != null) + var commandTranslation = translator.Single(c => c.Name == payload.Name); + if (commandTranslation != null && commandTranslation.SubGroups != null) { - var sub_command_translation = command_translation.SubGroups.Single(sc => sc.Name == subGroupAttribute.Name); + var subCommandTranslation = commandTranslation.SubGroups.Single(sc => sc.Name == subGroupAttribute.Name); - if (sub_command_translation != null) + if (subCommandTranslation != null) { - SubNameLocalizations = sub_command_translation.NameTranslations; - SubDescriptionLocalizations = sub_command_translation.DescriptionTranslations; + subNameLocalizations = subCommandTranslation.NameTranslations; + subDescriptionLocalizations = subCommandTranslation.DescriptionTranslations; } } } //Similar to the one for regular groups foreach (var subsubmethod in subsubmethods) { var suboptions = new List(); var commatt = subsubmethod.GetCustomAttribute(); var parameters = subsubmethod.GetParameters(); if (parameters.Length == 0 || parameters == null || !ReferenceEquals(parameters.First().ParameterType, typeof(InteractionContext))) throw new ArgumentException($"The first argument must be an InteractionContext!"); parameters = parameters.Skip(1).ToArray(); suboptions = suboptions.Concat(await ApplicationCommandsExtension.ParseParametersAsync(parameters, guildId)).ToList(); - DiscordApplicationCommandLocalization SubSubNameLocalizations = null; - DiscordApplicationCommandLocalization SubSubDescriptionLocalizations = null; - List LocalizisedOptions = null; + DiscordApplicationCommandLocalization subSubNameLocalizations = null; + DiscordApplicationCommandLocalization subSubDescriptionLocalizations = null; + List localizisedOptions = null; - var command_translation = translator?.Single(c => c.Name == payload.Name); + var commandTranslation = translator?.Single(c => c.Name == payload.Name); - var sub_command_translation = command_translation?.SubGroups?.Single(sc => sc.Name == commatt.Name); + var subCommandTranslation = commandTranslation?.SubGroups?.Single(sc => sc.Name == commatt.Name); - var sub_sub_command_translation = sub_command_translation?.Commands.Single(sc => sc.Name == commatt.Name); + var subSubCommandTranslation = subCommandTranslation?.Commands.Single(sc => sc.Name == commatt.Name); - if (sub_sub_command_translation != null && sub_sub_command_translation.Options != null) + if (subSubCommandTranslation != null && subSubCommandTranslation.Options != null) { - LocalizisedOptions = new(suboptions.Count); + localizisedOptions = new(suboptions.Count); foreach (var option in suboptions) { List choices = option.Choices != null ? new(option.Choices.Count) : null; if (option.Choices != null) { foreach (var choice in option.Choices) { - choices.Add(new DiscordApplicationCommandOptionChoice(choice.Name, choice.Value, sub_sub_command_translation.Options.Single(o => o.Name == option.Name).Choices.Single(c => c.Name == choice.Name).NameTranslations)); + choices.Add(new DiscordApplicationCommandOptionChoice(choice.Name, choice.Value, subSubCommandTranslation.Options.Single(o => o.Name == option.Name).Choices.Single(c => c.Name == choice.Name).NameTranslations)); } } - LocalizisedOptions.Add(new DiscordApplicationCommandOption(option.Name, option.Description, option.Type, option.Required, + localizisedOptions.Add(new DiscordApplicationCommandOption(option.Name, option.Description, option.Type, option.Required, choices, option.Options, option.ChannelTypes, option.AutoComplete, option.MinimumValue, option.MaximumValue, - sub_sub_command_translation.Options.Single(o => o.Name == option.Name).NameTranslations, sub_sub_command_translation.Options.Single(o => o.Name == option.Name).DescriptionTranslations + subSubCommandTranslation.Options.Single(o => o.Name == option.Name).NameTranslations, subSubCommandTranslation.Options.Single(o => o.Name == option.Name).DescriptionTranslations )); } - SubSubNameLocalizations = sub_sub_command_translation.NameTranslations; - SubSubDescriptionLocalizations = sub_sub_command_translation.DescriptionTranslations; + subSubNameLocalizations = subSubCommandTranslation.NameTranslations; + subSubDescriptionLocalizations = subSubCommandTranslation.DescriptionTranslations; } - var subsubpayload = new DiscordApplicationCommandOption(commatt.Name, commatt.Description, ApplicationCommandOptionType.SubCommand, null, null, LocalizisedOptions ?? suboptions, nameLocalizations: SubSubNameLocalizations, descriptionLocalizations: SubSubDescriptionLocalizations); + var subsubpayload = new DiscordApplicationCommandOption(commatt.Name, commatt.Description, ApplicationCommandOptionType.SubCommand, null, null, localizisedOptions ?? suboptions, nameLocalizations: subSubNameLocalizations, descriptionLocalizations: subSubDescriptionLocalizations); options.Add(subsubpayload); commandmethods.Add(new KeyValuePair(commatt.Name, subsubmethod)); currentMethods.Add(new KeyValuePair(commatt.Name, subsubmethod)); } //Adds the group to the command and method lists - var subpayload = new DiscordApplicationCommandOption(subGroupAttribute.Name, subGroupAttribute.Description, ApplicationCommandOptionType.SubCommandGroup, null, null, options, nameLocalizations: SubNameLocalizations, descriptionLocalizations: SubDescriptionLocalizations); + var subpayload = new DiscordApplicationCommandOption(subGroupAttribute.Name, subGroupAttribute.Description, ApplicationCommandOptionType.SubCommandGroup, null, null, options, nameLocalizations: subNameLocalizations, descriptionLocalizations: subDescriptionLocalizations); command.SubCommands.Add(new GroupCommand { Name = subGroupAttribute.Name, Methods = currentMethods }); payload = new DiscordApplicationCommand(payload.Name, payload.Description, payload.Options?.Append(subpayload) ?? new[] { subpayload }, payload.DefaultPermission, nameLocalizations: payload.NameLocalizations, descriptionLocalizations: payload.DescriptionLocalizations); commandTypeSources.Add(new KeyValuePair(subclass, type)); //Accounts for lifespans for the sub group if (subclass.GetCustomAttribute() != null && subclass.GetCustomAttribute().Lifespan == ApplicationCommandModuleLifespan.Singleton) { - singletonModules.Add(ApplicationCommandsExtension.CreateInstance(subclass, ApplicationCommandsExtension._configuration?.ServiceProvider)); + singletonModules.Add(ApplicationCommandsExtension.CreateInstance(subclass, ApplicationCommandsExtension.Configuration?.ServiceProvider)); } } if (command.SubCommands.Any()) subGroupCommands.Add(command); commands.Add(payload); //Accounts for lifespans if (subclassinfo.GetCustomAttribute() != null && subclassinfo.GetCustomAttribute().Lifespan == ApplicationCommandModuleLifespan.Singleton) { - singletonModules.Add(ApplicationCommandsExtension.CreateInstance(subclassinfo, ApplicationCommandsExtension._configuration?.ServiceProvider)); + singletonModules.Add(ApplicationCommandsExtension.CreateInstance(subclassinfo, ApplicationCommandsExtension.Configuration?.ServiceProvider)); } } return (commands, commandTypeSources, singletonModules, groupCommands, subGroupCommands); } } } diff --git a/DisCatSharp.ApplicationCommands/Workers/PermissionWorker.cs b/DisCatSharp.ApplicationCommands/Workers/PermissionWorker.cs index d95f2b262..c3146c8c4 100644 --- a/DisCatSharp.ApplicationCommands/Workers/PermissionWorker.cs +++ b/DisCatSharp.ApplicationCommands/Workers/PermissionWorker.cs @@ -1,54 +1,54 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.Extensions.Logging; namespace DisCatSharp.ApplicationCommands { /// /// Represents a . /// internal class PermissionWorker { internal static async Task UpdateCommandPermissionAsync(IEnumerable types, ulong? guildid, ulong commandId, string commandName, Type commandDeclaringType, Type commandRootType) { if (!guildid.HasValue) { - ApplicationCommandsExtension._client.Logger.LogTrace("You can't set global permissions till yet. See https://discord.com/developers/docs/interactions/application-commands#permissions"); + ApplicationCommandsExtension.ClientInternal.Logger.LogTrace("You can't set global permissions till yet. See https://discord.com/developers/docs/interactions/application-commands#permissions"); return; } var ctx = new ApplicationCommandsPermissionContext(commandDeclaringType, commandName); var conf = types.First(t => t.Type == commandRootType); conf.Setup?.Invoke(ctx); if (ctx.Permissions.Count == 0) return; - await ApplicationCommandsExtension._client.OverwriteGuildApplicationCommandPermissionsAsync(guildid.Value, commandId, ctx.Permissions); + await ApplicationCommandsExtension.ClientInternal.OverwriteGuildApplicationCommandPermissionsAsync(guildid.Value, commandId, ctx.Permissions); } } } diff --git a/DisCatSharp.ApplicationCommands/Workers/RegistrationWorker.cs b/DisCatSharp.ApplicationCommands/Workers/RegistrationWorker.cs index 8506e1574..0f267cd21 100644 --- a/DisCatSharp.ApplicationCommands/Workers/RegistrationWorker.cs +++ b/DisCatSharp.ApplicationCommands/Workers/RegistrationWorker.cs @@ -1,359 +1,359 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using DisCatSharp.Common; using DisCatSharp.Entities; using DisCatSharp.Exceptions; using Microsoft.Extensions.Logging; namespace DisCatSharp.ApplicationCommands { /// /// Represents a . /// internal class RegistrationWorker { /// /// Registers the global commands. /// - /// The command list. + /// The command list. /// A list of registered commands. - internal static async Task> RegisterGlobalCommandsAsync(List Commands) + internal static async Task> RegisterGlobalCommandsAsync(List commands) { - var GlobalCommandsOverwriteList = BuildGlobalOverwriteList(Commands); - var GlobalCommandsCreateList = BuildGlobalCreateList(Commands); - var GlobalCommandsDeleteList = BuildGlobalDeleteList(Commands); + var globalCommandsOverwriteList = BuildGlobalOverwriteList(commands); + var globalCommandsCreateList = BuildGlobalCreateList(commands); + var globalCommandsDeleteList = BuildGlobalDeleteList(commands); - if (GlobalCommandsCreateList.NotEmptyAndNotNull() && GlobalCommandsOverwriteList.EmptyOrNull()) + if (globalCommandsCreateList.NotEmptyAndNotNull() && globalCommandsOverwriteList.EmptyOrNull()) { - var cmds = await ApplicationCommandsExtension._client.BulkOverwriteGlobalApplicationCommandsAsync(GlobalCommandsCreateList); - Commands.AddRange(cmds); + var cmds = await ApplicationCommandsExtension.ClientInternal.BulkOverwriteGlobalApplicationCommandsAsync(globalCommandsCreateList); + commands.AddRange(cmds); } - else if (GlobalCommandsCreateList.EmptyOrNull() && GlobalCommandsOverwriteList.NotEmptyAndNotNull()) + else if (globalCommandsCreateList.EmptyOrNull() && globalCommandsOverwriteList.NotEmptyAndNotNull()) { - List OverwriteList = new(); - foreach (var overwrite in GlobalCommandsOverwriteList) + List overwriteList = new(); + foreach (var overwrite in globalCommandsOverwriteList) { var cmd = overwrite.Value; cmd.Id = overwrite.Key; - OverwriteList.Add(cmd); + overwriteList.Add(cmd); } - var discord_backend_commands = await ApplicationCommandsExtension._client.BulkOverwriteGlobalApplicationCommandsAsync(OverwriteList); - Commands.AddRange(discord_backend_commands); + var discordBackendCommands = await ApplicationCommandsExtension.ClientInternal.BulkOverwriteGlobalApplicationCommandsAsync(overwriteList); + commands.AddRange(discordBackendCommands); } - else if (GlobalCommandsCreateList.NotEmptyAndNotNull() && GlobalCommandsOverwriteList.NotEmptyAndNotNull()) + else if (globalCommandsCreateList.NotEmptyAndNotNull() && globalCommandsOverwriteList.NotEmptyAndNotNull()) { - foreach (var cmd in GlobalCommandsCreateList) + foreach (var cmd in globalCommandsCreateList) { - var discord_backend_command = await ApplicationCommandsExtension._client.CreateGlobalApplicationCommandAsync(cmd); - Commands.Add(discord_backend_command); + var discordBackendCommand = await ApplicationCommandsExtension.ClientInternal.CreateGlobalApplicationCommandAsync(cmd); + commands.Add(discordBackendCommand); } - foreach (var cmd in GlobalCommandsOverwriteList) + foreach (var cmd in globalCommandsOverwriteList) { var command = cmd.Value; - var discord_backend_command = await ApplicationCommandsExtension._client.EditGlobalApplicationCommandAsync(cmd.Key, action => + var discordBackendCommand = await ApplicationCommandsExtension.ClientInternal.EditGlobalApplicationCommandAsync(cmd.Key, action => { action.Name = command.Name; action.NameLocalizations = command.NameLocalizations; action.Description = command.Description; action.DescriptionLocalizations = command.DescriptionLocalizations; if(command.Options != null && command.Options.Any()) action.Options = Entities.Optional.FromValue(command.Options); action.DefaultPermission = command.DefaultPermission; }); - Commands.Add(discord_backend_command); + commands.Add(discordBackendCommand); } } - if (GlobalCommandsDeleteList.NotEmptyAndNotNull()) + if (globalCommandsDeleteList.NotEmptyAndNotNull()) { - foreach (var cmdId in GlobalCommandsDeleteList) + foreach (var cmdId in globalCommandsDeleteList) { try { - await ApplicationCommandsExtension._client.DeleteGlobalApplicationCommandAsync(cmdId); + await ApplicationCommandsExtension.ClientInternal.DeleteGlobalApplicationCommandAsync(cmdId); } catch (NotFoundException) { - ApplicationCommandsExtension._client.Logger.LogError($"Could not delete global command {cmdId}. Please clean up manually"); + ApplicationCommandsExtension.ClientInternal.Logger.LogError($"Could not delete global command {cmdId}. Please clean up manually"); } } } - return Commands.NotEmptyAndNotNull() ? Commands : null; + return commands.NotEmptyAndNotNull() ? commands : null; } /// /// Registers the guild commands. /// - /// The target guild id. - /// The command list. + /// The target guild id. + /// The command list. /// A list of registered commands. - internal static async Task> RegisterGuilldCommandsAsync(ulong GuildId, List Commands) + internal static async Task> RegisterGuilldCommandsAsync(ulong guildId, List commands) { - var GuildCommandsOverwriteList = BuildGuildOverwriteList(GuildId, Commands); - var GuildCommandsCreateList = BuildGuildCreateList(GuildId, Commands); - var GuildCommandsDeleteList = BuildGuildDeleteList(GuildId, Commands); + var guildCommandsOverwriteList = BuildGuildOverwriteList(guildId, commands); + var guildCommandsCreateList = BuildGuildCreateList(guildId, commands); + var guildCommandsDeleteList = BuildGuildDeleteList(guildId, commands); - if (GuildCommandsCreateList.NotEmptyAndNotNull() && GuildCommandsOverwriteList.EmptyOrNull()) + if (guildCommandsCreateList.NotEmptyAndNotNull() && guildCommandsOverwriteList.EmptyOrNull()) { - var cmds = await ApplicationCommandsExtension._client.BulkOverwriteGuildApplicationCommandsAsync(GuildId, GuildCommandsCreateList); - Commands.AddRange(cmds); + var cmds = await ApplicationCommandsExtension.ClientInternal.BulkOverwriteGuildApplicationCommandsAsync(guildId, guildCommandsCreateList); + commands.AddRange(cmds); } - else if (GuildCommandsCreateList.EmptyOrNull() && GuildCommandsOverwriteList.NotEmptyAndNotNull()) + else if (guildCommandsCreateList.EmptyOrNull() && guildCommandsOverwriteList.NotEmptyAndNotNull()) { - List OverwriteList = new(); - foreach (var overwrite in GuildCommandsOverwriteList) + List overwriteList = new(); + foreach (var overwrite in guildCommandsOverwriteList) { var cmd = overwrite.Value; cmd.Id = overwrite.Key; - OverwriteList.Add(cmd); + overwriteList.Add(cmd); } - var discord_backend_commands = await ApplicationCommandsExtension._client.BulkOverwriteGuildApplicationCommandsAsync(GuildId, OverwriteList); - Commands.AddRange(discord_backend_commands); + var discordBackendCommands = await ApplicationCommandsExtension.ClientInternal.BulkOverwriteGuildApplicationCommandsAsync(guildId, overwriteList); + commands.AddRange(discordBackendCommands); } - else if (GuildCommandsCreateList.NotEmptyAndNotNull() && GuildCommandsOverwriteList.NotEmptyAndNotNull()) + else if (guildCommandsCreateList.NotEmptyAndNotNull() && guildCommandsOverwriteList.NotEmptyAndNotNull()) { - foreach (var cmd in GuildCommandsCreateList) + foreach (var cmd in guildCommandsCreateList) { - var discord_backend_command = await ApplicationCommandsExtension._client.CreateGuildApplicationCommandAsync(GuildId, cmd); - Commands.Add(discord_backend_command); + var discordBackendCommand = await ApplicationCommandsExtension.ClientInternal.CreateGuildApplicationCommandAsync(guildId, cmd); + commands.Add(discordBackendCommand); } - foreach (var cmd in GuildCommandsOverwriteList) + foreach (var cmd in guildCommandsOverwriteList) { var command = cmd.Value; - var discord_backend_command = await ApplicationCommandsExtension._client.EditGuildApplicationCommandAsync(GuildId, cmd.Key, action => + var discordBackendCommand = await ApplicationCommandsExtension.ClientInternal.EditGuildApplicationCommandAsync(guildId, cmd.Key, action => { action.Name = command.Name; action.NameLocalizations = command.NameLocalizations; action.Description = command.Description; action.DescriptionLocalizations = command.DescriptionLocalizations; if(command.Options != null && command.Options.Any()) action.Options = Entities.Optional.FromValue(command.Options); action.DefaultPermission = command.DefaultPermission; }); - Commands.Add(discord_backend_command); + commands.Add(discordBackendCommand); } } - if (GuildCommandsDeleteList.NotEmptyAndNotNull()) + if (guildCommandsDeleteList.NotEmptyAndNotNull()) { - foreach (var cmdId in GuildCommandsDeleteList) + foreach (var cmdId in guildCommandsDeleteList) { try { - await ApplicationCommandsExtension._client.DeleteGuildApplicationCommandAsync(GuildId, cmdId); + await ApplicationCommandsExtension.ClientInternal.DeleteGuildApplicationCommandAsync(guildId, cmdId); } catch (NotFoundException) { - ApplicationCommandsExtension._client.Logger.LogError($"Could not delete guild command {cmdId} in guild {GuildId}. Please clean up manually"); + ApplicationCommandsExtension.ClientInternal.Logger.LogError($"Could not delete guild command {cmdId} in guild {guildId}. Please clean up manually"); } } } - return Commands.NotEmptyAndNotNull() ? Commands : null; + return commands.NotEmptyAndNotNull() ? commands : null; } /// /// Builds a list of guild command ids to be deleted on discords backend. /// /// The guild id these commands belong to. /// The command list. /// A list of command ids. private static List BuildGuildDeleteList(ulong guildId, List updateList) { List discord; - if (ApplicationCommandsExtension._guildDiscordCommands == null || !ApplicationCommandsExtension._guildDiscordCommands.Any() - || !ApplicationCommandsExtension._guildDiscordCommands.GetFirstValueByKey(guildId, out discord) + if (ApplicationCommandsExtension.GuildDiscordCommands == null || !ApplicationCommandsExtension.GuildDiscordCommands.Any() + || !ApplicationCommandsExtension.GuildDiscordCommands.GetFirstValueByKey(guildId, out discord) ) return null; - List InvalidCommandIds = new(); + List invalidCommandIds = new(); if (updateList == null) { foreach (var cmd in discord) { - InvalidCommandIds.Add(cmd.Id); + invalidCommandIds.Add(cmd.Id); } } else { foreach (var cmd in discord) { if (!updateList.Any(ul => ul.Name == cmd.Name)) - InvalidCommandIds.Add(cmd.Id); + invalidCommandIds.Add(cmd.Id); } } - return InvalidCommandIds; + return invalidCommandIds; } /// /// Builds a list of guild commands to be created on discords backend. /// /// The guild id these commands belong to. /// The command list. /// private static List BuildGuildCreateList(ulong guildId, List updateList) { List discord; - if (ApplicationCommandsExtension._guildDiscordCommands == null || !ApplicationCommandsExtension._guildDiscordCommands.Any() - || updateList == null || !ApplicationCommandsExtension._guildDiscordCommands.GetFirstValueByKey(guildId, out discord) + if (ApplicationCommandsExtension.GuildDiscordCommands == null || !ApplicationCommandsExtension.GuildDiscordCommands.Any() + || updateList == null || !ApplicationCommandsExtension.GuildDiscordCommands.GetFirstValueByKey(guildId, out discord) ) return updateList; - List NewCommands = new(); + List newCommands = new(); foreach (var cmd in updateList) { if (!discord.Any(d => d.Name == cmd.Name)) { - NewCommands.Add(cmd); + newCommands.Add(cmd); } } - return NewCommands; + return newCommands; } /// /// Builds a list of guild commands to be overwritten on discords backend. /// /// The guild id these commands belong to. /// The command list. /// A dictionary of command id and command. private static Dictionary BuildGuildOverwriteList(ulong guildId, List updateList) { List discord; - if (ApplicationCommandsExtension._guildDiscordCommands == null || !ApplicationCommandsExtension._guildDiscordCommands.Any() - || !ApplicationCommandsExtension._guildDiscordCommands.Any(l => l.Key == guildId) || updateList == null - || !ApplicationCommandsExtension._guildDiscordCommands.GetFirstValueByKey(guildId, out discord) + if (ApplicationCommandsExtension.GuildDiscordCommands == null || !ApplicationCommandsExtension.GuildDiscordCommands.Any() + || !ApplicationCommandsExtension.GuildDiscordCommands.Any(l => l.Key == guildId) || updateList == null + || !ApplicationCommandsExtension.GuildDiscordCommands.GetFirstValueByKey(guildId, out discord) ) return null; - Dictionary UpdateCommands = new(); + Dictionary updateCommands = new(); foreach (var cmd in updateList) { if (discord.GetFirstValueWhere(d => d.Name == cmd.Name, out var command)) - UpdateCommands.Add(command.Id, cmd); + updateCommands.Add(command.Id, cmd); } - return UpdateCommands; + return updateCommands; } /// /// Builds a list of global command ids to be deleted on discords backend. /// /// The command list. /// A list of command ids. private static List BuildGlobalDeleteList(List updateList = null) { - if (ApplicationCommandsExtension._globalDiscordCommands == null || !ApplicationCommandsExtension._globalDiscordCommands.Any() - || ApplicationCommandsExtension._globalDiscordCommands == null + if (ApplicationCommandsExtension.GlobalDiscordCommands == null || !ApplicationCommandsExtension.GlobalDiscordCommands.Any() + || ApplicationCommandsExtension.GlobalDiscordCommands == null ) return null; - var discord = ApplicationCommandsExtension._globalDiscordCommands; + var discord = ApplicationCommandsExtension.GlobalDiscordCommands; - List InvalidCommandIds = new(); + List invalidCommandIds = new(); if (updateList == null) { foreach (var cmd in discord) { - InvalidCommandIds.Add(cmd.Id); + invalidCommandIds.Add(cmd.Id); } } else { foreach (var cmd in discord) { if (!updateList.Any(ul => ul.Name == cmd.Name)) - InvalidCommandIds.Add(cmd.Id); + invalidCommandIds.Add(cmd.Id); } } - return InvalidCommandIds; + return invalidCommandIds; } /// /// Builds a list of global commands to be created on discords backend. /// /// The command list. /// A list of commands. private static List BuildGlobalCreateList(List updateList) { - if (ApplicationCommandsExtension._globalDiscordCommands == null || !ApplicationCommandsExtension._globalDiscordCommands.Any() || updateList == null) + if (ApplicationCommandsExtension.GlobalDiscordCommands == null || !ApplicationCommandsExtension.GlobalDiscordCommands.Any() || updateList == null) return updateList; - var discord = ApplicationCommandsExtension._globalDiscordCommands; + var discord = ApplicationCommandsExtension.GlobalDiscordCommands; - List NewCommands = new(); + List newCommands = new(); foreach (var cmd in updateList) { if (discord.Any(d => d.Name == cmd.Name)) { - NewCommands.Add(cmd); + newCommands.Add(cmd); } } - return NewCommands; + return newCommands; } /// /// Builds a list of global commands to be overwritten on discords backend. /// /// The command list. /// A dictionary of command ids and commands. private static Dictionary BuildGlobalOverwriteList(List updateList) { - if (ApplicationCommandsExtension._globalDiscordCommands == null || !ApplicationCommandsExtension._globalDiscordCommands.Any() - || updateList == null || ApplicationCommandsExtension._globalDiscordCommands == null + if (ApplicationCommandsExtension.GlobalDiscordCommands == null || !ApplicationCommandsExtension.GlobalDiscordCommands.Any() + || updateList == null || ApplicationCommandsExtension.GlobalDiscordCommands == null ) return null; - var discord = ApplicationCommandsExtension._globalDiscordCommands; + var discord = ApplicationCommandsExtension.GlobalDiscordCommands; - Dictionary UpdateCommands = new(); + Dictionary updateCommands = new(); foreach (var cmd in updateList) { if (discord.GetFirstValueWhere(d => d.Name == cmd.Name, out var command)) - UpdateCommands.Add(command.Id, cmd); + updateCommands.Add(command.Id, cmd); } - return UpdateCommands; + return updateCommands; } } } diff --git a/DisCatSharp.CommandsNext/Attributes/CooldownAttribute.cs b/DisCatSharp.CommandsNext/Attributes/CooldownAttribute.cs index b10132dd3..50a45be29 100644 --- a/DisCatSharp.CommandsNext/Attributes/CooldownAttribute.cs +++ b/DisCatSharp.CommandsNext/Attributes/CooldownAttribute.cs @@ -1,333 +1,333 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System; using System.Collections.Concurrent; using System.Globalization; using System.Threading; using System.Threading.Tasks; namespace DisCatSharp.CommandsNext.Attributes { /// /// Defines a cooldown for this command. This allows you to define how many times can users execute a specific command /// [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = true, Inherited = false)] public sealed class CooldownAttribute : CheckBaseAttribute { /// /// Gets the maximum number of uses before this command triggers a cooldown for its bucket. /// public int MaxUses { get; } /// /// Gets the time after which the cooldown is reset. /// public TimeSpan Reset { get; } /// /// Gets the type of the cooldown bucket. This determines how cooldowns are applied. /// public CooldownBucketType BucketType { get; } /// /// Gets the cooldown buckets for this command. /// private ConcurrentDictionary Buckets { get; } /// /// Defines a cooldown for this command. This means that users will be able to use the command a specific number of times before they have to wait to use it again. /// /// Number of times the command can be used before triggering a cooldown. /// Number of seconds after which the cooldown is reset. /// Type of cooldown bucket. This allows controlling whether the bucket will be cooled down per user, guild, channel, or globally. public CooldownAttribute(int maxUses, double resetAfter, CooldownBucketType bucketType) { this.MaxUses = maxUses; this.Reset = TimeSpan.FromSeconds(resetAfter); this.BucketType = bucketType; this.Buckets = new ConcurrentDictionary(); } /// /// Gets a cooldown bucket for given command context. /// /// Command context to get cooldown bucket for. /// Requested cooldown bucket, or null if one wasn't present. public CommandCooldownBucket GetBucket(CommandContext ctx) { var bid = this.GetBucketId(ctx, out _, out _, out _); this.Buckets.TryGetValue(bid, out var bucket); return bucket; } /// /// Calculates the cooldown remaining for given command context. /// /// Context for which to calculate the cooldown. /// Remaining cooldown, or zero if no cooldown is active. public TimeSpan GetRemainingCooldown(CommandContext ctx) { var bucket = this.GetBucket(ctx); return bucket == null ? TimeSpan.Zero : bucket.RemainingUses > 0 ? TimeSpan.Zero : bucket.ResetsAt - DateTimeOffset.UtcNow; } /// /// Calculates bucket ID for given command context. /// /// Context for which to calculate bucket ID for. /// ID of the user with which this bucket is associated. /// ID of the channel with which this bucket is associated. /// ID of the guild with which this bucket is associated. /// Calculated bucket ID. private string GetBucketId(CommandContext ctx, out ulong userId, out ulong channelId, out ulong guildId) { userId = 0ul; if ((this.BucketType & CooldownBucketType.User) != 0) userId = ctx.User.Id; channelId = 0ul; if ((this.BucketType & CooldownBucketType.Channel) != 0) channelId = ctx.Channel.Id; if ((this.BucketType & CooldownBucketType.Guild) != 0 && ctx.Guild == null) channelId = ctx.Channel.Id; guildId = 0ul; if (ctx.Guild != null && (this.BucketType & CooldownBucketType.Guild) != 0) guildId = ctx.Guild.Id; var bid = CommandCooldownBucket.MakeId(userId, channelId, guildId); return bid; } /// /// Executes a check. /// /// The command context. /// If true, help - returns true. public override async Task ExecuteCheckAsync(CommandContext ctx, bool help) { if (help) return true; var bid = this.GetBucketId(ctx, out var usr, out var chn, out var gld); if (!this.Buckets.TryGetValue(bid, out var bucket)) { bucket = new CommandCooldownBucket(this.MaxUses, this.Reset, usr, chn, gld); this.Buckets.AddOrUpdate(bid, bucket, (k, v) => bucket); } return await bucket.DecrementUseAsync().ConfigureAwait(false); } } /// /// Defines how are command cooldowns applied. /// public enum CooldownBucketType : int { /// /// Denotes that the command will have its cooldown applied per-user. /// User = 1, /// /// Denotes that the command will have its cooldown applied per-channel. /// Channel = 2, /// /// Denotes that the command will have its cooldown applied per-guild. In DMs, this applies the cooldown per-channel. /// Guild = 4, /// /// Denotes that the command will have its cooldown applied globally. /// Global = 0 } /// /// Represents a cooldown bucket for commands. /// public sealed class CommandCooldownBucket : IEquatable { /// /// Gets the ID of the user with whom this cooldown is associated. /// public ulong UserId { get; } /// /// Gets the ID of the channel with which this cooldown is associated. /// public ulong ChannelId { get; } /// /// Gets the ID of the guild with which this cooldown is associated. /// public ulong GuildId { get; } /// /// Gets the ID of the bucket. This is used to distinguish between cooldown buckets. /// public string BucketId { get; } /// /// Gets the remaining number of uses before the cooldown is triggered. /// public int RemainingUses - => Volatile.Read(ref this._remaining_uses); + => Volatile.Read(ref this._remainingUses); - private int _remaining_uses; + private int _remainingUses; /// /// Gets the maximum number of times this command can be used in given timespan. /// public int MaxUses { get; } /// /// Gets the date and time at which the cooldown resets. /// public DateTimeOffset ResetsAt { get; internal set; } /// /// Gets the time after which this cooldown resets. /// public TimeSpan Reset { get; internal set; } /// /// Gets the semaphore used to lock the use value. /// private SemaphoreSlim UsageSemaphore { get; } /// /// Creates a new command cooldown bucket. /// /// Maximum number of uses for this bucket. /// Time after which this bucket resets. /// ID of the user with which this cooldown is associated. /// ID of the channel with which this cooldown is associated. /// ID of the guild with which this cooldown is associated. internal CommandCooldownBucket(int maxUses, TimeSpan resetAfter, ulong userId = 0, ulong channelId = 0, ulong guildId = 0) { - this._remaining_uses = maxUses; + this._remainingUses = maxUses; this.MaxUses = maxUses; this.ResetsAt = DateTimeOffset.UtcNow + resetAfter; this.Reset = resetAfter; this.UserId = userId; this.ChannelId = channelId; this.GuildId = guildId; this.BucketId = MakeId(userId, channelId, guildId); this.UsageSemaphore = new SemaphoreSlim(1, 1); } /// /// Decrements the remaining use counter. /// /// Whether decrement succeded or not. internal async Task DecrementUseAsync() { await this.UsageSemaphore.WaitAsync().ConfigureAwait(false); // if we're past reset time... var now = DateTimeOffset.UtcNow; if (now >= this.ResetsAt) { // ...do the reset and set a new reset time - Interlocked.Exchange(ref this._remaining_uses, this.MaxUses); + Interlocked.Exchange(ref this._remainingUses, this.MaxUses); this.ResetsAt = now + this.Reset; } // check if we have any uses left, if we do... var success = false; if (this.RemainingUses > 0) { // ...decrement, and return success... - Interlocked.Decrement(ref this._remaining_uses); + Interlocked.Decrement(ref this._remainingUses); success = true; } // ...otherwise just fail this.UsageSemaphore.Release(); return success; } /// /// Returns a string representation of this command cooldown bucket. /// /// String representation of this command cooldown bucket. public override string ToString() => $"Command bucket {this.BucketId}"; /// /// 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 CommandCooldownBucket); /// /// Checks whether this is equal to another . /// /// to compare to. /// Whether the is equal to this . public bool Equals(CommandCooldownBucket other) => other is not null && (ReferenceEquals(this, other) || (this.UserId == other.UserId && this.ChannelId == other.ChannelId && this.GuildId == other.GuildId)); /// /// Gets the hash code for this . /// /// The hash code for this . public override int GetHashCode() => HashCode.Combine(this.UserId, this.ChannelId, this.GuildId); /// /// Gets whether the two objects are equal. /// /// First bucket to compare. /// Second bucket to compare. /// Whether the two buckets are equal. public static bool operator ==(CommandCooldownBucket bucket1, CommandCooldownBucket bucket2) { var null1 = bucket1 is null; var null2 = bucket2 is null; return (null1 && null2) || (null1 == null2 && null1.Equals(null2)); } /// /// Gets whether the two objects are not equal. /// /// First bucket to compare. /// Second bucket to compare. /// Whether the two buckets are not equal. public static bool operator !=(CommandCooldownBucket bucket1, CommandCooldownBucket bucket2) => !(bucket1 == bucket2); /// /// Creates a bucket ID from given bucket parameters. /// /// ID of the user with which this cooldown is associated. /// ID of the channel with which this cooldown is associated. /// ID of the guild with which this cooldown is associated. /// Generated bucket ID. public static string MakeId(ulong userId = 0, ulong channelId = 0, ulong guildId = 0) => $"{userId.ToString(CultureInfo.InvariantCulture)}:{channelId.ToString(CultureInfo.InvariantCulture)}:{guildId.ToString(CultureInfo.InvariantCulture)}"; } } diff --git a/DisCatSharp.CommandsNext/Attributes/RequireBoostingAttribute.cs b/DisCatSharp.CommandsNext/Attributes/RequireBoostingAttribute.cs index d64589094..5449de4d2 100644 --- a/DisCatSharp.CommandsNext/Attributes/RequireBoostingAttribute.cs +++ b/DisCatSharp.CommandsNext/Attributes/RequireBoostingAttribute.cs @@ -1,84 +1,84 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System; using System.Threading.Tasks; namespace DisCatSharp.CommandsNext.Attributes { /// /// Defines that usage of this command is restricted to boosters. /// [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false, Inherited = false)] public sealed class RequireBoostingAttribute : CheckBaseAttribute { /// /// Gets the required boost time. /// public int Since { get; } /// /// Gets the required guild. /// public ulong GuildId { get; } /// /// Initializes a new instance of the class. /// /// Boosting since days. public RequireBoostingAttribute(int days = 0) { this.GuildId = 0; this.Since = days; } /// /// Initializes a new instance of the class. /// /// Target guild id. /// Boosting since days. - public RequireBoostingAttribute(ulong guild_id, int days = 0) + public RequireBoostingAttribute(ulong guildId, int days = 0) { - this.GuildId = guild_id; + this.GuildId = guildId; this.Since = days; } /// /// Executes the a check. /// /// The command context. /// If true, help - returns true. public override async Task ExecuteCheckAsync(CommandContext ctx, bool help) { if (this.GuildId != 0) { var guild = await ctx.Client.GetGuildAsync(this.GuildId); var member = await guild.GetMemberAsync(ctx.User.Id); return member != null && member.PremiumSince.HasValue ? await Task.FromResult(member.PremiumSince.Value.UtcDateTime.Date < DateTime.UtcNow.Date.AddDays(-this.Since)) : await Task.FromResult(false); } else { return ctx.Member != null && ctx.Member.PremiumSince.HasValue ? await Task.FromResult(ctx.Member.PremiumSince.Value.UtcDateTime.Date < DateTime.UtcNow.Date.AddDays(-this.Since)) : await Task.FromResult(false); } } } } diff --git a/DisCatSharp.CommandsNext/Attributes/RequireNsfwAttribute.cs b/DisCatSharp.CommandsNext/Attributes/RequireNsfwAttribute.cs index 58a57f2e1..c192a495c 100644 --- a/DisCatSharp.CommandsNext/Attributes/RequireNsfwAttribute.cs +++ b/DisCatSharp.CommandsNext/Attributes/RequireNsfwAttribute.cs @@ -1,42 +1,42 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System; using System.Threading.Tasks; namespace DisCatSharp.CommandsNext.Attributes { /// /// Defines that usage of this command is restricted to NSFW channels. /// [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false, Inherited = false)] public sealed class RequireNsfwAttribute : CheckBaseAttribute { /// /// Executes the a check. /// /// The command context. /// If true, help - returns true. public override Task ExecuteCheckAsync(CommandContext ctx, bool help) - => Task.FromResult(ctx.Channel.Guild == null || ctx.Channel.IsNSFW); + => Task.FromResult(ctx.Channel.Guild == null || ctx.Channel.IsNsfw); } } diff --git a/DisCatSharp.CommandsNext/Attributes/RequireOwnerOrIdAttribute.cs b/DisCatSharp.CommandsNext/Attributes/RequireOwnerOrIdAttribute.cs index 0812cb21e..692cfc680 100644 --- a/DisCatSharp.CommandsNext/Attributes/RequireOwnerOrIdAttribute.cs +++ b/DisCatSharp.CommandsNext/Attributes/RequireOwnerOrIdAttribute.cs @@ -1,69 +1,69 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using System.Threading.Tasks; namespace DisCatSharp.CommandsNext.Attributes { /// /// Requires ownership of the bot or a whitelisted id to execute this command. /// [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false, Inherited = false)] public sealed class RequireOwnerOrIdAttribute : CheckBaseAttribute { /// /// Allowed user ids /// public IReadOnlyList UserIds { get; } /// /// Defines that usage of this command is restricted to the owner or whitelisted ids of the bot. /// /// List of allowed user ids - public RequireOwnerOrIdAttribute(params ulong[] user_ids) + public RequireOwnerOrIdAttribute(params ulong[] userIds) { - this.UserIds = new ReadOnlyCollection(user_ids); + this.UserIds = new ReadOnlyCollection(userIds); } /// /// Executes the a check. /// /// The command context. /// If true, help - returns true. public override async Task ExecuteCheckAsync(CommandContext ctx, bool help) { var app = ctx.Client.CurrentApplication; var me = ctx.Client.CurrentUser; var owner = app != null ? await Task.FromResult(app.Owners.Any(x => x.Id == ctx.User.Id)) : await Task.FromResult(ctx.User.Id == me.Id); var allowed = this.UserIds.Contains(ctx.User.Id); return owner || allowed; } } } diff --git a/DisCatSharp.CommandsNext/CommandsNextExtension.cs b/DisCatSharp.CommandsNext/CommandsNextExtension.cs index c69970886..26b1ea305 100644 --- a/DisCatSharp.CommandsNext/CommandsNextExtension.cs +++ b/DisCatSharp.CommandsNext/CommandsNextExtension.cs @@ -1,1085 +1,1085 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using System.Reflection; using System.Threading.Tasks; using DisCatSharp.CommandsNext.Attributes; using DisCatSharp.CommandsNext.Builders; using DisCatSharp.CommandsNext.Converters; using DisCatSharp.CommandsNext.Entities; using DisCatSharp.CommandsNext.Exceptions; using DisCatSharp.Common.Utilities; using DisCatSharp.Entities; using DisCatSharp.EventArgs; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; namespace DisCatSharp.CommandsNext { /// /// This is the class which handles command registration, management, and execution. /// public class CommandsNextExtension : BaseExtension { /// /// Gets the config. /// private CommandsNextConfiguration Config { get; } /// /// Gets the help formatter. /// private HelpFormatterFactory HelpFormatter { get; } /// /// Gets the convert generic. /// private MethodInfo ConvertGeneric { get; } /// /// Gets the user friendly type names. /// private Dictionary UserFriendlyTypeNames { get; } /// /// Gets the argument converters. /// internal Dictionary ArgumentConverters { get; } /// /// Gets the service provider this CommandsNext module was configured with. /// public IServiceProvider Services => this.Config.ServiceProvider; /// /// Initializes a new instance of the class. /// /// The cfg. internal CommandsNextExtension(CommandsNextConfiguration cfg) { this.Config = new CommandsNextConfiguration(cfg); this.TopLevelCommands = new Dictionary(); this._registeredCommandsLazy = new Lazy>(() => new ReadOnlyDictionary(this.TopLevelCommands)); this.HelpFormatter = new HelpFormatterFactory(); this.HelpFormatter.SetFormatterType(); this.ArgumentConverters = new Dictionary { [typeof(string)] = new StringConverter(), [typeof(bool)] = new BoolConverter(), [typeof(sbyte)] = new Int8Converter(), [typeof(byte)] = new Uint8Converter(), [typeof(short)] = new Int16Converter(), [typeof(ushort)] = new Uint16Converter(), [typeof(int)] = new Int32Converter(), [typeof(uint)] = new Uint32Converter(), [typeof(long)] = new Int64Converter(), [typeof(ulong)] = new Uint64Converter(), [typeof(float)] = new Float32Converter(), [typeof(double)] = new Float64Converter(), [typeof(decimal)] = new Float128Converter(), [typeof(DateTime)] = new DateTimeConverter(), [typeof(DateTimeOffset)] = new DateTimeOffsetConverter(), [typeof(TimeSpan)] = new TimeSpanConverter(), [typeof(Uri)] = new UriConverter(), [typeof(DiscordUser)] = new DiscordUserConverter(), [typeof(DiscordMember)] = new DiscordMemberConverter(), [typeof(DiscordRole)] = new DiscordRoleConverter(), [typeof(DiscordChannel)] = new DiscordChannelConverter(), [typeof(DiscordGuild)] = new DiscordGuildConverter(), [typeof(DiscordMessage)] = new DiscordMessageConverter(), [typeof(DiscordEmoji)] = new DiscordEmojiConverter(), [typeof(DiscordThreadChannel)] = new DiscordThreadChannelConverter(), [typeof(DiscordInvite)] = new DiscordInviteConverter(), [typeof(DiscordColor)] = new DiscordColorConverter(), [typeof(DiscordScheduledEvent)] = new DiscordScheduledEventConverter(), }; this.UserFriendlyTypeNames = new Dictionary() { [typeof(string)] = "string", [typeof(bool)] = "boolean", [typeof(sbyte)] = "signed byte", [typeof(byte)] = "byte", [typeof(short)] = "short", [typeof(ushort)] = "unsigned short", [typeof(int)] = "int", [typeof(uint)] = "unsigned int", [typeof(long)] = "long", [typeof(ulong)] = "unsigned long", [typeof(float)] = "float", [typeof(double)] = "double", [typeof(decimal)] = "decimal", [typeof(DateTime)] = "date and time", [typeof(DateTimeOffset)] = "date and time", [typeof(TimeSpan)] = "time span", [typeof(Uri)] = "URL", [typeof(DiscordUser)] = "user", [typeof(DiscordMember)] = "member", [typeof(DiscordRole)] = "role", [typeof(DiscordChannel)] = "channel", [typeof(DiscordGuild)] = "guild", [typeof(DiscordMessage)] = "message", [typeof(DiscordEmoji)] = "emoji", [typeof(DiscordThreadChannel)] = "thread", [typeof(DiscordInvite)] = "invite", [typeof(DiscordColor)] = "color", [typeof(DiscordScheduledEvent)] = "event" }; var ncvt = typeof(NullableConverter<>); var nt = typeof(Nullable<>); var cvts = this.ArgumentConverters.Keys.ToArray(); foreach (var xt in cvts) { var xti = xt.GetTypeInfo(); if (!xti.IsValueType) continue; var xcvt = ncvt.MakeGenericType(xt); var xnt = nt.MakeGenericType(xt); if (this.ArgumentConverters.ContainsKey(xcvt)) continue; var xcv = Activator.CreateInstance(xcvt) as IArgumentConverter; this.ArgumentConverters[xnt] = xcv; this.UserFriendlyTypeNames[xnt] = this.UserFriendlyTypeNames[xt]; } var t = typeof(CommandsNextExtension); var ms = t.GetTypeInfo().DeclaredMethods; var m = ms.FirstOrDefault(xm => xm.Name == "ConvertArgument" && xm.ContainsGenericParameters && !xm.IsStatic && xm.IsPublic); this.ConvertGeneric = m; } /// /// Sets the help formatter to use with the default help command. /// /// Type of the formatter to use. public void SetHelpFormatter() where T : BaseHelpFormatter => this.HelpFormatter.SetFormatterType(); #region DiscordClient Registration /// /// DO NOT USE THIS MANUALLY. /// /// DO NOT USE THIS MANUALLY. /// protected internal override void Setup(DiscordClient client) { if (this.Client != null) throw new InvalidOperationException("What did I tell you?"); this.Client = client; this._executed = new AsyncEvent("COMMAND_EXECUTED", TimeSpan.Zero, this.Client.EventErrorHandler); this._error = new AsyncEvent("COMMAND_ERRORED", TimeSpan.Zero, this.Client.EventErrorHandler); if (this.Config.UseDefaultCommandHandler) this.Client.MessageCreated += this.HandleCommandsAsync; else this.Client.Logger.LogWarning(CommandsNextEvents.Misc, "Not attaching default command handler - if this is intentional, you can ignore this message"); if (this.Config.EnableDefaultHelp) { this.RegisterCommands(typeof(DefaultHelpModule), null, null, out var tcmds); if (this.Config.DefaultHelpChecks != null) { var checks = this.Config.DefaultHelpChecks.ToArray(); for (var i = 0; i < tcmds.Count; i++) tcmds[i].WithExecutionChecks(checks); } if (tcmds != null) foreach (var xc in tcmds) this.AddToCommandDictionary(xc.Build(null)); } } #endregion #region Command Handling /// /// Handles the commands async. /// /// The sender. /// The e. /// A Task. private async Task HandleCommandsAsync(DiscordClient sender, MessageCreateEventArgs e) { if (e.Author.IsBot) // bad bot return; if (!this.Config.EnableDms && e.Channel.IsPrivate) return; var mpos = -1; if (this.Config.EnableMentionPrefix) mpos = e.Message.GetMentionPrefixLength(this.Client.CurrentUser); if (this.Config.StringPrefixes?.Any() == true) foreach (var pfix in this.Config.StringPrefixes) if (mpos == -1 && !string.IsNullOrWhiteSpace(pfix)) mpos = e.Message.GetStringPrefixLength(pfix, this.Config.CaseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase); if (mpos == -1 && this.Config.PrefixResolver != null) mpos = await this.Config.PrefixResolver(e.Message).ConfigureAwait(false); if (mpos == -1) return; var pfx = e.Message.Content[..mpos]; var cnt = e.Message.Content[mpos..]; var __ = 0; var fname = cnt.ExtractNextArgument(ref __); var cmd = this.FindCommand(cnt, out var args); var ctx = this.CreateContext(e.Message, pfx, cmd, args); if (cmd == null) { await this._error.InvokeAsync(this, new CommandErrorEventArgs(this.Client.ServiceProvider) { Context = ctx, Exception = new CommandNotFoundException(fname) }).ConfigureAwait(false); return; } _ = Task.Run(async () => await this.ExecuteCommandAsync(ctx).ConfigureAwait(false)); } /// /// Finds a specified command by its qualified name, then separates arguments. /// /// Qualified name of the command, optionally with arguments. /// Separated arguments. /// Found command or null if none was found. public Command FindCommand(string commandString, out string rawArguments) { rawArguments = null; var ignoreCase = !this.Config.CaseSensitive; var pos = 0; var next = commandString.ExtractNextArgument(ref pos); if (next == null) return null; if (!this.RegisteredCommands.TryGetValue(next, out var cmd)) { if (!ignoreCase) return null; next = next.ToLowerInvariant(); var cmdKvp = this.RegisteredCommands.FirstOrDefault(x => x.Key.ToLowerInvariant() == next); if (cmdKvp.Value == null) return null; cmd = cmdKvp.Value; } if (cmd is not CommandGroup) { rawArguments = commandString[pos..].Trim(); return cmd; } while (cmd is CommandGroup) { var cm2 = cmd as CommandGroup; var oldPos = pos; next = commandString.ExtractNextArgument(ref pos); if (next == null) break; if (ignoreCase) { next = next.ToLowerInvariant(); cmd = cm2.Children.FirstOrDefault(x => x.Name.ToLowerInvariant() == next || x.Aliases?.Any(xx => xx.ToLowerInvariant() == next) == true); } else { cmd = cm2.Children.FirstOrDefault(x => x.Name == next || x.Aliases?.Contains(next) == true); } if (cmd == null) { cmd = cm2; pos = oldPos; break; } } rawArguments = commandString[pos..].Trim(); return cmd; } /// /// Creates a command execution context from specified arguments. /// /// Message to use for context. /// Command prefix, used to execute commands. /// Command to execute. /// Raw arguments to pass to command. /// Created command execution context. public CommandContext CreateContext(DiscordMessage msg, string prefix, Command cmd, string rawArguments = null) { var ctx = new CommandContext { Client = this.Client, Command = cmd, Message = msg, Config = this.Config, RawArgumentString = rawArguments ?? "", Prefix = prefix, CommandsNext = this, Services = this.Services }; if (cmd != null && (cmd.Module is TransientCommandModule || cmd.Module == null)) { var scope = ctx.Services.CreateScope(); ctx.ServiceScopeContext = new CommandContext.ServiceContext(ctx.Services, scope); ctx.Services = scope.ServiceProvider; } return ctx; } /// /// Executes specified command from given context. /// /// Context to execute command from. /// public async Task ExecuteCommandAsync(CommandContext ctx) { try { var cmd = ctx.Command; await this.RunAllChecksAsync(cmd, ctx).ConfigureAwait(false); var res = await cmd.ExecuteAsync(ctx).ConfigureAwait(false); if (res.IsSuccessful) await this._executed.InvokeAsync(this, new CommandExecutionEventArgs(this.Client.ServiceProvider) { Context = res.Context }).ConfigureAwait(false); else await this._error.InvokeAsync(this, new CommandErrorEventArgs(this.Client.ServiceProvider) { Context = res.Context, Exception = res.Exception }).ConfigureAwait(false); } catch (Exception ex) { await this._error.InvokeAsync(this, new CommandErrorEventArgs(this.Client.ServiceProvider) { Context = ctx, Exception = ex }).ConfigureAwait(false); } finally { if (ctx.ServiceScopeContext.IsInitialized) ctx.ServiceScopeContext.Dispose(); } } /// /// Runs the all checks async. /// /// The cmd. /// The ctx. /// A Task. private async Task RunAllChecksAsync(Command cmd, CommandContext ctx) { if (cmd.Parent != null) await this.RunAllChecksAsync(cmd.Parent, ctx).ConfigureAwait(false); var fchecks = await cmd.RunChecksAsync(ctx, false).ConfigureAwait(false); if (fchecks.Any()) throw new ChecksFailedException(cmd, ctx, fchecks); } #endregion #region Command Registration /// /// Gets a dictionary of registered top-level commands. /// public IReadOnlyDictionary RegisteredCommands => this._registeredCommandsLazy.Value; /// /// Gets or sets the top level commands. /// private Dictionary TopLevelCommands { get; set; } private readonly Lazy> _registeredCommandsLazy; /// /// Registers all commands from a given assembly. The command classes need to be public to be considered for registration. /// /// Assembly to register commands from. public void RegisterCommands(Assembly assembly) { var types = assembly.ExportedTypes.Where(xt => { var xti = xt.GetTypeInfo(); return xti.IsModuleCandidateType() && !xti.IsNested; }); foreach (var xt in types) this.RegisterCommands(xt); } /// /// Registers all commands from a given command class. /// /// Class which holds commands to register. public void RegisterCommands() where T : BaseCommandModule { var t = typeof(T); this.RegisterCommands(t); } /// /// Registers all commands from a given command class. /// /// Type of the class which holds commands to register. public void RegisterCommands(Type t) { if (t == null) throw new ArgumentNullException(nameof(t), "Type cannot be null."); if (!t.IsModuleCandidateType()) throw new ArgumentNullException(nameof(t), "Type must be a class, which cannot be abstract or static."); this.RegisterCommands(t, null, null, out var tempCommands); if (tempCommands != null) foreach (var command in tempCommands) this.AddToCommandDictionary(command.Build(null)); } /// /// Registers the commands. /// /// The type. /// The current parent. /// The inherited checks. /// The found commands. private void RegisterCommands(Type t, CommandGroupBuilder currentParent, IEnumerable inheritedChecks, out List foundCommands) { var ti = t.GetTypeInfo(); var lifespan = ti.GetCustomAttribute(); var moduleLifespan = lifespan != null ? lifespan.Lifespan : ModuleLifespan.Singleton; var module = new CommandModuleBuilder() .WithType(t) .WithLifespan(moduleLifespan) .Build(this.Services); // restrict parent lifespan to more or equally restrictive if (currentParent?.Module is TransientCommandModule && moduleLifespan != ModuleLifespan.Transient) throw new InvalidOperationException("In a transient module, child modules can only be transient."); // check if we are anything var groupBuilder = new CommandGroupBuilder(module); var isModule = false; var moduleAttributes = ti.GetCustomAttributes(); var moduleHidden = false; var moduleChecks = new List(); foreach (var xa in moduleAttributes) { switch (xa) { case GroupAttribute g: isModule = true; var moduleName = g.Name; if (moduleName == null) { moduleName = ti.Name; if (moduleName.EndsWith("Group") && moduleName != "Group") moduleName = moduleName[0..^5]; else if (moduleName.EndsWith("Module") && moduleName != "Module") moduleName = moduleName[0..^6]; else if (moduleName.EndsWith("Commands") && moduleName != "Commands") moduleName = moduleName[0..^8]; } if (!this.Config.CaseSensitive) moduleName = moduleName.ToLowerInvariant(); groupBuilder.WithName(moduleName); if (inheritedChecks != null) foreach (var chk in inheritedChecks) groupBuilder.WithExecutionCheck(chk); foreach (var mi in ti.DeclaredMethods.Where(x => x.IsCommandCandidate(out _) && x.GetCustomAttribute() != null)) groupBuilder.WithOverload(new CommandOverloadBuilder(mi)); break; case AliasesAttribute a: foreach (var xalias in a.Aliases) groupBuilder.WithAlias(this.Config.CaseSensitive ? xalias : xalias.ToLowerInvariant()); break; case HiddenAttribute h: groupBuilder.WithHiddenStatus(true); moduleHidden = true; break; case DescriptionAttribute d: groupBuilder.WithDescription(d.Description); break; case CheckBaseAttribute c: moduleChecks.Add(c); groupBuilder.WithExecutionCheck(c); break; default: groupBuilder.WithCustomAttribute(xa); break; } } if (!isModule) { groupBuilder = null; if (inheritedChecks != null) moduleChecks.AddRange(inheritedChecks); } // candidate methods var methods = ti.DeclaredMethods; var commands = new List(); var commandBuilders = new Dictionary(); foreach (var m in methods) { if (!m.IsCommandCandidate(out _)) continue; var attrs = m.GetCustomAttributes(); if (attrs.FirstOrDefault(xa => xa is CommandAttribute) is not CommandAttribute cattr) continue; var commandName = cattr.Name; if (commandName == null) { commandName = m.Name; if (commandName.EndsWith("Async") && commandName != "Async") commandName = commandName[0..^5]; } if (!this.Config.CaseSensitive) commandName = commandName.ToLowerInvariant(); if (!commandBuilders.TryGetValue(commandName, out var commandBuilder)) { commandBuilders.Add(commandName, commandBuilder = new CommandBuilder(module).WithName(commandName)); if (!isModule) if (currentParent != null) currentParent.WithChild(commandBuilder); else commands.Add(commandBuilder); else groupBuilder.WithChild(commandBuilder); } commandBuilder.WithOverload(new CommandOverloadBuilder(m)); if (!isModule && moduleChecks.Any()) foreach (var chk in moduleChecks) commandBuilder.WithExecutionCheck(chk); foreach (var xa in attrs) { switch (xa) { case AliasesAttribute a: foreach (var xalias in a.Aliases) commandBuilder.WithAlias(this.Config.CaseSensitive ? xalias : xalias.ToLowerInvariant()); break; case CheckBaseAttribute p: commandBuilder.WithExecutionCheck(p); break; case DescriptionAttribute d: commandBuilder.WithDescription(d.Description); break; case HiddenAttribute h: commandBuilder.WithHiddenStatus(true); break; default: commandBuilder.WithCustomAttribute(xa); break; } } if (!isModule && moduleHidden) commandBuilder.WithHiddenStatus(true); } // candidate types var types = ti.DeclaredNestedTypes .Where(xt => xt.IsModuleCandidateType() && xt.DeclaredConstructors.Any(xc => xc.IsPublic)); foreach (var type in types) { this.RegisterCommands(type.AsType(), groupBuilder, !isModule ? moduleChecks : null, out var tempCommands); if (isModule) foreach (var chk in moduleChecks) groupBuilder.WithExecutionCheck(chk); if (isModule && tempCommands != null) foreach (var xtcmd in tempCommands) groupBuilder.WithChild(xtcmd); else if (tempCommands != null) commands.AddRange(tempCommands); } if (isModule && currentParent == null) commands.Add(groupBuilder); else if (isModule) currentParent.WithChild(groupBuilder); foundCommands = commands; } /// /// Builds and registers all supplied commands. /// /// Commands to build and register. public void RegisterCommands(params CommandBuilder[] cmds) { foreach (var cmd in cmds) this.AddToCommandDictionary(cmd.Build(null)); } /// /// Unregisters specified commands from CommandsNext. /// /// Commands to unregister. public void UnregisterCommands(params Command[] cmds) { if (cmds.Any(x => x.Parent != null)) throw new InvalidOperationException("Cannot unregister nested commands."); var keys = this.RegisteredCommands.Where(x => cmds.Contains(x.Value)).Select(x => x.Key).ToList(); foreach (var key in keys) this.TopLevelCommands.Remove(key); } /// /// Adds the to command dictionary. /// /// The cmd. private void AddToCommandDictionary(Command cmd) { if (cmd.Parent != null) return; if (this.TopLevelCommands.ContainsKey(cmd.Name) || (cmd.Aliases != null && cmd.Aliases.Any(xs => this.TopLevelCommands.ContainsKey(xs)))) throw new DuplicateCommandException(cmd.QualifiedName); this.TopLevelCommands[cmd.Name] = cmd; if (cmd.Aliases != null) foreach (var xs in cmd.Aliases) this.TopLevelCommands[xs] = cmd; } #endregion #region Default Help /// /// Represents the default help module. /// [ModuleLifespan(ModuleLifespan.Transient)] public class DefaultHelpModule : BaseCommandModule { /// /// Defaults the help async. /// /// The ctx. /// The command. /// A Task. [Command("help"), Description("Displays command help.")] public async Task DefaultHelpAsync(CommandContext ctx, [Description("Command to provide help for.")] params string[] command) { var topLevel = ctx.CommandsNext.TopLevelCommands.Values.Distinct(); var helpBuilder = ctx.CommandsNext.HelpFormatter.Create(ctx); if (command != null && command.Any()) { Command cmd = null; var searchIn = topLevel; foreach (var c in command) { if (searchIn == null) { cmd = null; break; } cmd = ctx.Config.CaseSensitive ? searchIn.FirstOrDefault(xc => xc.Name == c || (xc.Aliases != null && xc.Aliases.Contains(c))) : searchIn.FirstOrDefault(xc => xc.Name.ToLowerInvariant() == c.ToLowerInvariant() || (xc.Aliases != null && xc.Aliases.Select(xs => xs.ToLowerInvariant()).Contains(c.ToLowerInvariant()))); if (cmd == null) break; var failedChecks = await cmd.RunChecksAsync(ctx, true).ConfigureAwait(false); if (failedChecks.Any()) throw new ChecksFailedException(cmd, ctx, failedChecks); searchIn = cmd is CommandGroup ? (cmd as CommandGroup).Children : null; } if (cmd == null) throw new CommandNotFoundException(string.Join(" ", command)); helpBuilder.WithCommand(cmd); if (cmd is CommandGroup group) { var commandsToSearch = group.Children.Where(xc => !xc.IsHidden); var eligibleCommands = new List(); foreach (var candidateCommand in commandsToSearch) { if (candidateCommand.ExecutionChecks == null || !candidateCommand.ExecutionChecks.Any()) { eligibleCommands.Add(candidateCommand); continue; } var candidateFailedChecks = await candidateCommand.RunChecksAsync(ctx, true).ConfigureAwait(false); if (!candidateFailedChecks.Any()) eligibleCommands.Add(candidateCommand); } if (eligibleCommands.Any()) helpBuilder.WithSubcommands(eligibleCommands.OrderBy(xc => xc.Name)); } } else { var commandsToSearch = topLevel.Where(xc => !xc.IsHidden); var eligibleCommands = new List(); foreach (var sc in commandsToSearch) { if (sc.ExecutionChecks == null || !sc.ExecutionChecks.Any()) { eligibleCommands.Add(sc); continue; } var candidateFailedChecks = await sc.RunChecksAsync(ctx, true).ConfigureAwait(false); if (!candidateFailedChecks.Any()) eligibleCommands.Add(sc); } if (eligibleCommands.Any()) helpBuilder.WithSubcommands(eligibleCommands.OrderBy(xc => xc.Name)); } var helpMessage = helpBuilder.Build(); var builder = new DiscordMessageBuilder().WithContent(helpMessage.Content).WithEmbed(helpMessage.Embed); if (!ctx.Config.DmHelp || ctx.Channel is DiscordDmChannel || ctx.Guild == null) await ctx.RespondAsync(builder).ConfigureAwait(false); else await ctx.Member.SendMessageAsync(builder).ConfigureAwait(false); } } #endregion #region Sudo /// /// Creates a fake command context to execute commands with. /// /// The user or member to use as message author. /// The channel the message is supposed to appear from. /// Contents of the message. /// Command prefix, used to execute commands. /// Command to execute. /// Raw arguments to pass to command. /// Created fake context. public CommandContext CreateFakeContext(DiscordUser actor, DiscordChannel channel, string messageContents, string prefix, Command cmd, string rawArguments = null) { var epoch = new DateTimeOffset(2015, 1, 1, 0, 0, 0, TimeSpan.Zero); var now = DateTimeOffset.UtcNow; var timeSpan = (ulong)(now - epoch).TotalMilliseconds; // create fake message var msg = new DiscordMessage { Discord = this.Client, Author = actor, ChannelId = channel.Id, Content = messageContents, Id = timeSpan << 22, Pinned = false, MentionEveryone = messageContents.Contains("@everyone"), - IsTTS = false, - _attachments = new List(), - _embeds = new List(), + IsTts = false, + AttachmentsInternal = new List(), + EmbedsInternal = new List(), TimestampRaw = now.ToString("yyyy-MM-ddTHH:mm:sszzz"), - _reactions = new List() + ReactionsInternal = new List() }; var mentionedUsers = new List(); var mentionedRoles = msg.Channel.Guild != null ? new List() : null; var mentionedChannels = msg.Channel.Guild != null ? new List() : null; if (!string.IsNullOrWhiteSpace(msg.Content)) { if (msg.Channel.Guild != null) { - mentionedUsers = Utilities.GetUserMentions(msg).Select(xid => msg.Channel.Guild._members.TryGetValue(xid, out var member) ? member : null).Cast().ToList(); + mentionedUsers = Utilities.GetUserMentions(msg).Select(xid => msg.Channel.Guild.MembersInternal.TryGetValue(xid, out var member) ? member : null).Cast().ToList(); mentionedRoles = Utilities.GetRoleMentions(msg).Select(xid => msg.Channel.Guild.GetRole(xid)).ToList(); mentionedChannels = Utilities.GetChannelMentions(msg).Select(xid => msg.Channel.Guild.GetChannel(xid)).ToList(); } else { mentionedUsers = Utilities.GetUserMentions(msg).Select(this.Client.GetCachedOrEmptyUserInternal).ToList(); } } - msg._mentionedUsers = mentionedUsers; - msg._mentionedRoles = mentionedRoles; - msg._mentionedChannels = mentionedChannels; + msg.MentionedUsersInternal = mentionedUsers; + msg.MentionedRolesInternal = mentionedRoles; + msg.MentionedChannelsInternal = mentionedChannels; var ctx = new CommandContext { Client = this.Client, Command = cmd, Message = msg, Config = this.Config, RawArgumentString = rawArguments ?? "", Prefix = prefix, CommandsNext = this, Services = this.Services }; if (cmd != null && (cmd.Module is TransientCommandModule || cmd.Module == null)) { var scope = ctx.Services.CreateScope(); ctx.ServiceScopeContext = new CommandContext.ServiceContext(ctx.Services, scope); ctx.Services = scope.ServiceProvider; } return ctx; } #endregion #region Type Conversion /// /// Converts a string to specified type. /// /// Type to convert to. /// Value to convert. /// Context in which to convert to. /// Converted object. #pragma warning disable IDE1006 // Naming Styles public async Task ConvertArgument(string value, CommandContext ctx) #pragma warning restore IDE1006 // Naming Styles { var t = typeof(T); if (!this.ArgumentConverters.ContainsKey(t)) throw new ArgumentException("There is no converter specified for given type.", nameof(T)); if (this.ArgumentConverters[t] is not IArgumentConverter cv) throw new ArgumentException("Invalid converter registered for this type.", nameof(T)); var cvr = await cv.ConvertAsync(value, ctx).ConfigureAwait(false); return !cvr.HasValue ? throw new ArgumentException("Could not convert specified value to given type.", nameof(value)) : cvr.Value; } /// /// Converts a string to specified type. /// /// Value to convert. /// Context in which to convert to. /// Type to convert to. /// Converted object. #pragma warning disable IDE1006 // Naming Styles public async Task ConvertArgument(string value, CommandContext ctx, Type type) #pragma warning restore IDE1006 // Naming Styles { var m = this.ConvertGeneric.MakeGenericMethod(type); try { return await (m.Invoke(this, new object[] { value, ctx }) as Task).ConfigureAwait(false); } catch (TargetInvocationException ex) { throw ex.InnerException; } } /// /// Registers an argument converter for specified type. /// /// Type for which to register the converter. /// Converter to register. public void RegisterConverter(IArgumentConverter converter) { if (converter == null) throw new ArgumentNullException(nameof(converter), "Converter cannot be null."); var t = typeof(T); var ti = t.GetTypeInfo(); this.ArgumentConverters[t] = converter; if (!ti.IsValueType) return; var nullableConverterType = typeof(NullableConverter<>).MakeGenericType(t); var nullableType = typeof(Nullable<>).MakeGenericType(t); if (this.ArgumentConverters.ContainsKey(nullableType)) return; var nullableConverter = Activator.CreateInstance(nullableConverterType) as IArgumentConverter; this.ArgumentConverters[nullableType] = nullableConverter; } /// /// Unregisters an argument converter for specified type. /// /// Type for which to unregister the converter. public void UnregisterConverter() { var t = typeof(T); var ti = t.GetTypeInfo(); if (this.ArgumentConverters.ContainsKey(t)) this.ArgumentConverters.Remove(t); if (this.UserFriendlyTypeNames.ContainsKey(t)) this.UserFriendlyTypeNames.Remove(t); if (!ti.IsValueType) return; var nullableType = typeof(Nullable<>).MakeGenericType(t); if (!this.ArgumentConverters.ContainsKey(nullableType)) return; this.ArgumentConverters.Remove(nullableType); this.UserFriendlyTypeNames.Remove(nullableType); } /// /// Registers a user-friendly type name. /// /// Type to register the name for. /// Name to register. public void RegisterUserFriendlyTypeName(string value) { if (string.IsNullOrWhiteSpace(value)) throw new ArgumentNullException(nameof(value), "Name cannot be null or empty."); var t = typeof(T); var ti = t.GetTypeInfo(); if (!this.ArgumentConverters.ContainsKey(t)) throw new InvalidOperationException("Cannot register a friendly name for a type which has no associated converter."); this.UserFriendlyTypeNames[t] = value; if (!ti.IsValueType) return; var nullableType = typeof(Nullable<>).MakeGenericType(t); this.UserFriendlyTypeNames[nullableType] = value; } /// /// Converts a type into user-friendly type name. /// /// Type to convert. /// User-friendly type name. public string GetUserFriendlyTypeName(Type t) { if (this.UserFriendlyTypeNames.ContainsKey(t)) return this.UserFriendlyTypeNames[t]; var ti = t.GetTypeInfo(); if (ti.IsGenericTypeDefinition && t.GetGenericTypeDefinition() == typeof(Nullable<>)) { var tn = ti.GenericTypeArguments[0]; return this.UserFriendlyTypeNames.ContainsKey(tn) ? this.UserFriendlyTypeNames[tn] : tn.Name; } return t.Name; } #endregion #region Helpers /// /// Gets the configuration-specific string comparer. This returns or , /// depending on whether is set to or . /// /// A string comparer. internal IEqualityComparer GetStringComparer() => this.Config.CaseSensitive ? StringComparer.Ordinal : StringComparer.OrdinalIgnoreCase; #endregion #region Events /// /// Triggered whenever a command executes successfully. /// public event AsyncEventHandler CommandExecuted { add { this._executed.Register(value); } remove { this._executed.Unregister(value); } } private AsyncEvent _executed; /// /// Triggered whenever a command throws an exception during execution. /// public event AsyncEventHandler CommandErrored { add { this._error.Register(value); } remove { this._error.Unregister(value); } } private AsyncEvent _error; /// /// Ons the command executed. /// /// The e. /// A Task. private Task OnCommandExecuted(CommandExecutionEventArgs e) => this._executed.InvokeAsync(this, e); /// /// Ons the command errored. /// /// The e. /// A Task. private Task OnCommandErrored(CommandErrorEventArgs e) => this._error.InvokeAsync(this, e); #endregion } } diff --git a/DisCatSharp.CommandsNext/CommandsNextUtilities.cs b/DisCatSharp.CommandsNext/CommandsNextUtilities.cs index 899034ed8..c6e48d1f2 100644 --- a/DisCatSharp.CommandsNext/CommandsNextUtilities.cs +++ b/DisCatSharp.CommandsNext/CommandsNextUtilities.cs @@ -1,432 +1,432 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Text.RegularExpressions; using System.Threading.Tasks; using DisCatSharp.CommandsNext.Attributes; using DisCatSharp.CommandsNext.Converters; using DisCatSharp.Common.RegularExpressions; using DisCatSharp.Entities; using Microsoft.Extensions.DependencyInjection; namespace DisCatSharp.CommandsNext { /// /// Various CommandsNext-related utilities. /// public static class CommandsNextUtilities { /// /// Gets the user regex. /// - private static Regex UserRegex { get; } = DiscordRegEx.User; + private static Regex s_userRegex { get; } = DiscordRegEx.User; /// /// Checks whether the message has a specified string prefix. /// /// Message to check. /// String to check for. /// Method of string comparison for the purposes of finding prefixes. /// Positive number if the prefix is present, -1 otherwise. public static int GetStringPrefixLength(this DiscordMessage msg, string str, StringComparison comparisonType = StringComparison.Ordinal) { var content = msg.Content; return str.Length >= content.Length ? -1 : !content.StartsWith(str, comparisonType) ? -1 : str.Length; } /// /// Checks whether the message contains a specified mention prefix. /// /// Message to check. /// User to check for. /// Positive number if the prefix is present, -1 otherwise. public static int GetMentionPrefixLength(this DiscordMessage msg, DiscordUser user) { var content = msg.Content; if (!content.StartsWith("<@")) return -1; var cni = content.IndexOf('>'); if (cni == -1 || content.Length <= cni + 2) return -1; var cnp = content[..(cni + 2)]; - var m = UserRegex.Match(cnp); + var m = s_userRegex.Match(cnp); if (!m.Success) return -1; var userId = ulong.Parse(m.Groups[1].Value, CultureInfo.InvariantCulture); return user.Id != userId ? -1 : m.Value.Length; } //internal static string ExtractNextArgument(string str, out string remainder) /// /// Extracts the next argument. /// /// The string. /// The start position. internal static string ExtractNextArgument(this string str, ref int startPos) { if (string.IsNullOrWhiteSpace(str)) return null; var inBacktick = false; var inTripleBacktick = false; var inQuote = false; var inEscape = false; var removeIndices = new List(str.Length - startPos); var i = startPos; for (; i < str.Length; i++) if (!char.IsWhiteSpace(str[i])) break; startPos = i; var endPosition = -1; var startPosition = startPos; for (i = startPosition; i < str.Length; i++) { if (char.IsWhiteSpace(str[i]) && !inQuote && !inTripleBacktick && !inBacktick && !inEscape) endPosition = i; if (str[i] == '\\' && str.Length > i + 1) { if (!inEscape && !inBacktick && !inTripleBacktick) { inEscape = true; if (str.IndexOf("\\`", i) == i || str.IndexOf("\\\"", i) == i || str.IndexOf("\\\\", i) == i || (str.Length >= i && char.IsWhiteSpace(str[i + 1]))) removeIndices.Add(i - startPosition); i++; } else if ((inBacktick || inTripleBacktick) && str.IndexOf("\\`", i) == i) { inEscape = true; removeIndices.Add(i - startPosition); i++; } } if (str[i] == '`' && !inEscape) { var tripleBacktick = str.IndexOf("```", i) == i; if (inTripleBacktick && tripleBacktick) { inTripleBacktick = false; i += 2; } else if (!inBacktick && tripleBacktick) { inTripleBacktick = true; i += 2; } if (inBacktick && !tripleBacktick) inBacktick = false; else if (!inTripleBacktick && tripleBacktick) inBacktick = true; } if (str[i] == '"' && !inEscape && !inBacktick && !inTripleBacktick) { removeIndices.Add(i - startPosition); inQuote = !inQuote; } if (inEscape) inEscape = false; if (endPosition != -1) { startPos = endPosition; return startPosition != endPosition ? str[startPosition..endPosition].CleanupString(removeIndices) : null; } } startPos = str.Length; return startPos != startPosition ? str[startPosition..].CleanupString(removeIndices) : null; } /// /// Cleanups the string. /// /// The string. /// The indices. internal static string CleanupString(this string s, IList indices) { if (!indices.Any()) return s; var li = indices.Last(); var ll = 1; for (var x = indices.Count - 2; x >= 0; x--) { if (li - indices[x] == ll) { ll++; continue; } s = s.Remove(li - ll + 1, ll); li = indices[x]; ll = 1; } return s.Remove(li - ll + 1, ll); } #pragma warning disable IDE1006 // Naming Styles /// /// Binds the arguments. /// /// The command context. /// If true, ignore further text in string. internal static async Task BindArguments(CommandContext ctx, bool ignoreSurplus) #pragma warning restore IDE1006 // Naming Styles { var command = ctx.Command; var overload = ctx.Overload; var args = new object[overload.Arguments.Count + 2]; args[1] = ctx; var rawArgumentList = new List(overload.Arguments.Count); var argString = ctx.RawArgumentString; var foundAt = 0; var argValue = ""; for (var i = 0; i < overload.Arguments.Count; i++) { var arg = overload.Arguments[i]; if (arg.IsCatchAll) { if (arg.IsArray) { while (true) { argValue = ExtractNextArgument(argString, ref foundAt); if (argValue == null) break; rawArgumentList.Add(argValue); } break; } else { if (argString == null) break; argValue = argString[foundAt..].Trim(); argValue = argValue == "" ? null : argValue; foundAt = argString.Length; rawArgumentList.Add(argValue); break; } } else { argValue = ExtractNextArgument(argString, ref foundAt); rawArgumentList.Add(argValue); } if (argValue == null && !arg.IsOptional && !arg.IsCatchAll) return new ArgumentBindingResult(new ArgumentException("Not enough arguments supplied to the command.")); else if (argValue == null) rawArgumentList.Add(null); } if (!ignoreSurplus && foundAt < argString.Length) return new ArgumentBindingResult(new ArgumentException("Too many arguments were supplied to this command.")); for (var i = 0; i < overload.Arguments.Count; i++) { var arg = overload.Arguments[i]; if (arg.IsCatchAll && arg.IsArray) { var array = Array.CreateInstance(arg.Type, rawArgumentList.Count - i); var start = i; while (i < rawArgumentList.Count) { try { array.SetValue(await ctx.CommandsNext.ConvertArgument(rawArgumentList[i], ctx, arg.Type).ConfigureAwait(false), i - start); } catch (Exception ex) { return new ArgumentBindingResult(ex); } i++; } args[start + 2] = array; break; } else { try { args[i + 2] = rawArgumentList[i] != null ? await ctx.CommandsNext.ConvertArgument(rawArgumentList[i], ctx, arg.Type).ConfigureAwait(false) : arg.DefaultValue; } catch (Exception ex) { return new ArgumentBindingResult(ex); } } } return new ArgumentBindingResult(args, rawArgumentList); } /// /// Whether this module is a candidate type. /// /// The type. internal static bool IsModuleCandidateType(this Type type) => type.GetTypeInfo().IsModuleCandidateType(); /// /// Whether this module is a candidate type. /// /// The type info. internal static bool IsModuleCandidateType(this TypeInfo ti) { // check if compiler-generated if (ti.GetCustomAttribute(false) != null) return false; // check if derives from the required base class var tmodule = typeof(BaseCommandModule); var timodule = tmodule.GetTypeInfo(); if (!timodule.IsAssignableFrom(ti)) return false; // check if anonymous if (ti.IsGenericType && ti.Name.Contains("AnonymousType") && (ti.Name.StartsWith("<>") || ti.Name.StartsWith("VB$")) && (ti.Attributes & TypeAttributes.NotPublic) == TypeAttributes.NotPublic) return false; // check if abstract, static, or not a class if (!ti.IsClass || ti.IsAbstract) return false; // check if delegate type var tdelegate = typeof(Delegate).GetTypeInfo(); if (tdelegate.IsAssignableFrom(ti)) return false; // qualifies if any method or type qualifies return ti.DeclaredMethods.Any(xmi => xmi.IsCommandCandidate(out _)) || ti.DeclaredNestedTypes.Any(xti => xti.IsModuleCandidateType()); } /// /// Whether this is a command candidate. /// /// The method. /// The parameters. internal static bool IsCommandCandidate(this MethodInfo method, out ParameterInfo[] parameters) { parameters = null; // check if exists if (method == null) return false; // check if static, non-public, abstract, a constructor, or a special name if (method.IsStatic || method.IsAbstract || method.IsConstructor || method.IsSpecialName) return false; // check if appropriate return and arguments parameters = method.GetParameters(); if (!parameters.Any() || parameters.First().ParameterType != typeof(CommandContext) || method.ReturnType != typeof(Task)) return false; // qualifies return true; } /// /// Creates the instance. /// /// The type. /// The services provider. internal static object CreateInstance(this Type t, IServiceProvider services) { var ti = t.GetTypeInfo(); var constructors = ti.DeclaredConstructors .Where(xci => xci.IsPublic) .ToArray(); if (constructors.Length != 1) throw new ArgumentException("Specified type does not contain a public constructor or contains more than one public constructor."); var constructor = constructors[0]; var constructorArgs = constructor.GetParameters(); var args = new object[constructorArgs.Length]; if (constructorArgs.Length != 0 && services == null) throw new InvalidOperationException("Dependency collection needs to be specified for parameterized constructors."); // inject via constructor if (constructorArgs.Length != 0) for (var i = 0; i < args.Length; i++) args[i] = services.GetRequiredService(constructorArgs[i].ParameterType); var moduleInstance = Activator.CreateInstance(t, args); // inject into properties var props = t.GetRuntimeProperties().Where(xp => xp.CanWrite && xp.SetMethod != null && !xp.SetMethod.IsStatic && xp.SetMethod.IsPublic); foreach (var prop in props) { if (prop.GetCustomAttribute() != null) continue; var service = services.GetService(prop.PropertyType); if (service == null) continue; prop.SetValue(moduleInstance, service); } // inject into fields var fields = t.GetRuntimeFields().Where(xf => !xf.IsInitOnly && !xf.IsStatic && xf.IsPublic); foreach (var field in fields) { if (field.GetCustomAttribute() != null) continue; var service = services.GetService(field.FieldType); if (service == null) continue; field.SetValue(moduleInstance, service); } return moduleInstance; } } } diff --git a/DisCatSharp.CommandsNext/Converters/EntityConverters.cs b/DisCatSharp.CommandsNext/Converters/EntityConverters.cs index 681bdaaac..7b49c3ceb 100644 --- a/DisCatSharp.CommandsNext/Converters/EntityConverters.cs +++ b/DisCatSharp.CommandsNext/Converters/EntityConverters.cs @@ -1,600 +1,600 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System; using System.Globalization; using System.Linq; using System.Text.RegularExpressions; using System.Threading.Tasks; using DisCatSharp.Common.RegularExpressions; using DisCatSharp.Entities; namespace DisCatSharp.CommandsNext.Converters { /// /// Represents a discord user converter. /// public class DiscordUserConverter : IArgumentConverter { /// /// Gets the user regex. /// - private static Regex UserRegex { get; } + private static Regex s_userRegex { get; } /// /// Initializes a new instance of the class. /// static DiscordUserConverter() { - UserRegex = DiscordRegEx.User; + s_userRegex = DiscordRegEx.User; } /// /// Converts a string. /// /// The string to convert. /// The command context. async Task> IArgumentConverter.ConvertAsync(string value, CommandContext ctx) { if (ulong.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var uid)) { var result = await ctx.Client.GetUserAsync(uid).ConfigureAwait(false); var ret = result != null ? Optional.FromValue(result) : Optional.FromNoValue(); return ret; } - var m = UserRegex.Match(value); + var m = s_userRegex.Match(value); if (m.Success && ulong.TryParse(m.Groups[1].Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out uid)) { var result = await ctx.Client.GetUserAsync(uid).ConfigureAwait(false); var ret = result != null ? Optional.FromValue(result) : Optional.FromNoValue(); return ret; } var cs = ctx.Config.CaseSensitive; if (!cs) value = value.ToLowerInvariant(); var di = value.IndexOf('#'); var un = di != -1 ? value[..di] : value; var dv = di != -1 ? value[(di + 1)..] : null; var us = ctx.Client.Guilds.Values .SelectMany(xkvp => xkvp.Members.Values) .Where(xm => (cs ? xm.Username : xm.Username.ToLowerInvariant()) == un && ((dv != null && xm.Discriminator == dv) || dv == null)); var usr = us.FirstOrDefault(); return usr != null ? Optional.FromValue(usr) : Optional.FromNoValue(); } } /// /// Represents a discord member converter. /// public class DiscordMemberConverter : IArgumentConverter { /// /// Gets the user regex. /// - private static Regex UserRegex { get; } + private static Regex s_userRegex { get; } /// /// Initializes a new instance of the class. /// static DiscordMemberConverter() { - UserRegex = DiscordRegEx.User; + s_userRegex = DiscordRegEx.User; } /// /// Converts a string. /// /// The string to convert. /// The command context. async Task> IArgumentConverter.ConvertAsync(string value, CommandContext ctx) { if (ctx.Guild == null) return Optional.FromNoValue(); if (ulong.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var uid)) { var result = await ctx.Guild.GetMemberAsync(uid).ConfigureAwait(false); var ret = result != null ? Optional.FromValue(result) : Optional.FromNoValue(); return ret; } - var m = UserRegex.Match(value); + var m = s_userRegex.Match(value); if (m.Success && ulong.TryParse(m.Groups[1].Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out uid)) { var result = await ctx.Guild.GetMemberAsync(uid).ConfigureAwait(false); var ret = result != null ? Optional.FromValue(result) : Optional.FromNoValue(); return ret; } var searchResult = await ctx.Guild.SearchMembersAsync(value).ConfigureAwait(false); if (searchResult.Any()) return Optional.FromValue(searchResult.First()); var cs = ctx.Config.CaseSensitive; if (!cs) value = value.ToLowerInvariant(); var di = value.IndexOf('#'); var un = di != -1 ? value[..di] : value; var dv = di != -1 ? value[(di + 1)..] : null; var us = ctx.Guild.Members.Values .Where(xm => ((cs ? xm.Username : xm.Username.ToLowerInvariant()) == un && ((dv != null && xm.Discriminator == dv) || dv == null)) || (cs ? xm.Nickname : xm.Nickname?.ToLowerInvariant()) == value); var mbr = us.FirstOrDefault(); return mbr != null ? Optional.FromValue(mbr) : Optional.FromNoValue(); } } /// /// Represents a discord channel converter. /// public class DiscordChannelConverter : IArgumentConverter { /// /// Gets the channel regex. /// - private static Regex ChannelRegex { get; } + private static Regex s_channelRegex { get; } /// /// Initializes a new instance of the class. /// static DiscordChannelConverter() { - ChannelRegex = DiscordRegEx.Channel; + s_channelRegex = DiscordRegEx.Channel; } /// /// Converts a string. /// /// The string to convert. /// The command context. async Task> IArgumentConverter.ConvertAsync(string value, CommandContext ctx) { if (ulong.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var cid)) { var result = await ctx.Client.GetChannelAsync(cid).ConfigureAwait(false); var ret = result != null ? Optional.FromValue(result) : Optional.FromNoValue(); return ret; } - var m = ChannelRegex.Match(value); + var m = s_channelRegex.Match(value); if (m.Success && ulong.TryParse(m.Groups[1].Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out cid)) { var result = await ctx.Client.GetChannelAsync(cid).ConfigureAwait(false); var ret = result != null ? Optional.FromValue(result) : Optional.FromNoValue(); return ret; } var cs = ctx.Config.CaseSensitive; if (!cs) value = value.ToLowerInvariant(); var chn = ctx.Guild?.Channels.Values.FirstOrDefault(xc => (cs ? xc.Name : xc.Name.ToLowerInvariant()) == value); return chn != null ? Optional.FromValue(chn) : Optional.FromNoValue(); } } /// /// Represents a discord thread channel converter. /// public class DiscordThreadChannelConverter : IArgumentConverter { /// /// Gets the channel regex. /// - private static Regex ChannelRegex { get; } + private static Regex s_channelRegex { get; } /// /// Initializes a new instance of the class. /// static DiscordThreadChannelConverter() { - ChannelRegex = DiscordRegEx.Channel; + s_channelRegex = DiscordRegEx.Channel; } /// /// Converts a string. /// /// The string to convert. /// The command context. async Task> IArgumentConverter.ConvertAsync(string value, CommandContext ctx) { if (ulong.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var tid)) { var result = await ctx.Client.GetThreadAsync(tid).ConfigureAwait(false); var ret = result != null ? Optional.FromValue(result) : Optional.FromNoValue(); return ret; } - var m = ChannelRegex.Match(value); + var m = s_channelRegex.Match(value); if (m.Success && ulong.TryParse(m.Groups[1].Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out tid)) { var result = await ctx.Client.GetThreadAsync(tid).ConfigureAwait(false); var ret = result != null ? Optional.FromValue(result) : Optional.FromNoValue(); return ret; } var cs = ctx.Config.CaseSensitive; if (!cs) value = value.ToLowerInvariant(); var tchn = ctx.Guild?.Threads.Values.FirstOrDefault(xc => (cs ? xc.Name : xc.Name.ToLowerInvariant()) == value); return tchn != null ? Optional.FromValue(tchn) : Optional.FromNoValue(); } } /// /// Represents a discord role converter. /// public class DiscordRoleConverter : IArgumentConverter { /// /// Gets the role regex. /// - private static Regex RoleRegex { get; } + private static Regex s_roleRegex { get; } /// /// Initializes a new instance of the class. /// static DiscordRoleConverter() { - RoleRegex = DiscordRegEx.Role; + s_roleRegex = DiscordRegEx.Role; } /// /// Converts a string. /// /// The string to convert. /// The command context. Task> IArgumentConverter.ConvertAsync(string value, CommandContext ctx) { if (ctx.Guild == null) return Task.FromResult(Optional.FromNoValue()); if (ulong.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var rid)) { var result = ctx.Guild.GetRole(rid); var ret = result != null ? Optional.FromValue(result) : Optional.FromNoValue(); return Task.FromResult(ret); } - var m = RoleRegex.Match(value); + var m = s_roleRegex.Match(value); if (m.Success && ulong.TryParse(m.Groups[1].Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out rid)) { var result = ctx.Guild.GetRole(rid); var ret = result != null ? Optional.FromValue(result) : Optional.FromNoValue(); return Task.FromResult(ret); } var cs = ctx.Config.CaseSensitive; if (!cs) value = value.ToLowerInvariant(); var rol = ctx.Guild.Roles.Values.FirstOrDefault(xr => (cs ? xr.Name : xr.Name.ToLowerInvariant()) == value); return Task.FromResult(rol != null ? Optional.FromValue(rol) : Optional.FromNoValue()); } } /// /// Represents a discord guild converter. /// public class DiscordGuildConverter : IArgumentConverter { /// /// Converts a string. /// /// The string to convert. /// The command context. Task> IArgumentConverter.ConvertAsync(string value, CommandContext ctx) { if (ulong.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var gid)) { return ctx.Client.Guilds.TryGetValue(gid, out var result) ? Task.FromResult(Optional.FromValue(result)) : Task.FromResult(Optional.FromNoValue()); } var cs = ctx.Config.CaseSensitive; if (!cs) value = value?.ToLowerInvariant(); var gld = ctx.Client.Guilds.Values.FirstOrDefault(xg => (cs ? xg.Name : xg.Name.ToLowerInvariant()) == value); return Task.FromResult(gld != null ? Optional.FromValue(gld) : Optional.FromNoValue()); } } /// /// Represents a discord invite converter. /// public class DiscordInviteConverter : IArgumentConverter { /// /// Gets the invite regex. /// - private static Regex InviteRegex { get; } + private static Regex s_inviteRegex { get; } /// /// Initializes a new instance of the class. /// static DiscordInviteConverter() { - InviteRegex = DiscordRegEx.Invite; + s_inviteRegex = DiscordRegEx.Invite; } /// /// Converts a string. /// /// The string to convert. /// The command context. async Task> IArgumentConverter.ConvertAsync(string value, CommandContext ctx) { - var m = InviteRegex.Match(value); + var m = s_inviteRegex.Match(value); if (m.Success) { var result = await ctx.Client.GetInviteByCodeAsync(m.Groups[5].Value).ConfigureAwait(false); var ret = result != null ? Optional.FromValue(result) : Optional.FromNoValue(); return ret; } var cs = ctx.Config.CaseSensitive; if (!cs) value = value?.ToLowerInvariant(); var inv = await ctx.Client.GetInviteByCodeAsync(value); return inv != null ? Optional.FromValue(inv) : Optional.FromNoValue(); } } /// /// Represents a discord message converter. /// public class DiscordMessageConverter : IArgumentConverter { /// /// Gets the message path regex. /// - private static Regex MessagePathRegex { get; } + private static Regex s_messagePathRegex { get; } /// /// Initializes a new instance of the class. /// static DiscordMessageConverter() { - MessagePathRegex = DiscordRegEx.MessageLink; + s_messagePathRegex = DiscordRegEx.MessageLink; } /// /// Converts a string. /// /// The string to convert. /// The command context. async Task> IArgumentConverter.ConvertAsync(string value, CommandContext ctx) { if (string.IsNullOrWhiteSpace(value)) return Optional.FromNoValue(); var msguri = value.StartsWith("<") && value.EndsWith(">") ? value[1..^1] : value; ulong mid; if (Uri.TryCreate(msguri, UriKind.Absolute, out var uri)) { if (uri.Host != "discordapp.com" && uri.Host != "discord.com" && !uri.Host.EndsWith(".discordapp.com") && !uri.Host.EndsWith(".discord.com")) return Optional.FromNoValue(); - var uripath = MessagePathRegex.Match(uri.AbsolutePath); + var uripath = s_messagePathRegex.Match(uri.AbsolutePath); if (!uripath.Success || !ulong.TryParse(uripath.Groups["channel"].Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var cid) || !ulong.TryParse(uripath.Groups["message"].Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out mid)) return Optional.FromNoValue(); var chn = await ctx.Client.GetChannelAsync(cid).ConfigureAwait(false); if (chn == null) return Optional.FromNoValue(); var msg = await chn.GetMessageAsync(mid).ConfigureAwait(false); return msg != null ? Optional.FromValue(msg) : Optional.FromNoValue(); } if (ulong.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out mid)) { var result = await ctx.Channel.GetMessageAsync(mid).ConfigureAwait(false); return result != null ? Optional.FromValue(result) : Optional.FromNoValue(); } return Optional.FromNoValue(); } } /// /// Represents a discord scheduled event converter. /// public class DiscordScheduledEventConverter : IArgumentConverter { /// /// Gets the event regex. /// - private static Regex EventRegex { get; } + private static Regex s_eventRegex { get; } /// /// Initializes a new instance of the class. /// static DiscordScheduledEventConverter() { - EventRegex = DiscordRegEx.Event; + s_eventRegex = DiscordRegEx.Event; } /// /// Converts a string. /// /// The string to convert. /// The command context. async Task> IArgumentConverter.ConvertAsync(string value, CommandContext ctx) { if (string.IsNullOrWhiteSpace(value)) return Optional.FromNoValue(); var msguri = value.StartsWith("<") && value.EndsWith(">") ? value[1..^1] : value; ulong seid; if (Uri.TryCreate(msguri, UriKind.Absolute, out var uri)) { if (uri.Host != "discordapp.com" && uri.Host != "discord.com" && !uri.Host.EndsWith(".discordapp.com") && !uri.Host.EndsWith(".discord.com")) return Optional.FromNoValue(); - var uripath = EventRegex.Match(uri.AbsolutePath); + var uripath = s_eventRegex.Match(uri.AbsolutePath); if (!uripath.Success || !ulong.TryParse(uripath.Groups["guild"].Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var gid) || !ulong.TryParse(uripath.Groups["event"].Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out seid)) return Optional.FromNoValue(); var guild = await ctx.Client.GetGuildAsync(gid).ConfigureAwait(false); if (guild == null) return Optional.FromNoValue(); var ev = await guild.GetScheduledEventAsync(seid).ConfigureAwait(false); return ev != null ? Optional.FromValue(ev) : Optional.FromNoValue(); } if (ulong.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out seid)) { var result = await ctx.Guild.GetScheduledEventAsync(seid).ConfigureAwait(false); return result != null ? Optional.FromValue(result) : Optional.FromNoValue(); } return Optional.FromNoValue(); } } /// /// Represents a discord emoji converter. /// public class DiscordEmojiConverter : IArgumentConverter { /// /// Gets the emote regex. /// - private static Regex EmoteRegex { get; } + private static Regex s_emoteRegex { get; } /// /// Initializes a new instance of the class. /// static DiscordEmojiConverter() { - EmoteRegex = DiscordRegEx.Emoji; + s_emoteRegex = DiscordRegEx.Emoji; } /// /// Converts a string. /// /// The string to convert. /// The command context. Task> IArgumentConverter.ConvertAsync(string value, CommandContext ctx) { if (DiscordEmoji.TryFromUnicode(ctx.Client, value, out var emoji)) { var result = emoji; var ret = Optional.FromValue(result); return Task.FromResult(ret); } - var m = EmoteRegex.Match(value); + var m = s_emoteRegex.Match(value); if (m.Success) { var sid = m.Groups["id"].Value; var name = m.Groups["name"].Value; var anim = m.Groups["animated"].Success; return !ulong.TryParse(sid, NumberStyles.Integer, CultureInfo.InvariantCulture, out var id) ? Task.FromResult(Optional.FromNoValue()) : DiscordEmoji.TryFromGuildEmote(ctx.Client, id, out emoji) ? Task.FromResult(Optional.FromValue(emoji)) : Task.FromResult(Optional.FromValue(new DiscordEmoji { Discord = ctx.Client, Id = id, Name = name, IsAnimated = anim, RequiresColons = true, IsManaged = false })); } return Task.FromResult(Optional.FromNoValue()); } } /// /// Represents a discord color converter. /// public class DiscordColorConverter : IArgumentConverter { /// /// Gets the color regex hex. /// - private static Regex ColorRegexHex { get; } + private static Regex s_colorRegexHex { get; } /// /// Gets the color regex rgb. /// - private static Regex ColorRegexRgb { get; } + private static Regex s_colorRegexRgb { get; } /// /// Initializes a new instance of the class. /// static DiscordColorConverter() { - ColorRegexHex = CommonRegEx.HexColorString; - ColorRegexRgb = CommonRegEx.RgbColorString; + s_colorRegexHex = CommonRegEx.HexColorString; + s_colorRegexRgb = CommonRegEx.RgbColorString; } /// /// Converts a string. /// /// The string to convert. /// The command context. Task> IArgumentConverter.ConvertAsync(string value, CommandContext ctx) { - var m = ColorRegexHex.Match(value); + var m = s_colorRegexHex.Match(value); if (m.Success && int.TryParse(m.Groups[1].Value, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var clr)) return Task.FromResult(Optional.FromValue(clr)); - m = ColorRegexRgb.Match(value); + m = s_colorRegexRgb.Match(value); if (m.Success) { var p1 = byte.TryParse(m.Groups[1].Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var r); var p2 = byte.TryParse(m.Groups[2].Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var g); var p3 = byte.TryParse(m.Groups[3].Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var b); return !(p1 && p2 && p3) ? Task.FromResult(Optional.FromNoValue()) : Task.FromResult(Optional.FromValue(new DiscordColor(r, g, b))); } return Task.FromResult(Optional.FromNoValue()); } } } diff --git a/DisCatSharp.CommandsNext/Converters/TimeConverters.cs b/DisCatSharp.CommandsNext/Converters/TimeConverters.cs index a20356a81..3d479471f 100644 --- a/DisCatSharp.CommandsNext/Converters/TimeConverters.cs +++ b/DisCatSharp.CommandsNext/Converters/TimeConverters.cs @@ -1,145 +1,145 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System; using System.Globalization; using System.Text.RegularExpressions; using System.Threading.Tasks; using DisCatSharp.Common.RegularExpressions; using DisCatSharp.Entities; namespace DisCatSharp.CommandsNext.Converters { /// /// Represents a date time converter. /// public class DateTimeConverter : IArgumentConverter { /// /// Converts a string. /// /// The string to convert. /// The command context. Task> IArgumentConverter.ConvertAsync(string value, CommandContext ctx) { return DateTime.TryParse(value, CultureInfo.InvariantCulture, DateTimeStyles.None, out var result) ? Task.FromResult(new Optional(result)) : Task.FromResult(Optional.FromNoValue()); } } /// /// Represents a date time offset converter. /// public class DateTimeOffsetConverter : IArgumentConverter { /// /// Converts a string. /// /// The string to convert. /// The command context. Task> IArgumentConverter.ConvertAsync(string value, CommandContext ctx) { return DateTimeOffset.TryParse(value, CultureInfo.InvariantCulture, DateTimeStyles.None, out var result) ? Task.FromResult(Optional.FromValue(result)) : Task.FromResult(Optional.FromNoValue()); } } /// /// Represents a time span converter. /// public class TimeSpanConverter : IArgumentConverter { /// /// Gets or sets the time span regex. /// - private static Regex TimeSpanRegex { get; set; } + private static Regex s_timeSpanRegex { get; set; } /// /// Initializes a new instance of the class. /// static TimeSpanConverter() { - TimeSpanRegex = CommonRegEx.TimeSpan; + s_timeSpanRegex = CommonRegEx.TimeSpan; } /// /// Converts a string. /// /// The string to convert. /// The command context. Task> IArgumentConverter.ConvertAsync(string value, CommandContext ctx) { if (value == "0") return Task.FromResult(Optional.FromValue(TimeSpan.Zero)); if (int.TryParse(value, NumberStyles.Number, CultureInfo.InvariantCulture, out _)) return Task.FromResult(Optional.FromNoValue()); if (!ctx.Config.CaseSensitive) value = value.ToLowerInvariant(); if (TimeSpan.TryParse(value, CultureInfo.InvariantCulture, out var result)) return Task.FromResult(Optional.FromValue(result)); var gps = new string[] { "days", "hours", "minutes", "seconds" }; - var mtc = TimeSpanRegex.Match(value); + var mtc = s_timeSpanRegex.Match(value); if (!mtc.Success) return Task.FromResult(Optional.FromNoValue()); var d = 0; var h = 0; var m = 0; var s = 0; foreach (var gp in gps) { var gpc = mtc.Groups[gp].Value; if (string.IsNullOrWhiteSpace(gpc)) continue; var gpt = gpc[^1]; int.TryParse(gpc[0..^1], NumberStyles.Integer, CultureInfo.InvariantCulture, out var val); switch (gpt) { case 'd': d = val; break; case 'h': h = val; break; case 'm': m = val; break; case 's': s = val; break; } } result = new TimeSpan(d, h, m, s); return Task.FromResult(Optional.FromValue(result)); } } } diff --git a/DisCatSharp.Common/Attributes/DateTimeFormatAttribute.cs b/DisCatSharp.Common/Attributes/DateTimeFormatAttribute.cs index a6b0b70c6..9ffb98507 100644 --- a/DisCatSharp.Common/Attributes/DateTimeFormatAttribute.cs +++ b/DisCatSharp.Common/Attributes/DateTimeFormatAttribute.cs @@ -1,132 +1,133 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System; +// ReSharper disable InconsistentNaming namespace DisCatSharp.Common.Serialization { /// /// Defines the format for string-serialized and objects. /// [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false, Inherited = true)] public sealed class DateTimeFormatAttribute : SerializationAttribute { /// /// Gets the ISO 8601 format string of "yyyy-MM-ddTHH:mm:ss.fffzzz". /// public const string FORMAT_ISO_8601 = "yyyy-MM-ddTHH:mm:ss.fffzzz"; /// /// Gets the RFC 1123 format string of "R". /// public const string FORMAT_RFC_1123 = "R"; /// /// Gets the general long format. /// public const string FORMAT_LONG = "G"; /// /// Gets the general short format. /// public const string FORMAT_SHORT = "g"; /// /// Gets the custom datetime format string to use. /// public string Format { get; } /// /// Gets the predefined datetime format kind. /// public DateTimeFormatKind Kind { get; } /// /// Specifies a predefined format to use. /// /// Predefined format kind to use. public DateTimeFormatAttribute(DateTimeFormatKind kind) { if (kind < 0 || kind > DateTimeFormatKind.InvariantLocaleShort) throw new ArgumentOutOfRangeException(nameof(kind), "Specified format kind is not legal or supported."); this.Kind = kind; this.Format = null; } /// /// Specifies a custom format to use. /// See https://docs.microsoft.com/en-us/dotnet/standard/base-types/custom-date-and-time-format-strings for more details. /// /// Custom format string to use. public DateTimeFormatAttribute(string format) { if (string.IsNullOrWhiteSpace(format)) throw new ArgumentNullException(nameof(format), "Specified format cannot be null or empty."); this.Kind = DateTimeFormatKind.Custom; this.Format = format; } } /// /// Defines which built-in format to use for for and serialization. /// See https://docs.microsoft.com/en-us/dotnet/standard/base-types/custom-date-and-time-format-strings and https://docs.microsoft.com/en-us/dotnet/standard/base-types/standard-date-and-time-format-strings for more details. /// public enum DateTimeFormatKind : int { /// /// Specifies ISO 8601 format, which is equivalent to .NET format string of "yyyy-MM-ddTHH:mm:ss.fffzzz". /// ISO8601 = 0, /// /// Specifies RFC 1123 format, which is equivalent to .NET format string of "R". /// RFC1123 = 1, /// /// Specifies a format defined by , with a format string of "G". This format is not recommended for portability reasons. /// CurrentLocaleLong = 2, /// /// Specifies a format defined by , with a format string of "g". This format is not recommended for portability reasons. /// CurrentLocaleShort = 3, /// /// Specifies a format defined by , with a format string of "G". /// InvariantLocaleLong = 4, /// /// Specifies a format defined by , with a format string of "g". /// InvariantLocaleShort = 5, /// /// Specifies a custom format. This value is not usable directly. /// Custom = int.MaxValue } } diff --git a/DisCatSharp.Common/Attributes/TimeSpanFormatAttribute.cs b/DisCatSharp.Common/Attributes/TimeSpanFormatAttribute.cs index 4c62247a0..b46d57755 100644 --- a/DisCatSharp.Common/Attributes/TimeSpanFormatAttribute.cs +++ b/DisCatSharp.Common/Attributes/TimeSpanFormatAttribute.cs @@ -1,132 +1,133 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System; +// ReSharper disable InconsistentNaming namespace DisCatSharp.Common.Serialization { /// /// Defines the format for string-serialized objects. /// [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false, Inherited = true)] public sealed class TimeSpanFormatAttribute : SerializationAttribute { /// /// Gets the ISO 8601 format string of @"ddThh\:mm\:ss\.fff". /// public const string FORMAT_ISO_8601 = @"ddThh\:mm\:ss\.fff"; /// /// Gets the constant format. /// public const string FORMAT_CONSTANT = "c"; /// /// Gets the general long format. /// public const string FORMAT_LONG = "G"; /// /// Gets the general short format. /// public const string FORMAT_SHORT = "g"; /// /// Gets the custom datetime format string to use. /// public string Format { get; } /// /// Gets the predefined datetime format kind. /// public TimeSpanFormatKind Kind { get; } /// /// Specifies a predefined format to use. /// /// Predefined format kind to use. public TimeSpanFormatAttribute(TimeSpanFormatKind kind) { if (kind < 0 || kind > TimeSpanFormatKind.InvariantLocaleShort) throw new ArgumentOutOfRangeException(nameof(kind), "Specified format kind is not legal or supported."); this.Kind = kind; this.Format = null; } /// /// Specifies a custom format to use. /// See https://docs.microsoft.com/en-us/dotnet/standard/base-types/custom-timespan-format-strings for more details. /// /// Custom format string to use. public TimeSpanFormatAttribute(string format) { if (string.IsNullOrWhiteSpace(format)) throw new ArgumentNullException(nameof(format), "Specified format cannot be null or empty."); this.Kind = TimeSpanFormatKind.Custom; this.Format = format; } } /// /// Defines which built-in format to use for serialization. /// See https://docs.microsoft.com/en-us/dotnet/standard/base-types/standard-timespan-format-strings and https://docs.microsoft.com/en-us/dotnet/standard/base-types/custom-timespan-format-strings for more details. /// public enum TimeSpanFormatKind : int { /// /// Specifies ISO 8601-like time format, which is equivalent to .NET format string of @"ddThh\:mm\:ss\.fff". /// ISO8601 = 0, /// /// Specifies a format defined by , with a format string of "c". /// InvariantConstant = 1, /// /// Specifies a format defined by , with a format string of "G". This format is not recommended for portability reasons. /// CurrentLocaleLong = 2, /// /// Specifies a format defined by , with a format string of "g". This format is not recommended for portability reasons. /// CurrentLocaleShort = 3, /// /// Specifies a format defined by , with a format string of "G". This format is not recommended for portability reasons. /// InvariantLocaleLong = 4, /// /// Specifies a format defined by , with a format string of "g". This format is not recommended for portability reasons. /// InvariantLocaleShort = 5, /// /// Specifies a custom format. This value is not usable directly. /// Custom = int.MaxValue } } diff --git a/DisCatSharp.Common/Types/SecureRandom.cs b/DisCatSharp.Common/Types/SecureRandom.cs index 309c69b6f..d5dd2bdc4 100644 --- a/DisCatSharp.Common/Types/SecureRandom.cs +++ b/DisCatSharp.Common/Types/SecureRandom.cs @@ -1,348 +1,348 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System; using System.Buffers; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Security.Cryptography; namespace DisCatSharp.Common { /// /// Provides a cryptographically-secure pseudorandom number generator (CSPRNG) implementation compatible with . /// public sealed class SecureRandom : Random, IDisposable { /// /// Gets the r n g. /// - private RandomNumberGenerator RNG { get; } = RandomNumberGenerator.Create(); + private RandomNumberGenerator Rng { get; } = RandomNumberGenerator.Create(); private volatile bool _isDisposed = false; /// /// Creates a new instance of . /// public SecureRandom() { } /// /// Finalizes this instance by disposing it. /// ~SecureRandom() { this.Dispose(); } /// /// Fills a supplied buffer with random bytes. /// /// Buffer to fill with random bytes. public void GetBytes(byte[] buffer) { - this.RNG.GetBytes(buffer); + this.Rng.GetBytes(buffer); } /// /// Fills a supplied buffer with random nonzero bytes. /// /// Buffer to fill with random nonzero bytes. public void GetNonZeroBytes(byte[] buffer) { - this.RNG.GetNonZeroBytes(buffer); + this.Rng.GetNonZeroBytes(buffer); } /// /// Fills a supplied memory region with random bytes. /// /// Memmory region to fill with random bytes. public void GetBytes(Span buffer) { #if NETCOREAPP this.RNG.GetBytes(buffer); #else var buff = ArrayPool.Shared.Rent(buffer.Length); try { var buffSpan = buff.AsSpan(0, buffer.Length); - this.RNG.GetBytes(buff); + this.Rng.GetBytes(buff); buffSpan.CopyTo(buffer); } finally { ArrayPool.Shared.Return(buff); } #endif } /// /// Fills a supplied memory region with random nonzero bytes. /// /// Memmory region to fill with random nonzero bytes. public void GetNonZeroBytes(Span buffer) { #if NETCOREAPP this.RNG.GetNonZeroBytes(buffer); #else var buff = ArrayPool.Shared.Rent(buffer.Length); try { var buffSpan = buff.AsSpan(0, buffer.Length); - this.RNG.GetNonZeroBytes(buff); + this.Rng.GetNonZeroBytes(buff); buffSpan.CopyTo(buffer); } finally { ArrayPool.Shared.Return(buff); } #endif } /// /// Generates a signed 8-bit integer within specified range. /// /// Minimum value to generate. Defaults to 0. /// Maximum value to generate. Defaults to . /// Generated random value. public sbyte GetInt8(sbyte min = 0, sbyte max = sbyte.MaxValue) { if (max <= min) throw new ArgumentException("Maximum needs to be greater than minimum.", nameof(max)); var offset = (sbyte)(min < 0 ? -min : 0); min += offset; max += offset; return (sbyte)(Math.Abs(this.Generate()) % (max - min) + min - offset); } /// /// Generates a unsigned 8-bit integer within specified range. /// /// Minimum value to generate. Defaults to 0. /// Maximum value to generate. Defaults to . /// Generated random value. public byte GetUInt8(byte min = 0, byte max = byte.MaxValue) { if (max <= min) throw new ArgumentException("Maximum needs to be greater than minimum.", nameof(max)); return (byte)(this.Generate() % (max - min) + min); } /// /// Generates a signed 16-bit integer within specified range. /// /// Minimum value to generate. Defaults to 0. /// Maximum value to generate. Defaults to . /// Generated random value. public short GetInt16(short min = 0, short max = short.MaxValue) { if (max <= min) throw new ArgumentException("Maximum needs to be greater than minimum.", nameof(max)); var offset = (short)(min < 0 ? -min : 0); min += offset; max += offset; return (short)(Math.Abs(this.Generate()) % (max - min) + min - offset); } /// /// Generates a unsigned 16-bit integer within specified range. /// /// Minimum value to generate. Defaults to 0. /// Maximum value to generate. Defaults to . /// Generated random value. public ushort GetUInt16(ushort min = 0, ushort max = ushort.MaxValue) { if (max <= min) throw new ArgumentException("Maximum needs to be greater than minimum.", nameof(max)); return (ushort)(this.Generate() % (max - min) + min); } /// /// Generates a signed 32-bit integer within specified range. /// /// Minimum value to generate. Defaults to 0. /// Maximum value to generate. Defaults to . /// Generated random value. public int GetInt32(int min = 0, int max = int.MaxValue) { if (max <= min) throw new ArgumentException("Maximum needs to be greater than minimum.", nameof(max)); var offset = min < 0 ? -min : 0; min += offset; max += offset; return Math.Abs(this.Generate()) % (max - min) + min - offset; } /// /// Generates a unsigned 32-bit integer within specified range. /// /// Minimum value to generate. Defaults to 0. /// Maximum value to generate. Defaults to . /// Generated random value. public uint GetUInt32(uint min = 0, uint max = uint.MaxValue) { if (max <= min) throw new ArgumentException("Maximum needs to be greater than minimum.", nameof(max)); return this.Generate() % (max - min) + min; } /// /// Generates a signed 64-bit integer within specified range. /// /// Minimum value to generate. Defaults to 0. /// Maximum value to generate. Defaults to . /// Generated random value. public long GetInt64(long min = 0, long max = long.MaxValue) { if (max <= min) throw new ArgumentException("Maximum needs to be greater than minimum.", nameof(max)); var offset = min < 0 ? -min : 0; min += offset; max += offset; return Math.Abs(this.Generate()) % (max - min) + min - offset; } /// /// Generates a unsigned 64-bit integer within specified range. /// /// Minimum value to generate. Defaults to 0. /// Maximum value to generate. Defaults to . /// Generated random value. public ulong GetUInt64(ulong min = 0, ulong max = ulong.MaxValue) { if (max <= min) throw new ArgumentException("Maximum needs to be greater than minimum.", nameof(max)); return this.Generate() % (max - min) + min; } /// /// Generates a 32-bit floating-point number between 0.0 and 1.0. /// /// Generated 32-bit floating-point number. public float GetSingle() { var (i1, i2) = ((float)this.GetInt32(), (float)this.GetInt32()); return i1 / i2 % 1.0F; } /// /// Generates a 64-bit floating-point number between 0.0 and 1.0. /// /// Generated 64-bit floating-point number. public double GetDouble() { var (i1, i2) = ((double)this.GetInt64(), (double)this.GetInt64()); return i1 / i2 % 1.0; } /// /// Generates a 32-bit integer between 0 and . Upper end exclusive. /// /// Generated 32-bit integer. public override int Next() => this.GetInt32(); /// /// Generates a 32-bit integer between 0 and . Upper end exclusive. /// /// Maximum value of the generated integer. /// Generated 32-bit integer. public override int Next(int maxValue) => this.GetInt32(0, maxValue); /// /// Generates a 32-bit integer between and . Upper end exclusive. /// /// Minimum value of the generate integer. /// Maximum value of the generated integer. /// Generated 32-bit integer. public override int Next(int minValue, int maxValue) => this.GetInt32(minValue, maxValue); /// /// Generates a 64-bit floating-point number between 0.0 and 1.0. Upper end exclusive. /// /// Generated 64-bit floating-point number. public override double NextDouble() => this.GetDouble(); /// /// Fills specified buffer with random bytes. /// /// Buffer to fill with bytes. public override void NextBytes(byte[] buffer) => this.GetBytes(buffer); /// /// Fills specified memory region with random bytes. /// /// Memory region to fill with bytes. #if NETCOREAPP - override + override #endif public new void NextBytes(Span buffer) => this.GetBytes(buffer); /// /// Disposes this instance and its resources. /// public void Dispose() { if (this._isDisposed) return; this._isDisposed = true; - this.RNG.Dispose(); + this.Rng.Dispose(); } /// /// Generates a random 64-bit floating-point number between 0.0 and 1.0. Upper end exclusive. /// /// Generated 64-bit floating-point number. protected override double Sample() => this.GetDouble(); /// /// Generates the. /// /// A T. private T Generate() where T : struct { var size = Unsafe.SizeOf(); Span buff = stackalloc byte[size]; this.GetBytes(buff); return MemoryMarshal.Read(buff); } } } diff --git a/DisCatSharp.Common/Types/Serialization/ComplexDecomposer.cs b/DisCatSharp.Common/Types/Serialization/ComplexDecomposer.cs index a5fadf46b..37e51e57b 100644 --- a/DisCatSharp.Common/Types/Serialization/ComplexDecomposer.cs +++ b/DisCatSharp.Common/Types/Serialization/ComplexDecomposer.cs @@ -1,116 +1,116 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System; using System.Collections.Generic; using System.Linq; using System.Numerics; namespace DisCatSharp.Common.Serialization { /// /// Decomposes numbers into tuples (arrays of 2). /// public sealed class ComplexDecomposer : IDecomposer { /// /// Gets the t complex. /// - private static Type TComplex { get; } = typeof(Complex); + private static Type s_complex { get; } = typeof(Complex); /// /// Gets the t double array. /// - private static Type TDoubleArray { get; } = typeof(double[]); + private static Type s_doubleArray { get; } = typeof(double[]); /// /// Gets the t double enumerable. /// - private static Type TDoubleEnumerable { get; } = typeof(IEnumerable); + private static Type s_doubleEnumerable { get; } = typeof(IEnumerable); /// /// Gets the t object array. /// - private static Type TObjectArray { get; } = typeof(object[]); + private static Type s_objectArray { get; } = typeof(object[]); /// /// Gets the t object enumerable. /// - private static Type TObjectEnumerable { get; } = typeof(IEnumerable); + private static Type s_objectEnumerable { get; } = typeof(IEnumerable); /// public bool CanDecompose(Type t) - => t == TComplex; + => t == s_complex; /// public bool CanRecompose(Type t) - => t == TDoubleArray - || t == TObjectArray - || TDoubleEnumerable.IsAssignableFrom(t) - || TObjectEnumerable.IsAssignableFrom(t); + => t == s_doubleArray + || t == s_objectArray + || s_doubleEnumerable.IsAssignableFrom(t) + || s_objectEnumerable.IsAssignableFrom(t); /// public bool TryDecompose(object obj, Type tobj, out object decomposed, out Type tdecomposed) { decomposed = null; - tdecomposed = TDoubleArray; + tdecomposed = s_doubleArray; - if (tobj != TComplex || obj is not Complex c) + if (tobj != s_complex || obj is not Complex c) return false; decomposed = new[] { c.Real, c.Imaginary }; return true; } /// public bool TryRecompose(object obj, Type tobj, Type trecomposed, out object recomposed) { recomposed = null; - if (trecomposed != TComplex) + if (trecomposed != s_complex) return false; // ie - if (TDoubleEnumerable.IsAssignableFrom(tobj) && obj is IEnumerable ied) + if (s_doubleEnumerable.IsAssignableFrom(tobj) && obj is IEnumerable ied) { if (ied.Count() < 2) return false; var (real, imag) = ied.FirstTwoOrDefault(); recomposed = new Complex(real, imag); return true; } // ie - if (TObjectEnumerable.IsAssignableFrom(tobj) && obj is IEnumerable ieo) + if (s_objectEnumerable.IsAssignableFrom(tobj) && obj is IEnumerable ieo) { if (ieo.Count() < 2) return false; var (real, imag) = ieo.FirstTwoOrDefault(); if (real is not double dreal || imag is not double dimag) return false; recomposed = new Complex(dreal, dimag); return true; } return false; } } } diff --git a/DisCatSharp.Interactivity/EventHandling/Components/ComponentPaginator.cs b/DisCatSharp.Interactivity/EventHandling/Components/ComponentPaginator.cs index be9e6a989..6e3aa7b06 100644 --- a/DisCatSharp.Interactivity/EventHandling/Components/ComponentPaginator.cs +++ b/DisCatSharp.Interactivity/EventHandling/Components/ComponentPaginator.cs @@ -1,181 +1,181 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System; using System.Collections.Generic; using System.Threading.Tasks; using DisCatSharp.Entities; using DisCatSharp.EventArgs; using DisCatSharp.Interactivity.Enums; using Microsoft.Extensions.Logging; namespace DisCatSharp.Interactivity.EventHandling { /// /// The component paginator. /// internal class ComponentPaginator : IPaginator { private readonly DiscordClient _client; private readonly InteractivityConfiguration _config; private readonly DiscordMessageBuilder _builder = new(); private readonly Dictionary _requests = new(); /// /// Initializes a new instance of the class. /// /// The client. /// The config. public ComponentPaginator(DiscordClient client, InteractivityConfiguration config) { this._client = client; this._client.ComponentInteractionCreated += this.Handle; this._config = config; } /// /// Does the pagination async. /// /// The request. public async Task DoPaginationAsync(IPaginationRequest request) { var id = (await request.GetMessageAsync().ConfigureAwait(false)).Id; this._requests.Add(id, request); try { var tcs = await request.GetTaskCompletionSourceAsync().ConfigureAwait(false); await tcs.Task.ConfigureAwait(false); } catch (Exception ex) { this._client.Logger.LogError(InteractivityEvents.InteractivityPaginationError, ex, "There was an exception while paginating."); } finally { this._requests.Remove(id); try { await request.DoCleanupAsync().ConfigureAwait(false); } catch (Exception ex) { this._client.Logger.LogError(InteractivityEvents.InteractivityPaginationError, ex, "There was an exception while cleaning up pagination."); } } } /// /// Disposes the paginator. /// public void Dispose() => this._client.ComponentInteractionCreated -= this.Handle; /// /// Handles the pagination event. /// /// The client. /// The event arguments. private async Task Handle(DiscordClient _, ComponentInteractionCreateEventArgs e) { if (e.Interaction.Type == InteractionType.ModalSubmit) return; if (!this._requests.TryGetValue(e.Message.Id, out var req)) return; if (this._config.AckPaginationButtons) { e.Handled = true; await e.Interaction.CreateResponseAsync(InteractionResponseType.DeferredMessageUpdate).ConfigureAwait(false); } if (await req.GetUserAsync().ConfigureAwait(false) != e.User) { if (this._config.ResponseBehavior is InteractionResponseBehavior.Respond) await e.Interaction.CreateFollowupMessageAsync(new() { Content = this._config.ResponseMessage, IsEphemeral = true }).ConfigureAwait(false); return; } if (req is InteractionPaginationRequest ipr) - ipr.RegenerateCTS(e.Interaction); // Necessary to ensure we don't prematurely yeet the CTS // + ipr.RegenerateCts(e.Interaction); // Necessary to ensure we don't prematurely yeet the CTS // await this.HandlePaginationAsync(req, e).ConfigureAwait(false); } /// /// Handles the pagination async. /// /// The request. /// The arguments. private async Task HandlePaginationAsync(IPaginationRequest request, ComponentInteractionCreateEventArgs args) { var buttons = this._config.PaginationButtons; var msg = await request.GetMessageAsync().ConfigureAwait(false); var id = args.Id; var tcs = await request.GetTaskCompletionSourceAsync().ConfigureAwait(false); #pragma warning disable CS8846 // The switch expression does not handle all possible values of its input type (it is not exhaustive). var paginationTask = id switch #pragma warning restore CS8846 // The switch expression does not handle all possible values of its input type (it is not exhaustive). { _ when id == buttons.SkipLeft.CustomId => request.SkipLeftAsync(), _ when id == buttons.SkipRight.CustomId => request.SkipRightAsync(), _ when id == buttons.Stop.CustomId => Task.FromResult(tcs.TrySetResult(true)), _ when id == buttons.Left.CustomId => request.PreviousPageAsync(), _ when id == buttons.Right.CustomId => request.NextPageAsync(), }; await paginationTask.ConfigureAwait(false); if (id == buttons.Stop.CustomId) return; var page = await request.GetPageAsync().ConfigureAwait(false); var bts = await request.GetButtonsAsync().ConfigureAwait(false); if (request is InteractionPaginationRequest ipr) { var builder = new DiscordWebhookBuilder() .WithContent(page.Content) .AddEmbed(page.Embed) .AddComponents(bts); await args.Interaction.EditOriginalResponseAsync(builder).ConfigureAwait(false); return; } this._builder.Clear(); this._builder .WithContent(page.Content) .AddEmbed(page.Embed) .AddComponents(bts); await this._builder.ModifyAsync(msg).ConfigureAwait(false); } } } diff --git a/DisCatSharp.Interactivity/EventHandling/Components/Requests/InteractionPaginationRequest.cs b/DisCatSharp.Interactivity/EventHandling/Components/Requests/InteractionPaginationRequest.cs index fb225e55d..b524955ed 100644 --- a/DisCatSharp.Interactivity/EventHandling/Components/Requests/InteractionPaginationRequest.cs +++ b/DisCatSharp.Interactivity/EventHandling/Components/Requests/InteractionPaginationRequest.cs @@ -1,264 +1,264 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using DisCatSharp.Entities; using DisCatSharp.Interactivity.Enums; namespace DisCatSharp.Interactivity.EventHandling { /// /// The interaction pagination request. /// internal class InteractionPaginationRequest : IPaginationRequest { private int _index; private readonly List _pages = new(); private readonly TaskCompletionSource _tcs = new(); private DiscordInteraction _lastInteraction; private CancellationTokenSource _interactionCts; private readonly CancellationToken _token; private readonly DiscordUser _user; private readonly DiscordMessage _message; private readonly PaginationButtons _buttons; private readonly PaginationBehaviour _wrapBehavior; private readonly ButtonPaginationBehavior _behaviorBehavior; /// /// Initializes a new instance of the class. /// /// The interaction. /// The message. /// The user. /// The behavior. /// The behavior behavior. /// The buttons. /// The pages. /// The token. public InteractionPaginationRequest(DiscordInteraction interaction, DiscordMessage message, DiscordUser user, PaginationBehaviour behavior, ButtonPaginationBehavior behaviorBehavior, PaginationButtons buttons, IEnumerable pages, CancellationToken token) { this._user = user; this._token = token; this._buttons = new(buttons); this._message = message; this._wrapBehavior = behavior; this._behaviorBehavior = behaviorBehavior; this._pages.AddRange(pages); - this.RegenerateCTS(interaction); + this.RegenerateCts(interaction); this._token.Register(() => this._tcs.TrySetResult(false)); } /// /// Gets the page count. /// public int PageCount => this._pages.Count; /// /// Regenerates the cts. /// /// The interaction. - internal void RegenerateCTS(DiscordInteraction interaction) + internal void RegenerateCts(DiscordInteraction interaction) { this._interactionCts?.Dispose(); this._lastInteraction = interaction; this._interactionCts = new(TimeSpan.FromSeconds((60 * 15) - 5)); this._interactionCts.Token.Register(() => this._tcs.TrySetResult(false)); } /// /// Gets the page. /// public Task GetPageAsync() { var page = Task.FromResult(this._pages[this._index]); if (this.PageCount is 1) { this._buttons.ButtonArray.Select(b => b.Disable()); this._buttons.Stop.Enable(); return page; } if (this._wrapBehavior is PaginationBehaviour.WrapAround) return page; this._buttons.SkipLeft.Disabled = this._index < 2; this._buttons.Left.Disabled = this._index < 1; this._buttons.Right.Disabled = this._index == this.PageCount - 1; this._buttons.SkipRight.Disabled = this._index >= this.PageCount - 2; return page; } /// /// Skips the left page. /// public Task SkipLeftAsync() { if (this._wrapBehavior is PaginationBehaviour.WrapAround) { this._index = this._index is 0 ? this._pages.Count - 1 : 0; return Task.CompletedTask; } this._index = 0; return Task.CompletedTask; } /// /// Skips the right page. /// public Task SkipRightAsync() { if (this._wrapBehavior is PaginationBehaviour.WrapAround) { this._index = this._index == this.PageCount - 1 ? 0 : this.PageCount - 1; return Task.CompletedTask; } this._index = this._pages.Count - 1; return Task.CompletedTask; } /// /// Gets the next page. /// /// A Task. public Task NextPageAsync() { this._index++; if (this._wrapBehavior is PaginationBehaviour.WrapAround) { if (this._index >= this.PageCount) this._index = 0; return Task.CompletedTask; } this._index = Math.Min(this._index, this.PageCount - 1); return Task.CompletedTask; } /// /// Gets the previous page. /// public Task PreviousPageAsync() { this._index--; if (this._wrapBehavior is PaginationBehaviour.WrapAround) { if (this._index is -1) this._index = this._pages.Count - 1; return Task.CompletedTask; } this._index = Math.Max(this._index, 0); return Task.CompletedTask; } /// /// Gets the emojis. /// public Task GetEmojisAsync() => Task.FromException(new NotSupportedException("Emojis aren't supported for this request.")); /// /// Gets the buttons. /// public Task> GetButtonsAsync() => Task.FromResult((IEnumerable)this._buttons.ButtonArray); /// /// Gets the message. /// public Task GetMessageAsync() => Task.FromResult(this._message); /// /// Gets the user. /// public Task GetUserAsync() => Task.FromResult(this._user); /// /// Gets the task completion source. /// public Task> GetTaskCompletionSourceAsync() => Task.FromResult(this._tcs); /// /// Cleanup. /// public async Task DoCleanupAsync() { switch (this._behaviorBehavior) { case ButtonPaginationBehavior.Disable: var buttons = this._buttons.ButtonArray .Select(b => new DiscordButtonComponent(b)) .Select(b => b.Disable()); var builder = new DiscordWebhookBuilder() .WithContent(this._pages[this._index].Content) .AddEmbed(this._pages[this._index].Embed) .AddComponents(buttons); await this._lastInteraction.EditOriginalResponseAsync(builder).ConfigureAwait(false); break; case ButtonPaginationBehavior.DeleteButtons: builder = new DiscordWebhookBuilder() .WithContent(this._pages[this._index].Content) .AddEmbed(this._pages[this._index].Embed); await this._lastInteraction.EditOriginalResponseAsync(builder).ConfigureAwait(false); break; case ButtonPaginationBehavior.DeleteMessage: await this._lastInteraction.DeleteOriginalResponseAsync().ConfigureAwait(false); break; case ButtonPaginationBehavior.Ignore: break; } } } } diff --git a/DisCatSharp.Interactivity/EventHandling/Components/Requests/ModalMatchRequest.cs b/DisCatSharp.Interactivity/EventHandling/Components/Requests/ModalMatchRequest.cs index 92f727922..9266e80ac 100644 --- a/DisCatSharp.Interactivity/EventHandling/Components/Requests/ModalMatchRequest.cs +++ b/DisCatSharp.Interactivity/EventHandling/Components/Requests/ModalMatchRequest.cs @@ -1,68 +1,68 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System; using System.Threading; using System.Threading.Tasks; using DisCatSharp.EventArgs; namespace DisCatSharp.Interactivity.EventHandling { /// /// Represents a match that is being waited for. /// internal class ModalMatchRequest { /// /// The id to wait on. This should be uniquely formatted to avoid collisions. /// public string CustomId { get; private set; } /// /// The completion source that represents the result of the match. /// public TaskCompletionSource Tcs { get; private set; } = new(); - protected readonly CancellationToken _cancellation; - protected readonly Func _predicate; + protected readonly CancellationToken Cancellation; + protected readonly Func Predicate; /// /// Initializes a new instance of the class. /// /// The custom id. /// The predicate. /// The cancellation token. - public ModalMatchRequest(string custom_id, Func predicate, CancellationToken cancellation) + public ModalMatchRequest(string customId, Func predicate, CancellationToken cancellation) { - this.CustomId = custom_id; - this._predicate = predicate; - this._cancellation = cancellation; - this._cancellation.Register(() => this.Tcs.TrySetResult(null)); // TrySetCancelled would probably be better but I digress ~Velvet // + this.CustomId = customId; + this.Predicate = predicate; + this.Cancellation = cancellation; + this.Cancellation.Register(() => this.Tcs.TrySetResult(null)); // TrySetCancelled would probably be better but I digress ~Velvet // } /// /// Whether it is a match. /// /// The arguments. - public bool IsMatch(ComponentInteractionCreateEventArgs args) => this._predicate(args); + public bool IsMatch(ComponentInteractionCreateEventArgs args) => this.Predicate(args); } } diff --git a/DisCatSharp.Interactivity/EventHandling/EventWaiter.cs b/DisCatSharp.Interactivity/EventHandling/EventWaiter.cs index a5b1816c2..663f3e6a0 100644 --- a/DisCatSharp.Interactivity/EventHandling/EventWaiter.cs +++ b/DisCatSharp.Interactivity/EventHandling/EventWaiter.cs @@ -1,171 +1,171 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using System.Reflection; using System.Threading.Tasks; using ConcurrentCollections; using DisCatSharp.Common.Utilities; using Microsoft.Extensions.Logging; namespace DisCatSharp.Interactivity.EventHandling { /// /// Eventwaiter is a class that serves as a layer between the InteractivityExtension /// and the DiscordClient to listen to an event and check for matches to a predicate. /// /// internal class EventWaiter : IDisposable where T : AsyncEventArgs { DiscordClient _client; AsyncEvent _event; AsyncEventHandler _handler; ConcurrentHashSet> _matchrequests; ConcurrentHashSet> _collectrequests; - bool disposed = false; + bool _disposed = false; /// /// Creates a new Eventwaiter object. /// /// Your DiscordClient public EventWaiter(DiscordClient client) { this._client = client; var tinfo = this._client.GetType().GetTypeInfo(); var handler = tinfo.DeclaredFields.First(x => x.FieldType == typeof(AsyncEvent)); this._matchrequests = new ConcurrentHashSet>(); this._collectrequests = new ConcurrentHashSet>(); this._event = (AsyncEvent)handler.GetValue(this._client); this._handler = new AsyncEventHandler(this.HandleEvent); this._event.Register(this._handler); } /// /// Waits for a match to a specific request, else returns null. /// /// Request to match /// public async Task WaitForMatchAsync(MatchRequest request) { T result = null; this._matchrequests.Add(request); try { - result = await request._tcs.Task.ConfigureAwait(false); + result = await request.Tcs.Task.ConfigureAwait(false); } catch (Exception ex) { this._client.Logger.LogError(InteractivityEvents.InteractivityWaitError, ex, "An exception occurred while waiting for {0}", typeof(T).Name); } finally { request.Dispose(); this._matchrequests.TryRemove(request); } return result; } /// /// Collects the matches async. /// /// The request. public async Task> CollectMatchesAsync(CollectRequest request) { ReadOnlyCollection result = null; this._collectrequests.Add(request); try { - await request._tcs.Task.ConfigureAwait(false); + await request.Tcs.Task.ConfigureAwait(false); } catch (Exception ex) { this._client.Logger.LogError(InteractivityEvents.InteractivityWaitError, ex, "An exception occurred while collecting from {0}", typeof(T).Name); } finally { - result = new ReadOnlyCollection(new HashSet(request._collected).ToList()); + result = new ReadOnlyCollection(new HashSet(request.Collected).ToList()); request.Dispose(); this._collectrequests.TryRemove(request); } return result; } /// /// Handles the event. /// /// The client. /// The event args. private Task HandleEvent(DiscordClient client, T eventargs) { - if (!this.disposed) + if (!this._disposed) { foreach (var req in this._matchrequests) { - if (req._predicate(eventargs)) + if (req.Predicate(eventargs)) { - req._tcs.TrySetResult(eventargs); + req.Tcs.TrySetResult(eventargs); } } foreach (var req in this._collectrequests) { - if (req._predicate(eventargs)) + if (req.Predicate(eventargs)) { - req._collected.Add(eventargs); + req.Collected.Add(eventargs); } } } return Task.CompletedTask; } ~EventWaiter() { this.Dispose(); } /// /// Disposes this EventWaiter /// public void Dispose() { - this.disposed = true; + this._disposed = true; if (this._event != null) this._event.Unregister(this._handler); this._event = null; this._handler = null; this._client = null; if (this._matchrequests != null) this._matchrequests.Clear(); if (this._collectrequests != null) this._collectrequests.Clear(); this._matchrequests = null; this._collectrequests = null; } } } diff --git a/DisCatSharp.Interactivity/EventHandling/Poller.cs b/DisCatSharp.Interactivity/EventHandling/Poller.cs index f00ce0931..9b008d4da 100644 --- a/DisCatSharp.Interactivity/EventHandling/Poller.cs +++ b/DisCatSharp.Interactivity/EventHandling/Poller.cs @@ -1,174 +1,174 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using System.Threading.Tasks; using ConcurrentCollections; using DisCatSharp.EventArgs; using Microsoft.Extensions.Logging; namespace DisCatSharp.Interactivity.EventHandling { /// /// The poller. /// internal class Poller { DiscordClient _client; ConcurrentHashSet _requests; /// /// Creates a new Eventwaiter object. /// /// Your DiscordClient public Poller(DiscordClient client) { this._client = client; this._requests = new ConcurrentHashSet(); this._client.MessageReactionAdded += this.HandleReactionAdd; this._client.MessageReactionRemoved += this.HandleReactionRemove; this._client.MessageReactionsCleared += this.HandleReactionClear; } /// /// Dos the poll async. /// /// The request. /// A Task. public async Task> DoPollAsync(PollRequest request) { ReadOnlyCollection result = null; this._requests.Add(request); try { - await request._tcs.Task.ConfigureAwait(false); + await request.Tcs.Task.ConfigureAwait(false); } catch (Exception ex) { this._client.Logger.LogError(InteractivityEvents.InteractivityPollError, ex, "Exception occurred while polling"); } finally { - result = new ReadOnlyCollection(new HashSet(request._collected).ToList()); + result = new ReadOnlyCollection(new HashSet(request.Collected).ToList()); request.Dispose(); this._requests.TryRemove(request); } return result; } /// /// Handles the reaction add. /// /// The client. /// The eventargs. /// A Task. private Task HandleReactionAdd(DiscordClient client, MessageReactionAddEventArgs eventargs) { if (this._requests.Count == 0) return Task.CompletedTask; _ = Task.Run(async () => { foreach (var req in this._requests) { // match message - if (req._message.Id == eventargs.Message.Id && req._message.ChannelId == eventargs.Channel.Id) + if (req.Message.Id == eventargs.Message.Id && req.Message.ChannelId == eventargs.Channel.Id) { - if (req._emojis.Contains(eventargs.Emoji) && !req._collected.Any(x => x.Voted.Contains(eventargs.User))) + if (req.Emojis.Contains(eventargs.Emoji) && !req.Collected.Any(x => x.Voted.Contains(eventargs.User))) { if (eventargs.User.Id != this._client.CurrentUser.Id) req.AddReaction(eventargs.Emoji, eventargs.User); } else { var member = await eventargs.Channel.Guild.GetMemberAsync(client.CurrentUser.Id).ConfigureAwait(false); if (eventargs.Channel.PermissionsFor(member).HasPermission(Permissions.ManageMessages)) await eventargs.Message.DeleteReactionAsync(eventargs.Emoji, eventargs.User).ConfigureAwait(false); } } } }); return Task.CompletedTask; } /// /// Handles the reaction remove. /// /// The client. /// The eventargs. /// A Task. private Task HandleReactionRemove(DiscordClient client, MessageReactionRemoveEventArgs eventargs) { foreach (var req in this._requests) { // match message - if (req._message.Id == eventargs.Message.Id && req._message.ChannelId == eventargs.Channel.Id) + if (req.Message.Id == eventargs.Message.Id && req.Message.ChannelId == eventargs.Channel.Id) { if (eventargs.User.Id != this._client.CurrentUser.Id) req.RemoveReaction(eventargs.Emoji, eventargs.User); } } return Task.CompletedTask; } /// /// Handles the reaction clear. /// /// The client. /// The eventargs. /// A Task. private Task HandleReactionClear(DiscordClient client, MessageReactionsClearEventArgs eventargs) { foreach (var req in this._requests) { // match message - if (req._message.Id == eventargs.Message.Id && req._message.ChannelId == eventargs.Channel.Id) + if (req.Message.Id == eventargs.Message.Id && req.Message.ChannelId == eventargs.Channel.Id) { req.ClearCollected(); } } return Task.CompletedTask; } ~Poller() { this.Dispose(); } /// /// Disposes this EventWaiter /// public void Dispose() { this._client.MessageReactionAdded -= this.HandleReactionAdd; this._client.MessageReactionRemoved -= this.HandleReactionRemove; this._client.MessageReactionsCleared -= this.HandleReactionClear; this._client = null; this._requests.Clear(); this._requests = null; } } } diff --git a/DisCatSharp.Interactivity/EventHandling/ReactionCollector.cs b/DisCatSharp.Interactivity/EventHandling/ReactionCollector.cs index a0a7e09ac..141d650b8 100644 --- a/DisCatSharp.Interactivity/EventHandling/ReactionCollector.cs +++ b/DisCatSharp.Interactivity/EventHandling/ReactionCollector.cs @@ -1,284 +1,284 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using System.Reflection; using System.Threading; using System.Threading.Tasks; using ConcurrentCollections; using DisCatSharp.Common.Utilities; using DisCatSharp.Entities; using DisCatSharp.EventArgs; using Microsoft.Extensions.Logging; namespace DisCatSharp.Interactivity.EventHandling { /// /// Eventwaiter is a class that serves as a layer between the InteractivityExtension /// and the DiscordClient to listen to an event and check for matches to a predicate. /// internal class ReactionCollector : IDisposable { DiscordClient _client; AsyncEvent _reactionAddEvent; AsyncEventHandler _reactionAddHandler; AsyncEvent _reactionRemoveEvent; AsyncEventHandler _reactionRemoveHandler; AsyncEvent _reactionClearEvent; AsyncEventHandler _reactionClearHandler; ConcurrentHashSet _requests; /// /// Creates a new Eventwaiter object. /// /// Your DiscordClient public ReactionCollector(DiscordClient client) { this._client = client; var tinfo = this._client.GetType().GetTypeInfo(); this._requests = new ConcurrentHashSet(); // Grabbing all three events from client var handler = tinfo.DeclaredFields.First(x => x.FieldType == typeof(AsyncEvent)); this._reactionAddEvent = (AsyncEvent)handler.GetValue(this._client); this._reactionAddHandler = new AsyncEventHandler(this.HandleReactionAdd); this._reactionAddEvent.Register(this._reactionAddHandler); handler = tinfo.DeclaredFields.First(x => x.FieldType == typeof(AsyncEvent)); this._reactionRemoveEvent = (AsyncEvent)handler.GetValue(this._client); this._reactionRemoveHandler = new AsyncEventHandler(this.HandleReactionRemove); this._reactionRemoveEvent.Register(this._reactionRemoveHandler); handler = tinfo.DeclaredFields.First(x => x.FieldType == typeof(AsyncEvent)); this._reactionClearEvent = (AsyncEvent)handler.GetValue(this._client); this._reactionClearHandler = new AsyncEventHandler(this.HandleReactionClear); this._reactionClearEvent.Register(this._reactionClearHandler); } /// /// Collects the async. /// /// The request. /// A Task. public async Task> CollectAsync(ReactionCollectRequest request) { this._requests.Add(request); var result = (ReadOnlyCollection)null; try { - await request._tcs.Task.ConfigureAwait(false); + await request.Tcs.Task.ConfigureAwait(false); } catch (Exception ex) { this._client.Logger.LogError(InteractivityEvents.InteractivityCollectorError, ex, "Exception occurred while collecting reactions"); } finally { - result = new ReadOnlyCollection(new HashSet(request._collected).ToList()); + result = new ReadOnlyCollection(new HashSet(request.Collected).ToList()); request.Dispose(); this._requests.TryRemove(request); } return result; } /// /// Handles the reaction add. /// /// The client. /// The eventargs. /// A Task. private Task HandleReactionAdd(DiscordClient client, MessageReactionAddEventArgs eventargs) { // foreach request add foreach (var req in this._requests) { - if (req.message.Id == eventargs.Message.Id) + if (req.Message.Id == eventargs.Message.Id) { - if (req._collected.Any(x => x.Emoji == eventargs.Emoji && x.Users.Any(y => y.Id == eventargs.User.Id))) + if (req.Collected.Any(x => x.Emoji == eventargs.Emoji && x.Users.Any(y => y.Id == eventargs.User.Id))) { - var reaction = req._collected.First(x => x.Emoji == eventargs.Emoji && x.Users.Any(y => y.Id == eventargs.User.Id)); - req._collected.TryRemove(reaction); + var reaction = req.Collected.First(x => x.Emoji == eventargs.Emoji && x.Users.Any(y => y.Id == eventargs.User.Id)); + req.Collected.TryRemove(reaction); reaction.Users.Add(eventargs.User); - req._collected.Add(reaction); + req.Collected.Add(reaction); } else { - req._collected.Add(new Reaction() + req.Collected.Add(new Reaction() { Emoji = eventargs.Emoji, Users = new ConcurrentHashSet() { eventargs.User } }); } } } return Task.CompletedTask; } /// /// Handles the reaction remove. /// /// The client. /// The eventargs. /// A Task. private Task HandleReactionRemove(DiscordClient client, MessageReactionRemoveEventArgs eventargs) { // foreach request remove foreach (var req in this._requests) { - if (req.message.Id == eventargs.Message.Id) + if (req.Message.Id == eventargs.Message.Id) { - if (req._collected.Any(x => x.Emoji == eventargs.Emoji && x.Users.Any(y => y.Id == eventargs.User.Id))) + if (req.Collected.Any(x => x.Emoji == eventargs.Emoji && x.Users.Any(y => y.Id == eventargs.User.Id))) { - var reaction = req._collected.First(x => x.Emoji == eventargs.Emoji && x.Users.Any(y => y.Id == eventargs.User.Id)); - req._collected.TryRemove(reaction); + var reaction = req.Collected.First(x => x.Emoji == eventargs.Emoji && x.Users.Any(y => y.Id == eventargs.User.Id)); + req.Collected.TryRemove(reaction); reaction.Users.TryRemove(eventargs.User); if (reaction.Users.Count > 0) - req._collected.Add(reaction); + req.Collected.Add(reaction); } } } return Task.CompletedTask; } /// /// Handles the reaction clear. /// /// The client. /// The eventargs. /// A Task. private Task HandleReactionClear(DiscordClient client, MessageReactionsClearEventArgs eventargs) { // foreach request add foreach (var req in this._requests) { - if (req.message.Id == eventargs.Message.Id) + if (req.Message.Id == eventargs.Message.Id) { - req._collected.Clear(); + req.Collected.Clear(); } } return Task.CompletedTask; } ~ReactionCollector() { this.Dispose(); } /// /// Disposes this EventWaiter /// public void Dispose() { this._client = null; this._reactionAddEvent.Unregister(this._reactionAddHandler); this._reactionRemoveEvent.Unregister(this._reactionRemoveHandler); this._reactionClearEvent.Unregister(this._reactionClearHandler); this._reactionAddEvent = null; this._reactionAddHandler = null; this._reactionRemoveEvent = null; this._reactionRemoveHandler = null; this._reactionClearEvent = null; this._reactionClearHandler = null; this._requests.Clear(); this._requests = null; } } /// /// The reaction collect request. /// public class ReactionCollectRequest : IDisposable { - internal TaskCompletionSource _tcs; - internal CancellationTokenSource _ct; - internal TimeSpan _timeout; - internal DiscordMessage message; - internal ConcurrentHashSet _collected; + internal TaskCompletionSource Tcs; + internal CancellationTokenSource Ct; + internal TimeSpan Timeout; + internal DiscordMessage Message; + internal ConcurrentHashSet Collected; /// /// Initializes a new instance of the class. /// /// The msg. /// The timeout. public ReactionCollectRequest(DiscordMessage msg, TimeSpan timeout) { - this.message = msg; - this._collected = new ConcurrentHashSet(); - this._timeout = timeout; - this._tcs = new TaskCompletionSource(); - this._ct = new CancellationTokenSource(this._timeout); - this._ct.Token.Register(() => this._tcs.TrySetResult(null)); + this.Message = msg; + this.Collected = new ConcurrentHashSet(); + this.Timeout = timeout; + this.Tcs = new TaskCompletionSource(); + this.Ct = new CancellationTokenSource(this.Timeout); + this.Ct.Token.Register(() => this.Tcs.TrySetResult(null)); } ~ReactionCollectRequest() { this.Dispose(); } /// /// Disposes the. /// public void Dispose() { GC.SuppressFinalize(this); - this._ct.Dispose(); - this._tcs = null; - this.message = null; - this._collected?.Clear(); - this._collected = null; + this.Ct.Dispose(); + this.Tcs = null; + this.Message = null; + this.Collected?.Clear(); + this.Collected = null; } } /// /// The reaction. /// public class Reaction { /// /// Gets the emoji. /// public DiscordEmoji Emoji { get; internal set; } /// /// Gets the users. /// public ConcurrentHashSet Users { get; internal set; } /// /// Gets the total. /// public int Total => this.Users.Count; } } diff --git a/DisCatSharp.Interactivity/EventHandling/Requests/CollectRequest.cs b/DisCatSharp.Interactivity/EventHandling/Requests/CollectRequest.cs index 0a1b55348..972e67567 100644 --- a/DisCatSharp.Interactivity/EventHandling/Requests/CollectRequest.cs +++ b/DisCatSharp.Interactivity/EventHandling/Requests/CollectRequest.cs @@ -1,91 +1,91 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System; using System.Threading; using System.Threading.Tasks; using ConcurrentCollections; using DisCatSharp.Common.Utilities; namespace DisCatSharp.Interactivity.EventHandling { /// /// CollectRequest is a class that serves as a representation of /// EventArgs that are being collected within a specific time frame. /// /// internal class CollectRequest : IDisposable where T : AsyncEventArgs { - internal TaskCompletionSource _tcs; - internal CancellationTokenSource _ct; - internal Func _predicate; - internal TimeSpan _timeout; - internal ConcurrentHashSet _collected; + internal TaskCompletionSource Tcs; + internal CancellationTokenSource Ct; + internal Func Predicate; + internal TimeSpan Timeout; + internal ConcurrentHashSet Collected; /// /// Creates a new CollectRequest object. /// /// Predicate to match /// Timeout time public CollectRequest(Func predicate, TimeSpan timeout) { - this._tcs = new TaskCompletionSource(); - this._ct = new CancellationTokenSource(timeout); - this._predicate = predicate; - this._ct.Token.Register(() => this._tcs.TrySetResult(true)); - this._timeout = timeout; - this._collected = new ConcurrentHashSet(); + this.Tcs = new TaskCompletionSource(); + this.Ct = new CancellationTokenSource(timeout); + this.Predicate = predicate; + this.Ct.Token.Register(() => this.Tcs.TrySetResult(true)); + this.Timeout = timeout; + this.Collected = new ConcurrentHashSet(); } ~CollectRequest() { this.Dispose(); } /// /// Disposes this CollectRequest. /// public void Dispose() { - this._ct.Dispose(); - this._tcs = null; - this._predicate = null; + this.Ct.Dispose(); + this.Tcs = null; + this.Predicate = null; - if (this._collected != null) + if (this.Collected != null) { - this._collected.Clear(); - this._collected = null; + this.Collected.Clear(); + this.Collected = null; } } } } /* ^ ^ ( Quack! )> (ミචᆽචミ) (somewhere on twitter I read amazon had a duck that said meow so I had to add a cat that says quack) */ diff --git a/DisCatSharp.Interactivity/EventHandling/Requests/MatchRequest.cs b/DisCatSharp.Interactivity/EventHandling/Requests/MatchRequest.cs index 7d04b69e4..125984347 100644 --- a/DisCatSharp.Interactivity/EventHandling/Requests/MatchRequest.cs +++ b/DisCatSharp.Interactivity/EventHandling/Requests/MatchRequest.cs @@ -1,71 +1,71 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System; using System.Threading; using System.Threading.Tasks; using DisCatSharp.Common.Utilities; namespace DisCatSharp.Interactivity.EventHandling { /// /// MatchRequest is a class that serves as a representation of a /// match that is being waited for. /// /// internal class MatchRequest : IDisposable where T : AsyncEventArgs { - internal TaskCompletionSource _tcs; - internal CancellationTokenSource _ct; - internal Func _predicate; - internal TimeSpan _timeout; + internal TaskCompletionSource Tcs; + internal CancellationTokenSource Ct; + internal Func Predicate; + internal TimeSpan Timeout; /// /// Creates a new MatchRequest object. /// /// Predicate to match /// Timeout time public MatchRequest(Func predicate, TimeSpan timeout) { - this._tcs = new TaskCompletionSource(); - this._ct = new CancellationTokenSource(timeout); - this._predicate = predicate; - this._ct.Token.Register(() => this._tcs.TrySetResult(null)); - this._timeout = timeout; + this.Tcs = new TaskCompletionSource(); + this.Ct = new CancellationTokenSource(timeout); + this.Predicate = predicate; + this.Ct.Token.Register(() => this.Tcs.TrySetResult(null)); + this.Timeout = timeout; } ~MatchRequest() { this.Dispose(); } /// /// Disposes this MatchRequest. /// public void Dispose() { - this._ct.Dispose(); - this._tcs = null; - this._predicate = null; + this.Ct.Dispose(); + this.Tcs = null; + this.Predicate = null; } } } diff --git a/DisCatSharp.Interactivity/EventHandling/Requests/PollRequest.cs b/DisCatSharp.Interactivity/EventHandling/Requests/PollRequest.cs index 5ed795922..f85e96088 100644 --- a/DisCatSharp.Interactivity/EventHandling/Requests/PollRequest.cs +++ b/DisCatSharp.Interactivity/EventHandling/Requests/PollRequest.cs @@ -1,154 +1,154 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using ConcurrentCollections; using DisCatSharp.Entities; namespace DisCatSharp.Interactivity.EventHandling { /// /// The poll request. /// public class PollRequest { - internal TaskCompletionSource _tcs; - internal CancellationTokenSource _ct; - internal TimeSpan _timeout; - internal ConcurrentHashSet _collected; - internal DiscordMessage _message; - internal IEnumerable _emojis; + internal TaskCompletionSource Tcs; + internal CancellationTokenSource Ct; + internal TimeSpan Timeout; + internal ConcurrentHashSet Collected; + internal DiscordMessage Message; + internal IEnumerable Emojis; /// /// /// /// /// /// public PollRequest(DiscordMessage message, TimeSpan timeout, IEnumerable emojis) { - this._tcs = new TaskCompletionSource(); - this._ct = new CancellationTokenSource(timeout); - this._ct.Token.Register(() => this._tcs.TrySetResult(true)); - this._timeout = timeout; - this._emojis = emojis; - this._collected = new ConcurrentHashSet(); - this._message = message; + this.Tcs = new TaskCompletionSource(); + this.Ct = new CancellationTokenSource(timeout); + this.Ct.Token.Register(() => this.Tcs.TrySetResult(true)); + this.Timeout = timeout; + this.Emojis = emojis; + this.Collected = new ConcurrentHashSet(); + this.Message = message; foreach (var e in emojis) { - this._collected.Add(new PollEmoji(e)); + this.Collected.Add(new PollEmoji(e)); } } /// /// Clears the collected. /// internal void ClearCollected() { - this._collected.Clear(); - foreach (var e in this._emojis) + this.Collected.Clear(); + foreach (var e in this.Emojis) { - this._collected.Add(new PollEmoji(e)); + this.Collected.Add(new PollEmoji(e)); } } /// /// Removes the reaction. /// /// The emoji. /// The member. internal void RemoveReaction(DiscordEmoji emoji, DiscordUser member) { - if (this._collected.Any(x => x.Emoji == emoji)) + if (this.Collected.Any(x => x.Emoji == emoji)) { - if (this._collected.Any(x => x.Voted.Contains(member))) + if (this.Collected.Any(x => x.Voted.Contains(member))) { - var e = this._collected.First(x => x.Emoji == emoji); - this._collected.TryRemove(e); + var e = this.Collected.First(x => x.Emoji == emoji); + this.Collected.TryRemove(e); e.Voted.TryRemove(member); - this._collected.Add(e); + this.Collected.Add(e); } } } /// /// Adds the reaction. /// /// The emoji. /// The member. internal void AddReaction(DiscordEmoji emoji, DiscordUser member) { - if (this._collected.Any(x => x.Emoji == emoji)) + if (this.Collected.Any(x => x.Emoji == emoji)) { - if (!this._collected.Any(x => x.Voted.Contains(member))) + if (!this.Collected.Any(x => x.Voted.Contains(member))) { - var e = this._collected.First(x => x.Emoji == emoji); - this._collected.TryRemove(e); + var e = this.Collected.First(x => x.Emoji == emoji); + this.Collected.TryRemove(e); e.Voted.Add(member); - this._collected.Add(e); + this.Collected.Add(e); } } } ~PollRequest() { this.Dispose(); } /// /// Disposes this PollRequest. /// public void Dispose() { - this._ct.Dispose(); - this._tcs = null; + this.Ct.Dispose(); + this.Tcs = null; } } /// /// The poll emoji. /// public class PollEmoji { /// /// Initializes a new instance of the class. /// /// The emoji. internal PollEmoji(DiscordEmoji emoji) { this.Emoji = emoji; this.Voted = new ConcurrentHashSet(); } public DiscordEmoji Emoji; public ConcurrentHashSet Voted; /// /// Gets the total. /// public int Total => this.Voted.Count; } } diff --git a/DisCatSharp.Interactivity/Extensions/ChannelExtensions.cs b/DisCatSharp.Interactivity/Extensions/ChannelExtensions.cs index 98e2e1360..0b12fdd20 100644 --- a/DisCatSharp.Interactivity/Extensions/ChannelExtensions.cs +++ b/DisCatSharp.Interactivity/Extensions/ChannelExtensions.cs @@ -1,150 +1,150 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using DisCatSharp.Entities; using DisCatSharp.EventArgs; using DisCatSharp.Interactivity.Enums; using DisCatSharp.Interactivity.EventHandling; namespace DisCatSharp.Interactivity.Extensions { /// /// Interactivity extension methods for . /// public static class ChannelExtensions { /// /// Waits for the next message sent in this channel that satisfies the predicate. /// /// The channel to monitor. /// A predicate that should return if a message matches. /// Overrides the timeout set in /// Thrown if interactivity is not enabled for the client associated with the channel. public static Task> GetNextMessageAsync(this DiscordChannel channel, Func predicate, TimeSpan? timeoutOverride = null) => GetInteractivity(channel).WaitForMessageAsync(msg => msg.ChannelId == channel.Id && predicate(msg), timeoutOverride); /// /// Waits for the next message sent in this channel. /// /// The channel to monitor. /// Overrides the timeout set in /// Thrown if interactivity is not enabled for the client associated with the channel. public static Task> GetNextMessageAsync(this DiscordChannel channel, TimeSpan? timeoutOverride = null) => channel.GetNextMessageAsync(msg => true, timeoutOverride); /// /// Waits for the next message sent in this channel from a specific user. /// /// The channel to monitor. /// The target user. /// Overrides the timeout set in /// Thrown if interactivity is not enabled for the client associated with the channel. public static Task> GetNextMessageAsync(this DiscordChannel channel, DiscordUser user, TimeSpan? timeoutOverride = null) => channel.GetNextMessageAsync(msg => msg.Author.Id == user.Id, timeoutOverride); /// /// Waits for a specific user to start typing in this channel. /// /// The target channel. /// The target user. /// Overrides the timeout set in /// Thrown if interactivity is not enabled for the client associated with the channel. public static Task> WaitForUserTypingAsync(this DiscordChannel channel, DiscordUser user, TimeSpan? timeoutOverride = null) => GetInteractivity(channel).WaitForUserTypingAsync(user, channel, timeoutOverride); /// /// Sends a new paginated message. /// /// Target channel. /// The user that'll be able to control the pages. /// A collection of to display. /// Pagination emojis. /// Pagination behaviour (when hitting max and min indices). /// Deletion behaviour. /// Override timeout period. /// Thrown if interactivity is not enabled for the client associated with the channel. public static Task SendPaginatedMessageAsync(this DiscordChannel channel, DiscordUser user, IEnumerable pages, PaginationEmojis emojis, PaginationBehaviour? behaviour = default, PaginationDeletion? deletion = default, TimeSpan? timeoutoverride = null) => GetInteractivity(channel).SendPaginatedMessageAsync(channel, user, pages, emojis, behaviour, deletion, timeoutoverride); /// /// Sends a new paginated message with buttons. /// /// Target channel. /// The user that'll be able to control the pages. /// A collection of to display. /// Pagination buttons (leave null to default to ones on configuration). /// Pagination behaviour. /// Deletion behaviour /// A custom cancellation token that can be cancelled at any point. /// Thrown if interactivity is not enabled for the client associated with the channel. public static Task SendPaginatedMessageAsync(this DiscordChannel channel, DiscordUser user, IEnumerable pages, PaginationButtons buttons, PaginationBehaviour? behaviour = default, ButtonPaginationBehavior? deletion = default, CancellationToken token = default) => GetInteractivity(channel).SendPaginatedMessageAsync(channel, user, pages, buttons, behaviour, deletion, token); /// public static Task SendPaginatedMessageAsync(this DiscordChannel channel, DiscordUser user, IEnumerable pages, PaginationBehaviour? behaviour = default, ButtonPaginationBehavior? deletion = default, CancellationToken token = default) => channel.SendPaginatedMessageAsync(user, pages, default, behaviour, deletion, token); /// /// Sends a new paginated message with buttons. /// /// Target channel. /// The user that'll be able to control the pages. /// A collection of to display. /// Pagination buttons (leave null to default to ones on configuration). /// Pagination behaviour. /// Deletion behaviour /// Override timeout period. /// Thrown if interactivity is not enabled for the client associated with the channel. public static Task SendPaginatedMessageAsync(this DiscordChannel channel, DiscordUser user, IEnumerable pages, PaginationButtons buttons, TimeSpan? timeoutoverride, PaginationBehaviour? behaviour = default, ButtonPaginationBehavior? deletion = default) => GetInteractivity(channel).SendPaginatedMessageAsync(channel, user, pages, buttons, timeoutoverride, behaviour, deletion); /// /// Sends the paginated message async. /// /// The channel. /// The user. /// The pages. /// The timeoutoverride. /// The behaviour. /// The deletion. /// A Task. public static Task SendPaginatedMessageAsync(this DiscordChannel channel, DiscordUser user, IEnumerable pages, TimeSpan? timeoutoverride, PaginationBehaviour? behaviour = default, ButtonPaginationBehavior? deletion = default) => channel.SendPaginatedMessageAsync(user, pages, default, timeoutoverride, behaviour, deletion); /// /// Retrieves an interactivity instance from a channel instance. /// private static InteractivityExtension GetInteractivity(DiscordChannel channel) { var client = (DiscordClient)channel.Discord; var interactivity = client.GetInteractivity(); - return interactivity ?? throw new InvalidOperationException($"Interactivity is not enabled for this {(client._isShard ? "shard" : "client")}."); + return interactivity ?? throw new InvalidOperationException($"Interactivity is not enabled for this {(client.IsShard ? "shard" : "client")}."); } } } diff --git a/DisCatSharp.Interactivity/Extensions/ClientExtensions.cs b/DisCatSharp.Interactivity/Extensions/ClientExtensions.cs index 52d63266a..256bdfc29 100644 --- a/DisCatSharp.Interactivity/Extensions/ClientExtensions.cs +++ b/DisCatSharp.Interactivity/Extensions/ClientExtensions.cs @@ -1,100 +1,100 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using System.Threading.Tasks; namespace DisCatSharp.Interactivity.Extensions { /// /// Interactivity extension methods for and . /// public static class ClientExtensions { /// /// Enables interactivity for this instance. /// /// The client to enable interactivity for. /// A configuration instance. Default configuration values will be used if none is provided. /// A brand new instance. /// Thrown if interactivity has already been enabled for the client instance. public static InteractivityExtension UseInteractivity(this DiscordClient client, InteractivityConfiguration configuration = null) { - if (client.GetExtension() != null) throw new InvalidOperationException($"Interactivity is already enabled for this {(client._isShard ? "shard" : "client")}."); + if (client.GetExtension() != null) throw new InvalidOperationException($"Interactivity is already enabled for this {(client.IsShard ? "shard" : "client")}."); configuration ??= new InteractivityConfiguration(); var extension = new InteractivityExtension(configuration); client.AddExtension(extension); return extension; } /// /// Enables interactivity for each shard. /// /// The shard client to enable interactivity for. /// Configuration to use for all shards. If one isn't provided, default configuration values will be used. /// A dictionary containing new instances for each shard. public static async Task> UseInteractivityAsync(this DiscordShardedClient client, InteractivityConfiguration configuration = null) { var extensions = new Dictionary(); await client.InitializeShardsAsync().ConfigureAwait(false); foreach (var shard in client.ShardClients.Select(xkvp => xkvp.Value)) { var extension = shard.GetExtension() ?? shard.UseInteractivity(configuration); extensions.Add(shard.ShardId, extension); } return new ReadOnlyDictionary(extensions); } /// /// Retrieves the registered instance for this client. /// /// The client to retrieve an instance from. /// An existing instance, or if interactivity is not enabled for the instance. public static InteractivityExtension GetInteractivity(this DiscordClient client) => client.GetExtension(); /// /// Retrieves a instance for each shard. /// /// The shard client to retrieve interactivity instances from. /// A dictionary containing instances for each shard. public static async Task> GetInteractivityAsync(this DiscordShardedClient client) { await client.InitializeShardsAsync().ConfigureAwait(false); var extensions = new Dictionary(); foreach (var shard in client.ShardClients.Select(xkvp => xkvp.Value)) { extensions.Add(shard.ShardId, shard.GetExtension()); } return new ReadOnlyDictionary(extensions); } } } diff --git a/DisCatSharp.Interactivity/Extensions/MessageExtensions.cs b/DisCatSharp.Interactivity/Extensions/MessageExtensions.cs index c1b678405..d723406da 100644 --- a/DisCatSharp.Interactivity/Extensions/MessageExtensions.cs +++ b/DisCatSharp.Interactivity/Extensions/MessageExtensions.cs @@ -1,243 +1,243 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Threading; using System.Threading.Tasks; using DisCatSharp.Entities; using DisCatSharp.EventArgs; using DisCatSharp.Interactivity.Enums; using DisCatSharp.Interactivity.EventHandling; namespace DisCatSharp.Interactivity.Extensions { /// /// Interactivity extension methods for . /// public static class MessageExtensions { /// /// Waits for the next message that has the same author and channel as this message. /// /// Original message. /// Overrides the timeout set in public static Task> GetNextMessageAsync(this DiscordMessage message, TimeSpan? timeoutOverride = null) => message.Channel.GetNextMessageAsync(message.Author, timeoutOverride); /// /// Waits for the next message with the same author and channel as this message, which also satisfies a predicate. /// /// Original message. /// A predicate that should return if a message matches. /// Overrides the timeout set in public static Task> GetNextMessageAsync(this DiscordMessage message, Func predicate, TimeSpan? timeoutOverride = null) => message.Channel.GetNextMessageAsync(msg => msg.Author.Id == message.Author.Id && message.ChannelId == msg.ChannelId && predicate(msg), timeoutOverride); /// /// Waits for any button to be pressed on the specified message. /// /// The message to wait on. public static Task> WaitForButtonAsync(this DiscordMessage message) => GetInteractivity(message).WaitForButtonAsync(message); /// /// Waits for any button to be pressed on the specified message. /// /// The message to wait on. /// Overrides the timeout set in public static Task> WaitForButtonAsync(this DiscordMessage message, TimeSpan? timeoutOverride = null) => GetInteractivity(message).WaitForButtonAsync(message, timeoutOverride); /// /// Waits for any button to be pressed on the specified message. /// /// The message to wait on. /// A custom cancellation token that can be cancelled at any point. public static Task> WaitForButtonAsync(this DiscordMessage message, CancellationToken token) => GetInteractivity(message).WaitForButtonAsync(message, token); /// /// Waits for a button with the specified Id to be pressed on the specified message. /// /// The message to wait on. /// The Id of the button to wait for. /// Overrides the timeout set in public static Task> WaitForButtonAsync(this DiscordMessage message, string id, TimeSpan? timeoutOverride = null) => GetInteractivity(message).WaitForButtonAsync(message, id, timeoutOverride); /// /// Waits for a button with the specified Id to be pressed on the specified message. /// /// The message to wait on. /// The Id of the button to wait for. /// A custom cancellation token that can be cancelled at any point. public static Task> WaitForButtonAsync(this DiscordMessage message, string id, CancellationToken token) => GetInteractivity(message).WaitForButtonAsync(message, id, token); /// /// Waits for any button to be pressed on the specified message by the specified user. /// /// The message to wait on. /// The user to wait for button input from. /// Overrides the timeout set in public static Task> WaitForButtonAsync(this DiscordMessage message, DiscordUser user, TimeSpan? timeoutOverride = null) => GetInteractivity(message).WaitForButtonAsync(message, user, timeoutOverride); /// /// Waits for any button to be pressed on the specified message by the specified user. /// /// The message to wait on. /// The user to wait for button input from. /// A custom cancellation token that can be cancelled at any point. public static Task> WaitForButtonAsync(this DiscordMessage message, DiscordUser user, CancellationToken token) => GetInteractivity(message).WaitForButtonAsync(message, user, token); /// /// Waits for any button to be interacted with. /// /// The message to wait on. /// The predicate to filter interactions by. /// Override the timeout specified in public static Task> WaitForButtonAsync(this DiscordMessage message, Func predicate, TimeSpan? timeoutOverride = null) => GetInteractivity(message).WaitForButtonAsync(message, predicate, timeoutOverride); /// /// Waits for any button to be interacted with. /// /// The message to wait on. /// The predicate to filter interactions by. /// A token to cancel interactivity with at any time. Pass to wait indefinitely. public static Task> WaitForButtonAsync(this DiscordMessage message, Func predicate, CancellationToken token) => GetInteractivity(message).WaitForButtonAsync(message, predicate, token); /// /// Waits for any dropdown to be interacted with. /// /// The message to wait for. /// A filter predicate. /// Override the timeout period specified in . /// Thrown when the message doesn't contain any dropdowns public static Task> WaitForSelectAsync(this DiscordMessage message, Func predicate, TimeSpan? timeoutOverride = null) => GetInteractivity(message).WaitForSelectAsync(message, predicate, timeoutOverride); /// /// Waits for any dropdown to be interacted with. /// /// The message to wait for. /// A filter predicate. /// A token that can be used to cancel interactivity. Pass to wait indefinitely. /// Thrown when the message doesn't contain any dropdowns public static Task> WaitForSelectAsync(this DiscordMessage message, Func predicate, CancellationToken token) => GetInteractivity(message).WaitForSelectAsync(message, predicate, token); /// /// Waits for a dropdown to be interacted with. /// /// The message to wait on. /// The Id of the dropdown to wait for. /// Overrides the timeout set in public static Task> WaitForSelectAsync(this DiscordMessage message, string id, TimeSpan? timeoutOverride = null) => GetInteractivity(message).WaitForSelectAsync(message, id, timeoutOverride); /// /// Waits for a dropdown to be interacted with. /// /// The message to wait on. /// The Id of the dropdown to wait for. /// A custom cancellation token that can be cancelled at any point. public static Task> WaitForSelectAsync(this DiscordMessage message, string id, CancellationToken token) => GetInteractivity(message).WaitForSelectAsync(message, id, token); /// /// Waits for a dropdown to be interacted with by the specified user. /// /// The message to wait on. /// The user to wait for. /// The Id of the dropdown to wait for. /// public static Task> WaitForSelectAsync(this DiscordMessage message, DiscordUser user, string id, TimeSpan? timeoutOverride = null) => GetInteractivity(message).WaitForSelectAsync(message, user, id, timeoutOverride); /// /// Waits for a dropdown to be interacted with by the specified user. /// /// The message to wait on. /// The user to wait for. /// The Id of the dropdown to wait for. /// A custom cancellation token that can be cancelled at any point. public static Task> WaitForSelectAsync(this DiscordMessage message, DiscordUser user, string id, CancellationToken token) => GetInteractivity(message).WaitForSelectAsync(message, user, id, token); /// /// Waits for a reaction on this message from a specific user. /// /// Target message. /// The target user. /// Overrides the timeout set in /// Thrown if interactivity is not enabled for the client associated with the message. public static Task> WaitForReactionAsync(this DiscordMessage message, DiscordUser user, TimeSpan? timeoutOverride = null) => GetInteractivity(message).WaitForReactionAsync(message, user, timeoutOverride); /// /// Waits for a specific reaction on this message from the specified user. /// /// Target message. /// The target user. /// The target emoji. /// Overrides the timeout set in /// Thrown if interactivity is not enabled for the client associated with the message. public static Task> WaitForReactionAsync(this DiscordMessage message, DiscordUser user, DiscordEmoji emoji, TimeSpan? timeoutOverride = null) => GetInteractivity(message).WaitForReactionAsync(e => e.Emoji == emoji, message, user, timeoutOverride); /// /// Collects all reactions on this message within the timeout duration. /// /// The message to collect reactions from. /// Overrides the timeout set in /// Thrown if interactivity is not enabled for the client associated with the message. public static Task> CollectReactionsAsync(this DiscordMessage message, TimeSpan? timeoutOverride = null) => GetInteractivity(message).CollectReactionsAsync(message, timeoutOverride); /// /// Begins a poll using this message. /// /// Target message. /// Options for this poll. /// Overrides the action set in /// Overrides the timeout set in /// Thrown if interactivity is not enabled for the client associated with the message. public static Task> DoPollAsync(this DiscordMessage message, IEnumerable emojis, PollBehaviour? behaviorOverride = null, TimeSpan? timeoutOverride = null) => GetInteractivity(message).DoPollAsync(message, emojis, behaviorOverride, timeoutOverride); /// /// Retrieves an interactivity instance from a message instance. /// internal static InteractivityExtension GetInteractivity(DiscordMessage message) { var client = (DiscordClient)message.Discord; var interactivity = client.GetInteractivity(); - return interactivity ?? throw new InvalidOperationException($"Interactivity is not enabled for this {(client._isShard ? "shard" : "client")}."); + return interactivity ?? throw new InvalidOperationException($"Interactivity is not enabled for this {(client.IsShard ? "shard" : "client")}."); } } } diff --git a/DisCatSharp.Interactivity/InteractivityExtension.cs b/DisCatSharp.Interactivity/InteractivityExtension.cs index 1fe318280..88926a766 100644 --- a/DisCatSharp.Interactivity/InteractivityExtension.cs +++ b/DisCatSharp.Interactivity/InteractivityExtension.cs @@ -1,961 +1,961 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using System.Threading; using System.Threading.Tasks; using DisCatSharp.Common.Utilities; using DisCatSharp.Entities; using DisCatSharp.Enums; using DisCatSharp.EventArgs; using DisCatSharp.Interactivity.Enums; using DisCatSharp.Interactivity.EventHandling; namespace DisCatSharp.Interactivity { /// /// Extension class for DisCatSharp.Interactivity /// public class InteractivityExtension : BaseExtension { #pragma warning disable IDE1006 // Naming Styles /// /// Gets the config. /// internal InteractivityConfiguration Config { get; } - private EventWaiter MessageCreatedWaiter; + private EventWaiter _messageCreatedWaiter; - private EventWaiter MessageReactionAddWaiter; + private EventWaiter _messageReactionAddWaiter; - private EventWaiter TypingStartWaiter; + private EventWaiter _typingStartWaiter; - private EventWaiter ModalInteractionWaiter; + private EventWaiter _modalInteractionWaiter; - private EventWaiter ComponentInteractionWaiter; + private EventWaiter _componentInteractionWaiter; - private ComponentEventWaiter ComponentEventWaiter; + private ComponentEventWaiter _componentEventWaiter; - private ModalEventWaiter ModalEventWaiter; + private ModalEventWaiter _modalEventWaiter; - private ReactionCollector ReactionCollector; + private ReactionCollector _reactionCollector; - private Poller Poller; + private Poller _poller; - private Paginator Paginator; + private Paginator _paginator; private ComponentPaginator _compPaginator; #pragma warning restore IDE1006 // Naming Styles /// /// Initializes a new instance of the class. /// /// The configuration. internal InteractivityExtension(InteractivityConfiguration cfg) { this.Config = new InteractivityConfiguration(cfg); } /// /// Setups the Interactivity Extension. /// /// Discord client. protected internal override void Setup(DiscordClient client) { this.Client = client; - this.MessageCreatedWaiter = new EventWaiter(this.Client); - this.MessageReactionAddWaiter = new EventWaiter(this.Client); - this.ComponentInteractionWaiter = new EventWaiter(this.Client); - this.ModalInteractionWaiter = new EventWaiter(this.Client); - this.TypingStartWaiter = new EventWaiter(this.Client); - this.Poller = new Poller(this.Client); - this.ReactionCollector = new ReactionCollector(this.Client); - this.Paginator = new Paginator(this.Client); + this._messageCreatedWaiter = new EventWaiter(this.Client); + this._messageReactionAddWaiter = new EventWaiter(this.Client); + this._componentInteractionWaiter = new EventWaiter(this.Client); + this._modalInteractionWaiter = new EventWaiter(this.Client); + this._typingStartWaiter = new EventWaiter(this.Client); + this._poller = new Poller(this.Client); + this._reactionCollector = new ReactionCollector(this.Client); + this._paginator = new Paginator(this.Client); this._compPaginator = new(this.Client, this.Config); - this.ComponentEventWaiter = new(this.Client, this.Config); - this.ModalEventWaiter = new(this.Client, this.Config); + this._componentEventWaiter = new(this.Client, this.Config); + this._modalEventWaiter = new(this.Client, this.Config); } /// /// Makes a poll and returns poll results. /// /// Message to create poll on. /// Emojis to use for this poll. /// What to do when the poll ends. /// override timeout period. /// public async Task> DoPollAsync(DiscordMessage m, IEnumerable emojis, PollBehaviour? behaviour = default, TimeSpan? timeout = null) { if (!Utilities.HasReactionIntents(this.Client.Configuration.Intents)) throw new InvalidOperationException("No reaction intents are enabled."); if (!emojis.Any()) throw new ArgumentException("You need to provide at least one emoji for a poll!"); foreach (var em in emojis) await m.CreateReactionAsync(em).ConfigureAwait(false); - var res = await this.Poller.DoPollAsync(new PollRequest(m, timeout ?? this.Config.Timeout, emojis)).ConfigureAwait(false); + var res = await this._poller.DoPollAsync(new PollRequest(m, timeout ?? this.Config.Timeout, emojis)).ConfigureAwait(false); var pollbehaviour = behaviour ?? this.Config.PollBehaviour; var thismember = await m.Channel.Guild.GetMemberAsync(this.Client.CurrentUser.Id).ConfigureAwait(false); if (pollbehaviour == PollBehaviour.DeleteEmojis && m.Channel.PermissionsFor(thismember).HasPermission(Permissions.ManageMessages)) await m.DeleteAllReactionsAsync().ConfigureAwait(false); return new ReadOnlyCollection(res.ToList()); } /// /// Waits for any button in the specified collection to be pressed. /// /// The message to wait on. /// A collection of buttons to listen for. /// Override the timeout period in . /// A with the result of button that was pressed, if any. /// Thrown when attempting to wait for a message that is not authored by the current user. /// Thrown when the message does not contain a button with the specified Id, or any buttons at all. public Task> WaitForButtonAsync(DiscordMessage message, IEnumerable buttons, TimeSpan? timeoutOverride = null) => this.WaitForButtonAsync(message, buttons, this.GetCancellationToken(timeoutOverride)); /// /// Waits for any button in the specified collection to be pressed. /// /// The message to wait on. /// A collection of buttons to listen for. /// A custom cancellation token that can be cancelled at any point. /// A with the result of button that was pressed, if any. /// Thrown when attempting to wait for a message that is not authored by the current user. /// Thrown when the message does not contain a button with the specified Id, or any buttons at all. public async Task> WaitForButtonAsync(DiscordMessage message, IEnumerable buttons, CancellationToken token) { if (message.Author != this.Client.CurrentUser) throw new InvalidOperationException("Interaction events are only sent to the application that created them."); if (!buttons.Any()) throw new ArgumentException("You must specify at least one button to listen for."); if (!message.Components.Any()) throw new ArgumentException("Provided message does not contain any components."); if (!message.Components.SelectMany(c => c.Components).Any(c => c.Type is ComponentType.Button)) throw new ArgumentException("Provided Message does not contain any button components."); - var res = await this.ComponentEventWaiter + var res = await this._componentEventWaiter .WaitForMatchAsync(new(message, c => c.Interaction.Data.ComponentType == ComponentType.Button && buttons.Any(b => b.CustomId == c.Id), token)).ConfigureAwait(false); return new(res is null, res); } /// /// Waits for a user modal submit. /// /// The custom id of the modal to wait for. /// Override the timeout period specified in . /// A with the result of the modal. - public Task> WaitForModalAsync(string custom_id, TimeSpan? timeoutOverride = null) - => this.WaitForModalAsync(custom_id, this.GetCancellationToken(timeoutOverride)); + public Task> WaitForModalAsync(string customId, TimeSpan? timeoutOverride = null) + => this.WaitForModalAsync(customId, this.GetCancellationToken(timeoutOverride)); /// /// Waits for a user modal submit. /// /// The custom id of the modal to wait for. /// A custom cancellation token that can be cancelled at any point. /// A with the result of the modal. - public async Task> WaitForModalAsync(string custom_id, CancellationToken token) + public async Task> WaitForModalAsync(string customId, CancellationToken token) { var result = await this - .ModalEventWaiter - .WaitForModalMatchAsync(new(custom_id, c => c.Interaction.Type == InteractionType.ModalSubmit, token)) + ._modalEventWaiter + .WaitForModalMatchAsync(new(customId, c => c.Interaction.Type == InteractionType.ModalSubmit, token)) .ConfigureAwait(false); return new(result is null, result); } /// /// Waits for any button on the specified message to be pressed. /// /// The message to wait for the button on. /// Override the timeout period specified in . /// A with the result of button that was pressed, if any. /// Thrown when attempting to wait for a message that is not authored by the current user. /// Thrown when the message does not contain a button with the specified Id, or any buttons at all. public Task> WaitForButtonAsync(DiscordMessage message, TimeSpan? timeoutOverride = null) => this.WaitForButtonAsync(message, this.GetCancellationToken(timeoutOverride)); /// /// Waits for any button on the specified message to be pressed. /// /// The message to wait for the button on. /// A custom cancellation token that can be cancelled at any point. /// A with the result of button that was pressed, if any. /// Thrown when attempting to wait for a message that is not authored by the current user. /// Thrown when the message does not contain a button with the specified Id, or any buttons at all. public async Task> WaitForButtonAsync(DiscordMessage message, CancellationToken token) { if (message.Author != this.Client.CurrentUser) throw new InvalidOperationException("Interaction events are only sent to the application that created them."); if (!message.Components.Any()) throw new ArgumentException("Provided message does not contain any components."); if (!message.Components.SelectMany(c => c.Components).Any(c => c.Type is ComponentType.Button)) throw new ArgumentException("Message does not contain any button components."); var ids = message.Components.SelectMany(m => m.Components).Select(c => c.CustomId); var result = await this - .ComponentEventWaiter + ._componentEventWaiter .WaitForMatchAsync(new(message, c => c.Interaction.Data.ComponentType == ComponentType.Button && ids.Contains(c.Id), token)) .ConfigureAwait(false); return new(result is null, result); } /// /// Waits for any button on the specified message to be pressed by the specified user. /// /// The message to wait for the button on. /// The user to wait for the button press from. /// Override the timeout period specified in . /// A with the result of button that was pressed, if any. /// Thrown when attempting to wait for a message that is not authored by the current user. /// Thrown when the message does not contain a button with the specified Id, or any buttons at all. public Task> WaitForButtonAsync(DiscordMessage message, DiscordUser user, TimeSpan? timeoutOverride = null) => this.WaitForButtonAsync(message, user, this.GetCancellationToken(timeoutOverride)); /// /// Waits for any button on the specified message to be pressed by the specified user. /// /// The message to wait for the button on. /// The user to wait for the button press from. /// A custom cancellation token that can be cancelled at any point. /// A with the result of button that was pressed, if any. /// Thrown when attempting to wait for a message that is not authored by the current user. /// Thrown when the message does not contain a button with the specified Id, or any buttons at all. public async Task> WaitForButtonAsync(DiscordMessage message, DiscordUser user, CancellationToken token) { if (message.Author != this.Client.CurrentUser) throw new InvalidOperationException("Interaction events are only sent to the application that created them."); if (!message.Components.Any()) throw new ArgumentException("Provided message does not contain any components."); if (!message.Components.SelectMany(c => c.Components).Any(c => c.Type is ComponentType.Button)) throw new ArgumentException("Message does not contain any button components."); var result = await this - .ComponentEventWaiter + ._componentEventWaiter .WaitForMatchAsync(new(message, (c) => c.Interaction.Data.ComponentType is ComponentType.Button && c.User == user, token)) .ConfigureAwait(false); return new(result is null, result); } /// /// Waits for a button with the specified Id to be pressed. /// /// The message to wait for the button on. /// The Id of the button to wait for. /// Override the timeout period specified in . /// A with the result of the operation. /// Thrown when attempting to wait for a message that is not authored by the current user. /// Thrown when the message does not contain a button with the specified Id, or any buttons at all. public Task> WaitForButtonAsync(DiscordMessage message, string id, TimeSpan? timeoutOverride = null) => this.WaitForButtonAsync(message, id, this.GetCancellationToken(timeoutOverride)); /// /// Waits for a button with the specified Id to be pressed. /// /// The message to wait for the button on. /// The Id of the button to wait for. /// Override the timeout period specified in . /// A with the result of the operation. /// Thrown when attempting to wait for a message that is not authored by the current user. /// Thrown when the message does not contain a button with the specified Id, or any buttons at all. public async Task> WaitForButtonAsync(DiscordMessage message, string id, CancellationToken token) { if (message.Author != this.Client.CurrentUser) throw new InvalidOperationException("Interaction events are only sent to the application that created them."); if (!message.Components.Any()) throw new ArgumentException("Provided message does not contain any components."); if (!message.Components.SelectMany(c => c.Components).Any(c => c.Type is ComponentType.Button)) throw new ArgumentException("Message does not contain any button components."); if (!message.Components.SelectMany(c => c.Components).OfType().Any(c => c.CustomId == id)) throw new ArgumentException($"Message does not contain button with Id of '{id}'."); var result = await this - .ComponentEventWaiter + ._componentEventWaiter .WaitForMatchAsync(new(message, (c) => c.Interaction.Data.ComponentType is ComponentType.Button && c.Id == id, token)) .ConfigureAwait(false); return new(result is null, result); } /// /// Waits for any button to be interacted with. /// /// The message to wait on. /// The predicate to filter interactions by. /// Override the timeout specified in public Task> WaitForButtonAsync(DiscordMessage message, Func predicate, TimeSpan? timeoutOverride = null) => this.WaitForButtonAsync(message, predicate, this.GetCancellationToken(timeoutOverride)); /// /// Waits for any button to be interacted with. /// /// The message to wait on. /// The predicate to filter interactions by. /// A token to cancel interactivity with at any time. Pass to wait indefinitely. public async Task> WaitForButtonAsync(DiscordMessage message, Func predicate, CancellationToken token) { if (message.Author != this.Client.CurrentUser) throw new InvalidOperationException("Interaction events are only sent to the application that created them."); if (!message.Components.Any()) throw new ArgumentException("Provided message does not contain any components."); if (!message.Components.SelectMany(c => c.Components).Any(c => c.Type is ComponentType.Button)) throw new ArgumentException("Message does not contain any button components."); var result = await this - .ComponentEventWaiter + ._componentEventWaiter .WaitForMatchAsync(new(message, c => c.Interaction.Data.ComponentType is ComponentType.Button && predicate(c), token)) .ConfigureAwait(false); return new(result is null, result); } /// /// Waits for any dropdown to be interacted with. /// /// The message to wait for. /// A filter predicate. /// Override the timeout period specified in . /// Thrown when the Provided message does not contain any dropdowns public Task> WaitForSelectAsync(DiscordMessage message, Func predicate, TimeSpan? timeoutOverride = null) => this.WaitForSelectAsync(message, predicate, this.GetCancellationToken(timeoutOverride)); /// /// Waits for any dropdown to be interacted with. /// /// The message to wait for. /// A filter predicate. /// A token that can be used to cancel interactivity. Pass to wait indefinitely. /// Thrown when the Provided message does not contain any dropdowns public async Task> WaitForSelectAsync(DiscordMessage message, Func predicate, CancellationToken token) { if (message.Author != this.Client.CurrentUser) throw new InvalidOperationException("Interaction events are only sent to the application that created them."); if (!message.Components.Any()) throw new ArgumentException("Provided message does not contain any components."); if (!message.Components.SelectMany(c => c.Components).Any(c => c.Type is ComponentType.Select)) throw new ArgumentException("Message does not contain any select components."); var result = await this - .ComponentEventWaiter + ._componentEventWaiter .WaitForMatchAsync(new(message, c => c.Interaction.Data.ComponentType is ComponentType.Select && predicate(c), token)) .ConfigureAwait(false); return new(result is null, result); } /// /// Waits for a dropdown to be interacted with. /// /// This is here for backwards-compatibility and will internally create a cancellation token. /// The message to wait on. /// The Id of the dropdown to wait on. /// Override the timeout period specified in . /// Thrown when the message does not have any dropdowns or any dropdown with the specified Id. public Task> WaitForSelectAsync(DiscordMessage message, string id, TimeSpan? timeoutOverride = null) => this.WaitForSelectAsync(message, id, this.GetCancellationToken(timeoutOverride)); /// /// Waits for a dropdown to be interacted with. /// /// The message to wait on. /// The Id of the dropdown to wait on. /// A custom cancellation token that can be cancelled at any point. /// Thrown when the message does not have any dropdowns or any dropdown with the specified Id. public async Task> WaitForSelectAsync(DiscordMessage message, string id, CancellationToken token) { if (message.Author != this.Client.CurrentUser) throw new InvalidOperationException("Interaction events are only sent to the application that created them."); if (!message.Components.Any()) throw new ArgumentException("Provided message does not contain any components."); if (!message.Components.SelectMany(c => c.Components).Any(c => c.Type is ComponentType.Select)) throw new ArgumentException("Message does not contain any select components."); if (message.Components.SelectMany(c => c.Components).OfType().All(c => c.CustomId != id)) throw new ArgumentException($"Message does not contain select component with Id of '{id}'."); var result = await this - .ComponentEventWaiter + ._componentEventWaiter .WaitForMatchAsync(new(message, (c) => c.Interaction.Data.ComponentType is ComponentType.Select && c.Id == id, token)) .ConfigureAwait(false); return new(result is null, result); } /// /// Waits for a dropdown to be interacted with by a specific user. /// /// The message to wait on. /// The user to wait on. /// The Id of the dropdown to wait on. /// Override the timeout period specified in . /// Thrown when the message does not have any dropdowns or any dropdown with the specified Id. public Task> WaitForSelectAsync(DiscordMessage message, DiscordUser user, string id, TimeSpan? timeoutOverride = null) => this.WaitForSelectAsync(message, user, id, this.GetCancellationToken(timeoutOverride)); /// /// Waits for a dropdown to be interacted with by a specific user. /// /// The message to wait on. /// The user to wait on. /// The Id of the dropdown to wait on. /// A custom cancellation token that can be cancelled at any point. /// Thrown when the message does not have any dropdowns or any dropdown with the specified Id. public async Task> WaitForSelectAsync(DiscordMessage message, DiscordUser user, string id, CancellationToken token) { if (message.Author != this.Client.CurrentUser) throw new InvalidOperationException("Interaction events are only sent to the application that created them."); if (!message.Components.Any()) throw new ArgumentException("Provided message does not contain any components."); if (!message.Components.SelectMany(c => c.Components).Any(c => c.Type is ComponentType.Select)) throw new ArgumentException("Message does not contain any select components."); if (message.Components.SelectMany(c => c.Components).OfType().All(c => c.CustomId != id)) throw new ArgumentException($"Message does not contain select with Id of '{id}'."); var result = await this - .ComponentEventWaiter + ._componentEventWaiter .WaitForMatchAsync(new(message, (c) => c.Id == id && c.User == user, token)).ConfigureAwait(false); return new(result is null, result); } /// /// Waits for a specific message. /// /// Predicate to match. /// override timeout period. public async Task> WaitForMessageAsync(Func predicate, TimeSpan? timeoutoverride = null) { if (!Utilities.HasMessageIntents(this.Client.Configuration.Intents)) throw new InvalidOperationException("No message intents are enabled."); var timeout = timeoutoverride ?? this.Config.Timeout; - var returns = await this.MessageCreatedWaiter.WaitForMatchAsync(new MatchRequest(x => predicate(x.Message), timeout)).ConfigureAwait(false); + var returns = await this._messageCreatedWaiter.WaitForMatchAsync(new MatchRequest(x => predicate(x.Message), timeout)).ConfigureAwait(false); return new InteractivityResult(returns == null, returns?.Message); } /// /// Wait for a specific reaction. /// /// Predicate to match. /// override timeout period. public async Task> WaitForReactionAsync(Func predicate, TimeSpan? timeoutoverride = null) { if (!Utilities.HasReactionIntents(this.Client.Configuration.Intents)) throw new InvalidOperationException("No reaction intents are enabled."); var timeout = timeoutoverride ?? this.Config.Timeout; - var returns = await this.MessageReactionAddWaiter.WaitForMatchAsync(new MatchRequest(predicate, timeout)).ConfigureAwait(false); + var returns = await this._messageReactionAddWaiter.WaitForMatchAsync(new MatchRequest(predicate, timeout)).ConfigureAwait(false); return new InteractivityResult(returns == null, returns); } /// /// Wait for a specific reaction. /// For this Event you need the intent specified in /// /// Message reaction was added to. /// User that made the reaction. /// override timeout period. public async Task> WaitForReactionAsync(DiscordMessage message, DiscordUser user, TimeSpan? timeoutoverride = null) => await this.WaitForReactionAsync(x => x.User.Id == user.Id && x.Message.Id == message.Id, timeoutoverride).ConfigureAwait(false); /// /// Waits for a specific reaction. /// For this Event you need the intent specified in /// /// Predicate to match. /// Message reaction was added to. /// User that made the reaction. /// override timeout period. public async Task> WaitForReactionAsync(Func predicate, DiscordMessage message, DiscordUser user, TimeSpan? timeoutoverride = null) => await this.WaitForReactionAsync(x => predicate(x) && x.User.Id == user.Id && x.Message.Id == message.Id, timeoutoverride).ConfigureAwait(false); /// /// Waits for a specific reaction. /// For this Event you need the intent specified in /// /// predicate to match. /// User that made the reaction. /// Override timeout period. public async Task> WaitForReactionAsync(Func predicate, DiscordUser user, TimeSpan? timeoutoverride = null) => await this.WaitForReactionAsync(x => predicate(x) && x.User.Id == user.Id, timeoutoverride).ConfigureAwait(false); /// /// Waits for a user to start typing. /// /// User that starts typing. /// Channel the user is typing in. /// Override timeout period. public async Task> WaitForUserTypingAsync(DiscordUser user, DiscordChannel channel, TimeSpan? timeoutoverride = null) { if (!Utilities.HasTypingIntents(this.Client.Configuration.Intents)) throw new InvalidOperationException("No typing intents are enabled."); var timeout = timeoutoverride ?? this.Config.Timeout; - var returns = await this.TypingStartWaiter.WaitForMatchAsync( + var returns = await this._typingStartWaiter.WaitForMatchAsync( new MatchRequest(x => x.User.Id == user.Id && x.Channel.Id == channel.Id, timeout)) .ConfigureAwait(false); return new InteractivityResult(returns == null, returns); } /// /// Waits for a user to start typing. /// /// User that starts typing. /// Override timeout period. public async Task> WaitForUserTypingAsync(DiscordUser user, TimeSpan? timeoutoverride = null) { if (!Utilities.HasTypingIntents(this.Client.Configuration.Intents)) throw new InvalidOperationException("No typing intents are enabled."); var timeout = timeoutoverride ?? this.Config.Timeout; - var returns = await this.TypingStartWaiter.WaitForMatchAsync( + var returns = await this._typingStartWaiter.WaitForMatchAsync( new MatchRequest(x => x.User.Id == user.Id, timeout)) .ConfigureAwait(false); return new InteractivityResult(returns == null, returns); } /// /// Waits for any user to start typing. /// /// Channel to type in. /// Override timeout period. public async Task> WaitForTypingAsync(DiscordChannel channel, TimeSpan? timeoutoverride = null) { if (!Utilities.HasTypingIntents(this.Client.Configuration.Intents)) throw new InvalidOperationException("No typing intents are enabled."); var timeout = timeoutoverride ?? this.Config.Timeout; - var returns = await this.TypingStartWaiter.WaitForMatchAsync( + var returns = await this._typingStartWaiter.WaitForMatchAsync( new MatchRequest(x => x.Channel.Id == channel.Id, timeout)) .ConfigureAwait(false); return new InteractivityResult(returns == null, returns); } /// /// Collects reactions on a specific message. /// /// Message to collect reactions on. /// Override timeout period. public async Task> CollectReactionsAsync(DiscordMessage m, TimeSpan? timeoutoverride = null) { if (!Utilities.HasReactionIntents(this.Client.Configuration.Intents)) throw new InvalidOperationException("No reaction intents are enabled."); var timeout = timeoutoverride ?? this.Config.Timeout; - var collection = await this.ReactionCollector.CollectAsync(new ReactionCollectRequest(m, timeout)).ConfigureAwait(false); + var collection = await this._reactionCollector.CollectAsync(new ReactionCollectRequest(m, timeout)).ConfigureAwait(false); return collection; } /// /// Waits for specific event args to be received. Make sure the appropriate are registered, if needed. /// /// /// The predicate. /// Override timeout period. public async Task> WaitForEventArgsAsync(Func predicate, TimeSpan? timeoutoverride = null) where T : AsyncEventArgs { var timeout = timeoutoverride ?? this.Config.Timeout; using var waiter = new EventWaiter(this.Client); var res = await waiter.WaitForMatchAsync(new MatchRequest(predicate, timeout)).ConfigureAwait(false); return new InteractivityResult(res == null, res); } /// /// Collects the event arguments. /// /// The predicate. /// Override timeout period. public async Task> CollectEventArgsAsync(Func predicate, TimeSpan? timeoutoverride = null) where T : AsyncEventArgs { var timeout = timeoutoverride ?? this.Config.Timeout; using var waiter = new EventWaiter(this.Client); var res = await waiter.CollectMatchesAsync(new CollectRequest(predicate, timeout)).ConfigureAwait(false); return res; } /// /// Sends a paginated message with buttons. /// /// The channel to send it on. /// User to give control. /// The pages. /// Pagination buttons (pass null to use buttons defined in ). /// Pagination behaviour. /// Deletion behaviour /// A custom cancellation token that can be cancelled at any point. public async Task SendPaginatedMessageAsync( DiscordChannel channel, DiscordUser user, IEnumerable pages, PaginationButtons buttons, PaginationBehaviour? behaviour = default, ButtonPaginationBehavior? deletion = default, CancellationToken token = default) { var bhv = behaviour ?? this.Config.PaginationBehaviour; var del = deletion ?? this.Config.ButtonBehavior; var bts = buttons ?? this.Config.PaginationButtons; bts = new(bts); if (bhv is PaginationBehaviour.Ignore) { bts.SkipLeft.Disable(); bts.Left.Disable(); } var builder = new DiscordMessageBuilder() .WithContent(pages.First().Content) .WithEmbed(pages.First().Embed) .AddComponents(bts.ButtonArray); var message = await builder.SendAsync(channel).ConfigureAwait(false); var req = new ButtonPaginationRequest(message, user, bhv, del, bts, pages.ToArray(), token == default ? this.GetCancellationToken() : token); await this._compPaginator.DoPaginationAsync(req).ConfigureAwait(false); } /// /// Sends a paginated message with buttons. /// /// The channel to send it on. /// User to give control. /// The pages. /// Pagination buttons (pass null to use buttons defined in ). /// Pagination behaviour. /// Deletion behaviour /// Override timeout period. public Task SendPaginatedMessageAsync( DiscordChannel channel, DiscordUser user, IEnumerable pages, PaginationButtons buttons, TimeSpan? timeoutoverride, PaginationBehaviour? behaviour = default, ButtonPaginationBehavior? deletion = default) => this.SendPaginatedMessageAsync(channel, user, pages, buttons, behaviour, deletion, this.GetCancellationToken(timeoutoverride)); /// /// Sends the paginated message. /// /// The channel. /// The user. /// The pages. /// The behaviour. /// The deletion. /// The token. /// A Task. public Task SendPaginatedMessageAsync(DiscordChannel channel, DiscordUser user, IEnumerable pages, PaginationBehaviour? behaviour = default, ButtonPaginationBehavior? deletion = default, CancellationToken token = default) => this.SendPaginatedMessageAsync(channel, user, pages, default, behaviour, deletion, token); /// /// Sends the paginated message. /// /// The channel. /// The user. /// The pages. /// The timeoutoverride. /// The behaviour. /// The deletion. /// A Task. public Task SendPaginatedMessageAsync(DiscordChannel channel, DiscordUser user, IEnumerable pages, TimeSpan? timeoutoverride, PaginationBehaviour? behaviour = default, ButtonPaginationBehavior? deletion = default) => this.SendPaginatedMessageAsync(channel, user, pages, timeoutoverride, behaviour, deletion); /// /// Sends a paginated message. /// For this Event you need the intent specified in /// /// Channel to send paginated message in. /// User to give control. /// Pages. /// Pagination emojis. /// Pagination behaviour (when hitting max and min indices). /// Deletion behaviour. /// Override timeout period. public async Task SendPaginatedMessageAsync(DiscordChannel channel, DiscordUser user, IEnumerable pages, PaginationEmojis emojis, PaginationBehaviour? behaviour = default, PaginationDeletion? deletion = default, TimeSpan? timeoutoverride = null) { var builder = new DiscordMessageBuilder() .WithContent(pages.First().Content) .WithEmbed(pages.First().Embed); var m = await builder.SendAsync(channel).ConfigureAwait(false); var timeout = timeoutoverride ?? this.Config.Timeout; var bhv = behaviour ?? this.Config.PaginationBehaviour; var del = deletion ?? this.Config.PaginationDeletion; var ems = emojis ?? this.Config.PaginationEmojis; var prequest = new PaginationRequest(m, user, bhv, del, ems, timeout, pages.ToArray()); - await this.Paginator.DoPaginationAsync(prequest).ConfigureAwait(false); + await this._paginator.DoPaginationAsync(prequest).ConfigureAwait(false); } /// /// Sends a paginated message in response to an interaction. /// /// Pass the interaction directly. Interactivity will ACK it. /// /// /// The interaction to create a response to. /// Whether the response should be ephemeral. /// The user to listen for button presses from. /// The pages to paginate. /// Optional: custom buttons /// Pagination behaviour. /// Deletion behaviour /// A custom cancellation token that can be cancelled at any point. public async Task SendPaginatedResponseAsync(DiscordInteraction interaction, bool ephemeral, DiscordUser user, IEnumerable pages, PaginationButtons buttons = null, PaginationBehaviour? behaviour = default, ButtonPaginationBehavior? deletion = default, CancellationToken token = default) { var bhv = behaviour ?? this.Config.PaginationBehaviour; var del = deletion ?? this.Config.ButtonBehavior; var bts = buttons ?? this.Config.PaginationButtons; bts = new(bts); if (bhv is PaginationBehaviour.Ignore) { bts.SkipLeft.Disable(); bts.Left.Disable(); } var builder = new DiscordInteractionResponseBuilder() .WithContent(pages.First().Content) .AddEmbed(pages.First().Embed) .AsEphemeral(ephemeral) .AddComponents(bts.ButtonArray); await interaction.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, builder).ConfigureAwait(false); var message = await interaction.GetOriginalResponseAsync().ConfigureAwait(false); var req = new InteractionPaginationRequest(interaction, message, user, bhv, del, bts, pages, token); await this._compPaginator.DoPaginationAsync(req).ConfigureAwait(false); } /// /// Waits for a custom pagination request to finish. /// This does NOT handle removing emojis after finishing for you. /// /// /// - public async Task WaitForCustomPaginationAsync(IPaginationRequest request) => await this.Paginator.DoPaginationAsync(request).ConfigureAwait(false); + public async Task WaitForCustomPaginationAsync(IPaginationRequest request) => await this._paginator.DoPaginationAsync(request).ConfigureAwait(false); /// /// Waits for custom button-based pagination request to finish. ///
/// This does not invoke . ///
/// The request to wait for. public async Task WaitForCustomComponentPaginationAsync(IPaginationRequest request) => await this._compPaginator.DoPaginationAsync(request).ConfigureAwait(false); /// /// Generates pages from a string, and puts them in message content. /// /// Input string. /// How to split input string. /// public IEnumerable GeneratePagesInContent(string input, SplitType splittype = SplitType.Character) { if (string.IsNullOrEmpty(input)) throw new ArgumentException("You must provide a string that is not null or empty!"); var result = new List(); List split; switch (splittype) { default: case SplitType.Character: split = this.SplitString(input, 500).ToList(); break; case SplitType.Line: var subsplit = input.Split('\n'); split = new List(); var s = ""; for (var i = 0; i < subsplit.Length; i++) { s += subsplit[i]; if (i >= 15 && i % 15 == 0) { split.Add(s); s = ""; } } if (split.All(x => x != s)) split.Add(s); break; } var page = 1; foreach (var s in split) { result.Add(new Page($"Page {page}:\n{s}")); page++; } return result; } /// /// Generates pages from a string, and puts them in message embeds. /// /// Input string. /// How to split input string. /// Base embed for output embeds. /// public IEnumerable GeneratePagesInEmbed(string input, SplitType splittype = SplitType.Character, DiscordEmbedBuilder embedbase = null) { if (string.IsNullOrEmpty(input)) throw new ArgumentException("You must provide a string that is not null or empty!"); var embed = embedbase ?? new DiscordEmbedBuilder(); var result = new List(); List split; switch (splittype) { default: case SplitType.Character: split = this.SplitString(input, 500).ToList(); break; case SplitType.Line: var subsplit = input.Split('\n'); split = new List(); var s = ""; for (var i = 0; i < subsplit.Length; i++) { s += $"{subsplit[i]}\n"; if (i % 15 == 0 && i != 0) { split.Add(s); s = ""; } } if (!split.Any(x => x == s)) split.Add(s); break; } var page = 1; foreach (var s in split) { result.Add(new Page("", new DiscordEmbedBuilder(embed).WithDescription(s).WithFooter($"Page {page}/{split.Count}"))); page++; } return result; } /// /// Splits the string. /// /// The string. /// The chunk size. private List SplitString(string str, int chunkSize) { var res = new List(); var len = str.Length; var i = 0; while (i < len) { var size = Math.Min(len - i, chunkSize); res.Add(str.Substring(i, size)); i += size; } return res; } /// /// Gets the cancellation token. /// /// The timeout. private CancellationToken GetCancellationToken(TimeSpan? timeout = null) => new CancellationTokenSource(timeout ?? this.Config.Timeout).Token; /// /// Handles an invalid interaction. /// /// The interaction. private async Task HandleInvalidInteraction(DiscordInteraction interaction) { var at = this.Config.ResponseBehavior switch { InteractionResponseBehavior.Ack => interaction.CreateResponseAsync(InteractionResponseType.DeferredMessageUpdate), InteractionResponseBehavior.Respond => interaction.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, new() { Content = this.Config.ResponseMessage, IsEphemeral = true}), InteractionResponseBehavior.Ignore => Task.CompletedTask, _ => throw new ArgumentException("Unknown enum value.") }; await at; } } } diff --git a/DisCatSharp.Lavalink/Entities/LavalinkUpdates.cs b/DisCatSharp.Lavalink/Entities/LavalinkUpdates.cs index 5fe2a981a..967753d16 100644 --- a/DisCatSharp.Lavalink/Entities/LavalinkUpdates.cs +++ b/DisCatSharp.Lavalink/Entities/LavalinkUpdates.cs @@ -1,303 +1,303 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. #pragma warning disable 0649 using System; using Newtonsoft.Json; namespace DisCatSharp.Lavalink.Entities { /// /// The lavalink state. /// internal sealed class LavalinkState { /// /// Gets the time. /// [JsonIgnore] public DateTimeOffset Time => Utilities.GetDateTimeOffsetFromMilliseconds(this._time); [JsonProperty("time")] private readonly long _time; /// /// Gets the position. /// [JsonIgnore] public TimeSpan Position => TimeSpan.FromMilliseconds(this._position); [JsonProperty("position")] private readonly long _position; } /// /// Represents current state of given player. /// public sealed class LavalinkPlayerState { /// /// Gets the timestamp at which this state was last updated. /// public DateTimeOffset LastUpdate { get; internal set; } /// /// Gets the current playback position. /// public TimeSpan PlaybackPosition { get; internal set; } /// /// Gets the currently-played track. /// public LavalinkTrack CurrentTrack { get; internal set; } } /// /// The lavalink stats. /// internal sealed class LavalinkStats { /// /// Gets or sets the active players. /// [JsonProperty("playingPlayers")] public int ActivePlayers { get; set; } /// /// Gets or sets the total players. /// [JsonProperty("players")] public int TotalPlayers { get; set; } /// /// Gets the uptime. /// [JsonIgnore] public TimeSpan Uptime => TimeSpan.FromMilliseconds(this._uptime); [JsonProperty("uptime")] private readonly long _uptime; /// /// Gets or sets the cpu. /// [JsonProperty("cpu")] public CpuStats Cpu { get; set; } /// /// Gets or sets the memory. /// [JsonProperty("memory")] public MemoryStats Memory { get; set; } /// /// Gets or sets the frames. /// [JsonProperty("frameStats")] public FrameStats Frames { get; set; } /// /// The cpu stats. /// internal sealed class CpuStats { /// /// Gets or sets the cores. /// [JsonProperty("cores")] public int Cores { get; set; } /// /// Gets or sets the system load. /// [JsonProperty("systemLoad")] public double SystemLoad { get; set; } /// /// Gets or sets the lavalink load. /// [JsonProperty("lavalinkLoad")] public double LavalinkLoad { get; set; } } /// /// The memory stats. /// internal sealed class MemoryStats { /// /// Gets or sets the reservable. /// [JsonProperty("reservable")] public long Reservable { get; set; } /// /// Gets or sets the used. /// [JsonProperty("used")] public long Used { get; set; } /// /// Gets or sets the free. /// [JsonProperty("free")] public long Free { get; set; } /// /// Gets or sets the allocated. /// [JsonProperty("allocated")] public long Allocated { get; set; } } /// /// The frame stats. /// internal sealed class FrameStats { /// /// Gets or sets the sent. /// [JsonProperty("sent")] public int Sent { get; set; } /// /// Gets or sets the nulled. /// [JsonProperty("nulled")] public int Nulled { get; set; } /// /// Gets or sets the deficit. /// [JsonProperty("deficit")] public int Deficit { get; set; } } } /// /// Represents statistics of Lavalink resource usage. /// public sealed class LavalinkStatistics { /// /// Gets the number of currently-playing players. /// public int ActivePlayers { get; private set; } /// /// Gets the total number of players. /// public int TotalPlayers { get; private set; } /// /// Gets the node uptime. /// public TimeSpan Uptime { get; private set; } /// /// Gets the number of CPU cores available. /// public int CpuCoreCount { get; private set; } /// /// Gets the total % of CPU resources in use on the system. /// public double CpuSystemLoad { get; private set; } /// /// Gets the total % of CPU resources used by lavalink. /// public double CpuLavalinkLoad { get; private set; } /// /// Gets the amount of reservable RAM, in bytes. /// public long RamReservable { get; private set; } /// /// Gets the amount of used RAM, in bytes. /// public long RamUsed { get; private set; } /// /// Gets the amount of free RAM, in bytes. /// public long RamFree { get; private set; } /// /// Gets the amount of allocated RAM, in bytes. /// public long RamAllocated { get; private set; } /// /// Gets the average number of sent frames per minute. /// public int AverageSentFramesPerMinute { get; private set; } /// /// Gets the average number of frames that were sent as null per minute. /// public int AverageNulledFramesPerMinute { get; private set; } /// /// Gets the average frame deficit per minute. /// public int AverageDeficitFramesPerMinute { get; private set; } - internal bool _updated; + internal bool Updated; /// /// Initializes a new instance of the class. /// internal LavalinkStatistics() { - this._updated = false; + this.Updated = false; } /// /// Updates the stats. /// /// The new stats. internal void Update(LavalinkStats newStats) { - if (!this._updated) - this._updated = true; + if (!this.Updated) + this.Updated = true; this.ActivePlayers = newStats.ActivePlayers; this.TotalPlayers = newStats.TotalPlayers; this.Uptime = newStats.Uptime; this.CpuCoreCount = newStats.Cpu.Cores; this.CpuSystemLoad = newStats.Cpu.SystemLoad; this.CpuLavalinkLoad = newStats.Cpu.LavalinkLoad; this.RamReservable = newStats.Memory.Reservable; this.RamUsed = newStats.Memory.Used; this.RamFree = newStats.Memory.Free; this.RamAllocated = newStats.Memory.Allocated; this.RamReservable = newStats.Memory.Reservable; this.AverageSentFramesPerMinute = newStats.Frames?.Sent ?? 0; this.AverageNulledFramesPerMinute = newStats.Frames?.Nulled ?? 0; this.AverageDeficitFramesPerMinute = newStats.Frames?.Deficit ?? 0; } } } diff --git a/DisCatSharp.Lavalink/LavalinkExtension.cs b/DisCatSharp.Lavalink/LavalinkExtension.cs index 6214d0322..658ffd766 100644 --- a/DisCatSharp.Lavalink/LavalinkExtension.cs +++ b/DisCatSharp.Lavalink/LavalinkExtension.cs @@ -1,215 +1,215 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using DisCatSharp.Common.Utilities; using DisCatSharp.Entities; using DisCatSharp.Lavalink.EventArgs; using DisCatSharp.Net; namespace DisCatSharp.Lavalink { /// /// The lavalink extension. /// public sealed class LavalinkExtension : BaseExtension { /// /// Triggered whenever a node disconnects. /// public event AsyncEventHandler NodeDisconnected { add { this._nodeDisconnected.Register(value); } remove { this._nodeDisconnected.Unregister(value); } } private AsyncEvent _nodeDisconnected; /// /// Gets a dictionary of connected Lavalink nodes for the extension. /// public IReadOnlyDictionary ConnectedNodes { get; } private readonly ConcurrentDictionary _connectedNodes = new(); /// /// Creates a new instance of this Lavalink extension. /// internal LavalinkExtension() { this.ConnectedNodes = new ReadOnlyConcurrentDictionary(this._connectedNodes); } /// /// DO NOT USE THIS MANUALLY. /// /// DO NOT USE THIS MANUALLY. /// protected internal override void Setup(DiscordClient client) { if (this.Client != null) throw new InvalidOperationException("What did I tell you?"); this.Client = client; this._nodeDisconnected = new AsyncEvent("LAVALINK_NODE_DISCONNECTED", TimeSpan.Zero, this.Client.EventErrorHandler); } /// /// Connect to a Lavalink node. /// /// Lavalink client configuration. /// The established Lavalink connection. public async Task ConnectAsync(LavalinkConfiguration config) { if (this._connectedNodes.ContainsKey(config.SocketEndpoint)) return this._connectedNodes[config.SocketEndpoint]; var con = new LavalinkNodeConnection(this.Client, this, config); con.NodeDisconnected += this.Con_NodeDisconnected; con.Disconnected += this.Con_Disconnected; this._connectedNodes[con.NodeEndpoint] = con; try { await con.StartAsync().ConfigureAwait(false); } catch { this.Con_NodeDisconnected(con); throw; } return con; } /// /// Gets the Lavalink node connection for the specified endpoint. /// /// Endpoint at which the node resides. /// Lavalink node connection. public LavalinkNodeConnection GetNodeConnection(ConnectionEndpoint endpoint) => this._connectedNodes.ContainsKey(endpoint) ? this._connectedNodes[endpoint] : null; /// /// Gets a Lavalink node connection based on load balancing and an optional voice region. /// /// The region to compare with the node's , if any. /// The least load affected node connection, or null if no nodes are present. public LavalinkNodeConnection GetIdealNodeConnection(DiscordVoiceRegion region = null) { if (this._connectedNodes.Count <= 1) return this._connectedNodes.Values.FirstOrDefault(); var nodes = this._connectedNodes.Values.ToArray(); if (region != null) { var regionPredicate = new Func(x => x.Region == region); if (nodes.Any(regionPredicate)) nodes = nodes.Where(regionPredicate).ToArray(); if (nodes.Count() <= 1) return nodes.FirstOrDefault(); } return this.FilterByLoad(nodes); } /// /// Gets a Lavalink guild connection from a . /// /// The guild the connection is on. /// The found guild connection, or null if one could not be found. public LavalinkGuildConnection GetGuildConnection(DiscordGuild guild) { var nodes = this._connectedNodes.Values; - var node = nodes.FirstOrDefault(x => x._connectedGuilds.ContainsKey(guild.Id)); + var node = nodes.FirstOrDefault(x => x.ConnectedGuildsInternal.ContainsKey(guild.Id)); return node?.GetGuildConnection(guild); } /// /// Filters the by load. /// /// The nodes. private LavalinkNodeConnection FilterByLoad(LavalinkNodeConnection[] nodes) { Array.Sort(nodes, (a, b) => { - if (!a.Statistics._updated || !b.Statistics._updated) + if (!a.Statistics.Updated || !b.Statistics.Updated) return 0; //https://github.com/FredBoat/Lavalink-Client/blob/48bc27784f57be5b95d2ff2eff6665451b9366f5/src/main/java/lavalink/client/io/LavalinkLoadBalancer.java#L122 //https://github.com/briantanner/eris-lavalink/blob/master/src/PlayerManager.js#L329 //player count var aPenaltyCount = a.Statistics.ActivePlayers; var bPenaltyCount = b.Statistics.ActivePlayers; //cpu load aPenaltyCount += (int)Math.Pow(1.05d, (100 * (a.Statistics.CpuSystemLoad / a.Statistics.CpuCoreCount) * 10) - 10); bPenaltyCount += (int)Math.Pow(1.05d, (100 * (b.Statistics.CpuSystemLoad / a.Statistics.CpuCoreCount) * 10) - 10); //frame load if (a.Statistics.AverageDeficitFramesPerMinute > 0) { //deficit frame load aPenaltyCount += (int)((Math.Pow(1.03d, 500f * (a.Statistics.AverageDeficitFramesPerMinute / 3000f)) * 600) - 600); //null frame load aPenaltyCount += (int)((Math.Pow(1.03d, 500f * (a.Statistics.AverageNulledFramesPerMinute / 3000f)) * 300) - 300); } //frame load if (b.Statistics.AverageDeficitFramesPerMinute > 0) { //deficit frame load bPenaltyCount += (int)((Math.Pow(1.03d, 500f * (b.Statistics.AverageDeficitFramesPerMinute / 3000f)) * 600) - 600); //null frame load bPenaltyCount += (int)((Math.Pow(1.03d, 500f * (b.Statistics.AverageNulledFramesPerMinute / 3000f)) * 300) - 300); } return aPenaltyCount - bPenaltyCount; }); return nodes[0]; } /// /// Removes a node. /// /// The node to be removed. private void Con_NodeDisconnected(LavalinkNodeConnection node) => this._connectedNodes.TryRemove(node.NodeEndpoint, out _); /// /// Disconnects a node. /// /// The affected node. /// The node disconnected event args. private Task Con_Disconnected(LavalinkNodeConnection node, NodeDisconnectedEventArgs e) => this._nodeDisconnected.InvokeAsync(node, e); } } diff --git a/DisCatSharp.Lavalink/LavalinkNodeConnection.cs b/DisCatSharp.Lavalink/LavalinkNodeConnection.cs index e60ccd95e..21ff5d844 100644 --- a/DisCatSharp.Lavalink/LavalinkNodeConnection.cs +++ b/DisCatSharp.Lavalink/LavalinkNodeConnection.cs @@ -1,605 +1,605 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Threading; using System.Threading.Tasks; using DisCatSharp.Common.Utilities; using DisCatSharp.Entities; using DisCatSharp.EventArgs; using DisCatSharp.Lavalink.Entities; using DisCatSharp.Lavalink.EventArgs; using DisCatSharp.Net; using DisCatSharp.Net.WebSocket; using Microsoft.Extensions.Logging; using Newtonsoft.Json; using Newtonsoft.Json.Linq; namespace DisCatSharp.Lavalink { internal delegate void NodeDisconnectedEventHandler(LavalinkNodeConnection node); /// /// Represents a connection to a Lavalink node. /// public sealed class LavalinkNodeConnection { /// /// Triggered whenever Lavalink WebSocket throws an exception. /// public event AsyncEventHandler LavalinkSocketErrored { add { this._lavalinkSocketError.Register(value); } remove { this._lavalinkSocketError.Unregister(value); } } private readonly AsyncEvent _lavalinkSocketError; /// /// Triggered when this node disconnects. /// public event AsyncEventHandler Disconnected { add { this._disconnected.Register(value); } remove { this._disconnected.Unregister(value); } } private readonly AsyncEvent _disconnected; /// /// Triggered when this node receives a statistics update. /// public event AsyncEventHandler StatisticsReceived { add { this._statsReceived.Register(value); } remove { this._statsReceived.Unregister(value); } } private readonly AsyncEvent _statsReceived; /// /// Triggered whenever any of the players on this node is updated. /// public event AsyncEventHandler PlayerUpdated { add { this._playerUpdated.Register(value); } remove { this._playerUpdated.Unregister(value); } } private readonly AsyncEvent _playerUpdated; /// /// Triggered whenever playback of a track starts. /// This is only available for version 3.3.1 and greater. /// public event AsyncEventHandler PlaybackStarted { add { this._playbackStarted.Register(value); } remove { this._playbackStarted.Unregister(value); } } private readonly AsyncEvent _playbackStarted; /// /// Triggered whenever playback of a track finishes. /// public event AsyncEventHandler PlaybackFinished { add { this._playbackFinished.Register(value); } remove { this._playbackFinished.Unregister(value); } } private readonly AsyncEvent _playbackFinished; /// /// Triggered whenever playback of a track gets stuck. /// public event AsyncEventHandler TrackStuck { add { this._trackStuck.Register(value); } remove { this._trackStuck.Unregister(value); } } private readonly AsyncEvent _trackStuck; /// /// Triggered whenever playback of a track encounters an error. /// public event AsyncEventHandler TrackException { add { this._trackException.Register(value); } remove { this._trackException.Unregister(value); } } private readonly AsyncEvent _trackException; /// /// Gets the remote endpoint of this Lavalink node connection. /// public ConnectionEndpoint NodeEndpoint => this.Configuration.SocketEndpoint; /// /// Gets whether the client is connected to Lavalink. /// public bool IsConnected => !Volatile.Read(ref this._isDisposed); private bool _isDisposed = false; private int _backoff = 0; /// /// The minimum backoff. /// private const int MINIMUM_BACKOFF = 7500; /// /// The maximum backoff. /// private const int MAXIMUM_BACKOFF = 120000; /// /// Gets the current resource usage statistics. /// public LavalinkStatistics Statistics { get; } /// /// Gets a dictionary of Lavalink guild connections for this node. /// public IReadOnlyDictionary ConnectedGuilds { get; } - internal ConcurrentDictionary _connectedGuilds = new(); + internal ConcurrentDictionary ConnectedGuildsInternal = new(); /// /// Gets the REST client for this Lavalink connection. /// public LavalinkRestClient Rest { get; } /// /// Gets the parent extension which this node connection belongs to. /// public LavalinkExtension Parent { get; } /// /// Gets the Discord client this node connection belongs to. /// public DiscordClient Discord { get; } /// /// Gets the configuration. /// internal LavalinkConfiguration Configuration { get; } /// /// Gets the region. /// internal DiscordVoiceRegion Region { get; } /// /// Gets or sets the web socket. /// private IWebSocketClient WebSocket { get; set; } /// /// Gets the voice state updates. /// private ConcurrentDictionary> VoiceStateUpdates { get; } /// /// Gets the voice server updates. /// private ConcurrentDictionary> VoiceServerUpdates { get; } /// /// Initializes a new instance of the class. /// /// The client. /// the event.tension. /// The config. internal LavalinkNodeConnection(DiscordClient client, LavalinkExtension extension, LavalinkConfiguration config) { this.Discord = client; this.Parent = extension; this.Configuration = new LavalinkConfiguration(config); if (config.Region != null && this.Discord.VoiceRegions.Values.Contains(config.Region)) this.Region = config.Region; - this.ConnectedGuilds = new ReadOnlyConcurrentDictionary(this._connectedGuilds); + this.ConnectedGuilds = new ReadOnlyConcurrentDictionary(this.ConnectedGuildsInternal); this.Statistics = new LavalinkStatistics(); this._lavalinkSocketError = new AsyncEvent("LAVALINK_SOCKET_ERROR", TimeSpan.Zero, this.Discord.EventErrorHandler); this._disconnected = new AsyncEvent("LAVALINK_NODE_DISCONNECTED", TimeSpan.Zero, this.Discord.EventErrorHandler); this._statsReceived = new AsyncEvent("LAVALINK_STATS_RECEIVED", TimeSpan.Zero, this.Discord.EventErrorHandler); this._playerUpdated = new AsyncEvent("LAVALINK_PLAYER_UPDATED", TimeSpan.Zero, this.Discord.EventErrorHandler); this._playbackStarted = new AsyncEvent("LAVALINK_PLAYBACK_STARTED", TimeSpan.Zero, this.Discord.EventErrorHandler); this._playbackFinished = new AsyncEvent("LAVALINK_PLAYBACK_FINISHED", TimeSpan.Zero, this.Discord.EventErrorHandler); this._trackStuck = new AsyncEvent("LAVALINK_TRACK_STUCK", TimeSpan.Zero, this.Discord.EventErrorHandler); this._trackException = new AsyncEvent("LAVALINK_TRACK_EXCEPTION", TimeSpan.Zero, this.Discord.EventErrorHandler); this.VoiceServerUpdates = new ConcurrentDictionary>(); this.VoiceStateUpdates = new ConcurrentDictionary>(); this.Discord.VoiceStateUpdated += this.Discord_VoiceStateUpdated; this.Discord.VoiceServerUpdated += this.Discord_VoiceServerUpdated; this.Rest = new LavalinkRestClient(this.Configuration, this.Discord); Volatile.Write(ref this._isDisposed, false); } /// /// Establishes a connection to the Lavalink node. /// /// internal async Task StartAsync() { if (this.Discord?.CurrentUser?.Id == null || this.Discord?.ShardCount == null) throw new InvalidOperationException("This operation requires the Discord client to be fully initialized."); this.WebSocket = this.Discord.Configuration.WebSocketClientFactory(this.Discord.Configuration.Proxy, this.Discord.ServiceProvider); this.WebSocket.Connected += this.WebSocket_OnConnect; this.WebSocket.Disconnected += this.WebSocket_OnDisconnect; this.WebSocket.ExceptionThrown += this.WebSocket_OnException; this.WebSocket.MessageReceived += this.WebSocket_OnMessage; this.WebSocket.AddDefaultHeader("Authorization", this.Configuration.Password); this.WebSocket.AddDefaultHeader("Num-Shards", this.Discord.ShardCount.ToString(CultureInfo.InvariantCulture)); this.WebSocket.AddDefaultHeader("User-Id", this.Discord.CurrentUser.Id.ToString(CultureInfo.InvariantCulture)); if (this.Configuration.ResumeKey != null) this.WebSocket.AddDefaultHeader("Resume-Key", this.Configuration.ResumeKey); do { try { if (this._backoff != 0) { await Task.Delay(this._backoff).ConfigureAwait(false); this._backoff = Math.Min(this._backoff * 2, MAXIMUM_BACKOFF); } else { this._backoff = MINIMUM_BACKOFF; } await this.WebSocket.ConnectAsync(new Uri(this.Configuration.SocketEndpoint.ToWebSocketString())).ConfigureAwait(false); break; } catch (PlatformNotSupportedException) { throw; } catch (NotImplementedException) { throw; } catch (Exception ex) { if (!this.Configuration.SocketAutoReconnect || this._backoff == MAXIMUM_BACKOFF) { this.Discord.Logger.LogCritical(LavalinkEvents.LavalinkConnectionError, ex, "Failed to connect to Lavalink."); throw ex; } else { this.Discord.Logger.LogCritical(LavalinkEvents.LavalinkConnectionError, ex, $"Failed to connect to Lavalink, retrying in {this._backoff} ms."); } } } while (this.Configuration.SocketAutoReconnect); Volatile.Write(ref this._isDisposed, false); } /// /// Stops this Lavalink node connection and frees resources. /// /// public async Task StopAsync() { - foreach (var kvp in this._connectedGuilds) + foreach (var kvp in this.ConnectedGuildsInternal) await kvp.Value.DisconnectAsync().ConfigureAwait(false); this.NodeDisconnected?.Invoke(this); Volatile.Write(ref this._isDisposed, true); await this.WebSocket.DisconnectAsync().ConfigureAwait(false); // this should not be here, no? //await this._disconnected.InvokeAsync(this, new NodeDisconnectedEventArgs(this)).ConfigureAwait(false); } /// /// Connects this Lavalink node to specified Discord channel. /// /// Voice channel to connect to. /// Channel connection, which allows for playback control. public async Task ConnectAsync(DiscordChannel channel) { - if (this._connectedGuilds.ContainsKey(channel.Guild.Id)) - return this._connectedGuilds[channel.Guild.Id]; + if (this.ConnectedGuildsInternal.ContainsKey(channel.Guild.Id)) + return this.ConnectedGuildsInternal[channel.Guild.Id]; if (channel.Guild == null || (channel.Type != ChannelType.Voice && channel.Type != ChannelType.Stage)) throw new ArgumentException("Invalid channel specified.", nameof(channel)); var vstut = new TaskCompletionSource(); var vsrut = new TaskCompletionSource(); this.VoiceStateUpdates[channel.Guild.Id] = vstut; this.VoiceServerUpdates[channel.Guild.Id] = vsrut; var vsd = new VoiceDispatch { OpCode = 4, Payload = new VoiceStateUpdatePayload { GuildId = channel.Guild.Id, ChannelId = channel.Id, Deafened = false, Muted = false } }; var vsj = JsonConvert.SerializeObject(vsd, Formatting.None); await (channel.Discord as DiscordClient).WsSendAsync(vsj).ConfigureAwait(false); var vstu = await vstut.Task.ConfigureAwait(false); var vsru = await vsrut.Task.ConfigureAwait(false); await this.SendPayloadAsync(new LavalinkVoiceUpdate(vstu, vsru)).ConfigureAwait(false); var con = new LavalinkGuildConnection(this, channel, vstu); con.ChannelDisconnected += this.Con_ChannelDisconnected; con.PlayerUpdated += (s, e) => this._playerUpdated.InvokeAsync(s, e); con.PlaybackStarted += (s, e) => this._playbackStarted.InvokeAsync(s, e); con.PlaybackFinished += (s, e) => this._playbackFinished.InvokeAsync(s, e); con.TrackStuck += (s, e) => this._trackStuck.InvokeAsync(s, e); con.TrackException += (s, e) => this._trackException.InvokeAsync(s, e); - this._connectedGuilds[channel.Guild.Id] = con; + this.ConnectedGuildsInternal[channel.Guild.Id] = con; return con; } /// /// Gets a Lavalink connection to specified Discord channel. /// /// Guild to get connection for. /// Channel connection, which allows for playback control. public LavalinkGuildConnection GetGuildConnection(DiscordGuild guild) - => this._connectedGuilds.TryGetValue(guild.Id, out var lgc) && lgc.IsConnected ? lgc : null; + => this.ConnectedGuildsInternal.TryGetValue(guild.Id, out var lgc) && lgc.IsConnected ? lgc : null; /// /// Sends the payload async. /// /// The payload. internal async Task SendPayloadAsync(LavalinkPayload payload) => await this.WsSendAsync(JsonConvert.SerializeObject(payload, Formatting.None)).ConfigureAwait(false); /// /// Webs the socket_ on message. /// /// The client. /// the event.ent. private async Task WebSocket_OnMessage(IWebSocketClient client, SocketMessageEventArgs e) { if (e is not SocketTextMessageEventArgs et) { this.Discord.Logger.LogCritical(LavalinkEvents.LavalinkConnectionError, "Lavalink sent binary data - unable to process"); return; } this.Discord.Logger.LogTrace(LavalinkEvents.LavalinkWsRx, et.Message); var json = et.Message; var jsonData = JObject.Parse(json); switch (jsonData["op"].ToString()) { case "playerUpdate": var gid = (ulong)jsonData["guildId"]; var state = jsonData["state"].ToObject(); - if (this._connectedGuilds.TryGetValue(gid, out var lvl)) + if (this.ConnectedGuildsInternal.TryGetValue(gid, out var lvl)) await lvl.InternalUpdatePlayerStateAsync(state).ConfigureAwait(false); break; case "stats": var statsRaw = jsonData.ToObject(); this.Statistics.Update(statsRaw); await this._statsReceived.InvokeAsync(this, new StatisticsReceivedEventArgs(this.Discord.ServiceProvider, this.Statistics)).ConfigureAwait(false); break; case "event": var evtype = jsonData["type"].ToObject(); var guildId = (ulong)jsonData["guildId"]; switch (evtype) { case EventType.TrackStartEvent: - if (this._connectedGuilds.TryGetValue(guildId, out var lvl_evtst)) - await lvl_evtst.InternalPlaybackStartedAsync(jsonData["track"].ToString()).ConfigureAwait(false); + if (this.ConnectedGuildsInternal.TryGetValue(guildId, out var lvlEvtst)) + await lvlEvtst.InternalPlaybackStartedAsync(jsonData["track"].ToString()).ConfigureAwait(false); break; case EventType.TrackEndEvent: var reason = TrackEndReason.Cleanup; switch (jsonData["reason"].ToString()) { case "FINISHED": reason = TrackEndReason.Finished; break; case "LOAD_FAILED": reason = TrackEndReason.LoadFailed; break; case "STOPPED": reason = TrackEndReason.Stopped; break; case "REPLACED": reason = TrackEndReason.Replaced; break; case "CLEANUP": reason = TrackEndReason.Cleanup; break; } - if (this._connectedGuilds.TryGetValue(guildId, out var lvl_evtf)) - await lvl_evtf.InternalPlaybackFinishedAsync(new TrackFinishData { Track = jsonData["track"].ToString(), Reason = reason }).ConfigureAwait(false); + if (this.ConnectedGuildsInternal.TryGetValue(guildId, out var lvlEvtf)) + await lvlEvtf.InternalPlaybackFinishedAsync(new TrackFinishData { Track = jsonData["track"].ToString(), Reason = reason }).ConfigureAwait(false); break; case EventType.TrackStuckEvent: - if (this._connectedGuilds.TryGetValue(guildId, out var lvl_evts)) - await lvl_evts.InternalTrackStuckAsync(new TrackStuckData { Track = jsonData["track"].ToString(), Threshold = (long)jsonData["thresholdMs"] }).ConfigureAwait(false); + if (this.ConnectedGuildsInternal.TryGetValue(guildId, out var lvlEvts)) + await lvlEvts.InternalTrackStuckAsync(new TrackStuckData { Track = jsonData["track"].ToString(), Threshold = (long)jsonData["thresholdMs"] }).ConfigureAwait(false); break; case EventType.TrackExceptionEvent: - if (this._connectedGuilds.TryGetValue(guildId, out var lvl_evte)) - await lvl_evte.InternalTrackExceptionAsync(new TrackExceptionData { Track = jsonData["track"].ToString(), Error = jsonData["error"].ToString() }).ConfigureAwait(false); + if (this.ConnectedGuildsInternal.TryGetValue(guildId, out var lvlEvte)) + await lvlEvte.InternalTrackExceptionAsync(new TrackExceptionData { Track = jsonData["track"].ToString(), Error = jsonData["error"].ToString() }).ConfigureAwait(false); break; case EventType.WebSocketClosedEvent: - if (this._connectedGuilds.TryGetValue(guildId, out var lvl_ewsce)) + if (this.ConnectedGuildsInternal.TryGetValue(guildId, out var lvlEwsce)) { - lvl_ewsce.VoiceWsDisconnectTcs.SetResult(true); - await lvl_ewsce.InternalWebSocketClosedAsync(new WebSocketCloseEventArgs(jsonData["code"].ToObject(), jsonData["reason"].ToString(), jsonData["byRemote"].ToObject(), this.Discord.ServiceProvider)).ConfigureAwait(false); + lvlEwsce.VoiceWsDisconnectTcs.SetResult(true); + await lvlEwsce.InternalWebSocketClosedAsync(new WebSocketCloseEventArgs(jsonData["code"].ToObject(), jsonData["reason"].ToString(), jsonData["byRemote"].ToObject(), this.Discord.ServiceProvider)).ConfigureAwait(false); } break; } break; } } /// /// Webs the socket_ on exception. /// /// The client. /// the event. private Task WebSocket_OnException(IWebSocketClient client, SocketErrorEventArgs e) => this._lavalinkSocketError.InvokeAsync(this, new SocketErrorEventArgs(client.ServiceProvider) { Exception = e.Exception }); /// /// Webs the socket_ on disconnect. /// /// The client. /// the event. private async Task WebSocket_OnDisconnect(IWebSocketClient client, SocketCloseEventArgs e) { if (this.IsConnected && e.CloseCode != 1001 && e.CloseCode != -1) { this.Discord.Logger.LogWarning(LavalinkEvents.LavalinkConnectionClosed, "Connection broken ({0}, '{1}'), reconnecting", e.CloseCode, e.CloseMessage); await this._disconnected.InvokeAsync(this, new NodeDisconnectedEventArgs(this, false)).ConfigureAwait(false); if (this.Configuration.SocketAutoReconnect) await this.StartAsync().ConfigureAwait(false); } else if (e.CloseCode != 1001 && e.CloseCode != -1) { this.Discord.Logger.LogInformation(LavalinkEvents.LavalinkConnectionClosed, "Connection closed ({0}, '{1}')", e.CloseCode, e.CloseMessage); this.NodeDisconnected?.Invoke(this); await this._disconnected.InvokeAsync(this, new NodeDisconnectedEventArgs(this, true)).ConfigureAwait(false); } else { Volatile.Write(ref this._isDisposed, true); this.Discord.Logger.LogWarning(LavalinkEvents.LavalinkConnectionClosed, "Lavalink died"); - foreach (var kvp in this._connectedGuilds) + foreach (var kvp in this.ConnectedGuildsInternal) { await kvp.Value.SendVoiceUpdateAsync().ConfigureAwait(false); - _ = this._connectedGuilds.TryRemove(kvp.Key, out _); + _ = this.ConnectedGuildsInternal.TryRemove(kvp.Key, out _); } this.NodeDisconnected?.Invoke(this); await this._disconnected.InvokeAsync(this, new NodeDisconnectedEventArgs(this, false)).ConfigureAwait(false); if (this.Configuration.SocketAutoReconnect) await this.StartAsync().ConfigureAwait(false); } } /// /// Webs the socket_ on connect. /// /// The client. /// the event.. private async Task WebSocket_OnConnect(IWebSocketClient client, SocketEventArgs ea) { this.Discord.Logger.LogDebug(LavalinkEvents.LavalinkConnected, "Connection to Lavalink node established"); this._backoff = 0; if (this.Configuration.ResumeKey != null) await this.SendPayloadAsync(new LavalinkConfigureResume(this.Configuration.ResumeKey, this.Configuration.ResumeTimeout)).ConfigureAwait(false); } /// /// Con_S the channel disconnected. /// /// The con. private void Con_ChannelDisconnected(LavalinkGuildConnection con) - => this._connectedGuilds.TryRemove(con.GuildId, out _); + => this.ConnectedGuildsInternal.TryRemove(con.GuildId, out _); /// /// Discord voice state updated. /// /// The client. /// the event. private Task Discord_VoiceStateUpdated(DiscordClient client, VoiceStateUpdateEventArgs e) { var gld = e.Guild; if (gld == null) return Task.CompletedTask; if (e.User == null) return Task.CompletedTask; if (e.User.Id == this.Discord.CurrentUser.Id) { - if (this._connectedGuilds.TryGetValue(e.Guild.Id, out var lvlgc)) + if (this.ConnectedGuildsInternal.TryGetValue(e.Guild.Id, out var lvlgc)) lvlgc.VoiceStateUpdate = e; - if (e.After.Channel == null && this.IsConnected && this._connectedGuilds.ContainsKey(gld.Id)) + if (e.After.Channel == null && this.IsConnected && this.ConnectedGuildsInternal.ContainsKey(gld.Id)) { _ = Task.Run(async () => { var delayTask = Task.Delay(this.Configuration.WebSocketCloseTimeout); var tcs = lvlgc.VoiceWsDisconnectTcs.Task; _ = await Task.WhenAny(delayTask, tcs).ConfigureAwait(false); await lvlgc.DisconnectInternalAsync(false, true).ConfigureAwait(false); - _ = this._connectedGuilds.TryRemove(gld.Id, out _); + _ = this.ConnectedGuildsInternal.TryRemove(gld.Id, out _); }); } if (!string.IsNullOrWhiteSpace(e.SessionId) && e.Channel != null && this.VoiceStateUpdates.TryRemove(gld.Id, out var xe)) xe.SetResult(e); } return Task.CompletedTask; } /// /// Discord voice server updated. /// /// The client. /// the event. private Task Discord_VoiceServerUpdated(DiscordClient client, VoiceServerUpdateEventArgs e) { var gld = e.Guild; if (gld == null) return Task.CompletedTask; - if (this._connectedGuilds.TryGetValue(e.Guild.Id, out var lvlgc)) + if (this.ConnectedGuildsInternal.TryGetValue(e.Guild.Id, out var lvlgc)) { var lvlp = new LavalinkVoiceUpdate(lvlgc.VoiceStateUpdate, e); _ = Task.Run(() => this.WsSendAsync(JsonConvert.SerializeObject(lvlp))); } if (this.VoiceServerUpdates.TryRemove(gld.Id, out var xe)) xe.SetResult(e); return Task.CompletedTask; } /// /// Ws the send async. /// /// The payload. private async Task WsSendAsync(string payload) { this.Discord.Logger.LogTrace(LavalinkEvents.LavalinkWsTx, payload); await this.WebSocket.SendMessageAsync(payload).ConfigureAwait(false); } internal event NodeDisconnectedEventHandler NodeDisconnected; } } diff --git a/DisCatSharp.Lavalink/LavalinkTrack.cs b/DisCatSharp.Lavalink/LavalinkTrack.cs index 3f68304ca..418940157 100644 --- a/DisCatSharp.Lavalink/LavalinkTrack.cs +++ b/DisCatSharp.Lavalink/LavalinkTrack.cs @@ -1,225 +1,225 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. #pragma warning disable 0649 using System; using System.Collections.Generic; using System.Runtime.Serialization; using Newtonsoft.Json; using Newtonsoft.Json.Converters; namespace DisCatSharp.Lavalink { /// /// Represents a lavalink track. /// public class LavalinkTrack { /// /// Gets or sets the ID of the track to play. /// [JsonIgnore] public string TrackString { get; set; } /// /// Gets the identifier of the track. /// [JsonProperty("identifier")] public string Identifier { get; internal set; } /// /// Gets whether the track is seekable. /// [JsonProperty("isSeekable")] public bool IsSeekable { get; internal set; } /// /// Gets the author of the track. /// [JsonProperty("author")] public string Author { get; internal set; } /// /// Gets the track's duration. /// [JsonIgnore] - public TimeSpan Length => !this.IsStream ? TimeSpan.FromMilliseconds(this._length) : TimeSpan.Zero; + public TimeSpan Length => !this.IsStream ? TimeSpan.FromMilliseconds(this.LengthInternal) : TimeSpan.Zero; [JsonProperty("length")] - internal long _length; + internal long LengthInternal; /// /// Gets whether the track is a stream. /// [JsonProperty("isStream")] public bool IsStream { get; internal set; } /// /// Gets the starting position of the track. /// [JsonIgnore] - public TimeSpan Position => TimeSpan.FromMilliseconds(this._position); + public TimeSpan Position => TimeSpan.FromMilliseconds(this.PositionInternal); [JsonProperty("position")] - internal long _position; + internal long PositionInternal; /// /// Gets the title of the track. /// [JsonProperty("title")] public string Title { get; internal set; } /// /// Gets the source Uri of this track. /// [JsonProperty("uri")] public Uri Uri { get; internal set; } } /// /// Represents Lavalink track loading results. /// [JsonConverter(typeof(StringEnumConverter))] public enum LavalinkLoadResultType { /// /// Specifies that track was loaded successfully. /// [EnumMember(Value = "TRACK_LOADED")] TrackLoaded, /// /// Specifies that playlist was loaded successfully. /// [EnumMember(Value = "PLAYLIST_LOADED")] PlaylistLoaded, /// /// Specifies that the result set contains search results. /// [EnumMember(Value = "SEARCH_RESULT")] SearchResult, /// /// Specifies that the search yielded no results. /// [EnumMember(Value = "NO_MATCHES")] NoMatches, /// /// Specifies that the track failed to load. /// [EnumMember(Value = "LOAD_FAILED")] LoadFailed } /// /// Represents information about playlist that was loaded by Lavalink. /// public struct LavalinkPlaylistInfo { /// /// Gets the name of the playlist being loaded. /// [JsonProperty("name")] public string Name { get; internal set; } /// /// Gets the index of the track that was selected in this playlist. /// [JsonProperty("selectedTrack")] public int SelectedTrack { get; internal set; } } /// /// Represents information about track loading request. /// public class LavalinkLoadResult { /// /// Gets the loading result type for this request. /// [JsonProperty("loadType")] public LavalinkLoadResultType LoadResultType { get; internal set; } /// /// Gets the information about the playlist loaded as a result of this request. /// Only applicable if is set to . /// [JsonProperty("playlistInfo")] public LavalinkPlaylistInfo PlaylistInfo { get; internal set; } /// /// Gets the exception details if a track loading failed. /// [JsonProperty("exception", NullValueHandling = NullValueHandling.Ignore)] public LavalinkLoadFailedInfo Exception { get; internal set; } /// /// Gets the tracks that were loaded as a result of this request. /// //[JsonProperty("tracks")] [JsonIgnore] public IEnumerable Tracks { get; internal set; } } /// /// Represents properties sent when a Lavalink track is unable to load. /// public struct LavalinkLoadFailedInfo { /// /// Gets the message of the sent exception. /// [JsonProperty("message")] public string Message { get; internal set; } /// /// Gets the severity level of the track loading failure. /// [JsonProperty("severity")] public LoadFailedSeverity Severity { get; internal set; } } /// /// Represents severity level of the track loading failure. /// public enum LoadFailedSeverity { /// /// Indicates a known cause for the failure, and not because of Lavaplayer. /// [EnumMember(Value = "COMMON")] Common, /// /// Indicates an unknown cause for the failure, most likely caused by outside sources. /// [EnumMember(Value = "SUSPICIOUS")] Suspicious, /// /// Indicates an issue with Lavaplayer or otherwise no other way to determine the cause. /// [EnumMember(Value = "FAULT")] Fault } } diff --git a/DisCatSharp.Lavalink/LavalinkUtil.cs b/DisCatSharp.Lavalink/LavalinkUtil.cs index d2ba5297b..e0598ffd2 100644 --- a/DisCatSharp.Lavalink/LavalinkUtil.cs +++ b/DisCatSharp.Lavalink/LavalinkUtil.cs @@ -1,285 +1,285 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System; using System.IO; using System.Text; using DisCatSharp.Lavalink.EventArgs; namespace DisCatSharp.Lavalink { /// /// Various utilities for Lavalink. /// public static class LavalinkUtilities { /// /// Indicates whether a new track should be started after reciving this TrackEndReason. If this is false, either this event is /// already triggered because another track started (REPLACED) or because the player is stopped (STOPPED, CLEANUP). /// public static bool MayStartNext(this TrackEndReason reason) => reason == TrackEndReason.Finished || reason == TrackEndReason.LoadFailed; /// /// Decodes a Lavalink track string. /// /// Track string to decode. /// Decoded Lavalink track. public static LavalinkTrack DecodeTrack(string track) { // https://github.com/sedmelluq/lavaplayer/blob/804cd1038229230052d9b1dee5e6d1741e30e284/main/src/main/java/com/sedmelluq/discord/lavaplayer/player/DefaultAudioPlayerManager.java#L63-L64 const int TRACK_INFO_VERSIONED = 1; //const int TRACK_INFO_VERSION = 2; var raw = Convert.FromBase64String(track); var decoded = new LavalinkTrack { TrackString = track }; using (var ms = new MemoryStream(raw)) using (var br = new JavaBinaryReader(ms)) { // https://github.com/sedmelluq/lavaplayer/blob/b0c536098c4f92e6d03b00f19221021f8f50b19b/main/src/main/java/com/sedmelluq/discord/lavaplayer/tools/io/MessageInput.java#L37-L39 var messageHeader = br.ReadInt32(); var messageFlags = (int) ((messageHeader & 0xC0000000L) >> 30); var messageSize = messageHeader & 0x3FFFFFFF; //if (messageSize != raw.Length) // Warn($"Size conflict: {messageSize} but was {raw.Length}"); // https://github.com/sedmelluq/lavaplayer/blob/804cd1038229230052d9b1dee5e6d1741e30e284/main/src/main/java/com/sedmelluq/discord/lavaplayer/player/DefaultAudioPlayerManager.java#L268 // java bytes are signed // https://docs.oracle.com/javase/7/docs/api/java/io/DataInput.html#readByte() var version = (messageFlags & TRACK_INFO_VERSIONED) != 0 ? (br.ReadSByte() & 0xFF) : 1; //if (version != TRACK_INFO_VERSION) // Warn($"Version conflict: Expected {TRACK_INFO_VERSION} but got {version}"); decoded.Title = br.ReadJavaUtf8(); decoded.Author = br.ReadJavaUtf8(); - decoded._length = br.ReadInt64(); + decoded.LengthInternal = br.ReadInt64(); decoded.Identifier = br.ReadJavaUtf8(); decoded.IsStream = br.ReadBoolean(); var uri = br.ReadNullableString(); decoded.Uri = uri != null && version >= 2 ? new Uri(uri) : null; } return decoded; } } /// /// /// Java's DataOutputStream always uses big-endian, while BinaryReader always uses little-endian. /// This class converts a big-endian stream to little-endian, and includes some helper methods /// for interacting with Lavaplayer/Lavalink. /// internal class JavaBinaryReader : BinaryReader { private static readonly Encoding _utf8NoBom = new UTF8Encoding(); /// /// Initializes a new instance of the class. /// /// The ms. public JavaBinaryReader(Stream ms) : base(ms, _utf8NoBom) { } // https://docs.oracle.com/javase/7/docs/api/java/io/DataInput.html#readUTF() /// /// Reads the java utf8. /// /// A string. public string ReadJavaUtf8() { var length = this.ReadUInt16(); // string size in bytes var bytes = new byte[length]; var amountRead = this.Read(bytes, 0, length); if (amountRead < length) throw new InvalidDataException("EOS unexpected"); var output = new char[length]; var strlen = 0; // i'm gonna blindly assume here that the javadocs had the correct endianness for (var i = 0; i < length; i++) { var value1 = bytes[i]; if ((value1 & 0b10000000) == 0) // highest bit 1 is false { output[strlen++] = (char)value1; continue; } // remember to skip one byte for every extra byte var value2 = bytes[++i]; if ((value1 & 0b00100000) == 0 && // highest bit 3 is false (value1 & 0b11000000) != 0 && // highest bit 1 and 2 are true (value2 & 0b01000000) == 0 && // highest bit 2 is false (value2 & 0b10000000) != 0) // highest bit 1 is true { var value1Chop = (value1 & 0b00011111) << 6; var value2Chop = value2 & 0b00111111; output[strlen++] = (char)(value1Chop | value2Chop); continue; } var value3 = bytes[++i]; if ((value1 & 0b00010000) == 0 && // highest bit 4 is false (value1 & 0b11100000) != 0 && // highest bit 1,2,3 are true (value2 & 0b01000000) == 0 && // highest bit 2 is false (value2 & 0b10000000) != 0 && // highest bit 1 is true (value3 & 0b01000000) == 0 && // highest bit 2 is false (value3 & 0b10000000) != 0) // highest bit 1 is true { var value1Chop = (value1 & 0b00001111) << 12; var value2Chop = (value2 & 0b00111111) << 6; var value3Chop = value3 & 0b00111111; output[strlen++] = (char)(value1Chop | value2Chop | value3Chop); continue; } } return new string(output, 0, strlen); } // https://github.com/sedmelluq/lavaplayer/blob/b0c536098c4f92e6d03b00f19221021f8f50b19b/main/src/main/java/com/sedmelluq/discord/lavaplayer/tools/DataFormatTools.java#L114-L125 /// /// Reads the nullable string. /// /// A string. public string ReadNullableString() => this.ReadBoolean() ? this.ReadJavaUtf8() : null; // swap endianness /// /// Reads the decimal. /// /// A decimal. public override decimal ReadDecimal() => throw new MissingMethodException("This method does not have a Java equivalent"); // from https://github.com/Zoltu/Zoltu.EndianAwareBinaryReaderWriter under CC0 /// /// Reads the single. /// /// A float. public override float ReadSingle() => this.Read(4, BitConverter.ToSingle); /// /// Reads the double. /// /// A double. public override double ReadDouble() => this.Read(8, BitConverter.ToDouble); /// /// Reads the int16. /// /// A short. public override short ReadInt16() => this.Read(2, BitConverter.ToInt16); /// /// Reads the int32. /// /// An int. public override int ReadInt32() => this.Read(4, BitConverter.ToInt32); /// /// Reads the int64. /// /// A long. public override long ReadInt64() => this.Read(8, BitConverter.ToInt64); /// /// Reads the u int16. /// /// An ushort. public override ushort ReadUInt16() => this.Read(2, BitConverter.ToUInt16); /// /// Reads the u int32. /// /// An uint. public override uint ReadUInt32() => this.Read(4, BitConverter.ToUInt32); /// /// Reads the u int64. /// /// An ulong. public override ulong ReadUInt64() => this.Read(8, BitConverter.ToUInt64); /// /// Reads the. /// /// The size. /// The converter. /// A T. private T Read(int size, Func converter) where T : struct { //Contract.Requires(size >= 0); //Contract.Requires(converter != null); var bytes = this.GetNextBytesNativeEndian(size); return converter(bytes, 0); } /// /// Gets the next bytes native endian. /// /// The count. /// An array of byte. private byte[] GetNextBytesNativeEndian(int count) { //Contract.Requires(count >= 0); //Contract.Ensures(Contract.Result() != null); //Contract.Ensures(Contract.Result().Length == count); var bytes = this.GetNextBytes(count); if (BitConverter.IsLittleEndian) Array.Reverse(bytes); return bytes; } /// /// Gets the next bytes. /// /// The count. /// An array of byte. private byte[] GetNextBytes(int count) { //Contract.Requires(count >= 0); //Contract.Ensures(Contract.Result() != null); //Contract.Ensures(Contract.Result().Length == count); var buffer = new byte[count]; var bytesRead = this.BaseStream.Read(buffer, 0, count); return bytesRead != count ? throw new EndOfStreamException() : buffer; } } } diff --git a/DisCatSharp.VoiceNext/Codec/Rtp.cs b/DisCatSharp.VoiceNext/Codec/Rtp.cs index cedf32f33..4f2f4c638 100644 --- a/DisCatSharp.VoiceNext/Codec/Rtp.cs +++ b/DisCatSharp.VoiceNext/Codec/Rtp.cs @@ -1,161 +1,161 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System; using System.Buffers.Binary; namespace DisCatSharp.VoiceNext.Codec { /// /// The rtp. /// internal sealed class Rtp : IDisposable { /// /// The header size. /// public const int HEADER_SIZE = 12; /// /// The rtp no extension. /// private const byte RTP_NO_EXTENSION = 0x80; /// /// The rtp extension. /// private const byte RTP_EXTENSION = 0x90; /// /// The rtp version. /// private const byte RTP_VERSION = 0x78; /// /// Initializes a new instance of the class. /// public Rtp() { } /// /// Encodes the header. /// /// The sequence. /// The timestamp. /// The ssrc. /// The target. public void EncodeHeader(ushort sequence, uint timestamp, uint ssrc, Span target) { if (target.Length < HEADER_SIZE) throw new ArgumentException("Header buffer is too short.", nameof(target)); target[0] = RTP_NO_EXTENSION; target[1] = RTP_VERSION; // Write data big endian BinaryPrimitives.WriteUInt16BigEndian(target[2..], sequence); // header + magic BinaryPrimitives.WriteUInt32BigEndian(target[4..], timestamp); // header + magic + sizeof(sequence) BinaryPrimitives.WriteUInt32BigEndian(target[8..], ssrc); // header + magic + sizeof(sequence) + sizeof(timestamp) } /// /// Are the rtp header. /// /// The source. /// A bool. public bool IsRtpHeader(ReadOnlySpan source) => source.Length >= HEADER_SIZE && (source[0] == RTP_NO_EXTENSION || source[0] == RTP_EXTENSION) && source[1] == RTP_VERSION; /// /// Decodes the header. /// /// The source. /// The sequence. /// The timestamp. /// The ssrc. /// If true, has extension. public void DecodeHeader(ReadOnlySpan source, out ushort sequence, out uint timestamp, out uint ssrc, out bool hasExtension) { if (source.Length < HEADER_SIZE) throw new ArgumentException("Header buffer is too short.", nameof(source)); if ((source[0] != RTP_NO_EXTENSION && source[0] != RTP_EXTENSION) || source[1] != RTP_VERSION) throw new ArgumentException("Invalid RTP header.", nameof(source)); hasExtension = source[0] == RTP_EXTENSION; // Read data big endian sequence = BinaryPrimitives.ReadUInt16BigEndian(source[2..]); timestamp = BinaryPrimitives.ReadUInt32BigEndian(source[4..]); ssrc = BinaryPrimitives.ReadUInt32BigEndian(source[8..]); } /// /// Calculates the packet size. /// /// The encrypted length. /// The encryption mode. /// An int. public int CalculatePacketSize(int encryptedLength, EncryptionMode encryptionMode) { return encryptionMode switch { - EncryptionMode.XSalsa20_Poly1305 => HEADER_SIZE + encryptedLength, - EncryptionMode.XSalsa20_Poly1305_Suffix => HEADER_SIZE + encryptedLength + Interop.SodiumNonceSize, - EncryptionMode.XSalsa20_Poly1305_Lite => HEADER_SIZE + encryptedLength + 4, + EncryptionMode.XSalsa20Poly1305 => HEADER_SIZE + encryptedLength, + EncryptionMode.XSalsa20Poly1305Suffix => HEADER_SIZE + encryptedLength + Interop.SodiumNonceSize, + EncryptionMode.XSalsa20Poly1305Lite => HEADER_SIZE + encryptedLength + 4, _ => throw new ArgumentException("Unsupported encryption mode.", nameof(encryptionMode)), }; } /// /// Gets the data from packet. /// /// The packet. /// The data. /// The encryption mode. public void GetDataFromPacket(ReadOnlySpan packet, out ReadOnlySpan data, EncryptionMode encryptionMode) { switch (encryptionMode) { - case EncryptionMode.XSalsa20_Poly1305: + case EncryptionMode.XSalsa20Poly1305: data = packet[HEADER_SIZE..]; return; - case EncryptionMode.XSalsa20_Poly1305_Suffix: + case EncryptionMode.XSalsa20Poly1305Suffix: data = packet.Slice(HEADER_SIZE, packet.Length - HEADER_SIZE - Interop.SodiumNonceSize); return; - case EncryptionMode.XSalsa20_Poly1305_Lite: + case EncryptionMode.XSalsa20Poly1305Lite: data = packet.Slice(HEADER_SIZE, packet.Length - HEADER_SIZE - 4); break; default: throw new ArgumentException("Unsupported encryption mode.", nameof(encryptionMode)); } } /// /// Disposes the Rtp. /// public void Dispose() { } } } diff --git a/DisCatSharp.VoiceNext/Codec/Sodium.cs b/DisCatSharp.VoiceNext/Codec/Sodium.cs index c4b0dba7f..c344f33df 100644 --- a/DisCatSharp.VoiceNext/Codec/Sodium.cs +++ b/DisCatSharp.VoiceNext/Codec/Sodium.cs @@ -1,292 +1,292 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System; using System.Buffers.Binary; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using System.Runtime.CompilerServices; using System.Security.Cryptography; namespace DisCatSharp.VoiceNext.Codec { /// /// The sodium. /// internal sealed class Sodium : IDisposable { /// /// Gets the supported modes. /// public static IReadOnlyDictionary SupportedModes { get; } /// /// Gets the nonce size. /// public static int NonceSize => Interop.SodiumNonceSize; /// /// Gets the c s p r n g. /// - private RandomNumberGenerator CSPRNG { get; } + private RandomNumberGenerator Csprng { get; } /// /// Gets the buffer. /// private byte[] Buffer { get; } /// /// Gets the key. /// private ReadOnlyMemory Key { get; } /// /// Initializes a new instance of the class. /// static Sodium() { SupportedModes = new ReadOnlyDictionary(new Dictionary() { - ["xsalsa20_poly1305_lite"] = EncryptionMode.XSalsa20_Poly1305_Lite, - ["xsalsa20_poly1305_suffix"] = EncryptionMode.XSalsa20_Poly1305_Suffix, - ["xsalsa20_poly1305"] = EncryptionMode.XSalsa20_Poly1305 + ["xsalsa20_poly1305_lite"] = EncryptionMode.XSalsa20Poly1305Lite, + ["xsalsa20_poly1305_suffix"] = EncryptionMode.XSalsa20Poly1305Suffix, + ["xsalsa20_poly1305"] = EncryptionMode.XSalsa20Poly1305 }); } /// /// Initializes a new instance of the class. /// /// The key. public Sodium(ReadOnlyMemory key) { if (key.Length != Interop.SodiumKeySize) throw new ArgumentException($"Invalid Sodium key size. Key needs to have a length of {Interop.SodiumKeySize} bytes.", nameof(key)); this.Key = key; - this.CSPRNG = RandomNumberGenerator.Create(); + this.Csprng = RandomNumberGenerator.Create(); this.Buffer = new byte[Interop.SodiumNonceSize]; } /// /// Generates the nonce. /// /// The rtp header. /// The target. public void GenerateNonce(ReadOnlySpan rtpHeader, Span target) { if (rtpHeader.Length != Rtp.HEADER_SIZE) throw new ArgumentException($"RTP header needs to have a length of exactly {Rtp.HEADER_SIZE} bytes.", nameof(rtpHeader)); if (target.Length != Interop.SodiumNonceSize) throw new ArgumentException($"Invalid nonce buffer size. Target buffer for the nonce needs to have a capacity of {Interop.SodiumNonceSize} bytes.", nameof(target)); // Write the header to the beginning of the span. rtpHeader.CopyTo(target); // Zero rest of the span. Helpers.ZeroFill(target[rtpHeader.Length..]); } /// /// Generates the nonce. /// /// The target. public void GenerateNonce(Span target) { if (target.Length != Interop.SodiumNonceSize) throw new ArgumentException($"Invalid nonce buffer size. Target buffer for the nonce needs to have a capacity of {Interop.SodiumNonceSize} bytes.", nameof(target)); - this.CSPRNG.GetBytes(this.Buffer); + this.Csprng.GetBytes(this.Buffer); this.Buffer.AsSpan().CopyTo(target); } /// /// Generates the nonce. /// /// The nonce. /// The target. public void GenerateNonce(uint nonce, Span target) { if (target.Length != Interop.SodiumNonceSize) throw new ArgumentException($"Invalid nonce buffer size. Target buffer for the nonce needs to have a capacity of {Interop.SodiumNonceSize} bytes.", nameof(target)); // Write the uint to memory BinaryPrimitives.WriteUInt32BigEndian(target, nonce); // Zero rest of the buffer. Helpers.ZeroFill(target[4..]); } /// /// Appends the nonce. /// /// The nonce. /// The target. /// The encryption mode. public void AppendNonce(ReadOnlySpan nonce, Span target, EncryptionMode encryptionMode) { switch (encryptionMode) { - case EncryptionMode.XSalsa20_Poly1305: + case EncryptionMode.XSalsa20Poly1305: return; - case EncryptionMode.XSalsa20_Poly1305_Suffix: + case EncryptionMode.XSalsa20Poly1305Suffix: nonce.CopyTo(target[^12..]); return; - case EncryptionMode.XSalsa20_Poly1305_Lite: + case EncryptionMode.XSalsa20Poly1305Lite: nonce[..4].CopyTo(target[^4..]); return; default: throw new ArgumentException("Unsupported encryption mode.", nameof(encryptionMode)); } } /// /// Gets the nonce. /// /// The source. /// The target. /// The encryption mode. public void GetNonce(ReadOnlySpan source, Span target, EncryptionMode encryptionMode) { if (target.Length != Interop.SodiumNonceSize) throw new ArgumentException($"Invalid nonce buffer size. Target buffer for the nonce needs to have a capacity of {Interop.SodiumNonceSize} bytes.", nameof(target)); switch (encryptionMode) { - case EncryptionMode.XSalsa20_Poly1305: + case EncryptionMode.XSalsa20Poly1305: source[..12].CopyTo(target); return; - case EncryptionMode.XSalsa20_Poly1305_Suffix: + case EncryptionMode.XSalsa20Poly1305Suffix: source[^Interop.SodiumNonceSize..].CopyTo(target); return; - case EncryptionMode.XSalsa20_Poly1305_Lite: + case EncryptionMode.XSalsa20Poly1305Lite: source[^4..].CopyTo(target); return; default: throw new ArgumentException("Unsupported encryption mode.", nameof(encryptionMode)); } } /// /// Encrypts the Sodium. /// /// The source. /// The target. /// The nonce. public void Encrypt(ReadOnlySpan source, Span target, ReadOnlySpan nonce) { if (nonce.Length != Interop.SodiumNonceSize) throw new ArgumentException($"Invalid nonce size. Nonce needs to have a length of {Interop.SodiumNonceSize} bytes.", nameof(nonce)); if (target.Length != Interop.SodiumMacSize + source.Length) throw new ArgumentException($"Invalid target buffer size. Target buffer needs to have a length that is a sum of input buffer length and Sodium MAC size ({Interop.SodiumMacSize} bytes).", nameof(target)); int result; if ((result = Interop.Encrypt(source, target, this.Key.Span, nonce)) != 0) throw new CryptographicException($"Could not encrypt the buffer. Sodium returned code {result}."); } /// /// Decrypts the Sodium. /// /// The source. /// The target. /// The nonce. public void Decrypt(ReadOnlySpan source, Span target, ReadOnlySpan nonce) { if (nonce.Length != Interop.SodiumNonceSize) throw new ArgumentException($"Invalid nonce size. Nonce needs to have a length of {Interop.SodiumNonceSize} bytes.", nameof(nonce)); if (target.Length != source.Length - Interop.SodiumMacSize) throw new ArgumentException($"Invalid target buffer size. Target buffer needs to have a length that is input buffer decreased by Sodium MAC size ({Interop.SodiumMacSize} bytes).", nameof(target)); int result; if ((result = Interop.Decrypt(source, target, this.Key.Span, nonce)) != 0) throw new CryptographicException($"Could not decrypt the buffer. Sodium returned code {result}."); } /// /// Disposes the Sodium. /// - public void Dispose() => this.CSPRNG.Dispose(); + public void Dispose() => this.Csprng.Dispose(); /// /// Selects the mode. /// /// The available modes. /// A KeyValuePair. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static KeyValuePair SelectMode(IEnumerable availableModes) { foreach (var kvMode in SupportedModes) if (availableModes.Contains(kvMode.Key)) return kvMode; throw new CryptographicException("Could not negotiate Sodium encryption modes, as none of the modes offered by Discord are supported. This is usually an indicator that something went very wrong."); } /// /// Calculates the target size. /// /// The source. /// An int. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int CalculateTargetSize(ReadOnlySpan source) => source.Length + Interop.SodiumMacSize; /// /// Calculates the source size. /// /// The source. /// An int. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int CalculateSourceSize(ReadOnlySpan source) => source.Length - Interop.SodiumMacSize; } /// /// Specifies an encryption mode to use with Sodium. /// public enum EncryptionMode { /// /// The nonce is an incrementing uint32 value. It is encoded as big endian value at the beginning of the nonce buffer. The 4 bytes are also appended at the end of the packet. /// - XSalsa20_Poly1305_Lite, + XSalsa20Poly1305Lite, /// /// The nonce consists of random bytes. It is appended at the end of a packet. /// - XSalsa20_Poly1305_Suffix, + XSalsa20Poly1305Suffix, /// /// The nonce consists of the RTP header. Nothing is appended to the packet. /// - XSalsa20_Poly1305 + XSalsa20Poly1305 } } diff --git a/DisCatSharp.VoiceNext/Entities/AudioSender.cs b/DisCatSharp.VoiceNext/Entities/AudioSender.cs index 53cc91f3e..6d5010a69 100644 --- a/DisCatSharp.VoiceNext/Entities/AudioSender.cs +++ b/DisCatSharp.VoiceNext/Entities/AudioSender.cs @@ -1,71 +1,71 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System; using DisCatSharp.Entities; using DisCatSharp.VoiceNext.Codec; namespace DisCatSharp.VoiceNext.Entities { /// /// The audio sender. /// internal class AudioSender : IDisposable { /// /// Gets the s s r c. /// - public uint SSRC { get; } + public uint Ssrc { get; } /// /// Gets the id. /// public ulong Id => this.User?.Id ?? 0; /// /// Gets the decoder. /// public OpusDecoder Decoder { get; } /// /// Gets or sets the user. /// public DiscordUser User { get; set; } = null; /// /// Gets or sets the last sequence. /// public ushort LastSequence { get; set; } = 0; /// /// Initializes a new instance of the class. /// /// The ssrc. /// The decoder. public AudioSender(uint ssrc, OpusDecoder decoder) { - this.SSRC = ssrc; + this.Ssrc = ssrc; this.Decoder = decoder; } /// /// Disposes . /// public void Dispose() => this.Decoder?.Dispose(); } } diff --git a/DisCatSharp.VoiceNext/Entities/VoiceReadyPayload.cs b/DisCatSharp.VoiceNext/Entities/VoiceReadyPayload.cs index 4e11201ab..b7f07eb7e 100644 --- a/DisCatSharp.VoiceNext/Entities/VoiceReadyPayload.cs +++ b/DisCatSharp.VoiceNext/Entities/VoiceReadyPayload.cs @@ -1,63 +1,63 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System.Collections.Generic; using Newtonsoft.Json; namespace DisCatSharp.VoiceNext.Entities { /// /// The voice ready payload. /// internal sealed class VoiceReadyPayload { /// /// Gets or sets the s s r c. /// [JsonProperty("ssrc")] - public uint SSRC { get; set; } + public uint Ssrc { get; set; } /// /// Gets or sets the address. /// [JsonProperty("ip")] public string Address { get; set; } /// /// Gets or sets the port. /// [JsonProperty("port")] public ushort Port { get; set; } /// /// Gets or sets the modes. /// [JsonProperty("modes")] public IReadOnlyList Modes { get; set; } /// /// Gets or sets the heartbeat interval. /// [JsonProperty("heartbeat_interval")] public int HeartbeatInterval { get; set; } } } diff --git a/DisCatSharp.VoiceNext/Entities/VoiceSpeakingPayload.cs b/DisCatSharp.VoiceNext/Entities/VoiceSpeakingPayload.cs index 16a8dd3ec..a2d9fa8b0 100644 --- a/DisCatSharp.VoiceNext/Entities/VoiceSpeakingPayload.cs +++ b/DisCatSharp.VoiceNext/Entities/VoiceSpeakingPayload.cs @@ -1,56 +1,56 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using Newtonsoft.Json; namespace DisCatSharp.VoiceNext.Entities { /// /// The voice speaking payload. /// internal sealed class VoiceSpeakingPayload { /// /// Gets or sets a value indicating whether speaking. /// [JsonProperty("speaking")] public bool Speaking { get; set; } /// /// Gets or sets the delay. /// [JsonProperty("delay", NullValueHandling = NullValueHandling.Ignore)] public int? Delay { get; set; } /// /// Gets or sets the s s r c. /// [JsonProperty("ssrc", NullValueHandling = NullValueHandling.Ignore)] - public uint? SSRC { get; set; } + public uint? Ssrc { get; set; } /// /// Gets or sets the user id. /// [JsonProperty("user_id", NullValueHandling = NullValueHandling.Ignore)] public ulong? UserId { get; set; } } } diff --git a/DisCatSharp.VoiceNext/Entities/VoiceUserJoinPayload.cs b/DisCatSharp.VoiceNext/Entities/VoiceUserJoinPayload.cs index d6d34bc82..e6c41474f 100644 --- a/DisCatSharp.VoiceNext/Entities/VoiceUserJoinPayload.cs +++ b/DisCatSharp.VoiceNext/Entities/VoiceUserJoinPayload.cs @@ -1,44 +1,44 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using Newtonsoft.Json; namespace DisCatSharp.VoiceNext.Entities { /// /// The voice user join payload. /// internal sealed class VoiceUserJoinPayload { /// /// Gets the user id. /// [JsonProperty("user_id")] public ulong UserId { get; private set; } /// /// Gets the s s r c. /// [JsonProperty("audio_ssrc")] - public uint SSRC { get; private set; } + public uint Ssrc { get; private set; } } } diff --git a/DisCatSharp.VoiceNext/EventArgs/VoiceReceiveEventArgs.cs b/DisCatSharp.VoiceNext/EventArgs/VoiceReceiveEventArgs.cs index 86fa60774..913a8cd90 100644 --- a/DisCatSharp.VoiceNext/EventArgs/VoiceReceiveEventArgs.cs +++ b/DisCatSharp.VoiceNext/EventArgs/VoiceReceiveEventArgs.cs @@ -1,76 +1,76 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System; using DisCatSharp.Entities; using DisCatSharp.EventArgs; namespace DisCatSharp.VoiceNext.EventArgs { /// /// Represents arguments for VoiceReceived events. /// public class VoiceReceiveEventArgs : DiscordEventArgs { /// /// Gets the SSRC of the audio source. /// - public uint SSRC { get; internal set; } + public uint Ssrc { get; internal set; } #pragma warning disable CS8632 /// /// Gets the user that sent the audio data. /// public DiscordUser? User { get; internal set; } #pragma warning restore /// /// Gets the received voice data, decoded to PCM format. /// public ReadOnlyMemory PcmData { get; internal set; } /// /// Gets the received voice data, in Opus format. Note that for packets that were lost and/or compensated for, this will be empty. /// public ReadOnlyMemory OpusData { get; internal set; } /// /// Gets the format of the received PCM data. /// /// Important: This isn't always the format set in , and depends on the audio data recieved. /// /// public AudioFormat AudioFormat { get; internal set; } /// /// Gets the millisecond duration of the PCM audio sample. /// public int AudioDuration { get; internal set; } /// /// Initializes a new instance of the class. /// internal VoiceReceiveEventArgs(IServiceProvider provider) : base(provider) { } } } diff --git a/DisCatSharp.VoiceNext/EventArgs/VoiceUserJoinEventArgs.cs b/DisCatSharp.VoiceNext/EventArgs/VoiceUserJoinEventArgs.cs index a9dc54f50..77587479f 100644 --- a/DisCatSharp.VoiceNext/EventArgs/VoiceUserJoinEventArgs.cs +++ b/DisCatSharp.VoiceNext/EventArgs/VoiceUserJoinEventArgs.cs @@ -1,49 +1,49 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System; using DisCatSharp.Entities; using DisCatSharp.EventArgs; namespace DisCatSharp.VoiceNext.EventArgs { /// /// Arguments for . /// public sealed class VoiceUserJoinEventArgs : DiscordEventArgs { /// /// Gets the user who left. /// public DiscordUser User { get; internal set; } /// /// Gets the SSRC of the user who joined. /// - public uint SSRC { get; internal set; } + public uint Ssrc { get; internal set; } /// /// Initializes a new instance of the class. /// internal VoiceUserJoinEventArgs(IServiceProvider provider) : base(provider) { } } } diff --git a/DisCatSharp.VoiceNext/EventArgs/VoiceUserLeaveEventArgs.cs b/DisCatSharp.VoiceNext/EventArgs/VoiceUserLeaveEventArgs.cs index 026a760f4..49b8ab6ff 100644 --- a/DisCatSharp.VoiceNext/EventArgs/VoiceUserLeaveEventArgs.cs +++ b/DisCatSharp.VoiceNext/EventArgs/VoiceUserLeaveEventArgs.cs @@ -1,49 +1,49 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System; using DisCatSharp.Entities; using DisCatSharp.EventArgs; namespace DisCatSharp.VoiceNext.EventArgs { /// /// Arguments for . /// public sealed class VoiceUserLeaveEventArgs : DiscordEventArgs { /// /// Gets the user who left. /// public DiscordUser User { get; internal set; } /// /// Gets the SSRC of the user who left. /// - public uint SSRC { get; internal set; } + public uint Ssrc { get; internal set; } /// /// Initializes a new instance of the class. /// internal VoiceUserLeaveEventArgs(IServiceProvider provider) : base(provider) { } } } diff --git a/DisCatSharp.VoiceNext/VoiceNextConnection.cs b/DisCatSharp.VoiceNext/VoiceNextConnection.cs index f3abe2043..e0440c728 100644 --- a/DisCatSharp.VoiceNext/VoiceNextConnection.cs +++ b/DisCatSharp.VoiceNext/VoiceNextConnection.cs @@ -1,1330 +1,1330 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System; using System.Buffers; using System.Buffers.Binary; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Runtime.InteropServices; using System.Threading; using System.Threading.Channels; using System.Threading.Tasks; using DisCatSharp.Common.Utilities; using DisCatSharp.Entities; using DisCatSharp.EventArgs; using DisCatSharp.Net; using DisCatSharp.Net.Udp; using DisCatSharp.Net.WebSocket; using DisCatSharp.VoiceNext.Codec; using DisCatSharp.VoiceNext.Entities; using DisCatSharp.VoiceNext.EventArgs; using Microsoft.Extensions.Logging; using Newtonsoft.Json; using Newtonsoft.Json.Linq; namespace DisCatSharp.VoiceNext { internal delegate Task VoiceDisconnectedEventHandler(DiscordGuild guild); /// /// VoiceNext connection to a voice channel. /// public sealed class VoiceNextConnection : IDisposable { /// /// Triggered whenever a user speaks in the connected voice channel. /// public event AsyncEventHandler UserSpeaking { add { this._userSpeaking.Register(value); } remove { this._userSpeaking.Unregister(value); } } private readonly AsyncEvent _userSpeaking; /// /// Triggered whenever a user joins voice in the connected guild. /// public event AsyncEventHandler UserJoined { add { this._userJoined.Register(value); } remove { this._userJoined.Unregister(value); } } private readonly AsyncEvent _userJoined; /// /// Triggered whenever a user leaves voice in the connected guild. /// public event AsyncEventHandler UserLeft { add { this._userLeft.Register(value); } remove { this._userLeft.Unregister(value); } } private readonly AsyncEvent _userLeft; /// /// Triggered whenever voice data is received from the connected voice channel. /// public event AsyncEventHandler VoiceReceived { add { this._voiceReceived.Register(value); } remove { this._voiceReceived.Unregister(value); } } private readonly AsyncEvent _voiceReceived; /// /// Triggered whenever voice WebSocket throws an exception. /// public event AsyncEventHandler VoiceSocketErrored { add { this._voiceSocketError.Register(value); } remove { this._voiceSocketError.Unregister(value); } } private readonly AsyncEvent _voiceSocketError; internal event VoiceDisconnectedEventHandler VoiceDisconnected; /// /// Gets the unix epoch. /// - private static DateTimeOffset UnixEpoch { get; } = new DateTimeOffset(1970, 1, 1, 0, 0, 0, TimeSpan.Zero); + private static DateTimeOffset s_unixEpoch { get; } = new DateTimeOffset(1970, 1, 1, 0, 0, 0, TimeSpan.Zero); /// /// Gets the discord. /// private DiscordClient Discord { get; } /// /// Gets the guild. /// private DiscordGuild Guild { get; } /// /// Gets the transmitting s s r cs. /// - private ConcurrentDictionary TransmittingSSRCs { get; } + private ConcurrentDictionary TransmittingSsrCs { get; } /// /// Gets the udp client. /// private BaseUdpClient UdpClient { get; } /// /// Gets or sets the voice ws. /// private IWebSocketClient VoiceWs { get; set; } /// /// Gets or sets the heartbeat task. /// private Task HeartbeatTask { get; set; } /// /// Gets or sets the heartbeat interval. /// private int HeartbeatInterval { get; set; } /// /// Gets or sets the last heartbeat. /// private DateTimeOffset LastHeartbeat { get; set; } /// /// Gets or sets the token source. /// private CancellationTokenSource TokenSource { get; set; } /// /// Gets the token. /// private CancellationToken Token => this.TokenSource.Token; /// /// Gets or sets the server data. /// internal VoiceServerUpdatePayload ServerData { get; set; } /// /// Gets or sets the state data. /// internal VoiceStateUpdatePayload StateData { get; set; } /// /// Gets or sets a value indicating whether resume. /// internal bool Resume { get; set; } /// /// Gets the configuration. /// private VoiceNextConfiguration Configuration { get; } /// /// Gets or sets the opus. /// private Opus Opus { get; set; } /// /// Gets or sets the sodium. /// private Sodium Sodium { get; set; } /// /// Gets or sets the rtp. /// private Rtp Rtp { get; set; } /// /// Gets or sets the selected encryption mode. /// private EncryptionMode SelectedEncryptionMode { get; set; } /// /// Gets or sets the nonce. /// private uint Nonce { get; set; } = 0; /// /// Gets or sets the sequence. /// private ushort Sequence { get; set; } /// /// Gets or sets the timestamp. /// private uint Timestamp { get; set; } /// /// Gets or sets the s s r c. /// - private uint SSRC { get; set; } + private uint Ssrc { get; set; } /// /// Gets or sets the key. /// private byte[] Key { get; set; } /// /// Gets or sets the discovered endpoint. /// private IpEndpoint DiscoveredEndpoint { get; set; } /// /// Gets or sets the web socket endpoint. /// internal ConnectionEndpoint WebSocketEndpoint { get; set; } /// /// Gets or sets the udp endpoint. /// internal ConnectionEndpoint UdpEndpoint { get; set; } /// /// Gets or sets the ready wait. /// private TaskCompletionSource ReadyWait { get; set; } /// /// Gets or sets a value indicating whether is initialized. /// private bool IsInitialized { get; set; } /// /// Gets or sets a value indicating whether is disposed. /// private bool IsDisposed { get; set; } /// /// Gets or sets the playing wait. /// private TaskCompletionSource PlayingWait { get; set; } /// /// Gets the pause event. /// private AsyncManualResetEvent PauseEvent { get; } /// /// Gets or sets the transmit stream. /// private VoiceTransmitSink TransmitStream { get; set; } /// /// Gets the transmit channel. /// private Channel TransmitChannel { get; } /// /// Gets the keepalive timestamps. /// private ConcurrentDictionary KeepaliveTimestamps { get; } private ulong _lastKeepalive = 0; /// /// Gets or sets the sender task. /// private Task SenderTask { get; set; } /// /// Gets or sets the sender token source. /// private CancellationTokenSource SenderTokenSource { get; set; } /// /// Gets the sender token. /// private CancellationToken SenderToken => this.SenderTokenSource.Token; /// /// Gets or sets the receiver task. /// private Task ReceiverTask { get; set; } /// /// Gets or sets the receiver token source. /// private CancellationTokenSource ReceiverTokenSource { get; set; } /// /// Gets the receiver token. /// private CancellationToken ReceiverToken => this.ReceiverTokenSource.Token; /// /// Gets or sets the keepalive task. /// private Task KeepaliveTask { get; set; } /// /// Gets or sets the keepalive token source. /// private CancellationTokenSource KeepaliveTokenSource { get; set; } /// /// Gets the keepalive token. /// private CancellationToken KeepaliveToken => this.KeepaliveTokenSource.Token; private volatile bool _isSpeaking = false; /// /// Gets the audio format used by the Opus encoder. /// public AudioFormat AudioFormat => this.Configuration.AudioFormat; /// /// Gets whether this connection is still playing audio. /// public bool IsPlaying => this.PlayingWait != null && !this.PlayingWait.Task.IsCompleted; /// /// Gets the websocket round-trip time in ms. /// public int WebSocketPing => Volatile.Read(ref this._wsPing); private int _wsPing = 0; /// /// Gets the UDP round-trip time in ms. /// public int UdpPing => Volatile.Read(ref this._udpPing); private int _udpPing = 0; private int _queueCount; /// /// Gets the channel this voice client is connected to. /// public DiscordChannel TargetChannel { get; internal set; } /// /// Initializes a new instance of the class. /// /// The client. /// The guild. /// The channel. /// The config. /// The server. /// The state. internal VoiceNextConnection(DiscordClient client, DiscordGuild guild, DiscordChannel channel, VoiceNextConfiguration config, VoiceServerUpdatePayload server, VoiceStateUpdatePayload state) { this.Discord = client; this.Guild = guild; this.TargetChannel = channel; - this.TransmittingSSRCs = new ConcurrentDictionary(); + this.TransmittingSsrCs = new ConcurrentDictionary(); this._userSpeaking = new AsyncEvent("VNEXT_USER_SPEAKING", TimeSpan.Zero, this.Discord.EventErrorHandler); this._userJoined = new AsyncEvent("VNEXT_USER_JOINED", TimeSpan.Zero, this.Discord.EventErrorHandler); this._userLeft = new AsyncEvent("VNEXT_USER_LEFT", TimeSpan.Zero, this.Discord.EventErrorHandler); this._voiceReceived = new AsyncEvent("VNEXT_VOICE_RECEIVED", TimeSpan.Zero, this.Discord.EventErrorHandler); this._voiceSocketError = new AsyncEvent("VNEXT_WS_ERROR", TimeSpan.Zero, this.Discord.EventErrorHandler); this.TokenSource = new CancellationTokenSource(); this.Configuration = config; this.IsInitialized = false; this.IsDisposed = false; this.Opus = new Opus(this.AudioFormat); //this.Sodium = new Sodium(); this.Rtp = new Rtp(); this.ServerData = server; this.StateData = state; var eps = this.ServerData.Endpoint; var epi = eps.LastIndexOf(':'); var eph = string.Empty; var epp = 443; if (epi != -1) { eph = eps[..epi]; epp = int.Parse(eps[(epi + 1)..]); } else { eph = eps; } this.WebSocketEndpoint = new ConnectionEndpoint { Hostname = eph, Port = epp }; this.ReadyWait = new TaskCompletionSource(); this.PlayingWait = null; this.TransmitChannel = Channel.CreateBounded(new BoundedChannelOptions(this.Configuration.PacketQueueSize)); this.KeepaliveTimestamps = new ConcurrentDictionary(); this.PauseEvent = new AsyncManualResetEvent(true); this.UdpClient = this.Discord.Configuration.UdpClientFactory(); this.VoiceWs = this.Discord.Configuration.WebSocketClientFactory(this.Discord.Configuration.Proxy, this.Discord.ServiceProvider); this.VoiceWs.Disconnected += this.VoiceWS_SocketClosed; this.VoiceWs.MessageReceived += this.VoiceWS_SocketMessage; this.VoiceWs.Connected += this.VoiceWS_SocketOpened; this.VoiceWs.ExceptionThrown += this.VoiceWs_SocketException; } ~VoiceNextConnection() { this.Dispose(); } /// /// Connects to the specified voice channel. /// /// A task representing the connection operation. internal Task ConnectAsync() { var gwuri = new UriBuilder { Scheme = "wss", Host = this.WebSocketEndpoint.Hostname, Query = "encoding=json&v=4" }; return this.VoiceWs.ConnectAsync(gwuri.Uri); } /// /// Reconnects . /// /// A Task. internal Task ReconnectAsync() => this.VoiceWs.DisconnectAsync(); /// /// Starts . /// /// A Task. internal async Task StartAsync() { // Let's announce our intentions to the server var vdp = new VoiceDispatch(); if (!this.Resume) { vdp.OpCode = 0; vdp.Payload = new VoiceIdentifyPayload { ServerId = this.ServerData.GuildId, UserId = this.StateData.UserId.Value, SessionId = this.StateData.SessionId, Token = this.ServerData.Token }; this.Resume = true; } else { vdp.OpCode = 7; vdp.Payload = new VoiceIdentifyPayload { ServerId = this.ServerData.GuildId, SessionId = this.StateData.SessionId, Token = this.ServerData.Token }; } var vdj = JsonConvert.SerializeObject(vdp, Formatting.None); await this.WsSendAsync(vdj).ConfigureAwait(false); } /// /// Waits the for ready async. /// /// A Task. internal Task WaitForReadyAsync() => this.ReadyWait.Task; /// /// Enqueues the packet async. /// /// The packet. /// The token. /// A Task. internal async Task EnqueuePacketAsync(RawVoicePacket packet, CancellationToken token = default) { await this.TransmitChannel.Writer.WriteAsync(packet, token).ConfigureAwait(false); this._queueCount++; } /// /// Prepares the packet. /// /// The pcm. /// The target. /// The length. /// A bool. internal bool PreparePacket(ReadOnlySpan pcm, out byte[] target, out int length) { target = null; length = 0; if (this.IsDisposed) return false; var audioFormat = this.AudioFormat; var packetArray = ArrayPool.Shared.Rent(this.Rtp.CalculatePacketSize(audioFormat.SampleCountToSampleSize(audioFormat.CalculateMaximumFrameSize()), this.SelectedEncryptionMode)); var packet = packetArray.AsSpan(); - this.Rtp.EncodeHeader(this.Sequence, this.Timestamp, this.SSRC, packet); + this.Rtp.EncodeHeader(this.Sequence, this.Timestamp, this.Ssrc, packet); var opus = packet.Slice(Rtp.HEADER_SIZE, pcm.Length); this.Opus.Encode(pcm, ref opus); this.Sequence++; this.Timestamp += (uint)audioFormat.CalculateFrameSize(audioFormat.CalculateSampleDuration(pcm.Length)); Span nonce = stackalloc byte[Sodium.NonceSize]; switch (this.SelectedEncryptionMode) { - case EncryptionMode.XSalsa20_Poly1305: + case EncryptionMode.XSalsa20Poly1305: this.Sodium.GenerateNonce(packet[..Rtp.HEADER_SIZE], nonce); break; - case EncryptionMode.XSalsa20_Poly1305_Suffix: + case EncryptionMode.XSalsa20Poly1305Suffix: this.Sodium.GenerateNonce(nonce); break; - case EncryptionMode.XSalsa20_Poly1305_Lite: + case EncryptionMode.XSalsa20Poly1305Lite: this.Sodium.GenerateNonce(this.Nonce++, nonce); break; default: ArrayPool.Shared.Return(packetArray); throw new Exception("Unsupported encryption mode."); } Span encrypted = stackalloc byte[Sodium.CalculateTargetSize(opus)]; this.Sodium.Encrypt(opus, encrypted, nonce); encrypted.CopyTo(packet[Rtp.HEADER_SIZE..]); packet = packet[..this.Rtp.CalculatePacketSize(encrypted.Length, this.SelectedEncryptionMode)]; this.Sodium.AppendNonce(nonce, packet, this.SelectedEncryptionMode); target = packetArray; length = packet.Length; return true; } /// /// Voices the sender task. /// /// A Task. private async Task VoiceSenderTask() { var token = this.SenderToken; var client = this.UdpClient; var reader = this.TransmitChannel.Reader; byte[] data = null; var length = 0; var synchronizerTicks = (double)Stopwatch.GetTimestamp(); var synchronizerResolution = Stopwatch.Frequency * 0.005; var tickResolution = 10_000_000.0 / Stopwatch.Frequency; this.Discord.Logger.LogDebug(VoiceNextEvents.Misc, "Timer accuracy: {0}/{1} (high resolution? {2})", Stopwatch.Frequency, synchronizerResolution, Stopwatch.IsHighResolution); while (!token.IsCancellationRequested) { await this.PauseEvent.WaitAsync().ConfigureAwait(false); var hasPacket = reader.TryRead(out var rawPacket); if (hasPacket) { this._queueCount--; if (this.PlayingWait == null || this.PlayingWait.Task.IsCompleted) this.PlayingWait = new TaskCompletionSource(); } // Provided by Laura#0090 (214796473689178133); this is Python, but adaptable: // // delay = max(0, self.delay + ((start_time + self.delay * loops) + - time.time())) // // self.delay // sample size // start_time // time since streaming started // loops // number of samples sent // time.time() // DateTime.Now if (hasPacket) { hasPacket = this.PreparePacket(rawPacket.Bytes.Span, out data, out length); if (rawPacket.RentedBuffer != null) ArrayPool.Shared.Return(rawPacket.RentedBuffer); } var durationModifier = hasPacket ? rawPacket.Duration / 5 : 4; var cts = Math.Max(Stopwatch.GetTimestamp() - synchronizerTicks, 0); if (cts < synchronizerResolution * durationModifier) await Task.Delay(TimeSpan.FromTicks((long)(((synchronizerResolution * durationModifier) - cts) * tickResolution))).ConfigureAwait(false); synchronizerTicks += synchronizerResolution * durationModifier; if (!hasPacket) continue; await this.SendSpeakingAsync(true).ConfigureAwait(false); await client.SendAsync(data, length).ConfigureAwait(false); ArrayPool.Shared.Return(data); if (!rawPacket.Silence && this._queueCount == 0) { var nullpcm = new byte[this.AudioFormat.CalculateSampleSize(20)]; for (var i = 0; i < 3; i++) { var nullpacket = new byte[nullpcm.Length]; var nullpacketmem = nullpacket.AsMemory(); await this.EnqueuePacketAsync(new RawVoicePacket(nullpacketmem, 20, true)).ConfigureAwait(false); } } else if (this._queueCount == 0) { await this.SendSpeakingAsync(false).ConfigureAwait(false); this.PlayingWait?.SetResult(true); } } } /// /// Processes the packet. /// /// The data. /// The opus. /// The pcm. /// The pcm packets. /// The voice sender. /// The output format. /// A bool. private bool ProcessPacket(ReadOnlySpan data, ref Memory opus, ref Memory pcm, IList> pcmPackets, out AudioSender voiceSender, out AudioFormat outputFormat) { voiceSender = null; outputFormat = default; if (!this.Rtp.IsRtpHeader(data)) return false; this.Rtp.DecodeHeader(data, out var sequence, out var timestamp, out var ssrc, out var hasExtension); - if (!this.TransmittingSSRCs.TryGetValue(ssrc, out var vtx)) + if (!this.TransmittingSsrCs.TryGetValue(ssrc, out var vtx)) { var decoder = this.Opus.CreateDecoder(); vtx = new AudioSender(ssrc, decoder) { // user isn't present as we haven't received a speaking event yet. User = null }; } voiceSender = vtx; if (sequence <= vtx.LastSequence) // out-of-order packet; discard return false; var gap = vtx.LastSequence != 0 ? sequence - 1 - vtx.LastSequence : 0; if (gap >= 5) this.Discord.Logger.LogWarning(VoiceNextEvents.VoiceReceiveFailure, "5 or more voice packets were dropped when receiving"); Span nonce = stackalloc byte[Sodium.NonceSize]; this.Sodium.GetNonce(data, nonce, this.SelectedEncryptionMode); this.Rtp.GetDataFromPacket(data, out var encryptedOpus, this.SelectedEncryptionMode); var opusSize = Sodium.CalculateSourceSize(encryptedOpus); opus = opus[..opusSize]; var opusSpan = opus.Span; try { this.Sodium.Decrypt(encryptedOpus, opusSpan, nonce); // Strip extensions, if any if (hasExtension) { // RFC 5285, 4.2 One-Byte header // http://www.rfcreader.com/#rfc5285_line186 if (opusSpan[0] == 0xBE && opusSpan[1] == 0xDE) { var headerLen = (opusSpan[2] << 8) | opusSpan[3]; var i = 4; for (; i < headerLen + 4; i++) { var @byte = opusSpan[i]; // ID is currently unused since we skip it anyway //var id = (byte)(@byte >> 4); var length = (byte)(@byte & 0x0F) + 1; i += length; } // Strip extension padding too while (opusSpan[i] == 0) i++; opusSpan = opusSpan[i..]; } // TODO: consider implementing RFC 5285, 4.3. Two-Byte Header } if (opusSpan[0] == 0x90) { // I'm not 100% sure what this header is/does, however removing the data causes no // real issues, and has the added benefit of removing a lot of noise. opusSpan = opusSpan[2..]; } if (gap == 1) { var lastSampleCount = this.Opus.GetLastPacketSampleCount(vtx.Decoder); var fecpcm = new byte[this.AudioFormat.SampleCountToSampleSize(lastSampleCount)]; var fecpcmMem = fecpcm.AsSpan(); this.Opus.Decode(vtx.Decoder, opusSpan, ref fecpcmMem, true, out _); pcmPackets.Add(fecpcm.AsMemory(0, fecpcmMem.Length)); } else if (gap > 1) { var lastSampleCount = this.Opus.GetLastPacketSampleCount(vtx.Decoder); for (var i = 0; i < gap; i++) { var fecpcm = new byte[this.AudioFormat.SampleCountToSampleSize(lastSampleCount)]; var fecpcmMem = fecpcm.AsSpan(); this.Opus.ProcessPacketLoss(vtx.Decoder, lastSampleCount, ref fecpcmMem); pcmPackets.Add(fecpcm.AsMemory(0, fecpcmMem.Length)); } } var pcmSpan = pcm.Span; this.Opus.Decode(vtx.Decoder, opusSpan, ref pcmSpan, false, out outputFormat); pcm = pcm[..pcmSpan.Length]; } finally { vtx.LastSequence = sequence; } return true; } /// /// Processes the voice packet. /// /// The data. /// A Task. private async Task ProcessVoicePacket(byte[] data) { if (data.Length < 13) // minimum packet length return; try { var pcm = new byte[this.AudioFormat.CalculateMaximumFrameSize()]; var pcmMem = pcm.AsMemory(); var opus = new byte[pcm.Length]; var opusMem = opus.AsMemory(); var pcmFillers = new List>(); if (!this.ProcessPacket(data, ref opusMem, ref pcmMem, pcmFillers, out var vtx, out var audioFormat)) return; foreach (var pcmFiller in pcmFillers) await this._voiceReceived.InvokeAsync(this, new VoiceReceiveEventArgs(this.Discord.ServiceProvider) { - SSRC = vtx.SSRC, + Ssrc = vtx.Ssrc, User = vtx.User, PcmData = pcmFiller, OpusData = new byte[0].AsMemory(), AudioFormat = audioFormat, AudioDuration = audioFormat.CalculateSampleDuration(pcmFiller.Length) }).ConfigureAwait(false); await this._voiceReceived.InvokeAsync(this, new VoiceReceiveEventArgs(this.Discord.ServiceProvider) { - SSRC = vtx.SSRC, + Ssrc = vtx.Ssrc, User = vtx.User, PcmData = pcmMem, OpusData = opusMem, AudioFormat = audioFormat, AudioDuration = audioFormat.CalculateSampleDuration(pcmMem.Length) }).ConfigureAwait(false); } catch (Exception ex) { this.Discord.Logger.LogError(VoiceNextEvents.VoiceReceiveFailure, ex, "Exception occurred when decoding incoming audio data"); } } /// /// Processes the keepalive. /// /// The data. private void ProcessKeepalive(byte[] data) { try { var keepalive = BinaryPrimitives.ReadUInt64LittleEndian(data); if (!this.KeepaliveTimestamps.TryRemove(keepalive, out var timestamp)) return; var tdelta = (int)((Stopwatch.GetTimestamp() - timestamp) / (double)Stopwatch.Frequency * 1000); this.Discord.Logger.LogDebug(VoiceNextEvents.VoiceKeepalive, "Received UDP keepalive {0} (ping {1}ms)", keepalive, tdelta); Volatile.Write(ref this._udpPing, tdelta); } catch (Exception ex) { this.Discord.Logger.LogError(VoiceNextEvents.VoiceKeepalive, ex, "Exception occurred when handling keepalive"); } } /// /// Udps the receiver task. /// /// A Task. private async Task UdpReceiverTask() { var token = this.ReceiverToken; var client = this.UdpClient; while (!token.IsCancellationRequested) { var data = await client.ReceiveAsync().ConfigureAwait(false); if (data.Length == 8) this.ProcessKeepalive(data); else if (this.Configuration.EnableIncoming) await this.ProcessVoicePacket(data).ConfigureAwait(false); } } /// /// Sends a speaking status to the connected voice channel. /// /// Whether the current user is speaking or not. /// A task representing the sending operation. public async Task SendSpeakingAsync(bool speaking = true) { if (!this.IsInitialized) throw new InvalidOperationException("The connection is not initialized"); if (this._isSpeaking != speaking) { this._isSpeaking = speaking; var pld = new VoiceDispatch { OpCode = 5, Payload = new VoiceSpeakingPayload { Speaking = speaking, Delay = 0 } }; var plj = JsonConvert.SerializeObject(pld, Formatting.None); await this.WsSendAsync(plj).ConfigureAwait(false); } } /// /// Gets a transmit stream for this connection, optionally specifying a packet size to use with the stream. If a stream is already configured, it will return the existing one. /// /// Duration, in ms, to use for audio packets. /// Transmit stream. public VoiceTransmitSink GetTransmitSink(int sampleDuration = 20) { if (!AudioFormat.AllowedSampleDurations.Contains(sampleDuration)) throw new ArgumentOutOfRangeException(nameof(sampleDuration), "Invalid PCM sample duration specified."); if (this.TransmitStream == null) this.TransmitStream = new VoiceTransmitSink(this, sampleDuration); return this.TransmitStream; } /// /// Asynchronously waits for playback to be finished. Playback is finished when speaking = false is signalled. /// /// A task representing the waiting operation. public async Task WaitForPlaybackFinishAsync() { if (this.PlayingWait != null) await this.PlayingWait.Task.ConfigureAwait(false); } /// /// Pauses playback. /// public void Pause() => this.PauseEvent.Reset(); /// /// Asynchronously resumes playback. /// /// public async Task ResumeAsync() => await this.PauseEvent.SetAsync().ConfigureAwait(false); /// /// Disconnects and disposes this voice connection. /// public void Disconnect() => this.Dispose(); /// /// Disconnects and disposes this voice connection. /// public void Dispose() { if (this.IsDisposed) return; try { this.IsDisposed = true; this.IsInitialized = false; this.TokenSource?.Cancel(); this.SenderTokenSource?.Cancel(); this.ReceiverTokenSource?.Cancel(); } catch (Exception ex) { this.Discord.Logger.LogError(ex, ex.Message); } try { this.VoiceWs.DisconnectAsync().ConfigureAwait(false).GetAwaiter().GetResult(); this.UdpClient.Close(); } catch { } try { this.KeepaliveTokenSource?.Cancel(); this.TokenSource?.Dispose(); this.SenderTokenSource?.Dispose(); this.ReceiverTokenSource?.Dispose(); this.KeepaliveTokenSource?.Dispose(); this.Opus?.Dispose(); this.Opus = null; this.Sodium?.Dispose(); this.Sodium = null; this.Rtp?.Dispose(); this.Rtp = null; } catch (Exception ex) { this.Discord.Logger.LogError(ex, ex.Message); } this.VoiceDisconnected?.Invoke(this.Guild); } /// /// Heartbeats . /// /// A Task. private async Task HeartbeatAsync() { await Task.Yield(); var token = this.Token; while (true) { try { token.ThrowIfCancellationRequested(); var dt = DateTime.Now; this.Discord.Logger.LogTrace(VoiceNextEvents.VoiceHeartbeat, "Sent heartbeat"); var hbd = new VoiceDispatch { OpCode = 3, Payload = UnixTimestamp(dt) }; var hbj = JsonConvert.SerializeObject(hbd); await this.WsSendAsync(hbj).ConfigureAwait(false); this.LastHeartbeat = dt; await Task.Delay(this.HeartbeatInterval).ConfigureAwait(false); } catch (OperationCanceledException) { return; } } } /// /// Keepalives . /// /// A Task. private async Task KeepaliveAsync() { await Task.Yield(); var token = this.KeepaliveToken; var client = this.UdpClient; while (!token.IsCancellationRequested) { var timestamp = Stopwatch.GetTimestamp(); var keepalive = Volatile.Read(ref this._lastKeepalive); Volatile.Write(ref this._lastKeepalive, keepalive + 1); this.KeepaliveTimestamps.TryAdd(keepalive, timestamp); var packet = new byte[8]; BinaryPrimitives.WriteUInt64LittleEndian(packet, keepalive); await client.SendAsync(packet, packet.Length).ConfigureAwait(false); await Task.Delay(5000, token).ConfigureAwait(false); } } /// /// Stage1S . /// /// The voice ready. /// A Task. private async Task Stage1(VoiceReadyPayload voiceReady) { // IP Discovery this.UdpClient.Setup(this.UdpEndpoint); var pck = new byte[70]; PreparePacket(pck); await this.UdpClient.SendAsync(pck, pck.Length).ConfigureAwait(false); var ipd = await this.UdpClient.ReceiveAsync().ConfigureAwait(false); ReadPacket(ipd, out var ip, out var port); this.DiscoveredEndpoint = new IpEndpoint { Address = ip, Port = port }; this.Discord.Logger.LogTrace(VoiceNextEvents.VoiceHandshake, "Endpoint dicovery finished - discovered endpoint is {0}:{1}", ip, port); void PreparePacket(byte[] packet) { - var ssrc = this.SSRC; + var ssrc = this.Ssrc; var packetSpan = packet.AsSpan(); MemoryMarshal.Write(packetSpan, ref ssrc); Helpers.ZeroFill(packetSpan); } void ReadPacket(byte[] packet, out System.Net.IPAddress decodedIp, out ushort decodedPort) { var packetSpan = packet.AsSpan(); var ipString = Utilities.UTF8.GetString(packet, 4, 64 /* 70 - 6 */).TrimEnd('\0'); decodedIp = System.Net.IPAddress.Parse(ipString); decodedPort = BinaryPrimitives.ReadUInt16LittleEndian(packetSpan[68 /* 70 - 2 */..]); } // Select voice encryption mode var selectedEncryptionMode = Sodium.SelectMode(voiceReady.Modes); this.SelectedEncryptionMode = selectedEncryptionMode.Value; // Ready this.Discord.Logger.LogTrace(VoiceNextEvents.VoiceHandshake, "Selected encryption mode is {0}", selectedEncryptionMode.Key); var vsp = new VoiceDispatch { OpCode = 1, Payload = new VoiceSelectProtocolPayload { Protocol = "udp", Data = new VoiceSelectProtocolPayloadData { Address = this.DiscoveredEndpoint.Address.ToString(), Port = (ushort)this.DiscoveredEndpoint.Port, Mode = selectedEncryptionMode.Key } } }; var vsj = JsonConvert.SerializeObject(vsp, Formatting.None); await this.WsSendAsync(vsj).ConfigureAwait(false); this.SenderTokenSource = new CancellationTokenSource(); this.SenderTask = Task.Run(this.VoiceSenderTask, this.SenderToken); this.ReceiverTokenSource = new CancellationTokenSource(); this.ReceiverTask = Task.Run(this.UdpReceiverTask, this.ReceiverToken); } /// /// Stage2S . /// /// The voice session description. /// A Task. private async Task Stage2(VoiceSessionDescriptionPayload voiceSessionDescription) { this.SelectedEncryptionMode = Sodium.SupportedModes[voiceSessionDescription.Mode.ToLowerInvariant()]; this.Discord.Logger.LogTrace(VoiceNextEvents.VoiceHandshake, "Discord updated encryption mode - new mode is {0}", this.SelectedEncryptionMode); // start keepalive this.KeepaliveTokenSource = new CancellationTokenSource(); this.KeepaliveTask = this.KeepaliveAsync(); // send 3 packets of silence to get things going var nullpcm = new byte[this.AudioFormat.CalculateSampleSize(20)]; for (var i = 0; i < 3; i++) { var nullPcm = new byte[nullpcm.Length]; var nullpacketmem = nullPcm.AsMemory(); await this.EnqueuePacketAsync(new RawVoicePacket(nullpacketmem, 20, true)).ConfigureAwait(false); } this.IsInitialized = true; this.ReadyWait.SetResult(true); } /// /// Handles the dispatch. /// /// The jo. /// A Task. private async Task HandleDispatch(JObject jo) { var opc = (int)jo["op"]; var opp = jo["d"] as JObject; switch (opc) { case 2: // READY this.Discord.Logger.LogTrace(VoiceNextEvents.VoiceDispatch, "Received READY (OP2)"); var vrp = opp.ToObject(); - this.SSRC = vrp.SSRC; + this.Ssrc = vrp.Ssrc; this.UdpEndpoint = new ConnectionEndpoint(vrp.Address, vrp.Port); // this is not the valid interval // oh, discord //this.HeartbeatInterval = vrp.HeartbeatInterval; this.HeartbeatTask = Task.Run(this.HeartbeatAsync); await this.Stage1(vrp).ConfigureAwait(false); break; case 4: // SESSION_DESCRIPTION this.Discord.Logger.LogTrace(VoiceNextEvents.VoiceDispatch, "Received SESSION_DESCRIPTION (OP4)"); var vsd = opp.ToObject(); this.Key = vsd.SecretKey; this.Sodium = new Sodium(this.Key.AsMemory()); await this.Stage2(vsd).ConfigureAwait(false); break; case 5: // SPEAKING // Don't spam OP5 // No longer spam, Discord supposedly doesn't send many of these this.Discord.Logger.LogTrace(VoiceNextEvents.VoiceDispatch, "Received SPEAKING (OP5)"); var spd = opp.ToObject(); var foundUserInCache = this.Discord.TryGetCachedUserInternal(spd.UserId.Value, out var resolvedUser); var spk = new UserSpeakingEventArgs(this.Discord.ServiceProvider) { Speaking = spd.Speaking, - SSRC = spd.SSRC.Value, + Ssrc = spd.Ssrc.Value, User = resolvedUser, }; - if (foundUserInCache && this.TransmittingSSRCs.TryGetValue(spk.SSRC, out var txssrc5) && txssrc5.Id == 0) + if (foundUserInCache && this.TransmittingSsrCs.TryGetValue(spk.Ssrc, out var txssrc5) && txssrc5.Id == 0) { txssrc5.User = spk.User; } else { var opus = this.Opus.CreateDecoder(); - var vtx = new AudioSender(spk.SSRC, opus) + var vtx = new AudioSender(spk.Ssrc, opus) { User = await this.Discord.GetUserAsync(spd.UserId.Value).ConfigureAwait(false) }; - if (!this.TransmittingSSRCs.TryAdd(spk.SSRC, vtx)) + if (!this.TransmittingSsrCs.TryAdd(spk.Ssrc, vtx)) this.Opus.DestroyDecoder(opus); } await this._userSpeaking.InvokeAsync(this, spk).ConfigureAwait(false); break; case 6: // HEARTBEAT ACK var dt = DateTime.Now; var ping = (int)(dt - this.LastHeartbeat).TotalMilliseconds; Volatile.Write(ref this._wsPing, ping); this.Discord.Logger.LogTrace(VoiceNextEvents.VoiceDispatch, "Received HEARTBEAT_ACK (OP6, {0}ms)", ping); this.LastHeartbeat = dt; break; case 8: // HELLO // this sends a heartbeat interval that we need to use for heartbeating this.Discord.Logger.LogTrace(VoiceNextEvents.VoiceDispatch, "Received HELLO (OP8)"); this.HeartbeatInterval = opp["heartbeat_interval"].ToObject(); break; case 9: // RESUMED this.Discord.Logger.LogTrace(VoiceNextEvents.VoiceDispatch, "Received RESUMED (OP9)"); this.HeartbeatTask = Task.Run(this.HeartbeatAsync); break; case 12: // CLIENT_CONNECTED this.Discord.Logger.LogTrace(VoiceNextEvents.VoiceDispatch, "Received CLIENT_CONNECTED (OP12)"); var ujpd = opp.ToObject(); var usrj = await this.Discord.GetUserAsync(ujpd.UserId).ConfigureAwait(false); { var opus = this.Opus.CreateDecoder(); - var vtx = new AudioSender(ujpd.SSRC, opus) + var vtx = new AudioSender(ujpd.Ssrc, opus) { User = usrj }; - if (!this.TransmittingSSRCs.TryAdd(vtx.SSRC, vtx)) + if (!this.TransmittingSsrCs.TryAdd(vtx.Ssrc, vtx)) this.Opus.DestroyDecoder(opus); } - await this._userJoined.InvokeAsync(this, new VoiceUserJoinEventArgs(this.Discord.ServiceProvider) { User = usrj, SSRC = ujpd.SSRC }).ConfigureAwait(false); + await this._userJoined.InvokeAsync(this, new VoiceUserJoinEventArgs(this.Discord.ServiceProvider) { User = usrj, Ssrc = ujpd.Ssrc }).ConfigureAwait(false); break; case 13: // CLIENT_DISCONNECTED this.Discord.Logger.LogTrace(VoiceNextEvents.VoiceDispatch, "Received CLIENT_DISCONNECTED (OP13)"); var ulpd = opp.ToObject(); - var txssrc = this.TransmittingSSRCs.FirstOrDefault(x => x.Value.Id == ulpd.UserId); - if (this.TransmittingSSRCs.ContainsKey(txssrc.Key)) + var txssrc = this.TransmittingSsrCs.FirstOrDefault(x => x.Value.Id == ulpd.UserId); + if (this.TransmittingSsrCs.ContainsKey(txssrc.Key)) { - this.TransmittingSSRCs.TryRemove(txssrc.Key, out var txssrc13); + this.TransmittingSsrCs.TryRemove(txssrc.Key, out var txssrc13); this.Opus.DestroyDecoder(txssrc13.Decoder); } var usrl = await this.Discord.GetUserAsync(ulpd.UserId).ConfigureAwait(false); await this._userLeft.InvokeAsync(this, new VoiceUserLeaveEventArgs(this.Discord.ServiceProvider) { User = usrl, - SSRC = txssrc.Key + Ssrc = txssrc.Key }).ConfigureAwait(false); break; default: this.Discord.Logger.LogTrace(VoiceNextEvents.VoiceDispatch, "Received unknown voice opcode (OP{0})", opc); break; } } /// /// Voices the w s_ socket closed. /// /// The client. /// The e. /// A Task. private async Task VoiceWS_SocketClosed(IWebSocketClient client, SocketCloseEventArgs e) { this.Discord.Logger.LogDebug(VoiceNextEvents.VoiceConnectionClose, "Voice WebSocket closed ({0}, '{1}')", e.CloseCode, e.CloseMessage); // generally this should not be disposed on all disconnects, only on requested ones // or something // otherwise problems happen //this.Dispose(); if (e.CloseCode == 4006 || e.CloseCode == 4009) this.Resume = false; if (!this.IsDisposed) { this.TokenSource.Cancel(); this.TokenSource = new CancellationTokenSource(); this.VoiceWs = this.Discord.Configuration.WebSocketClientFactory(this.Discord.Configuration.Proxy, this.Discord.ServiceProvider); this.VoiceWs.Disconnected += this.VoiceWS_SocketClosed; this.VoiceWs.MessageReceived += this.VoiceWS_SocketMessage; this.VoiceWs.Connected += this.VoiceWS_SocketOpened; if (this.Resume) // emzi you dipshit await this.ConnectAsync().ConfigureAwait(false); } } /// /// Voices the w s_ socket message. /// /// The client. /// The e. /// A Task. private Task VoiceWS_SocketMessage(IWebSocketClient client, SocketMessageEventArgs e) { if (e is not SocketTextMessageEventArgs et) { this.Discord.Logger.LogCritical(VoiceNextEvents.VoiceGatewayError, "Discord Voice Gateway sent binary data - unable to process"); return Task.CompletedTask; } this.Discord.Logger.LogTrace(VoiceNextEvents.VoiceWsRx, et.Message); return this.HandleDispatch(JObject.Parse(et.Message)); } /// /// Voices the w s_ socket opened. /// /// The client. /// The e. /// A Task. private Task VoiceWS_SocketOpened(IWebSocketClient client, SocketEventArgs e) => this.StartAsync(); /// /// Voices the ws_ socket exception. /// /// The client. /// The e. /// A Task. private Task VoiceWs_SocketException(IWebSocketClient client, SocketErrorEventArgs e) => this._voiceSocketError.InvokeAsync(this, new SocketErrorEventArgs(this.Discord.ServiceProvider) { Exception = e.Exception }); /// /// Ws the send async. /// /// The payload. /// A Task. private async Task WsSendAsync(string payload) { this.Discord.Logger.LogTrace(VoiceNextEvents.VoiceWsTx, payload); await this.VoiceWs.SendMessageAsync(payload).ConfigureAwait(false); } /// /// Gets the unix timestamp. /// /// The datetine. private static uint UnixTimestamp(DateTime dt) { - var ts = dt - UnixEpoch; + var ts = dt - s_unixEpoch; var sd = ts.TotalSeconds; var si = (uint)sd; return si; } } } diff --git a/DisCatSharp/Clients/BaseDiscordClient.cs b/DisCatSharp/Clients/BaseDiscordClient.cs index 00f0d2ba2..6558d981f 100644 --- a/DisCatSharp/Clients/BaseDiscordClient.cs +++ b/DisCatSharp/Clients/BaseDiscordClient.cs @@ -1,313 +1,313 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. #pragma warning disable CS0618 using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using System.Net.Http; using System.Reflection; using System.Threading.Tasks; using DisCatSharp.Entities; using DisCatSharp.Net; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; namespace DisCatSharp { /// /// Represents a common base for various Discord client implementations. /// public abstract class BaseDiscordClient : IDisposable { /// /// Gets the api client. /// internal protected DiscordApiClient ApiClient { get; } /// /// Gets the configuration. /// internal protected DiscordConfiguration Configuration { get; } /// /// Gets the instance of the logger for this client. /// public ILogger Logger { get; } /// /// Gets the string representing the version of bot lib. /// public string VersionString { get; } /// /// Gets the bot library name. /// public string BotLibrary { get; } /// /// Gets the library team. /// public DisCatSharpTeam LibraryDeveloperTeam => this.ApiClient.GetDisCatSharpTeamAsync().Result; /// /// Gets the current user. /// public DiscordUser CurrentUser { get; internal set; } /// /// Gets the current application. /// public DiscordApplication CurrentApplication { get; internal set; } /// /// Exposes a Http Client for custom operations. /// public HttpClient RestClient { get; internal set; } /// /// Gets the cached guilds for this client. /// public abstract IReadOnlyDictionary Guilds { get; } /// /// Gets the cached users for this client. /// public ConcurrentDictionary UserCache { get; internal set; } /// /// Gets the service provider. /// This allows passing data around without resorting to static members. /// Defaults to null. /// internal IServiceProvider ServiceProvider { get; set; } = new ServiceCollection().BuildServiceProvider(true); /// /// Gets the list of available voice regions. Note that this property will not contain VIP voice regions. /// public IReadOnlyDictionary VoiceRegions - => this._voice_regions_lazy.Value; + => this.VoiceRegionsLazy.Value; /// /// Gets the list of available voice regions. This property is meant as a way to modify . /// protected internal ConcurrentDictionary InternalVoiceRegions { get; set; } - internal Lazy> _voice_regions_lazy; + internal Lazy> VoiceRegionsLazy; /// /// Initializes this Discord API client. /// /// Configuration for this client. protected BaseDiscordClient(DiscordConfiguration config) { this.Configuration = new DiscordConfiguration(config); if (this.Configuration.LoggerFactory == null) { this.Configuration.LoggerFactory = new DefaultLoggerFactory(); this.Configuration.LoggerFactory.AddProvider(new DefaultLoggerProvider(this)); } this.Logger = this.Configuration.LoggerFactory.CreateLogger(); this.ApiClient = new DiscordApiClient(this); this.UserCache = new ConcurrentDictionary(); this.InternalVoiceRegions = new ConcurrentDictionary(); - this._voice_regions_lazy = new Lazy>(() => new ReadOnlyDictionary(this.InternalVoiceRegions)); + this.VoiceRegionsLazy = new Lazy>(() => new ReadOnlyDictionary(this.InternalVoiceRegions)); this.RestClient = this.ApiClient.Rest.HttpClient; var a = typeof(DiscordClient).GetTypeInfo().Assembly; var iv = a.GetCustomAttribute(); if (iv != null) { this.VersionString = iv.InformationalVersion; } else { var v = a.GetName().Version; var vs = v.ToString(3); if (v.Revision > 0) this.VersionString = $"{vs}, CI build {v.Revision}"; } this.BotLibrary = "DisCatSharp"; this.ServiceProvider = config.ServiceProvider; } /// /// Gets the current API application. /// /// Current API application. public async Task GetCurrentApplicationAsync() { var tapp = await this.ApiClient.GetCurrentApplicationInfoAsync().ConfigureAwait(false); var app = new DiscordApplication { Discord = this, Id = tapp.Id, Name = tapp.Name, Description = tapp.Description, Summary = tapp.Summary, IconHash = tapp.IconHash, RpcOrigins = tapp.RpcOrigins != null ? new ReadOnlyCollection(tapp.RpcOrigins) : null, Flags = tapp.Flags, RequiresCodeGrant = tapp.BotRequiresCodeGrant, IsPublic = tapp.IsPublicBot, PrivacyPolicyUrl = tapp.PrivacyPolicyUrl, TermsOfServiceUrl = tapp.TermsOfServiceUrl, CustomInstallUrl = tapp.CustomInstallUrl, InstallParams = tapp.InstallParams, Tags = (tapp.Tags ?? Enumerable.Empty()).ToArray() }; // do team and owners // tbh fuck doing this properly if (tapp.Team == null) { // singular owner app.Owners = new ReadOnlyCollection(new[] { new DiscordUser(tapp.Owner) }); app.Team = null; app.TeamName = null; } else { // team owner app.Team = new DiscordTeam(tapp.Team); var members = tapp.Team.Members .Select(x => new DiscordTeamMember(x) { Team = app.Team, User = new DiscordUser(x.User) }) .ToArray(); var owners = members .Where(x => x.MembershipStatus == DiscordTeamMembershipStatus.Accepted) .Select(x => x.User) .ToArray(); app.Owners = new ReadOnlyCollection(owners); app.Team.Owner = owners.FirstOrDefault(x => x.Id == tapp.Team.OwnerId); app.Team.Members = new ReadOnlyCollection(members); app.TeamName = app.Team.Name; } app.GuildId = tapp.GuildId.HasValue ? tapp.GuildId.Value : null; app.Slug = tapp.Slug.HasValue ? tapp.Slug.Value : null; app.PrimarySkuId = tapp.PrimarySkuId.HasValue ? tapp.PrimarySkuId.Value : null; app.VerifyKey = tapp.VerifyKey.HasValue ? tapp.VerifyKey.Value : null; app.CoverImageHash = tapp.CoverImageHash.HasValue ? tapp.CoverImageHash.Value : null; return app; } /// /// Gets a list of regions /// /// /// Thrown when Discord is unable to process the request. public Task> ListVoiceRegionsAsync() => this.ApiClient.ListVoiceRegionsAsync(); /// /// Initializes this client. This method fetches information about current user, application, and voice regions. /// /// public virtual async Task InitializeAsync() { if (this.CurrentUser == null) { this.CurrentUser = await this.ApiClient.GetCurrentUserAsync().ConfigureAwait(false); this.UserCache.AddOrUpdate(this.CurrentUser.Id, this.CurrentUser, (id, xu) => this.CurrentUser); } if (this.Configuration.TokenType == TokenType.Bot && this.CurrentApplication == null) this.CurrentApplication = await this.GetCurrentApplicationAsync().ConfigureAwait(false); if (this.Configuration.TokenType != TokenType.Bearer && this.InternalVoiceRegions.Count == 0) { var vrs = await this.ListVoiceRegionsAsync().ConfigureAwait(false); foreach (var xvr in vrs) this.InternalVoiceRegions.TryAdd(xvr.Id, xvr); } } /// /// Gets the current gateway info for the provided token. /// If no value is provided, the configuration value will be used instead. /// /// A gateway info object. public async Task GetGatewayInfoAsync(string token = null) { if (this.Configuration.TokenType != TokenType.Bot) throw new InvalidOperationException("Only bot tokens can access this info."); if (string.IsNullOrEmpty(this.Configuration.Token)) { if (string.IsNullOrEmpty(token)) throw new InvalidOperationException("Could not locate a valid token."); this.Configuration.Token = token; var res = await this.ApiClient.GetGatewayInfoAsync().ConfigureAwait(false); this.Configuration.Token = null; return res; } return await this.ApiClient.GetGatewayInfoAsync().ConfigureAwait(false); } /// /// Gets a cached user. /// - /// The user_id. - internal DiscordUser GetCachedOrEmptyUserInternal(ulong user_id) + /// The user_id. + internal DiscordUser GetCachedOrEmptyUserInternal(ulong userId) { - this.TryGetCachedUserInternal(user_id, out var user); + this.TryGetCachedUserInternal(userId, out var user); return user; } /// /// Tries the get a cached user. /// - /// The user_id. + /// The user_id. /// The user. - internal bool TryGetCachedUserInternal(ulong user_id, out DiscordUser user) + internal bool TryGetCachedUserInternal(ulong userId, out DiscordUser user) { - if (this.UserCache.TryGetValue(user_id, out user)) + if (this.UserCache.TryGetValue(userId, out user)) return true; - user = new DiscordUser { Id = user_id, Discord = this }; + user = new DiscordUser { Id = userId, Discord = this }; return false; } /// /// Disposes this client. /// public abstract void Dispose(); } } diff --git a/DisCatSharp/Clients/DiscordClient.Dispatch.cs b/DisCatSharp/Clients/DiscordClient.Dispatch.cs index bba1b608c..04480f8ae 100644 --- a/DisCatSharp/Clients/DiscordClient.Dispatch.cs +++ b/DisCatSharp/Clients/DiscordClient.Dispatch.cs @@ -1,3229 +1,3229 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Globalization; using System.Linq; using System.Threading; using System.Threading.Tasks; using DisCatSharp.Common.Utilities; using DisCatSharp.Entities; using DisCatSharp.Enums; using DisCatSharp.EventArgs; using DisCatSharp.Exceptions; using DisCatSharp.Net.Abstractions; using DisCatSharp.Net.Serialization; using Microsoft.Extensions.Logging; using Newtonsoft.Json.Linq; namespace DisCatSharp { /// /// Represents a discord client. /// public sealed partial class DiscordClient { #region Private Fields private string _sessionId; private bool _guildDownloadCompleted = false; #endregion #region Dispatch Handler /// /// Handles the dispatch. /// /// The payload. internal async Task HandleDispatchAsync(GatewayPayload payload) { if (payload.Data is not JObject dat) { this.Logger.LogWarning(LoggerEvents.WebSocketReceive, "Invalid payload body (this message is probably safe to ignore); opcode: {0} event: {1}; payload: {2}", payload.OpCode, payload.EventName, payload.Data); return; } await this._payloadReceived.InvokeAsync(this, new(this.ServiceProvider) { EventName = payload.EventName, PayloadObject = dat }).ConfigureAwait(false); DiscordChannel chn; ulong gid; ulong cid; ulong uid; DiscordStageInstance stg = default; DiscordIntegration itg = default; DiscordThreadChannel trd = default; DiscordThreadChannelMember trdm = default; DiscordScheduledEvent gse = default; TransportUser usr = default; TransportMember mbr = default; TransportUser refUsr = default; TransportMember refMbr = default; JToken rawMbr = default; var rawRefMsg = dat["referenced_message"]; switch (payload.EventName.ToLowerInvariant()) { #region Gateway Status case "ready": var glds = (JArray)dat["guilds"]; await this.OnReadyEventAsync(dat.ToObject(), glds).ConfigureAwait(false); break; case "resumed": await this.OnResumedAsync().ConfigureAwait(false); break; #endregion #region Channel case "channel_create": chn = dat.ToObject(); await this.OnChannelCreateEventAsync(chn).ConfigureAwait(false); break; case "channel_update": await this.OnChannelUpdateEventAsync(dat.ToObject()).ConfigureAwait(false); break; case "channel_delete": chn = dat.ToObject(); await this.OnChannelDeleteEventAsync(chn.IsPrivate ? dat.ToObject() : chn).ConfigureAwait(false); break; case "channel_pins_update": cid = (ulong)dat["channel_id"]; var ts = (string)dat["last_pin_timestamp"]; await this.OnChannelPinsUpdateAsync((ulong?)dat["guild_id"], cid, ts != null ? DateTimeOffset.Parse(ts, CultureInfo.InvariantCulture) : default(DateTimeOffset?)).ConfigureAwait(false); break; #endregion #region Guild case "guild_create": await this.OnGuildCreateEventAsync(dat.ToDiscordObject(), (JArray)dat["members"], dat["presences"].ToDiscordObject>()).ConfigureAwait(false); break; case "guild_update": await this.OnGuildUpdateEventAsync(dat.ToDiscordObject(), (JArray)dat["members"]).ConfigureAwait(false); break; case "guild_delete": await this.OnGuildDeleteEventAsync(dat.ToDiscordObject()).ConfigureAwait(false); break; case "guild_sync": gid = (ulong)dat["id"]; - await this.OnGuildSyncEventAsync(this._guilds[gid], (bool)dat["large"], (JArray)dat["members"], dat["presences"].ToDiscordObject>()).ConfigureAwait(false); + await this.OnGuildSyncEventAsync(this.GuildsInternal[gid], (bool)dat["large"], (JArray)dat["members"], dat["presences"].ToDiscordObject>()).ConfigureAwait(false); break; case "guild_emojis_update": gid = (ulong)dat["guild_id"]; var ems = dat["emojis"].ToObject>(); - await this.OnGuildEmojisUpdateEventAsync(this._guilds[gid], ems).ConfigureAwait(false); + await this.OnGuildEmojisUpdateEventAsync(this.GuildsInternal[gid], ems).ConfigureAwait(false); break; case "guild_stickers_update": var strs = dat["stickers"].ToDiscordObject>(); await this.OnStickersUpdatedAsync(strs, dat).ConfigureAwait(false); break; case "guild_integrations_update": gid = (ulong)dat["guild_id"]; // discord fires this event inconsistently if the current user leaves a guild. - if (!this._guilds.ContainsKey(gid)) + if (!this.GuildsInternal.ContainsKey(gid)) return; - await this.OnGuildIntegrationsUpdateEventAsync(this._guilds[gid]).ConfigureAwait(false); + await this.OnGuildIntegrationsUpdateEventAsync(this.GuildsInternal[gid]).ConfigureAwait(false); break; /* Ok soooo.. this isn't documented yet It seems to be part of the next version of membership screening (https://discord.com/channels/641574644578648068/689591708962652289/845836910991507486) advaith said the following (https://discord.com/channels/641574644578648068/689591708962652289/845838160047112202): > iirc it happens when a user leaves a server where they havent completed screening yet We have to wait till it's documented, but the fields are: { "user_id": "snowflake_user", "guild_id": "snowflake_guild" } We could handle it rn, but due to the fact that it isn't documented, it's not an good idea. */ case "guild_join_request_delete": break; #endregion #region Guild Ban case "guild_ban_add": usr = dat["user"].ToObject(); gid = (ulong)dat["guild_id"]; - await this.OnGuildBanAddEventAsync(usr, this._guilds[gid]).ConfigureAwait(false); + await this.OnGuildBanAddEventAsync(usr, this.GuildsInternal[gid]).ConfigureAwait(false); break; case "guild_ban_remove": usr = dat["user"].ToObject(); gid = (ulong)dat["guild_id"]; - await this.OnGuildBanRemoveEventAsync(usr, this._guilds[gid]).ConfigureAwait(false); + await this.OnGuildBanRemoveEventAsync(usr, this.GuildsInternal[gid]).ConfigureAwait(false); break; #endregion #region Guild Event case "guild_scheduled_event_create": gse = dat.ToObject(); gid = (ulong)dat["guild_id"]; - await this.OnGuildScheduledEventCreateEventAsync(gse, this._guilds[gid]).ConfigureAwait(false); + await this.OnGuildScheduledEventCreateEventAsync(gse, this.GuildsInternal[gid]).ConfigureAwait(false); break; case "guild_scheduled_event_update": gse = dat.ToObject(); gid = (ulong)dat["guild_id"]; - await this.OnGuildScheduledEventUpdateEventAsync(gse, this._guilds[gid]).ConfigureAwait(false); + await this.OnGuildScheduledEventUpdateEventAsync(gse, this.GuildsInternal[gid]).ConfigureAwait(false); break; case "guild_scheduled_event_delete": gse = dat.ToObject(); gid = (ulong)dat["guild_id"]; - await this.OnGuildScheduledEventDeleteEventAsync(gse, this._guilds[gid]).ConfigureAwait(false); + await this.OnGuildScheduledEventDeleteEventAsync(gse, this.GuildsInternal[gid]).ConfigureAwait(false); break; case "guild_scheduled_event_user_add": gid = (ulong)dat["guild_id"]; uid = (ulong)dat["user_id"]; - await this.OnGuildScheduledEventUserAddedEventAsync((ulong)dat["guild_scheduled_event_id"], uid, this._guilds[gid]).ConfigureAwait(false); + await this.OnGuildScheduledEventUserAddedEventAsync((ulong)dat["guild_scheduled_event_id"], uid, this.GuildsInternal[gid]).ConfigureAwait(false); break; case "guild_scheduled_event_user_remove": gid = (ulong)dat["guild_id"]; uid = (ulong)dat["user_id"]; - await this.OnGuildScheduledEventUserRemovedEventAsync((ulong)dat["guild_scheduled_event_id"], uid, this._guilds[gid]).ConfigureAwait(false); + await this.OnGuildScheduledEventUserRemovedEventAsync((ulong)dat["guild_scheduled_event_id"], uid, this.GuildsInternal[gid]).ConfigureAwait(false); break; #endregion #region Guild Integration case "integration_create": gid = (ulong)dat["guild_id"]; itg = dat.ToObject(); // discord fires this event inconsistently if the current user leaves a guild. - if (!this._guilds.ContainsKey(gid)) + if (!this.GuildsInternal.ContainsKey(gid)) return; - await this.OnGuildIntegrationCreateEventAsync(this._guilds[gid], itg).ConfigureAwait(false); + await this.OnGuildIntegrationCreateEventAsync(this.GuildsInternal[gid], itg).ConfigureAwait(false); break; case "integration_update": gid = (ulong)dat["guild_id"]; itg = dat.ToObject(); // discord fires this event inconsistently if the current user leaves a guild. - if (!this._guilds.ContainsKey(gid)) + if (!this.GuildsInternal.ContainsKey(gid)) return; - await this.OnGuildIntegrationUpdateEventAsync(this._guilds[gid], itg).ConfigureAwait(false); + await this.OnGuildIntegrationUpdateEventAsync(this.GuildsInternal[gid], itg).ConfigureAwait(false); break; case "integration_delete": gid = (ulong)dat["guild_id"]; // discord fires this event inconsistently if the current user leaves a guild. - if (!this._guilds.ContainsKey(gid)) + if (!this.GuildsInternal.ContainsKey(gid)) return; - await this.OnGuildIntegrationDeleteEventAsync(this._guilds[gid], (ulong)dat["id"], (ulong?)dat["application_id"]).ConfigureAwait(false); + await this.OnGuildIntegrationDeleteEventAsync(this.GuildsInternal[gid], (ulong)dat["id"], (ulong?)dat["application_id"]).ConfigureAwait(false); break; #endregion #region Guild Member case "guild_member_add": gid = (ulong)dat["guild_id"]; - await this.OnGuildMemberAddEventAsync(dat.ToObject(), this._guilds[gid]).ConfigureAwait(false); + await this.OnGuildMemberAddEventAsync(dat.ToObject(), this.GuildsInternal[gid]).ConfigureAwait(false); break; case "guild_member_remove": gid = (ulong)dat["guild_id"]; usr = dat["user"].ToObject(); - if (!this._guilds.ContainsKey(gid)) + if (!this.GuildsInternal.ContainsKey(gid)) { // discord fires this event inconsistently if the current user leaves a guild. if (usr.Id != this.CurrentUser.Id) this.Logger.LogError(LoggerEvents.WebSocketReceive, "Could not find {0} in guild cache", gid); return; } - await this.OnGuildMemberRemoveEventAsync(usr, this._guilds[gid]).ConfigureAwait(false); + await this.OnGuildMemberRemoveEventAsync(usr, this.GuildsInternal[gid]).ConfigureAwait(false); break; case "guild_member_update": gid = (ulong)dat["guild_id"]; - await this.OnGuildMemberUpdateEventAsync(dat.ToDiscordObject(), this._guilds[gid], dat["roles"].ToObject>(), (string)dat["nick"], (bool?)dat["pending"]).ConfigureAwait(false); + await this.OnGuildMemberUpdateEventAsync(dat.ToDiscordObject(), this.GuildsInternal[gid], dat["roles"].ToObject>(), (string)dat["nick"], (bool?)dat["pending"]).ConfigureAwait(false); break; case "guild_members_chunk": await this.OnGuildMembersChunkEventAsync(dat).ConfigureAwait(false); break; #endregion #region Guild Role case "guild_role_create": gid = (ulong)dat["guild_id"]; - await this.OnGuildRoleCreateEventAsync(dat["role"].ToObject(), this._guilds[gid]).ConfigureAwait(false); + await this.OnGuildRoleCreateEventAsync(dat["role"].ToObject(), this.GuildsInternal[gid]).ConfigureAwait(false); break; case "guild_role_update": gid = (ulong)dat["guild_id"]; - await this.OnGuildRoleUpdateEventAsync(dat["role"].ToObject(), this._guilds[gid]).ConfigureAwait(false); + await this.OnGuildRoleUpdateEventAsync(dat["role"].ToObject(), this.GuildsInternal[gid]).ConfigureAwait(false); break; case "guild_role_delete": gid = (ulong)dat["guild_id"]; - await this.OnGuildRoleDeleteEventAsync((ulong)dat["role_id"], this._guilds[gid]).ConfigureAwait(false); + await this.OnGuildRoleDeleteEventAsync((ulong)dat["role_id"], this.GuildsInternal[gid]).ConfigureAwait(false); break; #endregion #region Invite case "invite_create": gid = (ulong)dat["guild_id"]; cid = (ulong)dat["channel_id"]; await this.OnInviteCreateEventAsync(cid, gid, dat.ToObject()).ConfigureAwait(false); break; case "invite_delete": gid = (ulong)dat["guild_id"]; cid = (ulong)dat["channel_id"]; await this.OnInviteDeleteEventAsync(cid, gid, dat).ConfigureAwait(false); break; #endregion #region Message case "message_ack": cid = (ulong)dat["channel_id"]; var mid = (ulong)dat["message_id"]; await this.OnMessageAckEventAsync(this.InternalGetCachedChannel(cid), mid).ConfigureAwait(false); break; case "message_create": rawMbr = dat["member"]; if (rawMbr != null) mbr = rawMbr.ToObject(); if (rawRefMsg != null && rawRefMsg.HasValues) { if (rawRefMsg.SelectToken("author") != null) { refUsr = rawRefMsg.SelectToken("author").ToObject(); } if (rawRefMsg.SelectToken("member") != null) { refMbr = rawRefMsg.SelectToken("member").ToObject(); } } await this.OnMessageCreateEventAsync(dat.ToDiscordObject(), dat["author"].ToObject(), mbr, refUsr, refMbr).ConfigureAwait(false); break; case "message_update": rawMbr = dat["member"]; if (rawMbr != null) mbr = rawMbr.ToObject(); if (rawRefMsg != null && rawRefMsg.HasValues) { if (rawRefMsg.SelectToken("author") != null) { refUsr = rawRefMsg.SelectToken("author").ToObject(); } if (rawRefMsg.SelectToken("member") != null) { refMbr = rawRefMsg.SelectToken("member").ToObject(); } } await this.OnMessageUpdateEventAsync(dat.ToDiscordObject(), dat["author"]?.ToObject(), mbr, refUsr, refMbr).ConfigureAwait(false); break; // delete event does *not* include message object case "message_delete": await this.OnMessageDeleteEventAsync((ulong)dat["id"], (ulong)dat["channel_id"], (ulong?)dat["guild_id"]).ConfigureAwait(false); break; case "message_delete_bulk": await this.OnMessageBulkDeleteEventAsync(dat["ids"].ToObject(), (ulong)dat["channel_id"], (ulong?)dat["guild_id"]).ConfigureAwait(false); break; #endregion #region Message Reaction case "message_reaction_add": rawMbr = dat["member"]; if (rawMbr != null) mbr = rawMbr.ToObject(); await this.OnMessageReactionAddAsync((ulong)dat["user_id"], (ulong)dat["message_id"], (ulong)dat["channel_id"], (ulong?)dat["guild_id"], mbr, dat["emoji"].ToObject()).ConfigureAwait(false); break; case "message_reaction_remove": await this.OnMessageReactionRemoveAsync((ulong)dat["user_id"], (ulong)dat["message_id"], (ulong)dat["channel_id"], (ulong?)dat["guild_id"], dat["emoji"].ToObject()).ConfigureAwait(false); break; case "message_reaction_remove_all": await this.OnMessageReactionRemoveAllAsync((ulong)dat["message_id"], (ulong)dat["channel_id"], (ulong?)dat["guild_id"]).ConfigureAwait(false); break; case "message_reaction_remove_emoji": await this.OnMessageReactionRemoveEmojiAsync((ulong)dat["message_id"], (ulong)dat["channel_id"], (ulong)dat["guild_id"], dat["emoji"]).ConfigureAwait(false); break; #endregion #region Stage Instance case "stage_instance_create": stg = dat.ToObject(); await this.OnStageInstanceCreateEventAsync(stg).ConfigureAwait(false); break; case "stage_instance_update": stg = dat.ToObject(); await this.OnStageInstanceUpdateEventAsync(stg).ConfigureAwait(false); break; case "stage_instance_delete": stg = dat.ToObject(); await this.OnStageInstanceDeleteEventAsync(stg).ConfigureAwait(false); break; #endregion #region Thread case "thread_create": trd = dat.ToObject(); await this.OnThreadCreateEventAsync(trd).ConfigureAwait(false); break; case "thread_update": trd = dat.ToObject(); await this.OnThreadUpdateEventAsync(trd).ConfigureAwait(false); break; case "thread_delete": trd = dat.ToObject(); await this.OnThreadDeleteEventAsync(trd).ConfigureAwait(false); break; case "thread_list_sync": gid = (ulong)dat["guild_id"]; //get guild - await this.OnThreadListSyncEventAsync(this._guilds[gid], dat["channel_ids"].ToObject>(), dat["threads"].ToObject>(), dat["members"].ToObject>()).ConfigureAwait(false); + await this.OnThreadListSyncEventAsync(this.GuildsInternal[gid], dat["channel_ids"].ToObject>(), dat["threads"].ToObject>(), dat["members"].ToObject>()).ConfigureAwait(false); break; case "thread_member_update": trdm = dat.ToObject(); await this.OnThreadMemberUpdateEventAsync(trdm).ConfigureAwait(false); break; case "thread_members_update": gid = (ulong)dat["guild_id"]; - await this.OnThreadMembersUpdateEventAsync(this._guilds[gid], (ulong)dat["id"], (JArray)dat["added_members"], (JArray)dat["removed_member_ids"], (int)dat["member_count"]).ConfigureAwait(false); + await this.OnThreadMembersUpdateEventAsync(this.GuildsInternal[gid], (ulong)dat["id"], (JArray)dat["added_members"], (JArray)dat["removed_member_ids"], (int)dat["member_count"]).ConfigureAwait(false); break; #endregion #region Activities case "embedded_activity_update": gid = (ulong)dat["guild_id"]; cid = (ulong)dat["channel_id"]; - await this.OnEmbeddedActivityUpdateAsync((JObject)dat["embedded_activity"], this._guilds[gid], cid, (JArray)dat["users"], (ulong)dat["embedded_activity"]["application_id"]).ConfigureAwait(false); + await this.OnEmbeddedActivityUpdateAsync((JObject)dat["embedded_activity"], this.GuildsInternal[gid], cid, (JArray)dat["users"], (ulong)dat["embedded_activity"]["application_id"]).ConfigureAwait(false); break; #endregion #region User/Presence Update case "presence_update": await this.OnPresenceUpdateEventAsync(dat, (JObject)dat["user"]).ConfigureAwait(false); break; case "user_settings_update": await this.OnUserSettingsUpdateEventAsync(dat.ToObject()).ConfigureAwait(false); break; case "user_update": await this.OnUserUpdateEventAsync(dat.ToObject()).ConfigureAwait(false); break; #endregion #region Voice case "voice_state_update": await this.OnVoiceStateUpdateEventAsync(dat).ConfigureAwait(false); break; case "voice_server_update": gid = (ulong)dat["guild_id"]; - await this.OnVoiceServerUpdateEventAsync((string)dat["endpoint"], (string)dat["token"], this._guilds[gid]).ConfigureAwait(false); + await this.OnVoiceServerUpdateEventAsync((string)dat["endpoint"], (string)dat["token"], this.GuildsInternal[gid]).ConfigureAwait(false); break; #endregion #region Interaction/Integration/Application case "interaction_create": rawMbr = dat["member"]; if (rawMbr != null) { mbr = dat["member"].ToObject(); usr = mbr.User; } else { usr = dat["user"].ToObject(); } cid = (ulong)dat["channel_id"]; await this.OnInteractionCreateAsync((ulong?)dat["guild_id"], cid, usr, mbr, dat.ToDiscordObject()).ConfigureAwait(false); break; case "application_command_create": await this.OnApplicationCommandCreateAsync(dat.ToObject(), (ulong?)dat["guild_id"]).ConfigureAwait(false); break; case "application_command_update": await this.OnApplicationCommandUpdateAsync(dat.ToObject(), (ulong?)dat["guild_id"]).ConfigureAwait(false); break; case "application_command_delete": await this.OnApplicationCommandDeleteAsync(dat.ToObject(), (ulong?)dat["guild_id"]).ConfigureAwait(false); break; case "guild_application_command_counts_update": var counts = dat["application_command_counts"]; await this.OnGuildApplicationCommandCountsUpdateAsync((int)counts["1"], (int)counts["2"], (int)counts["3"], (ulong)dat["guild_id"]).ConfigureAwait(false); break; case "application_command_permissions_update": var aid = (ulong)dat["application_id"]; if (aid != this.CurrentApplication.Id) return; var pms = dat["permissions"].ToObject>(); gid = (ulong)dat["guild_id"]; await this.OnApplicationCommandPermissionsUpdateAsync(pms, (ulong)dat["id"], gid, aid).ConfigureAwait(false); break; #endregion #region Misc case "gift_code_update": //Not supposed to be dispatched to bots break; case "typing_start": cid = (ulong)dat["channel_id"]; rawMbr = dat["member"]; if (rawMbr != null) mbr = rawMbr.ToObject(); await this.OnTypingStartEventAsync((ulong)dat["user_id"], cid, this.InternalGetCachedChannel(cid), (ulong?)dat["guild_id"], Utilities.GetDateTimeOffset((long)dat["timestamp"]), mbr).ConfigureAwait(false); break; case "webhooks_update": gid = (ulong)dat["guild_id"]; cid = (ulong)dat["channel_id"]; - await this.OnWebhooksUpdateAsync(this._guilds[gid].GetChannel(cid), this._guilds[gid]).ConfigureAwait(false); + await this.OnWebhooksUpdateAsync(this.GuildsInternal[gid].GetChannel(cid), this.GuildsInternal[gid]).ConfigureAwait(false); break; default: await this.OnUnknownEventAsync(payload).ConfigureAwait(false); this.Logger.LogWarning(LoggerEvents.WebSocketReceive, "Unknown event: {0}\npayload: {1}", payload.EventName, payload.Data); break; #endregion } } #endregion #region Events #region Gateway /// /// Handles the ready event. /// /// The ready. /// The raw guilds. internal async Task OnReadyEventAsync(ReadyPayload ready, JArray rawGuilds) { //ready.CurrentUser.Discord = this; var rusr = ready.CurrentUser; this.CurrentUser.Username = rusr.Username; this.CurrentUser.Discriminator = rusr.Discriminator; this.CurrentUser.AvatarHash = rusr.AvatarHash; this.CurrentUser.MfaEnabled = rusr.MfaEnabled; this.CurrentUser.Verified = rusr.Verified; this.CurrentUser.IsBot = rusr.IsBot; this.GatewayVersion = ready.GatewayVersion; this._sessionId = ready.SessionId; - var raw_guild_index = rawGuilds.ToDictionary(xt => (ulong)xt["id"], xt => (JObject)xt); + var rawGuildIndex = rawGuilds.ToDictionary(xt => (ulong)xt["id"], xt => (JObject)xt); - this._guilds.Clear(); + this.GuildsInternal.Clear(); foreach (var guild in ready.Guilds) { guild.Discord = this; - if (guild._channels == null) - guild._channels = new ConcurrentDictionary(); + if (guild.ChannelsInternal == null) + guild.ChannelsInternal = new ConcurrentDictionary(); foreach (var xc in guild.Channels.Values) { xc.GuildId = guild.Id; xc.Discord = this; - foreach (var xo in xc._permissionOverwrites) + foreach (var xo in xc.PermissionOverwritesInternal) { xo.Discord = this; - xo._channel_id = xc.Id; + xo.ChannelId = xc.Id; } } - if (guild._roles == null) - guild._roles = new ConcurrentDictionary(); + if (guild.RolesInternal == null) + guild.RolesInternal = new ConcurrentDictionary(); foreach (var xr in guild.Roles.Values) { xr.Discord = this; - xr._guild_id = guild.Id; + xr.GuildId = guild.Id; } - var raw_guild = raw_guild_index[guild.Id]; - var raw_members = (JArray)raw_guild["members"]; + var rawGuild = rawGuildIndex[guild.Id]; + var rawMembers = (JArray)rawGuild["members"]; - if (guild._members != null) - guild._members.Clear(); + if (guild.MembersInternal != null) + guild.MembersInternal.Clear(); else - guild._members = new ConcurrentDictionary(); + guild.MembersInternal = new ConcurrentDictionary(); - if (raw_members != null) + if (rawMembers != null) { - foreach (var xj in raw_members) + foreach (var xj in rawMembers) { var xtm = xj.ToObject(); var xu = new DiscordUser(xtm.User) { Discord = this }; xu = this.UserCache.AddOrUpdate(xtm.User.Id, xu, (id, old) => { old.Username = xu.Username; old.Discriminator = xu.Discriminator; old.AvatarHash = xu.AvatarHash; return old; }); - guild._members[xtm.User.Id] = new DiscordMember(xtm) { Discord = this, _guild_id = guild.Id }; + guild.MembersInternal[xtm.User.Id] = new DiscordMember(xtm) { Discord = this, GuildId = guild.Id }; } } - if (guild._emojis == null) - guild._emojis = new ConcurrentDictionary(); + if (guild.EmojisInternal == null) + guild.EmojisInternal = new ConcurrentDictionary(); foreach (var xe in guild.Emojis.Values) xe.Discord = this; - if (guild._voiceStates == null) - guild._voiceStates = new ConcurrentDictionary(); + if (guild.VoiceStatesInternal == null) + guild.VoiceStatesInternal = new ConcurrentDictionary(); foreach (var xvs in guild.VoiceStates.Values) xvs.Discord = this; - this._guilds[guild.Id] = guild; + this.GuildsInternal[guild.Id] = guild; } await this._ready.InvokeAsync(this, new ReadyEventArgs(this.ServiceProvider)).ConfigureAwait(false); } /// /// Handles the resumed. /// internal Task OnResumedAsync() { this.Logger.LogInformation(LoggerEvents.SessionUpdate, "Session resumed"); return this._resumed.InvokeAsync(this, new ReadyEventArgs(this.ServiceProvider)); } #endregion #region Channel /// /// Handles the channel create event. /// /// The channel. internal async Task OnChannelCreateEventAsync(DiscordChannel channel) { channel.Discord = this; - foreach (var xo in channel._permissionOverwrites) + foreach (var xo in channel.PermissionOverwritesInternal) { xo.Discord = this; - xo._channel_id = channel.Id; + xo.ChannelId = channel.Id; } - this._guilds[channel.GuildId.Value]._channels[channel.Id] = channel; + this.GuildsInternal[channel.GuildId.Value].ChannelsInternal[channel.Id] = channel; /*if (this.Configuration.AutoRefreshChannelCache) { await this.RefreshChannelsAsync(channel.Guild.Id); }*/ await this._channelCreated.InvokeAsync(this, new ChannelCreateEventArgs(this.ServiceProvider) { Channel = channel, Guild = channel.Guild }).ConfigureAwait(false); } /// /// Handles the channel update event. /// /// The channel. internal async Task OnChannelUpdateEventAsync(DiscordChannel channel) { if (channel == null) return; channel.Discord = this; var gld = channel.Guild; - var channel_new = this.InternalGetCachedChannel(channel.Id); - DiscordChannel channel_old = null; + var channelNew = this.InternalGetCachedChannel(channel.Id); + DiscordChannel channelOld = null; - if (channel_new != null) + if (channelNew != null) { - channel_old = new DiscordChannel + channelOld = new DiscordChannel { - Bitrate = channel_new.Bitrate, + Bitrate = channelNew.Bitrate, Discord = this, - GuildId = channel_new.GuildId, - Id = channel_new.Id, + GuildId = channelNew.GuildId, + Id = channelNew.Id, //IsPrivate = channel_new.IsPrivate, - LastMessageId = channel_new.LastMessageId, - Name = channel_new.Name, - _permissionOverwrites = new List(channel_new._permissionOverwrites), - Position = channel_new.Position, - Topic = channel_new.Topic, - Type = channel_new.Type, - UserLimit = channel_new.UserLimit, - ParentId = channel_new.ParentId, - IsNSFW = channel_new.IsNSFW, - PerUserRateLimit = channel_new.PerUserRateLimit, - RtcRegionId = channel_new.RtcRegionId, - QualityMode = channel_new.QualityMode, - DefaultAutoArchiveDuration = channel_new.DefaultAutoArchiveDuration + LastMessageId = channelNew.LastMessageId, + Name = channelNew.Name, + PermissionOverwritesInternal = new List(channelNew.PermissionOverwritesInternal), + Position = channelNew.Position, + Topic = channelNew.Topic, + Type = channelNew.Type, + UserLimit = channelNew.UserLimit, + ParentId = channelNew.ParentId, + IsNsfw = channelNew.IsNsfw, + PerUserRateLimit = channelNew.PerUserRateLimit, + RtcRegionId = channelNew.RtcRegionId, + QualityMode = channelNew.QualityMode, + DefaultAutoArchiveDuration = channelNew.DefaultAutoArchiveDuration }; - channel_new.Bitrate = channel.Bitrate; - channel_new.Name = channel.Name; - channel_new.Position = channel.Position; - channel_new.Topic = channel.Topic; - channel_new.UserLimit = channel.UserLimit; - channel_new.ParentId = channel.ParentId; - channel_new.IsNSFW = channel.IsNSFW; - channel_new.PerUserRateLimit = channel.PerUserRateLimit; - channel_new.Type = channel.Type; - channel_new.RtcRegionId = channel.RtcRegionId; - channel_new.QualityMode = channel.QualityMode; - channel_new.DefaultAutoArchiveDuration = channel.DefaultAutoArchiveDuration; - - channel_new._permissionOverwrites.Clear(); - - foreach (var po in channel._permissionOverwrites) + channelNew.Bitrate = channel.Bitrate; + channelNew.Name = channel.Name; + channelNew.Position = channel.Position; + channelNew.Topic = channel.Topic; + channelNew.UserLimit = channel.UserLimit; + channelNew.ParentId = channel.ParentId; + channelNew.IsNsfw = channel.IsNsfw; + channelNew.PerUserRateLimit = channel.PerUserRateLimit; + channelNew.Type = channel.Type; + channelNew.RtcRegionId = channel.RtcRegionId; + channelNew.QualityMode = channel.QualityMode; + channelNew.DefaultAutoArchiveDuration = channel.DefaultAutoArchiveDuration; + + channelNew.PermissionOverwritesInternal.Clear(); + + foreach (var po in channel.PermissionOverwritesInternal) { po.Discord = this; - po._channel_id = channel.Id; + po.ChannelId = channel.Id; } - channel_new._permissionOverwrites.AddRange(channel._permissionOverwrites); + channelNew.PermissionOverwritesInternal.AddRange(channel.PermissionOverwritesInternal); if (this.Configuration.AutoRefreshChannelCache && gld != null) { await this.RefreshChannelsAsync(channel.Guild.Id); } } else if (gld != null) { - gld._channels[channel.Id] = channel; + gld.ChannelsInternal[channel.Id] = channel; if (this.Configuration.AutoRefreshChannelCache) { await this.RefreshChannelsAsync(channel.Guild.Id); } } - await this._channelUpdated.InvokeAsync(this, new ChannelUpdateEventArgs(this.ServiceProvider) { ChannelAfter = channel_new, Guild = gld, ChannelBefore = channel_old }).ConfigureAwait(false); + await this._channelUpdated.InvokeAsync(this, new ChannelUpdateEventArgs(this.ServiceProvider) { ChannelAfter = channelNew, Guild = gld, ChannelBefore = channelOld }).ConfigureAwait(false); } /// /// Handles the channel delete event. /// /// The channel. internal async Task OnChannelDeleteEventAsync(DiscordChannel channel) { if (channel == null) return; channel.Discord = this; //if (channel.IsPrivate) if (channel.Type == ChannelType.Group || channel.Type == ChannelType.Private) { var dmChannel = channel as DiscordDmChannel; await this._dmChannelDeleted.InvokeAsync(this, new DmChannelDeleteEventArgs(this.ServiceProvider) { Channel = dmChannel }).ConfigureAwait(false); } else { var gld = channel.Guild; - if (gld._channels.TryRemove(channel.Id, out var cachedChannel)) channel = cachedChannel; + if (gld.ChannelsInternal.TryRemove(channel.Id, out var cachedChannel)) channel = cachedChannel; if (this.Configuration.AutoRefreshChannelCache) { await this.RefreshChannelsAsync(channel.Guild.Id); } await this._channelDeleted.InvokeAsync(this, new ChannelDeleteEventArgs(this.ServiceProvider) { Channel = channel, Guild = gld }).ConfigureAwait(false); } } /// /// Refreshes the channels. /// /// The guild id. internal async Task RefreshChannelsAsync(ulong guildId) { var guild = this.InternalGetCachedGuild(guildId); var channels = await this.ApiClient.GetGuildChannelsAsync(guildId); - guild._channels.Clear(); + guild.ChannelsInternal.Clear(); foreach (var channel in channels.ToList()) { channel.Discord = this; - foreach (var xo in channel._permissionOverwrites) + foreach (var xo in channel.PermissionOverwritesInternal) { xo.Discord = this; - xo._channel_id = channel.Id; + xo.ChannelId = channel.Id; } - guild._channels[channel.Id] = channel; + guild.ChannelsInternal[channel.Id] = channel; } } /// /// Handles the channel pins update. /// /// The guild id. /// The channel id. /// The last pin timestamp. internal async Task OnChannelPinsUpdateAsync(ulong? guildId, ulong channelId, DateTimeOffset? lastPinTimestamp) { var guild = this.InternalGetCachedGuild(guildId); var channel = this.InternalGetCachedChannel(channelId) ?? this.InternalGetCachedThread(channelId); var ea = new ChannelPinsUpdateEventArgs(this.ServiceProvider) { Guild = guild, Channel = channel, LastPinTimestamp = lastPinTimestamp }; await this._channelPinsUpdated.InvokeAsync(this, ea).ConfigureAwait(false); } #endregion #region Guild /// /// Handles the guild create event. /// /// The guild. /// The raw members. /// The presences. internal async Task OnGuildCreateEventAsync(DiscordGuild guild, JArray rawMembers, IEnumerable presences) { if (presences != null) { foreach (var xp in presences) { xp.Discord = this; xp.GuildId = guild.Id; xp.Activity = new DiscordActivity(xp.RawActivity); if (xp.RawActivities != null) { - xp._internalActivities = new DiscordActivity[xp.RawActivities.Length]; + xp.InternalActivities = new DiscordActivity[xp.RawActivities.Length]; for (var i = 0; i < xp.RawActivities.Length; i++) - xp._internalActivities[i] = new DiscordActivity(xp.RawActivities[i]); + xp.InternalActivities[i] = new DiscordActivity(xp.RawActivities[i]); } - this._presences[xp.InternalUser.Id] = xp; + this.PresencesInternal[xp.InternalUser.Id] = xp; } } - var exists = this._guilds.TryGetValue(guild.Id, out var foundGuild); + var exists = this.GuildsInternal.TryGetValue(guild.Id, out var foundGuild); guild.Discord = this; guild.IsUnavailable = false; var eventGuild = guild; if (exists) guild = foundGuild; - if (guild._channels == null) - guild._channels = new ConcurrentDictionary(); - if (guild._threads == null) - guild._threads = new ConcurrentDictionary(); - if (guild._roles == null) - guild._roles = new ConcurrentDictionary(); - if (guild._threads == null) - guild._threads = new ConcurrentDictionary(); - if (guild._stickers == null) - guild._stickers = new ConcurrentDictionary(); - if (guild._emojis == null) - guild._emojis = new ConcurrentDictionary(); - if (guild._voiceStates == null) - guild._voiceStates = new ConcurrentDictionary(); - if (guild._members == null) - guild._members = new ConcurrentDictionary(); - if (guild._scheduledEvents == null) - guild._scheduledEvents = new ConcurrentDictionary(); + if (guild.ChannelsInternal == null) + guild.ChannelsInternal = new ConcurrentDictionary(); + if (guild.ThreadsInternal == null) + guild.ThreadsInternal = new ConcurrentDictionary(); + if (guild.RolesInternal == null) + guild.RolesInternal = new ConcurrentDictionary(); + if (guild.ThreadsInternal == null) + guild.ThreadsInternal = new ConcurrentDictionary(); + if (guild.StickersInternal == null) + guild.StickersInternal = new ConcurrentDictionary(); + if (guild.EmojisInternal == null) + guild.EmojisInternal = new ConcurrentDictionary(); + if (guild.VoiceStatesInternal == null) + guild.VoiceStatesInternal = new ConcurrentDictionary(); + if (guild.MembersInternal == null) + guild.MembersInternal = new ConcurrentDictionary(); + if (guild.ScheduledEventsInternal == null) + guild.ScheduledEventsInternal = new ConcurrentDictionary(); this.UpdateCachedGuild(eventGuild, rawMembers); guild.JoinedAt = eventGuild.JoinedAt; guild.IsLarge = eventGuild.IsLarge; - guild.MemberCount = Math.Max(eventGuild.MemberCount, guild._members.Count); + guild.MemberCount = Math.Max(eventGuild.MemberCount, guild.MembersInternal.Count); guild.IsUnavailable = eventGuild.IsUnavailable; guild.PremiumSubscriptionCount = eventGuild.PremiumSubscriptionCount; guild.PremiumTier = eventGuild.PremiumTier; guild.BannerHash = eventGuild.BannerHash; guild.VanityUrlCode = eventGuild.VanityUrlCode; guild.Description = eventGuild.Description; - guild.IsNSFW = eventGuild.IsNSFW; + guild.IsNsfw = eventGuild.IsNsfw; - foreach (var kvp in eventGuild._voiceStates) guild._voiceStates[kvp.Key] = kvp.Value; - foreach (var kvp in eventGuild._channels) guild._channels[kvp.Key] = kvp.Value; - foreach (var kvp in eventGuild._roles) guild._roles[kvp.Key] = kvp.Value; - foreach (var kvp in eventGuild._emojis) guild._emojis[kvp.Key] = kvp.Value; - foreach (var kvp in eventGuild._threads) guild._threads[kvp.Key] = kvp.Value; - foreach (var kvp in eventGuild._stickers) guild._stickers[kvp.Key] = kvp.Value; - foreach (var kvp in eventGuild._stageInstances) guild._stageInstances[kvp.Key] = kvp.Value; - foreach (var kvp in eventGuild._scheduledEvents) guild._scheduledEvents[kvp.Key] = kvp.Value; + foreach (var kvp in eventGuild.VoiceStatesInternal) guild.VoiceStatesInternal[kvp.Key] = kvp.Value; + foreach (var kvp in eventGuild.ChannelsInternal) guild.ChannelsInternal[kvp.Key] = kvp.Value; + foreach (var kvp in eventGuild.RolesInternal) guild.RolesInternal[kvp.Key] = kvp.Value; + foreach (var kvp in eventGuild.EmojisInternal) guild.EmojisInternal[kvp.Key] = kvp.Value; + foreach (var kvp in eventGuild.ThreadsInternal) guild.ThreadsInternal[kvp.Key] = kvp.Value; + foreach (var kvp in eventGuild.StickersInternal) guild.StickersInternal[kvp.Key] = kvp.Value; + foreach (var kvp in eventGuild.StageInstancesInternal) guild.StageInstancesInternal[kvp.Key] = kvp.Value; + foreach (var kvp in eventGuild.ScheduledEventsInternal) guild.ScheduledEventsInternal[kvp.Key] = kvp.Value; - foreach (var xc in guild._channels.Values) + foreach (var xc in guild.ChannelsInternal.Values) { xc.GuildId = guild.Id; xc.Discord = this; - foreach (var xo in xc._permissionOverwrites) + foreach (var xo in xc.PermissionOverwritesInternal) { xo.Discord = this; - xo._channel_id = xc.Id; + xo.ChannelId = xc.Id; } } - foreach (var xt in guild._threads.Values) + foreach (var xt in guild.ThreadsInternal.Values) { xt.GuildId = guild.Id; xt.Discord = this; } - foreach (var xe in guild._emojis.Values) + foreach (var xe in guild.EmojisInternal.Values) xe.Discord = this; - foreach (var xs in guild._stickers.Values) + foreach (var xs in guild.StickersInternal.Values) xs.Discord = this; - foreach (var xvs in guild._voiceStates.Values) + foreach (var xvs in guild.VoiceStatesInternal.Values) xvs.Discord = this; - foreach (var xsi in guild._stageInstances.Values) + foreach (var xsi in guild.StageInstancesInternal.Values) { xsi.Discord = this; xsi.GuildId = guild.Id; } - foreach (var xr in guild._roles.Values) + foreach (var xr in guild.RolesInternal.Values) { xr.Discord = this; - xr._guild_id = guild.Id; + xr.GuildId = guild.Id; } - foreach (var xse in guild._scheduledEvents.Values) + foreach (var xse in guild.ScheduledEventsInternal.Values) { xse.Discord = this; xse.GuildId = guild.Id; if (xse.Creator != null) xse.Creator.Discord = this; } var old = Volatile.Read(ref this._guildDownloadCompleted); - var dcompl = this._guilds.Values.All(xg => !xg.IsUnavailable); + var dcompl = this.GuildsInternal.Values.All(xg => !xg.IsUnavailable); Volatile.Write(ref this._guildDownloadCompleted, dcompl); if (exists) await this._guildAvailable.InvokeAsync(this, new GuildCreateEventArgs(this.ServiceProvider) { Guild = guild }).ConfigureAwait(false); else await this._guildCreated.InvokeAsync(this, new GuildCreateEventArgs(this.ServiceProvider) { Guild = guild }).ConfigureAwait(false); if (dcompl && !old) await this._guildDownloadCompletedEv.InvokeAsync(this, new GuildDownloadCompletedEventArgs(this.Guilds, this.ServiceProvider)).ConfigureAwait(false); } /// /// Handles the guild update event. /// /// The guild. /// The raw members. internal async Task OnGuildUpdateEventAsync(DiscordGuild guild, JArray rawMembers) { DiscordGuild oldGuild; - if (!this._guilds.ContainsKey(guild.Id)) + if (!this.GuildsInternal.ContainsKey(guild.Id)) { - this._guilds[guild.Id] = guild; + this.GuildsInternal[guild.Id] = guild; oldGuild = null; } else { - var gld = this._guilds[guild.Id]; + var gld = this.GuildsInternal[guild.Id]; oldGuild = new DiscordGuild { Discord = gld.Discord, Name = gld.Name, AfkChannelId = gld.AfkChannelId, AfkTimeout = gld.AfkTimeout, ApplicationId = gld.ApplicationId, DefaultMessageNotifications = gld.DefaultMessageNotifications, ExplicitContentFilter = gld.ExplicitContentFilter, RawFeatures = gld.RawFeatures, IconHash = gld.IconHash, Id = gld.Id, IsLarge = gld.IsLarge, IsSynced = gld.IsSynced, IsUnavailable = gld.IsUnavailable, JoinedAt = gld.JoinedAt, MemberCount = gld.MemberCount, MaxMembers = gld.MaxMembers, MaxPresences = gld.MaxPresences, ApproximateMemberCount = gld.ApproximateMemberCount, ApproximatePresenceCount = gld.ApproximatePresenceCount, MaxVideoChannelUsers = gld.MaxVideoChannelUsers, DiscoverySplashHash = gld.DiscoverySplashHash, PreferredLocale = gld.PreferredLocale, MfaLevel = gld.MfaLevel, OwnerId = gld.OwnerId, SplashHash = gld.SplashHash, SystemChannelId = gld.SystemChannelId, SystemChannelFlags = gld.SystemChannelFlags, Description = gld.Description, WidgetEnabled = gld.WidgetEnabled, WidgetChannelId = gld.WidgetChannelId, VerificationLevel = gld.VerificationLevel, RulesChannelId = gld.RulesChannelId, PublicUpdatesChannelId = gld.PublicUpdatesChannelId, VoiceRegionId = gld.VoiceRegionId, - IsNSFW = gld.IsNSFW, + IsNsfw = gld.IsNsfw, PremiumProgressBarEnabled = gld.PremiumProgressBarEnabled, PremiumSubscriptionCount = gld.PremiumSubscriptionCount, PremiumTier = gld.PremiumTier, - _channels = new ConcurrentDictionary(), - _threads = new ConcurrentDictionary(), - _emojis = new ConcurrentDictionary(), - _stickers = new ConcurrentDictionary(), - _members = new ConcurrentDictionary(), - _roles = new ConcurrentDictionary(), - _stageInstances = new ConcurrentDictionary(), - _voiceStates = new ConcurrentDictionary(), - _scheduledEvents = new ConcurrentDictionary() + ChannelsInternal = new ConcurrentDictionary(), + ThreadsInternal = new ConcurrentDictionary(), + EmojisInternal = new ConcurrentDictionary(), + StickersInternal = new ConcurrentDictionary(), + MembersInternal = new ConcurrentDictionary(), + RolesInternal = new ConcurrentDictionary(), + StageInstancesInternal = new ConcurrentDictionary(), + VoiceStatesInternal = new ConcurrentDictionary(), + ScheduledEventsInternal = new ConcurrentDictionary() }; - foreach (var kvp in gld._channels) oldGuild._channels[kvp.Key] = kvp.Value; - foreach (var kvp in gld._threads) oldGuild._threads[kvp.Key] = kvp.Value; - foreach (var kvp in gld._emojis) oldGuild._emojis[kvp.Key] = kvp.Value; - foreach (var kvp in gld._stickers) oldGuild._stickers[kvp.Key] = kvp.Value; - foreach (var kvp in gld._roles) oldGuild._roles[kvp.Key] = kvp.Value; - foreach (var kvp in gld._voiceStates) oldGuild._voiceStates[kvp.Key] = kvp.Value; - foreach (var kvp in gld._members) oldGuild._members[kvp.Key] = kvp.Value; - foreach (var kvp in gld._stageInstances) oldGuild._stageInstances[kvp.Key] = kvp.Value; - foreach (var kvp in gld._scheduledEvents) oldGuild._scheduledEvents[kvp.Key] = kvp.Value; + foreach (var kvp in gld.ChannelsInternal) oldGuild.ChannelsInternal[kvp.Key] = kvp.Value; + foreach (var kvp in gld.ThreadsInternal) oldGuild.ThreadsInternal[kvp.Key] = kvp.Value; + foreach (var kvp in gld.EmojisInternal) oldGuild.EmojisInternal[kvp.Key] = kvp.Value; + foreach (var kvp in gld.StickersInternal) oldGuild.StickersInternal[kvp.Key] = kvp.Value; + foreach (var kvp in gld.RolesInternal) oldGuild.RolesInternal[kvp.Key] = kvp.Value; + foreach (var kvp in gld.VoiceStatesInternal) oldGuild.VoiceStatesInternal[kvp.Key] = kvp.Value; + foreach (var kvp in gld.MembersInternal) oldGuild.MembersInternal[kvp.Key] = kvp.Value; + foreach (var kvp in gld.StageInstancesInternal) oldGuild.StageInstancesInternal[kvp.Key] = kvp.Value; + foreach (var kvp in gld.ScheduledEventsInternal) oldGuild.ScheduledEventsInternal[kvp.Key] = kvp.Value; } guild.Discord = this; guild.IsUnavailable = false; var eventGuild = guild; - guild = this._guilds[eventGuild.Id]; - - if (guild._channels == null) - guild._channels = new ConcurrentDictionary(); - if (guild._threads == null) - guild._threads = new ConcurrentDictionary(); - if (guild._roles == null) - guild._roles = new ConcurrentDictionary(); - if (guild._emojis == null) - guild._emojis = new ConcurrentDictionary(); - if (guild._stickers == null) - guild._stickers = new ConcurrentDictionary(); - if (guild._voiceStates == null) - guild._voiceStates = new ConcurrentDictionary(); - if (guild._stageInstances == null) - guild._stageInstances = new ConcurrentDictionary(); - if (guild._members == null) - guild._members = new ConcurrentDictionary(); - if (guild._scheduledEvents == null) - guild._scheduledEvents = new ConcurrentDictionary(); + guild = this.GuildsInternal[eventGuild.Id]; + + if (guild.ChannelsInternal == null) + guild.ChannelsInternal = new ConcurrentDictionary(); + if (guild.ThreadsInternal == null) + guild.ThreadsInternal = new ConcurrentDictionary(); + if (guild.RolesInternal == null) + guild.RolesInternal = new ConcurrentDictionary(); + if (guild.EmojisInternal == null) + guild.EmojisInternal = new ConcurrentDictionary(); + if (guild.StickersInternal == null) + guild.StickersInternal = new ConcurrentDictionary(); + if (guild.VoiceStatesInternal == null) + guild.VoiceStatesInternal = new ConcurrentDictionary(); + if (guild.StageInstancesInternal == null) + guild.StageInstancesInternal = new ConcurrentDictionary(); + if (guild.MembersInternal == null) + guild.MembersInternal = new ConcurrentDictionary(); + if (guild.ScheduledEventsInternal == null) + guild.ScheduledEventsInternal = new ConcurrentDictionary(); this.UpdateCachedGuild(eventGuild, rawMembers); - foreach (var xc in guild._channels.Values) + foreach (var xc in guild.ChannelsInternal.Values) { xc.GuildId = guild.Id; xc.Discord = this; - foreach (var xo in xc._permissionOverwrites) + foreach (var xo in xc.PermissionOverwritesInternal) { xo.Discord = this; - xo._channel_id = xc.Id; + xo.ChannelId = xc.Id; } } - foreach (var xc in guild._threads.Values) + foreach (var xc in guild.ThreadsInternal.Values) { xc.GuildId = guild.Id; xc.Discord = this; } - foreach (var xe in guild._emojis.Values) + foreach (var xe in guild.EmojisInternal.Values) xe.Discord = this; - foreach (var xs in guild._stickers.Values) + foreach (var xs in guild.StickersInternal.Values) xs.Discord = this; - foreach (var xvs in guild._voiceStates.Values) + foreach (var xvs in guild.VoiceStatesInternal.Values) xvs.Discord = this; - foreach (var xr in guild._roles.Values) + foreach (var xr in guild.RolesInternal.Values) { xr.Discord = this; - xr._guild_id = guild.Id; + xr.GuildId = guild.Id; } - foreach (var xsi in guild._stageInstances.Values) + foreach (var xsi in guild.StageInstancesInternal.Values) { xsi.Discord = this; xsi.GuildId = guild.Id; } - foreach (var xse in guild._scheduledEvents.Values) + foreach (var xse in guild.ScheduledEventsInternal.Values) { xse.Discord = this; xse.GuildId = guild.Id; if (xse.Creator != null) xse.Creator.Discord = this; } await this._guildUpdated.InvokeAsync(this, new GuildUpdateEventArgs(this.ServiceProvider) { GuildBefore = oldGuild, GuildAfter = guild }).ConfigureAwait(false); } /// /// Handles the guild delete event. /// /// The guild. internal async Task OnGuildDeleteEventAsync(DiscordGuild guild) { if (guild.IsUnavailable) { - if (!this._guilds.TryGetValue(guild.Id, out var gld)) + if (!this.GuildsInternal.TryGetValue(guild.Id, out var gld)) return; gld.IsUnavailable = true; await this._guildUnavailable.InvokeAsync(this, new GuildDeleteEventArgs(this.ServiceProvider) { Guild = guild, Unavailable = true }).ConfigureAwait(false); } else { - if (!this._guilds.TryRemove(guild.Id, out var gld)) + if (!this.GuildsInternal.TryRemove(guild.Id, out var gld)) return; await this._guildDeleted.InvokeAsync(this, new GuildDeleteEventArgs(this.ServiceProvider) { Guild = gld }).ConfigureAwait(false); } } /// /// Handles the guild sync event. /// /// The guild. /// If true, is large. /// The raw members. /// The presences. internal async Task OnGuildSyncEventAsync(DiscordGuild guild, bool isLarge, JArray rawMembers, IEnumerable presences) { presences = presences.Select(xp => { xp.Discord = this; xp.Activity = new DiscordActivity(xp.RawActivity); return xp; }); foreach (var xp in presences) - this._presences[xp.InternalUser.Id] = xp; + this.PresencesInternal[xp.InternalUser.Id] = xp; guild.IsSynced = true; guild.IsLarge = isLarge; this.UpdateCachedGuild(guild, rawMembers); await this._guildAvailable.InvokeAsync(this, new GuildCreateEventArgs(this.ServiceProvider) { Guild = guild }).ConfigureAwait(false); } /// /// Handles the guild emojis update event. /// /// The guild. /// The new emojis. internal async Task OnGuildEmojisUpdateEventAsync(DiscordGuild guild, IEnumerable newEmojis) { - var oldEmojis = new ConcurrentDictionary(guild._emojis); - guild._emojis.Clear(); + var oldEmojis = new ConcurrentDictionary(guild.EmojisInternal); + guild.EmojisInternal.Clear(); foreach (var emoji in newEmojis) { emoji.Discord = this; - guild._emojis[emoji.Id] = emoji; + guild.EmojisInternal[emoji.Id] = emoji; } var ea = new GuildEmojisUpdateEventArgs(this.ServiceProvider) { Guild = guild, EmojisAfter = guild.Emojis, EmojisBefore = new ReadOnlyConcurrentDictionary(oldEmojis) }; await this._guildEmojisUpdated.InvokeAsync(this, ea).ConfigureAwait(false); } /// /// Handles the stickers updated. /// /// The new stickers. /// The raw. internal async Task OnStickersUpdatedAsync(IEnumerable newStickers, JObject raw) { var guild = this.InternalGetCachedGuild((ulong)raw["guild_id"]); - var oldStickers = new ConcurrentDictionary(guild._stickers); - guild._stickers.Clear(); + var oldStickers = new ConcurrentDictionary(guild.StickersInternal); + guild.StickersInternal.Clear(); foreach (var nst in newStickers) { if (nst.User is not null) { nst.User.Discord = this; this.UserCache.AddOrUpdate(nst.User.Id, nst.User, (old, @new) => @new); } nst.Discord = this; - guild._stickers[nst.Id] = nst; + guild.StickersInternal[nst.Id] = nst; } var sea = new GuildStickersUpdateEventArgs(this.ServiceProvider) { Guild = guild, StickersBefore = oldStickers, StickersAfter = guild.Stickers }; await this._guildStickersUpdated.InvokeAsync(this, sea).ConfigureAwait(false); } /// /// Handles the guild integrations update event. /// /// The guild. internal async Task OnGuildIntegrationsUpdateEventAsync(DiscordGuild guild) { var ea = new GuildIntegrationsUpdateEventArgs(this.ServiceProvider) { Guild = guild }; await this._guildIntegrationsUpdated.InvokeAsync(this, ea).ConfigureAwait(false); } #endregion #region Guild Ban /// /// Handles the guild ban add event. /// /// The user. /// The guild. internal async Task OnGuildBanAddEventAsync(TransportUser user, DiscordGuild guild) { var usr = new DiscordUser(user) { Discord = this }; usr = this.UserCache.AddOrUpdate(user.Id, usr, (id, old) => { old.Username = usr.Username; old.Discriminator = usr.Discriminator; old.AvatarHash = usr.AvatarHash; return old; }); if (!guild.Members.TryGetValue(user.Id, out var mbr)) - mbr = new DiscordMember(usr) { Discord = this, _guild_id = guild.Id }; + mbr = new DiscordMember(usr) { Discord = this, GuildId = guild.Id }; var ea = new GuildBanAddEventArgs(this.ServiceProvider) { Guild = guild, Member = mbr }; await this._guildBanAdded.InvokeAsync(this, ea).ConfigureAwait(false); } /// /// Handles the guild ban remove event. /// /// The user. /// The guild. internal async Task OnGuildBanRemoveEventAsync(TransportUser user, DiscordGuild guild) { var usr = new DiscordUser(user) { Discord = this }; usr = this.UserCache.AddOrUpdate(user.Id, usr, (id, old) => { old.Username = usr.Username; old.Discriminator = usr.Discriminator; old.AvatarHash = usr.AvatarHash; return old; }); if (!guild.Members.TryGetValue(user.Id, out var mbr)) - mbr = new DiscordMember(usr) { Discord = this, _guild_id = guild.Id }; + mbr = new DiscordMember(usr) { Discord = this, GuildId = guild.Id }; var ea = new GuildBanRemoveEventArgs(this.ServiceProvider) { Guild = guild, Member = mbr }; await this._guildBanRemoved.InvokeAsync(this, ea).ConfigureAwait(false); } #endregion #region Guild Scheduled Event /// /// Dispatches the event. /// /// The created event. /// The target guild. - internal async Task OnGuildScheduledEventCreateEventAsync(DiscordScheduledEvent scheduled_event, DiscordGuild guild) + internal async Task OnGuildScheduledEventCreateEventAsync(DiscordScheduledEvent scheduledEvent, DiscordGuild guild) { - scheduled_event.Discord = this; + scheduledEvent.Discord = this; - guild._scheduledEvents.AddOrUpdate(scheduled_event.Id, scheduled_event, (old, newScheduledEvent) => newScheduledEvent); + guild.ScheduledEventsInternal.AddOrUpdate(scheduledEvent.Id, scheduledEvent, (old, newScheduledEvent) => newScheduledEvent); - if (scheduled_event.Creator != null) + if (scheduledEvent.Creator != null) { - scheduled_event.Creator.Discord = this; - this.UserCache.AddOrUpdate(scheduled_event.Creator.Id, scheduled_event.Creator, (id, old) => + scheduledEvent.Creator.Discord = this; + this.UserCache.AddOrUpdate(scheduledEvent.Creator.Id, scheduledEvent.Creator, (id, old) => { - old.Username = scheduled_event.Creator.Username; - old.Discriminator = scheduled_event.Creator.Discriminator; - old.AvatarHash = scheduled_event.Creator.AvatarHash; - old.Flags = scheduled_event.Creator.Flags; + old.Username = scheduledEvent.Creator.Username; + old.Discriminator = scheduledEvent.Creator.Discriminator; + old.AvatarHash = scheduledEvent.Creator.AvatarHash; + old.Flags = scheduledEvent.Creator.Flags; return old; }); } - await this._guildScheduledEventCreated.InvokeAsync(this, new GuildScheduledEventCreateEventArgs(this.ServiceProvider) { ScheduledEvent = scheduled_event, Guild = scheduled_event.Guild }).ConfigureAwait(false); + await this._guildScheduledEventCreated.InvokeAsync(this, new GuildScheduledEventCreateEventArgs(this.ServiceProvider) { ScheduledEvent = scheduledEvent, Guild = scheduledEvent.Guild }).ConfigureAwait(false); } /// /// Dispatches the event. /// /// The updated event. /// The target guild. - internal async Task OnGuildScheduledEventUpdateEventAsync(DiscordScheduledEvent scheduled_event, DiscordGuild guild) + internal async Task OnGuildScheduledEventUpdateEventAsync(DiscordScheduledEvent scheduledEvent, DiscordGuild guild) { if (guild == null) return; - DiscordScheduledEvent old_event; - if (!guild._scheduledEvents.ContainsKey(scheduled_event.Id)) + DiscordScheduledEvent oldEvent; + if (!guild.ScheduledEventsInternal.ContainsKey(scheduledEvent.Id)) { - old_event = null; + oldEvent = null; } else { - var ev = guild._scheduledEvents[scheduled_event.Id]; - old_event = new DiscordScheduledEvent + var ev = guild.ScheduledEventsInternal[scheduledEvent.Id]; + oldEvent = new DiscordScheduledEvent { Id = ev.Id, ChannelId = ev.ChannelId, EntityId = ev.EntityId, EntityMetadata = ev.EntityMetadata, CreatorId = ev.CreatorId, Creator = ev.Creator, Discord = this, Description = ev.Description, EntityType = ev.EntityType, ScheduledStartTimeRaw = ev.ScheduledStartTimeRaw, ScheduledEndTimeRaw = ev.ScheduledEndTimeRaw, GuildId = ev.GuildId, Status = ev.Status, Name = ev.Name, UserCount = ev.UserCount }; } - if (scheduled_event.Creator != null) + if (scheduledEvent.Creator != null) { - scheduled_event.Creator.Discord = this; - this.UserCache.AddOrUpdate(scheduled_event.Creator.Id, scheduled_event.Creator, (id, old) => + scheduledEvent.Creator.Discord = this; + this.UserCache.AddOrUpdate(scheduledEvent.Creator.Id, scheduledEvent.Creator, (id, old) => { - old.Username = scheduled_event.Creator.Username; - old.Discriminator = scheduled_event.Creator.Discriminator; - old.AvatarHash = scheduled_event.Creator.AvatarHash; - old.Flags = scheduled_event.Creator.Flags; + old.Username = scheduledEvent.Creator.Username; + old.Discriminator = scheduledEvent.Creator.Discriminator; + old.AvatarHash = scheduledEvent.Creator.AvatarHash; + old.Flags = scheduledEvent.Creator.Flags; return old; }); } - if (scheduled_event.Status == ScheduledEventStatus.Completed) + if (scheduledEvent.Status == ScheduledEventStatus.Completed) { - guild._scheduledEvents.TryRemove(scheduled_event.Id, out var deleted_event); - await this._guildScheduledEventDeleted.InvokeAsync(this, new GuildScheduledEventDeleteEventArgs(this.ServiceProvider) { ScheduledEvent = scheduled_event, Guild = guild, Reason = ScheduledEventStatus.Completed }).ConfigureAwait(false); + guild.ScheduledEventsInternal.TryRemove(scheduledEvent.Id, out var deletedEvent); + await this._guildScheduledEventDeleted.InvokeAsync(this, new GuildScheduledEventDeleteEventArgs(this.ServiceProvider) { ScheduledEvent = scheduledEvent, Guild = guild, Reason = ScheduledEventStatus.Completed }).ConfigureAwait(false); } - else if (scheduled_event.Status == ScheduledEventStatus.Canceled) + else if (scheduledEvent.Status == ScheduledEventStatus.Canceled) { - guild._scheduledEvents.TryRemove(scheduled_event.Id, out var deleted_event); - scheduled_event.Status = ScheduledEventStatus.Canceled; - await this._guildScheduledEventDeleted.InvokeAsync(this, new GuildScheduledEventDeleteEventArgs(this.ServiceProvider) { ScheduledEvent = scheduled_event, Guild = guild, Reason = ScheduledEventStatus.Canceled }).ConfigureAwait(false); + guild.ScheduledEventsInternal.TryRemove(scheduledEvent.Id, out var deletedEvent); + scheduledEvent.Status = ScheduledEventStatus.Canceled; + await this._guildScheduledEventDeleted.InvokeAsync(this, new GuildScheduledEventDeleteEventArgs(this.ServiceProvider) { ScheduledEvent = scheduledEvent, Guild = guild, Reason = ScheduledEventStatus.Canceled }).ConfigureAwait(false); } else { - this.UpdateScheduledEvent(scheduled_event, guild); - await this._guildScheduledEventUpdated.InvokeAsync(this, new GuildScheduledEventUpdateEventArgs(this.ServiceProvider) { ScheduledEventBefore = old_event, ScheduledEventAfter = scheduled_event, Guild = guild }).ConfigureAwait(false); + this.UpdateScheduledEvent(scheduledEvent, guild); + await this._guildScheduledEventUpdated.InvokeAsync(this, new GuildScheduledEventUpdateEventArgs(this.ServiceProvider) { ScheduledEventBefore = oldEvent, ScheduledEventAfter = scheduledEvent, Guild = guild }).ConfigureAwait(false); } } /// /// Dispatches the event. /// /// The deleted event. /// The target guild. - internal async Task OnGuildScheduledEventDeleteEventAsync(DiscordScheduledEvent scheduled_event, DiscordGuild guild) + internal async Task OnGuildScheduledEventDeleteEventAsync(DiscordScheduledEvent scheduledEvent, DiscordGuild guild) { - scheduled_event.Discord = this; + scheduledEvent.Discord = this; - if (scheduled_event.Status == ScheduledEventStatus.Scheduled) - scheduled_event.Status = ScheduledEventStatus.Canceled; + if (scheduledEvent.Status == ScheduledEventStatus.Scheduled) + scheduledEvent.Status = ScheduledEventStatus.Canceled; - if (scheduled_event.Creator != null) + if (scheduledEvent.Creator != null) { - scheduled_event.Creator.Discord = this; - this.UserCache.AddOrUpdate(scheduled_event.Creator.Id, scheduled_event.Creator, (id, old) => + scheduledEvent.Creator.Discord = this; + this.UserCache.AddOrUpdate(scheduledEvent.Creator.Id, scheduledEvent.Creator, (id, old) => { - old.Username = scheduled_event.Creator.Username; - old.Discriminator = scheduled_event.Creator.Discriminator; - old.AvatarHash = scheduled_event.Creator.AvatarHash; - old.Flags = scheduled_event.Creator.Flags; + old.Username = scheduledEvent.Creator.Username; + old.Discriminator = scheduledEvent.Creator.Discriminator; + old.AvatarHash = scheduledEvent.Creator.AvatarHash; + old.Flags = scheduledEvent.Creator.Flags; return old; }); } - await this._guildScheduledEventDeleted.InvokeAsync(this, new GuildScheduledEventDeleteEventArgs(this.ServiceProvider) { ScheduledEvent = scheduled_event, Guild = scheduled_event.Guild, Reason = scheduled_event.Status }).ConfigureAwait(false); - guild._scheduledEvents.TryRemove(scheduled_event.Id, out var deleted_event); + await this._guildScheduledEventDeleted.InvokeAsync(this, new GuildScheduledEventDeleteEventArgs(this.ServiceProvider) { ScheduledEvent = scheduledEvent, Guild = scheduledEvent.Guild, Reason = scheduledEvent.Status }).ConfigureAwait(false); + guild.ScheduledEventsInternal.TryRemove(scheduledEvent.Id, out var deletedEvent); } /// /// Dispatches the event. /// The target event. /// The added user id. /// The target guild. /// - internal async Task OnGuildScheduledEventUserAddedEventAsync(ulong guild_scheduled_event_id, ulong user_id, DiscordGuild guild) + internal async Task OnGuildScheduledEventUserAddedEventAsync(ulong guildScheduledEventId, ulong userId, DiscordGuild guild) { - var scheduled_event = this.InternalGetCachedScheduledEvent(guild_scheduled_event_id) ?? this.UpdateScheduledEvent(new DiscordScheduledEvent + var scheduledEvent = this.InternalGetCachedScheduledEvent(guildScheduledEventId) ?? this.UpdateScheduledEvent(new DiscordScheduledEvent { - Id = guild_scheduled_event_id, + Id = guildScheduledEventId, GuildId = guild.Id, Discord = this, UserCount = 0 }, guild); - scheduled_event.UserCount++; - scheduled_event.Discord = this; + scheduledEvent.UserCount++; + scheduledEvent.Discord = this; guild.Discord = this; - var user = this.GetUserAsync(user_id, true).Result; + var user = this.GetUserAsync(userId, true).Result; user.Discord = this; - var member = guild.Members.TryGetValue(user_id, out var mem) ? mem : guild.GetMemberAsync(user_id).Result; + var member = guild.Members.TryGetValue(userId, out var mem) ? mem : guild.GetMemberAsync(userId).Result; member.Discord = this; - await this._guildScheduledEventUserAdded.InvokeAsync(this, new GuildScheduledEventUserAddEventArgs(this.ServiceProvider) { ScheduledEvent = scheduled_event, Guild = guild, User = user, Member = member }).ConfigureAwait(false); + await this._guildScheduledEventUserAdded.InvokeAsync(this, new GuildScheduledEventUserAddEventArgs(this.ServiceProvider) { ScheduledEvent = scheduledEvent, Guild = guild, User = user, Member = member }).ConfigureAwait(false); } /// /// Dispatches the event. /// The target event. /// The removed user id. /// The target guild. /// - internal async Task OnGuildScheduledEventUserRemovedEventAsync(ulong guild_scheduled_event_id, ulong user_id, DiscordGuild guild) + internal async Task OnGuildScheduledEventUserRemovedEventAsync(ulong guildScheduledEventId, ulong userId, DiscordGuild guild) { - var scheduled_event = this.InternalGetCachedScheduledEvent(guild_scheduled_event_id) ?? this.UpdateScheduledEvent(new DiscordScheduledEvent + var scheduledEvent = this.InternalGetCachedScheduledEvent(guildScheduledEventId) ?? this.UpdateScheduledEvent(new DiscordScheduledEvent { - Id = guild_scheduled_event_id, + Id = guildScheduledEventId, GuildId = guild.Id, Discord = this, UserCount = 0 }, guild); - scheduled_event.UserCount = scheduled_event.UserCount == 0 ? 0 : scheduled_event.UserCount - 1; - scheduled_event.Discord = this; + scheduledEvent.UserCount = scheduledEvent.UserCount == 0 ? 0 : scheduledEvent.UserCount - 1; + scheduledEvent.Discord = this; guild.Discord = this; - var user = this.GetUserAsync(user_id, true).Result; + var user = this.GetUserAsync(userId, true).Result; user.Discord = this; - var member = guild.Members.TryGetValue(user_id, out var mem) ? mem : guild.GetMemberAsync(user_id).Result; + var member = guild.Members.TryGetValue(userId, out var mem) ? mem : guild.GetMemberAsync(userId).Result; member.Discord = this; - await this._guildScheduledEventUserRemoved.InvokeAsync(this, new GuildScheduledEventUserRemoveEventArgs(this.ServiceProvider) { ScheduledEvent = scheduled_event, Guild = guild, User = user, Member = member }).ConfigureAwait(false); + await this._guildScheduledEventUserRemoved.InvokeAsync(this, new GuildScheduledEventUserRemoveEventArgs(this.ServiceProvider) { ScheduledEvent = scheduledEvent, Guild = guild, User = user, Member = member }).ConfigureAwait(false); } #endregion #region Guild Integration /// /// Handles the guild integration create event. /// /// The guild. /// The integration. internal async Task OnGuildIntegrationCreateEventAsync(DiscordGuild guild, DiscordIntegration integration) { integration.Discord = this; await this._guildIntegrationCreated.InvokeAsync(this, new GuildIntegrationCreateEventArgs(this.ServiceProvider) { Integration = integration, Guild = guild }).ConfigureAwait(false); } /// /// Handles the guild integration update event. /// /// The guild. /// The integration. internal async Task OnGuildIntegrationUpdateEventAsync(DiscordGuild guild, DiscordIntegration integration) { integration.Discord = this; await this._guildIntegrationUpdated.InvokeAsync(this, new GuildIntegrationUpdateEventArgs(this.ServiceProvider) { Integration = integration, Guild = guild }).ConfigureAwait(false); } /// /// Handles the guild integration delete event. /// /// The guild. /// The integration_id. /// The application_id. - internal async Task OnGuildIntegrationDeleteEventAsync(DiscordGuild guild, ulong integration_id, ulong? application_id) - => await this._guildIntegrationDeleted.InvokeAsync(this, new GuildIntegrationDeleteEventArgs(this.ServiceProvider) { Guild = guild, IntegrationId = integration_id, ApplicationId = application_id }).ConfigureAwait(false); + internal async Task OnGuildIntegrationDeleteEventAsync(DiscordGuild guild, ulong integrationId, ulong? applicationId) + => await this._guildIntegrationDeleted.InvokeAsync(this, new GuildIntegrationDeleteEventArgs(this.ServiceProvider) { Guild = guild, IntegrationId = integrationId, ApplicationId = applicationId }).ConfigureAwait(false); #endregion #region Guild Member /// /// Handles the guild member add event. /// /// The member. /// The guild. internal async Task OnGuildMemberAddEventAsync(TransportMember member, DiscordGuild guild) { var usr = new DiscordUser(member.User) { Discord = this }; usr = this.UserCache.AddOrUpdate(member.User.Id, usr, (id, old) => { old.Username = usr.Username; old.Discriminator = usr.Discriminator; old.AvatarHash = usr.AvatarHash; return old; }); var mbr = new DiscordMember(member) { Discord = this, - _guild_id = guild.Id + GuildId = guild.Id }; - guild._members[mbr.Id] = mbr; + guild.MembersInternal[mbr.Id] = mbr; guild.MemberCount++; var ea = new GuildMemberAddEventArgs(this.ServiceProvider) { Guild = guild, Member = mbr }; await this._guildMemberAdded.InvokeAsync(this, ea).ConfigureAwait(false); } /// /// Handles the guild member remove event. /// /// The user. /// The guild. internal async Task OnGuildMemberRemoveEventAsync(TransportUser user, DiscordGuild guild) { var usr = new DiscordUser(user); - if (!guild._members.TryRemove(user.Id, out var mbr)) - mbr = new DiscordMember(usr) { Discord = this, _guild_id = guild.Id }; + if (!guild.MembersInternal.TryRemove(user.Id, out var mbr)) + mbr = new DiscordMember(usr) { Discord = this, GuildId = guild.Id }; guild.MemberCount--; _ = this.UserCache.AddOrUpdate(user.Id, usr, (old, @new) => @new); var ea = new GuildMemberRemoveEventArgs(this.ServiceProvider) { Guild = guild, Member = mbr }; await this._guildMemberRemoved.InvokeAsync(this, ea).ConfigureAwait(false); } /// /// Handles the guild member update event. /// /// The member. /// The guild. /// The roles. /// The nick. /// If true, pending. internal async Task OnGuildMemberUpdateEventAsync(TransportMember member, DiscordGuild guild, IEnumerable roles, string nick, bool? pending) { var usr = new DiscordUser(member.User) { Discord = this }; usr = this.UserCache.AddOrUpdate(usr.Id, usr, (id, old) => { old.Username = usr.Username; old.Discriminator = usr.Discriminator; old.AvatarHash = usr.AvatarHash; return old; }); if (!guild.Members.TryGetValue(member.User.Id, out var mbr)) - mbr = new DiscordMember(usr) { Discord = this, _guild_id = guild.Id }; + mbr = new DiscordMember(usr) { Discord = this, GuildId = guild.Id }; - var nick_old = mbr.Nickname; - var pending_old = mbr.IsPending; - var roles_old = new ReadOnlyCollection(new List(mbr.Roles)); - var cdu_old = mbr.CommunicationDisabledUntil; - mbr._avatarHash = member.AvatarHash; + var nickOld = mbr.Nickname; + var pendingOld = mbr.IsPending; + var rolesOld = new ReadOnlyCollection(new List(mbr.Roles)); + var cduOld = mbr.CommunicationDisabledUntil; + mbr.AvatarHashInternal = member.AvatarHash; mbr.GuildAvatarHash = member.GuildAvatarHash; mbr.Nickname = nick; mbr.IsPending = pending; mbr.CommunicationDisabledUntil = member.CommunicationDisabledUntil; - mbr._role_ids.Clear(); - mbr._role_ids.AddRange(roles); + mbr.RoleIdsInternal.Clear(); + mbr.RoleIdsInternal.AddRange(roles); var ea = new GuildMemberUpdateEventArgs(this.ServiceProvider) { Guild = guild, Member = mbr, NicknameAfter = mbr.Nickname, RolesAfter = new ReadOnlyCollection(new List(mbr.Roles)), PendingAfter = mbr.IsPending, TimeoutAfter = mbr.CommunicationDisabledUntil, - NicknameBefore = nick_old, - RolesBefore = roles_old, - PendingBefore = pending_old, - TimeoutBefore = cdu_old + NicknameBefore = nickOld, + RolesBefore = rolesOld, + PendingBefore = pendingOld, + TimeoutBefore = cduOld }; await this._guildMemberUpdated.InvokeAsync(this, ea).ConfigureAwait(false); } /// /// Handles the guild members chunk event. /// /// The dat. internal async Task OnGuildMembersChunkEventAsync(JObject dat) { var guild = this.Guilds[(ulong)dat["guild_id"]]; var chunkIndex = (int)dat["chunk_index"]; var chunkCount = (int)dat["chunk_count"]; var nonce = (string)dat["nonce"]; var mbrs = new HashSet(); var pres = new HashSet(); var members = dat["members"].ToObject(); var memCount = members.Count(); for (var i = 0; i < memCount; i++) { - var mbr = new DiscordMember(members[i]) { Discord = this, _guild_id = guild.Id }; + var mbr = new DiscordMember(members[i]) { Discord = this, GuildId = guild.Id }; if (!this.UserCache.ContainsKey(mbr.Id)) this.UserCache[mbr.Id] = new DiscordUser(members[i].User) { Discord = this }; - guild._members[mbr.Id] = mbr; + guild.MembersInternal[mbr.Id] = mbr; mbrs.Add(mbr); } - guild.MemberCount = guild._members.Count; + guild.MemberCount = guild.MembersInternal.Count; var ea = new GuildMembersChunkEventArgs(this.ServiceProvider) { Guild = guild, Members = new ReadOnlySet(mbrs), ChunkIndex = chunkIndex, ChunkCount = chunkCount, Nonce = nonce, }; if (dat["presences"] != null) { var presences = dat["presences"].ToObject(); var presCount = presences.Count(); for (var i = 0; i < presCount; i++) { var xp = presences[i]; xp.Discord = this; xp.Activity = new DiscordActivity(xp.RawActivity); if (xp.RawActivities != null) { - xp._internalActivities = new DiscordActivity[xp.RawActivities.Length]; + xp.InternalActivities = new DiscordActivity[xp.RawActivities.Length]; for (var j = 0; j < xp.RawActivities.Length; j++) - xp._internalActivities[j] = new DiscordActivity(xp.RawActivities[j]); + xp.InternalActivities[j] = new DiscordActivity(xp.RawActivities[j]); } pres.Add(xp); } ea.Presences = new ReadOnlySet(pres); } if (dat["not_found"] != null) { var nf = dat["not_found"].ToObject>(); ea.NotFound = new ReadOnlySet(nf); } await this._guildMembersChunked.InvokeAsync(this, ea).ConfigureAwait(false); } #endregion #region Guild Role /// /// Handles the guild role create event. /// /// The role. /// The guild. internal async Task OnGuildRoleCreateEventAsync(DiscordRole role, DiscordGuild guild) { role.Discord = this; - role._guild_id = guild.Id; + role.GuildId = guild.Id; - guild._roles[role.Id] = role; + guild.RolesInternal[role.Id] = role; var ea = new GuildRoleCreateEventArgs(this.ServiceProvider) { Guild = guild, Role = role }; await this._guildRoleCreated.InvokeAsync(this, ea).ConfigureAwait(false); } /// /// Handles the guild role update event. /// /// The role. /// The guild. internal async Task OnGuildRoleUpdateEventAsync(DiscordRole role, DiscordGuild guild) { var newRole = guild.GetRole(role.Id); var oldRole = new DiscordRole { - _guild_id = guild.Id, - _color = newRole._color, + GuildId = guild.Id, + ColorInternal = newRole.ColorInternal, Discord = this, IsHoisted = newRole.IsHoisted, Id = newRole.Id, IsManaged = newRole.IsManaged, IsMentionable = newRole.IsMentionable, Name = newRole.Name, Permissions = newRole.Permissions, Position = newRole.Position }; - newRole._guild_id = guild.Id; - newRole._color = role._color; + newRole.GuildId = guild.Id; + newRole.ColorInternal = role.ColorInternal; newRole.IsHoisted = role.IsHoisted; newRole.IsManaged = role.IsManaged; newRole.IsMentionable = role.IsMentionable; newRole.Name = role.Name; newRole.Permissions = role.Permissions; newRole.Position = role.Position; var ea = new GuildRoleUpdateEventArgs(this.ServiceProvider) { Guild = guild, RoleAfter = newRole, RoleBefore = oldRole }; await this._guildRoleUpdated.InvokeAsync(this, ea).ConfigureAwait(false); } /// /// Handles the guild role delete event. /// /// The role id. /// The guild. internal async Task OnGuildRoleDeleteEventAsync(ulong roleId, DiscordGuild guild) { - if (!guild._roles.TryRemove(roleId, out var role)) + if (!guild.RolesInternal.TryRemove(roleId, out var role)) this.Logger.LogWarning($"Attempted to delete a nonexistent role ({roleId}) from guild ({guild})."); var ea = new GuildRoleDeleteEventArgs(this.ServiceProvider) { Guild = guild, Role = role }; await this._guildRoleDeleted.InvokeAsync(this, ea).ConfigureAwait(false); } #endregion #region Invite /// /// Handles the invite create event. /// /// The channel id. /// The guild id. /// The invite. internal async Task OnInviteCreateEventAsync(ulong channelId, ulong guildId, DiscordInvite invite) { var guild = this.InternalGetCachedGuild(guildId); var channel = this.InternalGetCachedChannel(channelId); invite.Discord = this; if (invite.Inviter is not null) { invite.Inviter.Discord = this; this.UserCache.AddOrUpdate(invite.Inviter.Id, invite.Inviter, (old, @new) => @new); } - guild._invites[invite.Code] = invite; + guild.Invites[invite.Code] = invite; var ea = new InviteCreateEventArgs(this.ServiceProvider) { Channel = channel, Guild = guild, Invite = invite }; await this._inviteCreated.InvokeAsync(this, ea).ConfigureAwait(false); } /// /// Handles the invite delete event. /// /// The channel id. /// The guild id. /// The dat. internal async Task OnInviteDeleteEventAsync(ulong channelId, ulong guildId, JToken dat) { var guild = this.InternalGetCachedGuild(guildId); var channel = this.InternalGetCachedChannel(channelId); - if (!guild._invites.TryRemove(dat["code"].ToString(), out var invite)) + if (!guild.Invites.TryRemove(dat["code"].ToString(), out var invite)) { invite = dat.ToObject(); invite.Discord = this; } invite.IsRevoked = true; var ea = new InviteDeleteEventArgs(this.ServiceProvider) { Channel = channel, Guild = guild, Invite = invite }; await this._inviteDeleted.InvokeAsync(this, ea).ConfigureAwait(false); } #endregion #region Message /// /// Handles the message ack event. /// /// The chn. /// The message id. internal async Task OnMessageAckEventAsync(DiscordChannel chn, ulong messageId) { if (this.MessageCache == null || !this.MessageCache.TryGet(xm => xm.Id == messageId && xm.ChannelId == chn.Id, out var msg)) { msg = new DiscordMessage { Id = messageId, ChannelId = chn.Id, Discord = this, }; } await this._messageAcknowledged.InvokeAsync(this, new MessageAcknowledgeEventArgs(this.ServiceProvider) { Message = msg }).ConfigureAwait(false); } /// /// Handles the message create event. /// /// The message. /// The author. /// The member. /// The reference author. /// The reference member. internal async Task OnMessageCreateEventAsync(DiscordMessage message, TransportUser author, TransportMember member, TransportUser referenceAuthor, TransportMember referenceMember) { message.Discord = this; this.PopulateMessageReactionsAndCache(message, author, member); message.PopulateMentions(); if (message.Channel == null && message.ChannelId == default) this.Logger.LogWarning(LoggerEvents.WebSocketReceive, "Channel which the last message belongs to is not in cache - cache state might be invalid!"); if (message.ReferencedMessage != null) { message.ReferencedMessage.Discord = this; this.PopulateMessageReactionsAndCache(message.ReferencedMessage, referenceAuthor, referenceMember); message.ReferencedMessage.PopulateMentions(); } foreach (var sticker in message.Stickers) sticker.Discord = this; var ea = new MessageCreateEventArgs(this.ServiceProvider) { Message = message, - MentionedUsers = new ReadOnlyCollection(message._mentionedUsers), - MentionedRoles = message._mentionedRoles != null ? new ReadOnlyCollection(message._mentionedRoles) : null, - MentionedChannels = message._mentionedChannels != null ? new ReadOnlyCollection(message._mentionedChannels) : null + MentionedUsers = new ReadOnlyCollection(message.MentionedUsersInternal), + MentionedRoles = message.MentionedRolesInternal != null ? new ReadOnlyCollection(message.MentionedRolesInternal) : null, + MentionedChannels = message.MentionedChannelsInternal != null ? new ReadOnlyCollection(message.MentionedChannelsInternal) : null }; await this._messageCreated.InvokeAsync(this, ea).ConfigureAwait(false); } /// /// Handles the message update event. /// /// The message. /// The author. /// The member. /// The reference author. /// The reference member. internal async Task OnMessageUpdateEventAsync(DiscordMessage message, TransportUser author, TransportMember member, TransportUser referenceAuthor, TransportMember referenceMember) { DiscordGuild guild; message.Discord = this; - var event_message = message; + var eventMessage = message; DiscordMessage oldmsg = null; if (this.Configuration.MessageCacheSize == 0 || this.MessageCache == null - || !this.MessageCache.TryGet(xm => xm.Id == event_message.Id && xm.ChannelId == event_message.ChannelId, out message)) + || !this.MessageCache.TryGet(xm => xm.Id == eventMessage.Id && xm.ChannelId == eventMessage.ChannelId, out message)) { - message = event_message; + message = eventMessage; this.PopulateMessageReactionsAndCache(message, author, member); guild = message.Channel?.Guild; if (message.ReferencedMessage != null) { message.ReferencedMessage.Discord = this; this.PopulateMessageReactionsAndCache(message.ReferencedMessage, referenceAuthor, referenceMember); message.ReferencedMessage.PopulateMentions(); } } else { oldmsg = new DiscordMessage(message); guild = message.Channel?.Guild; - message.EditedTimestampRaw = event_message.EditedTimestampRaw; - if (event_message.Content != null) - message.Content = event_message.Content; - message._embeds.Clear(); - message._embeds.AddRange(event_message._embeds); - message.Pinned = event_message.Pinned; - message.IsTTS = event_message.IsTTS; + message.EditedTimestampRaw = eventMessage.EditedTimestampRaw; + if (eventMessage.Content != null) + message.Content = eventMessage.Content; + message.EmbedsInternal.Clear(); + message.EmbedsInternal.AddRange(eventMessage.EmbedsInternal); + message.Pinned = eventMessage.Pinned; + message.IsTts = eventMessage.IsTts; } message.PopulateMentions(); var ea = new MessageUpdateEventArgs(this.ServiceProvider) { Message = message, MessageBefore = oldmsg, - MentionedUsers = new ReadOnlyCollection(message._mentionedUsers), - MentionedRoles = message._mentionedRoles != null ? new ReadOnlyCollection(message._mentionedRoles) : null, - MentionedChannels = message._mentionedChannels != null ? new ReadOnlyCollection(message._mentionedChannels) : null + MentionedUsers = new ReadOnlyCollection(message.MentionedUsersInternal), + MentionedRoles = message.MentionedRolesInternal != null ? new ReadOnlyCollection(message.MentionedRolesInternal) : null, + MentionedChannels = message.MentionedChannelsInternal != null ? new ReadOnlyCollection(message.MentionedChannelsInternal) : null }; await this._messageUpdated.InvokeAsync(this, ea).ConfigureAwait(false); } /// /// Handles the message delete event. /// /// The message id. /// The channel id. /// The guild id. internal async Task OnMessageDeleteEventAsync(ulong messageId, ulong channelId, ulong? guildId) { var channel = this.InternalGetCachedChannel(channelId) ?? this.InternalGetCachedThread(channelId); var guild = this.InternalGetCachedGuild(guildId); if (channel == null || this.Configuration.MessageCacheSize == 0 || this.MessageCache == null || !this.MessageCache.TryGet(xm => xm.Id == messageId && xm.ChannelId == channelId, out var msg)) { msg = new DiscordMessage { Id = messageId, ChannelId = channelId, Discord = this, }; } if (this.Configuration.MessageCacheSize > 0) this.MessageCache?.Remove(xm => xm.Id == msg.Id && xm.ChannelId == channelId); var ea = new MessageDeleteEventArgs(this.ServiceProvider) { Channel = channel, Message = msg, Guild = guild }; await this._messageDeleted.InvokeAsync(this, ea).ConfigureAwait(false); } /// /// Handles the message bulk delete event. /// /// The message ids. /// The channel id. /// The guild id. internal async Task OnMessageBulkDeleteEventAsync(ulong[] messageIds, ulong channelId, ulong? guildId) { var channel = this.InternalGetCachedChannel(channelId) ?? this.InternalGetCachedThread(channelId); var msgs = new List(messageIds.Length); foreach (var messageId in messageIds) { if (channel == null || this.Configuration.MessageCacheSize == 0 || this.MessageCache == null || !this.MessageCache.TryGet(xm => xm.Id == messageId && xm.ChannelId == channelId, out var msg)) { msg = new DiscordMessage { Id = messageId, ChannelId = channelId, Discord = this, }; } if (this.Configuration.MessageCacheSize > 0) this.MessageCache?.Remove(xm => xm.Id == msg.Id && xm.ChannelId == channelId); msgs.Add(msg); } var guild = this.InternalGetCachedGuild(guildId); var ea = new MessageBulkDeleteEventArgs(this.ServiceProvider) { Channel = channel, Messages = new ReadOnlyCollection(msgs), Guild = guild }; await this._messagesBulkDeleted.InvokeAsync(this, ea).ConfigureAwait(false); } #endregion #region Message Reaction /// /// Handles the message reaction add. /// /// The user id. /// The message id. /// The channel id. /// The guild id. /// The mbr. /// The emoji. internal async Task OnMessageReactionAddAsync(ulong userId, ulong messageId, ulong channelId, ulong? guildId, TransportMember mbr, DiscordEmoji emoji) { var channel = this.InternalGetCachedChannel(channelId) ?? this.InternalGetCachedThread(channelId); var guild = this.InternalGetCachedGuild(guildId); emoji.Discord = this; var usr = this.UpdateUser(new DiscordUser { Id = userId, Discord = this }, guildId, guild, mbr); if (channel == null || this.Configuration.MessageCacheSize == 0 || this.MessageCache == null || !this.MessageCache.TryGet(xm => xm.Id == messageId && xm.ChannelId == channelId, out var msg)) { msg = new DiscordMessage { Id = messageId, ChannelId = channelId, Discord = this, - _reactions = new List() + ReactionsInternal = new List() }; } - var react = msg._reactions.FirstOrDefault(xr => xr.Emoji == emoji); + var react = msg.ReactionsInternal.FirstOrDefault(xr => xr.Emoji == emoji); if (react == null) { - msg._reactions.Add(react = new DiscordReaction + msg.ReactionsInternal.Add(react = new DiscordReaction { Count = 1, Emoji = emoji, IsMe = this.CurrentUser.Id == userId }); } else { react.Count++; react.IsMe |= this.CurrentUser.Id == userId; } var ea = new MessageReactionAddEventArgs(this.ServiceProvider) { Message = msg, User = usr, Guild = guild, Emoji = emoji }; await this._messageReactionAdded.InvokeAsync(this, ea).ConfigureAwait(false); } /// /// Handles the message reaction remove. /// /// The user id. /// The message id. /// The channel id. /// The guild id. /// The emoji. internal async Task OnMessageReactionRemoveAsync(ulong userId, ulong messageId, ulong channelId, ulong? guildId, DiscordEmoji emoji) { var channel = this.InternalGetCachedChannel(channelId) ?? this.InternalGetCachedThread(channelId); emoji.Discord = this; if (!this.UserCache.TryGetValue(userId, out var usr)) usr = new DiscordUser { Id = userId, Discord = this }; if (channel?.Guild != null) usr = channel.Guild.Members.TryGetValue(userId, out var member) ? member - : new DiscordMember(usr) { Discord = this, _guild_id = channel.GuildId.Value }; + : new DiscordMember(usr) { Discord = this, GuildId = channel.GuildId.Value }; if (channel == null || this.Configuration.MessageCacheSize == 0 || this.MessageCache == null || !this.MessageCache.TryGet(xm => xm.Id == messageId && xm.ChannelId == channelId, out var msg)) { msg = new DiscordMessage { Id = messageId, ChannelId = channelId, Discord = this }; } - var react = msg._reactions?.FirstOrDefault(xr => xr.Emoji == emoji); + var react = msg.ReactionsInternal?.FirstOrDefault(xr => xr.Emoji == emoji); if (react != null) { react.Count--; react.IsMe &= this.CurrentUser.Id != userId; - if (msg._reactions != null && react.Count <= 0) // shit happens - for (var i = 0; i < msg._reactions.Count; i++) - if (msg._reactions[i].Emoji == emoji) + if (msg.ReactionsInternal != null && react.Count <= 0) // shit happens + for (var i = 0; i < msg.ReactionsInternal.Count; i++) + if (msg.ReactionsInternal[i].Emoji == emoji) { - msg._reactions.RemoveAt(i); + msg.ReactionsInternal.RemoveAt(i); break; } } var guild = this.InternalGetCachedGuild(guildId); var ea = new MessageReactionRemoveEventArgs(this.ServiceProvider) { Message = msg, User = usr, Guild = guild, Emoji = emoji }; await this._messageReactionRemoved.InvokeAsync(this, ea).ConfigureAwait(false); } /// /// Handles the message reaction remove all. /// /// The message id. /// The channel id. /// The guild id. internal async Task OnMessageReactionRemoveAllAsync(ulong messageId, ulong channelId, ulong? guildId) { var channel = this.InternalGetCachedChannel(channelId) ?? this.InternalGetCachedThread(channelId); if (channel == null || this.Configuration.MessageCacheSize == 0 || this.MessageCache == null || !this.MessageCache.TryGet(xm => xm.Id == messageId && xm.ChannelId == channelId, out var msg)) { msg = new DiscordMessage { Id = messageId, ChannelId = channelId, Discord = this }; } - msg._reactions?.Clear(); + msg.ReactionsInternal?.Clear(); var guild = this.InternalGetCachedGuild(guildId); var ea = new MessageReactionsClearEventArgs(this.ServiceProvider) { Message = msg }; await this._messageReactionsCleared.InvokeAsync(this, ea).ConfigureAwait(false); } /// /// Handles the message reaction remove emoji. /// /// The message id. /// The channel id. /// The guild id. /// The dat. internal async Task OnMessageReactionRemoveEmojiAsync(ulong messageId, ulong channelId, ulong guildId, JToken dat) { var guild = this.InternalGetCachedGuild(guildId); var channel = this.InternalGetCachedChannel(channelId) ?? this.InternalGetCachedThread(channelId); if (channel == null || this.Configuration.MessageCacheSize == 0 || this.MessageCache == null || !this.MessageCache.TryGet(xm => xm.Id == messageId && xm.ChannelId == channelId, out var msg)) { msg = new DiscordMessage { Id = messageId, ChannelId = channelId, Discord = this }; } var partialEmoji = dat.ToObject(); - if (!guild._emojis.TryGetValue(partialEmoji.Id, out var emoji)) + if (!guild.EmojisInternal.TryGetValue(partialEmoji.Id, out var emoji)) { emoji = partialEmoji; emoji.Discord = this; } - msg._reactions?.RemoveAll(r => r.Emoji.Equals(emoji)); + msg.ReactionsInternal?.RemoveAll(r => r.Emoji.Equals(emoji)); var ea = new MessageReactionRemoveEmojiEventArgs(this.ServiceProvider) { Channel = channel, Guild = guild, Message = msg, Emoji = emoji }; await this._messageReactionRemovedEmoji.InvokeAsync(this, ea).ConfigureAwait(false); } #endregion #region Stage Instance /// /// Dispatches the event. /// /// The created stage instance. internal async Task OnStageInstanceCreateEventAsync(DiscordStageInstance stage) { stage.Discord = this; var guild = this.InternalGetCachedGuild(stage.GuildId); - guild._stageInstances[stage.Id] = stage; + guild.StageInstancesInternal[stage.Id] = stage; await this._stageInstanceCreated.InvokeAsync(this, new StageInstanceCreateEventArgs(this.ServiceProvider) { StageInstance = stage, Guild = stage.Guild }).ConfigureAwait(false); } /// /// Dispatches the event. /// /// The updated stage instance. internal async Task OnStageInstanceUpdateEventAsync(DiscordStageInstance stage) { stage.Discord = this; var guild = this.InternalGetCachedGuild(stage.GuildId); - guild._stageInstances[stage.Id] = stage; + guild.StageInstancesInternal[stage.Id] = stage; await this._stageInstanceUpdated.InvokeAsync(this, new StageInstanceUpdateEventArgs(this.ServiceProvider) { StageInstance = stage, Guild = stage.Guild }).ConfigureAwait(false); } /// /// Dispatches the event. /// /// The deleted stage instance. internal async Task OnStageInstanceDeleteEventAsync(DiscordStageInstance stage) { stage.Discord = this; var guild = this.InternalGetCachedGuild(stage.GuildId); - guild._stageInstances[stage.Id] = stage; + guild.StageInstancesInternal[stage.Id] = stage; await this._stageInstanceDeleted.InvokeAsync(this, new StageInstanceDeleteEventArgs(this.ServiceProvider) { StageInstance = stage, Guild = stage.Guild }).ConfigureAwait(false); } #endregion #region Thread /// /// Dispatches the event. /// /// The created thread. internal async Task OnThreadCreateEventAsync(DiscordThreadChannel thread) { thread.Discord = this; - this.InternalGetCachedGuild(thread.GuildId)._threads.AddOrUpdate(thread.Id, thread, (oldThread, newThread) => newThread); + this.InternalGetCachedGuild(thread.GuildId).ThreadsInternal.AddOrUpdate(thread.Id, thread, (oldThread, newThread) => newThread); await this._threadCreated.InvokeAsync(this, new ThreadCreateEventArgs(this.ServiceProvider) { Thread = thread, Guild = thread.Guild, Parent = thread.Parent }).ConfigureAwait(false); } /// /// Dispatches the event. /// /// The updated thread. internal async Task OnThreadUpdateEventAsync(DiscordThreadChannel thread) { if (thread == null) return; thread.Discord = this; var guild = thread.Guild; var threadNew = this.InternalGetCachedThread(thread.Id); DiscordThreadChannel threadOld = null; ThreadUpdateEventArgs updateEvent; if (threadNew != null) { threadOld = new DiscordThreadChannel { Discord = this, Type = threadNew.Type, ThreadMetadata = thread.ThreadMetadata, - _threadMembers = threadNew._threadMembers, + ThreadMembersInternal = threadNew.ThreadMembersInternal, ParentId = thread.ParentId, OwnerId = thread.OwnerId, Name = thread.Name, LastMessageId = threadNew.LastMessageId, MessageCount = thread.MessageCount, MemberCount = thread.MemberCount, GuildId = thread.GuildId, LastPinTimestampRaw = threadNew.LastPinTimestampRaw, PerUserRateLimit = threadNew.PerUserRateLimit, CurrentMember = threadNew.CurrentMember }; threadNew.ThreadMetadata = thread.ThreadMetadata; threadNew.ParentId = thread.ParentId; threadNew.OwnerId = thread.OwnerId; threadNew.Name = thread.Name; threadNew.LastMessageId = thread.LastMessageId.HasValue ? thread.LastMessageId : threadOld.LastMessageId; threadNew.MessageCount = thread.MessageCount; threadNew.MemberCount = thread.MemberCount; threadNew.GuildId = thread.GuildId; updateEvent = new ThreadUpdateEventArgs(this.ServiceProvider) { ThreadAfter = thread, ThreadBefore = threadOld, Guild = thread.Guild, Parent = thread.Parent }; } else { updateEvent = new ThreadUpdateEventArgs(this.ServiceProvider) { ThreadAfter = thread, Guild = thread.Guild, Parent = thread.Parent }; - guild._threads[thread.Id] = thread; + guild.ThreadsInternal[thread.Id] = thread; } await this._threadUpdated.InvokeAsync(this, updateEvent).ConfigureAwait(false); } /// /// Dispatches the event. /// /// The deleted thread. internal async Task OnThreadDeleteEventAsync(DiscordThreadChannel thread) { if (thread == null) return; thread.Discord = this; var gld = thread.Guild; - if (gld._threads.TryRemove(thread.Id, out var cachedThread)) + if (gld.ThreadsInternal.TryRemove(thread.Id, out var cachedThread)) thread = cachedThread; await this._threadDeleted.InvokeAsync(this, new ThreadDeleteEventArgs(this.ServiceProvider) { Thread = thread, Guild = thread.Guild, Parent = thread.Parent, Type = thread.Type }).ConfigureAwait(false); } /// /// Dispatches the event. /// /// The synced guild. /// The synced channel ids. /// The synced threads. /// The synced members. - internal async Task OnThreadListSyncEventAsync(DiscordGuild guild, IReadOnlyList channel_ids, IReadOnlyList threads, IReadOnlyList members) + internal async Task OnThreadListSyncEventAsync(DiscordGuild guild, IReadOnlyList channelIds, IReadOnlyList threads, IReadOnlyList members) { guild.Discord = this; - var channels = channel_ids.Select(x => guild.GetChannel(x.Value)); //getting channel objects + var channels = channelIds.Select(x => guild.GetChannel(x.Value)); //getting channel objects foreach (var chan in channels) { chan.Discord = this; } threads.Select(x => x.Discord = this); await this._threadListSynced.InvokeAsync(this, new ThreadListSyncEventArgs(this.ServiceProvider) { Guild = guild, Channels = channels.ToList().AsReadOnly(), Threads = threads, Members = members.ToList().AsReadOnly() }).ConfigureAwait(false); } /// /// Dispatches the event. /// /// The updated member. internal async Task OnThreadMemberUpdateEventAsync(DiscordThreadChannelMember member) { member.Discord = this; var thread = this.InternalGetCachedThread(member.Id); if (thread == null) { var tempThread = await this.ApiClient.GetThreadAsync(member.Id); - thread = this._guilds[member._guild_id]._threads.AddOrUpdate(member.Id, tempThread, (old, newThread) => newThread); + thread = this.GuildsInternal[member.GuildId].ThreadsInternal.AddOrUpdate(member.Id, tempThread, (old, newThread) => newThread); } thread.CurrentMember = member; - thread.Guild._threads.AddOrUpdate(member.Id, thread, (oldThread, newThread) => newThread); + thread.Guild.ThreadsInternal.AddOrUpdate(member.Id, thread, (oldThread, newThread) => newThread); await this._threadMemberUpdated.InvokeAsync(this, new ThreadMemberUpdateEventArgs(this.ServiceProvider) { ThreadMember = member, Thread = thread }).ConfigureAwait(false); } /// /// Dispatches the event. /// /// The target guild. - /// The thread id of the target thread this update belongs to. - /// The added members. - /// The ids of the removed members. - /// The new member count. - internal async Task OnThreadMembersUpdateEventAsync(DiscordGuild guild, ulong thread_id, JArray added_members, JArray removed_members, int member_count) + /// The thread id of the target thread this update belongs to. + /// The added members. + /// The ids of the removed members. + /// The new member count. + internal async Task OnThreadMembersUpdateEventAsync(DiscordGuild guild, ulong threadId, JArray membersAdded, JArray membersRemoved, int memberCount) { - var thread = this.InternalGetCachedThread(thread_id); + var thread = this.InternalGetCachedThread(threadId); if (thread == null) { - var tempThread = await this.ApiClient.GetThreadAsync(thread_id); - thread = guild._threads.AddOrUpdate(thread_id, tempThread, (old, newThread) => newThread); + var tempThread = await this.ApiClient.GetThreadAsync(threadId); + thread = guild.ThreadsInternal.AddOrUpdate(threadId, tempThread, (old, newThread) => newThread); } thread.Discord = this; guild.Discord = this; List addedMembers = new(); - List removed_member_ids = new(); + List removedMemberIds = new(); - if (added_members != null) + if (membersAdded != null) { - foreach (var xj in added_members) + foreach (var xj in membersAdded) { var xtm = xj.ToDiscordObject(); xtm.Discord = this; - xtm._guild_id = guild.Id; + xtm.GuildId = guild.Id; if (xtm != null) addedMembers.Add(xtm); if (xtm.Id == this.CurrentUser.Id) thread.CurrentMember = xtm; } } var removedMembers = new List(); - if (removed_members != null) + if (membersRemoved != null) { - foreach (var removedId in removed_members) + foreach (var removedId in membersRemoved) { - removedMembers.Add(guild._members.TryGetValue((ulong)removedId, out var member) ? member : new DiscordMember { Id = (ulong)removedId, _guild_id = guild.Id, Discord = this }); + removedMembers.Add(guild.MembersInternal.TryGetValue((ulong)removedId, out var member) ? member : new DiscordMember { Id = (ulong)removedId, GuildId = guild.Id, Discord = this }); } } - if (removed_member_ids.Contains(this.CurrentUser.Id)) //indicates the bot was removed from the thread + if (removedMemberIds.Contains(this.CurrentUser.Id)) //indicates the bot was removed from the thread thread.CurrentMember = null; - thread.MemberCount = member_count; + thread.MemberCount = memberCount; var threadMembersUpdateArg = new ThreadMembersUpdateEventArgs(this.ServiceProvider) { Guild = guild, Thread = thread, AddedMembers = addedMembers, RemovedMembers = removedMembers, - MemberCount = member_count + MemberCount = memberCount }; await this._threadMembersUpdated.InvokeAsync(this, threadMembersUpdateArg).ConfigureAwait(false); } #endregion #region Activities /// /// Dispatches the event. /// /// The transport activity. /// The guild. /// The channel id. /// The users in the activity. /// The application id. /// A Task. - internal async Task OnEmbeddedActivityUpdateAsync(JObject tr_activity, DiscordGuild guild, ulong channel_id, JArray j_users, ulong app_id) + internal async Task OnEmbeddedActivityUpdateAsync(JObject trActivity, DiscordGuild guild, ulong channelId, JArray jUsers, ulong appId) => await Task.Delay(20); /*{ try { var users = j_users?.ToObject>(); DiscordActivity old = null; var uid = $"{guild.Id}_{channel_id}_{app_id}"; if (this._embeddedActivities.TryGetValue(uid, out var activity)) { old = new DiscordActivity(activity); DiscordJson.PopulateObject(tr_activity, activity); } else { activity = tr_activity.ToObject(); this._embeddedActivities[uid] = activity; } var activity_users = new List(); var channel = this.InternalGetCachedChannel(channel_id) ?? await this.ApiClient.GetChannelAsync(channel_id); if (users != null) { foreach (var user in users) { var activity_user = guild._members.TryGetValue(user, out var member) ? member : new DiscordMember { Id = user, _guild_id = guild.Id, Discord = this }; activity_users.Add(activity_user); } } else activity_users = null; var ea = new EmbeddedActivityUpdateEventArgs(this.ServiceProvider) { Guild = guild, Users = activity_users, Channel = channel }; await this._embeddedActivityUpdated.InvokeAsync(this, ea).ConfigureAwait(false); } catch (Exception ex) { this.Logger.LogError(ex, ex.Message); } }*/ #endregion #region User/Presence Update /// /// Handles the presence update event. /// /// The raw presence. /// The raw user. internal async Task OnPresenceUpdateEventAsync(JObject rawPresence, JObject rawUser) { var uid = (ulong)rawUser["id"]; DiscordPresence old = null; - if (this._presences.TryGetValue(uid, out var presence)) + if (this.PresencesInternal.TryGetValue(uid, out var presence)) { old = new DiscordPresence(presence); DiscordJson.PopulateObject(rawPresence, presence); } else { presence = rawPresence.ToObject(); presence.Discord = this; presence.Activity = new DiscordActivity(presence.RawActivity); - this._presences[presence.InternalUser.Id] = presence; + this.PresencesInternal[presence.InternalUser.Id] = presence; } // reuse arrays / avoid linq (this is a hot zone) if (presence.Activities == null || rawPresence["activities"] == null) { - presence._internalActivities = Array.Empty(); + presence.InternalActivities = Array.Empty(); } else { - if (presence._internalActivities.Length != presence.RawActivities.Length) - presence._internalActivities = new DiscordActivity[presence.RawActivities.Length]; + if (presence.InternalActivities.Length != presence.RawActivities.Length) + presence.InternalActivities = new DiscordActivity[presence.RawActivities.Length]; - for (var i = 0; i < presence._internalActivities.Length; i++) - presence._internalActivities[i] = new DiscordActivity(presence.RawActivities[i]); + for (var i = 0; i < presence.InternalActivities.Length; i++) + presence.InternalActivities[i] = new DiscordActivity(presence.RawActivities[i]); - if (presence._internalActivities.Length > 0) + if (presence.InternalActivities.Length > 0) { presence.RawActivity = presence.RawActivities[0]; if (presence.Activity != null) presence.Activity.UpdateWith(presence.RawActivity); else presence.Activity = new DiscordActivity(presence.RawActivity); } } if (this.UserCache.TryGetValue(uid, out var usr)) { if (old != null) { old.InternalUser.Username = usr.Username; old.InternalUser.Discriminator = usr.Discriminator; old.InternalUser.AvatarHash = usr.AvatarHash; } if (rawUser["username"] is object) usr.Username = (string)rawUser["username"]; if (rawUser["discriminator"] is object) usr.Discriminator = (string)rawUser["discriminator"]; if (rawUser["avatar"] is object) usr.AvatarHash = (string)rawUser["avatar"]; presence.InternalUser.Username = usr.Username; presence.InternalUser.Discriminator = usr.Discriminator; presence.InternalUser.AvatarHash = usr.AvatarHash; } var usrafter = usr ?? new DiscordUser(presence.InternalUser); var ea = new PresenceUpdateEventArgs(this.ServiceProvider) { Status = presence.Status, Activity = presence.Activity, User = usr, PresenceBefore = old, PresenceAfter = presence, UserBefore = old != null ? new DiscordUser(old.InternalUser) : usrafter, UserAfter = usrafter }; await this._presenceUpdated.InvokeAsync(this, ea).ConfigureAwait(false); } /// /// Handles the user settings update event. /// /// The user. internal async Task OnUserSettingsUpdateEventAsync(TransportUser user) { var usr = new DiscordUser(user) { Discord = this }; var ea = new UserSettingsUpdateEventArgs(this.ServiceProvider) { User = usr }; await this._userSettingsUpdated.InvokeAsync(this, ea).ConfigureAwait(false); } /// /// Handles the user update event. /// /// The user. internal async Task OnUserUpdateEventAsync(TransportUser user) { - var usr_old = new DiscordUser + var usrOld = new DiscordUser { AvatarHash = this.CurrentUser.AvatarHash, Discord = this, Discriminator = this.CurrentUser.Discriminator, Email = this.CurrentUser.Email, Id = this.CurrentUser.Id, IsBot = this.CurrentUser.IsBot, MfaEnabled = this.CurrentUser.MfaEnabled, Username = this.CurrentUser.Username, Verified = this.CurrentUser.Verified }; this.CurrentUser.AvatarHash = user.AvatarHash; this.CurrentUser.Discriminator = user.Discriminator; this.CurrentUser.Email = user.Email; this.CurrentUser.Id = user.Id; this.CurrentUser.IsBot = user.IsBot; this.CurrentUser.MfaEnabled = user.MfaEnabled; this.CurrentUser.Username = user.Username; this.CurrentUser.Verified = user.Verified; var ea = new UserUpdateEventArgs(this.ServiceProvider) { UserAfter = this.CurrentUser, - UserBefore = usr_old + UserBefore = usrOld }; await this._userUpdated.InvokeAsync(this, ea).ConfigureAwait(false); } #endregion #region Voice /// /// Handles the voice state update event. /// /// The raw. internal async Task OnVoiceStateUpdateEventAsync(JObject raw) { var gid = (ulong)raw["guild_id"]; var uid = (ulong)raw["user_id"]; - var gld = this._guilds[gid]; + var gld = this.GuildsInternal[gid]; var vstateNew = raw.ToObject(); vstateNew.Discord = this; - gld._voiceStates.TryRemove(uid, out var vstateOld); + gld.VoiceStatesInternal.TryRemove(uid, out var vstateOld); if (vstateNew.Channel != null) { - gld._voiceStates[vstateNew.UserId] = vstateNew; + gld.VoiceStatesInternal[vstateNew.UserId] = vstateNew; } - if (gld._members.TryGetValue(uid, out var mbr)) + if (gld.MembersInternal.TryGetValue(uid, out var mbr)) { mbr.IsMuted = vstateNew.IsServerMuted; mbr.IsDeafened = vstateNew.IsServerDeafened; } else { var transportMbr = vstateNew.TransportMember; this.UpdateUser(new DiscordUser(transportMbr.User) { Discord = this }, gid, gld, transportMbr); } var ea = new VoiceStateUpdateEventArgs(this.ServiceProvider) { Guild = vstateNew.Guild, Channel = vstateNew.Channel, User = vstateNew.User, SessionId = vstateNew.SessionId, Before = vstateOld, After = vstateNew }; await this._voiceStateUpdated.InvokeAsync(this, ea).ConfigureAwait(false); } /// /// Handles the voice server update event. /// /// The endpoint. /// The token. /// The guild. internal async Task OnVoiceServerUpdateEventAsync(string endpoint, string token, DiscordGuild guild) { var ea = new VoiceServerUpdateEventArgs(this.ServiceProvider) { Endpoint = endpoint, VoiceToken = token, Guild = guild }; await this._voiceServerUpdated.InvokeAsync(this, ea).ConfigureAwait(false); } #endregion #region Commands /// /// Handles the application command create. /// /// The cmd. /// The guild_id. - internal async Task OnApplicationCommandCreateAsync(DiscordApplicationCommand cmd, ulong? guild_id) + internal async Task OnApplicationCommandCreateAsync(DiscordApplicationCommand cmd, ulong? guildId) { cmd.Discord = this; - var guild = this.InternalGetCachedGuild(guild_id); + var guild = this.InternalGetCachedGuild(guildId); - if (guild == null && guild_id.HasValue) + if (guild == null && guildId.HasValue) { guild = new DiscordGuild { - Id = guild_id.Value, + Id = guildId.Value, Discord = this }; } var ea = new ApplicationCommandEventArgs(this.ServiceProvider) { Guild = guild, Command = cmd }; await this._applicationCommandCreated.InvokeAsync(this, ea).ConfigureAwait(false); } /// /// Handles the application command update. /// /// The cmd. /// The guild_id. - internal async Task OnApplicationCommandUpdateAsync(DiscordApplicationCommand cmd, ulong? guild_id) + internal async Task OnApplicationCommandUpdateAsync(DiscordApplicationCommand cmd, ulong? guildId) { cmd.Discord = this; - var guild = this.InternalGetCachedGuild(guild_id); + var guild = this.InternalGetCachedGuild(guildId); - if (guild == null && guild_id.HasValue) + if (guild == null && guildId.HasValue) { guild = new DiscordGuild { - Id = guild_id.Value, + Id = guildId.Value, Discord = this }; } var ea = new ApplicationCommandEventArgs(this.ServiceProvider) { Guild = guild, Command = cmd }; await this._applicationCommandUpdated.InvokeAsync(this, ea).ConfigureAwait(false); } /// /// Handles the application command delete. /// /// The cmd. /// The guild_id. - internal async Task OnApplicationCommandDeleteAsync(DiscordApplicationCommand cmd, ulong? guild_id) + internal async Task OnApplicationCommandDeleteAsync(DiscordApplicationCommand cmd, ulong? guildId) { cmd.Discord = this; - var guild = this.InternalGetCachedGuild(guild_id); + var guild = this.InternalGetCachedGuild(guildId); - if (guild == null && guild_id.HasValue) + if (guild == null && guildId.HasValue) { guild = new DiscordGuild { - Id = guild_id.Value, + Id = guildId.Value, Discord = this }; } var ea = new ApplicationCommandEventArgs(this.ServiceProvider) { Guild = guild, Command = cmd }; await this._applicationCommandDeleted.InvokeAsync(this, ea).ConfigureAwait(false); } /// /// Handles the guild application command counts update. /// /// The count. /// The count. /// The count. /// The guild_id. /// Count of application commands. - internal async Task OnGuildApplicationCommandCountsUpdateAsync(int sc, int ucmc, int mcmc, ulong guild_id) + internal async Task OnGuildApplicationCommandCountsUpdateAsync(int sc, int ucmc, int mcmc, ulong guildId) { - var guild = this.InternalGetCachedGuild(guild_id); + var guild = this.InternalGetCachedGuild(guildId); if (guild == null) { guild = new DiscordGuild { - Id = guild_id, + Id = guildId, Discord = this }; } var ea = new GuildApplicationCommandCountEventArgs(this.ServiceProvider) { SlashCommands = sc, UserContextMenuCommands = ucmc, MessageContextMenuCommands = mcmc, Guild = guild }; await this._guildApplicationCommandCountUpdated.InvokeAsync(this, ea).ConfigureAwait(false); } /// /// Handles the application command permissions update. /// /// The new permissions. /// The command id. /// The guild id. /// The application id. - internal async Task OnApplicationCommandPermissionsUpdateAsync(IEnumerable perms, ulong c_id, ulong guild_id, ulong a_id) + internal async Task OnApplicationCommandPermissionsUpdateAsync(IEnumerable perms, ulong cId, ulong guildId, ulong aId) { - if (a_id != this.CurrentApplication.Id) + if (aId != this.CurrentApplication.Id) return; - var guild = this.InternalGetCachedGuild(guild_id); + var guild = this.InternalGetCachedGuild(guildId); DiscordApplicationCommand cmd; try { - cmd = await this.GetGuildApplicationCommandAsync(guild_id, c_id); + cmd = await this.GetGuildApplicationCommandAsync(guildId, cId); } catch (NotFoundException) { - cmd = await this.GetGlobalApplicationCommandAsync(c_id); + cmd = await this.GetGlobalApplicationCommandAsync(cId); } if (guild == null) { guild = new DiscordGuild { - Id = guild_id, + Id = guildId, Discord = this }; } var ea = new ApplicationCommandPermissionsUpdateEventArgs(this.ServiceProvider) { Permissions = perms.ToList(), Command = cmd, - ApplicationId = a_id, + ApplicationId = aId, Guild = guild }; await this._applicationCommandPermissionsUpdated.InvokeAsync(this, ea).ConfigureAwait(false); } #endregion #region Interaction /// /// Handles the interaction create. /// /// The guild id. /// The channel id. /// The user. /// The member. /// The interaction. internal async Task OnInteractionCreateAsync(ulong? guildId, ulong channelId, TransportUser user, TransportMember member, DiscordInteraction interaction) { var usr = new DiscordUser(user) { Discord = this }; interaction.ChannelId = channelId; interaction.GuildId = guildId; interaction.Discord = this; interaction.Data.Discord = this; if (member != null) { - usr = new DiscordMember(member) { _guild_id = guildId.Value, Discord = this }; + usr = new DiscordMember(member) { GuildId = guildId.Value, Discord = this }; this.UpdateUser(usr, guildId, interaction.Guild, member); } else { this.UserCache.AddOrUpdate(usr.Id, usr, (old, @new) => @new); } interaction.User = usr; var resolved = interaction.Data.Resolved; if (resolved != null) { if (resolved.Users != null) { foreach (var c in resolved.Users) { c.Value.Discord = this; this.UserCache.AddOrUpdate(c.Value.Id, c.Value, (old, @new) => @new); } } if (resolved.Members != null) { foreach (var c in resolved.Members) { c.Value.Discord = this; c.Value.Id = c.Key; - c.Value._guild_id = guildId.Value; + c.Value.GuildId = guildId.Value; c.Value.User.Discord = this; this.UserCache.AddOrUpdate(c.Value.User.Id, c.Value.User, (old, @new) => @new); } } if (resolved.Channels != null) { foreach (var c in resolved.Channels) { c.Value.Discord = this; if (guildId.HasValue) c.Value.GuildId = guildId.Value; } } if (resolved.Roles != null) { foreach (var c in resolved.Roles) { c.Value.Discord = this; if (guildId.HasValue) - c.Value._guild_id = guildId.Value; + c.Value.GuildId = guildId.Value; } } if (resolved.Messages != null) { foreach (var m in resolved.Messages) { m.Value.Discord = this; if (guildId.HasValue) m.Value.GuildId = guildId.Value; } } } if (interaction.Type is InteractionType.Component || interaction.Type is InteractionType.ModalSubmit) { if (interaction.Message != null) { interaction.Message.Discord = this; interaction.Message.ChannelId = interaction.ChannelId; } var cea = new ComponentInteractionCreateEventArgs(this.ServiceProvider) { Message = interaction.Message, Interaction = interaction }; await this._componentInteractionCreated.InvokeAsync(this, cea).ConfigureAwait(false); } else { if (interaction.Data.Target.HasValue) // Context-Menu. // { var targetId = interaction.Data.Target.Value; DiscordUser targetUser = null; DiscordMember targetMember = null; DiscordMessage targetMessage = null; interaction.Data.Resolved.Messages?.TryGetValue(targetId, out targetMessage); interaction.Data.Resolved.Members?.TryGetValue(targetId, out targetMember); interaction.Data.Resolved.Users?.TryGetValue(targetId, out targetUser); var ctea = new ContextMenuInteractionCreateEventArgs(this.ServiceProvider) { Interaction = interaction, TargetUser = targetMember ?? targetUser, TargetMessage = targetMessage, Type = interaction.Data.Type, }; await this._contextMenuInteractionCreated.InvokeAsync(this, ctea).ConfigureAwait(false); } else { var ea = new InteractionCreateEventArgs(this.ServiceProvider) { Interaction = interaction }; await this._interactionCreated.InvokeAsync(this, ea).ConfigureAwait(false); } } } #endregion #region Misc /// /// Handles the typing start event. /// /// The user id. /// The channel id. /// The channel. /// The guild id. /// The started. /// The mbr. internal async Task OnTypingStartEventAsync(ulong userId, ulong channelId, DiscordChannel channel, ulong? guildId, DateTimeOffset started, TransportMember mbr) { if (channel == null) { channel = new DiscordChannel { Discord = this, Id = channelId, GuildId = guildId ?? default, }; } var guild = this.InternalGetCachedGuild(guildId); var usr = this.UpdateUser(new DiscordUser { Id = userId, Discord = this }, guildId, guild, mbr); var ea = new TypingStartEventArgs(this.ServiceProvider) { Channel = channel, User = usr, Guild = guild, StartedAt = started }; await this._typingStarted.InvokeAsync(this, ea).ConfigureAwait(false); } /// /// Handles the webhooks update. /// /// The channel. /// The guild. internal async Task OnWebhooksUpdateAsync(DiscordChannel channel, DiscordGuild guild) { var ea = new WebhooksUpdateEventArgs(this.ServiceProvider) { Channel = channel, Guild = guild }; await this._webhooksUpdated.InvokeAsync(this, ea).ConfigureAwait(false); } /// /// Handles the unknown event. /// /// The payload. internal async Task OnUnknownEventAsync(GatewayPayload payload) { var ea = new UnknownEventArgs(this.ServiceProvider) { EventName = payload.EventName, Json = (payload.Data as JObject)?.ToString() }; await this._unknownEvent.InvokeAsync(this, ea).ConfigureAwait(false); } #endregion #endregion } } diff --git a/DisCatSharp/Clients/DiscordClient.WebSocket.cs b/DisCatSharp/Clients/DiscordClient.WebSocket.cs index bee655baf..7caa8cc99 100644 --- a/DisCatSharp/Clients/DiscordClient.WebSocket.cs +++ b/DisCatSharp/Clients/DiscordClient.WebSocket.cs @@ -1,601 +1,601 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System; using System.Collections.Concurrent; using System.IO; using System.Threading; using System.Threading.Tasks; using DisCatSharp.Entities; using DisCatSharp.EventArgs; using DisCatSharp.Net.Abstractions; using DisCatSharp.Net.WebSocket; using Microsoft.Extensions.Logging; using Newtonsoft.Json; using Newtonsoft.Json.Linq; namespace DisCatSharp { /// /// Represents a discord websocket client. /// public sealed partial class DiscordClient { #region Private Fields private int _heartbeatInterval; private DateTimeOffset _lastHeartbeat; [System.Diagnostics.CodeAnalysis.SuppressMessage("CodeQuality", "IDE0052:Remove unread private members", Justification = "")] private Task _heartbeatTask; - internal static DateTimeOffset _discordEpoch = new(2015, 1, 1, 0, 0, 0, TimeSpan.Zero); + internal static DateTimeOffset DiscordEpoch = new(2015, 1, 1, 0, 0, 0, TimeSpan.Zero); private int _skippedHeartbeats = 0; private long _lastSequence; - internal IWebSocketClient _webSocketClient; + internal IWebSocketClient WebSocketClient; private PayloadDecompressor _payloadDecompressor; private CancellationTokenSource _cancelTokenSource; private CancellationToken _cancelToken; #endregion #region Connection Semaphore /// /// Gets the socket locks. /// - private static ConcurrentDictionary SocketLocks { get; } = new ConcurrentDictionary(); + private static ConcurrentDictionary s_socketLocks { get; } = new ConcurrentDictionary(); /// /// Gets the session lock. /// private ManualResetEventSlim SessionLock { get; } = new ManualResetEventSlim(true); #endregion #region Internal Connection Methods /// /// Internals the reconnect async. /// /// If true, start new session. /// The code. /// The message. /// A Task. private Task InternalReconnectAsync(bool startNewSession = false, int code = 1000, string message = "") { if (startNewSession) this._sessionId = null; - _ = this._webSocketClient.DisconnectAsync(code, message); + _ = this.WebSocketClient.DisconnectAsync(code, message); return Task.CompletedTask; } /// /// Internals the connect async. /// /// A Task. internal async Task InternalConnectAsync() { SocketLock socketLock = null; try { if (this.GatewayInfo == null) await this.InternalUpdateGatewayAsync().ConfigureAwait(false); await this.InitializeAsync().ConfigureAwait(false); socketLock = this.GetSocketLock(); await socketLock.LockAsync().ConfigureAwait(false); } catch { socketLock?.UnlockAfter(TimeSpan.Zero); throw; } if (!this.Presences.ContainsKey(this.CurrentUser.Id)) { - this._presences[this.CurrentUser.Id] = new DiscordPresence + this.PresencesInternal[this.CurrentUser.Id] = new DiscordPresence { Discord = this, RawActivity = new TransportActivity(), Activity = new DiscordActivity(), Status = UserStatus.Online, InternalUser = new TransportUser { Id = this.CurrentUser.Id, Username = this.CurrentUser.Username, Discriminator = this.CurrentUser.Discriminator, AvatarHash = this.CurrentUser.AvatarHash } }; } else { - var pr = this._presences[this.CurrentUser.Id]; + var pr = this.PresencesInternal[this.CurrentUser.Id]; pr.RawActivity = new TransportActivity(); pr.Activity = new DiscordActivity(); pr.Status = UserStatus.Online; } Volatile.Write(ref this._skippedHeartbeats, 0); - this._webSocketClient = this.Configuration.WebSocketClientFactory(this.Configuration.Proxy, this.ServiceProvider); + this.WebSocketClient = this.Configuration.WebSocketClientFactory(this.Configuration.Proxy, this.ServiceProvider); this._payloadDecompressor = this.Configuration.GatewayCompressionLevel != GatewayCompressionLevel.None ? new PayloadDecompressor(this.Configuration.GatewayCompressionLevel) : null; this._cancelTokenSource = new CancellationTokenSource(); this._cancelToken = this._cancelTokenSource.Token; - this._webSocketClient.Connected += SocketOnConnect; - this._webSocketClient.Disconnected += SocketOnDisconnect; - this._webSocketClient.MessageReceived += SocketOnMessage; - this._webSocketClient.ExceptionThrown += SocketOnException; + this.WebSocketClient.Connected += SocketOnConnect; + this.WebSocketClient.Disconnected += SocketOnDisconnect; + this.WebSocketClient.MessageReceived += SocketOnMessage; + this.WebSocketClient.ExceptionThrown += SocketOnException; var gwuri = new QueryUriBuilder(this.GatewayUri) .AddParameter("v", this.Configuration.ApiVersion) .AddParameter("encoding", "json"); if (this.Configuration.GatewayCompressionLevel == GatewayCompressionLevel.Stream) gwuri.AddParameter("compress", "zlib-stream"); - await this._webSocketClient.ConnectAsync(gwuri.Build()).ConfigureAwait(false); + await this.WebSocketClient.ConnectAsync(gwuri.Build()).ConfigureAwait(false); Task SocketOnConnect(IWebSocketClient sender, SocketEventArgs e) => this._socketOpened.InvokeAsync(this, e); async Task SocketOnMessage(IWebSocketClient sender, SocketMessageEventArgs e) { string msg = null; if (e is SocketTextMessageEventArgs etext) { msg = etext.Message; } else if (e is SocketBinaryMessageEventArgs ebin) // :DDDD { using var ms = new MemoryStream(); if (!this._payloadDecompressor.TryDecompress(new ArraySegment(ebin.Message), ms)) { this.Logger.LogError(LoggerEvents.WebSocketReceiveFailure, "Payload decompression failed"); return; } ms.Position = 0; using var sr = new StreamReader(ms, Utilities.UTF8); msg = await sr.ReadToEndAsync().ConfigureAwait(false); } try { this.Logger.LogTrace(LoggerEvents.GatewayWsRx, msg); await this.HandleSocketMessageAsync(msg).ConfigureAwait(false); } catch (Exception ex) { this.Logger.LogError(LoggerEvents.WebSocketReceiveFailure, ex, "Socket handler suppressed an exception"); } } Task SocketOnException(IWebSocketClient sender, SocketErrorEventArgs e) => this._socketErrored.InvokeAsync(this, e); async Task SocketOnDisconnect(IWebSocketClient sender, SocketCloseEventArgs e) { // release session and connection this.ConnectionLock.Set(); this.SessionLock.Set(); if (!this._disposed) this._cancelTokenSource.Cancel(); this.Logger.LogDebug(LoggerEvents.ConnectionClose, "Connection closed ({0}, '{1}')", e.CloseCode, e.CloseMessage); await this._socketClosed.InvokeAsync(this, e).ConfigureAwait(false); if (this.Configuration.AutoReconnect && (e.CloseCode < 4001 || e.CloseCode >= 5000)) { this.Logger.LogCritical(LoggerEvents.ConnectionClose, "Connection terminated ({0}, '{1}'), reconnecting", e.CloseCode, e.CloseMessage); if (this._status == null) await this.ConnectAsync().ConfigureAwait(false); else if (this._status.IdleSince.HasValue) - await this.ConnectAsync(this._status._activity, this._status.Status, Utilities.GetDateTimeOffsetFromMilliseconds(this._status.IdleSince.Value)).ConfigureAwait(false); + await this.ConnectAsync(this._status.ActivityInternal, this._status.Status, Utilities.GetDateTimeOffsetFromMilliseconds(this._status.IdleSince.Value)).ConfigureAwait(false); else - await this.ConnectAsync(this._status._activity, this._status.Status).ConfigureAwait(false); + await this.ConnectAsync(this._status.ActivityInternal, this._status.Status).ConfigureAwait(false); } else { this.Logger.LogCritical(LoggerEvents.ConnectionClose, "Connection terminated ({0}, '{1}')", e.CloseCode, e.CloseMessage); } } } #endregion #region WebSocket (Events) /// /// Handles the socket message async. /// /// The data. /// A Task. internal async Task HandleSocketMessageAsync(string data) { var payload = JsonConvert.DeserializeObject(data); this._lastSequence = payload.Sequence ?? this._lastSequence; switch (payload.OpCode) { case GatewayOpCode.Dispatch: await this.HandleDispatchAsync(payload).ConfigureAwait(false); break; case GatewayOpCode.Heartbeat: await this.OnHeartbeatAsync((long)payload.Data).ConfigureAwait(false); break; case GatewayOpCode.Reconnect: await this.OnReconnectAsync().ConfigureAwait(false); break; case GatewayOpCode.InvalidSession: await this.OnInvalidateSessionAsync((bool)payload.Data).ConfigureAwait(false); break; case GatewayOpCode.Hello: await this.OnHelloAsync((payload.Data as JObject).ToObject()).ConfigureAwait(false); break; case GatewayOpCode.HeartbeatAck: await this.OnHeartbeatAckAsync().ConfigureAwait(false); break; default: this.Logger.LogWarning(LoggerEvents.WebSocketReceive, "Unknown Discord opcode: {0}\nPayload: {1}", payload.OpCode, payload.Data); break; } } /// /// Ons the heartbeat async. /// /// The seq. /// A Task. internal async Task OnHeartbeatAsync(long seq) { this.Logger.LogTrace(LoggerEvents.WebSocketReceive, "Received HEARTBEAT (OP1)"); await this.SendHeartbeatAsync(seq).ConfigureAwait(false); } /// /// Ons the reconnect async. /// /// A Task. internal async Task OnReconnectAsync() { this.Logger.LogTrace(LoggerEvents.WebSocketReceive, "Received RECONNECT (OP7)"); await this.InternalReconnectAsync(code: 4000, message: "OP7 acknowledged").ConfigureAwait(false); } /// /// Ons the invalidate session async. /// /// If true, data. /// A Task. internal async Task OnInvalidateSessionAsync(bool data) { // begin a session if one is not open already if (this.SessionLock.Wait(0)) this.SessionLock.Reset(); // we are sending a fresh resume/identify, so lock the socket var socketLock = this.GetSocketLock(); await socketLock.LockAsync().ConfigureAwait(false); socketLock.UnlockAfter(TimeSpan.FromSeconds(5)); if (data) { this.Logger.LogTrace(LoggerEvents.WebSocketReceive, "Received INVALID_SESSION (OP9, true)"); await Task.Delay(6000).ConfigureAwait(false); await this.SendResumeAsync().ConfigureAwait(false); } else { this.Logger.LogTrace(LoggerEvents.WebSocketReceive, "Received INVALID_SESSION (OP9, false)"); this._sessionId = null; await this.SendIdentifyAsync(this._status).ConfigureAwait(false); } } /// /// Ons the hello async. /// /// The hello. /// A Task. internal async Task OnHelloAsync(GatewayHello hello) { this.Logger.LogTrace(LoggerEvents.WebSocketReceive, "Received HELLO (OP10)"); if (this.SessionLock.Wait(0)) { this.SessionLock.Reset(); this.GetSocketLock().UnlockAfter(TimeSpan.FromSeconds(5)); } else { this.Logger.LogWarning(LoggerEvents.SessionUpdate, "Attempt to start a session while another session is active"); return; } Interlocked.CompareExchange(ref this._skippedHeartbeats, 0, 0); this._heartbeatInterval = hello.HeartbeatInterval; this._heartbeatTask = Task.Run(this.HeartbeatLoopAsync, this._cancelToken); if (string.IsNullOrEmpty(this._sessionId)) await this.SendIdentifyAsync(this._status).ConfigureAwait(false); else await this.SendResumeAsync().ConfigureAwait(false); } /// /// Ons the heartbeat ack async. /// /// A Task. internal async Task OnHeartbeatAckAsync() { Interlocked.Decrement(ref this._skippedHeartbeats); var ping = (int)(DateTime.Now - this._lastHeartbeat).TotalMilliseconds; this.Logger.LogTrace(LoggerEvents.WebSocketReceive, "Received HEARTBEAT_ACK (OP11, {0}ms)", ping); Volatile.Write(ref this._ping, ping); var args = new HeartbeatEventArgs(this.ServiceProvider) { Ping = this.Ping, Timestamp = DateTimeOffset.Now }; await this._heartbeated.InvokeAsync(this, args).ConfigureAwait(false); } /// /// Heartbeats the loop async. /// /// A Task. internal async Task HeartbeatLoopAsync() { this.Logger.LogDebug(LoggerEvents.Heartbeat, "Heartbeat task started"); var token = this._cancelToken; try { while (true) { await this.SendHeartbeatAsync(this._lastSequence).ConfigureAwait(false); await Task.Delay(this._heartbeatInterval, token).ConfigureAwait(false); token.ThrowIfCancellationRequested(); } } catch (OperationCanceledException) { } } #endregion #region Internal Gateway Methods /// /// Internals the update status async. /// /// The activity. /// The user status. /// The idle since. /// A Task. internal async Task InternalUpdateStatusAsync(DiscordActivity activity, UserStatus? userStatus, DateTimeOffset? idleSince) { if (activity != null && activity.Name != null && activity.Name.Length > 128) throw new Exception("Game name can't be longer than 128 characters!"); - var since_unix = idleSince != null ? (long?)Utilities.GetUnixTime(idleSince.Value) : null; + var sinceUnix = idleSince != null ? (long?)Utilities.GetUnixTime(idleSince.Value) : null; var act = activity ?? new DiscordActivity(); var status = new StatusUpdate { Activity = new TransportActivity(act), - IdleSince = since_unix, - IsAFK = idleSince != null, + IdleSince = sinceUnix, + IsAfk = idleSince != null, Status = userStatus ?? UserStatus.Online }; // Solution to have status persist between sessions this._status = status; - var status_update = new GatewayPayload + var statusUpdate = new GatewayPayload { OpCode = GatewayOpCode.StatusUpdate, Data = status }; - var statusstr = JsonConvert.SerializeObject(status_update); + var statusstr = JsonConvert.SerializeObject(statusUpdate); await this.WsSendAsync(statusstr).ConfigureAwait(false); - if (!this._presences.ContainsKey(this.CurrentUser.Id)) + if (!this.PresencesInternal.ContainsKey(this.CurrentUser.Id)) { - this._presences[this.CurrentUser.Id] = new DiscordPresence + this.PresencesInternal[this.CurrentUser.Id] = new DiscordPresence { Discord = this, Activity = act, Status = userStatus ?? UserStatus.Online, InternalUser = new TransportUser { Id = this.CurrentUser.Id } }; } else { - var pr = this._presences[this.CurrentUser.Id]; + var pr = this.PresencesInternal[this.CurrentUser.Id]; pr.Activity = act; pr.Status = userStatus ?? pr.Status; } } /// /// Sends the heartbeat async. /// /// The seq. /// A Task. internal async Task SendHeartbeatAsync(long seq) { - var more_than_5 = Volatile.Read(ref this._skippedHeartbeats) > 5; - var guilds_comp = Volatile.Read(ref this._guildDownloadCompleted); - if (guilds_comp && more_than_5) + var moreThan5 = Volatile.Read(ref this._skippedHeartbeats) > 5; + var guildsComp = Volatile.Read(ref this._guildDownloadCompleted); + if (guildsComp && moreThan5) { this.Logger.LogCritical(LoggerEvents.HeartbeatFailure, "Server failed to acknowledge more than 5 heartbeats - connection is zombie"); var args = new ZombiedEventArgs(this.ServiceProvider) { Failures = Volatile.Read(ref this._skippedHeartbeats), GuildDownloadCompleted = true }; await this._zombied.InvokeAsync(this, args).ConfigureAwait(false); await this.InternalReconnectAsync(code: 4001, message: "Too many heartbeats missed").ConfigureAwait(false); return; } - else if (!guilds_comp && more_than_5) + else if (!guildsComp && moreThan5) { var args = new ZombiedEventArgs(this.ServiceProvider) { Failures = Volatile.Read(ref this._skippedHeartbeats), GuildDownloadCompleted = false }; await this._zombied.InvokeAsync(this, args).ConfigureAwait(false); this.Logger.LogWarning(LoggerEvents.HeartbeatFailure, "Server failed to acknowledge more than 5 heartbeats, but the guild download is still running - check your connection speed"); } Volatile.Write(ref this._lastSequence, seq); this.Logger.LogTrace(LoggerEvents.Heartbeat, "Sending heartbeat"); var heartbeat = new GatewayPayload { OpCode = GatewayOpCode.Heartbeat, Data = seq }; - var heartbeat_str = JsonConvert.SerializeObject(heartbeat); - await this.WsSendAsync(heartbeat_str).ConfigureAwait(false); + var heartbeatStr = JsonConvert.SerializeObject(heartbeat); + await this.WsSendAsync(heartbeatStr).ConfigureAwait(false); this._lastHeartbeat = DateTimeOffset.Now; Interlocked.Increment(ref this._skippedHeartbeats); } /// /// Sends the identify async. /// /// The status. /// A Task. internal async Task SendIdentifyAsync(StatusUpdate status) { var identify = new GatewayIdentify { Token = Utilities.GetFormattedToken(this), Compress = this.Configuration.GatewayCompressionLevel == GatewayCompressionLevel.Payload, LargeThreshold = this.Configuration.LargeThreshold, ShardInfo = new ShardInfo { ShardId = this.Configuration.ShardId, ShardCount = this.Configuration.ShardCount }, Presence = status, Intents = this.Configuration.Intents, Discord = this }; var payload = new GatewayPayload { OpCode = GatewayOpCode.Identify, Data = identify }; var payloadstr = JsonConvert.SerializeObject(payload); await this.WsSendAsync(payloadstr).ConfigureAwait(false); this.Logger.LogDebug(LoggerEvents.Intents, "Registered gateway intents ({0})", this.Configuration.Intents); } /// /// Sends the resume async. /// /// A Task. internal async Task SendResumeAsync() { var resume = new GatewayResume { Token = Utilities.GetFormattedToken(this), SessionId = this._sessionId, SequenceNumber = Volatile.Read(ref this._lastSequence) }; - var resume_payload = new GatewayPayload + var resumePayload = new GatewayPayload { OpCode = GatewayOpCode.Resume, Data = resume }; - var resumestr = JsonConvert.SerializeObject(resume_payload); + var resumestr = JsonConvert.SerializeObject(resumePayload); await this.WsSendAsync(resumestr).ConfigureAwait(false); } /// /// Internals the update gateway async. /// /// A Task. internal async Task InternalUpdateGatewayAsync() { var info = await this.GetGatewayInfoAsync().ConfigureAwait(false); this.GatewayInfo = info; this.GatewayUri = new Uri(info.Url); } /// /// Ws the send async. /// /// The payload. /// A Task. internal async Task WsSendAsync(string payload) { this.Logger.LogTrace(LoggerEvents.GatewayWsTx, payload); - await this._webSocketClient.SendMessageAsync(payload).ConfigureAwait(false); + await this.WebSocketClient.SendMessageAsync(payload).ConfigureAwait(false); } #endregion #region Semaphore Methods /// /// Gets the socket lock. /// /// A SocketLock. private SocketLock GetSocketLock() - => SocketLocks.GetOrAdd(this.CurrentApplication.Id, appId => new SocketLock(appId, this.GatewayInfo.SessionBucket.MaxConcurrency)); + => s_socketLocks.GetOrAdd(this.CurrentApplication.Id, appId => new SocketLock(appId, this.GatewayInfo.SessionBucket.MaxConcurrency)); #endregion } } diff --git a/DisCatSharp/Clients/DiscordClient.cs b/DisCatSharp/Clients/DiscordClient.cs index e726ca5ce..b11ea7d50 100644 --- a/DisCatSharp/Clients/DiscordClient.cs +++ b/DisCatSharp/Clients/DiscordClient.cs @@ -1,1316 +1,1316 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Globalization; using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; using DisCatSharp.Common.Utilities; using DisCatSharp.Entities; using DisCatSharp.Enums; using DisCatSharp.EventArgs; using DisCatSharp.Exceptions; using DisCatSharp.Net; using DisCatSharp.Net.Abstractions; using DisCatSharp.Net.Models; using DisCatSharp.Net.Serialization; using Microsoft.Extensions.Logging; using Newtonsoft.Json.Linq; namespace DisCatSharp { /// /// A Discord API wrapper. /// public sealed partial class DiscordClient : BaseDiscordClient { #region Internal Fields/Properties - internal bool _isShard = false; + internal bool IsShard = false; /// /// Gets the message cache. /// internal RingBuffer MessageCache { get; } private List _extensions = new(); private StatusUpdate _status = null; /// /// Gets the connection lock. /// private ManualResetEventSlim ConnectionLock { get; } = new ManualResetEventSlim(true); #endregion #region Public Fields/Properties /// /// Gets the gateway protocol version. /// public int GatewayVersion { get; internal set; } /// /// Gets the gateway session information for this client. /// public GatewayInfo GatewayInfo { get; internal set; } /// /// Gets the gateway URL. /// public Uri GatewayUri { get; internal set; } /// /// Gets the total number of shards the bot is connected to. /// public int ShardCount => this.GatewayInfo != null ? this.GatewayInfo.ShardCount : this.Configuration.ShardCount; /// /// Gets the currently connected shard ID. /// public int ShardId => this.Configuration.ShardId; /// /// Gets the intents configured for this client. /// public DiscordIntents Intents => this.Configuration.Intents; /// /// Gets a dictionary of guilds that this client is in. The dictionary's key is the guild ID. Note that the /// guild objects in this dictionary will not be filled in if the specific guilds aren't available (the /// or events haven't been fired yet) /// public override IReadOnlyDictionary Guilds { get; } - internal ConcurrentDictionary _guilds = new(); + internal ConcurrentDictionary GuildsInternal = new(); /// /// Gets the WS latency for this client. /// public int Ping => Volatile.Read(ref this._ping); private int _ping; /// /// Gets the collection of presences held by this client. /// public IReadOnlyDictionary Presences => this._presencesLazy.Value; - internal Dictionary _presences = new(); + internal Dictionary PresencesInternal = new(); private Lazy> _presencesLazy; /// /// Gets the collection of presences held by this client. /// public IReadOnlyDictionary EmbeddedActivities => this._embeddedActivitiesLazy.Value; - internal Dictionary _embeddedActivities = new(); + internal Dictionary EmbeddedActivitiesInternal = new(); private Lazy> _embeddedActivitiesLazy; #endregion #region Constructor/Internal Setup /// /// Initializes a new instance of . /// /// Specifies configuration parameters. public DiscordClient(DiscordConfiguration config) : base(config) { if (this.Configuration.MessageCacheSize > 0) { var intents = this.Configuration.Intents; this.MessageCache = intents.HasIntent(DiscordIntents.GuildMessages) || intents.HasIntent(DiscordIntents.DirectMessages) ? new RingBuffer(this.Configuration.MessageCacheSize) : null; } this.InternalSetup(); - this.Guilds = new ReadOnlyConcurrentDictionary(this._guilds); + this.Guilds = new ReadOnlyConcurrentDictionary(this.GuildsInternal); } /// /// Internal setup of the Client. /// internal void InternalSetup() { this._clientErrored = new AsyncEvent("CLIENT_ERRORED", EventExecutionLimit, this.Goof); this._socketErrored = new AsyncEvent("SOCKET_ERRORED", EventExecutionLimit, this.Goof); this._socketOpened = new AsyncEvent("SOCKET_OPENED", EventExecutionLimit, this.EventErrorHandler); this._socketClosed = new AsyncEvent("SOCKET_CLOSED", EventExecutionLimit, this.EventErrorHandler); this._ready = new AsyncEvent("READY", EventExecutionLimit, this.EventErrorHandler); this._resumed = new AsyncEvent("RESUMED", EventExecutionLimit, this.EventErrorHandler); this._channelCreated = new AsyncEvent("CHANNEL_CREATED", EventExecutionLimit, this.EventErrorHandler); this._channelUpdated = new AsyncEvent("CHANNEL_UPDATED", EventExecutionLimit, this.EventErrorHandler); this._channelDeleted = new AsyncEvent("CHANNEL_DELETED", EventExecutionLimit, this.EventErrorHandler); this._dmChannelDeleted = new AsyncEvent("DM_CHANNEL_DELETED", EventExecutionLimit, this.EventErrorHandler); this._channelPinsUpdated = new AsyncEvent("CHANNEL_PINS_UPDATED", EventExecutionLimit, this.EventErrorHandler); this._guildCreated = new AsyncEvent("GUILD_CREATED", EventExecutionLimit, this.EventErrorHandler); this._guildAvailable = new AsyncEvent("GUILD_AVAILABLE", EventExecutionLimit, this.EventErrorHandler); this._guildUpdated = new AsyncEvent("GUILD_UPDATED", EventExecutionLimit, this.EventErrorHandler); this._guildDeleted = new AsyncEvent("GUILD_DELETED", EventExecutionLimit, this.EventErrorHandler); this._guildUnavailable = new AsyncEvent("GUILD_UNAVAILABLE", EventExecutionLimit, this.EventErrorHandler); this._guildDownloadCompletedEv = new AsyncEvent("GUILD_DOWNLOAD_COMPLETED", EventExecutionLimit, this.EventErrorHandler); this._inviteCreated = new AsyncEvent("INVITE_CREATED", EventExecutionLimit, this.EventErrorHandler); this._inviteDeleted = new AsyncEvent("INVITE_DELETED", EventExecutionLimit, this.EventErrorHandler); this._messageCreated = new AsyncEvent("MESSAGE_CREATED", EventExecutionLimit, this.EventErrorHandler); this._presenceUpdated = new AsyncEvent("PRESENCE_UPDATED", EventExecutionLimit, this.EventErrorHandler); this._guildBanAdded = new AsyncEvent("GUILD_BAN_ADD", EventExecutionLimit, this.EventErrorHandler); this._guildBanRemoved = new AsyncEvent("GUILD_BAN_REMOVED", EventExecutionLimit, this.EventErrorHandler); this._guildEmojisUpdated = new AsyncEvent("GUILD_EMOJI_UPDATED", EventExecutionLimit, this.EventErrorHandler); this._guildStickersUpdated = new AsyncEvent("GUILD_STICKER_UPDATED", EventExecutionLimit, this.EventErrorHandler); this._guildIntegrationsUpdated = new AsyncEvent("GUILD_INTEGRATIONS_UPDATED", EventExecutionLimit, this.EventErrorHandler); this._guildMemberAdded = new AsyncEvent("GUILD_MEMBER_ADD", EventExecutionLimit, this.EventErrorHandler); this._guildMemberRemoved = new AsyncEvent("GUILD_MEMBER_REMOVED", EventExecutionLimit, this.EventErrorHandler); this._guildMemberUpdated = new AsyncEvent("GUILD_MEMBER_UPDATED", EventExecutionLimit, this.EventErrorHandler); this._guildRoleCreated = new AsyncEvent("GUILD_ROLE_CREATED", EventExecutionLimit, this.EventErrorHandler); this._guildRoleUpdated = new AsyncEvent("GUILD_ROLE_UPDATED", EventExecutionLimit, this.EventErrorHandler); this._guildRoleDeleted = new AsyncEvent("GUILD_ROLE_DELETED", EventExecutionLimit, this.EventErrorHandler); this._messageAcknowledged = new AsyncEvent("MESSAGE_ACKNOWLEDGED", EventExecutionLimit, this.EventErrorHandler); this._messageUpdated = new AsyncEvent("MESSAGE_UPDATED", EventExecutionLimit, this.EventErrorHandler); this._messageDeleted = new AsyncEvent("MESSAGE_DELETED", EventExecutionLimit, this.EventErrorHandler); this._messagesBulkDeleted = new AsyncEvent("MESSAGE_BULK_DELETED", EventExecutionLimit, this.EventErrorHandler); this._interactionCreated = new AsyncEvent("INTERACTION_CREATED", EventExecutionLimit, this.EventErrorHandler); this._componentInteractionCreated = new AsyncEvent("COMPONENT_INTERACTED", EventExecutionLimit, this.EventErrorHandler); this._contextMenuInteractionCreated = new AsyncEvent("CONTEXT_MENU_INTERACTED", EventExecutionLimit, this.EventErrorHandler); this._typingStarted = new AsyncEvent("TYPING_STARTED", EventExecutionLimit, this.EventErrorHandler); this._userSettingsUpdated = new AsyncEvent("USER_SETTINGS_UPDATED", EventExecutionLimit, this.EventErrorHandler); this._userUpdated = new AsyncEvent("USER_UPDATED", EventExecutionLimit, this.EventErrorHandler); this._voiceStateUpdated = new AsyncEvent("VOICE_STATE_UPDATED", EventExecutionLimit, this.EventErrorHandler); this._voiceServerUpdated = new AsyncEvent("VOICE_SERVER_UPDATED", EventExecutionLimit, this.EventErrorHandler); this._guildMembersChunked = new AsyncEvent("GUILD_MEMBERS_CHUNKED", EventExecutionLimit, this.EventErrorHandler); this._unknownEvent = new AsyncEvent("UNKNOWN_EVENT", EventExecutionLimit, this.EventErrorHandler); this._messageReactionAdded = new AsyncEvent("MESSAGE_REACTION_ADDED", EventExecutionLimit, this.EventErrorHandler); this._messageReactionRemoved = new AsyncEvent("MESSAGE_REACTION_REMOVED", EventExecutionLimit, this.EventErrorHandler); this._messageReactionsCleared = new AsyncEvent("MESSAGE_REACTIONS_CLEARED", EventExecutionLimit, this.EventErrorHandler); this._messageReactionRemovedEmoji = new AsyncEvent("MESSAGE_REACTION_REMOVED_EMOJI", EventExecutionLimit, this.EventErrorHandler); this._webhooksUpdated = new AsyncEvent("WEBHOOKS_UPDATED", EventExecutionLimit, this.EventErrorHandler); this._heartbeated = new AsyncEvent("HEARTBEATED", EventExecutionLimit, this.EventErrorHandler); this._applicationCommandCreated = new AsyncEvent("APPLICATION_COMMAND_CREATED", EventExecutionLimit, this.EventErrorHandler); this._applicationCommandUpdated = new AsyncEvent("APPLICATION_COMMAND_UPDATED", EventExecutionLimit, this.EventErrorHandler); this._applicationCommandDeleted = new AsyncEvent("APPLICATION_COMMAND_DELETED", EventExecutionLimit, this.EventErrorHandler); this._guildApplicationCommandCountUpdated = new AsyncEvent("GUILD_APPLICATION_COMMAND_COUNTS_UPDATED", EventExecutionLimit, this.EventErrorHandler); this._applicationCommandPermissionsUpdated = new AsyncEvent("APPLICATION_COMMAND_PERMISSIONS_UPDATED", EventExecutionLimit, this.EventErrorHandler); this._guildIntegrationCreated = new AsyncEvent("INTEGRATION_CREATED", EventExecutionLimit, this.EventErrorHandler); this._guildIntegrationUpdated = new AsyncEvent("INTEGRATION_UPDATED", EventExecutionLimit, this.EventErrorHandler); this._guildIntegrationDeleted = new AsyncEvent("INTEGRATION_DELETED", EventExecutionLimit, this.EventErrorHandler); this._stageInstanceCreated = new AsyncEvent("STAGE_INSTANCE_CREATED", EventExecutionLimit, this.EventErrorHandler); this._stageInstanceUpdated = new AsyncEvent("STAGE_INSTANCE_UPDATED", EventExecutionLimit, this.EventErrorHandler); this._stageInstanceDeleted = new AsyncEvent("STAGE_INSTANCE_DELETED", EventExecutionLimit, this.EventErrorHandler); this._threadCreated = new AsyncEvent("THREAD_CREATED", EventExecutionLimit, this.EventErrorHandler); this._threadUpdated = new AsyncEvent("THREAD_UPDATED", EventExecutionLimit, this.EventErrorHandler); this._threadDeleted = new AsyncEvent("THREAD_DELETED", EventExecutionLimit, this.EventErrorHandler); this._threadListSynced = new AsyncEvent("THREAD_LIST_SYNCED", EventExecutionLimit, this.EventErrorHandler); this._threadMemberUpdated = new AsyncEvent("THREAD_MEMBER_UPDATED", EventExecutionLimit, this.EventErrorHandler); this._threadMembersUpdated = new AsyncEvent("THREAD_MEMBERS_UPDATED", EventExecutionLimit, this.EventErrorHandler); this._zombied = new AsyncEvent("ZOMBIED", EventExecutionLimit, this.EventErrorHandler); this._payloadReceived = new AsyncEvent("PAYLOAD_RECEIVED", EventExecutionLimit, this.EventErrorHandler); this._guildScheduledEventCreated = new AsyncEvent("GUILD_SCHEDULED_EVENT_CREATED", EventExecutionLimit, this.EventErrorHandler); this._guildScheduledEventUpdated = new AsyncEvent("GUILD_SCHEDULED_EVENT_UPDATED", EventExecutionLimit, this.EventErrorHandler); this._guildScheduledEventDeleted = new AsyncEvent("GUILD_SCHEDULED_EVENT_DELETED", EventExecutionLimit, this.EventErrorHandler); this._guildScheduledEventUserAdded = new AsyncEvent("GUILD_SCHEDULED_EVENT_USER_ADDED", EventExecutionLimit, this.EventErrorHandler); this._guildScheduledEventUserRemoved = new AsyncEvent("GUILD_SCHEDULED_EVENT_USER_REMOVED", EventExecutionLimit, this.EventErrorHandler); this._embeddedActivityUpdated = new AsyncEvent("EMBEDDED_ACTIVITY_UPDATED", EventExecutionLimit, this.EventErrorHandler); - this._guilds.Clear(); + this.GuildsInternal.Clear(); - this._presencesLazy = new Lazy>(() => new ReadOnlyDictionary(this._presences)); - this._embeddedActivitiesLazy = new Lazy>(() => new ReadOnlyDictionary(this._embeddedActivities)); + this._presencesLazy = new Lazy>(() => new ReadOnlyDictionary(this.PresencesInternal)); + this._embeddedActivitiesLazy = new Lazy>(() => new ReadOnlyDictionary(this.EmbeddedActivitiesInternal)); } #endregion #region Client Extension Methods /// /// Registers an extension with this client. /// /// Extension to register. public void AddExtension(BaseExtension ext) { ext.Setup(this); this._extensions.Add(ext); } /// /// Retrieves a previously-registered extension from this client. /// /// Type of extension to retrieve. /// The requested extension. public T GetExtension() where T : BaseExtension => this._extensions.FirstOrDefault(x => x.GetType() == typeof(T)) as T; #endregion #region Public Connection Methods /// /// Connects to the gateway. /// /// Thrown when an invalid token was provided. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public async Task ConnectAsync(DiscordActivity activity = null, UserStatus? status = null, DateTimeOffset? idlesince = null) { // Check if connection lock is already set, and set it if it isn't if (!this.ConnectionLock.Wait(0)) throw new InvalidOperationException("This client is already connected."); this.ConnectionLock.Set(); var w = 7500; var i = 5; var s = false; Exception cex = null; if (activity == null && status == null && idlesince == null) this._status = null; else { - var since_unix = idlesince != null ? (long?)Utilities.GetUnixTime(idlesince.Value) : null; + var sinceUnix = idlesince != null ? (long?)Utilities.GetUnixTime(idlesince.Value) : null; this._status = new StatusUpdate() { Activity = new TransportActivity(activity), Status = status ?? UserStatus.Online, - IdleSince = since_unix, - IsAFK = idlesince != null, - _activity = activity + IdleSince = sinceUnix, + IsAfk = idlesince != null, + ActivityInternal = activity }; } - if (!this._isShard) + if (!this.IsShard) { if (this.Configuration.TokenType != TokenType.Bot) this.Logger.LogWarning(LoggerEvents.Misc, "You are logging in with a token that is not a bot token. This is not officially supported by Discord, and can result in your account being terminated if you aren't careful."); this.Logger.LogInformation(LoggerEvents.Startup, "Lib {0}, version {1}", this.BotLibrary, this.VersionString); } while (i-- > 0 || this.Configuration.ReconnectIndefinitely) { try { await this.InternalConnectAsync().ConfigureAwait(false); s = true; break; } catch (UnauthorizedException e) { FailConnection(this.ConnectionLock); throw new Exception("Authentication failed. Check your token and try again.", e); } catch (PlatformNotSupportedException) { FailConnection(this.ConnectionLock); throw; } catch (NotImplementedException) { FailConnection(this.ConnectionLock); throw; } catch (Exception ex) { FailConnection(null); cex = ex; if (i <= 0 && !this.Configuration.ReconnectIndefinitely) break; this.Logger.LogError(LoggerEvents.ConnectionFailure, ex, "Connection attempt failed, retrying in {0}s", w / 1000); await Task.Delay(w).ConfigureAwait(false); if (i > 0) w *= 2; } } if (!s && cex != null) { this.ConnectionLock.Set(); throw new Exception("Could not connect to Discord.", cex); } // non-closure, hence args static void FailConnection(ManualResetEventSlim cl) => // unlock this (if applicable) so we can let others attempt to connect cl?.Set(); } /// /// Reconnects to the gateway. /// /// If true, start new session. public Task ReconnectAsync(bool startNewSession = false) => this.InternalReconnectAsync(startNewSession, code: startNewSession ? 1000 : 4002); /// /// Disconnects from the gateway. /// /// public async Task DisconnectAsync() { this.Configuration.AutoReconnect = false; - if (this._webSocketClient != null) - await this._webSocketClient.DisconnectAsync().ConfigureAwait(false); + if (this.WebSocketClient != null) + await this.WebSocketClient.DisconnectAsync().ConfigureAwait(false); } #endregion #region Public REST Methods /// /// Gets a user. /// /// Id of the user /// Whether to fetch the user again (Defaults to false). /// The requested user. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public async Task GetUserAsync(ulong userId, bool fetch = true) { if (!fetch) { return this.TryGetCachedUserInternal(userId, out var usr) ? usr : new DiscordUser { Id = userId, Discord = this }; } else { var usr = await this.ApiClient.GetUserAsync(userId).ConfigureAwait(false); usr = this.UserCache.AddOrUpdate(userId, usr, (id, old) => { old.Username = usr.Username; old.Discriminator = usr.Discriminator; old.AvatarHash = usr.AvatarHash; old.BannerHash = usr.BannerHash; - old._bannerColor = usr._bannerColor; + old.BannerColorInternal = usr.BannerColorInternal; return old; }); return usr; } } /// /// Gets a channel. /// /// The id of the channel to get. /// The requested channel. /// Thrown when the channel does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public async Task GetChannelAsync(ulong id) => this.InternalGetCachedChannel(id) ?? await this.ApiClient.GetChannelAsync(id).ConfigureAwait(false); /// /// Gets a thread. /// /// The id of the thread to get. /// The requested thread. /// Thrown when the thread does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public async Task GetThreadAsync(ulong id) => this.InternalGetCachedThread(id) ?? await this.ApiClient.GetThreadAsync(id).ConfigureAwait(false); /// /// Sends a normal message. /// /// Channel to send to. /// Message content to send. /// The message that was sent. /// Thrown when the client does not have the permission. /// Thrown when the channel does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task SendMessageAsync(DiscordChannel channel, string content) => this.ApiClient.CreateMessageAsync(channel.Id, content, embeds: null, sticker: null, replyMessageId: null, mentionReply: false, failOnInvalidReply: false); /// /// Sends a message with an embed. /// /// Channel to send to. /// Embed to attach to the message. /// The message that was sent. /// Thrown when the client does not have the permission. /// Thrown when the channel does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task SendMessageAsync(DiscordChannel channel, DiscordEmbed embed) => this.ApiClient.CreateMessageAsync(channel.Id, null, embed != null ? new[] { embed } : null, sticker: null, replyMessageId: null, mentionReply: false, failOnInvalidReply: false); /// /// Sends a message with content and an embed. /// /// Channel to send to. /// Message content to send. /// Embed to attach to the message. /// The message that was sent. /// Thrown when the client does not have the permission. /// Thrown when the channel does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task SendMessageAsync(DiscordChannel channel, string content, DiscordEmbed embed) => this.ApiClient.CreateMessageAsync(channel.Id, content, embed != null ? new[] { embed } : null, sticker: null, replyMessageId: null, mentionReply: false, failOnInvalidReply: false); /// /// Sends a message with the . /// /// Channel to send the message to. /// The message builder. /// The message that was sent. /// Thrown when the client does not have the permission if TTS is false and if TTS is true. /// Thrown when the channel does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task SendMessageAsync(DiscordChannel channel, DiscordMessageBuilder builder) => this.ApiClient.CreateMessageAsync(channel.Id, builder); /// /// Sends a message with an . /// /// Channel to send the message to. /// The message builder. /// The message that was sent. /// Thrown when the client does not have the permission if TTS is false and if TTS is true. /// Thrown when the channel does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task SendMessageAsync(DiscordChannel channel, Action action) { var builder = new DiscordMessageBuilder(); action(builder); return this.ApiClient.CreateMessageAsync(channel.Id, builder); } /// /// Creates a guild. This requires the bot to be in less than 10 guilds total. /// /// Name of the guild. /// Voice region of the guild. /// Stream containing the icon for the guild. /// Verification level for the guild. /// Default message notification settings for the guild. /// System channel flags fopr the guild. /// The created guild. /// Thrown when the channel does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task CreateGuildAsync(string name, string region = null, Optional icon = default, VerificationLevel? verificationLevel = null, DefaultMessageNotifications? defaultMessageNotifications = null, SystemChannelFlags? systemChannelFlags = null) { var iconb64 = Optional.FromNoValue(); if (icon.HasValue && icon.Value != null) using (var imgtool = new ImageTool(icon.Value)) iconb64 = imgtool.GetBase64(); else if (icon.HasValue) iconb64 = null; return this.ApiClient.CreateGuildAsync(name, region, iconb64, verificationLevel, defaultMessageNotifications, systemChannelFlags); } /// /// Creates a guild from a template. This requires the bot to be in less than 10 guilds total. /// /// The template code. /// Name of the guild. /// Stream containing the icon for the guild. /// The created guild. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task CreateGuildFromTemplateAsync(string code, string name, Optional icon = default) { var iconb64 = Optional.FromNoValue(); if (icon.HasValue && icon.Value != null) using (var imgtool = new ImageTool(icon.Value)) iconb64 = imgtool.GetBase64(); else if (icon.HasValue) iconb64 = null; return this.ApiClient.CreateGuildFromTemplateAsync(code, name, iconb64); } /// /// Executes a raw request. /// /// /// /// var request = await Client.ExecuteRawRequestAsync(RestRequestMethod.GET, $"{Endpoints.CHANNELS}/243184972190742178964/{Endpoints.INVITES}"); /// List<DiscordInvite> invites = DiscordJson.ToDiscordObject<List<DiscordInvite>>(request.Response); /// /// /// The method. /// The route. /// The route parameters. /// The json body. /// The addditional headers. /// The ratelimit wait override. /// Thrown when the ressource does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. /// A awaitable RestResponse public async Task ExecuteRawRequestAsync(RestRequestMethod method, string route, object routeParams, string jsonBody = null, Dictionary additionalHeaders = null, double? ratelimitWaitOverride = null) { var bucket = this.ApiClient.Rest.GetBucket(method, route, routeParams, out var path); var url = Utilities.GetApiUriFor(path, this.Configuration); var res = await this.ApiClient.DoRequestAsync(this, bucket, url, method, route, additionalHeaders, DiscordJson.SerializeObject(jsonBody), ratelimitWaitOverride); return res; } /// /// Gets a guild. /// Setting to true will make a REST request. /// /// The guild ID to search for. /// Whether to include approximate presence and member counts in the returned guild. /// The requested Guild. /// Thrown when the guild does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public async Task GetGuildAsync(ulong id, bool? withCounts = null) { - if (this._guilds.TryGetValue(id, out var guild) && (!withCounts.HasValue || !withCounts.Value)) + if (this.GuildsInternal.TryGetValue(id, out var guild) && (!withCounts.HasValue || !withCounts.Value)) return guild; guild = await this.ApiClient.GetGuildAsync(id, withCounts).ConfigureAwait(false); var channels = await this.ApiClient.GetGuildChannelsAsync(guild.Id).ConfigureAwait(false); - foreach (var channel in channels) guild._channels[channel.Id] = channel; + foreach (var channel in channels) guild.ChannelsInternal[channel.Id] = channel; return guild; } /// /// Gets a guild preview. /// /// The guild ID. /// /// Thrown when the guild does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task GetGuildPreviewAsync(ulong id) => this.ApiClient.GetGuildPreviewAsync(id); /// /// Gets an invite. /// /// The invite code. /// Whether to include presence and total member counts in the returned invite. /// Whether to include the expiration date in the returned invite. /// The scheduled event id. /// The requested Invite. /// Thrown when the invite does not exists. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task GetInviteByCodeAsync(string code, bool? withCounts = null, bool? withExpiration = null, ulong? scheduledEventId = null) => this.ApiClient.GetInviteAsync(code, withCounts, withExpiration, scheduledEventId); /// /// Gets a list of connections. /// /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task> GetConnectionsAsync() => this.ApiClient.GetUsersConnectionsAsync(); /// /// Gets a sticker. /// /// The requested sticker. /// The id of the sticker. /// Thrown when the sticker does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task GetStickerAsync(ulong id) => this.ApiClient.GetStickerAsync(id); /// /// Gets all nitro sticker packs. /// /// List of sticker packs. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task> GetStickerPacksAsync() => this.ApiClient.GetStickerPacksAsync(); /// /// Gets the In-App OAuth Url. /// /// Defaults to . /// Redirect Uri. /// Defaults to . /// The OAuth Url public Uri GetInAppOAuth(Permissions permissions = Permissions.None, OAuthScopes scopes = OAuthScopes.BOT_DEFAULT, string redir = null) { - permissions &= PermissionMethods.FULL_PERMS; + permissions &= PermissionMethods.FullPerms; // hey look, it's not all annoying and blue :P return new Uri(new QueryUriBuilder($"{DiscordDomain.GetDomain(CoreDomain.Discord).Url}{Endpoints.OAUTH2}{Endpoints.AUTHORIZE}") .AddParameter("client_id", this.CurrentApplication.Id.ToString(CultureInfo.InvariantCulture)) .AddParameter("scope", OAuth.ResolveScopes(scopes)) .AddParameter("permissions", ((long)permissions).ToString(CultureInfo.InvariantCulture)) .AddParameter("state", "") .AddParameter("redirect_uri", redir ?? "") .ToString()); } /// /// Gets a webhook. /// /// The target webhook id. /// The requested webhook. /// Thrown when the webhook does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task GetWebhookAsync(ulong id) => this.ApiClient.GetWebhookAsync(id); /// /// Gets a webhook. /// /// The target webhook id. /// The target webhook token. /// The requested webhook. /// Thrown when the webhook does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task GetWebhookWithTokenAsync(ulong id, string token) => this.ApiClient.GetWebhookWithTokenAsync(id, token); /// /// Updates current user's activity and status. /// /// Activity to set. /// Status of the user. /// Since when is the client performing the specified activity. /// public Task UpdateStatusAsync(DiscordActivity activity = null, UserStatus? userStatus = null, DateTimeOffset? idleSince = null) => this.InternalUpdateStatusAsync(activity, userStatus, idleSince); /// /// Edits current user. /// /// New username. /// New avatar. /// The modified user. /// Thrown when the user does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public async Task UpdateCurrentUserAsync(string username = null, Optional avatar = default) { var av64 = Optional.FromNoValue(); if (avatar.HasValue && avatar.Value != null) using (var imgtool = new ImageTool(avatar.Value)) av64 = imgtool.GetBase64(); else if (avatar.HasValue) av64 = null; var usr = await this.ApiClient.ModifyCurrentUserAsync(username, av64).ConfigureAwait(false); this.CurrentUser.Username = usr.Username; this.CurrentUser.Discriminator = usr.Discriminator; this.CurrentUser.AvatarHash = usr.AvatarHash; return this.CurrentUser; } /// /// Gets a guild template by the code. /// /// The code of the template. /// The guild template for the code. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task GetTemplateAsync(string code) => this.ApiClient.GetTemplateAsync(code); /// /// Gets all the global application commands for this application. /// /// A list of global application commands. public Task> GetGlobalApplicationCommandsAsync() => this.ApiClient.GetGlobalApplicationCommandsAsync(this.CurrentApplication.Id); /// /// Overwrites the existing global application commands. New commands are automatically created and missing commands are automatically deleted. /// /// The list of commands to overwrite with. /// The list of global commands. public Task> BulkOverwriteGlobalApplicationCommandsAsync(IEnumerable commands) => this.ApiClient.BulkOverwriteGlobalApplicationCommandsAsync(this.CurrentApplication.Id, commands); /// /// Creates or overwrites a global application command. /// /// The command to create. /// The created command. public Task CreateGlobalApplicationCommandAsync(DiscordApplicationCommand command) => this.ApiClient.CreateGlobalApplicationCommandAsync(this.CurrentApplication.Id, command); /// /// Gets a global application command by its id. /// /// The id of the command to get. /// The command with the id. public Task GetGlobalApplicationCommandAsync(ulong commandId) => this.ApiClient.GetGlobalApplicationCommandAsync(this.CurrentApplication.Id, commandId); /// /// Edits a global application command. /// /// The id of the command to edit. /// Action to perform. /// The edited command. public async Task EditGlobalApplicationCommandAsync(ulong commandId, Action action) { var mdl = new ApplicationCommandEditModel(); action(mdl); var applicationId = this.CurrentApplication?.Id ?? (await this.GetCurrentApplicationAsync().ConfigureAwait(false)).Id; return await this.ApiClient.EditGlobalApplicationCommandAsync(applicationId, commandId, mdl.Name, mdl.Description, mdl.Options, mdl.DefaultPermission, mdl.NameLocalizations, mdl.DescriptionLocalizations).ConfigureAwait(false); } /// /// Deletes a global application command. /// /// The id of the command to delete. public Task DeleteGlobalApplicationCommandAsync(ulong commandId) => this.ApiClient.DeleteGlobalApplicationCommandAsync(this.CurrentApplication.Id, commandId); /// /// Gets all the application commands for a guild. /// /// The id of the guild to get application commands for. /// A list of application commands in the guild. public Task> GetGuildApplicationCommandsAsync(ulong guildId) => this.ApiClient.GetGuildApplicationCommandsAsync(this.CurrentApplication.Id, guildId); /// /// Overwrites the existing application commands in a guild. New commands are automatically created and missing commands are automatically deleted. /// /// The id of the guild. /// The list of commands to overwrite with. /// The list of guild commands. public Task> BulkOverwriteGuildApplicationCommandsAsync(ulong guildId, IEnumerable commands) => this.ApiClient.BulkOverwriteGuildApplicationCommandsAsync(this.CurrentApplication.Id, guildId, commands); /// /// Creates or overwrites a guild application command. /// /// The id of the guild to create the application command in. /// The command to create. /// The created command. public Task CreateGuildApplicationCommandAsync(ulong guildId, DiscordApplicationCommand command) => this.ApiClient.CreateGuildApplicationCommandAsync(this.CurrentApplication.Id, guildId, command); /// /// Gets a application command in a guild by its id. /// /// The id of the guild the application command is in. /// The id of the command to get. /// The command with the id. public Task GetGuildApplicationCommandAsync(ulong guildId, ulong commandId) => this.ApiClient.GetGuildApplicationCommandAsync(this.CurrentApplication.Id, guildId, commandId); /// /// Edits a application command in a guild. /// /// The id of the guild the application command is in. /// The id of the command to edit. /// Action to perform. /// The edited command. public async Task EditGuildApplicationCommandAsync(ulong guildId, ulong commandId, Action action) { var mdl = new ApplicationCommandEditModel(); action(mdl); var applicationId = this.CurrentApplication?.Id ?? (await this.GetCurrentApplicationAsync().ConfigureAwait(false)).Id; return await this.ApiClient.EditGuildApplicationCommandAsync(applicationId, guildId, commandId, mdl.Name, mdl.Description, mdl.Options, mdl.DefaultPermission, mdl.NameLocalizations, mdl.DescriptionLocalizations).ConfigureAwait(false); } /// /// Deletes a application command in a guild. /// /// The id of the guild to delete the application command in. /// The id of the command. public Task DeleteGuildApplicationCommandAsync(ulong guildId, ulong commandId) => this.ApiClient.DeleteGuildApplicationCommandAsync(this.CurrentApplication.Id, guildId, commandId); /// /// Gets all command permissions for a guild. /// /// The target guild. public Task> GetGuildApplicationCommandPermissionsAsync(ulong guildId) => this.ApiClient.GetGuildApplicationCommandPermissionsAsync(this.CurrentApplication.Id, guildId); /// /// Gets the permissions for a guild command. /// /// The target guild. /// The target command id. public Task GetApplicationCommandPermissionAsync(ulong guildId, ulong commandId) => this.ApiClient.GetApplicationCommandPermissionAsync(this.CurrentApplication.Id, guildId, commandId); /// /// Overwrites the existing permissions for a application command in a guild. New permissions are automatically created and missing permissions are deleted. /// A command takes up to 10 permission overwrites. /// /// The id of the guild. /// The id of the command. /// List of permissions. public Task OverwriteGuildApplicationCommandPermissionsAsync(ulong guildId, ulong commandId, IEnumerable permissions) => this.ApiClient.OverwriteGuildApplicationCommandPermissionsAsync(this.CurrentApplication.Id, guildId, commandId, permissions); /// /// Overwrites the existing application command permissions in a guild. New permissions are automatically created and missing permissions are deleted. /// Each command takes up to 10 permission overwrites. /// /// The id of the guild. /// The list of permissions to overwrite with. public Task> BulkOverwriteGuildApplicationCommandsAsync(ulong guildId, IEnumerable permissionsOverwrites) => this.ApiClient.BulkOverwriteApplicationCommandPermissionsAsync(this.CurrentApplication.Id, guildId, permissionsOverwrites); #endregion #region Internal Caching Methods /// /// Gets the internal chached threads. /// /// The target thread id. /// The requested thread. internal DiscordThreadChannel InternalGetCachedThread(ulong threadId) { foreach (var guild in this.Guilds.Values) if (guild.Threads.TryGetValue(threadId, out var foundThread)) return foundThread; return null; } /// /// Gets the internal chached scheduled event. /// /// The target scheduled event id. /// The requested scheduled event. internal DiscordScheduledEvent InternalGetCachedScheduledEvent(ulong scheduledEventId) { foreach (var guild in this.Guilds.Values) if (guild.ScheduledEvents.TryGetValue(scheduledEventId, out var foundScheduledEvent)) return foundScheduledEvent; return null; } /// /// Gets the internal chached channel. /// /// The target channel id. /// The requested channel. internal DiscordChannel InternalGetCachedChannel(ulong channelId) { foreach (var guild in this.Guilds.Values) if (guild.Channels.TryGetValue(channelId, out var foundChannel)) return foundChannel; return null; } /// /// Gets the internal chached guild. /// /// The target guild id. /// The requested guild. internal DiscordGuild InternalGetCachedGuild(ulong? guildId) { - if (this._guilds != null && guildId.HasValue) + if (this.GuildsInternal != null && guildId.HasValue) { - if (this._guilds.TryGetValue(guildId.Value, out var guild)) + if (this.GuildsInternal.TryGetValue(guildId.Value, out var guild)) return guild; } return null; } /// /// Updates a message. /// /// The message to update. /// The author to update. /// The guild to update. /// The member to update. private void UpdateMessage(DiscordMessage message, TransportUser author, DiscordGuild guild, TransportMember member) { if (author != null) { var usr = new DiscordUser(author) { Discord = this }; if (member != null) member.User = author; message.Author = this.UpdateUser(usr, guild?.Id, guild, member); } var channel = this.InternalGetCachedChannel(message.ChannelId); if (channel != null) return; channel = !message.GuildId.HasValue ? new DiscordDmChannel { Id = message.ChannelId, Discord = this, Type = ChannelType.Private } : new DiscordChannel { Id = message.ChannelId, Discord = this }; message.Channel = channel; } /// /// Updates a scheduled event. /// /// The scheduled event to update. /// The guild to update. /// The updated scheduled event. private DiscordScheduledEvent UpdateScheduledEvent(DiscordScheduledEvent scheduledEvent, DiscordGuild guild) { if (scheduledEvent != null) { - _ = guild._scheduledEvents.AddOrUpdate(scheduledEvent.Id, scheduledEvent, (id, old) => + _ = guild.ScheduledEventsInternal.AddOrUpdate(scheduledEvent.Id, scheduledEvent, (id, old) => { old.Discord = this; old.Description = scheduledEvent.Description; old.ChannelId = scheduledEvent.ChannelId; old.EntityId = scheduledEvent.EntityId; old.EntityType = scheduledEvent.EntityType; old.EntityMetadata = scheduledEvent.EntityMetadata; old.PrivacyLevel = scheduledEvent.PrivacyLevel; old.Name = scheduledEvent.Name; old.Status = scheduledEvent.Status; old.UserCount = scheduledEvent.UserCount; old.ScheduledStartTimeRaw = scheduledEvent.ScheduledStartTimeRaw; old.ScheduledEndTimeRaw = scheduledEvent.ScheduledEndTimeRaw; return old; }); } return scheduledEvent; } /// /// Updates a user. /// /// The user to update. /// The guild id to update. /// The guild to update. /// The member to update. /// The updated user. private DiscordUser UpdateUser(DiscordUser usr, ulong? guildId, DiscordGuild guild, TransportMember mbr) { if (mbr != null) { if (mbr.User != null) { usr = new DiscordUser(mbr.User) { Discord = this }; _ = this.UserCache.AddOrUpdate(usr.Id, usr, (id, old) => { old.Username = usr.Username; old.Discriminator = usr.Discriminator; old.AvatarHash = usr.AvatarHash; return old; }); - usr = new DiscordMember(mbr) { Discord = this, _guild_id = guildId.Value }; + usr = new DiscordMember(mbr) { Discord = this, GuildId = guildId.Value }; } var intents = this.Configuration.Intents; DiscordMember member = default; if (!intents.HasAllPrivilegedIntents() || guild.IsLarge) // we have the necessary privileged intents, no need to worry about caching here unless guild is large. { - if (guild?._members.TryGetValue(usr.Id, out member) == false) + if (guild?.MembersInternal.TryGetValue(usr.Id, out member) == false) { if (intents.HasIntent(DiscordIntents.GuildMembers) || this.Configuration.AlwaysCacheMembers) // member can be updated by events, so cache it { - guild._members.TryAdd(usr.Id, (DiscordMember)usr); + guild.MembersInternal.TryAdd(usr.Id, (DiscordMember)usr); } } else if (intents.HasIntent(DiscordIntents.GuildPresences) || this.Configuration.AlwaysCacheMembers) // we can attempt to update it if it's already in cache. { if (!intents.HasIntent(DiscordIntents.GuildMembers)) // no need to update if we already have the member events { - _ = guild._members.TryUpdate(usr.Id, (DiscordMember)usr, member); + _ = guild.MembersInternal.TryUpdate(usr.Id, (DiscordMember)usr, member); } } } } else if (usr.Username != null) // check if not a skeleton user { _ = this.UserCache.AddOrUpdate(usr.Id, usr, (id, old) => { old.Username = usr.Username; old.Discriminator = usr.Discriminator; old.AvatarHash = usr.AvatarHash; return old; }); } return usr; } /// /// Updates the cached events in a guild. /// /// The guild. /// The raw events. private void UpdateCachedScheduledEvent(DiscordGuild guild, JArray rawEvents) { if (this._disposed) return; if (rawEvents != null) { - guild._scheduledEvents.Clear(); + guild.ScheduledEventsInternal.Clear(); foreach (var xj in rawEvents) { var xtm = xj.ToDiscordObject(); xtm.Discord = this; - guild._scheduledEvents[xtm.Id] = xtm; + guild.ScheduledEventsInternal[xtm.Id] = xtm; } } } /// /// Updates the cached guild. /// /// The new guild. /// The raw members. private void UpdateCachedGuild(DiscordGuild newGuild, JArray rawMembers) { if (this._disposed) return; - if (!this._guilds.ContainsKey(newGuild.Id)) - this._guilds[newGuild.Id] = newGuild; + if (!this.GuildsInternal.ContainsKey(newGuild.Id)) + this.GuildsInternal[newGuild.Id] = newGuild; - var guild = this._guilds[newGuild.Id]; + var guild = this.GuildsInternal[newGuild.Id]; - if (newGuild._channels != null && newGuild._channels.Count > 0) + if (newGuild.ChannelsInternal != null && newGuild.ChannelsInternal.Count > 0) { - foreach (var channel in newGuild._channels.Values) + foreach (var channel in newGuild.ChannelsInternal.Values) { - if (guild._channels.TryGetValue(channel.Id, out _)) continue; + if (guild.ChannelsInternal.TryGetValue(channel.Id, out _)) continue; - foreach (var overwrite in channel._permissionOverwrites) + foreach (var overwrite in channel.PermissionOverwritesInternal) { overwrite.Discord = this; - overwrite._channel_id = channel.Id; + overwrite.ChannelId = channel.Id; } - guild._channels[channel.Id] = channel; + guild.ChannelsInternal[channel.Id] = channel; } } - if (newGuild._threads != null && newGuild._threads.Count > 0) + if (newGuild.ThreadsInternal != null && newGuild.ThreadsInternal.Count > 0) { - foreach (var thread in newGuild._threads.Values) + foreach (var thread in newGuild.ThreadsInternal.Values) { - if (guild._threads.TryGetValue(thread.Id, out _)) continue; + if (guild.ThreadsInternal.TryGetValue(thread.Id, out _)) continue; - guild._threads[thread.Id] = thread; + guild.ThreadsInternal[thread.Id] = thread; } } - if (newGuild._scheduledEvents != null && newGuild._scheduledEvents.Count > 0) + if (newGuild.ScheduledEventsInternal != null && newGuild.ScheduledEventsInternal.Count > 0) { - foreach (var s_event in newGuild._scheduledEvents.Values) + foreach (var @event in newGuild.ScheduledEventsInternal.Values) { - if (guild._scheduledEvents.TryGetValue(s_event.Id, out _)) continue; + if (guild.ScheduledEventsInternal.TryGetValue(@event.Id, out _)) continue; - guild._scheduledEvents[s_event.Id] = s_event; + guild.ScheduledEventsInternal[@event.Id] = @event; } } - foreach (var newEmoji in newGuild._emojis.Values) - _ = guild._emojis.GetOrAdd(newEmoji.Id, _ => newEmoji); + foreach (var newEmoji in newGuild.EmojisInternal.Values) + _ = guild.EmojisInternal.GetOrAdd(newEmoji.Id, _ => newEmoji); - foreach (var newSticker in newGuild._stickers.Values) - _ = guild._stickers.GetOrAdd(newSticker.Id, _ => newSticker); + foreach (var newSticker in newGuild.StickersInternal.Values) + _ = guild.StickersInternal.GetOrAdd(newSticker.Id, _ => newSticker); - foreach (var newStageInstance in newGuild._stageInstances.Values) - _ = guild._stageInstances.GetOrAdd(newStageInstance.Id, _ => newStageInstance); + foreach (var newStageInstance in newGuild.StageInstancesInternal.Values) + _ = guild.StageInstancesInternal.GetOrAdd(newStageInstance.Id, _ => newStageInstance); if (rawMembers != null) { - guild._members.Clear(); + guild.MembersInternal.Clear(); foreach (var xj in rawMembers) { var xtm = xj.ToDiscordObject(); var xu = new DiscordUser(xtm.User) { Discord = this }; _ = this.UserCache.AddOrUpdate(xtm.User.Id, xu, (id, old) => { old.Username = xu.Username; old.Discriminator = xu.Discriminator; old.AvatarHash = xu.AvatarHash; old.PremiumType = xu.PremiumType; return old; }); - guild._members[xtm.User.Id] = new DiscordMember(xtm) { Discord = this, _guild_id = guild.Id }; + guild.MembersInternal[xtm.User.Id] = new DiscordMember(xtm) { Discord = this, GuildId = guild.Id }; } } - foreach (var role in newGuild._roles.Values) + foreach (var role in newGuild.RolesInternal.Values) { - if (guild._roles.TryGetValue(role.Id, out _)) continue; + if (guild.RolesInternal.TryGetValue(role.Id, out _)) continue; - role._guild_id = guild.Id; - guild._roles[role.Id] = role; + role.GuildId = guild.Id; + guild.RolesInternal[role.Id] = role; } guild.Name = newGuild.Name; guild.AfkChannelId = newGuild.AfkChannelId; guild.AfkTimeout = newGuild.AfkTimeout; guild.DefaultMessageNotifications = newGuild.DefaultMessageNotifications; guild.RawFeatures = newGuild.RawFeatures; guild.IconHash = newGuild.IconHash; guild.MfaLevel = newGuild.MfaLevel; guild.OwnerId = newGuild.OwnerId; guild.VoiceRegionId = newGuild.VoiceRegionId; guild.SplashHash = newGuild.SplashHash; guild.VerificationLevel = newGuild.VerificationLevel; guild.WidgetEnabled = newGuild.WidgetEnabled; guild.WidgetChannelId = newGuild.WidgetChannelId; guild.ExplicitContentFilter = newGuild.ExplicitContentFilter; guild.PremiumTier = newGuild.PremiumTier; guild.PremiumSubscriptionCount = newGuild.PremiumSubscriptionCount; guild.PremiumProgressBarEnabled = newGuild.PremiumProgressBarEnabled; guild.BannerHash = newGuild.BannerHash; guild.Description = newGuild.Description; guild.VanityUrlCode = newGuild.VanityUrlCode; guild.SystemChannelId = newGuild.SystemChannelId; guild.SystemChannelFlags = newGuild.SystemChannelFlags; guild.DiscoverySplashHash = newGuild.DiscoverySplashHash; guild.MaxMembers = newGuild.MaxMembers; guild.MaxPresences = newGuild.MaxPresences; guild.ApproximateMemberCount = newGuild.ApproximateMemberCount; guild.ApproximatePresenceCount = newGuild.ApproximatePresenceCount; guild.MaxVideoChannelUsers = newGuild.MaxVideoChannelUsers; guild.PreferredLocale = newGuild.PreferredLocale; guild.RulesChannelId = newGuild.RulesChannelId; guild.PublicUpdatesChannelId = newGuild.PublicUpdatesChannelId; guild.ApplicationId = newGuild.ApplicationId; // fields not sent for update: // - guild.Channels // - voice states // - guild.JoinedAt = new_guild.JoinedAt; // - guild.Large = new_guild.Large; // - guild.MemberCount = Math.Max(new_guild.MemberCount, guild._members.Count); // - guild.Unavailable = new_guild.Unavailable; } /// /// Populates the message reactions and cache. /// /// The message. /// The author. /// The member. private void PopulateMessageReactionsAndCache(DiscordMessage message, TransportUser author, TransportMember member) { var guild = message.Channel?.Guild ?? this.InternalGetCachedGuild(message.GuildId); this.UpdateMessage(message, author, guild, member); - if (message._reactions == null) - message._reactions = new List(); - foreach (var xr in message._reactions) + if (message.ReactionsInternal == null) + message.ReactionsInternal = new List(); + foreach (var xr in message.ReactionsInternal) xr.Emoji.Discord = this; if (this.Configuration.MessageCacheSize > 0 && message.Channel != null) this.MessageCache?.Add(message); } #endregion #region Disposal ~DiscordClient() { this.Dispose(); } private bool _disposed; /// /// Disposes the client. /// public override void Dispose() { if (this._disposed) return; this._disposed = true; GC.SuppressFinalize(this); this.DisconnectAsync().ConfigureAwait(false).GetAwaiter().GetResult(); this.ApiClient.Rest.Dispose(); this.CurrentUser = null; var extensions = this._extensions; // prevent _extensions being modified during dispose this._extensions = null; foreach (var extension in extensions) if (extension is IDisposable disposable) disposable.Dispose(); try { this._cancelTokenSource?.Cancel(); this._cancelTokenSource?.Dispose(); } catch { } - this._guilds = null; + this.GuildsInternal = null; this._heartbeatTask = null; } #endregion } } diff --git a/DisCatSharp/Clients/DiscordShardedClient.cs b/DisCatSharp/Clients/DiscordShardedClient.cs index d2fb28d7e..574a45d63 100644 --- a/DisCatSharp/Clients/DiscordShardedClient.cs +++ b/DisCatSharp/Clients/DiscordShardedClient.cs @@ -1,745 +1,745 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. #pragma warning disable CS0618 using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; using System.Globalization; using System.Linq; using System.Net.Http; using System.Reflection; using System.Threading.Tasks; using DisCatSharp.Common.Utilities; using DisCatSharp.Entities; using DisCatSharp.EventArgs; using DisCatSharp.Net; using Microsoft.Extensions.Logging; using Newtonsoft.Json.Linq; namespace DisCatSharp { /// /// A Discord client that shards automatically. /// public sealed partial class DiscordShardedClient { #region Public Properties /// /// Gets the logger for this client. /// public ILogger Logger { get; } /// /// Gets all client shards. /// public IReadOnlyDictionary ShardClients { get; } /// /// Gets the gateway info for the client's session. /// public GatewayInfo GatewayInfo { get; private set; } /// /// Gets the current user. /// public DiscordUser CurrentUser { get; private set; } /// /// Gets the current application. /// public DiscordApplication CurrentApplication { get; private set; } /// /// Gets the library team. /// public DisCatSharpTeam LibraryDeveloperTeam => this.GetShard(0).LibraryDeveloperTeam; /// /// Gets the list of available voice regions. Note that this property will not contain VIP voice regions. /// public IReadOnlyDictionary VoiceRegions => this._voiceRegionsLazy?.Value; #endregion #region Private Properties/Fields /// /// Gets the configuration. /// private DiscordConfiguration Configuration { get; } /// /// Gets the list of available voice regions. This property is meant as a way to modify . /// private ConcurrentDictionary _internalVoiceRegions; private readonly ConcurrentDictionary _shards = new(); private Lazy> _voiceRegionsLazy; private bool _isStarted; private readonly bool _manuallySharding; #endregion #region Constructor /// /// Initializes new auto-sharding Discord client. /// /// Configuration to use. public DiscordShardedClient(DiscordConfiguration config) { this.InternalSetup(); if (config.ShardCount > 1) this._manuallySharding = true; this.Configuration = config; this.ShardClients = new ReadOnlyConcurrentDictionary(this._shards); if (this.Configuration.LoggerFactory == null) { this.Configuration.LoggerFactory = new DefaultLoggerFactory(); this.Configuration.LoggerFactory.AddProvider(new DefaultLoggerProvider(this.Configuration.MinimumLogLevel, this.Configuration.LogTimestampFormat)); } this.Logger = this.Configuration.LoggerFactory.CreateLogger(); } #endregion #region Public Methods /// /// Initializes and connects all shards. /// /// /// /// public async Task StartAsync() { if (this._isStarted) throw new InvalidOperationException("This client has already been started."); this._isStarted = true; try { if (this.Configuration.TokenType != TokenType.Bot) this.Logger.LogWarning(LoggerEvents.Misc, "You are logging in with a token that is not a bot token. This is not officially supported by Discord, and can result in your account being terminated if you aren't careful."); this.Logger.LogInformation(LoggerEvents.Startup, "Lib {0}, version {1}", this._botLibrary, this._versionString.Value); var shardc = await this.InitializeShardsAsync().ConfigureAwait(false); var connectTasks = new List(); this.Logger.LogInformation(LoggerEvents.ShardStartup, "Booting {0} shards.", shardc); for (var i = 0; i < shardc; i++) { //This should never happen, but in case it does... if (this.GatewayInfo.SessionBucket.MaxConcurrency < 1) this.GatewayInfo.SessionBucket.MaxConcurrency = 1; if (this.GatewayInfo.SessionBucket.MaxConcurrency == 1) await this.ConnectShardAsync(i).ConfigureAwait(false); else { //Concurrent login. connectTasks.Add(this.ConnectShardAsync(i)); if (connectTasks.Count == this.GatewayInfo.SessionBucket.MaxConcurrency) { await Task.WhenAll(connectTasks).ConfigureAwait(false); connectTasks.Clear(); } } } } catch (Exception ex) { await this.InternalStopAsync(false).ConfigureAwait(false); var message = $"Shard initialization failed, check inner exceptions for details: "; this.Logger.LogCritical(LoggerEvents.ShardClientError, $"{message}\n{ex}"); throw new AggregateException(message, ex); } } /// /// Disconnects and disposes of all shards. /// /// /// public Task StopAsync() => this.InternalStopAsync(); /// /// Gets a shard from a guild ID. /// /// If automatically sharding, this will use the method. /// Otherwise if manually sharding, it will instead iterate through each shard's guild caches. /// /// /// The guild ID for the shard. /// The found shard. Otherwise if the shard was not found for the guild ID. public DiscordClient GetShard(ulong guildId) { var index = this._manuallySharding ? this.GetShardIdFromGuilds(guildId) : Utilities.GetShardId(guildId, this.ShardClients.Count); return index != -1 ? this._shards[index] : null; } /// /// Gets a shard from a guild. /// /// If automatically sharding, this will use the method. /// Otherwise if manually sharding, it will instead iterate through each shard's guild caches. /// /// /// The guild for the shard. /// The found shard. Otherwise if the shard was not found for the guild. public DiscordClient GetShard(DiscordGuild guild) => this.GetShard(guild.Id); /// /// Updates playing statuses on all shards. /// /// Activity to set. /// Status of the user. /// Since when is the client performing the specified activity. /// Asynchronous operation. public async Task UpdateStatusAsync(DiscordActivity activity = null, UserStatus? userStatus = null, DateTimeOffset? idleSince = null) { var tasks = new List(); foreach (var client in this._shards.Values) tasks.Add(client.UpdateStatusAsync(activity, userStatus, idleSince)); await Task.WhenAll(tasks).ConfigureAwait(false); } #endregion #region Internal Methods /// /// Initializes the shards async. /// /// A Task. internal async Task InitializeShardsAsync() { if (this._shards.Count != 0) return this._shards.Count; this.GatewayInfo = await this.GetGatewayInfoAsync().ConfigureAwait(false); var shardc = this.Configuration.ShardCount == 1 ? this.GatewayInfo.ShardCount : this.Configuration.ShardCount; var lf = new ShardedLoggerFactory(this.Logger); for (var i = 0; i < shardc; i++) { var cfg = new DiscordConfiguration(this.Configuration) { ShardId = i, ShardCount = shardc, LoggerFactory = lf }; var client = new DiscordClient(cfg); if (!this._shards.TryAdd(i, client)) throw new InvalidOperationException("Could not initialize shards."); } return shardc; } #endregion #region Private Methods/Version Property /// /// Gets the gateway info async. /// /// A Task. private async Task GetGatewayInfoAsync() { var url = $"{Utilities.GetApiBaseUri(this.Configuration)}{Endpoints.GATEWAY}{Endpoints.BOT}"; var http = new HttpClient(); http.DefaultRequestHeaders.TryAddWithoutValidation("User-Agent", Utilities.GetUserAgent()); http.DefaultRequestHeaders.TryAddWithoutValidation("Authorization", Utilities.GetFormattedToken(this.Configuration)); if (this.Configuration != null && this.Configuration.Override != null) { http.DefaultRequestHeaders.TryAddWithoutValidation("x-super-properties", this.Configuration.Override); } this.Logger.LogDebug(LoggerEvents.ShardRest, $"Obtaining gateway information from GET {Endpoints.GATEWAY}{Endpoints.BOT}..."); var resp = await http.GetAsync(url).ConfigureAwait(false); http.Dispose(); if (!resp.IsSuccessStatusCode) { var ratelimited = await HandleHttpError(url, resp).ConfigureAwait(false); if (ratelimited) return await this.GetGatewayInfoAsync().ConfigureAwait(false); } var timer = new Stopwatch(); timer.Start(); var jo = JObject.Parse(await resp.Content.ReadAsStringAsync().ConfigureAwait(false)); var info = jo.ToObject(); //There is a delay from parsing here. timer.Stop(); - info.SessionBucket.resetAfter -= (int)timer.ElapsedMilliseconds; - info.SessionBucket.ResetAfter = DateTimeOffset.UtcNow + TimeSpan.FromMilliseconds(info.SessionBucket.resetAfter); + info.SessionBucket.ResetAfterInternal -= (int)timer.ElapsedMilliseconds; + info.SessionBucket.ResetAfter = DateTimeOffset.UtcNow + TimeSpan.FromMilliseconds(info.SessionBucket.ResetAfterInternal); return info; async Task HandleHttpError(string reqUrl, HttpResponseMessage msg) { var code = (int)msg.StatusCode; if (code == 401 || code == 403) { throw new Exception($"Authentication failed, check your token and try again: {code} {msg.ReasonPhrase}"); } else if (code == 429) { this.Logger.LogError(LoggerEvents.ShardClientError, $"Ratelimit hit, requeuing request to {reqUrl}"); var hs = msg.Headers.ToDictionary(xh => xh.Key, xh => string.Join("\n", xh.Value), StringComparer.OrdinalIgnoreCase); var waitInterval = 0; - if (hs.TryGetValue("Retry-After", out var retry_after_raw)) - waitInterval = int.Parse(retry_after_raw, CultureInfo.InvariantCulture); + if (hs.TryGetValue("Retry-After", out var retryAfterRaw)) + waitInterval = int.Parse(retryAfterRaw, CultureInfo.InvariantCulture); await Task.Delay(waitInterval).ConfigureAwait(false); return true; } else if (code >= 500) { throw new Exception($"Internal Server Error: {code} {msg.ReasonPhrase}"); } else { throw new Exception($"An unsuccessful HTTP status code was encountered: {code} {msg.ReasonPhrase}"); } } } private readonly Lazy _versionString = new(() => { var a = typeof(DiscordShardedClient).GetTypeInfo().Assembly; var iv = a.GetCustomAttribute(); if (iv != null) return iv.InformationalVersion; var v = a.GetName().Version; var vs = v.ToString(3); if (v.Revision > 0) vs = $"{vs}, CI build {v.Revision}"; return vs; }); private readonly string _botLibrary = "DisCatSharp"; #endregion #region Private Connection Methods /// /// Connects the shard async. /// /// The i. /// A Task. private async Task ConnectShardAsync(int i) { if (!this._shards.TryGetValue(i, out var client)) throw new Exception($"Could not initialize shard {i}."); if (this.GatewayInfo != null) { client.GatewayInfo = this.GatewayInfo; client.GatewayUri = new Uri(client.GatewayInfo.Url); } if (this.CurrentUser != null) client.CurrentUser = this.CurrentUser; if (this.CurrentApplication != null) client.CurrentApplication = this.CurrentApplication; if (this._internalVoiceRegions != null) { client.InternalVoiceRegions = this._internalVoiceRegions; - client._voice_regions_lazy = new Lazy>(() => new ReadOnlyDictionary(client.InternalVoiceRegions)); + client.VoiceRegionsLazy = new Lazy>(() => new ReadOnlyDictionary(client.InternalVoiceRegions)); } this.HookEventHandlers(client); - client._isShard = true; + client.IsShard = true; await client.ConnectAsync().ConfigureAwait(false); this.Logger.LogInformation(LoggerEvents.ShardStartup, "Booted shard {0}.", i); if (this.CurrentUser == null) this.CurrentUser = client.CurrentUser; if (this.CurrentApplication == null) this.CurrentApplication = client.CurrentApplication; if (this._internalVoiceRegions == null) { this._internalVoiceRegions = client.InternalVoiceRegions; this._voiceRegionsLazy = new Lazy>(() => new ReadOnlyDictionary(this._internalVoiceRegions)); } } /// /// Internals the stop async. /// /// If true, enable logger. /// A Task. private Task InternalStopAsync(bool enableLogger = true) { if (!this._isStarted) throw new InvalidOperationException("This client has not been started."); if (enableLogger) this.Logger.LogInformation(LoggerEvents.ShardShutdown, "Disposing {0} shards.", this._shards.Count); this._isStarted = false; this._voiceRegionsLazy = null; this.GatewayInfo = null; this.CurrentUser = null; this.CurrentApplication = null; for (var i = 0; i < this._shards.Count; i++) { if (this._shards.TryGetValue(i, out var client)) { this.UnhookEventHandlers(client); client.Dispose(); if (enableLogger) this.Logger.LogInformation(LoggerEvents.ShardShutdown, "Disconnected shard {0}.", i); } } this._shards.Clear(); return Task.CompletedTask; } #endregion #region Event Handler Initialization/Registering /// /// Internals the setup. /// private void InternalSetup() { this._clientErrored = new AsyncEvent("CLIENT_ERRORED", DiscordClient.EventExecutionLimit, this.Goof); this._socketErrored = new AsyncEvent("SOCKET_ERRORED", DiscordClient.EventExecutionLimit, this.Goof); this._socketOpened = new AsyncEvent("SOCKET_OPENED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._socketClosed = new AsyncEvent("SOCKET_CLOSED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._ready = new AsyncEvent("READY", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._resumed = new AsyncEvent("RESUMED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._channelCreated = new AsyncEvent("CHANNEL_CREATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._channelUpdated = new AsyncEvent("CHANNEL_UPDATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._channelDeleted = new AsyncEvent("CHANNEL_DELETED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._dmChannelDeleted = new AsyncEvent("DM_CHANNEL_DELETED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._channelPinsUpdated = new AsyncEvent("CHANNEL_PINS_UPDATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._guildCreated = new AsyncEvent("GUILD_CREATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._guildAvailable = new AsyncEvent("GUILD_AVAILABLE", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._guildUpdated = new AsyncEvent("GUILD_UPDATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._guildDeleted = new AsyncEvent("GUILD_DELETED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._guildUnavailable = new AsyncEvent("GUILD_UNAVAILABLE", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._guildDownloadCompleted = new AsyncEvent("GUILD_DOWNLOAD_COMPLETED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._inviteCreated = new AsyncEvent("INVITE_CREATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._inviteDeleted = new AsyncEvent("INVITE_DELETED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._messageCreated = new AsyncEvent("MESSAGE_CREATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._presenceUpdated = new AsyncEvent("PRESENCE_UPDATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._guildBanAdded = new AsyncEvent("GUILD_BAN_ADDED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._guildBanRemoved = new AsyncEvent("GUILD_BAN_REMOVED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._guildEmojisUpdated = new AsyncEvent("GUILD_EMOJI_UPDATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._guildStickersUpdated = new AsyncEvent("GUILD_STICKER_UPDATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._guildIntegrationsUpdated = new AsyncEvent("GUILD_INTEGRATIONS_UPDATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._guildMemberAdded = new AsyncEvent("GUILD_MEMBER_ADDED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._guildMemberRemoved = new AsyncEvent("GUILD_MEMBER_REMOVED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._guildMemberUpdated = new AsyncEvent("GUILD_MEMBER_UPDATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._guildRoleCreated = new AsyncEvent("GUILD_ROLE_CREATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._guildRoleUpdated = new AsyncEvent("GUILD_ROLE_UPDATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._guildRoleDeleted = new AsyncEvent("GUILD_ROLE_DELETED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._messageUpdated = new AsyncEvent("MESSAGE_UPDATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._messageDeleted = new AsyncEvent("MESSAGE_DELETED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._messageBulkDeleted = new AsyncEvent("MESSAGE_BULK_DELETED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._interactionCreated = new AsyncEvent("INTERACTION_CREATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._componentInteractionCreated = new AsyncEvent("COMPONENT_INTERACTED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._contextMenuInteractionCreated = new AsyncEvent("CONTEXT_MENU_INTERACTED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._typingStarted = new AsyncEvent("TYPING_STARTED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._userSettingsUpdated = new AsyncEvent("USER_SETTINGS_UPDATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._userUpdated = new AsyncEvent("USER_UPDATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._voiceStateUpdated = new AsyncEvent("VOICE_STATE_UPDATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._voiceServerUpdated = new AsyncEvent("VOICE_SERVER_UPDATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._guildMembersChunk = new AsyncEvent("GUILD_MEMBERS_CHUNKED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._unknownEvent = new AsyncEvent("UNKNOWN_EVENT", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._messageReactionAdded = new AsyncEvent("MESSAGE_REACTION_ADDED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._messageReactionRemoved = new AsyncEvent("MESSAGE_REACTION_REMOVED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._messageReactionsCleared = new AsyncEvent("MESSAGE_REACTIONS_CLEARED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._messageReactionRemovedEmoji = new AsyncEvent("MESSAGE_REACTION_REMOVED_EMOJI", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._webhooksUpdated = new AsyncEvent("WEBHOOKS_UPDATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._heartbeated = new AsyncEvent("HEARTBEATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._applicationCommandCreated = new AsyncEvent("APPLICATION_COMMAND_CREATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._applicationCommandUpdated = new AsyncEvent("APPLICATION_COMMAND_UPDATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._applicationCommandDeleted = new AsyncEvent("APPLICATION_COMMAND_DELETED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._guildApplicationCommandCountUpdated = new AsyncEvent("GUILD_APPLICATION_COMMAND_COUNTS_UPDATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._applicationCommandPermissionsUpdated = new AsyncEvent("APPLICATION_COMMAND_PERMISSIONS_UPDATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._guildIntegrationCreated = new AsyncEvent("INTEGRATION_CREATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._guildIntegrationUpdated = new AsyncEvent("INTEGRATION_UPDATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._guildIntegrationDeleted = new AsyncEvent("INTEGRATION_DELETED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._stageInstanceCreated = new AsyncEvent("STAGE_INSTANCE_CREATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._stageInstanceUpdated = new AsyncEvent("STAGE_INSTANCE_UPDATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._stageInstanceDeleted = new AsyncEvent("STAGE_INSTANCE_DELETED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._threadCreated = new AsyncEvent("THREAD_CREATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._threadUpdated = new AsyncEvent("THREAD_UPDATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._threadDeleted = new AsyncEvent("THREAD_DELETED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._threadListSynced = new AsyncEvent("THREAD_LIST_SYNCED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._threadMemberUpdated = new AsyncEvent("THREAD_MEMBER_UPDATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._threadMembersUpdated = new AsyncEvent("THREAD_MEMBERS_UPDATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._zombied = new AsyncEvent("ZOMBIED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._payloadReceived = new AsyncEvent("PAYLOAD_RECEIVED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._guildScheduledEventCreated = new AsyncEvent("GUILD_SCHEDULED_EVENT_CREATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._guildScheduledEventUpdated = new AsyncEvent("GUILD_SCHEDULED_EVENT_UPDATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._guildScheduledEventDeleted = new AsyncEvent("GUILD_SCHEDULED_EVENT_DELETED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._guildScheduledEventUserAdded = new AsyncEvent("GUILD_SCHEDULED_EVENT_USER_ADDED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._guildScheduledEventUserRemoved = new AsyncEvent("GUILD_SCHEDULED_EVENT_USER_REMOVED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); this._embeddedActivityUpdated = new AsyncEvent("EMBEDDED_ACTIVITY_UPDATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler); } /// /// Hooks the event handlers. /// /// The client. private void HookEventHandlers(DiscordClient client) { client.ClientErrored += this.Client_ClientError; client.SocketErrored += this.Client_SocketError; client.SocketOpened += this.Client_SocketOpened; client.SocketClosed += this.Client_SocketClosed; client.Ready += this.Client_Ready; client.Resumed += this.Client_Resumed; client.ChannelCreated += this.Client_ChannelCreated; client.ChannelUpdated += this.Client_ChannelUpdated; client.ChannelDeleted += this.Client_ChannelDeleted; client.DmChannelDeleted += this.Client_DMChannelDeleted; client.ChannelPinsUpdated += this.Client_ChannelPinsUpdated; client.GuildCreated += this.Client_GuildCreated; client.GuildAvailable += this.Client_GuildAvailable; client.GuildUpdated += this.Client_GuildUpdated; client.GuildDeleted += this.Client_GuildDeleted; client.GuildUnavailable += this.Client_GuildUnavailable; client.GuildDownloadCompleted += this.Client_GuildDownloadCompleted; client.InviteCreated += this.Client_InviteCreated; client.InviteDeleted += this.Client_InviteDeleted; client.MessageCreated += this.Client_MessageCreated; client.PresenceUpdated += this.Client_PresenceUpdate; client.GuildBanAdded += this.Client_GuildBanAdd; client.GuildBanRemoved += this.Client_GuildBanRemove; client.GuildEmojisUpdated += this.Client_GuildEmojisUpdate; client.GuildStickersUpdated += this.Client_GuildStickersUpdate; client.GuildIntegrationsUpdated += this.Client_GuildIntegrationsUpdate; client.GuildMemberAdded += this.Client_GuildMemberAdd; client.GuildMemberRemoved += this.Client_GuildMemberRemove; client.GuildMemberUpdated += this.Client_GuildMemberUpdate; client.GuildRoleCreated += this.Client_GuildRoleCreate; client.GuildRoleUpdated += this.Client_GuildRoleUpdate; client.GuildRoleDeleted += this.Client_GuildRoleDelete; client.MessageUpdated += this.Client_MessageUpdate; client.MessageDeleted += this.Client_MessageDelete; client.MessagesBulkDeleted += this.Client_MessageBulkDelete; client.InteractionCreated += this.Client_InteractionCreate; client.ComponentInteractionCreated += this.Client_ComponentInteractionCreate; client.ContextMenuInteractionCreated += this.Client_ContextMenuInteractionCreate; client.TypingStarted += this.Client_TypingStart; client.UserSettingsUpdated += this.Client_UserSettingsUpdate; client.UserUpdated += this.Client_UserUpdate; client.VoiceStateUpdated += this.Client_VoiceStateUpdate; client.VoiceServerUpdated += this.Client_VoiceServerUpdate; client.GuildMembersChunked += this.Client_GuildMembersChunk; client.UnknownEvent += this.Client_UnknownEvent; client.MessageReactionAdded += this.Client_MessageReactionAdd; client.MessageReactionRemoved += this.Client_MessageReactionRemove; client.MessageReactionsCleared += this.Client_MessageReactionRemoveAll; client.MessageReactionRemovedEmoji += this.Client_MessageReactionRemovedEmoji; client.WebhooksUpdated += this.Client_WebhooksUpdate; client.Heartbeated += this.Client_HeartBeated; client.ApplicationCommandCreated += this.Client_ApplicationCommandCreated; client.ApplicationCommandUpdated += this.Client_ApplicationCommandUpdated; client.ApplicationCommandDeleted += this.Client_ApplicationCommandDeleted; client.GuildApplicationCommandCountUpdated += this.Client_GuildApplicationCommandCountUpdated; client.ApplicationCommandPermissionsUpdated += this.Client_ApplicationCommandPermissionsUpdated; client.GuildIntegrationCreated += this.Client_GuildIntegrationCreated; client.GuildIntegrationUpdated += this.Client_GuildIntegrationUpdated; client.GuildIntegrationDeleted += this.Client_GuildIntegrationDeleted; client.StageInstanceCreated += this.Client_StageInstanceCreated; client.StageInstanceUpdated += this.Client_StageInstanceUpdated; client.StageInstanceDeleted += this.Client_StageInstanceDeleted; client.ThreadCreated += this.Client_ThreadCreated; client.ThreadUpdated += this.Client_ThreadUpdated; client.ThreadDeleted += this.Client_ThreadDeleted; client.ThreadListSynced += this.Client_ThreadListSynced; client.ThreadMemberUpdated += this.Client_ThreadMemberUpdated; client.ThreadMembersUpdated += this.Client_ThreadMembersUpdated; client.Zombied += this.Client_Zombied; client.PayloadReceived += this.Client_PayloadReceived; client.GuildScheduledEventCreated += this.Client_GuildScheduledEventCreated; client.GuildScheduledEventUpdated += this.Client_GuildScheduledEventUpdated; client.GuildScheduledEventDeleted += this.Client_GuildScheduledEventDeleted; client.GuildScheduledEventUserAdded += this.Client_GuildScheduledEventUserAdded; ; client.GuildScheduledEventUserRemoved += this.Client_GuildScheduledEventUserRemoved; client.EmbeddedActivityUpdated += this.Client_EmbeddedActivityUpdated; } /// /// Unhooks the event handlers. /// /// The client. private void UnhookEventHandlers(DiscordClient client) { client.ClientErrored -= this.Client_ClientError; client.SocketErrored -= this.Client_SocketError; client.SocketOpened -= this.Client_SocketOpened; client.SocketClosed -= this.Client_SocketClosed; client.Ready -= this.Client_Ready; client.Resumed -= this.Client_Resumed; client.ChannelCreated -= this.Client_ChannelCreated; client.ChannelUpdated -= this.Client_ChannelUpdated; client.ChannelDeleted -= this.Client_ChannelDeleted; client.DmChannelDeleted -= this.Client_DMChannelDeleted; client.ChannelPinsUpdated -= this.Client_ChannelPinsUpdated; client.GuildCreated -= this.Client_GuildCreated; client.GuildAvailable -= this.Client_GuildAvailable; client.GuildUpdated -= this.Client_GuildUpdated; client.GuildDeleted -= this.Client_GuildDeleted; client.GuildUnavailable -= this.Client_GuildUnavailable; client.GuildDownloadCompleted -= this.Client_GuildDownloadCompleted; client.InviteCreated -= this.Client_InviteCreated; client.InviteDeleted -= this.Client_InviteDeleted; client.MessageCreated -= this.Client_MessageCreated; client.PresenceUpdated -= this.Client_PresenceUpdate; client.GuildBanAdded -= this.Client_GuildBanAdd; client.GuildBanRemoved -= this.Client_GuildBanRemove; client.GuildEmojisUpdated -= this.Client_GuildEmojisUpdate; client.GuildStickersUpdated -= this.Client_GuildStickersUpdate; client.GuildIntegrationsUpdated -= this.Client_GuildIntegrationsUpdate; client.GuildMemberAdded -= this.Client_GuildMemberAdd; client.GuildMemberRemoved -= this.Client_GuildMemberRemove; client.GuildMemberUpdated -= this.Client_GuildMemberUpdate; client.GuildRoleCreated -= this.Client_GuildRoleCreate; client.GuildRoleUpdated -= this.Client_GuildRoleUpdate; client.GuildRoleDeleted -= this.Client_GuildRoleDelete; client.MessageUpdated -= this.Client_MessageUpdate; client.MessageDeleted -= this.Client_MessageDelete; client.MessagesBulkDeleted -= this.Client_MessageBulkDelete; client.InteractionCreated -= this.Client_InteractionCreate; client.ComponentInteractionCreated -= this.Client_ComponentInteractionCreate; client.ContextMenuInteractionCreated -= this.Client_ContextMenuInteractionCreate; client.TypingStarted -= this.Client_TypingStart; client.UserSettingsUpdated -= this.Client_UserSettingsUpdate; client.UserUpdated -= this.Client_UserUpdate; client.VoiceStateUpdated -= this.Client_VoiceStateUpdate; client.VoiceServerUpdated -= this.Client_VoiceServerUpdate; client.GuildMembersChunked -= this.Client_GuildMembersChunk; client.UnknownEvent -= this.Client_UnknownEvent; client.MessageReactionAdded -= this.Client_MessageReactionAdd; client.MessageReactionRemoved -= this.Client_MessageReactionRemove; client.MessageReactionsCleared -= this.Client_MessageReactionRemoveAll; client.MessageReactionRemovedEmoji -= this.Client_MessageReactionRemovedEmoji; client.WebhooksUpdated -= this.Client_WebhooksUpdate; client.Heartbeated -= this.Client_HeartBeated; client.ApplicationCommandCreated -= this.Client_ApplicationCommandCreated; client.ApplicationCommandUpdated -= this.Client_ApplicationCommandUpdated; client.ApplicationCommandDeleted -= this.Client_ApplicationCommandDeleted; client.GuildApplicationCommandCountUpdated -= this.Client_GuildApplicationCommandCountUpdated; client.ApplicationCommandPermissionsUpdated -= this.Client_ApplicationCommandPermissionsUpdated; client.GuildIntegrationCreated -= this.Client_GuildIntegrationCreated; client.GuildIntegrationUpdated -= this.Client_GuildIntegrationUpdated; client.GuildIntegrationDeleted -= this.Client_GuildIntegrationDeleted; client.StageInstanceCreated -= this.Client_StageInstanceCreated; client.StageInstanceUpdated -= this.Client_StageInstanceUpdated; client.StageInstanceDeleted -= this.Client_StageInstanceDeleted; client.ThreadCreated -= this.Client_ThreadCreated; client.ThreadUpdated -= this.Client_ThreadUpdated; client.ThreadDeleted -= this.Client_ThreadDeleted; client.ThreadListSynced -= this.Client_ThreadListSynced; client.ThreadMemberUpdated -= this.Client_ThreadMemberUpdated; client.ThreadMembersUpdated -= this.Client_ThreadMembersUpdated; client.Zombied -= this.Client_Zombied; client.PayloadReceived -= this.Client_PayloadReceived; client.GuildScheduledEventCreated -= this.Client_GuildScheduledEventCreated; client.GuildScheduledEventUpdated -= this.Client_GuildScheduledEventUpdated; client.GuildScheduledEventDeleted -= this.Client_GuildScheduledEventDeleted; client.GuildScheduledEventUserAdded -= this.Client_GuildScheduledEventUserAdded; ; client.GuildScheduledEventUserRemoved -= this.Client_GuildScheduledEventUserRemoved; client.EmbeddedActivityUpdated -= this.Client_EmbeddedActivityUpdated; } /// /// Gets the shard id from guilds. /// /// The id. /// An int. private int GetShardIdFromGuilds(ulong id) { foreach (var s in this._shards.Values) { - if (s._guilds.TryGetValue(id, out _)) + if (s.GuildsInternal.TryGetValue(id, out _)) { return s.ShardId; } } return -1; } #endregion #region Destructor ~DiscordShardedClient() => this.InternalStopAsync(false).GetAwaiter().GetResult(); #endregion } } diff --git a/DisCatSharp/Clients/DiscordWebhookClient.cs b/DisCatSharp/Clients/DiscordWebhookClient.cs index da3eae668..004d4651e 100644 --- a/DisCatSharp/Clients/DiscordWebhookClient.cs +++ b/DisCatSharp/Clients/DiscordWebhookClient.cs @@ -1,283 +1,283 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Globalization; using System.Linq; using System.Net; using System.Text.RegularExpressions; using System.Threading.Tasks; using DisCatSharp.Entities; using DisCatSharp.Exceptions; using DisCatSharp.Net; using Microsoft.Extensions.Logging; namespace DisCatSharp { /// /// Represents a webhook-only client. This client can be used to execute Discord webhooks. /// public class DiscordWebhookClient { /// /// Gets the logger for this client. /// public ILogger Logger { get; } /// /// Gets the webhook regex. /// this regex has 2 named capture groups: "id" and "token". /// - private static Regex WebhookRegex { get; } = new Regex(@"(?:https?:\/\/)?discord(?:app)?.com\/api\/(?:v\d\/)?webhooks\/(?\d+)\/(?[A-Za-z0-9_\-]+)", RegexOptions.ECMAScript); + private static Regex s_webhookRegex { get; } = new Regex(@"(?:https?:\/\/)?discord(?:app)?.com\/api\/(?:v\d\/)?webhooks\/(?\d+)\/(?[A-Za-z0-9_\-]+)", RegexOptions.ECMAScript); /// /// Gets the collection of registered webhooks. /// public IReadOnlyList Webhooks { get; } /// /// Gets or sets the username override for registered webhooks. Note that this only takes effect when broadcasting. /// public string Username { get; set; } /// /// Gets or set the avatar override for registered webhooks. Note that this only takes effect when broadcasting. /// public string AvatarUrl { get; set; } - internal List _hooks; - internal DiscordApiClient _apiclient; + internal List Hooks; + internal DiscordApiClient Apiclient; - internal LogLevel _minimumLogLevel; - internal string _logTimestampFormat; + internal LogLevel MinimumLogLevel; + internal string LogTimestampFormat; /// /// Creates a new webhook client. /// public DiscordWebhookClient() : this(null, null) { } /// /// Creates a new webhook client, with specified HTTP proxy, timeout, and logging settings. /// /// Proxy to use for HTTP connections. /// Timeout to use for HTTP requests. Set to to disable timeouts. /// Whether to use the system clock for computing rate limit resets. See for more details. /// The optional logging factory to use for this client. /// The minimum logging level for messages. /// The timestamp format to use for the logger. public DiscordWebhookClient(IWebProxy proxy = null, TimeSpan? timeout = null, bool useRelativeRateLimit = true, ILoggerFactory loggerFactory = null, LogLevel minimumLogLevel = LogLevel.Information, string logTimestampFormat = "yyyy-MM-dd HH:mm:ss zzz") { - this._minimumLogLevel = minimumLogLevel; - this._logTimestampFormat = logTimestampFormat; + this.MinimumLogLevel = minimumLogLevel; + this.LogTimestampFormat = logTimestampFormat; if (loggerFactory == null) { loggerFactory = new DefaultLoggerFactory(); loggerFactory.AddProvider(new DefaultLoggerProvider(this)); } this.Logger = loggerFactory.CreateLogger(); var parsedTimeout = timeout ?? TimeSpan.FromSeconds(10); - this._apiclient = new DiscordApiClient(proxy, parsedTimeout, useRelativeRateLimit, this.Logger); - this._hooks = new List(); - this.Webhooks = new ReadOnlyCollection(this._hooks); + this.Apiclient = new DiscordApiClient(proxy, parsedTimeout, useRelativeRateLimit, this.Logger); + this.Hooks = new List(); + this.Webhooks = new ReadOnlyCollection(this.Hooks); } /// /// Registers a webhook with this client. This retrieves a webhook based on the ID and token supplied. /// /// The ID of the webhook to add. /// The token of the webhook to add. /// The registered webhook. public async Task AddWebhookAsync(ulong id, string token) { if (string.IsNullOrWhiteSpace(token)) throw new ArgumentNullException(nameof(token)); token = token.Trim(); - if (this._hooks.Any(x => x.Id == id)) + if (this.Hooks.Any(x => x.Id == id)) throw new InvalidOperationException("This webhook is registered with this client."); - var wh = await this._apiclient.GetWebhookWithTokenAsync(id, token).ConfigureAwait(false); - this._hooks.Add(wh); + var wh = await this.Apiclient.GetWebhookWithTokenAsync(id, token).ConfigureAwait(false); + this.Hooks.Add(wh); return wh; } /// /// Registers a webhook with this client. This retrieves a webhook from webhook URL. /// /// URL of the webhook to retrieve. This URL must contain both ID and token. /// The registered webhook. public Task AddWebhookAsync(Uri url) { if (url == null) throw new ArgumentNullException(nameof(url)); - var m = WebhookRegex.Match(url.ToString()); + var m = s_webhookRegex.Match(url.ToString()); if (!m.Success) throw new ArgumentException("Invalid webhook URL supplied.", nameof(url)); var idraw = m.Groups["id"]; var tokenraw = m.Groups["token"]; if (!ulong.TryParse(idraw.Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var id)) throw new ArgumentException("Invalid webhook URL supplied.", nameof(url)); var token = tokenraw.Value; return this.AddWebhookAsync(id, token); } /// /// Registers a webhook with this client. This retrieves a webhook using the supplied full discord client. /// /// ID of the webhook to register. /// Discord client to which the webhook will belong. /// The registered webhook. public async Task AddWebhookAsync(ulong id, BaseDiscordClient client) { if (client == null) throw new ArgumentNullException(nameof(client)); - if (this._hooks.Any(x => x.Id == id)) + if (this.Hooks.Any(x => x.Id == id)) throw new ArgumentException("This webhook is already registered with this client."); var wh = await client.ApiClient.GetWebhookAsync(id).ConfigureAwait(false); // personally I don't think we need to override anything. // it would even make sense to keep the hook as-is, in case // it's returned without a token for some bizarre reason // remember -- discord is not really consistent //var nwh = new DiscordWebhook() //{ // ApiClient = _apiclient, // AvatarHash = wh.AvatarHash, // ChannelId = wh.ChannelId, // GuildId = wh.GuildId, // Id = wh.Id, // Name = wh.Name, // Token = wh.Token, // User = wh.User, // Discord = null //}; - this._hooks.Add(wh); + this.Hooks.Add(wh); return wh; } /// /// Registers a webhook with this client. This reuses the supplied webhook object. /// /// Webhook to register. /// The registered webhook. public DiscordWebhook AddWebhook(DiscordWebhook webhook) { if (webhook == null) throw new ArgumentNullException(nameof(webhook)); - if (this._hooks.Any(x => x.Id == webhook.Id)) + if (this.Hooks.Any(x => x.Id == webhook.Id)) throw new ArgumentException("This webhook is already registered with this client."); // see line 128-131 for explanation // For christ's sake, update the line numbers if they change. //var nwh = new DiscordWebhook() //{ // ApiClient = _apiclient, // AvatarHash = webhook.AvatarHash, // ChannelId = webhook.ChannelId, // GuildId = webhook.GuildId, // Id = webhook.Id, // Name = webhook.Name, // Token = webhook.Token, // User = webhook.User, // Discord = null //}; - this._hooks.Add(webhook); + this.Hooks.Add(webhook); return webhook; } /// /// Unregisters a webhook with this client. /// /// ID of the webhook to unregister. /// The unregistered webhook. public DiscordWebhook RemoveWebhook(ulong id) { - if (!this._hooks.Any(x => x.Id == id)) + if (!this.Hooks.Any(x => x.Id == id)) throw new ArgumentException("This webhook is not registered with this client."); var wh = this.GetRegisteredWebhook(id); - this._hooks.Remove(wh); + this.Hooks.Remove(wh); return wh; } /// /// Gets a registered webhook with specified ID. /// /// ID of the registered webhook to retrieve. /// The requested webhook. public DiscordWebhook GetRegisteredWebhook(ulong id) - => this._hooks.FirstOrDefault(xw => xw.Id == id); + => this.Hooks.FirstOrDefault(xw => xw.Id == id); /// /// Broadcasts a message to all registered webhooks. /// /// Webhook builder filled with data to send. /// public async Task> BroadcastMessageAsync(DiscordWebhookBuilder builder) { var deadhooks = new List(); var messages = new Dictionary(); - foreach (var hook in this._hooks) + foreach (var hook in this.Hooks) { try { messages.Add(hook, await hook.ExecuteAsync(builder).ConfigureAwait(false)); } catch (NotFoundException) { deadhooks.Add(hook); } } // Removing dead webhooks from collection - foreach (var xwh in deadhooks) this._hooks.Remove(xwh); + foreach (var xwh in deadhooks) this.Hooks.Remove(xwh); return messages; } ~DiscordWebhookClient() { - this._hooks.Clear(); - this._hooks = null; - this._apiclient.Rest.Dispose(); + this.Hooks.Clear(); + this.Hooks = null; + this.Apiclient.Rest.Dispose(); } } } diff --git a/DisCatSharp/DiscordConfiguration.cs b/DisCatSharp/DiscordConfiguration.cs index 7143552be..35016f651 100644 --- a/DisCatSharp/DiscordConfiguration.cs +++ b/DisCatSharp/DiscordConfiguration.cs @@ -1,275 +1,275 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System; using System.Net; using DisCatSharp.Net.Udp; using DisCatSharp.Net.WebSocket; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; namespace DisCatSharp { /// /// Represents configuration for and . /// public sealed class DiscordConfiguration { /// /// Sets the token used to identify the client. /// public string Token { internal get => this._token; set { if (string.IsNullOrWhiteSpace(value)) throw new ArgumentNullException(nameof(value), "Token cannot be null, empty, or all whitespace."); this._token = value.Trim(); } } private string _token = ""; /// /// Sets the type of the token used to identify the client. /// Defaults to . /// public TokenType TokenType { internal get; set; } = TokenType.Bot; /// /// Sets the minimum logging level for messages. /// Typically, the default value of is ok for most uses. /// public LogLevel MinimumLogLevel { internal get; set; } = LogLevel.Information; /// /// Overwrites the api version. /// Defaults to 9. /// public string ApiVersion { internal get; set; } = "9"; /// /// Sets whether to rely on Discord for NTP (Network Time Protocol) synchronization with the "X-Ratelimit-Reset-After" header. /// If the system clock is unsynced, setting this to true will ensure ratelimits are synced with Discord and reduce the risk of hitting one. /// This should only be set to false if the system clock is synced with NTP. /// Defaults to true. /// public bool UseRelativeRatelimit { internal get; set; } = true; /// /// Allows you to overwrite the time format used by the internal debug logger. /// Only applicable when is set left at default value. Defaults to ISO 8601-like format. /// public string LogTimestampFormat { internal get; set; } = "yyyy-MM-dd HH:mm:ss zzz"; /// /// Sets the member count threshold at which guilds are considered large. /// Defaults to 250. /// public int LargeThreshold { internal get; set; } = 250; /// /// Sets whether to automatically reconnect in case a connection is lost. /// Defaults to true. /// public bool AutoReconnect { internal get; set; } = true; /// /// Sets the ID of the shard to connect to. /// If not sharding, or sharding automatically, this value should be left with the default value of 0. /// public int ShardId { internal get; set; } = 0; /// /// Sets the total number of shards the bot is on. If not sharding, this value should be left with a default value of 1. /// If sharding automatically, this value will indicate how many shards to boot. If left default for automatic sharding, the client will determine the shard count automatically. /// public int ShardCount { internal get; set; } = 1; /// /// Sets the level of compression for WebSocket traffic. /// Disabling this option will increase the amount of traffic sent via WebSocket. Setting will enable compression for READY and GUILD_CREATE payloads. Setting will enable compression for the entire WebSocket stream, drastically reducing amount of traffic. /// Defaults to . /// public GatewayCompressionLevel GatewayCompressionLevel { internal get; set; } = GatewayCompressionLevel.Stream; /// /// Sets the size of the global message cache. /// Setting this to 0 will disable message caching entirely. Defaults to 1024. /// public int MessageCacheSize { internal get; set; } = 1024; /// /// Sets the proxy to use for HTTP and WebSocket connections to Discord. /// Defaults to null. /// public IWebProxy Proxy { internal get; set; } = null; /// /// Sets the timeout for HTTP requests. /// Set to to disable timeouts. /// Defaults to 20 seconds. /// public TimeSpan HttpTimeout { internal get; set; } = TimeSpan.FromSeconds(20); /// /// Defines that the client should attempt to reconnect indefinitely. /// This is typically a very bad idea to set to true, as it will swallow all connection errors. /// Defaults to false. /// public bool ReconnectIndefinitely { internal get; set; } = false; /// /// Sets whether the client should attempt to cache members if exclusively using unprivileged intents. /// /// This will only take effect if there are no or /// intents specified. Otherwise, this will always be overwritten to true. /// /// Defaults to true. /// public bool AlwaysCacheMembers { internal get; set; } = true; /// /// Sets the gateway intents for this client. /// If set, the client will only receive events that they specify with intents. /// Defaults to . /// public DiscordIntents Intents { internal get; set; } = DiscordIntents.AllUnprivileged; /// /// Sets the factory method used to create instances of WebSocket clients. /// Use and equivalents on other implementations to switch out client implementations. /// Defaults to . /// public WebSocketClientFactoryDelegate WebSocketClientFactory { internal get => this._webSocketClientFactory; set { if (value == null) throw new InvalidOperationException("You need to supply a valid WebSocket client factory method."); this._webSocketClientFactory = value; } } private WebSocketClientFactoryDelegate _webSocketClientFactory = WebSocketClient.CreateNew; /// /// Sets the factory method used to create instances of UDP clients. - /// Use and equivalents on other implementations to switch out client implementations. - /// Defaults to . + /// Use and equivalents on other implementations to switch out client implementations. + /// Defaults to . /// public UdpClientFactoryDelegate UdpClientFactory { internal get => this._udpClientFactory; set => this._udpClientFactory = value ?? throw new InvalidOperationException("You need to supply a valid UDP client factory method."); } - private UdpClientFactoryDelegate _udpClientFactory = DCSUdpClient.CreateNew; + private UdpClientFactoryDelegate _udpClientFactory = DcsUdpClient.CreateNew; /// /// Sets the logger implementation to use. /// To create your own logger, implement the instance. /// Defaults to built-in implementation. /// public ILoggerFactory LoggerFactory { internal get; set; } = null; /// /// Sets if the bot's status should show the mobile icon. /// Defaults to false. /// public bool MobileStatus { internal get; set; } = false; /// /// Use canary. /// Defaults to false. /// public bool UseCanary { internal get; set; } = false; /// /// Refresh full guild channel cache. /// Defaults to false. /// public bool AutoRefreshChannelCache { internal get; set; } = false; /// /// Do not use, this is meant for DisCatSharp Devs. /// Defaults to null. /// public string Override { internal get; set; } = null; /// /// Sets the service provider. /// This allows passing data around without resorting to static members. /// Defaults to null. /// public IServiceProvider ServiceProvider { internal get; set; } = new ServiceCollection().BuildServiceProvider(true); /// /// Creates a new configuration with default values. /// public DiscordConfiguration() { } /// /// Utilized via Dependency Injection Pipeline /// /// [ActivatorUtilitiesConstructor] public DiscordConfiguration(IServiceProvider provider) { this.ServiceProvider = provider; } /// /// Creates a clone of another discord configuration. /// /// Client configuration to clone. public DiscordConfiguration(DiscordConfiguration other) { this.Token = other.Token; this.TokenType = other.TokenType; this.MinimumLogLevel = other.MinimumLogLevel; this.UseRelativeRatelimit = other.UseRelativeRatelimit; this.LogTimestampFormat = other.LogTimestampFormat; this.LargeThreshold = other.LargeThreshold; this.AutoReconnect = other.AutoReconnect; this.ShardId = other.ShardId; this.ShardCount = other.ShardCount; this.GatewayCompressionLevel = other.GatewayCompressionLevel; this.MessageCacheSize = other.MessageCacheSize; this.WebSocketClientFactory = other.WebSocketClientFactory; this.UdpClientFactory = other.UdpClientFactory; this.Proxy = other.Proxy; this.HttpTimeout = other.HttpTimeout; this.ReconnectIndefinitely = other.ReconnectIndefinitely; this.Intents = other.Intents; this.LoggerFactory = other.LoggerFactory; this.MobileStatus = other.MobileStatus; this.UseCanary = other.UseCanary; this.AutoRefreshChannelCache = other.AutoRefreshChannelCache; this.ApiVersion = other.ApiVersion; this.ServiceProvider = other.ServiceProvider; this.Override = other.Override; } } } diff --git a/DisCatSharp/Entities/Application/DiscordApplication.cs b/DisCatSharp/Entities/Application/DiscordApplication.cs index 0df18ac9c..3a1fc63df 100644 --- a/DisCatSharp/Entities/Application/DiscordApplication.cs +++ b/DisCatSharp/Entities/Application/DiscordApplication.cs @@ -1,415 +1,415 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System; using System.Collections.Generic; using System.Globalization; using System.Threading.Tasks; using DisCatSharp.Enums; using DisCatSharp.Net; using Newtonsoft.Json; namespace DisCatSharp.Entities { /// /// Represents an OAuth2 application. /// public sealed class DiscordApplication : DiscordMessageApplication, IEquatable { /// /// Gets the application's summary. /// public string Summary { get; internal set; } /// /// Gets the application's icon. /// public override string Icon => !string.IsNullOrWhiteSpace(this.IconHash) ? $"{DiscordDomain.GetDomain(CoreDomain.DiscordCdn).Url}{Endpoints.APP_ICONS}/{this.Id.ToString(CultureInfo.InvariantCulture)}/{this.IconHash}.png?size=1024" : null; /// /// Gets the application's icon hash. /// public string IconHash { get; internal set; } /// /// Gets the application's allowed RPC origins. /// public IReadOnlyList RpcOrigins { get; internal set; } /// /// Gets the application's flags. /// public ApplicationFlags Flags { get; internal set; } /// /// Gets the application's owners. /// public IEnumerable Owners { get; internal set; } /// /// Gets whether this application's bot user requires code grant. /// public bool? RequiresCodeGrant { get; internal set; } /// /// Gets whether this bot application is public. /// public bool? IsPublic { get; internal set; } /// /// Gets the terms of service url of the application. /// public string TermsOfServiceUrl { get; internal set; } /// /// Gets the privacy policy url of the application. /// public string PrivacyPolicyUrl { get; internal set; } /// /// Gets the team name of the application. /// public string TeamName { get; internal set; } /// /// Gets the hash of the application's cover image. /// public string CoverImageHash { get; internal set; } /// /// Gets this application's cover image URL. /// public override string CoverImageUrl => $"{DiscordDomain.GetDomain(CoreDomain.DiscordCdn).Url}{Endpoints.APP_ICONS}/{this.Id.ToString(CultureInfo.InvariantCulture)}/{this.CoverImageHash}.png?size=1024"; /// /// Gets the team which owns this application. /// public DiscordTeam Team { get; internal set; } /// /// Gets the hex encoded key for verification in interactions and the GameSDK's GetTicket /// public string VerifyKey { get; internal set; } /// /// If this application is a game sold on Discord, this field will be the guild to which it has been linked /// public ulong? GuildId { get; internal set; } /// /// If this application is a game sold on Discord, this field will be the id of the "Game SKU" that is created, if exists /// public ulong? PrimarySkuId { get; internal set; } /// /// If this application is a game sold on Discord, this field will be the URL slug that links to the store page /// public string Slug { get; internal set; } /// /// Gets or sets a list of . /// private IReadOnlyList Assets { get; set; } /// /// A custom url for the Add To Server button. /// public string CustomInstallUrl { get; internal set; } /// /// Install parameters for adding the application to a guild. /// public DiscordApplicationInstallParams InstallParams { get; internal set; } /// /// The application tags. /// Not used atm. /// public IReadOnlyList Tags { get; internal set; } /// /// Initializes a new instance of the class. /// internal DiscordApplication() { } /// /// Gets the application's cover image URL, in requested format and size. /// /// Format of the image to get. /// Maximum size of the cover image. Must be a power of two, minimum 16, maximum 2048. /// URL of the application's cover image. public string GetAvatarUrl(ImageFormat fmt, ushort size = 1024) { if (fmt == ImageFormat.Unknown) throw new ArgumentException("You must specify valid image format.", nameof(fmt)); if (size < 16 || size > 2048) throw new ArgumentOutOfRangeException(nameof(size)); var log = Math.Log(size, 2); if (log < 4 || log > 11 || log % 1 != 0) throw new ArgumentOutOfRangeException(nameof(size)); var sfmt = ""; sfmt = fmt switch { ImageFormat.Gif => "gif", ImageFormat.Jpeg => "jpg", ImageFormat.Auto or ImageFormat.Png => "png", ImageFormat.WebP => "webp", _ => throw new ArgumentOutOfRangeException(nameof(fmt)), }; var ssize = size.ToString(CultureInfo.InvariantCulture); return !string.IsNullOrWhiteSpace(this.CoverImageHash) ? $"{DiscordDomain.GetDomain(CoreDomain.DiscordCdn).Url}{Endpoints.AVATARS}/{this.Id.ToString(CultureInfo.InvariantCulture)}/{this.IconHash}.{sfmt}?size={ssize}" : null; } /// /// Retrieves this application's assets. /// /// This application's assets. public async Task> GetAssetsAsync() { if (this.Assets == null) this.Assets = await this.Discord.ApiClient.GetApplicationAssetsAsync(this).ConfigureAwait(false); return this.Assets; } /// /// Generates an oauth url for the application. /// /// The permissions. /// OAuth Url public string GenerateBotOAuth(Permissions permissions = Permissions.None) { - permissions &= PermissionMethods.FULL_PERMS; + permissions &= PermissionMethods.FullPerms; // hey look, it's not all annoying and blue :P return new QueryUriBuilder($"{DiscordDomain.GetDomain(CoreDomain.Discord).Url}{Endpoints.OAUTH2}{Endpoints.AUTHORIZE}") .AddParameter("client_id", this.Id.ToString(CultureInfo.InvariantCulture)) .AddParameter("scope", "bot") .AddParameter("permissions", ((long)permissions).ToString(CultureInfo.InvariantCulture)) .ToString(); } /// /// 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 DiscordApplication); /// /// Checks whether this is equal to another . /// /// to compare to. /// Whether the is equal to this . public bool Equals(DiscordApplication 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 application to compare. /// Second application to compare. /// Whether the two applications are equal. public static bool operator ==(DiscordApplication e1, DiscordApplication 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 application to compare. /// Second application to compare. /// Whether the two applications are not equal. public static bool operator !=(DiscordApplication e1, DiscordApplication e2) => !(e1 == e2); } /// /// Represents an discord asset. /// public abstract class DiscordAsset { /// /// Gets the ID of this asset. /// public virtual string Id { get; set; } /// /// Gets the URL of this asset. /// public abstract Uri Url { get; } } /// /// Represents an asset for an OAuth2 application. /// public sealed class DiscordApplicationAsset : DiscordAsset, IEquatable { /// /// Gets the Discord client instance for this asset. /// internal BaseDiscordClient Discord { get; set; } /// /// Gets the asset's name. /// [JsonProperty("name")] public string Name { get; internal set; } /// /// Gets the asset's type. /// [JsonProperty("type")] public ApplicationAssetType Type { get; internal set; } /// /// Gets the application this asset belongs to. /// public DiscordApplication Application { get; internal set; } /// /// Gets the Url of this asset. /// public override Uri Url => new($"{DiscordDomain.GetDomain(CoreDomain.DiscordCdn).Url}{Endpoints.APP_ASSETS}/{this.Application.Id.ToString(CultureInfo.InvariantCulture)}/{this.Id}.png"); /// /// Initializes a new instance of the class. /// internal DiscordApplicationAsset() { } /// /// Initializes a new instance of the class. /// /// The app. internal DiscordApplicationAsset(DiscordApplication app) { this.Discord = app.Discord; } /// /// 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 DiscordApplicationAsset); /// /// Checks whether this is equal to another . /// /// to compare to. /// Whether the is equal to this . public bool Equals(DiscordApplicationAsset 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 application asset to compare. /// Second application asset to compare. /// Whether the two application assets not equal. public static bool operator ==(DiscordApplicationAsset e1, DiscordApplicationAsset 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 application asset to compare. /// Second application asset to compare. /// Whether the two application assets are not equal. public static bool operator !=(DiscordApplicationAsset e1, DiscordApplicationAsset e2) => !(e1 == e2); } /// /// Represents an spotify asset. /// public sealed class DiscordSpotifyAsset : DiscordAsset { /// /// Gets the URL of this asset. /// public override Uri Url => this._url.Value; private readonly Lazy _url; /// /// Initializes a new instance of the class. /// public DiscordSpotifyAsset() { this._url = new Lazy(() => { var ids = this.Id.Split(':'); var id = ids[1]; return new Uri($"https://i.scdn.co/image/{id}"); }); } } /// /// Determines the type of the asset attached to the application. /// public enum ApplicationAssetType : int { /// /// Unknown type. This indicates something went terribly wrong. /// Unknown = 0, /// /// This asset can be used as small image for rich presences. /// SmallImage = 1, /// /// This asset can be used as large image for rich presences. /// LargeImage = 2 } } diff --git a/DisCatSharp/Entities/Application/DiscordApplicationCommand.cs b/DisCatSharp/Entities/Application/DiscordApplicationCommand.cs index f7c42d275..095bb5e8c 100644 --- a/DisCatSharp/Entities/Application/DiscordApplicationCommand.cs +++ b/DisCatSharp/Entities/Application/DiscordApplicationCommand.cs @@ -1,202 +1,202 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using DisCatSharp.Enums; using Newtonsoft.Json; namespace DisCatSharp.Entities { /// /// Represents a command that is registered to an application. /// public sealed class DiscordApplicationCommand : SnowflakeObject, IEquatable { /// /// Gets the type of this application command. /// [JsonProperty("type")] public ApplicationCommandType Type { get; internal set; } /// /// Gets the unique ID of this command's application. /// [JsonProperty("application_id")] public ulong ApplicationId { get; internal set; } /// /// Gets the name of this command. /// [JsonProperty("name")] public string Name { get; internal set; } /// /// Sets the name localizations. /// [JsonProperty("name_localizations", NullValueHandling = NullValueHandling.Ignore)] internal Dictionary RawNameLocalizations { get; set; } /// /// Gets the name localizations. /// [JsonIgnore] public DiscordApplicationCommandLocalization NameLocalizations => new(this.RawNameLocalizations); /// /// Gets the description of this command. /// [JsonProperty("description")] public string Description { get; internal set; } /// /// Sets the description localizations. /// [JsonProperty("description_localizations", NullValueHandling = NullValueHandling.Ignore)] internal Dictionary RawDescriptionLocalizations { get; set; } /// /// Gets the description localizations. /// [JsonIgnore] public DiscordApplicationCommandLocalization DescriptionLocalizations => new(this.RawDescriptionLocalizations); /// /// Gets the potential parameters for this command. /// [JsonProperty("options", NullValueHandling = NullValueHandling.Ignore)] public IReadOnlyCollection Options { get; internal set; } /// /// Gets whether the command is enabled by default when the app is added to a guild. /// [JsonProperty("default_permission", NullValueHandling = NullValueHandling.Ignore)] public bool DefaultPermission { get; internal set; } /// /// Gets the commands needed permissions. /// [JsonProperty("default_member_permissions", NullValueHandling = NullValueHandling.Ignore)] public Permissions? Permission { get; internal set; } /// /// Gets whether the command can be used in direct messages. /// [JsonProperty("dm_permission", NullValueHandling = NullValueHandling.Ignore)] public bool? DmPermission { get; internal set; } /// - /// Gets the version number for this command. + /// Gets the version number for this command. /// [JsonProperty("version")] public ulong Version { get; internal set; } /// /// Creates a new instance of a . /// /// The name of the command. /// The description of the command. /// Optional parameters for this command. /// Optional default permission for this command. /// The type of the command. Defaults to ChatInput. /// The localizations of the command name. /// The localizations of the command description. - public DiscordApplicationCommand(string name, string description, IEnumerable options = null, bool default_permission = true, ApplicationCommandType type = ApplicationCommandType.ChatInput, DiscordApplicationCommandLocalization nameLocalizations = null, DiscordApplicationCommandLocalization descriptionLocalizations = null) + public DiscordApplicationCommand(string name, string description, IEnumerable options = null, bool defaultPermission = true, ApplicationCommandType type = ApplicationCommandType.ChatInput, DiscordApplicationCommandLocalization nameLocalizations = null, DiscordApplicationCommandLocalization descriptionLocalizations = null) { if (type is ApplicationCommandType.ChatInput) { if (!Utilities.IsValidSlashCommandName(name)) throw new ArgumentException("Invalid slash command name specified. It must be below 32 characters and not contain any whitespace.", nameof(name)); if (name.Any(ch => char.IsUpper(ch))) throw new ArgumentException("Slash command name cannot have any upper case characters.", nameof(name)); if (description.Length > 100) throw new ArgumentException("Slash command description cannot exceed 100 characters.", nameof(description)); this.RawNameLocalizations = nameLocalizations?.GetKeyValuePairs(); this.RawDescriptionLocalizations = descriptionLocalizations?.GetKeyValuePairs(); } else { if (!string.IsNullOrWhiteSpace(description)) throw new ArgumentException("Context menus do not support descriptions."); if (options?.Any() ?? false) throw new ArgumentException("Context menus do not support options."); description = string.Empty; this.RawNameLocalizations = nameLocalizations?.GetKeyValuePairs(); } var optionsList = options != null ? new ReadOnlyCollection(options.ToList()) : null; this.Type = type; this.Name = name; this.Description = description; this.Options = optionsList; - this.DefaultPermission = default_permission; + this.DefaultPermission = defaultPermission; } /// /// Checks whether this object is equal to another object. /// /// The command to compare to. /// Whether the command is equal to this . public bool Equals(DiscordApplicationCommand other) => this.Id == other.Id; /// /// Determines if two objects are equal. /// /// The first command object. /// The second command object. /// Whether the two objects are equal. public static bool operator ==(DiscordApplicationCommand e1, DiscordApplicationCommand e2) => e1.Equals(e2); /// /// Determines if two objects are not equal. /// /// The first command object. /// The second command object. /// Whether the two objects are not equal. public static bool operator !=(DiscordApplicationCommand e1, DiscordApplicationCommand e2) => !(e1 == e2); /// /// Determines if a is equal to the current . /// /// The object to compare to. /// Whether the two objects are not equal. public override bool Equals(object other) => other is DiscordApplicationCommand dac && this.Equals(dac); /// /// Gets the hash code for this . /// /// The hash code for this . public override int GetHashCode() => this.Id.GetHashCode(); } } diff --git a/DisCatSharp/Entities/Application/DiscordApplicationCommandLocalization.cs b/DisCatSharp/Entities/Application/DiscordApplicationCommandLocalization.cs index dc1d5790e..30796fda6 100644 --- a/DisCatSharp/Entities/Application/DiscordApplicationCommandLocalization.cs +++ b/DisCatSharp/Entities/Application/DiscordApplicationCommandLocalization.cs @@ -1,106 +1,106 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System; using System.Collections.Generic; namespace DisCatSharp.Entities { /// /// Represents a application command localization. /// public sealed class DiscordApplicationCommandLocalization { /// /// Gets the localization dict. /// public Dictionary Localizations { get; internal set; } /// /// Gets valid [locales](xref:application_commands_translations_reference#valid-locales) for Discord. /// - internal List _validLocales = new() { "ru", "fi", "hr", "de", "hu", "sv-SE", "cs", "fr", "it", "en-GB", "pt-BR", "ja", "tr", "en-US", "es-ES", "uk", "hi", "th", "el", "no", "ro", "ko", "zh-TW", "vi", "zh-CN", "pl", "bg", "da", "nl", "lt" }; + internal List ValidLocales = new() { "ru", "fi", "hr", "de", "hu", "sv-SE", "cs", "fr", "it", "en-GB", "pt-BR", "ja", "tr", "en-US", "es-ES", "uk", "hi", "th", "el", "no", "ro", "ko", "zh-TW", "vi", "zh-CN", "pl", "bg", "da", "nl", "lt" }; /// /// Adds a localization. /// /// The [locale](xref:application_commands_translations_reference#valid-locales) to add. /// The translation to add. public void AddLocalization(string locale, string value) { if (this.Validate(locale)) { this.Localizations.Add(locale, value); } else { throw new NotSupportedException($"The provided locale \"{locale}\" is not valid for Discord.\n" + - $"Valid locales: {string.Join(", ", this._validLocales.ToArray())}"); + $"Valid locales: {string.Join(", ", this.ValidLocales.ToArray())}"); } } /// /// Removes a localization. /// /// The [locale](xref:application_commands_translations_reference#valid-locales) to remove. public void RemoveLocalization(string locale) => this.Localizations.Remove(locale); /// /// Initializes a new instance of . /// public DiscordApplicationCommandLocalization() { } /// /// Initializes a new instance of . /// /// Localizations. public DiscordApplicationCommandLocalization(Dictionary localizations) { if (localizations != null) { foreach (var locale in localizations.Keys) { if (!this.Validate(locale)) throw new NotSupportedException($"The provided locale \"{locale}\" is not valid for Discord.\n" + - $"Valid locales: {string.Join(", ", this._validLocales.ToArray())}"); + $"Valid locales: {string.Join(", ", this.ValidLocales.ToArray())}"); } } this.Localizations = localizations; } /// /// Gets the KVPs. /// /// public Dictionary GetKeyValuePairs() => this.Localizations; /// /// Whether the [locale](xref:application_commands_translations_reference#valid-locales) to be added is valid for Discord. /// /// [Locale](xref:application_commands_translations_reference#valid-locales) string. public bool Validate(string lang) - => this._validLocales.Contains(lang); + => this.ValidLocales.Contains(lang); } } diff --git a/DisCatSharp/Entities/Channel/DiscordChannel.cs b/DisCatSharp/Entities/Channel/DiscordChannel.cs index 8a3901c46..be1121fef 100644 --- a/DisCatSharp/Entities/Channel/DiscordChannel.cs +++ b/DisCatSharp/Entities/Channel/DiscordChannel.cs @@ -1,1402 +1,1402 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Globalization; using System.IO; using System.Linq; using System.Threading.Tasks; using DisCatSharp.Enums; using DisCatSharp.Net; using DisCatSharp.Net.Abstractions; using DisCatSharp.Net.Models; using Newtonsoft.Json; namespace DisCatSharp.Entities { /// /// Represents a discord channel. /// public class DiscordChannel : SnowflakeObject, IEquatable { /// /// Gets ID of the guild to which this channel belongs. /// [JsonProperty("guild_id", NullValueHandling = NullValueHandling.Ignore)] public ulong? GuildId { get; internal set; } /// /// Gets ID of the category that contains this channel. /// [JsonProperty("parent_id", NullValueHandling = NullValueHandling.Include)] public ulong? ParentId { get; internal set; } /// /// Gets the category that contains this channel. /// [JsonIgnore] public DiscordChannel Parent => this.ParentId.HasValue ? this.Guild.GetChannel(this.ParentId.Value) : null; /// /// Gets the name of this channel. /// [JsonProperty("name", NullValueHandling = NullValueHandling.Ignore)] public string Name { get; internal set; } /// /// Gets the type of this channel. /// [JsonProperty("type", NullValueHandling = NullValueHandling.Ignore)] public ChannelType Type { get; internal set; } /// /// Gets this channels's banner hash, when applicable. /// [JsonProperty("banner")] public string BannerHash { get; internal set; } /// /// Gets this channels's banner in url form. /// [JsonIgnore] public string BannerUrl => !string.IsNullOrWhiteSpace(this.BannerHash) ? $"{DiscordDomain.GetDomain(CoreDomain.DiscordCdn).Uri}{Endpoints.CHANNELS}/{this.Id.ToString(CultureInfo.InvariantCulture)}{Endpoints.BANNERS}/{this.BannerHash}.{(this.BannerHash.StartsWith("a_") ? "gif" : "png")}" : null; /// /// Gets the position of this channel. /// [JsonProperty("position", NullValueHandling = NullValueHandling.Ignore)] public int Position { get; internal set; } /// /// Gets the maximum available position to move the channel to. /// This can contain outdated informations. /// public int GetMaxPosition() { var channels = this.Guild.Channels.Values; return this.ParentId != null ? this.Type == ChannelType.Text || this.Type == ChannelType.News ? channels.Where(xc => xc.ParentId == this.ParentId && (xc.Type == ChannelType.Text || xc.Type == ChannelType.News)).OrderBy(xc => xc.Position).ToArray().Last().Position : this.Type == ChannelType.Voice || this.Type == ChannelType.Stage ? channels.Where(xc => xc.ParentId == this.ParentId && (xc.Type == ChannelType.Voice || xc.Type == ChannelType.Stage)).OrderBy(xc => xc.Position).ToArray().Last().Position : channels.Where(xc => xc.ParentId == this.ParentId && xc.Type == this.Type).OrderBy(xc => xc.Position).ToArray().Last().Position : channels.Where(xc => xc.ParentId == null && xc.Type == this.Type).OrderBy(xc => xc.Position).ToArray().Last().Position; } /// /// Gets the minimum available position to move the channel to. /// public int GetMinPosition() { var channels = this.Guild.Channels.Values; return this.ParentId != null ? this.Type == ChannelType.Text || this.Type == ChannelType.News ? channels.Where(xc => xc.ParentId == this.ParentId && (xc.Type == ChannelType.Text || xc.Type == ChannelType.News)).OrderBy(xc => xc.Position).ToArray().First().Position : this.Type == ChannelType.Voice || this.Type == ChannelType.Stage ? channels.Where(xc => xc.ParentId == this.ParentId && (xc.Type == ChannelType.Voice || xc.Type == ChannelType.Stage)).OrderBy(xc => xc.Position).ToArray().First().Position : channels.Where(xc => xc.ParentId == this.ParentId && xc.Type == this.Type).OrderBy(xc => xc.Position).ToArray().First().Position : channels.Where(xc => xc.ParentId == null && xc.Type == this.Type).OrderBy(xc => xc.Position).ToArray().First().Position; } /// /// Gets whether this channel is a DM channel. /// [JsonIgnore] public bool IsPrivate => this.Type == ChannelType.Private || this.Type == ChannelType.Group; /// /// Gets whether this channel is a channel category. /// [JsonIgnore] public bool IsCategory => this.Type == ChannelType.Category; /// /// Gets whether this channel is a stage channel. /// [JsonIgnore] public bool IsStage => this.Type == ChannelType.Stage; /// /// Gets the guild to which this channel belongs. /// [JsonIgnore] public DiscordGuild Guild => this.GuildId.HasValue && this.Discord.Guilds.TryGetValue(this.GuildId.Value, out var guild) ? guild : null; /// /// Gets a collection of permission overwrites for this channel. /// [JsonIgnore] public IReadOnlyList PermissionOverwrites => this._permissionOverwritesLazy.Value; [JsonProperty("permission_overwrites", NullValueHandling = NullValueHandling.Ignore)] - internal List _permissionOverwrites = new(); + internal List PermissionOverwritesInternal = new(); [JsonIgnore] private readonly Lazy> _permissionOverwritesLazy; /// /// Gets the channel's topic. This is applicable to text channels only. /// [JsonProperty("topic", NullValueHandling = NullValueHandling.Ignore)] public string Topic { get; internal set; } /// /// Gets the ID of the last message sent in this channel. This is applicable to text channels only. /// [JsonProperty("last_message_id", NullValueHandling = NullValueHandling.Ignore)] public ulong? LastMessageId { get; internal set; } /// /// Gets this channel's bitrate. This is applicable to voice channels only. /// [JsonProperty("bitrate", NullValueHandling = NullValueHandling.Ignore)] public int? Bitrate { get; internal set; } /// /// Gets this channel's user limit. This is applicable to voice channels only. /// [JsonProperty("user_limit", NullValueHandling = NullValueHandling.Ignore)] public int? UserLimit { get; internal set; } /// /// Gets the slow mode delay configured for this channel. /// All bots, as well as users with or permissions in the channel are exempt from slow mode. /// [JsonProperty("rate_limit_per_user")] public int? PerUserRateLimit { get; internal set; } /// /// Gets this channel's video quality mode. This is applicable to voice channels only. /// [JsonProperty("video_quality_mode", NullValueHandling = NullValueHandling.Ignore)] public VideoQualityMode? QualityMode { get; internal set; } /// /// Gets when the last pinned message was pinned. /// [JsonIgnore] public DateTimeOffset? LastPinTimestamp => !string.IsNullOrWhiteSpace(this.LastPinTimestampRaw) && DateTimeOffset.TryParse(this.LastPinTimestampRaw, CultureInfo.InvariantCulture, DateTimeStyles.None, out var dto) ? dto : null; /// /// Gets when the last pinned message was pinned as raw string. /// [JsonProperty("last_pin_timestamp", NullValueHandling = NullValueHandling.Ignore)] internal string LastPinTimestampRaw { get; set; } /// /// Gets this channel's default duration for newly created threads, in minutes, to automatically archive the thread after recent activity. /// [JsonProperty("default_auto_archive_duration", NullValueHandling = NullValueHandling.Ignore)] public ThreadAutoArchiveDuration? DefaultAutoArchiveDuration { get; internal set; } /// /// Gets this channel's mention string. /// [JsonIgnore] public string Mention => Formatter.Mention(this); /// /// Gets this channel's children. This applies only to channel categories. /// [JsonIgnore] public IReadOnlyList Children { get { return !this.IsCategory ? throw new ArgumentException("Only channel categories contain children.") - : this.Guild._channels.Values.Where(e => e.ParentId == this.Id).ToList(); + : this.Guild.ChannelsInternal.Values.Where(e => e.ParentId == this.Id).ToList(); } } /// /// Gets the list of members currently in the channel (if voice channel), or members who can see the channel (otherwise). /// [JsonIgnore] public virtual IReadOnlyList Users { get { return this.Guild == null ? throw new InvalidOperationException("Cannot query users outside of guild channels.") : this.IsVoiceJoinable() ? this.Guild.Members.Values.Where(x => x.VoiceState?.ChannelId == this.Id).ToList() : this.Guild.Members.Values.Where(x => (this.PermissionsFor(x) & Permissions.AccessChannels) == Permissions.AccessChannels).ToList(); } } /// /// Gets whether this channel is an NSFW channel. /// [JsonProperty("nsfw")] - public bool IsNSFW { get; internal set; } + public bool IsNsfw { get; internal set; } /// /// Gets this channel's region id (if voice channel). /// [JsonProperty("rtc_region", NullValueHandling = NullValueHandling.Ignore)] internal string RtcRegionId { get; set; } /// /// Gets this channel's region override (if voice channel). /// [JsonIgnore] public DiscordVoiceRegion RtcRegion => this.RtcRegionId != null ? this.Discord.VoiceRegions[this.RtcRegionId] : null; /// /// Only sent on the resolved channels of interaction responses for application commands. Gets the permissions of the user in this channel who invoked the command. /// [JsonProperty("permissions")] public Permissions? UserPermissions { get; internal set; } /// /// Initializes a new instance of the class. /// internal DiscordChannel() { - this._permissionOverwritesLazy = new Lazy>(() => new ReadOnlyCollection(this._permissionOverwrites)); + this._permissionOverwritesLazy = new Lazy>(() => new ReadOnlyCollection(this.PermissionOverwritesInternal)); } #region Methods /// /// Sends a message to this channel. /// /// Content of the message to send. /// The sent message. /// Thrown when the client does not have the permission if TTS is true and if TTS is true. /// Thrown when the channel does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task SendMessageAsync(string content) { return !this.IsWriteable() ? throw new ArgumentException("Cannot send a text message to a non-text channel.") : this.Discord.ApiClient.CreateMessageAsync(this.Id, content, null, sticker: null, replyMessageId: null, mentionReply: false, failOnInvalidReply: false); } /// /// Sends a message to this channel. /// /// Embed to attach to the message. /// The sent message. /// Thrown when the client does not have the permission and if TTS is true. /// Thrown when the channel does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task SendMessageAsync(DiscordEmbed embed) { return !this.IsWriteable() ? throw new ArgumentException("Cannot send a text message to a non-text channel.") : this.Discord.ApiClient.CreateMessageAsync(this.Id, null, embed != null ? new[] { embed } : null, sticker: null, replyMessageId: null, mentionReply: false, failOnInvalidReply: false); } /// /// Sends a message to this channel. /// /// Embed to attach to the message. /// Content of the message to send. /// The sent message. /// Thrown when the client does not have the permission if TTS is true and if TTS is true. /// Thrown when the channel does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task SendMessageAsync(string content, DiscordEmbed embed) { return !this.IsWriteable() ? throw new ArgumentException("Cannot send a text message to a non-text channel.") : this.Discord.ApiClient.CreateMessageAsync(this.Id, content, embed != null ? new[] { embed } : null, sticker: null, replyMessageId: null, mentionReply: false, failOnInvalidReply: false); } /// /// Sends a message to this channel. /// /// The builder with all the items to send. /// The sent message. /// Thrown when the client does not have the permission TTS is true and if TTS is true. /// Thrown when the channel does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task SendMessageAsync(DiscordMessageBuilder builder) => this.Discord.ApiClient.CreateMessageAsync(this.Id, builder); /// /// Sends a message to this channel. /// /// The builder with all the items to send. /// The sent message. /// Thrown when the client does not have the permission TTS is true and if TTS is true. /// Thrown when the channel does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task SendMessageAsync(Action action) { var builder = new DiscordMessageBuilder(); action(builder); return !this.IsWriteable() ? throw new ArgumentException("Cannot send a text message to a non-text channel.") : this.Discord.ApiClient.CreateMessageAsync(this.Id, builder); } /// /// Deletes a guild channel /// /// Reason for audit logs. /// Thrown when the client does not have the permission. /// Thrown when the channel does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task DeleteAsync(string reason = null) => this.Discord.ApiClient.DeleteChannelAsync(this.Id, reason); /// /// Clones this channel. This operation will create a channel with identical settings to this one. Note that this will not copy messages. /// /// Reason for audit logs. /// Newly-created channel. /// Thrown when the client does not have the permission. /// Thrown when the channel does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public async Task CloneAsync(string reason = null) { if (this.Guild == null) throw new InvalidOperationException("Non-guild channels cannot be cloned."); var ovrs = new List(); - foreach (var ovr in this._permissionOverwrites) + foreach (var ovr in this.PermissionOverwritesInternal) #pragma warning disable CS0618 // Type or member is obsolete ovrs.Add(await new DiscordOverwriteBuilder().FromAsync(ovr).ConfigureAwait(false)); #pragma warning restore CS0618 // Type or member is obsolete var bitrate = this.Bitrate; var userLimit = this.UserLimit; Optional perUserRateLimit = this.PerUserRateLimit; if (!this.IsVoiceJoinable()) { bitrate = null; userLimit = null; } if (this.Type == ChannelType.Stage) { userLimit = null; } if (!this.IsWriteable()) { perUserRateLimit = Optional.FromNoValue(); } - return await this.Guild.CreateChannelAsync(this.Name, this.Type, this.Parent, this.Topic, bitrate, userLimit, ovrs, this.IsNSFW, perUserRateLimit, this.QualityMode, reason).ConfigureAwait(false); + return await this.Guild.CreateChannelAsync(this.Name, this.Type, this.Parent, this.Topic, bitrate, userLimit, ovrs, this.IsNsfw, perUserRateLimit, this.QualityMode, reason).ConfigureAwait(false); } /// /// Returns a specific message /// /// The id of the message /// Thrown when the client does not have the permission. /// Thrown when the channel does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public async Task GetMessageAsync(ulong id) { return this.Discord.Configuration.MessageCacheSize > 0 && this.Discord is DiscordClient dc && dc.MessageCache != null && dc.MessageCache.TryGet(xm => xm.Id == id && xm.ChannelId == this.Id, out var msg) ? msg : await this.Discord.ApiClient.GetMessageAsync(this.Id, id).ConfigureAwait(false); } /// /// Modifies the current channel. /// /// Action to perform on this channel /// Thrown when the client does not have the . /// Thrown when the client does not have the correct for modifying the . /// Thrown when the channel does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task ModifyAsync(Action action) { var mdl = new ChannelEditModel(); action(mdl); if (mdl.DefaultAutoArchiveDuration.HasValue) { if (!Utilities.CheckThreadAutoArchiveDurationFeature(this.Guild, mdl.DefaultAutoArchiveDuration.Value)) throw new NotSupportedException($"Cannot modify DefaultAutoArchiveDuration. Guild needs boost tier {(mdl.DefaultAutoArchiveDuration.Value == ThreadAutoArchiveDuration.ThreeDays ? "one" : "two")}."); } if (mdl.Banner.HasValue) { if (!this.Guild.Features.CanSetChannelBanner) throw new NotSupportedException($"Cannot modify Banner. Guild needs boost tier three."); } var bannerb64 = Optional.FromNoValue(); if (mdl.Banner.HasValue && mdl.Banner.Value != null) using (var imgtool = new ImageTool(mdl.Banner.Value)) bannerb64 = imgtool.GetBase64(); else if (mdl.Banner.HasValue) bannerb64 = null; return this.Discord.ApiClient.ModifyChannelAsync(this.Id, mdl.Name, mdl.Position, mdl.Topic, mdl.Nsfw, mdl.Parent.HasValue ? mdl.Parent.Value?.Id : default(Optional), mdl.Bitrate, mdl.Userlimit, mdl.PerUserRateLimit, mdl.RtcRegion.IfPresent(r => r?.Id), mdl.QualityMode, mdl.DefaultAutoArchiveDuration, mdl.Type, mdl.PermissionOverwrites, bannerb64, mdl.AuditLogReason); } /// /// Updates the channel position when it doesn't have a category. - /// + /// /// Use for moving to other categories. /// Use to move out of a category. /// Use for moving within a category. /// /// Position the channel should be moved to. /// Reason for audit logs. /// Thrown when the client does not have the permission. /// Thrown when the channel does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task ModifyPositionAsync(int position, string reason = null) { if (this.Guild == null) throw new ArgumentException("Cannot modify order of non-guild channels."); if (!this.IsMovable()) throw new NotSupportedException("You can't move this type of channel in categories."); if (this.ParentId != null) throw new ArgumentException("Cannot modify order of channels within a category. Use ModifyPositionInCategoryAsync instead."); - var chns = this.Guild._channels.Values.Where(xc => xc.Type == this.Type).OrderBy(xc => xc.Position).ToArray(); + var chns = this.Guild.ChannelsInternal.Values.Where(xc => xc.Type == this.Type).OrderBy(xc => xc.Position).ToArray(); var pmds = new RestGuildChannelReorderPayload[chns.Length]; for (var i = 0; i < chns.Length; i++) { pmds[i] = new RestGuildChannelReorderPayload { ChannelId = chns[i].Id, }; pmds[i].Position = chns[i].Id == this.Id ? position : chns[i].Position >= position ? chns[i].Position + 1 : chns[i].Position; } return this.Discord.ApiClient.ModifyGuildChannelPositionAsync(this.Guild.Id, pmds, reason); } /// /// Updates the channel position within it's own category. /// /// Use for moving to other categories. /// Use to move out of a category. /// Use to move channels outside a category. /// /// The position. /// The reason. /// Thrown when the client does not have the permission. /// Thrown when the channel does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. /// Thrown when is out of range. /// Thrown when function is called on a channel without a parent channel. public async Task ModifyPositionInCategoryAsync(int position, string reason = null) { //if (this.ParentId == null) // throw new ArgumentException("You can call this function only on channels in categories."); if (!this.IsMovableInParent()) throw new NotSupportedException("You can't move this type of channel in categories."); var isUp = position > this.Position; var channels = await this.InternalRefreshChannelsAsync(); var chns = this.ParentId != null ? this.Type == ChannelType.Text || this.Type == ChannelType.News ? channels.Where(xc => xc.ParentId == this.ParentId && (xc.Type == ChannelType.Text || xc.Type == ChannelType.News)) : this.Type == ChannelType.Voice || this.Type == ChannelType.Stage ? channels.Where(xc => xc.ParentId == this.ParentId && (xc.Type == ChannelType.Voice || xc.Type == ChannelType.Stage)) : channels.Where(xc => xc.ParentId == this.ParentId && xc.Type == this.Type) : this.Type == ChannelType.Text || this.Type == ChannelType.News ? channels.Where(xc => xc.ParentId == null && (xc.Type == ChannelType.Text || xc.Type == ChannelType.News)) : this.Type == ChannelType.Voice || this.Type == ChannelType.Stage ? channels.Where(xc => xc.ParentId == null && (xc.Type == ChannelType.Voice || xc.Type == ChannelType.Stage)) : channels.Where(xc => xc.ParentId == null && xc.Type == this.Type); var ochns = chns.OrderBy(xc => xc.Position).ToArray(); var min = ochns.First().Position; var max = ochns.Last().Position; if (position > max || position < min) throw new IndexOutOfRangeException($"Position is not in range. {position} is {(position > max ? "greater then the maximal" : "lower then the minimal")} position."); var pmds = new RestGuildChannelReorderPayload[ochns.Length]; for (var i = 0; i < ochns.Length; i++) { pmds[i] = new RestGuildChannelReorderPayload { ChannelId = ochns[i].Id, }; if (ochns[i].Id == this.Id) { pmds[i].Position = position; } else { if (isUp) { if (ochns[i].Position <= position && ochns[i].Position > this.Position) { pmds[i].Position = ochns[i].Position - 1; } else if (ochns[i].Position < this.Position || ochns[i].Position > position) { pmds[i].Position = ochns[i].Position; } } else { if (ochns[i].Position >= position && ochns[i].Position < this.Position) { pmds[i].Position = ochns[i].Position + 1; } else if (ochns[i].Position > this.Position || ochns[i].Position < position) { pmds[i].Position = ochns[i].Position; } } } } await this.Discord.ApiClient.ModifyGuildChannelPositionAsync(this.Guild.Id, pmds, reason).ConfigureAwait(false); } /// /// Internaly refreshes the channel list. /// private async Task> InternalRefreshChannelsAsync() { await this.RefreshPositionsAsync(); return this.Guild.Channels.Values.ToList().AsReadOnly(); } /// /// Refreshes the positions. /// public async Task RefreshPositionsAsync() { var channels = await this.Discord.ApiClient.GetGuildChannelsAsync(this.Guild.Id); - this.Guild._channels.Clear(); + this.Guild.ChannelsInternal.Clear(); foreach (var channel in channels.ToList()) { channel.Discord = this.Discord; - foreach (var xo in channel._permissionOverwrites) + foreach (var xo in channel.PermissionOverwritesInternal) { xo.Discord = this.Discord; - xo._channel_id = channel.Id; + xo.ChannelId = channel.Id; } - this.Guild._channels[channel.Id] = channel; + this.Guild.ChannelsInternal[channel.Id] = channel; } } /// /// Updates the channel position within it's own category. /// Valid modes: '+' or 'down' to move a channel down | '-' or 'up' to move a channel up. - /// + /// /// Use for moving to other categories. /// Use to move out of a category. /// Use to move channels outside a category. /// /// The mode. Valid: '+' or 'down' to move a channel down | '-' or 'up' to move a channel up /// The position. /// The reason. /// Thrown when the client does not have the permission. /// Thrown when the channel does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. /// Thrown when is out of range. /// Thrown when function is called on a channel without a parent channel, a wrong mode is givven or given position is zero. public Task ModifyPositionInCategorySmartAsync(string mode, int position, string reason = null) { if (!this.IsMovableInParent()) throw new NotSupportedException("You can't move this type of channel in categories."); if (mode != "+" && mode != "-" && mode != "down" && mode != "up") throw new ArgumentException("Error with the selected mode: Valid is '+' or 'down' to move a channel down and '-' or 'up' to move a channel up"); var positive = mode == "+" || mode == "positive" || mode == "down"; var negative = mode == "-" || mode == "negative" || mode == "up"; return positive ? position < this.GetMaxPosition() ? this.ModifyPositionInCategoryAsync(this.Position + position, reason) : throw new IndexOutOfRangeException($"Position is not in range of category.") : negative ? position > this.GetMinPosition() ? this.ModifyPositionInCategoryAsync(this.Position - position, reason) : throw new IndexOutOfRangeException($"Position is not in range of category.") : throw new ArgumentException("You can only modify with +X or -X. 0 is not valid."); } /// /// Updates the channel parent, moving the channel to the bottom of the new category. /// /// New parent for channel. Will move out of parent if null. - /// Sync permissions with parent. Defaults to null. + /// Sync permissions with parent. Defaults to null. /// Reason for audit logs. /// Thrown when the client does not have the permission. /// Thrown when the channel does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. #pragma warning disable CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context. - public Task ModifyParentAsync(DiscordChannel? newParent = null, bool? lock_permissions = null, string reason = null) + public Task ModifyParentAsync(DiscordChannel? newParent = null, bool? lockPermissions = null, string reason = null) #pragma warning restore CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context. { if (this.Guild == null) throw new ArgumentException("Cannot modify parent of non-guild channels."); if (!this.IsMovableInParent()) throw new NotSupportedException("You can't move this type of channel in categories."); if (newParent.Type is not ChannelType.Category) throw new ArgumentException("Only category type channels can be parents."); - var position = this.Guild._channels.Values.Where(xc => xc.Type == this.Type && xc.ParentId == newParent.Id) // gets list same type channels in parent + var position = this.Guild.ChannelsInternal.Values.Where(xc => xc.Type == this.Type && xc.ParentId == newParent.Id) // gets list same type channels in parent .Select(xc => xc.Position).DefaultIfEmpty(-1).Max() + 1; // returns highest position of list +1, default val: 0 - var chns = this.Guild._channels.Values.Where(xc => xc.Type == this.Type) + var chns = this.Guild.ChannelsInternal.Values.Where(xc => xc.Type == this.Type) .OrderBy(xc => xc.Position).ToArray(); var pmds = new RestGuildChannelNewParentPayload[chns.Length]; for (var i = 0; i < chns.Length; i++) { pmds[i] = new RestGuildChannelNewParentPayload { ChannelId = chns[i].Id, Position = chns[i].Position >= position ? chns[i].Position + 1 : chns[i].Position, }; if (chns[i].Id == this.Id) { pmds[i].Position = position; pmds[i].ParentId = newParent is not null ? newParent.Id : null; - pmds[i].LockPermissions = lock_permissions; + pmds[i].LockPermissions = lockPermissions; } } return this.Discord.ApiClient.ModifyGuildChannelParentAsync(this.Guild.Id, pmds, reason); } /// /// Moves the channel out of a category. /// /// Reason for audit logs. /// Thrown when the client does not have the permission. /// Thrown when the channel does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task RemoveParentAsync(string reason = null) { if (this.Guild == null) throw new ArgumentException("Cannot modify parent of non-guild channels."); if (!this.IsMovableInParent()) throw new NotSupportedException("You can't move this type of channel in categories."); - var position = this.Guild._channels.Values.Where(xc => xc.Type == this.Type && xc.Parent is null) //gets list of same type channels with no parent + var position = this.Guild.ChannelsInternal.Values.Where(xc => xc.Type == this.Type && xc.Parent is null) //gets list of same type channels with no parent .Select(xc => xc.Position).DefaultIfEmpty(-1).Max() + 1; // returns highest position of list +1, default val: 0 - var chns = this.Guild._channels.Values.Where(xc => xc.Type == this.Type) + var chns = this.Guild.ChannelsInternal.Values.Where(xc => xc.Type == this.Type) .OrderBy(xc => xc.Position).ToArray(); var pmds = new RestGuildChannelNoParentPayload[chns.Length]; for (var i = 0; i < chns.Length; i++) { pmds[i] = new RestGuildChannelNoParentPayload { ChannelId = chns[i].Id, }; if (chns[i].Id == this.Id) { pmds[i].Position = 1; pmds[i].ParentId = null; } else { pmds[i].Position = chns[i].Position < this.Position ? chns[i].Position + 1 : chns[i].Position; } } return this.Discord.ApiClient.DetachGuildChannelParentAsync(this.Guild.Id, pmds, reason); } /// /// Returns a list of messages before a certain message. /// The amount of messages to fetch. /// Message to fetch before from. /// /// Thrown when the client does not have the permission. /// Thrown when the channel does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task> GetMessagesBeforeAsync(ulong before, int limit = 100) => this.GetMessagesInternalAsync(limit, before, null, null); /// /// Returns a list of messages after a certain message. /// The amount of messages to fetch. /// Message to fetch after from. /// /// Thrown when the client does not have the permission. /// Thrown when the channel does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task> GetMessagesAfterAsync(ulong after, int limit = 100) => this.GetMessagesInternalAsync(limit, null, after, null); /// /// Returns a list of messages around a certain message. /// The amount of messages to fetch. /// Message to fetch around from. /// /// Thrown when the client does not have the permission. /// Thrown when the channel does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task> GetMessagesAroundAsync(ulong around, int limit = 100) => this.GetMessagesInternalAsync(limit, null, null, around); /// /// Returns a list of messages from the last message in the channel. /// The amount of messages to fetch. /// /// Thrown when the client does not have the permission. /// Thrown when the channel does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task> GetMessagesAsync(int limit = 100) => this.GetMessagesInternalAsync(limit, null, null, null); /// /// Returns a list of messages /// /// How many messages should be returned. /// Get messages before snowflake. /// Get messages after snowflake. /// Get messages around snowflake. private async Task> GetMessagesInternalAsync(int limit = 100, ulong? before = null, ulong? after = null, ulong? around = null) { if (!this.IsWriteable()) throw new ArgumentException("Cannot get the messages of a non-text channel."); if (limit < 0) throw new ArgumentException("Cannot get a negative number of messages."); if (limit == 0) return Array.Empty(); //return this.Discord.ApiClient.GetChannelMessagesAsync(this.Id, limit, before, after, around); if (limit > 100 && around != null) throw new InvalidOperationException("Cannot get more than 100 messages around the specified ID."); var msgs = new List(limit); var remaining = limit; ulong? last = null; var isAfter = after != null; int lastCount; do { var fetchSize = remaining > 100 ? 100 : remaining; var fetch = await this.Discord.ApiClient.GetChannelMessagesAsync(this.Id, fetchSize, !isAfter ? last ?? before : null, isAfter ? last ?? after : null, around).ConfigureAwait(false); lastCount = fetch.Count; remaining -= lastCount; if (!isAfter) { msgs.AddRange(fetch); last = fetch.LastOrDefault()?.Id; } else { msgs.InsertRange(0, fetch); last = fetch.FirstOrDefault()?.Id; } } while (remaining > 0 && lastCount > 0); return new ReadOnlyCollection(msgs); } /// /// Deletes multiple messages if they are less than 14 days old. If they are older, none of the messages will be deleted and you will receive a error. /// /// A collection of messages to delete. /// Reason for audit logs. /// Thrown when the client does not have the permission. /// Thrown when the channel does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public async Task DeleteMessagesAsync(IEnumerable messages, string reason = null) { // don't enumerate more than once var msgs = messages.Where(x => x.Channel.Id == this.Id).Select(x => x.Id).ToArray(); if (messages == null || !msgs.Any()) throw new ArgumentException("You need to specify at least one message to delete."); if (msgs.Count() < 2) { await this.Discord.ApiClient.DeleteMessageAsync(this.Id, msgs.Single(), reason).ConfigureAwait(false); return; } for (var i = 0; i < msgs.Count(); i += 100) await this.Discord.ApiClient.DeleteMessagesAsync(this.Id, msgs.Skip(i).Take(100), reason).ConfigureAwait(false); } /// /// Deletes a message /// /// The message to be deleted. /// Reason for audit logs. /// Thrown when the client does not have the permission. /// Thrown when the channel does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task DeleteMessageAsync(DiscordMessage message, string reason = null) => this.Discord.ApiClient.DeleteMessageAsync(this.Id, message.Id, reason); /// /// Returns a list of invite objects /// /// Thrown when the client does not have the permission. /// Thrown when the channel does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task> GetInvitesAsync() { return this.Guild == null ? throw new ArgumentException("Cannot get the invites of a channel that does not belong to a guild.") : this.Discord.ApiClient.GetChannelInvitesAsync(this.Id); } /// /// Create a new invite object /// - /// Duration of invite in seconds before expiry, or 0 for never. Defaults to 86400. - /// Max number of uses or 0 for unlimited. Defaults to 0 + /// Duration of invite in seconds before expiry, or 0 for never. Defaults to 86400. + /// Max number of uses or 0 for unlimited. Defaults to 0 /// Whether this invite should be temporary. Defaults to false. /// Whether this invite should be unique. Defaults to false. - /// The target type. Defaults to null. - /// The target activity. Defaults to null. - /// The target user id. Defaults to null. + /// The target type. Defaults to null. + /// The target activity. Defaults to null. + /// The target user id. Defaults to null. /// The audit log reason. /// Thrown when the client does not have the permission. /// Thrown when the channel does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. - public Task CreateInviteAsync(int max_age = 86400, int max_uses = 0, bool temporary = false, bool unique = false, TargetType? target_type = null, TargetActivity? target_application = null, ulong? target_user = null, string reason = null) - => this.Discord.ApiClient.CreateChannelInviteAsync(this.Id, max_age, max_uses, target_type, target_application, target_user, temporary, unique, reason); + public Task CreateInviteAsync(int maxAge = 86400, int maxUses = 0, bool temporary = false, bool unique = false, TargetType? targetType = null, TargetActivity? targetApplication = null, ulong? targetUser = null, string reason = null) + => this.Discord.ApiClient.CreateChannelInviteAsync(this.Id, maxAge, maxUses, targetType, targetApplication, targetUser, temporary, unique, reason); #region Stage /// /// Opens a stage. /// /// Topic of the stage. - /// Whether @everyone should be notified. - /// Privacy level of the stage (Defaults to . + /// Whether @everyone should be notified. + /// Privacy level of the stage (Defaults to . /// Audit log reason. /// Stage instance /// Thrown when the client does not have the permission. /// Thrown when the channel does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. - public async Task OpenStageAsync(string topic, bool send_start_notification = false, StagePrivacyLevel privacy_level = StagePrivacyLevel.GuildOnly, string reason = null) - => await this.Discord.ApiClient.CreateStageInstanceAsync(this.Id, topic, send_start_notification, privacy_level, reason); + public async Task OpenStageAsync(string topic, bool sendStartNotification = false, StagePrivacyLevel privacyLevel = StagePrivacyLevel.GuildOnly, string reason = null) + => await this.Discord.ApiClient.CreateStageInstanceAsync(this.Id, topic, sendStartNotification, privacyLevel, reason); /// /// Modifies a stage topic. /// /// New topic of the stage. - /// New privacy level of the stage. + /// New privacy level of the stage. /// Audit log reason. /// Thrown when the client does not have the permission. /// Thrown when the channel does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. - public async Task ModifyStageAsync(Optional topic, Optional privacy_level, string reason = null) - => await this.Discord.ApiClient.ModifyStageInstanceAsync(this.Id, topic, privacy_level, reason); + public async Task ModifyStageAsync(Optional topic, Optional privacyLevel, string reason = null) + => await this.Discord.ApiClient.ModifyStageInstanceAsync(this.Id, topic, privacyLevel, reason); /// /// Closes a stage. /// /// Audit log reason. /// Thrown when the client does not have the permission. /// Thrown when the channel does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public async Task CloseStageAsync(string reason = null) => await this.Discord.ApiClient.DeleteStageInstanceAsync(this.Id, reason); /// /// Gets a stage. /// /// The requested stage. /// Thrown when the client does not have the or permission. /// Thrown when the channel does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public async Task GetStageAsync() => await this.Discord.ApiClient.GetStageInstanceAsync(this.Id); #endregion #region Threads /// /// Creates a thread. /// Depending on whether it is created inside an or an it is either an or an . /// Depending on whether the is set to it is either an or an (default). /// /// The name of the thread. - /// till it gets archived. Defaults to . + /// till it gets archived. Defaults to . /// Can be either an , or an . - /// The per user ratelimit, aka slowdown. + /// The per user ratelimit, aka slowdown. /// Audit log reason. /// The created thread. /// Thrown when the client does not have the or or if creating a private thread the permission. /// Thrown when the guild hasn't enabled threads atm. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. /// Thrown when the cannot be modified. This happens, when the guild hasn't reached a certain boost . Or if is not enabled for guild. This happens, if the guild does not have - public async Task CreateThreadAsync(string name, ThreadAutoArchiveDuration auto_archive_duration = ThreadAutoArchiveDuration.OneHour, ChannelType type = ChannelType.PublicThread, int? rate_limit_per_user = null, string reason = null) + public async Task CreateThreadAsync(string name, ThreadAutoArchiveDuration autoArchiveDuration = ThreadAutoArchiveDuration.OneHour, ChannelType type = ChannelType.PublicThread, int? rateLimitPerUser = null, string reason = null) { return (type != ChannelType.NewsThread && type != ChannelType.PublicThread && type != ChannelType.PrivateThread) ? throw new NotSupportedException("Wrong thread type given.") : (!this.IsThreadHolder()) ? throw new NotSupportedException("Parent channel can't have threads.") : type == ChannelType.PrivateThread ? Utilities.CheckThreadPrivateFeature(this.Guild) - ? Utilities.CheckThreadAutoArchiveDurationFeature(this.Guild, auto_archive_duration) - ? await this.Discord.ApiClient.CreateThreadWithoutMessageAsync(this.Id, name, auto_archive_duration, type, rate_limit_per_user, reason) - : throw new NotSupportedException($"Cannot modify ThreadAutoArchiveDuration. Guild needs boost tier {(auto_archive_duration == ThreadAutoArchiveDuration.ThreeDays ? "one" : "two")}.") + ? Utilities.CheckThreadAutoArchiveDurationFeature(this.Guild, autoArchiveDuration) + ? await this.Discord.ApiClient.CreateThreadWithoutMessageAsync(this.Id, name, autoArchiveDuration, type, rateLimitPerUser, reason) + : throw new NotSupportedException($"Cannot modify ThreadAutoArchiveDuration. Guild needs boost tier {(autoArchiveDuration == ThreadAutoArchiveDuration.ThreeDays ? "one" : "two")}.") : throw new NotSupportedException($"Cannot create a private thread. Guild needs to be boost tier two.") - : Utilities.CheckThreadAutoArchiveDurationFeature(this.Guild, auto_archive_duration) - ? await this.Discord.ApiClient.CreateThreadWithoutMessageAsync(this.Id, name, auto_archive_duration, this.Type == ChannelType.News ? ChannelType.NewsThread : ChannelType.PublicThread, rate_limit_per_user, reason) - : throw new NotSupportedException($"Cannot modify ThreadAutoArchiveDuration. Guild needs boost tier {(auto_archive_duration == ThreadAutoArchiveDuration.ThreeDays ? "one" : "two")}."); + : Utilities.CheckThreadAutoArchiveDurationFeature(this.Guild, autoArchiveDuration) + ? await this.Discord.ApiClient.CreateThreadWithoutMessageAsync(this.Id, name, autoArchiveDuration, this.Type == ChannelType.News ? ChannelType.NewsThread : ChannelType.PublicThread, rateLimitPerUser, reason) + : throw new NotSupportedException($"Cannot modify ThreadAutoArchiveDuration. Guild needs boost tier {(autoArchiveDuration == ThreadAutoArchiveDuration.ThreeDays ? "one" : "two")}."); } /// /// Creates a scheduled event based on the channel type. /// /// The name. /// The scheduled start time. /// The description. /// The reason. /// A scheduled event. /// Thrown when the ressource does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public async Task CreateScheduledEventAsync(string name, DateTimeOffset scheduledStartTime, string description = null, string reason = null) { if (!this.IsVoiceJoinable()) throw new NotSupportedException("Cannot create a scheduled event for this type of channel. Channel type must be either voice or stage."); var type = this.Type == ChannelType.Voice ? ScheduledEventEntityType.Voice : ScheduledEventEntityType.StageInstance; return await this.Guild.CreateScheduledEventAsync(name, scheduledStartTime, null, this, null, description, type, reason); } /// /// Gets joined archived private threads. Can contain more threads. /// If the result's value 'HasMore' is true, you need to recall this function to get older threads. /// /// Get threads created before this thread id. /// Defines the limit of returned . /// Thrown when the client does not have the permission. /// Thrown when the channel does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public async Task GetJoinedPrivateArchivedThreadsAsync(ulong? before, int? limit) => await this.Discord.ApiClient.GetJoinedPrivateArchivedThreadsAsync(this.Id, before, limit); /// /// Gets archived public threads. Can contain more threads. /// If the result's value 'HasMore' is true, you need to recall this function to get older threads. /// /// Get threads created before this thread id. /// Defines the limit of returned . /// Thrown when the client does not have the permission. /// Thrown when the channel does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public async Task GetPublicArchivedThreadsAsync(ulong? before, int? limit) => await this.Discord.ApiClient.GetPublicArchivedThreadsAsync(this.Id, before, limit); /// /// Gets archived private threads. Can contain more threads. /// If the result's value 'HasMore' is true, you need to recall this function to get older threads. /// /// Get threads created before this thread id. /// Defines the limit of returned . /// Thrown when the client does not have the or permission. /// Thrown when the channel does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public async Task GetPrivateArchivedThreadsAsync(ulong? before, int? limit) => await this.Discord.ApiClient.GetPrivateArchivedThreadsAsync(this.Id, before, limit); #endregion /// /// Adds a channel permission overwrite for specified role. /// /// The role to have the permission added. /// The permissions to allow. /// The permissions to deny. /// Reason for audit logs. /// Thrown when the client does not have the permission. /// Thrown when the channel does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task AddOverwriteAsync(DiscordRole role, Permissions allow = Permissions.None, Permissions deny = Permissions.None, string reason = null) => this.Discord.ApiClient.EditChannelPermissionsAsync(this.Id, role.Id, allow, deny, "role", reason); /// /// Adds a channel permission overwrite for specified member. /// /// The member to have the permission added. /// The permissions to allow. /// The permissions to deny. /// Reason for audit logs. /// Thrown when the client does not have the permission. /// Thrown when the channel does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task AddOverwriteAsync(DiscordMember member, Permissions allow = Permissions.None, Permissions deny = Permissions.None, string reason = null) => this.Discord.ApiClient.EditChannelPermissionsAsync(this.Id, member.Id, allow, deny, "member", reason); /// /// Deletes a channel permission overwrite for specified member. /// /// The member to have the permission deleted. /// Reason for audit logs. /// Thrown when the client does not have the permission. /// Thrown when the channel does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task DeleteOverwriteAsync(DiscordMember member, string reason = null) => this.Discord.ApiClient.DeleteChannelPermissionAsync(this.Id, member.Id, reason); /// /// Deletes a channel permission overwrite for specified role. /// /// The role to have the permission deleted. /// Reason for audit logs. /// Thrown when the client does not have the permission. /// Thrown when the channel does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task DeleteOverwriteAsync(DiscordRole role, string reason = null) => this.Discord.ApiClient.DeleteChannelPermissionAsync(this.Id, role.Id, reason); /// /// Post a typing indicator /// /// Thrown when the channel does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task TriggerTypingAsync() { return !this.IsWriteable() ? throw new ArgumentException("Cannot start typing in a non-text channel.") : this.Discord.ApiClient.TriggerTypingAsync(this.Id); } /// /// Returns all pinned messages /// /// Thrown when the client does not have the permission. /// Thrown when the channel does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task> GetPinnedMessagesAsync() { return !this.IsWriteable() ? throw new ArgumentException("A non-text channel does not have pinned messages.") : this.Discord.ApiClient.GetPinnedMessagesAsync(this.Id); } /// /// Create a new webhook /// /// The name of the webhook. /// The image for the default webhook avatar. /// Reason for audit logs. /// Thrown when the client does not have the permission. /// Thrown when the channel does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public async Task CreateWebhookAsync(string name, Optional avatar = default, string reason = null) { var av64 = Optional.FromNoValue(); if (avatar.HasValue && avatar.Value != null) using (var imgtool = new ImageTool(avatar.Value)) av64 = imgtool.GetBase64(); else if (avatar.HasValue) av64 = null; return await this.Discord.ApiClient.CreateWebhookAsync(this.Id, name, av64, reason).ConfigureAwait(false); } /// /// Returns a list of webhooks /// /// Thrown when the client does not have the permission. /// Thrown when the channel does not exist. /// Thrown when Discord is unable to process the request. public Task> GetWebhooksAsync() => this.Discord.ApiClient.GetChannelWebhooksAsync(this.Id); /// /// Moves a member to this voice channel /// /// The member to be moved. /// Thrown when the client does not have the permission. /// Thrown when the channel does not exists or if the Member does not exists. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public async Task PlaceMemberAsync(DiscordMember member) { if (!this.IsVoiceJoinable()) throw new ArgumentException("Cannot place a member in a non-voice channel."); await this.Discord.ApiClient.ModifyGuildMemberAsync(this.Guild.Id, member.Id, default, default, default, default, this.Id, null).ConfigureAwait(false); } /// /// Follows a news channel /// /// Channel to crosspost messages to /// Thrown when trying to follow a non-news channel /// Thrown when the current user doesn't have on the target channel public Task FollowAsync(DiscordChannel targetChannel) { return this.Type != ChannelType.News ? throw new ArgumentException("Cannot follow a non-news channel.") : this.Discord.ApiClient.FollowChannelAsync(this.Id, targetChannel.Id); } /// /// Publishes a message in a news channel to following channels /// /// Message to publish /// Thrown when the message has already been crossposted /// /// Thrown when the current user doesn't have and/or /// public Task CrosspostMessageAsync(DiscordMessage message) { return (message.Flags & MessageFlags.Crossposted) == MessageFlags.Crossposted ? throw new ArgumentException("Message is already crossposted.") : this.Discord.ApiClient.CrosspostMessageAsync(this.Id, message.Id); } /// /// Updates the current user's suppress state in this channel, if stage channel. /// /// Toggles the suppress state. /// Sets the time the user requested to speak. /// Thrown when the channel is not a stage channel. public async Task UpdateCurrentUserVoiceStateAsync(bool? suppress, DateTimeOffset? requestToSpeakTimestamp = null) { if (this.Type != ChannelType.Stage) throw new ArgumentException("Voice state can only be updated in a stage channel."); await this.Discord.ApiClient.UpdateCurrentUserVoiceStateAsync(this.GuildId.Value, this.Id, suppress, requestToSpeakTimestamp).ConfigureAwait(false); } /// /// Calculates permissions for a given member. /// /// Member to calculate permissions for. /// Calculated permissions for a given member. public Permissions PermissionsFor(DiscordMember mbr) { // future note: might be able to simplify @everyone role checks to just check any role ... but I'm not sure // xoxo, ~uwx // // you should use a single tilde // ~emzi // user > role > everyone // allow > deny > undefined // => // user allow > user deny > role allow > role deny > everyone allow > everyone deny // thanks to meew0 if (this.IsPrivate || this.Guild == null) return Permissions.None; if (this.Guild.OwnerId == mbr.Id) - return PermissionMethods.FULL_PERMS; + return PermissionMethods.FullPerms; Permissions perms; // assign @everyone permissions var everyoneRole = this.Guild.EveryoneRole; perms = everyoneRole.Permissions; // roles that member is in var mbRoles = mbr.Roles.Where(xr => xr.Id != everyoneRole.Id).ToArray(); // assign permissions from member's roles (in order) perms |= mbRoles.Aggregate(Permissions.None, (c, role) => c | role.Permissions); // Adminstrator grants all permissions and cannot be overridden if ((perms & Permissions.Administrator) == Permissions.Administrator) - return PermissionMethods.FULL_PERMS; + return PermissionMethods.FullPerms; // channel overrides for roles that member is in var mbRoleOverrides = mbRoles - .Select(xr => this._permissionOverwrites.FirstOrDefault(xo => xo.Id == xr.Id)) + .Select(xr => this.PermissionOverwritesInternal.FirstOrDefault(xo => xo.Id == xr.Id)) .Where(xo => xo != null) .ToList(); // assign channel permission overwrites for @everyone pseudo-role - var everyoneOverwrites = this._permissionOverwrites.FirstOrDefault(xo => xo.Id == everyoneRole.Id); + var everyoneOverwrites = this.PermissionOverwritesInternal.FirstOrDefault(xo => xo.Id == everyoneRole.Id); if (everyoneOverwrites != null) { perms &= ~everyoneOverwrites.Denied; perms |= everyoneOverwrites.Allowed; } // assign channel permission overwrites for member's roles (explicit deny) perms &= ~mbRoleOverrides.Aggregate(Permissions.None, (c, overs) => c | overs.Denied); // assign channel permission overwrites for member's roles (explicit allow) perms |= mbRoleOverrides.Aggregate(Permissions.None, (c, overs) => c | overs.Allowed); // channel overrides for just this member - var mbOverrides = this._permissionOverwrites.FirstOrDefault(xo => xo.Id == mbr.Id); + var mbOverrides = this.PermissionOverwritesInternal.FirstOrDefault(xo => xo.Id == mbr.Id); if (mbOverrides == null) return perms; // assign channel permission overwrites for just this member perms &= ~mbOverrides.Denied; perms |= mbOverrides.Allowed; return perms; } /// /// Returns a string representation of this channel. /// /// String representation of this channel. public override string ToString() { return this.Type == ChannelType.Category ? $"Channel Category {this.Name} ({this.Id})" : this.Type == ChannelType.Text || this.Type == ChannelType.News || this.IsThread() ? $"Channel #{this.Name} ({this.Id})" : this.IsVoiceJoinable() ? $"Channel #!{this.Name} ({this.Id})" : !string.IsNullOrWhiteSpace(this.Name) ? $"Channel {this.Name} ({this.Id})" : $"Channel {this.Id}"; } #endregion /// /// Checks whether this is equal to another object. /// /// Object to compare to. /// Whether the object is equal to this . public override bool Equals(object obj) => this.Equals(obj as DiscordChannel); /// /// Checks whether this is equal to another . /// /// to compare to. /// Whether the is equal to this . public bool Equals(DiscordChannel e) => e is not null && (ReferenceEquals(this, e) || this.Id == e.Id); /// /// Gets the hash code for this . /// /// The hash code for this . public override int GetHashCode() => this.Id.GetHashCode(); /// /// Gets whether the two objects are equal. /// /// First channel to compare. /// Second channel to compare. /// Whether the two channels are equal. public static bool operator ==(DiscordChannel e1, DiscordChannel e2) { var o1 = e1 as object; var o2 = e2 as object; return (o1 != null || o2 == null) && (o1 == null || o2 != null) && ((o1 == null && o2 == null) || e1.Id == e2.Id); } /// /// Gets whether the two objects are not equal. /// /// First channel to compare. /// Second channel to compare. /// Whether the two channels are not equal. public static bool operator !=(DiscordChannel e1, DiscordChannel e2) => !(e1 == e2); } } diff --git a/DisCatSharp/Entities/Channel/DiscordDmChannel.cs b/DisCatSharp/Entities/Channel/DiscordDmChannel.cs index 2113759af..6790ff328 100644 --- a/DisCatSharp/Entities/Channel/DiscordDmChannel.cs +++ b/DisCatSharp/Entities/Channel/DiscordDmChannel.cs @@ -1,91 +1,91 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System.Collections.Generic; using System.Globalization; using System.Threading.Tasks; using DisCatSharp.Enums; using DisCatSharp.Net; using Newtonsoft.Json; namespace DisCatSharp.Entities { /// /// Represents a direct message channel. /// public class DiscordDmChannel : DiscordChannel { /// /// Gets the recipients of this direct message. /// [JsonProperty("recipients", NullValueHandling = NullValueHandling.Ignore)] public IReadOnlyList Recipients { get; internal set; } /// /// Gets the hash of this channel's icon. /// [JsonProperty("icon", NullValueHandling = NullValueHandling.Ignore)] public string IconHash { get; internal set; } /// /// Gets the id of this direct message's creator. /// [JsonProperty("owner_id", NullValueHandling = NullValueHandling.Ignore)] public ulong OwnerId { get; internal set; } /// /// Gets the application id of the direct message's creator if it a bot. /// [JsonProperty("application_id", NullValueHandling = NullValueHandling.Ignore)] public ulong ApplicationId { get; internal set; } /// /// Gets the URL of this channel's icon. /// [JsonIgnore] public string IconUrl => !string.IsNullOrWhiteSpace(this.IconHash) ? $"{DiscordDomain.GetDomain(CoreDomain.DiscordCdn).Url}{Endpoints.CHANNEL_ICONS}/{this.Id.ToString(CultureInfo.InvariantCulture)}/{this.IconHash}.png" : null; /// /// Only use for Group DMs! Whitelisted bots only. Requires user's oauth2 access token /// /// The id of the user to add. /// The OAuth2 access token. /// The nickname to give to the user. /// Thrown when the channel does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. - public Task AddDmRecipientAsync(ulong user_id, string accesstoken, string nickname) - => this.Discord.ApiClient.AddGroupDmRecipientAsync(this.Id, user_id, accesstoken, nickname); + public Task AddDmRecipientAsync(ulong userId, string accesstoken, string nickname) + => this.Discord.ApiClient.AddGroupDmRecipientAsync(this.Id, userId, accesstoken, nickname); /// /// Only use for Group DMs! /// /// The id of the User to remove. /// The OAuth2 access token. /// Thrown when the channel does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. - public Task RemoveDmRecipientAsync(ulong user_id, string accesstoken) - => this.Discord.ApiClient.RemoveGroupDmRecipientAsync(this.Id, user_id); + public Task RemoveDmRecipientAsync(ulong userId, string accesstoken) + => this.Discord.ApiClient.RemoveGroupDmRecipientAsync(this.Id, userId); } } diff --git a/DisCatSharp/Entities/Channel/Overwrite/DiscordOverwrite.cs b/DisCatSharp/Entities/Channel/Overwrite/DiscordOverwrite.cs index 7f339c0ff..606062e3e 100644 --- a/DisCatSharp/Entities/Channel/Overwrite/DiscordOverwrite.cs +++ b/DisCatSharp/Entities/Channel/Overwrite/DiscordOverwrite.cs @@ -1,124 +1,124 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System; using System.Threading.Tasks; using Newtonsoft.Json; namespace DisCatSharp.Entities { /// /// Represents a permission overwrite for a channel. /// public class DiscordOverwrite : SnowflakeObject { /// /// Gets the type of the overwrite. Either "role" or "member". /// [JsonProperty("type", NullValueHandling = NullValueHandling.Ignore)] public OverwriteType Type { get; internal set; } /// /// Gets the allowed permission set. /// [JsonProperty("allow", NullValueHandling = NullValueHandling.Ignore)] public Permissions Allowed { get; internal set; } /// /// Gets the denied permission set. /// [JsonProperty("deny", NullValueHandling = NullValueHandling.Ignore)] public Permissions Denied { get; internal set; } [JsonIgnore] - internal ulong _channel_id; + internal ulong ChannelId; #region Methods /// /// Deletes this channel overwrite. /// /// Reason as to why this overwrite gets deleted. /// - public Task DeleteAsync(string reason = null) => this.Discord.ApiClient.DeleteChannelPermissionAsync(this._channel_id, this.Id, reason); + public Task DeleteAsync(string reason = null) => this.Discord.ApiClient.DeleteChannelPermissionAsync(this.ChannelId, this.Id, reason); /// /// Updates this channel overwrite. /// /// Permissions that are allowed. /// Permissions that are denied. /// Reason as to why you made this change. /// /// Thrown when the client does not have the permission. /// Thrown when the overwrite does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task UpdateAsync(Permissions? allow = null, Permissions? deny = null, string reason = null) - => this.Discord.ApiClient.EditChannelPermissionsAsync(this._channel_id, this.Id, allow ?? this.Allowed, deny ?? this.Denied, this.Type.ToString().ToLowerInvariant(), reason); + => this.Discord.ApiClient.EditChannelPermissionsAsync(this.ChannelId, this.Id, allow ?? this.Allowed, deny ?? this.Denied, this.Type.ToString().ToLowerInvariant(), reason); #endregion /// /// Gets the DiscordMember that is affected by this overwrite. /// /// The DiscordMember that is affected by this overwrite /// Thrown when the client does not have the permission. /// Thrown when the overwrite does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public async Task GetMemberAsync() { return this.Type != OverwriteType.Member ? throw new ArgumentException(nameof(this.Type), "This overwrite is for a role, not a member.") - : await (await this.Discord.ApiClient.GetChannelAsync(this._channel_id).ConfigureAwait(false)).Guild.GetMemberAsync(this.Id).ConfigureAwait(false); + : await (await this.Discord.ApiClient.GetChannelAsync(this.ChannelId).ConfigureAwait(false)).Guild.GetMemberAsync(this.Id).ConfigureAwait(false); } /// /// Gets the DiscordRole that is affected by this overwrite. /// /// The DiscordRole that is affected by this overwrite /// Thrown when the role does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public async Task GetRoleAsync() { return this.Type != OverwriteType.Role ? throw new ArgumentException(nameof(this.Type), "This overwrite is for a member, not a role.") - : (await this.Discord.ApiClient.GetChannelAsync(this._channel_id).ConfigureAwait(false)).Guild.GetRole(this.Id); + : (await this.Discord.ApiClient.GetChannelAsync(this.ChannelId).ConfigureAwait(false)).Guild.GetRole(this.Id); } /// /// Initializes a new instance of the class. /// internal DiscordOverwrite() { } /// /// Checks whether given permissions are allowed, denied, or not set. /// /// Permissions to check. /// Whether given permissions are allowed, denied, or not set. public PermissionLevel CheckPermission(Permissions permission) { return (this.Allowed & permission) != 0 ? PermissionLevel.Allowed : (this.Denied & permission) != 0 ? PermissionLevel.Denied : PermissionLevel.Unset; } } } diff --git a/DisCatSharp/Entities/Color/DiscordColor.cs b/DisCatSharp/Entities/Color/DiscordColor.cs index ac5e0c691..c7d8bab43 100644 --- a/DisCatSharp/Entities/Color/DiscordColor.cs +++ b/DisCatSharp/Entities/Color/DiscordColor.cs @@ -1,130 +1,130 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System; using System.Globalization; using System.Linq; namespace DisCatSharp.Entities { /// /// Represents a color used in Discord API. /// public partial struct DiscordColor { - private static char[] HexAlphabet = new[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; + private static char[] s_hexAlphabet = new[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; /// /// Gets the integer representation of this color. /// public int Value { get; } /// /// Gets the red component of this color as an 8-bit integer. /// public byte R => (byte)((this.Value >> 16) & 0xFF); /// /// Gets the green component of this color as an 8-bit integer. /// public byte G => (byte)((this.Value >> 8) & 0xFF); /// /// Gets the blue component of this color as an 8-bit integer. /// public byte B => (byte)(this.Value & 0xFF); /// /// Creates a new color with specified value. /// /// Value of the color. public DiscordColor(int color) { this.Value = color; } /// /// Creates a new color with specified values for red, green, and blue components. /// /// Value of the red component. /// Value of the green component. /// Value of the blue component. public DiscordColor(byte r, byte g, byte b) { this.Value = (r << 16) | (g << 8) | b; } /// /// Creates a new color with specified values for red, green, and blue components. /// /// Value of the red component. /// Value of the green component. /// Value of the blue component. public DiscordColor(float r, float g, float b) { if (r < 0 || r > 1 || g < 0 || g > 1 || b < 0 || b > 1) throw new ArgumentOutOfRangeException("Each component must be between 0.0 and 1.0 inclusive."); var rb = (byte)(r * 255); var gb = (byte)(g * 255); var bb = (byte)(b * 255); this.Value = (rb << 16) | (gb << 8) | bb; } /// /// Creates a new color from specified string representation. /// /// String representation of the color. Must be 6 hexadecimal characters, optionally with # prefix. public DiscordColor(string color) { if (string.IsNullOrWhiteSpace(color)) throw new ArgumentNullException(nameof(color), "Null or empty values are not allowed!"); if (color.Length != 6 && color.Length != 7) throw new ArgumentException(nameof(color), "Color must be 6 or 7 characters in length."); color = color.ToUpper(); if (color.Length == 7 && color[0] != '#') throw new ArgumentException(nameof(color), "7-character colors must begin with #."); else if (color.Length == 7) color = color[1..]; - if (color.Any(xc => !HexAlphabet.Contains(xc))) + if (color.Any(xc => !s_hexAlphabet.Contains(xc))) throw new ArgumentException(nameof(color), "Colors must consist of hexadecimal characters only."); this.Value = int.Parse(color, NumberStyles.HexNumber, CultureInfo.InvariantCulture); } /// /// Gets a string representation of this color. /// /// String representation of this color. public override string ToString() => $"#{this.Value:X6}"; public static implicit operator DiscordColor(int value) => new(value); } } diff --git a/DisCatSharp/Entities/DCS/DisCatSharpTeamMember.cs b/DisCatSharp/Entities/DCS/DisCatSharpTeamMember.cs index 70d58ea74..adfe63094 100644 --- a/DisCatSharp/Entities/DCS/DisCatSharpTeamMember.cs +++ b/DisCatSharp/Entities/DCS/DisCatSharpTeamMember.cs @@ -1,91 +1,91 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System.Globalization; using DisCatSharp.Enums; using DisCatSharp.Net; namespace DisCatSharp.Entities { /// /// Represents a DisCatSharp team member. /// public sealed class DisCatSharpTeamMember : SnowflakeObject { /// /// Gets this user's username. /// public string Username { get; internal set; } /// /// Gets the user's 4-digit discriminator. /// public string Discriminator { get; internal set; } /// /// Gets the discriminator integer. /// internal int DiscriminatorInt => int.Parse(this.Discriminator, NumberStyles.Integer, CultureInfo.InvariantCulture); /// /// Gets the user's banner color, if set. Mutually exclusive with . /// public DiscordColor? BannerColor - => !this._bannerColor.HasValue ? null : new DiscordColor(this._bannerColor.Value); + => !this.BannerColorInternal.HasValue ? null : new DiscordColor(this.BannerColorInternal.Value); - internal int? _bannerColor; + internal int? BannerColorInternal; /// /// Gets the user's banner url /// public string BannerUrl => string.IsNullOrWhiteSpace(this.BannerHash) ? null : $"{DiscordDomain.GetDomain(CoreDomain.DiscordCdn).Url}{Endpoints.BANNERS}/{this.Id.ToString(CultureInfo.InvariantCulture)}/{this.BannerHash}.{(this.BannerHash.StartsWith("a_") ? "gif" : "png")}?size=4096"; /// /// Gets the user's profile banner hash. Mutually exclusive with . /// public string BannerHash { get; internal set; } /// /// Gets the user's avatar hash. /// public string AvatarHash { get; internal set; } /// /// Gets the user's avatar URL. /// public string AvatarUrl => string.IsNullOrWhiteSpace(this.AvatarHash) ? this.DefaultAvatarUrl : $"{DiscordDomain.GetDomain(CoreDomain.DiscordCdn).Url}{Endpoints.AVATARS}/{this.Id.ToString(CultureInfo.InvariantCulture)}/{this.AvatarHash}.{(this.AvatarHash.StartsWith("a_") ? "gif" : "png")}?size=1024"; /// /// Gets the URL of default avatar for this user. /// public string DefaultAvatarUrl => $"{DiscordDomain.GetDomain(CoreDomain.DiscordCdn).Url}{Endpoints.EMBED}{Endpoints.AVATARS}/{(this.DiscriminatorInt % 5).ToString(CultureInfo.InvariantCulture)}.png?size=1024"; /// /// Initializes a new instance of the class. /// internal DisCatSharpTeamMember() { } } } diff --git a/DisCatSharp/Entities/Embed/DiscordEmbed.cs b/DisCatSharp/Entities/Embed/DiscordEmbed.cs index 83b4c44fc..60d4c484e 100644 --- a/DisCatSharp/Entities/Embed/DiscordEmbed.cs +++ b/DisCatSharp/Entities/Embed/DiscordEmbed.cs @@ -1,126 +1,126 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System; using System.Collections.Generic; using Newtonsoft.Json; namespace DisCatSharp.Entities { /// /// Represents a discord embed. /// public sealed class DiscordEmbed { /// /// Gets the embed's title. /// [JsonProperty("title", NullValueHandling = NullValueHandling.Ignore)] public string Title { get; internal set; } /// /// Gets the embed's type. /// [JsonProperty("type", NullValueHandling = NullValueHandling.Ignore)] public string Type { get; internal set; } /// /// Gets the embed's description. /// [JsonProperty("description", NullValueHandling = NullValueHandling.Ignore)] public string Description { get; internal set; } /// /// Gets the embed's url. /// [JsonProperty("url", NullValueHandling = NullValueHandling.Ignore)] public Uri Url { get; internal set; } /// /// Gets the embed's timestamp. /// [JsonProperty("timestamp", NullValueHandling = NullValueHandling.Ignore)] public DateTimeOffset? Timestamp { get; internal set; } /// /// Gets the embed's color. /// [JsonIgnore] public Optional Color => this._colorLazy.Value; [JsonProperty("color", NullValueHandling = NullValueHandling.Include)] - internal Optional _color; + internal Optional ColorInternal; [JsonIgnore] private readonly Lazy> _colorLazy; /// /// Gets the embed's footer. /// [JsonProperty("footer", NullValueHandling = NullValueHandling.Ignore)] public DiscordEmbedFooter Footer { get; internal set; } /// /// Gets the embed's image. /// [JsonProperty("image", NullValueHandling = NullValueHandling.Ignore)] public DiscordEmbedImage Image { get; internal set; } /// /// Gets the embed's thumbnail. /// [JsonProperty("thumbnail", NullValueHandling = NullValueHandling.Ignore)] public DiscordEmbedThumbnail Thumbnail { get; internal set; } /// /// Gets the embed's video. /// [JsonProperty("video", NullValueHandling = NullValueHandling.Ignore)] public DiscordEmbedVideo Video { get; internal set; } /// /// Gets the embed's provider. /// [JsonProperty("provider", NullValueHandling = NullValueHandling.Ignore)] public DiscordEmbedProvider Provider { get; internal set; } /// /// Gets the embed's author. /// [JsonProperty("author", NullValueHandling = NullValueHandling.Ignore)] public DiscordEmbedAuthor Author { get; internal set; } /// /// Gets the embed's fields. /// [JsonProperty("fields", NullValueHandling = NullValueHandling.Ignore)] public IReadOnlyList Fields { get; internal set; } /// /// Initializes a new instance of the class. /// internal DiscordEmbed() { - this._colorLazy = new Lazy>(() => this._color.HasValue ? Optional.FromValue(this._color.Value) : Optional.FromNoValue()); + this._colorLazy = new Lazy>(() => this.ColorInternal.HasValue ? Optional.FromValue(this.ColorInternal.Value) : Optional.FromNoValue()); } } } diff --git a/DisCatSharp/Entities/Embed/DiscordEmbedBuilder.cs b/DisCatSharp/Entities/Embed/DiscordEmbedBuilder.cs index fa7212ca3..0e789fad8 100644 --- a/DisCatSharp/Entities/Embed/DiscordEmbedBuilder.cs +++ b/DisCatSharp/Entities/Embed/DiscordEmbedBuilder.cs @@ -1,616 +1,616 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using DisCatSharp.Net; namespace DisCatSharp.Entities { /// /// Constructs embeds. /// public sealed class DiscordEmbedBuilder { /// /// Gets or sets the embed's title. /// public string Title { get => this._title; set { if (value != null && value.Length > 256) throw new ArgumentException("Title length cannot exceed 256 characters.", nameof(value)); this._title = value; } } private string _title; /// /// Gets or sets the embed's description. /// public string Description { get => this._description; set { if (value != null && value.Length > 4096) throw new ArgumentException("Description length cannot exceed 4096 characters.", nameof(value)); this._description = value; } } private string _description; /// /// Gets or sets the url for the embed's title. /// public string Url { get => this._url?.ToString(); set => this._url = string.IsNullOrEmpty(value) ? null : new Uri(value); } private Uri _url; /// /// Gets or sets the embed's color. /// public Optional Color { get; set; } /// /// Gets or sets the embed's timestamp. /// public DateTimeOffset? Timestamp { get; set; } /// /// Gets or sets the embed's image url. /// public string ImageUrl { get => this._imageUri?.ToString(); set => this._imageUri = string.IsNullOrEmpty(value) ? null : new DiscordUri(value); } private DiscordUri _imageUri; /// /// Gets or sets the embed's author. /// public EmbedAuthor Author { get; set; } /// /// Gets or sets the embed's footer. /// public EmbedFooter Footer { get; set; } /// /// Gets or sets the embed's thumbnail. /// public EmbedThumbnail Thumbnail { get; set; } /// /// Gets the embed's fields. /// public IReadOnlyList Fields { get; } private readonly List _fields = new(); /// /// Constructs a new empty embed builder. /// public DiscordEmbedBuilder() { this.Fields = new ReadOnlyCollection(this._fields); } /// /// Constructs a new embed builder using another embed as prototype. /// /// Embed to use as prototype. public DiscordEmbedBuilder(DiscordEmbed original) : this() { this.Title = original.Title; this.Description = original.Description; this.Url = original.Url?.ToString(); this.ImageUrl = original.Image?.Url?.ToString(); this.Color = original.Color; this.Timestamp = original.Timestamp; if (original.Thumbnail != null) this.Thumbnail = new EmbedThumbnail { Url = original.Thumbnail.Url?.ToString(), Height = original.Thumbnail.Height, Width = original.Thumbnail.Width }; if (original.Author != null) this.Author = new EmbedAuthor { IconUrl = original.Author.IconUrl?.ToString(), Name = original.Author.Name, Url = original.Author.Url?.ToString() }; if (original.Footer != null) this.Footer = new EmbedFooter { IconUrl = original.Footer.IconUrl?.ToString(), Text = original.Footer.Text }; if (original.Fields?.Any() == true) this._fields.AddRange(original.Fields); while (this._fields.Count > 25) this._fields.RemoveAt(this._fields.Count - 1); } /// /// Sets the embed's title. /// /// Title to set. /// This embed builder. public DiscordEmbedBuilder WithTitle(string title) { this.Title = title; return this; } /// /// Sets the embed's description. /// /// Description to set. /// This embed builder. public DiscordEmbedBuilder WithDescription(string description) { this.Description = description; return this; } /// /// Sets the embed's title url. /// /// Title url to set. /// This embed builder. public DiscordEmbedBuilder WithUrl(string url) { this.Url = url; return this; } /// /// Sets the embed's title url. /// /// Title url to set. /// This embed builder. public DiscordEmbedBuilder WithUrl(Uri url) { this._url = url; return this; } /// /// Sets the embed's color. /// /// Embed color to set. /// This embed builder. public DiscordEmbedBuilder WithColor(DiscordColor color) { this.Color = color; return this; } /// /// Sets the embed's timestamp. /// /// Timestamp to set. /// This embed builder. public DiscordEmbedBuilder WithTimestamp(DateTimeOffset? timestamp) { this.Timestamp = timestamp; return this; } /// /// Sets the embed's timestamp. /// /// Timestamp to set. /// This embed builder. public DiscordEmbedBuilder WithTimestamp(DateTime? timestamp) { this.Timestamp = timestamp == null ? null : (DateTimeOffset?)new DateTimeOffset(timestamp.Value); return this; } /// /// Sets the embed's timestamp based on a snowflake. /// /// Snowflake to calculate timestamp from. /// This embed builder. public DiscordEmbedBuilder WithTimestamp(ulong snowflake) { this.Timestamp = new DateTimeOffset(2015, 1, 1, 0, 0, 0, TimeSpan.Zero).AddMilliseconds(snowflake >> 22); return this; } /// /// Sets the embed's image url. /// /// Image url to set. /// This embed builder. public DiscordEmbedBuilder WithImageUrl(string url) { this.ImageUrl = url; return this; } /// /// Sets the embed's image url. /// /// Image url to set. /// This embed builder. public DiscordEmbedBuilder WithImageUrl(Uri url) { this._imageUri = new DiscordUri(url); return this; } /// /// Sets the embed's thumbnail. /// /// Thumbnail url to set. /// The height of the thumbnail to set. /// The width of the thumbnail to set. /// This embed builder. public DiscordEmbedBuilder WithThumbnail(string url, int height = 0, int width = 0) { this.Thumbnail = new EmbedThumbnail { Url = url, Height = height, Width = width }; return this; } /// /// Sets the embed's thumbnail. /// /// Thumbnail url to set. /// The height of the thumbnail to set. /// The width of the thumbnail to set. /// This embed builder. public DiscordEmbedBuilder WithThumbnail(Uri url, int height = 0, int width = 0) { this.Thumbnail = new EmbedThumbnail { - _uri = new DiscordUri(url), + Uri = new DiscordUri(url), Height = height, Width = width }; return this; } /// /// Sets the embed's author. /// /// Author's name. /// Author's url. /// Author icon's url. /// This embed builder. public DiscordEmbedBuilder WithAuthor(string name = null, string url = null, string iconUrl = null) { if (!string.IsNullOrEmpty(name) && name.Length > 256) throw new NotSupportedException("Embed author name can not exceed 256 chars. See https://discord.com/developers/docs/resources/channel#embed-limits."); this.Author = string.IsNullOrEmpty(name) && string.IsNullOrEmpty(url) && string.IsNullOrEmpty(iconUrl) ? null : new EmbedAuthor { Name = name, Url = url, IconUrl = iconUrl }; return this; } /// /// Sets the embed's footer. /// /// Footer's text. /// Footer icon's url. /// This embed builder. public DiscordEmbedBuilder WithFooter(string text = null, string iconUrl = null) { if (text != null && text.Length > 2048) throw new ArgumentException("Footer text length cannot exceed 2048 characters.", nameof(text)); this.Footer = string.IsNullOrEmpty(text) && string.IsNullOrEmpty(iconUrl) ? null : new EmbedFooter { Text = text, IconUrl = iconUrl }; return this; } /// /// Adds a field to this embed. /// /// Name of the field to add. /// Value of the field to add. /// Whether the field is to be inline or not. /// This embed builder. public DiscordEmbedBuilder AddField(string name, string value, bool inline = false) { if (string.IsNullOrWhiteSpace(name)) { if (name == null) throw new ArgumentNullException(nameof(name)); throw new ArgumentException("Name cannot be empty or whitespace.", nameof(name)); } if (string.IsNullOrWhiteSpace(value)) { if (value == null) throw new ArgumentNullException(nameof(value)); throw new ArgumentException("Value cannot be empty or whitespace.", nameof(value)); } if (name.Length > 256) throw new ArgumentException("Embed field name length cannot exceed 256 characters."); if (value.Length > 1024) throw new ArgumentException("Embed field value length cannot exceed 1024 characters."); if (this._fields.Count >= 25) throw new InvalidOperationException("Cannot add more than 25 fields."); this._fields.Add(new DiscordEmbedField { Inline = inline, Name = name, Value = value }); return this; } /// /// Removes a field of the specified index from this embed. /// /// Index of the field to remove. /// This embed builder. public DiscordEmbedBuilder RemoveFieldAt(int index) { this._fields.RemoveAt(index); return this; } /// /// Removes fields of the specified range from this embed. /// /// Index of the first field to remove. /// Number of fields to remove. /// This embed builder. public DiscordEmbedBuilder RemoveFieldRange(int index, int count) { this._fields.RemoveRange(index, count); return this; } /// /// Removes all fields from this embed. /// /// This embed builder. public DiscordEmbedBuilder ClearFields() { this._fields.Clear(); return this; } /// /// Constructs a new embed from data supplied to this builder. /// /// New discord embed. public DiscordEmbed Build() { var embed = new DiscordEmbed { Title = this._title, Description = this._description, Url = this._url, - _color = this.Color.IfPresent(e => e.Value), + ColorInternal = this.Color.IfPresent(e => e.Value), Timestamp = this.Timestamp }; if (this.Footer != null) embed.Footer = new DiscordEmbedFooter { Text = this.Footer.Text, - IconUrl = this.Footer._iconUri + IconUrl = this.Footer.IconUri }; if (this.Author != null) embed.Author = new DiscordEmbedAuthor { Name = this.Author.Name, - Url = this.Author._uri, - IconUrl = this.Author._iconUri + Url = this.Author.Uri, + IconUrl = this.Author.IconUri }; if (this._imageUri != null) embed.Image = new DiscordEmbedImage { Url = this._imageUri }; if (this.Thumbnail != null) embed.Thumbnail = new DiscordEmbedThumbnail { - Url = this.Thumbnail._uri, + Url = this.Thumbnail.Uri, Height = this.Thumbnail.Height, Width = this.Thumbnail.Width }; embed.Fields = new ReadOnlyCollection(new List(this._fields)); // copy the list, don't wrap it, prevents mutation - var char_count = 0; + var charCount = 0; if (embed.Fields.Any()) { foreach (var field in embed.Fields) { - char_count += field.Name.Length; - char_count += field.Value.Length; + charCount += field.Name.Length; + charCount += field.Value.Length; } } if (embed.Author != null && !string.IsNullOrEmpty(embed.Author.Name)) - char_count += embed.Author.Name.Length; + charCount += embed.Author.Name.Length; if (embed.Footer != null && !string.IsNullOrEmpty(embed.Footer.Text)) - char_count += embed.Footer.Text.Length; + charCount += embed.Footer.Text.Length; if (!string.IsNullOrEmpty(embed.Title)) - char_count += embed.Title.Length; + charCount += embed.Title.Length; if (!string.IsNullOrEmpty(embed.Description)) - char_count += embed.Description.Length; + charCount += embed.Description.Length; - return char_count >= 6000 + return charCount >= 6000 ? throw new NotSupportedException("Total char count can not exceed 6000 chars. See https://discord.com/developers/docs/resources/channel#embed-limits.") : embed; } /// /// Implicitly converts this builder to an embed. /// /// Builder to convert. public static implicit operator DiscordEmbed(DiscordEmbedBuilder builder) => builder?.Build(); /// /// Represents an embed author. /// public class EmbedAuthor { /// /// Gets or sets the name of the author. /// public string Name { get => this._name; set { if (value != null && value.Length > 256) throw new ArgumentException("Author name length cannot exceed 256 characters.", nameof(value)); this._name = value; } } private string _name; /// /// Gets or sets the Url to which the author's link leads. /// public string Url { - get => this._uri?.ToString(); - set => this._uri = string.IsNullOrEmpty(value) ? null : new Uri(value); + get => this.Uri?.ToString(); + set => this.Uri = string.IsNullOrEmpty(value) ? null : new Uri(value); } - internal Uri _uri; + internal Uri Uri; /// /// Gets or sets the Author's icon url. /// public string IconUrl { - get => this._iconUri?.ToString(); - set => this._iconUri = string.IsNullOrEmpty(value) ? null : new DiscordUri(value); + get => this.IconUri?.ToString(); + set => this.IconUri = string.IsNullOrEmpty(value) ? null : new DiscordUri(value); } - internal DiscordUri _iconUri; + internal DiscordUri IconUri; } /// /// Represents an embed footer. /// public class EmbedFooter { /// /// Gets or sets the text of the footer. /// public string Text { get => this._text; set { if (value != null && value.Length > 2048) throw new ArgumentException("Footer text length cannot exceed 2048 characters.", nameof(value)); this._text = value; } } private string _text; /// /// Gets or sets the Url /// public string IconUrl { - get => this._iconUri?.ToString(); - set => this._iconUri = string.IsNullOrEmpty(value) ? null : new DiscordUri(value); + get => this.IconUri?.ToString(); + set => this.IconUri = string.IsNullOrEmpty(value) ? null : new DiscordUri(value); } - internal DiscordUri _iconUri; + internal DiscordUri IconUri; } /// /// Represents an embed thumbnail. /// public class EmbedThumbnail { /// /// Gets or sets the thumbnail's image url. /// public string Url { - get => this._uri?.ToString(); - set => this._uri = string.IsNullOrEmpty(value) ? null : new DiscordUri(value); + get => this.Uri?.ToString(); + set => this.Uri = string.IsNullOrEmpty(value) ? null : new DiscordUri(value); } - internal DiscordUri _uri; + internal DiscordUri Uri; /// /// Gets or sets the thumbnail's height. /// public int Height { get => this._height; set => this._height = value >= 0 ? value : 0; } private int _height; /// /// Gets or sets the thumbnail's width. /// public int Width { get => this._width; set => this._width = value >= 0 ? value : 0; } private int _width; } } } diff --git a/DisCatSharp/Entities/Emoji/DiscordEmoji.cs b/DisCatSharp/Entities/Emoji/DiscordEmoji.cs index 4d4c9f22a..a49547705 100644 --- a/DisCatSharp/Entities/Emoji/DiscordEmoji.cs +++ b/DisCatSharp/Entities/Emoji/DiscordEmoji.cs @@ -1,380 +1,380 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Globalization; using System.Linq; using DisCatSharp.Enums; using DisCatSharp.Net; using Newtonsoft.Json; namespace DisCatSharp.Entities { /// /// Represents a Discord emoji. /// public partial class DiscordEmoji : SnowflakeObject, IEquatable { /// /// Gets the name of this emoji. /// [JsonProperty("name", NullValueHandling = NullValueHandling.Ignore)] public string Name { get; internal set; } /// /// Gets IDs the roles this emoji is enabled for. /// [JsonIgnore] public IReadOnlyList Roles => this._rolesLazy.Value; [JsonProperty("roles", NullValueHandling = NullValueHandling.Ignore)] - internal List _roles; + public List RolesInternal; private readonly Lazy> _rolesLazy; /// /// Gets whether this emoji requires colons to use. /// [JsonProperty("require_colons")] public bool RequiresColons { get; internal set; } /// /// Gets whether this emoji is managed by an integration. /// [JsonProperty("managed")] public bool IsManaged { get; internal set; } /// /// Gets whether this emoji is animated. /// [JsonProperty("animated")] public bool IsAnimated { get; internal set; } /// /// Gets whether the emoji is available for use. /// An emoji may not be available due to loss of server boost. /// [JsonProperty("available", NullValueHandling = NullValueHandling.Ignore)] public bool IsAvailable { get; internal set; } /// /// Gets the image URL of this emoji. /// [JsonIgnore] public string Url { get { return this.Id == 0 ? throw new InvalidOperationException("Cannot get URL of unicode emojis.") : this.IsAnimated ? $"{DiscordDomain.GetDomain(CoreDomain.DiscordCdn).Url}{Endpoints.EMOJIS}/{this.Id.ToString(CultureInfo.InvariantCulture)}.gif" : $"{DiscordDomain.GetDomain(CoreDomain.DiscordCdn).Url}{Endpoints.EMOJIS}/{this.Id.ToString(CultureInfo.InvariantCulture)}.png"; } } /// /// Initializes a new instance of the class. /// internal DiscordEmoji() { - this._rolesLazy = new Lazy>(() => new ReadOnlyCollection(this._roles)); + this._rolesLazy = new Lazy>(() => new ReadOnlyCollection(this.RolesInternal)); } /// /// Gets emoji's name in non-Unicode format (eg. :thinking: instead of the Unicode representation of the emoji). /// public string GetDiscordName() { DiscordNameLookup.TryGetValue(this.Name, out var name); return name ?? $":{ this.Name }:"; } /// /// Returns a string representation of this emoji. /// /// String representation of this emoji. public override string ToString() { return this.Id != 0 ? this.IsAnimated ? $"" : $"<:{this.Name}:{this.Id.ToString(CultureInfo.InvariantCulture)}>" : 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 DiscordEmoji); /// /// Checks whether this is equal to another . /// /// to compare to. /// Whether the is equal to this . public bool Equals(DiscordEmoji e) => e is not null && (ReferenceEquals(this, e) || (this.Id == e.Id && this.Name == e.Name)); /// /// Gets the hash code for this . /// /// The hash code for this . public override int GetHashCode() { var hash = 13; hash = (hash * 7) + this.Id.GetHashCode(); hash = (hash * 7) + this.Name.GetHashCode(); return hash; } /// /// Gets the reactions string. /// internal string ToReactionString() => this.Id != 0 ? $"{this.Name}:{this.Id.ToString(CultureInfo.InvariantCulture)}" : this.Name; /// /// Gets whether the two objects are equal. /// /// First emoji to compare. /// Second emoji to compare. /// Whether the two emoji are equal. public static bool operator ==(DiscordEmoji e1, DiscordEmoji 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 emoji to compare. /// Second emoji to compare. /// Whether the two emoji are not equal. public static bool operator !=(DiscordEmoji e1, DiscordEmoji e2) => !(e1 == e2); /// /// Implicitly converts this emoji to its string representation. /// /// Emoji to convert. public static implicit operator string(DiscordEmoji e1) => e1.ToString(); /// /// Checks whether specified unicode entity is a valid unicode emoji. /// /// Entity to check. /// Whether it's a valid emoji. public static bool IsValidUnicode(string unicodeEntity) => DiscordNameLookup.ContainsKey(unicodeEntity); /// /// Creates an emoji object from a unicode entity. /// /// to attach to the object. /// Unicode entity to create the object from. /// Create object. public static DiscordEmoji FromUnicode(BaseDiscordClient client, string unicodeEntity) { return !IsValidUnicode(unicodeEntity) ? throw new ArgumentException("Specified unicode entity is not a valid unicode emoji.", nameof(unicodeEntity)) : new DiscordEmoji { Name = unicodeEntity, Discord = client }; } /// /// Creates an emoji object from a unicode entity. /// /// Unicode entity to create the object from. /// Create object. public static DiscordEmoji FromUnicode(string unicodeEntity) => FromUnicode(null, unicodeEntity); /// /// Attempts to create an emoji object from a unicode entity. /// /// to attach to the object. /// Unicode entity to create the object from. /// Resulting object. /// Whether the operation was successful. public static bool TryFromUnicode(BaseDiscordClient client, string unicodeEntity, out DiscordEmoji emoji) { // this is a round-trip operation because of FE0F inconsistencies. // through this, the inconsistency is normalized. emoji = null; if (!DiscordNameLookup.TryGetValue(unicodeEntity, out var discordName)) return false; if (!UnicodeEmojis.TryGetValue(discordName, out unicodeEntity)) return false; emoji = new DiscordEmoji { Name = unicodeEntity, Discord = client }; return true; } /// /// Attempts to create an emoji object from a unicode entity. /// /// Unicode entity to create the object from. /// Resulting object. /// Whether the operation was successful. public static bool TryFromUnicode(string unicodeEntity, out DiscordEmoji emoji) => TryFromUnicode(null, unicodeEntity, out emoji); /// /// Creates an emoji object from a guild emote. /// /// to attach to the object. /// Id of the emote. /// Create object. public static DiscordEmoji FromGuildEmote(BaseDiscordClient client, ulong id) { if (client == null) throw new ArgumentNullException(nameof(client), "Client cannot be null."); foreach (var guild in client.Guilds.Values) { if (guild.Emojis.TryGetValue(id, out var found)) return found; } throw new KeyNotFoundException("Given emote was not found."); } /// /// Attempts to create an emoji object from a guild emote. /// /// to attach to the object. /// Id of the emote. /// Resulting object. /// Whether the operation was successful. public static bool TryFromGuildEmote(BaseDiscordClient client, ulong id, out DiscordEmoji emoji) { if (client == null) throw new ArgumentNullException(nameof(client), "Client cannot be null."); foreach (var guild in client.Guilds.Values) { if (guild.Emojis.TryGetValue(id, out emoji)) return true; } emoji = null; return false; } /// /// Creates an emoji obejct from emote name that includes colons (eg. :thinking:). This method also supports /// skin tone variations (eg. :ok_hand::skin-tone-2:), standard emoticons (eg. :D), as well as guild emoji /// (still specified by :name:). /// /// to attach to the object. /// Name of the emote to find, including colons (eg. :thinking:). /// Should guild emojis be included in the search. /// Create object. public static DiscordEmoji FromName(BaseDiscordClient client, string name, bool includeGuilds = true) { if (client == null) throw new ArgumentNullException(nameof(client), "Client cannot be null."); if (string.IsNullOrWhiteSpace(name)) throw new ArgumentNullException(nameof(name), "Name cannot be empty or null."); if (UnicodeEmojis.TryGetValue(name, out var unicodeEntity)) return new DiscordEmoji { Discord = client, Name = unicodeEntity }; if (includeGuilds) { var allEmojis = client.Guilds.Values .SelectMany(xg => xg.Emojis.Values); // save cycles - don't order var ek = name.AsSpan().Slice(1, name.Length - 2); foreach (var emoji in allEmojis) if (emoji.Name.AsSpan().SequenceEqual(ek)) return emoji; } throw new ArgumentException("Invalid emoji name specified.", nameof(name)); } /// /// Attempts to create an emoji object from emote name that includes colons (eg. :thinking:). This method also /// supports skin tone variations (eg. :ok_hand::skin-tone-2:), standard emoticons (eg. :D), as well as guild /// emoji (still specified by :name:). /// /// to attach to the object. /// Name of the emote to find, including colons (eg. :thinking:). /// Resulting object. /// Whether the operation was successful. public static bool TryFromName(BaseDiscordClient client, string name, out DiscordEmoji emoji) => TryFromName(client, name, true, out emoji); /// /// Attempts to create an emoji object from emote name that includes colons (eg. :thinking:). This method also /// supports skin tone variations (eg. :ok_hand::skin-tone-2:), standard emoticons (eg. :D), as well as guild /// emoji (still specified by :name:). /// /// to attach to the object. /// Name of the emote to find, including colons (eg. :thinking:). /// Should guild emojis be included in the search. /// Resulting object. /// Whether the operation was successful. public static bool TryFromName(BaseDiscordClient client, string name, bool includeGuilds, out DiscordEmoji emoji) { if (client == null) throw new ArgumentNullException(nameof(client), "Client cannot be null."); if (string.IsNullOrWhiteSpace(name)) throw new ArgumentNullException(nameof(name), "Name cannot be empty or null."); if (UnicodeEmojis.TryGetValue(name, out var unicodeEntity)) { emoji = new DiscordEmoji { Discord = client, Name = unicodeEntity }; return true; } if (includeGuilds) { var allEmojis = client.Guilds.Values .SelectMany(xg => xg.Emojis.Values); // save cycles - don't order var ek = name.AsSpan().Slice(1, name.Length - 2); foreach (var xemoji in allEmojis) if (xemoji.Name.AsSpan().SequenceEqual(ek)) { emoji = xemoji; return true; } } emoji = null; return false; } } } diff --git a/DisCatSharp/Entities/Emoji/DiscordUnicodeEmoji.cs b/DisCatSharp/Entities/Emoji/DiscordUnicodeEmoji.cs index a8d2e04b0..3f2b91d45 100644 --- a/DisCatSharp/Entities/Emoji/DiscordUnicodeEmoji.cs +++ b/DisCatSharp/Entities/Emoji/DiscordUnicodeEmoji.cs @@ -1,2148 +1,2148 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. namespace DisCatSharp.Entities { /// /// Represents a discord unicode emoji. /// public class DiscordUnicodeEmoji { #pragma warning disable ConstFieldDocumentationHeader #pragma warning disable IDE1006 public const string _1 = "\U0001f44e"; public const string _100 = "\U0001f4af"; public const string _1234 = "\U0001f522"; public const string _1SkinTone1 = "\U0001f44e\U0001f3fb"; public const string _1SkinTone2 = "\U0001f44e\U0001f3fc"; public const string _1SkinTone3 = "\U0001f44e\U0001f3fd"; public const string _1SkinTone4 = "\U0001f44e\U0001f3fe"; public const string _1SkinTone5 = "\U0001f44e\U0001f3ff"; public const string _8ball = "\U0001f3b1"; public const string A = "\U0001f170"; - public const string Ab = "\U0001f18e"; - public const string Abc = "\U0001f524"; - public const string Abcd = "\U0001f521"; - public const string Accept = "\U0001f251"; - public const string AdmissionTickets = "\U0001f39f"; - public const string AerialTramway = "\U0001f6a1"; - public const string Airplane = "\U00002708"; - public const string AirplaneArriving = "\U0001f6ec"; - public const string AirplaneDeparture = "\U0001f6eb"; - public const string AirplaneSmall = "\U0001f6e9"; - public const string AlarmClock = "\U000023f0"; - public const string Alembic = "\U00002697"; - public const string Alien = "\U0001f47d"; - public const string Ambulance = "\U0001f691"; - public const string Amphora = "\U0001f3fa"; - public const string Anchor = "\U00002693"; - public const string Angel = "\U0001f47c"; - public const string AngelSkinTone1 = "\U0001f47c\U0001f3fb"; - public const string AngelSkinTone2 = "\U0001f47c\U0001f3fc"; - public const string AngelSkinTone3 = "\U0001f47c\U0001f3fd"; - public const string AngelSkinTone4 = "\U0001f47c\U0001f3fe"; - public const string AngelSkinTone5 = "\U0001f47c\U0001f3ff"; - public const string Anger = "\U0001f4a2"; - public const string AngerRight = "\U0001f5ef"; - public const string Angry = "\U0001f620"; - public const string Anguished = "\U0001f627"; - public const string Ant = "\U0001f41c"; - public const string Apple = "\U0001f34e"; - public const string Aquarius = "\U00002652"; - public const string Archery = "\U0001f3f9"; - public const string Aries = "\U00002648"; - public const string ArrowBackward = "\U000025c0"; - public const string ArrowDoubleDown = "\U000023ec"; - public const string ArrowDoubleUp = "\U000023eb"; - public const string ArrowDown = "\U00002b07"; - public const string ArrowDownSmall = "\U0001f53d"; - public const string ArrowForward = "\U000025b6"; - public const string ArrowHeadingDown = "\U00002935"; - public const string ArrowHeadingUp = "\U00002934"; - public const string ArrowLeft = "\U00002b05"; - public const string ArrowLowerLeft = "\U00002199"; - public const string ArrowLowerRight = "\U00002198"; - public const string ArrowRight = "\U000027a1"; - public const string ArrowRightHook = "\U000021aa"; - public const string ArrowsClockwise = "\U0001f503"; - public const string ArrowsCounterclockwise = "\U0001f504"; - public const string ArrowUp = "\U00002b06"; - public const string ArrowUpDown = "\U00002195"; - public const string ArrowUpperLeft = "\U00002196"; - public const string ArrowUpperRight = "\U00002197"; - public const string ArrowUpSmall = "\U0001f53c"; - public const string Art = "\U0001f3a8"; - public const string ArticulatedLorry = "\U0001f69b"; - public const string Asterisk = "\U0000002a\U000020e3"; - public const string Astonished = "\U0001f632"; - public const string AthleticShoe = "\U0001f45f"; - public const string Atm = "\U0001f3e7"; - public const string Atom = "\U0000269b"; - public const string AtomSymbol = "\U0000269b"; - public const string Avocado = "\U0001f951"; + public const string AB = "\U0001f18e"; + public const string ABC = "\U0001f524"; + public const string ABCD = "\U0001f521"; + public const string ACCEPT = "\U0001f251"; + public const string ADMISSION_TICKETS = "\U0001f39f"; + public const string AERIAL_TRAMWAY = "\U0001f6a1"; + public const string AIRPLANE = "\U00002708"; + public const string AIRPLANE_ARRIVING = "\U0001f6ec"; + public const string AIRPLANE_DEPARTURE = "\U0001f6eb"; + public const string AIRPLANE_SMALL = "\U0001f6e9"; + public const string ALARM_CLOCK = "\U000023f0"; + public const string ALEMBIC = "\U00002697"; + public const string ALIEN = "\U0001f47d"; + public const string AMBULANCE = "\U0001f691"; + public const string AMPHORA = "\U0001f3fa"; + public const string ANCHOR = "\U00002693"; + public const string ANGEL = "\U0001f47c"; + public const string ANGEL_SKIN_TONE1 = "\U0001f47c\U0001f3fb"; + public const string ANGEL_SKIN_TONE2 = "\U0001f47c\U0001f3fc"; + public const string ANGEL_SKIN_TONE3 = "\U0001f47c\U0001f3fd"; + public const string ANGEL_SKIN_TONE4 = "\U0001f47c\U0001f3fe"; + public const string ANGEL_SKIN_TONE5 = "\U0001f47c\U0001f3ff"; + public const string ANGER = "\U0001f4a2"; + public const string ANGER_RIGHT = "\U0001f5ef"; + public const string ANGRY = "\U0001f620"; + public const string ANGUISHED = "\U0001f627"; + public const string ANT = "\U0001f41c"; + public const string APPLE = "\U0001f34e"; + public const string AQUARIUS = "\U00002652"; + public const string ARCHERY = "\U0001f3f9"; + public const string ARIES = "\U00002648"; + public const string ARROW_BACKWARD = "\U000025c0"; + public const string ARROW_DOUBLE_DOWN = "\U000023ec"; + public const string ARROW_DOUBLE_UP = "\U000023eb"; + public const string ARROW_DOWN = "\U00002b07"; + public const string ARROW_DOWN_SMALL = "\U0001f53d"; + public const string ARROW_FORWARD = "\U000025b6"; + public const string ARROW_HEADING_DOWN = "\U00002935"; + public const string ARROW_HEADING_UP = "\U00002934"; + public const string ARROW_LEFT = "\U00002b05"; + public const string ARROW_LOWER_LEFT = "\U00002199"; + public const string ARROW_LOWER_RIGHT = "\U00002198"; + public const string ARROW_RIGHT = "\U000027a1"; + public const string ARROW_RIGHT_HOOK = "\U000021aa"; + public const string ARROWS_CLOCKWISE = "\U0001f503"; + public const string ARROWS_COUNTERCLOCKWISE = "\U0001f504"; + public const string ARROW_UP = "\U00002b06"; + public const string ARROW_UP_DOWN = "\U00002195"; + public const string ARROW_UPPER_LEFT = "\U00002196"; + public const string ARROW_UPPER_RIGHT = "\U00002197"; + public const string ARROW_UP_SMALL = "\U0001f53c"; + public const string ART = "\U0001f3a8"; + public const string ARTICULATED_LORRY = "\U0001f69b"; + public const string ASTERISK = "\U0000002a\U000020e3"; + public const string ASTONISHED = "\U0001f632"; + public const string ATHLETIC_SHOE = "\U0001f45f"; + public const string ATM = "\U0001f3e7"; + public const string ATOM = "\U0000269b"; + public const string ATOM_SYMBOL = "\U0000269b"; + public const string AVOCADO = "\U0001f951"; public const string B = "\U0001f171"; - public const string Baby = "\U0001f476"; - public const string BabyBottle = "\U0001f37c"; - public const string BabyChick = "\U0001f424"; - public const string BabySkinTone1 = "\U0001f476\U0001f3fb"; - public const string BabySkinTone2 = "\U0001f476\U0001f3fc"; - public const string BabySkinTone3 = "\U0001f476\U0001f3fd"; - public const string BabySkinTone4 = "\U0001f476\U0001f3fe"; - public const string BabySkinTone5 = "\U0001f476\U0001f3ff"; - public const string BabySymbol = "\U0001f6bc"; - public const string Back = "\U0001f519"; - public const string BackOfHand = "\U0001f91a"; - public const string BackOfHandSkinTone1 = "\U0001f91a\U0001f3fb"; - public const string BackOfHandSkinTone2 = "\U0001f91a\U0001f3fc"; - public const string BackOfHandSkinTone3 = "\U0001f91a\U0001f3fd"; - public const string BackOfHandSkinTone4 = "\U0001f91a\U0001f3fe"; - public const string BackOfHandSkinTone5 = "\U0001f91a\U0001f3ff"; - public const string Bacon = "\U0001f953"; - public const string Badminton = "\U0001f3f8"; - public const string BaggageClaim = "\U0001f6c4"; - public const string BaguetteBread = "\U0001f956"; - public const string Balloon = "\U0001f388"; - public const string BallotBox = "\U0001f5f3"; - public const string BallotBoxWithBallot = "\U0001f5f3"; - public const string BallotBoxWithCheck = "\U00002611"; - public const string Bamboo = "\U0001f38d"; - public const string Banana = "\U0001f34c"; - public const string Bangbang = "\U0000203c"; - public const string Bank = "\U0001f3e6"; - public const string Barber = "\U0001f488"; - public const string BarChart = "\U0001f4ca"; - public const string Baseball = "\U000026be"; - public const string Basketball = "\U0001f3c0"; - public const string BasketballPlayer = "\U000026f9"; - public const string BasketballPlayerSkinTone1 = "\U000026f9\U0001f3fb"; - public const string BasketballPlayerSkinTone2 = "\U000026f9\U0001f3fc"; - public const string BasketballPlayerSkinTone3 = "\U000026f9\U0001f3fd"; - public const string BasketballPlayerSkinTone4 = "\U000026f9\U0001f3fe"; - public const string BasketballPlayerSkinTone5 = "\U000026f9\U0001f3ff"; - public const string Bat = "\U0001f987"; - public const string Bath = "\U0001f6c0"; - public const string BathSkinTone1 = "\U0001f6c0\U0001f3fb"; - public const string BathSkinTone2 = "\U0001f6c0\U0001f3fc"; - public const string BathSkinTone3 = "\U0001f6c0\U0001f3fd"; - public const string BathSkinTone4 = "\U0001f6c0\U0001f3fe"; - public const string BathSkinTone5 = "\U0001f6c0\U0001f3ff"; - public const string Bathtub = "\U0001f6c1"; - public const string Battery = "\U0001f50b"; - public const string Beach = "\U0001f3d6"; - public const string BeachUmbrella = "\U000026f1"; - public const string BeachWithUmbrella = "\U0001f3d6"; - public const string Bear = "\U0001f43b"; - public const string Bed = "\U0001f6cf"; - public const string Bee = "\U0001f41d"; - public const string Beer = "\U0001f37a"; - public const string Beers = "\U0001f37b"; - public const string Beetle = "\U0001f41e"; - public const string Beginner = "\U0001f530"; - public const string Bell = "\U0001f514"; - public const string Bellhop = "\U0001f6ce"; - public const string BellhopBell = "\U0001f6ce"; - public const string Bento = "\U0001f371"; - public const string Bicyclist = "\U0001f6b4"; - public const string BicyclistSkinTone1 = "\U0001f6b4\U0001f3fb"; - public const string BicyclistSkinTone2 = "\U0001f6b4\U0001f3fc"; - public const string BicyclistSkinTone3 = "\U0001f6b4\U0001f3fd"; - public const string BicyclistSkinTone4 = "\U0001f6b4\U0001f3fe"; - public const string BicyclistSkinTone5 = "\U0001f6b4\U0001f3ff"; - public const string Bike = "\U0001f6b2"; - public const string Bikini = "\U0001f459"; - public const string Biohazard = "\U00002623"; - public const string BiohazardSign = "\U00002623"; - public const string Bird = "\U0001f426"; - public const string Birthday = "\U0001f382"; - public const string BlackCircle = "\U000026ab"; - public const string BlackHeart = "\U0001f5a4"; - public const string BlackJoker = "\U0001f0cf"; - public const string BlackLargeSquare = "\U00002b1b"; - public const string BlackMediumSmallSquare = "\U000025fe"; - public const string BlackMediumSquare = "\U000025fc"; - public const string BlackNib = "\U00002712"; - public const string BlackSmallSquare = "\U000025aa"; - public const string BlackSquareButton = "\U0001f532"; - public const string Blossom = "\U0001f33c"; - public const string Blowfish = "\U0001f421"; - public const string BlueBook = "\U0001f4d8"; - public const string BlueCar = "\U0001f699"; - public const string BlueHeart = "\U0001f499"; - public const string Blush = "\U0001f60a"; - public const string Boar = "\U0001f417"; - public const string Bomb = "\U0001f4a3"; - public const string Book = "\U0001f4d6"; - public const string Bookmark = "\U0001f516"; - public const string BookmarkTabs = "\U0001f4d1"; - public const string Books = "\U0001f4da"; - public const string Boom = "\U0001f4a5"; - public const string Boot = "\U0001f462"; - public const string BottleWithPoppingCork = "\U0001f37e"; - public const string Bouquet = "\U0001f490"; - public const string Bow = "\U0001f647"; - public const string BowAndArrow = "\U0001f3f9"; - public const string Bowling = "\U0001f3b3"; - public const string BowSkinTone1 = "\U0001f647\U0001f3fb"; - public const string BowSkinTone2 = "\U0001f647\U0001f3fc"; - public const string BowSkinTone3 = "\U0001f647\U0001f3fd"; - public const string BowSkinTone4 = "\U0001f647\U0001f3fe"; - public const string BowSkinTone5 = "\U0001f647\U0001f3ff"; - public const string BoxingGlove = "\U0001f94a"; - public const string BoxingGloves = "\U0001f94a"; - public const string Boy = "\U0001f466"; - public const string BoySkinTone1 = "\U0001f466\U0001f3fb"; - public const string BoySkinTone2 = "\U0001f466\U0001f3fc"; - public const string BoySkinTone3 = "\U0001f466\U0001f3fd"; - public const string BoySkinTone4 = "\U0001f466\U0001f3fe"; - public const string BoySkinTone5 = "\U0001f466\U0001f3ff"; - public const string Bread = "\U0001f35e"; - public const string BrideWithVeil = "\U0001f470"; - public const string BrideWithVeilSkinTone1 = "\U0001f470\U0001f3fb"; - public const string BrideWithVeilSkinTone2 = "\U0001f470\U0001f3fc"; - public const string BrideWithVeilSkinTone3 = "\U0001f470\U0001f3fd"; - public const string BrideWithVeilSkinTone4 = "\U0001f470\U0001f3fe"; - public const string BrideWithVeilSkinTone5 = "\U0001f470\U0001f3ff"; - public const string BridgeAtNight = "\U0001f309"; - public const string Briefcase = "\U0001f4bc"; - public const string BrokenHeart = "\U0001f494"; - public const string Bug = "\U0001f41b"; - public const string BuildingConstruction = "\U0001f3d7"; - public const string Bulb = "\U0001f4a1"; - public const string BullettrainFront = "\U0001f685"; - public const string BullettrainSide = "\U0001f684"; - public const string Burrito = "\U0001f32f"; - public const string Bus = "\U0001f68c"; - public const string Busstop = "\U0001f68f"; - public const string BustInSilhouette = "\U0001f464"; - public const string BustsInSilhouette = "\U0001f465"; - public const string Butterfly = "\U0001f98b"; - public const string Cactus = "\U0001f335"; - public const string Cake = "\U0001f370"; - public const string Calendar = "\U0001f4c6"; - public const string CalendarSpiral = "\U0001f5d3"; - public const string Calling = "\U0001f4f2"; - public const string CallMe = "\U0001f919"; - public const string CallMeHand = "\U0001f919"; - public const string CallMeHandSkinTone1 = "\U0001f919\U0001f3fb"; - public const string CallMeHandSkinTone2 = "\U0001f919\U0001f3fc"; - public const string CallMeHandSkinTone3 = "\U0001f919\U0001f3fd"; - public const string CallMeHandSkinTone4 = "\U0001f919\U0001f3fe"; - public const string CallMeHandSkinTone5 = "\U0001f919\U0001f3ff"; - public const string CallMeSkinTone1 = "\U0001f919\U0001f3fb"; - public const string CallMeSkinTone2 = "\U0001f919\U0001f3fc"; - public const string CallMeSkinTone3 = "\U0001f919\U0001f3fd"; - public const string CallMeSkinTone4 = "\U0001f919\U0001f3fe"; - public const string CallMeSkinTone5 = "\U0001f919\U0001f3ff"; - public const string Camel = "\U0001f42b"; - public const string Camera = "\U0001f4f7"; - public const string CameraWithFlash = "\U0001f4f8"; - public const string Camping = "\U0001f3d5"; - public const string Cancer = "\U0000264b"; - public const string Candle = "\U0001f56f"; - public const string Candy = "\U0001f36c"; - public const string Canoe = "\U0001f6f6"; - public const string CapitalAbcd = "\U0001f520"; - public const string Capricorn = "\U00002651"; - public const string CardBox = "\U0001f5c3"; - public const string CardFileBox = "\U0001f5c3"; - public const string CardIndex = "\U0001f4c7"; - public const string CardIndexDividers = "\U0001f5c2"; - public const string CarouselHorse = "\U0001f3a0"; - public const string Carrot = "\U0001f955"; - public const string Cartwheel = "\U0001f938"; - public const string CartwheelSkinTone1 = "\U0001f938\U0001f3fb"; - public const string CartwheelSkinTone2 = "\U0001f938\U0001f3fc"; - public const string CartwheelSkinTone3 = "\U0001f938\U0001f3fd"; - public const string CartwheelSkinTone4 = "\U0001f938\U0001f3fe"; - public const string CartwheelSkinTone5 = "\U0001f938\U0001f3ff"; - public const string Cat = "\U0001f431"; - public const string Cat2 = "\U0001f408"; - public const string Cd = "\U0001f4bf"; - public const string Chains = "\U000026d3"; - public const string Champagne = "\U0001f37e"; - public const string ChampagneGlass = "\U0001f942"; - public const string Chart = "\U0001f4b9"; - public const string ChartWithDownwardsTrend = "\U0001f4c9"; - public const string ChartWithUpwardsTrend = "\U0001f4c8"; - public const string CheckeredFlag = "\U0001f3c1"; - public const string Cheese = "\U0001f9c0"; - public const string CheeseWedge = "\U0001f9c0"; - public const string Cherries = "\U0001f352"; - public const string CherryBlossom = "\U0001f338"; - public const string Chestnut = "\U0001f330"; - public const string Chicken = "\U0001f414"; - public const string ChildrenCrossing = "\U0001f6b8"; - public const string Chipmunk = "\U0001f43f"; - public const string ChocolateBar = "\U0001f36b"; - public const string ChristmasTree = "\U0001f384"; - public const string Church = "\U000026ea"; - public const string Cinema = "\U0001f3a6"; - public const string CircusTent = "\U0001f3aa"; - public const string CityDusk = "\U0001f306"; - public const string Cityscape = "\U0001f3d9"; - public const string CitySunrise = "\U0001f307"; - public const string CitySunset = "\U0001f307"; - public const string Cl = "\U0001f191"; - public const string Clap = "\U0001f44f"; - public const string Clapper = "\U0001f3ac"; - public const string ClapSkinTone1 = "\U0001f44f\U0001f3fb"; - public const string ClapSkinTone2 = "\U0001f44f\U0001f3fc"; - public const string ClapSkinTone3 = "\U0001f44f\U0001f3fd"; - public const string ClapSkinTone4 = "\U0001f44f\U0001f3fe"; - public const string ClapSkinTone5 = "\U0001f44f\U0001f3ff"; - public const string ClassicalBuilding = "\U0001f3db"; - public const string ClinkingGlass = "\U0001f942"; - public const string Clipboard = "\U0001f4cb"; - public const string Clock = "\U0001f570"; - public const string Clock1 = "\U0001f550"; - public const string Clock10 = "\U0001f559"; - public const string Clock1030 = "\U0001f565"; - public const string Clock11 = "\U0001f55a"; - public const string Clock1130 = "\U0001f566"; - public const string Clock12 = "\U0001f55b"; - public const string Clock1230 = "\U0001f567"; - public const string Clock130 = "\U0001f55c"; - public const string Clock2 = "\U0001f551"; - public const string Clock230 = "\U0001f55d"; - public const string Clock3 = "\U0001f552"; - public const string Clock330 = "\U0001f55e"; - public const string Clock4 = "\U0001f553"; - public const string Clock430 = "\U0001f55f"; - public const string Clock5 = "\U0001f554"; - public const string Clock530 = "\U0001f560"; - public const string Clock6 = "\U0001f555"; - public const string Clock630 = "\U0001f561"; - public const string Clock7 = "\U0001f556"; - public const string Clock730 = "\U0001f562"; - public const string Clock8 = "\U0001f557"; - public const string Clock830 = "\U0001f563"; - public const string Clock9 = "\U0001f558"; - public const string Clock930 = "\U0001f564"; - public const string ClosedBook = "\U0001f4d5"; - public const string ClosedLockWithKey = "\U0001f510"; - public const string ClosedUmbrella = "\U0001f302"; - public const string Cloud = "\U00002601"; - public const string CloudLightning = "\U0001f329"; - public const string CloudRain = "\U0001f327"; - public const string CloudSnow = "\U0001f328"; - public const string CloudTornado = "\U0001f32a"; - public const string CloudWithLightning = "\U0001f329"; - public const string CloudWithRain = "\U0001f327"; - public const string CloudWithSnow = "\U0001f328"; - public const string CloudWithTornado = "\U0001f32a"; - public const string Clown = "\U0001f921"; - public const string ClownFace = "\U0001f921"; - public const string Clubs = "\U00002663"; - public const string Cocktail = "\U0001f378"; - public const string Coffee = "\U00002615"; - public const string Coffin = "\U000026b0"; - public const string ColdSweat = "\U0001f630"; - public const string Comet = "\U00002604"; - public const string Compression = "\U0001f5dc"; - public const string Computer = "\U0001f4bb"; - public const string ConfettiBall = "\U0001f38a"; - public const string Confounded = "\U0001f616"; - public const string Confused = "\U0001f615"; - public const string Congratulations = "\U00003297"; - public const string Construction = "\U0001f6a7"; - public const string ConstructionSite = "\U0001f3d7"; - public const string ConstructionWorker = "\U0001f477"; - public const string ConstructionWorkerSkinTone1 = "\U0001f477\U0001f3fb"; - public const string ConstructionWorkerSkinTone2 = "\U0001f477\U0001f3fc"; - public const string ConstructionWorkerSkinTone3 = "\U0001f477\U0001f3fd"; - public const string ConstructionWorkerSkinTone4 = "\U0001f477\U0001f3fe"; - public const string ConstructionWorkerSkinTone5 = "\U0001f477\U0001f3ff"; - public const string ControlKnobs = "\U0001f39b"; - public const string ConvenienceStore = "\U0001f3ea"; - public const string Cookie = "\U0001f36a"; - public const string Cooking = "\U0001f373"; - public const string Cool = "\U0001f192"; - public const string Cop = "\U0001f46e"; - public const string CopSkinTone1 = "\U0001f46e\U0001f3fb"; - public const string CopSkinTone2 = "\U0001f46e\U0001f3fc"; - public const string CopSkinTone3 = "\U0001f46e\U0001f3fd"; - public const string CopSkinTone4 = "\U0001f46e\U0001f3fe"; - public const string CopSkinTone5 = "\U0001f46e\U0001f3ff"; - public const string Copyright = "\U000000a9"; - public const string Corn = "\U0001f33d"; - public const string Couch = "\U0001f6cb"; - public const string CouchAndLamp = "\U0001f6cb"; - public const string Couple = "\U0001f46b"; - public const string Couplekiss = "\U0001f48f"; - public const string CouplekissMm = "\U0001f468\U0000200d\U00002764\U0000fe0f\U0000200d\U0001f48b\U0000200d\U0001f468"; - public const string CouplekissWw = "\U0001f469\U0000200d\U00002764\U0000fe0f\U0000200d\U0001f48b\U0000200d\U0001f469"; - public const string CoupleMm = "\U0001f468\U0000200d\U00002764\U0000fe0f\U0000200d\U0001f468"; - public const string CoupleWithHeart = "\U0001f491"; - public const string CoupleWithHeartMm = "\U0001f468\U0000200d\U00002764\U0000fe0f\U0000200d\U0001f468"; - public const string CoupleWithHeartWw = "\U0001f469\U0000200d\U00002764\U0000fe0f\U0000200d\U0001f469"; - public const string CoupleWw = "\U0001f469\U0000200d\U00002764\U0000fe0f\U0000200d\U0001f469"; - public const string Cow = "\U0001f42e"; - public const string Cow2 = "\U0001f404"; - public const string Cowboy = "\U0001f920"; - public const string Crab = "\U0001f980"; - public const string Crayon = "\U0001f58d"; - public const string CreditCard = "\U0001f4b3"; - public const string CrescentMoon = "\U0001f319"; - public const string Cricket = "\U0001f3cf"; - public const string CricketBatBall = "\U0001f3cf"; - public const string Crocodile = "\U0001f40a"; - public const string Croissant = "\U0001f950"; - public const string Cross = "\U0000271d"; - public const string CrossedFlags = "\U0001f38c"; - public const string CrossedSwords = "\U00002694"; - public const string Crown = "\U0001f451"; - public const string CruiseShip = "\U0001f6f3"; - public const string Cry = "\U0001f622"; - public const string CryingCatFace = "\U0001f63f"; - public const string CrystalBall = "\U0001f52e"; - public const string Cucumber = "\U0001f952"; - public const string Cupid = "\U0001f498"; - public const string CurlyLoop = "\U000027b0"; - public const string CurrencyExchange = "\U0001f4b1"; - public const string Curry = "\U0001f35b"; - public const string Custard = "\U0001f36e"; - public const string Customs = "\U0001f6c3"; - public const string Cyclone = "\U0001f300"; - public const string Dagger = "\U0001f5e1"; - public const string DaggerKnife = "\U0001f5e1"; - public const string Dancer = "\U0001f483"; - public const string Dancers = "\U0001f46f"; - public const string DancerSkinTone1 = "\U0001f483\U0001f3fb"; - public const string DancerSkinTone2 = "\U0001f483\U0001f3fc"; - public const string DancerSkinTone3 = "\U0001f483\U0001f3fd"; - public const string DancerSkinTone4 = "\U0001f483\U0001f3fe"; - public const string DancerSkinTone5 = "\U0001f483\U0001f3ff"; - public const string Dango = "\U0001f361"; - public const string DarkSunglasses = "\U0001f576"; - public const string Dart = "\U0001f3af"; - public const string Dash = "\U0001f4a8"; - public const string Date = "\U0001f4c5"; - public const string DeciduousTree = "\U0001f333"; - public const string Deer = "\U0001f98c"; - public const string DepartmentStore = "\U0001f3ec"; - public const string DerelictHouseBuilding = "\U0001f3da"; - public const string Desert = "\U0001f3dc"; - public const string DesertIsland = "\U0001f3dd"; - public const string Desktop = "\U0001f5a5"; - public const string DesktopComputer = "\U0001f5a5"; - public const string Diamonds = "\U00002666"; - public const string DiamondShapeWithADotInside = "\U0001f4a0"; - public const string Disappointed = "\U0001f61e"; - public const string DisappointedRelieved = "\U0001f625"; - public const string Dividers = "\U0001f5c2"; - public const string Dizzy = "\U0001f4ab"; - public const string DizzyFace = "\U0001f635"; - public const string Dog = "\U0001f436"; - public const string Dog2 = "\U0001f415"; - public const string Dollar = "\U0001f4b5"; - public const string Dolls = "\U0001f38e"; - public const string Dolphin = "\U0001f42c"; - public const string DoNotLitter = "\U0001f6af"; - public const string Door = "\U0001f6aa"; - public const string DoubleVerticalBar = "\U000023f8"; - public const string Doughnut = "\U0001f369"; - public const string Dove = "\U0001f54a"; - public const string DoveOfPeace = "\U0001f54a"; - public const string Dragon = "\U0001f409"; - public const string DragonFace = "\U0001f432"; - public const string Dress = "\U0001f457"; - public const string DromedaryCamel = "\U0001f42a"; - public const string Drool = "\U0001f924"; - public const string DroolingFace = "\U0001f924"; - public const string Droplet = "\U0001f4a7"; - public const string Drum = "\U0001f941"; - public const string DrumWithDrumsticks = "\U0001f941"; - public const string Duck = "\U0001f986"; - public const string Dvd = "\U0001f4c0"; - public const string Eagle = "\U0001f985"; - public const string Ear = "\U0001f442"; - public const string EarOfRice = "\U0001f33e"; - public const string EarSkinTone1 = "\U0001f442\U0001f3fb"; - public const string EarSkinTone2 = "\U0001f442\U0001f3fc"; - public const string EarSkinTone3 = "\U0001f442\U0001f3fd"; - public const string EarSkinTone4 = "\U0001f442\U0001f3fe"; - public const string EarSkinTone5 = "\U0001f442\U0001f3ff"; - public const string EarthAfrica = "\U0001f30d"; - public const string EarthAmericas = "\U0001f30e"; - public const string EarthAsia = "\U0001f30f"; - public const string Egg = "\U0001f95a"; - public const string Eggplant = "\U0001f346"; - public const string Eight = "\U00000038\U000020e3"; - public const string EightPointedBlackStar = "\U00002734"; - public const string EightSpokedAsterisk = "\U00002733"; - public const string Eject = "\U000023cf"; - public const string EjectSymbol = "\U000023cf"; - public const string ElectricPlug = "\U0001f50c"; - public const string Elephant = "\U0001f418"; - public const string EMail = "\U0001f4e7"; - public const string Email = "\U0001f4e7"; - public const string End = "\U0001f51a"; - public const string Envelope = "\U00002709"; - public const string EnvelopeWithArrow = "\U0001f4e9"; - public const string Euro = "\U0001f4b6"; - public const string EuropeanCastle = "\U0001f3f0"; - public const string EuropeanPostOffice = "\U0001f3e4"; - public const string EvergreenTree = "\U0001f332"; - public const string Exclamation = "\U00002757"; - public const string ExpectingWoman = "\U0001f930"; - public const string ExpectingWomanSkinTone1 = "\U0001f930\U0001f3fb"; - public const string ExpectingWomanSkinTone2 = "\U0001f930\U0001f3fc"; - public const string ExpectingWomanSkinTone3 = "\U0001f930\U0001f3fd"; - public const string ExpectingWomanSkinTone4 = "\U0001f930\U0001f3fe"; - public const string ExpectingWomanSkinTone5 = "\U0001f930\U0001f3ff"; - public const string Expressionless = "\U0001f611"; - public const string Eye = "\U0001f441"; - public const string Eyeglasses = "\U0001f453"; - public const string EyeInSpeechBubble = "\U0001f441\U0000200d\U0001f5e8"; - public const string Eyes = "\U0001f440"; - public const string FacePalm = "\U0001f926"; - public const string Facepalm = "\U0001f926"; - public const string FacePalmSkinTone1 = "\U0001f926\U0001f3fb"; - public const string FacepalmSkinTone1 = "\U0001f926\U0001f3fb"; - public const string FacePalmSkinTone2 = "\U0001f926\U0001f3fc"; - public const string FacepalmSkinTone2 = "\U0001f926\U0001f3fc"; - public const string FacePalmSkinTone3 = "\U0001f926\U0001f3fd"; - public const string FacepalmSkinTone3 = "\U0001f926\U0001f3fd"; - public const string FacePalmSkinTone4 = "\U0001f926\U0001f3fe"; - public const string FacepalmSkinTone4 = "\U0001f926\U0001f3fe"; - public const string FacePalmSkinTone5 = "\U0001f926\U0001f3ff"; - public const string FacepalmSkinTone5 = "\U0001f926\U0001f3ff"; - public const string FaceWithCowboyHat = "\U0001f920"; - public const string FaceWithHeadBandage = "\U0001f915"; - public const string FaceWithRollingEyes = "\U0001f644"; - public const string FaceWithThermometer = "\U0001f912"; - public const string Factory = "\U0001f3ed"; - public const string FallenLeaf = "\U0001f342"; - public const string Family = "\U0001f46a"; - public const string FamilyMmb = "\U0001f468\U0000200d\U0001f468\U0000200d\U0001f466"; - public const string FamilyMmbb = "\U0001f468\U0000200d\U0001f468\U0000200d\U0001f466\U0000200d\U0001f466"; - public const string FamilyMmg = "\U0001f468\U0000200d\U0001f468\U0000200d\U0001f467"; - public const string FamilyMmgb = "\U0001f468\U0000200d\U0001f468\U0000200d\U0001f467\U0000200d\U0001f466"; - public const string FamilyMmgg = "\U0001f468\U0000200d\U0001f468\U0000200d\U0001f467\U0000200d\U0001f467"; - public const string FamilyMwbb = "\U0001f468\U0000200d\U0001f469\U0000200d\U0001f466\U0000200d\U0001f466"; - public const string FamilyMwg = "\U0001f468\U0000200d\U0001f469\U0000200d\U0001f467"; - public const string FamilyMwgb = "\U0001f468\U0000200d\U0001f469\U0000200d\U0001f467\U0000200d\U0001f466"; - public const string FamilyMwgg = "\U0001f468\U0000200d\U0001f469\U0000200d\U0001f467\U0000200d\U0001f467"; - public const string FamilyWwb = "\U0001f469\U0000200d\U0001f469\U0000200d\U0001f466"; - public const string FamilyWwbb = "\U0001f469\U0000200d\U0001f469\U0000200d\U0001f466\U0000200d\U0001f466"; - public const string FamilyWwg = "\U0001f469\U0000200d\U0001f469\U0000200d\U0001f467"; - public const string FamilyWwgb = "\U0001f469\U0000200d\U0001f469\U0000200d\U0001f467\U0000200d\U0001f466"; - public const string FamilyWwgg = "\U0001f469\U0000200d\U0001f469\U0000200d\U0001f467\U0000200d\U0001f467"; - public const string FastForward = "\U000023e9"; - public const string Fax = "\U0001f4e0"; - public const string Fearful = "\U0001f628"; - public const string Feet = "\U0001f43e"; - public const string Fencer = "\U0001f93a"; - public const string Fencing = "\U0001f93a"; - public const string FerrisWheel = "\U0001f3a1"; - public const string Ferry = "\U000026f4"; - public const string FieldHockey = "\U0001f3d1"; - public const string FileCabinet = "\U0001f5c4"; - public const string FileFolder = "\U0001f4c1"; - public const string FilmFrames = "\U0001f39e"; - public const string FilmProjector = "\U0001f4fd"; - public const string FingersCrossed = "\U0001f91e"; - public const string FingersCrossedSkinTone1 = "\U0001f91e\U0001f3fb"; - public const string FingersCrossedSkinTone2 = "\U0001f91e\U0001f3fc"; - public const string FingersCrossedSkinTone3 = "\U0001f91e\U0001f3fd"; - public const string FingersCrossedSkinTone4 = "\U0001f91e\U0001f3fe"; - public const string FingersCrossedSkinTone5 = "\U0001f91e\U0001f3ff"; - public const string Fire = "\U0001f525"; - public const string FireEngine = "\U0001f692"; - public const string Fireworks = "\U0001f386"; - public const string FirstPlace = "\U0001f947"; - public const string FirstPlaceMedal = "\U0001f947"; - public const string FirstQuarterMoon = "\U0001f313"; - public const string FirstQuarterMoonWithFace = "\U0001f31b"; - public const string Fish = "\U0001f41f"; - public const string FishCake = "\U0001f365"; - public const string FishingPoleAndFish = "\U0001f3a3"; - public const string Fist = "\U0000270a"; - public const string FistSkinTone1 = "\U0000270a\U0001f3fb"; - public const string FistSkinTone2 = "\U0000270a\U0001f3fc"; - public const string FistSkinTone3 = "\U0000270a\U0001f3fd"; - public const string FistSkinTone4 = "\U0000270a\U0001f3fe"; - public const string FistSkinTone5 = "\U0000270a\U0001f3ff"; - public const string Five = "\U00000035\U000020e3"; - public const string FlagAc = "\U0001f1e6\U0001f1e8"; - public const string FlagAd = "\U0001f1e6\U0001f1e9"; - public const string FlagAe = "\U0001f1e6\U0001f1ea"; - public const string FlagAf = "\U0001f1e6\U0001f1eb"; - public const string FlagAg = "\U0001f1e6\U0001f1ec"; - public const string FlagAi = "\U0001f1e6\U0001f1ee"; - public const string FlagAl = "\U0001f1e6\U0001f1f1"; - public const string FlagAm = "\U0001f1e6\U0001f1f2"; - public const string FlagAo = "\U0001f1e6\U0001f1f4"; - public const string FlagAq = "\U0001f1e6\U0001f1f6"; - public const string FlagAr = "\U0001f1e6\U0001f1f7"; - public const string FlagAs = "\U0001f1e6\U0001f1f8"; - public const string FlagAt = "\U0001f1e6\U0001f1f9"; - public const string FlagAu = "\U0001f1e6\U0001f1fa"; - public const string FlagAw = "\U0001f1e6\U0001f1fc"; - public const string FlagAx = "\U0001f1e6\U0001f1fd"; - public const string FlagAz = "\U0001f1e6\U0001f1ff"; - public const string FlagBa = "\U0001f1e7\U0001f1e6"; - public const string FlagBb = "\U0001f1e7\U0001f1e7"; - public const string FlagBd = "\U0001f1e7\U0001f1e9"; - public const string FlagBe = "\U0001f1e7\U0001f1ea"; - public const string FlagBf = "\U0001f1e7\U0001f1eb"; - public const string FlagBg = "\U0001f1e7\U0001f1ec"; - public const string FlagBh = "\U0001f1e7\U0001f1ed"; - public const string FlagBi = "\U0001f1e7\U0001f1ee"; - public const string FlagBj = "\U0001f1e7\U0001f1ef"; - public const string FlagBl = "\U0001f1e7\U0001f1f1"; - public const string FlagBlack = "\U0001f3f4"; - public const string FlagBm = "\U0001f1e7\U0001f1f2"; - public const string FlagBn = "\U0001f1e7\U0001f1f3"; - public const string FlagBo = "\U0001f1e7\U0001f1f4"; - public const string FlagBq = "\U0001f1e7\U0001f1f6"; - public const string FlagBr = "\U0001f1e7\U0001f1f7"; - public const string FlagBs = "\U0001f1e7\U0001f1f8"; - public const string FlagBt = "\U0001f1e7\U0001f1f9"; - public const string FlagBv = "\U0001f1e7\U0001f1fb"; - public const string FlagBw = "\U0001f1e7\U0001f1fc"; - public const string FlagBy = "\U0001f1e7\U0001f1fe"; - public const string FlagBz = "\U0001f1e7\U0001f1ff"; - public const string FlagCa = "\U0001f1e8\U0001f1e6"; - public const string FlagCc = "\U0001f1e8\U0001f1e8"; - public const string FlagCd = "\U0001f1e8\U0001f1e9"; - public const string FlagCf = "\U0001f1e8\U0001f1eb"; - public const string FlagCg = "\U0001f1e8\U0001f1ec"; - public const string FlagCh = "\U0001f1e8\U0001f1ed"; - public const string FlagCi = "\U0001f1e8\U0001f1ee"; - public const string FlagCk = "\U0001f1e8\U0001f1f0"; - public const string FlagCl = "\U0001f1e8\U0001f1f1"; - public const string FlagCm = "\U0001f1e8\U0001f1f2"; - public const string FlagCn = "\U0001f1e8\U0001f1f3"; - public const string FlagCo = "\U0001f1e8\U0001f1f4"; - public const string FlagCp = "\U0001f1e8\U0001f1f5"; - public const string FlagCr = "\U0001f1e8\U0001f1f7"; - public const string FlagCu = "\U0001f1e8\U0001f1fa"; - public const string FlagCv = "\U0001f1e8\U0001f1fb"; - public const string FlagCw = "\U0001f1e8\U0001f1fc"; - public const string FlagCx = "\U0001f1e8\U0001f1fd"; - public const string FlagCy = "\U0001f1e8\U0001f1fe"; - public const string FlagCz = "\U0001f1e8\U0001f1ff"; - public const string FlagDe = "\U0001f1e9\U0001f1ea"; - public const string FlagDg = "\U0001f1e9\U0001f1ec"; - public const string FlagDj = "\U0001f1e9\U0001f1ef"; - public const string FlagDk = "\U0001f1e9\U0001f1f0"; - public const string FlagDm = "\U0001f1e9\U0001f1f2"; - public const string FlagDo = "\U0001f1e9\U0001f1f4"; - public const string FlagDz = "\U0001f1e9\U0001f1ff"; - public const string FlagEa = "\U0001f1ea\U0001f1e6"; - public const string FlagEc = "\U0001f1ea\U0001f1e8"; - public const string FlagEe = "\U0001f1ea\U0001f1ea"; - public const string FlagEg = "\U0001f1ea\U0001f1ec"; - public const string FlagEh = "\U0001f1ea\U0001f1ed"; - public const string FlagEr = "\U0001f1ea\U0001f1f7"; - public const string FlagEs = "\U0001f1ea\U0001f1f8"; - public const string FlagEt = "\U0001f1ea\U0001f1f9"; - public const string FlagEu = "\U0001f1ea\U0001f1fa"; - public const string FlagFi = "\U0001f1eb\U0001f1ee"; - public const string FlagFj = "\U0001f1eb\U0001f1ef"; - public const string FlagFk = "\U0001f1eb\U0001f1f0"; - public const string FlagFm = "\U0001f1eb\U0001f1f2"; - public const string FlagFo = "\U0001f1eb\U0001f1f4"; - public const string FlagFr = "\U0001f1eb\U0001f1f7"; - public const string FlagGa = "\U0001f1ec\U0001f1e6"; - public const string FlagGb = "\U0001f1ec\U0001f1e7"; - public const string FlagGd = "\U0001f1ec\U0001f1e9"; - public const string FlagGe = "\U0001f1ec\U0001f1ea"; - public const string FlagGf = "\U0001f1ec\U0001f1eb"; - public const string FlagGg = "\U0001f1ec\U0001f1ec"; - public const string FlagGh = "\U0001f1ec\U0001f1ed"; - public const string FlagGi = "\U0001f1ec\U0001f1ee"; - public const string FlagGl = "\U0001f1ec\U0001f1f1"; - public const string FlagGm = "\U0001f1ec\U0001f1f2"; - public const string FlagGn = "\U0001f1ec\U0001f1f3"; - public const string FlagGp = "\U0001f1ec\U0001f1f5"; - public const string FlagGq = "\U0001f1ec\U0001f1f6"; - public const string FlagGr = "\U0001f1ec\U0001f1f7"; - public const string FlagGs = "\U0001f1ec\U0001f1f8"; - public const string FlagGt = "\U0001f1ec\U0001f1f9"; - public const string FlagGu = "\U0001f1ec\U0001f1fa"; - public const string FlagGw = "\U0001f1ec\U0001f1fc"; - public const string FlagGy = "\U0001f1ec\U0001f1fe"; - public const string FlagHk = "\U0001f1ed\U0001f1f0"; - public const string FlagHm = "\U0001f1ed\U0001f1f2"; - public const string FlagHn = "\U0001f1ed\U0001f1f3"; - public const string FlagHr = "\U0001f1ed\U0001f1f7"; - public const string FlagHt = "\U0001f1ed\U0001f1f9"; - public const string FlagHu = "\U0001f1ed\U0001f1fa"; - public const string FlagIc = "\U0001f1ee\U0001f1e8"; - public const string FlagId = "\U0001f1ee\U0001f1e9"; - public const string FlagIe = "\U0001f1ee\U0001f1ea"; - public const string FlagIl = "\U0001f1ee\U0001f1f1"; - public const string FlagIm = "\U0001f1ee\U0001f1f2"; - public const string FlagIn = "\U0001f1ee\U0001f1f3"; - public const string FlagIo = "\U0001f1ee\U0001f1f4"; - public const string FlagIq = "\U0001f1ee\U0001f1f6"; - public const string FlagIr = "\U0001f1ee\U0001f1f7"; - public const string FlagIs = "\U0001f1ee\U0001f1f8"; - public const string FlagIt = "\U0001f1ee\U0001f1f9"; - public const string FlagJe = "\U0001f1ef\U0001f1ea"; - public const string FlagJm = "\U0001f1ef\U0001f1f2"; - public const string FlagJo = "\U0001f1ef\U0001f1f4"; - public const string FlagJp = "\U0001f1ef\U0001f1f5"; - public const string FlagKe = "\U0001f1f0\U0001f1ea"; - public const string FlagKg = "\U0001f1f0\U0001f1ec"; - public const string FlagKh = "\U0001f1f0\U0001f1ed"; - public const string FlagKi = "\U0001f1f0\U0001f1ee"; - public const string FlagKm = "\U0001f1f0\U0001f1f2"; - public const string FlagKn = "\U0001f1f0\U0001f1f3"; - public const string FlagKp = "\U0001f1f0\U0001f1f5"; - public const string FlagKr = "\U0001f1f0\U0001f1f7"; - public const string FlagKw = "\U0001f1f0\U0001f1fc"; - public const string FlagKy = "\U0001f1f0\U0001f1fe"; - public const string FlagKz = "\U0001f1f0\U0001f1ff"; - public const string FlagLa = "\U0001f1f1\U0001f1e6"; - public const string FlagLb = "\U0001f1f1\U0001f1e7"; - public const string FlagLc = "\U0001f1f1\U0001f1e8"; - public const string FlagLi = "\U0001f1f1\U0001f1ee"; - public const string FlagLk = "\U0001f1f1\U0001f1f0"; - public const string FlagLr = "\U0001f1f1\U0001f1f7"; - public const string FlagLs = "\U0001f1f1\U0001f1f8"; - public const string FlagLt = "\U0001f1f1\U0001f1f9"; - public const string FlagLu = "\U0001f1f1\U0001f1fa"; - public const string FlagLv = "\U0001f1f1\U0001f1fb"; - public const string FlagLy = "\U0001f1f1\U0001f1fe"; - public const string FlagMa = "\U0001f1f2\U0001f1e6"; - public const string FlagMc = "\U0001f1f2\U0001f1e8"; - public const string FlagMd = "\U0001f1f2\U0001f1e9"; - public const string FlagMe = "\U0001f1f2\U0001f1ea"; - public const string FlagMf = "\U0001f1f2\U0001f1eb"; - public const string FlagMg = "\U0001f1f2\U0001f1ec"; - public const string FlagMh = "\U0001f1f2\U0001f1ed"; - public const string FlagMk = "\U0001f1f2\U0001f1f0"; - public const string FlagMl = "\U0001f1f2\U0001f1f1"; - public const string FlagMm = "\U0001f1f2\U0001f1f2"; - public const string FlagMn = "\U0001f1f2\U0001f1f3"; - public const string FlagMo = "\U0001f1f2\U0001f1f4"; - public const string FlagMp = "\U0001f1f2\U0001f1f5"; - public const string FlagMq = "\U0001f1f2\U0001f1f6"; - public const string FlagMr = "\U0001f1f2\U0001f1f7"; - public const string FlagMs = "\U0001f1f2\U0001f1f8"; - public const string FlagMt = "\U0001f1f2\U0001f1f9"; - public const string FlagMu = "\U0001f1f2\U0001f1fa"; - public const string FlagMv = "\U0001f1f2\U0001f1fb"; - public const string FlagMw = "\U0001f1f2\U0001f1fc"; - public const string FlagMx = "\U0001f1f2\U0001f1fd"; - public const string FlagMy = "\U0001f1f2\U0001f1fe"; - public const string FlagMz = "\U0001f1f2\U0001f1ff"; - public const string FlagNa = "\U0001f1f3\U0001f1e6"; - public const string FlagNc = "\U0001f1f3\U0001f1e8"; - public const string FlagNe = "\U0001f1f3\U0001f1ea"; - public const string FlagNf = "\U0001f1f3\U0001f1eb"; - public const string FlagNg = "\U0001f1f3\U0001f1ec"; - public const string FlagNi = "\U0001f1f3\U0001f1ee"; - public const string FlagNl = "\U0001f1f3\U0001f1f1"; - public const string FlagNo = "\U0001f1f3\U0001f1f4"; - public const string FlagNp = "\U0001f1f3\U0001f1f5"; - public const string FlagNr = "\U0001f1f3\U0001f1f7"; - public const string FlagNu = "\U0001f1f3\U0001f1fa"; - public const string FlagNz = "\U0001f1f3\U0001f1ff"; - public const string FlagOm = "\U0001f1f4\U0001f1f2"; - public const string FlagPa = "\U0001f1f5\U0001f1e6"; - public const string FlagPe = "\U0001f1f5\U0001f1ea"; - public const string FlagPf = "\U0001f1f5\U0001f1eb"; - public const string FlagPg = "\U0001f1f5\U0001f1ec"; - public const string FlagPh = "\U0001f1f5\U0001f1ed"; - public const string FlagPk = "\U0001f1f5\U0001f1f0"; - public const string FlagPl = "\U0001f1f5\U0001f1f1"; - public const string FlagPm = "\U0001f1f5\U0001f1f2"; - public const string FlagPn = "\U0001f1f5\U0001f1f3"; - public const string FlagPr = "\U0001f1f5\U0001f1f7"; - public const string FlagPs = "\U0001f1f5\U0001f1f8"; - public const string FlagPt = "\U0001f1f5\U0001f1f9"; - public const string FlagPw = "\U0001f1f5\U0001f1fc"; - public const string FlagPy = "\U0001f1f5\U0001f1fe"; - public const string FlagQa = "\U0001f1f6\U0001f1e6"; - public const string FlagRe = "\U0001f1f7\U0001f1ea"; - public const string FlagRo = "\U0001f1f7\U0001f1f4"; - public const string FlagRs = "\U0001f1f7\U0001f1f8"; - public const string FlagRu = "\U0001f1f7\U0001f1fa"; - public const string FlagRw = "\U0001f1f7\U0001f1fc"; - public const string Flags = "\U0001f38f"; - public const string FlagSa = "\U0001f1f8\U0001f1e6"; - public const string FlagSb = "\U0001f1f8\U0001f1e7"; - public const string FlagSc = "\U0001f1f8\U0001f1e8"; - public const string FlagSd = "\U0001f1f8\U0001f1e9"; - public const string FlagSe = "\U0001f1f8\U0001f1ea"; - public const string FlagSg = "\U0001f1f8\U0001f1ec"; - public const string FlagSh = "\U0001f1f8\U0001f1ed"; - public const string FlagSi = "\U0001f1f8\U0001f1ee"; - public const string FlagSj = "\U0001f1f8\U0001f1ef"; - public const string FlagSk = "\U0001f1f8\U0001f1f0"; - public const string FlagSl = "\U0001f1f8\U0001f1f1"; - public const string FlagSm = "\U0001f1f8\U0001f1f2"; - public const string FlagSn = "\U0001f1f8\U0001f1f3"; - public const string FlagSo = "\U0001f1f8\U0001f1f4"; - public const string FlagSr = "\U0001f1f8\U0001f1f7"; - public const string FlagSs = "\U0001f1f8\U0001f1f8"; - public const string FlagSt = "\U0001f1f8\U0001f1f9"; - public const string FlagSv = "\U0001f1f8\U0001f1fb"; - public const string FlagSx = "\U0001f1f8\U0001f1fd"; - public const string FlagSy = "\U0001f1f8\U0001f1fe"; - public const string FlagSz = "\U0001f1f8\U0001f1ff"; - public const string FlagTa = "\U0001f1f9\U0001f1e6"; - public const string FlagTc = "\U0001f1f9\U0001f1e8"; - public const string FlagTd = "\U0001f1f9\U0001f1e9"; - public const string FlagTf = "\U0001f1f9\U0001f1eb"; - public const string FlagTg = "\U0001f1f9\U0001f1ec"; - public const string FlagTh = "\U0001f1f9\U0001f1ed"; - public const string FlagTj = "\U0001f1f9\U0001f1ef"; - public const string FlagTk = "\U0001f1f9\U0001f1f0"; - public const string FlagTl = "\U0001f1f9\U0001f1f1"; - public const string FlagTm = "\U0001f1f9\U0001f1f2"; - public const string FlagTn = "\U0001f1f9\U0001f1f3"; - public const string FlagTo = "\U0001f1f9\U0001f1f4"; - public const string FlagTr = "\U0001f1f9\U0001f1f7"; - public const string FlagTt = "\U0001f1f9\U0001f1f9"; - public const string FlagTv = "\U0001f1f9\U0001f1fb"; - public const string FlagTw = "\U0001f1f9\U0001f1fc"; - public const string FlagTz = "\U0001f1f9\U0001f1ff"; - public const string FlagUa = "\U0001f1fa\U0001f1e6"; - public const string FlagUg = "\U0001f1fa\U0001f1ec"; - public const string FlagUm = "\U0001f1fa\U0001f1f2"; - public const string FlagUs = "\U0001f1fa\U0001f1f8"; - public const string FlagUy = "\U0001f1fa\U0001f1fe"; - public const string FlagUz = "\U0001f1fa\U0001f1ff"; - public const string FlagVa = "\U0001f1fb\U0001f1e6"; - public const string FlagVc = "\U0001f1fb\U0001f1e8"; - public const string FlagVe = "\U0001f1fb\U0001f1ea"; - public const string FlagVg = "\U0001f1fb\U0001f1ec"; - public const string FlagVi = "\U0001f1fb\U0001f1ee"; - public const string FlagVn = "\U0001f1fb\U0001f1f3"; - public const string FlagVu = "\U0001f1fb\U0001f1fa"; - public const string FlagWf = "\U0001f1fc\U0001f1eb"; - public const string FlagWhite = "\U0001f3f3"; - public const string FlagWs = "\U0001f1fc\U0001f1f8"; - public const string FlagXk = "\U0001f1fd\U0001f1f0"; - public const string FlagYe = "\U0001f1fe\U0001f1ea"; - public const string FlagYt = "\U0001f1fe\U0001f1f9"; - public const string FlagZa = "\U0001f1ff\U0001f1e6"; - public const string FlagZm = "\U0001f1ff\U0001f1f2"; - public const string FlagZw = "\U0001f1ff\U0001f1fc"; - public const string Flame = "\U0001f525"; - public const string Flan = "\U0001f36e"; - public const string Flashlight = "\U0001f526"; - public const string FleurDeLis = "\U0000269c"; - public const string FloppyDisk = "\U0001f4be"; - public const string FlowerPlayingCards = "\U0001f3b4"; - public const string Flushed = "\U0001f633"; - public const string Fog = "\U0001f32b"; - public const string Foggy = "\U0001f301"; - public const string Football = "\U0001f3c8"; - public const string Footprints = "\U0001f463"; - public const string ForkAndKnife = "\U0001f374"; - public const string ForkAndKnifeWithPlate = "\U0001f37d"; - public const string ForkKnifePlate = "\U0001f37d"; - public const string Fountain = "\U000026f2"; - public const string Four = "\U00000034\U000020e3"; - public const string FourLeafClover = "\U0001f340"; - public const string Fox = "\U0001f98a"; - public const string FoxFace = "\U0001f98a"; - public const string FramePhoto = "\U0001f5bc"; - public const string FrameWithPicture = "\U0001f5bc"; - public const string Free = "\U0001f193"; - public const string FrenchBread = "\U0001f956"; - public const string FriedShrimp = "\U0001f364"; - public const string Fries = "\U0001f35f"; - public const string Frog = "\U0001f438"; - public const string Frowning = "\U0001f626"; - public const string Frowning2 = "\U00002639"; - public const string Fuelpump = "\U000026fd"; - public const string FullMoon = "\U0001f315"; - public const string FullMoonWithFace = "\U0001f31d"; - public const string FuneralUrn = "\U000026b1"; - public const string GameDie = "\U0001f3b2"; - public const string GayPrideFlag = "\U0001f3f3\U0000fe0f\U0000200d\U0001f308"; - public const string Gear = "\U00002699"; - public const string Gem = "\U0001f48e"; - public const string Gemini = "\U0000264a"; - public const string Ghost = "\U0001f47b"; - public const string Gift = "\U0001f381"; - public const string GiftHeart = "\U0001f49d"; - public const string Girl = "\U0001f467"; - public const string GirlSkinTone1 = "\U0001f467\U0001f3fb"; - public const string GirlSkinTone2 = "\U0001f467\U0001f3fc"; - public const string GirlSkinTone3 = "\U0001f467\U0001f3fd"; - public const string GirlSkinTone4 = "\U0001f467\U0001f3fe"; - public const string GirlSkinTone5 = "\U0001f467\U0001f3ff"; - public const string GlassOfMilk = "\U0001f95b"; - public const string GlobeWithMeridians = "\U0001f310"; - public const string Goal = "\U0001f945"; - public const string GoalNet = "\U0001f945"; - public const string Goat = "\U0001f410"; - public const string Golf = "\U000026f3"; - public const string Golfer = "\U0001f3cc"; - public const string GolferSkinTone1 = "\U0001f3cc\U0001f3fb"; - public const string GolferSkinTone2 = "\U0001f3cc\U0001f3fc"; - public const string GolferSkinTone3 = "\U0001f3cc\U0001f3fd"; - public const string GolferSkinTone4 = "\U0001f3cc\U0001f3fe"; - public const string GolferSkinTone5 = "\U0001f3cc\U0001f3ff"; - public const string Gorilla = "\U0001f98d"; - public const string Grandma = "\U0001f475"; - public const string GrandmaSkinTone1 = "\U0001f475\U0001f3fb"; - public const string GrandmaSkinTone2 = "\U0001f475\U0001f3fc"; - public const string GrandmaSkinTone3 = "\U0001f475\U0001f3fd"; - public const string GrandmaSkinTone4 = "\U0001f475\U0001f3fe"; - public const string GrandmaSkinTone5 = "\U0001f475\U0001f3ff"; - public const string Grapes = "\U0001f347"; - public const string GreenApple = "\U0001f34f"; - public const string GreenBook = "\U0001f4d7"; - public const string GreenHeart = "\U0001f49a"; - public const string GreenSalad = "\U0001f957"; - public const string GreyExclamation = "\U00002755"; - public const string GreyQuestion = "\U00002754"; - public const string Grimacing = "\U0001f62c"; - public const string Grin = "\U0001f601"; - public const string Grinning = "\U0001f600"; - public const string Guardsman = "\U0001f482"; - public const string GuardsmanSkinTone1 = "\U0001f482\U0001f3fb"; - public const string GuardsmanSkinTone2 = "\U0001f482\U0001f3fc"; - public const string GuardsmanSkinTone3 = "\U0001f482\U0001f3fd"; - public const string GuardsmanSkinTone4 = "\U0001f482\U0001f3fe"; - public const string GuardsmanSkinTone5 = "\U0001f482\U0001f3ff"; - public const string Guitar = "\U0001f3b8"; - public const string Gun = "\U0001f52b"; - public const string Haircut = "\U0001f487"; - public const string HaircutSkinTone1 = "\U0001f487\U0001f3fb"; - public const string HaircutSkinTone2 = "\U0001f487\U0001f3fc"; - public const string HaircutSkinTone3 = "\U0001f487\U0001f3fd"; - public const string HaircutSkinTone4 = "\U0001f487\U0001f3fe"; - public const string HaircutSkinTone5 = "\U0001f487\U0001f3ff"; - public const string Hamburger = "\U0001f354"; - public const string Hammer = "\U0001f528"; - public const string HammerAndPick = "\U00002692"; - public const string HammerAndWrench = "\U0001f6e0"; - public const string HammerPick = "\U00002692"; - public const string Hamster = "\U0001f439"; - public const string Handbag = "\U0001f45c"; - public const string Handball = "\U0001f93e"; - public const string HandballSkinTone1 = "\U0001f93e\U0001f3fb"; - public const string HandballSkinTone2 = "\U0001f93e\U0001f3fc"; - public const string HandballSkinTone3 = "\U0001f93e\U0001f3fd"; - public const string HandballSkinTone4 = "\U0001f93e\U0001f3fe"; - public const string HandballSkinTone5 = "\U0001f93e\U0001f3ff"; - public const string Handshake = "\U0001f91d"; - public const string HandSplayed = "\U0001f590"; - public const string HandSplayedSkinTone1 = "\U0001f590\U0001f3fb"; - public const string HandSplayedSkinTone2 = "\U0001f590\U0001f3fc"; - public const string HandSplayedSkinTone3 = "\U0001f590\U0001f3fd"; - public const string HandSplayedSkinTone4 = "\U0001f590\U0001f3fe"; - public const string HandSplayedSkinTone5 = "\U0001f590\U0001f3ff"; - public const string HandWithIndexAndMiddleFingerCrossed = "\U0001f91e"; - public const string HandWithIndexAndMiddleFingerCrossedSkinTone1 = "\U0001f91e\U0001f3fb"; - public const string HandWithIndexAndMiddleFingerCrossedSkinTone2 = "\U0001f91e\U0001f3fc"; - public const string HandWithIndexAndMiddleFingerCrossedSkinTone3 = "\U0001f91e\U0001f3fd"; - public const string HandWithIndexAndMiddleFingerCrossedSkinTone4 = "\U0001f91e\U0001f3fe"; - public const string HandWithIndexAndMiddleFingerCrossedSkinTone5 = "\U0001f91e\U0001f3ff"; - public const string Hankey = "\U0001f4a9"; - public const string Hash = "\U00000023\U000020e3"; - public const string HatchedChick = "\U0001f425"; - public const string HatchingChick = "\U0001f423"; - public const string HeadBandage = "\U0001f915"; - public const string Headphones = "\U0001f3a7"; - public const string HearNoEvil = "\U0001f649"; - public const string Heart = "\U00002764"; - public const string Heartbeat = "\U0001f493"; - public const string HeartDecoration = "\U0001f49f"; - public const string HeartExclamation = "\U00002763"; - public const string HeartEyes = "\U0001f60d"; - public const string HeartEyesCat = "\U0001f63b"; - public const string Heartpulse = "\U0001f497"; - public const string Hearts = "\U00002665"; - public const string HeavyCheckMark = "\U00002714"; - public const string HeavyDivisionSign = "\U00002797"; - public const string HeavyDollarSign = "\U0001f4b2"; - public const string HeavyHeartExclamationMarkOrnament = "\U00002763"; - public const string HeavyMinusSign = "\U00002796"; - public const string HeavyMultiplicationX = "\U00002716"; - public const string HeavyPlusSign = "\U00002795"; - public const string Helicopter = "\U0001f681"; - public const string HelmetWithCross = "\U000026d1"; - public const string HelmetWithWhiteCross = "\U000026d1"; - public const string Herb = "\U0001f33f"; - public const string Hibiscus = "\U0001f33a"; - public const string HighBrightness = "\U0001f506"; - public const string HighHeel = "\U0001f460"; - public const string Hockey = "\U0001f3d2"; - public const string Hole = "\U0001f573"; - public const string Homes = "\U0001f3d8"; - public const string HoneyPot = "\U0001f36f"; - public const string Horse = "\U0001f434"; - public const string HorseRacing = "\U0001f3c7"; - public const string HorseRacingSkinTone1 = "\U0001f3c7\U0001f3fb"; - public const string HorseRacingSkinTone2 = "\U0001f3c7\U0001f3fc"; - public const string HorseRacingSkinTone3 = "\U0001f3c7\U0001f3fd"; - public const string HorseRacingSkinTone4 = "\U0001f3c7\U0001f3fe"; - public const string HorseRacingSkinTone5 = "\U0001f3c7\U0001f3ff"; - public const string Hospital = "\U0001f3e5"; - public const string Hotdog = "\U0001f32d"; - public const string HotDog = "\U0001f32d"; - public const string Hotel = "\U0001f3e8"; - public const string HotPepper = "\U0001f336"; - public const string Hotsprings = "\U00002668"; - public const string Hourglass = "\U0000231b"; - public const string HourglassFlowingSand = "\U000023f3"; - public const string House = "\U0001f3e0"; - public const string HouseAbandoned = "\U0001f3da"; - public const string HouseBuildings = "\U0001f3d8"; - public const string HouseWithGarden = "\U0001f3e1"; - public const string Hugging = "\U0001f917"; - public const string HuggingFace = "\U0001f917"; - public const string Hushed = "\U0001f62f"; - public const string Icecream = "\U0001f366"; - public const string IceCream = "\U0001f368"; - public const string IceSkate = "\U000026f8"; - public const string Id = "\U0001f194"; - public const string IdeographAdvantage = "\U0001f250"; - public const string Imp = "\U0001f47f"; - public const string InboxTray = "\U0001f4e5"; - public const string IncomingEnvelope = "\U0001f4e8"; - public const string InformationDeskPerson = "\U0001f481"; - public const string InformationDeskPersonSkinTone1 = "\U0001f481\U0001f3fb"; - public const string InformationDeskPersonSkinTone2 = "\U0001f481\U0001f3fc"; - public const string InformationDeskPersonSkinTone3 = "\U0001f481\U0001f3fd"; - public const string InformationDeskPersonSkinTone4 = "\U0001f481\U0001f3fe"; - public const string InformationDeskPersonSkinTone5 = "\U0001f481\U0001f3ff"; - public const string InformationSource = "\U00002139"; - public const string Innocent = "\U0001f607"; - public const string Interrobang = "\U00002049"; - public const string Iphone = "\U0001f4f1"; - public const string Island = "\U0001f3dd"; - public const string IzakayaLantern = "\U0001f3ee"; - public const string JackOLantern = "\U0001f383"; - public const string Japan = "\U0001f5fe"; - public const string JapaneseCastle = "\U0001f3ef"; - public const string JapaneseGoblin = "\U0001f47a"; - public const string JapaneseOgre = "\U0001f479"; - public const string Jeans = "\U0001f456"; - public const string Joy = "\U0001f602"; - public const string JoyCat = "\U0001f639"; - public const string Joystick = "\U0001f579"; - public const string Juggler = "\U0001f939"; - public const string JugglerSkinTone1 = "\U0001f939\U0001f3fb"; - public const string JugglerSkinTone2 = "\U0001f939\U0001f3fc"; - public const string JugglerSkinTone3 = "\U0001f939\U0001f3fd"; - public const string JugglerSkinTone4 = "\U0001f939\U0001f3fe"; - public const string JugglerSkinTone5 = "\U0001f939\U0001f3ff"; - public const string Juggling = "\U0001f939"; - public const string JugglingSkinTone1 = "\U0001f939\U0001f3fb"; - public const string JugglingSkinTone2 = "\U0001f939\U0001f3fc"; - public const string JugglingSkinTone3 = "\U0001f939\U0001f3fd"; - public const string JugglingSkinTone4 = "\U0001f939\U0001f3fe"; - public const string JugglingSkinTone5 = "\U0001f939\U0001f3ff"; - public const string Kaaba = "\U0001f54b"; - public const string KarateUniform = "\U0001f94b"; - public const string Kayak = "\U0001f6f6"; - public const string Key = "\U0001f511"; - public const string Key2 = "\U0001f5dd"; - public const string Keyboard = "\U00002328"; - public const string KeycapAsterisk = "\U0000002a\U000020e3"; - public const string KeycapTen = "\U0001f51f"; - public const string Kimono = "\U0001f458"; - public const string Kiss = "\U0001f48b"; - public const string Kissing = "\U0001f617"; - public const string KissingCat = "\U0001f63d"; - public const string KissingClosedEyes = "\U0001f61a"; - public const string KissingHeart = "\U0001f618"; - public const string KissingSmilingEyes = "\U0001f619"; - public const string KissMm = "\U0001f468\U0000200d\U00002764\U0000fe0f\U0000200d\U0001f48b\U0000200d\U0001f468"; - public const string KissWw = "\U0001f469\U0000200d\U00002764\U0000fe0f\U0000200d\U0001f48b\U0000200d\U0001f469"; - public const string Kiwi = "\U0001f95d"; - public const string Kiwifruit = "\U0001f95d"; - public const string Knife = "\U0001f52a"; - public const string Koala = "\U0001f428"; - public const string Koko = "\U0001f201"; - public const string Label = "\U0001f3f7"; - public const string LargeBlueCircle = "\U0001f535"; - public const string LargeBlueDiamond = "\U0001f537"; - public const string LargeOrangeDiamond = "\U0001f536"; - public const string LastQuarterMoon = "\U0001f317"; - public const string LastQuarterMoonWithFace = "\U0001f31c"; - public const string LatinCross = "\U0000271d"; - public const string Laughing = "\U0001f606"; - public const string Leaves = "\U0001f343"; - public const string Ledger = "\U0001f4d2"; - public const string LeftFacingFist = "\U0001f91b"; - public const string LeftFacingFistSkinTone1 = "\U0001f91b\U0001f3fb"; - public const string LeftFacingFistSkinTone2 = "\U0001f91b\U0001f3fc"; - public const string LeftFacingFistSkinTone3 = "\U0001f91b\U0001f3fd"; - public const string LeftFacingFistSkinTone4 = "\U0001f91b\U0001f3fe"; - public const string LeftFacingFistSkinTone5 = "\U0001f91b\U0001f3ff"; - public const string LeftFist = "\U0001f91b"; - public const string LeftFistSkinTone1 = "\U0001f91b\U0001f3fb"; - public const string LeftFistSkinTone2 = "\U0001f91b\U0001f3fc"; - public const string LeftFistSkinTone3 = "\U0001f91b\U0001f3fd"; - public const string LeftFistSkinTone4 = "\U0001f91b\U0001f3fe"; - public const string LeftFistSkinTone5 = "\U0001f91b\U0001f3ff"; - public const string LeftLuggage = "\U0001f6c5"; - public const string LeftRightArrow = "\U00002194"; - public const string LeftSpeechBubble = "\U0001f5e8"; - public const string LeftwardsArrowWithHook = "\U000021a9"; - public const string Lemon = "\U0001f34b"; - public const string Leo = "\U0000264c"; - public const string Leopard = "\U0001f406"; - public const string LevelSlider = "\U0001f39a"; - public const string Levitate = "\U0001f574"; - public const string LevitateSkinTone1 = "\U0001f574\U0001f3fb"; - public const string LevitateSkinTone2 = "\U0001f574\U0001f3fc"; - public const string LevitateSkinTone3 = "\U0001f574\U0001f3fd"; - public const string LevitateSkinTone4 = "\U0001f574\U0001f3fe"; - public const string LevitateSkinTone5 = "\U0001f574\U0001f3ff"; - public const string Liar = "\U0001f925"; - public const string Libra = "\U0000264e"; - public const string Lifter = "\U0001f3cb"; - public const string LifterSkinTone1 = "\U0001f3cb\U0001f3fb"; - public const string LifterSkinTone2 = "\U0001f3cb\U0001f3fc"; - public const string LifterSkinTone3 = "\U0001f3cb\U0001f3fd"; - public const string LifterSkinTone4 = "\U0001f3cb\U0001f3fe"; - public const string LifterSkinTone5 = "\U0001f3cb\U0001f3ff"; - public const string LightRail = "\U0001f688"; - public const string Link = "\U0001f517"; - public const string LinkedPaperclips = "\U0001f587"; - public const string Lion = "\U0001f981"; - public const string LionFace = "\U0001f981"; - public const string Lips = "\U0001f444"; - public const string Lipstick = "\U0001f484"; - public const string Lizard = "\U0001f98e"; - public const string Lock = "\U0001f512"; - public const string LockWithInkPen = "\U0001f50f"; - public const string Lollipop = "\U0001f36d"; - public const string Loop = "\U000027bf"; - public const string LoudSound = "\U0001f50a"; - public const string Loudspeaker = "\U0001f4e2"; - public const string LoveHotel = "\U0001f3e9"; - public const string LoveLetter = "\U0001f48c"; - public const string LowBrightness = "\U0001f505"; - public const string LowerLeftBallpointPen = "\U0001f58a"; - public const string LowerLeftCrayon = "\U0001f58d"; - public const string LowerLeftFountainPen = "\U0001f58b"; - public const string LowerLeftPaintbrush = "\U0001f58c"; - public const string LyingFace = "\U0001f925"; + public const string BABY = "\U0001f476"; + public const string BABY_BOTTLE = "\U0001f37c"; + public const string BABY_CHICK = "\U0001f424"; + public const string BABY_SKIN_TONE1 = "\U0001f476\U0001f3fb"; + public const string BABY_SKIN_TONE2 = "\U0001f476\U0001f3fc"; + public const string BABY_SKIN_TONE3 = "\U0001f476\U0001f3fd"; + public const string BABY_SKIN_TONE4 = "\U0001f476\U0001f3fe"; + public const string BABY_SKIN_TONE5 = "\U0001f476\U0001f3ff"; + public const string BABY_SYMBOL = "\U0001f6bc"; + public const string BACK = "\U0001f519"; + public const string BACK_OF_HAND = "\U0001f91a"; + public const string BACK_OF_HAND_SKIN_TONE1 = "\U0001f91a\U0001f3fb"; + public const string BACK_OF_HAND_SKIN_TONE2 = "\U0001f91a\U0001f3fc"; + public const string BACK_OF_HAND_SKIN_TONE3 = "\U0001f91a\U0001f3fd"; + public const string BACK_OF_HAND_SKIN_TONE4 = "\U0001f91a\U0001f3fe"; + public const string BACK_OF_HAND_SKIN_TONE5 = "\U0001f91a\U0001f3ff"; + public const string BACON = "\U0001f953"; + public const string BADMINTON = "\U0001f3f8"; + public const string BAGGAGE_CLAIM = "\U0001f6c4"; + public const string BAGUETTE_BREAD = "\U0001f956"; + public const string BALLOON = "\U0001f388"; + public const string BALLOT_BOX = "\U0001f5f3"; + public const string BALLOT_BOX_WITH_BALLOT = "\U0001f5f3"; + public const string BALLOT_BOX_WITH_CHECK = "\U00002611"; + public const string BAMBOO = "\U0001f38d"; + public const string BANANA = "\U0001f34c"; + public const string BANGBANG = "\U0000203c"; + public const string BANK = "\U0001f3e6"; + public const string BARBER = "\U0001f488"; + public const string BAR_CHART = "\U0001f4ca"; + public const string BASEBALL = "\U000026be"; + public const string BASKETBALL = "\U0001f3c0"; + public const string BASKETBALL_PLAYER = "\U000026f9"; + public const string BASKETBALL_PLAYER_SKIN_TONE1 = "\U000026f9\U0001f3fb"; + public const string BASKETBALL_PLAYER_SKIN_TONE2 = "\U000026f9\U0001f3fc"; + public const string BASKETBALL_PLAYER_SKIN_TONE3 = "\U000026f9\U0001f3fd"; + public const string BASKETBALL_PLAYER_SKIN_TONE4 = "\U000026f9\U0001f3fe"; + public const string BASKETBALL_PLAYER_SKIN_TONE5 = "\U000026f9\U0001f3ff"; + public const string BAT = "\U0001f987"; + public const string BATH = "\U0001f6c0"; + public const string BATH_SKIN_TONE1 = "\U0001f6c0\U0001f3fb"; + public const string BATH_SKIN_TONE2 = "\U0001f6c0\U0001f3fc"; + public const string BATH_SKIN_TONE3 = "\U0001f6c0\U0001f3fd"; + public const string BATH_SKIN_TONE4 = "\U0001f6c0\U0001f3fe"; + public const string BATH_SKIN_TONE5 = "\U0001f6c0\U0001f3ff"; + public const string BATHTUB = "\U0001f6c1"; + public const string BATTERY = "\U0001f50b"; + public const string BEACH = "\U0001f3d6"; + public const string BEACH_UMBRELLA = "\U000026f1"; + public const string BEACH_WITH_UMBRELLA = "\U0001f3d6"; + public const string BEAR = "\U0001f43b"; + public const string BED = "\U0001f6cf"; + public const string BEE = "\U0001f41d"; + public const string BEER = "\U0001f37a"; + public const string BEERS = "\U0001f37b"; + public const string BEETLE = "\U0001f41e"; + public const string BEGINNER = "\U0001f530"; + public const string BELL = "\U0001f514"; + public const string BELLHOP = "\U0001f6ce"; + public const string BELLHOP_BELL = "\U0001f6ce"; + public const string BENTO = "\U0001f371"; + public const string BICYCLIST = "\U0001f6b4"; + public const string BICYCLIST_SKIN_TONE1 = "\U0001f6b4\U0001f3fb"; + public const string BICYCLIST_SKIN_TONE2 = "\U0001f6b4\U0001f3fc"; + public const string BICYCLIST_SKIN_TONE3 = "\U0001f6b4\U0001f3fd"; + public const string BICYCLIST_SKIN_TONE4 = "\U0001f6b4\U0001f3fe"; + public const string BICYCLIST_SKIN_TONE5 = "\U0001f6b4\U0001f3ff"; + public const string BIKE = "\U0001f6b2"; + public const string BIKINI = "\U0001f459"; + public const string BIOHAZARD = "\U00002623"; + public const string BIOHAZARD_SIGN = "\U00002623"; + public const string BIRD = "\U0001f426"; + public const string BIRTHDAY = "\U0001f382"; + public const string BLACK_CIRCLE = "\U000026ab"; + public const string BLACK_HEART = "\U0001f5a4"; + public const string BLACK_JOKER = "\U0001f0cf"; + public const string BLACK_LARGE_SQUARE = "\U00002b1b"; + public const string BLACK_MEDIUM_SMALL_SQUARE = "\U000025fe"; + public const string BLACK_MEDIUM_SQUARE = "\U000025fc"; + public const string BLACK_NIB = "\U00002712"; + public const string BLACK_SMALL_SQUARE = "\U000025aa"; + public const string BLACK_SQUARE_BUTTON = "\U0001f532"; + public const string BLOSSOM = "\U0001f33c"; + public const string BLOWFISH = "\U0001f421"; + public const string BLUE_BOOK = "\U0001f4d8"; + public const string BLUE_CAR = "\U0001f699"; + public const string BLUE_HEART = "\U0001f499"; + public const string BLUSH = "\U0001f60a"; + public const string BOAR = "\U0001f417"; + public const string BOMB = "\U0001f4a3"; + public const string BOOK = "\U0001f4d6"; + public const string BOOKMARK = "\U0001f516"; + public const string BOOKMARK_TABS = "\U0001f4d1"; + public const string BOOKS = "\U0001f4da"; + public const string BOOM = "\U0001f4a5"; + public const string BOOT = "\U0001f462"; + public const string BOTTLE_WITH_POPPING_CORK = "\U0001f37e"; + public const string BOUQUET = "\U0001f490"; + public const string BOW = "\U0001f647"; + public const string BOW_AND_ARROW = "\U0001f3f9"; + public const string BOWLING = "\U0001f3b3"; + public const string BOW_SKIN_TONE1 = "\U0001f647\U0001f3fb"; + public const string BOW_SKIN_TONE2 = "\U0001f647\U0001f3fc"; + public const string BOW_SKIN_TONE3 = "\U0001f647\U0001f3fd"; + public const string BOW_SKIN_TONE4 = "\U0001f647\U0001f3fe"; + public const string BOW_SKIN_TONE5 = "\U0001f647\U0001f3ff"; + public const string BOXING_GLOVE = "\U0001f94a"; + public const string BOXING_GLOVES = "\U0001f94a"; + public const string BOY = "\U0001f466"; + public const string BOY_SKIN_TONE1 = "\U0001f466\U0001f3fb"; + public const string BOY_SKIN_TONE2 = "\U0001f466\U0001f3fc"; + public const string BOY_SKIN_TONE3 = "\U0001f466\U0001f3fd"; + public const string BOY_SKIN_TONE4 = "\U0001f466\U0001f3fe"; + public const string BOY_SKIN_TONE5 = "\U0001f466\U0001f3ff"; + public const string BREAD = "\U0001f35e"; + public const string BRIDE_WITH_VEIL = "\U0001f470"; + public const string BRIDE_WITH_VEIL_SKIN_TONE1 = "\U0001f470\U0001f3fb"; + public const string BRIDE_WITH_VEIL_SKIN_TONE2 = "\U0001f470\U0001f3fc"; + public const string BRIDE_WITH_VEIL_SKIN_TONE3 = "\U0001f470\U0001f3fd"; + public const string BRIDE_WITH_VEIL_SKIN_TONE4 = "\U0001f470\U0001f3fe"; + public const string BRIDE_WITH_VEIL_SKIN_TONE5 = "\U0001f470\U0001f3ff"; + public const string BRIDGE_AT_NIGHT = "\U0001f309"; + public const string BRIEFCASE = "\U0001f4bc"; + public const string BROKEN_HEART = "\U0001f494"; + public const string BUG = "\U0001f41b"; + public const string BUILDING_CONSTRUCTION = "\U0001f3d7"; + public const string BULB = "\U0001f4a1"; + public const string BULLETTRAIN_FRONT = "\U0001f685"; + public const string BULLETTRAIN_SIDE = "\U0001f684"; + public const string BURRITO = "\U0001f32f"; + public const string BUS = "\U0001f68c"; + public const string BUSSTOP = "\U0001f68f"; + public const string BUST_IN_SILHOUETTE = "\U0001f464"; + public const string BUSTS_IN_SILHOUETTE = "\U0001f465"; + public const string BUTTERFLY = "\U0001f98b"; + public const string CACTUS = "\U0001f335"; + public const string CAKE = "\U0001f370"; + public const string CALENDAR = "\U0001f4c6"; + public const string CALENDAR_SPIRAL = "\U0001f5d3"; + public const string CALLING = "\U0001f4f2"; + public const string CALL_ME = "\U0001f919"; + public const string CALL_ME_HAND = "\U0001f919"; + public const string CALL_ME_HAND_SKIN_TONE1 = "\U0001f919\U0001f3fb"; + public const string CALL_ME_HAND_SKIN_TONE2 = "\U0001f919\U0001f3fc"; + public const string CALL_ME_HAND_SKIN_TONE3 = "\U0001f919\U0001f3fd"; + public const string CALL_ME_HAND_SKIN_TONE4 = "\U0001f919\U0001f3fe"; + public const string CALL_ME_HAND_SKIN_TONE5 = "\U0001f919\U0001f3ff"; + public const string CALL_ME_SKIN_TONE1 = "\U0001f919\U0001f3fb"; + public const string CALL_ME_SKIN_TONE2 = "\U0001f919\U0001f3fc"; + public const string CALL_ME_SKIN_TONE3 = "\U0001f919\U0001f3fd"; + public const string CALL_ME_SKIN_TONE4 = "\U0001f919\U0001f3fe"; + public const string CALL_ME_SKIN_TONE5 = "\U0001f919\U0001f3ff"; + public const string CAMEL = "\U0001f42b"; + public const string CAMERA = "\U0001f4f7"; + public const string CAMERA_WITH_FLASH = "\U0001f4f8"; + public const string CAMPING = "\U0001f3d5"; + public const string CANCER = "\U0000264b"; + public const string CANDLE = "\U0001f56f"; + public const string CANDY = "\U0001f36c"; + public const string CANOE = "\U0001f6f6"; + public const string CAPITAL_ABCD = "\U0001f520"; + public const string CAPRICORN = "\U00002651"; + public const string CARD_BOX = "\U0001f5c3"; + public const string CARD_FILE_BOX = "\U0001f5c3"; + public const string CARD_INDEX = "\U0001f4c7"; + public const string CARD_INDEX_DIVIDERS = "\U0001f5c2"; + public const string CAROUSEL_HORSE = "\U0001f3a0"; + public const string CARROT = "\U0001f955"; + public const string CARTWHEEL = "\U0001f938"; + public const string CARTWHEEL_SKIN_TONE1 = "\U0001f938\U0001f3fb"; + public const string CARTWHEEL_SKIN_TONE2 = "\U0001f938\U0001f3fc"; + public const string CARTWHEEL_SKIN_TONE3 = "\U0001f938\U0001f3fd"; + public const string CARTWHEEL_SKIN_TONE4 = "\U0001f938\U0001f3fe"; + public const string CARTWHEEL_SKIN_TONE5 = "\U0001f938\U0001f3ff"; + public const string CAT = "\U0001f431"; + public const string CAT2 = "\U0001f408"; + public const string CD = "\U0001f4bf"; + public const string CHAINS = "\U000026d3"; + public const string CHAMPAGNE = "\U0001f37e"; + public const string CHAMPAGNE_GLASS = "\U0001f942"; + public const string CHART = "\U0001f4b9"; + public const string CHART_WITH_DOWNWARDS_TREND = "\U0001f4c9"; + public const string CHART_WITH_UPWARDS_TREND = "\U0001f4c8"; + public const string CHECKERED_FLAG = "\U0001f3c1"; + public const string CHEESE = "\U0001f9c0"; + public const string CHEESE_WEDGE = "\U0001f9c0"; + public const string CHERRIES = "\U0001f352"; + public const string CHERRY_BLOSSOM = "\U0001f338"; + public const string CHESTNUT = "\U0001f330"; + public const string CHICKEN = "\U0001f414"; + public const string CHILDREN_CROSSING = "\U0001f6b8"; + public const string CHIPMUNK = "\U0001f43f"; + public const string CHOCOLATE_BAR = "\U0001f36b"; + public const string CHRISTMAS_TREE = "\U0001f384"; + public const string CHURCH = "\U000026ea"; + public const string CINEMA = "\U0001f3a6"; + public const string CIRCUS_TENT = "\U0001f3aa"; + public const string CITY_DUSK = "\U0001f306"; + public const string CITYSCAPE = "\U0001f3d9"; + public const string CITY_SUNRISE = "\U0001f307"; + public const string CITY_SUNSET = "\U0001f307"; + public const string CL = "\U0001f191"; + public const string CLAP = "\U0001f44f"; + public const string CLAPPER = "\U0001f3ac"; + public const string CLAP_SKIN_TONE1 = "\U0001f44f\U0001f3fb"; + public const string CLAP_SKIN_TONE2 = "\U0001f44f\U0001f3fc"; + public const string CLAP_SKIN_TONE3 = "\U0001f44f\U0001f3fd"; + public const string CLAP_SKIN_TONE4 = "\U0001f44f\U0001f3fe"; + public const string CLAP_SKIN_TONE5 = "\U0001f44f\U0001f3ff"; + public const string CLASSICAL_BUILDING = "\U0001f3db"; + public const string CLINKING_GLASS = "\U0001f942"; + public const string CLIPBOARD = "\U0001f4cb"; + public const string CLOCK = "\U0001f570"; + public const string CLOCK1 = "\U0001f550"; + public const string CLOCK10 = "\U0001f559"; + public const string CLOCK1030 = "\U0001f565"; + public const string CLOCK11 = "\U0001f55a"; + public const string CLOCK1130 = "\U0001f566"; + public const string CLOCK12 = "\U0001f55b"; + public const string CLOCK1230 = "\U0001f567"; + public const string CLOCK130 = "\U0001f55c"; + public const string CLOCK2 = "\U0001f551"; + public const string CLOCK230 = "\U0001f55d"; + public const string CLOCK3 = "\U0001f552"; + public const string CLOCK330 = "\U0001f55e"; + public const string CLOCK4 = "\U0001f553"; + public const string CLOCK430 = "\U0001f55f"; + public const string CLOCK5 = "\U0001f554"; + public const string CLOCK530 = "\U0001f560"; + public const string CLOCK6 = "\U0001f555"; + public const string CLOCK630 = "\U0001f561"; + public const string CLOCK7 = "\U0001f556"; + public const string CLOCK730 = "\U0001f562"; + public const string CLOCK8 = "\U0001f557"; + public const string CLOCK830 = "\U0001f563"; + public const string CLOCK9 = "\U0001f558"; + public const string CLOCK930 = "\U0001f564"; + public const string CLOSED_BOOK = "\U0001f4d5"; + public const string CLOSED_LOCK_WITH_KEY = "\U0001f510"; + public const string CLOSED_UMBRELLA = "\U0001f302"; + public const string CLOUD = "\U00002601"; + public const string CLOUD_LIGHTNING = "\U0001f329"; + public const string CLOUD_RAIN = "\U0001f327"; + public const string CLOUD_SNOW = "\U0001f328"; + public const string CLOUD_TORNADO = "\U0001f32a"; + public const string CLOUD_WITH_LIGHTNING = "\U0001f329"; + public const string CLOUD_WITH_RAIN = "\U0001f327"; + public const string CLOUD_WITH_SNOW = "\U0001f328"; + public const string CLOUD_WITH_TORNADO = "\U0001f32a"; + public const string CLOWN = "\U0001f921"; + public const string CLOWN_FACE = "\U0001f921"; + public const string CLUBS = "\U00002663"; + public const string COCKTAIL = "\U0001f378"; + public const string COFFEE = "\U00002615"; + public const string COFFIN = "\U000026b0"; + public const string COLD_SWEAT = "\U0001f630"; + public const string COMET = "\U00002604"; + public const string COMPRESSION = "\U0001f5dc"; + public const string COMPUTER = "\U0001f4bb"; + public const string CONFETTI_BALL = "\U0001f38a"; + public const string CONFOUNDED = "\U0001f616"; + public const string CONFUSED = "\U0001f615"; + public const string CONGRATULATIONS = "\U00003297"; + public const string CONSTRUCTION = "\U0001f6a7"; + public const string CONSTRUCTION_SITE = "\U0001f3d7"; + public const string CONSTRUCTION_WORKER = "\U0001f477"; + public const string CONSTRUCTION_WORKER_SKIN_TONE1 = "\U0001f477\U0001f3fb"; + public const string CONSTRUCTION_WORKER_SKIN_TONE2 = "\U0001f477\U0001f3fc"; + public const string CONSTRUCTION_WORKER_SKIN_TONE3 = "\U0001f477\U0001f3fd"; + public const string CONSTRUCTION_WORKER_SKIN_TONE4 = "\U0001f477\U0001f3fe"; + public const string CONSTRUCTION_WORKER_SKIN_TONE5 = "\U0001f477\U0001f3ff"; + public const string CONTROL_KNOBS = "\U0001f39b"; + public const string CONVENIENCE_STORE = "\U0001f3ea"; + public const string COOKIE = "\U0001f36a"; + public const string COOKING = "\U0001f373"; + public const string COOL = "\U0001f192"; + public const string COP = "\U0001f46e"; + public const string COP_SKIN_TONE1 = "\U0001f46e\U0001f3fb"; + public const string COP_SKIN_TONE2 = "\U0001f46e\U0001f3fc"; + public const string COP_SKIN_TONE3 = "\U0001f46e\U0001f3fd"; + public const string COP_SKIN_TONE4 = "\U0001f46e\U0001f3fe"; + public const string COP_SKIN_TONE5 = "\U0001f46e\U0001f3ff"; + public const string COPYRIGHT = "\U000000a9"; + public const string CORN = "\U0001f33d"; + public const string COUCH = "\U0001f6cb"; + public const string COUCH_AND_LAMP = "\U0001f6cb"; + public const string COUPLE = "\U0001f46b"; + public const string COUPLEKISS = "\U0001f48f"; + public const string COUPLEKISS_MM = "\U0001f468\U0000200d\U00002764\U0000fe0f\U0000200d\U0001f48b\U0000200d\U0001f468"; + public const string COUPLEKISS_WW = "\U0001f469\U0000200d\U00002764\U0000fe0f\U0000200d\U0001f48b\U0000200d\U0001f469"; + public const string COUPLE_MM = "\U0001f468\U0000200d\U00002764\U0000fe0f\U0000200d\U0001f468"; + public const string COUPLE_WITH_HEART = "\U0001f491"; + public const string COUPLE_WITH_HEART_MM = "\U0001f468\U0000200d\U00002764\U0000fe0f\U0000200d\U0001f468"; + public const string COUPLE_WITH_HEART_WW = "\U0001f469\U0000200d\U00002764\U0000fe0f\U0000200d\U0001f469"; + public const string COUPLE_WW = "\U0001f469\U0000200d\U00002764\U0000fe0f\U0000200d\U0001f469"; + public const string COW = "\U0001f42e"; + public const string COW2 = "\U0001f404"; + public const string COWBOY = "\U0001f920"; + public const string CRAB = "\U0001f980"; + public const string CRAYON = "\U0001f58d"; + public const string CREDIT_CARD = "\U0001f4b3"; + public const string CRESCENT_MOON = "\U0001f319"; + public const string CRICKET = "\U0001f3cf"; + public const string CRICKET_BAT_BALL = "\U0001f3cf"; + public const string CROCODILE = "\U0001f40a"; + public const string CROISSANT = "\U0001f950"; + public const string CROSS = "\U0000271d"; + public const string CROSSED_FLAGS = "\U0001f38c"; + public const string CROSSED_SWORDS = "\U00002694"; + public const string CROWN = "\U0001f451"; + public const string CRUISE_SHIP = "\U0001f6f3"; + public const string CRY = "\U0001f622"; + public const string CRYING_CAT_FACE = "\U0001f63f"; + public const string CRYSTAL_BALL = "\U0001f52e"; + public const string CUCUMBER = "\U0001f952"; + public const string CUPID = "\U0001f498"; + public const string CURLY_LOOP = "\U000027b0"; + public const string CURRENCY_EXCHANGE = "\U0001f4b1"; + public const string CURRY = "\U0001f35b"; + public const string CUSTARD = "\U0001f36e"; + public const string CUSTOMS = "\U0001f6c3"; + public const string CYCLONE = "\U0001f300"; + public const string DAGGER = "\U0001f5e1"; + public const string DAGGER_KNIFE = "\U0001f5e1"; + public const string DANCER = "\U0001f483"; + public const string DANCERS = "\U0001f46f"; + public const string DANCER_SKIN_TONE1 = "\U0001f483\U0001f3fb"; + public const string DANCER_SKIN_TONE2 = "\U0001f483\U0001f3fc"; + public const string DANCER_SKIN_TONE3 = "\U0001f483\U0001f3fd"; + public const string DANCER_SKIN_TONE4 = "\U0001f483\U0001f3fe"; + public const string DANCER_SKIN_TONE5 = "\U0001f483\U0001f3ff"; + public const string DANGO = "\U0001f361"; + public const string DARK_SUNGLASSES = "\U0001f576"; + public const string DART = "\U0001f3af"; + public const string DASH = "\U0001f4a8"; + public const string DATE = "\U0001f4c5"; + public const string DECIDUOUS_TREE = "\U0001f333"; + public const string DEER = "\U0001f98c"; + public const string DEPARTMENT_STORE = "\U0001f3ec"; + public const string DERELICT_HOUSE_BUILDING = "\U0001f3da"; + public const string DESERT = "\U0001f3dc"; + public const string DESERT_ISLAND = "\U0001f3dd"; + public const string DESKTOP = "\U0001f5a5"; + public const string DESKTOP_COMPUTER = "\U0001f5a5"; + public const string DIAMONDS = "\U00002666"; + public const string DIAMOND_SHAPE_WITH_A_DOT_INSIDE = "\U0001f4a0"; + public const string DISAPPOINTED = "\U0001f61e"; + public const string DISAPPOINTED_RELIEVED = "\U0001f625"; + public const string DIVIDERS = "\U0001f5c2"; + public const string DIZZY = "\U0001f4ab"; + public const string DIZZY_FACE = "\U0001f635"; + public const string DOG = "\U0001f436"; + public const string DOG2 = "\U0001f415"; + public const string DOLLAR = "\U0001f4b5"; + public const string DOLLS = "\U0001f38e"; + public const string DOLPHIN = "\U0001f42c"; + public const string DO_NOT_LITTER = "\U0001f6af"; + public const string DOOR = "\U0001f6aa"; + public const string DOUBLE_VERTICAL_BAR = "\U000023f8"; + public const string DOUGHNUT = "\U0001f369"; + public const string DOVE = "\U0001f54a"; + public const string DOVE_OF_PEACE = "\U0001f54a"; + public const string DRAGON = "\U0001f409"; + public const string DRAGON_FACE = "\U0001f432"; + public const string DRESS = "\U0001f457"; + public const string DROMEDARY_CAMEL = "\U0001f42a"; + public const string DROOL = "\U0001f924"; + public const string DROOLING_FACE = "\U0001f924"; + public const string DROPLET = "\U0001f4a7"; + public const string DRUM = "\U0001f941"; + public const string DRUM_WITH_DRUMSTICKS = "\U0001f941"; + public const string DUCK = "\U0001f986"; + public const string DVD = "\U0001f4c0"; + public const string EAGLE = "\U0001f985"; + public const string EAR = "\U0001f442"; + public const string EAR_OF_RICE = "\U0001f33e"; + public const string EAR_SKIN_TONE1 = "\U0001f442\U0001f3fb"; + public const string EAR_SKIN_TONE2 = "\U0001f442\U0001f3fc"; + public const string EAR_SKIN_TONE3 = "\U0001f442\U0001f3fd"; + public const string EAR_SKIN_TONE4 = "\U0001f442\U0001f3fe"; + public const string EAR_SKIN_TONE5 = "\U0001f442\U0001f3ff"; + public const string EARTH_AFRICA = "\U0001f30d"; + public const string EARTH_AMERICAS = "\U0001f30e"; + public const string EARTH_ASIA = "\U0001f30f"; + public const string EGG = "\U0001f95a"; + public const string EGGPLANT = "\U0001f346"; + public const string EIGHT = "\U00000038\U000020e3"; + public const string EIGHT_POINTED_BLACK_STAR = "\U00002734"; + public const string EIGHT_SPOKED_ASTERISK = "\U00002733"; + public const string EJECT = "\U000023cf"; + public const string EJECT_SYMBOL = "\U000023cf"; + public const string ELECTRIC_PLUG = "\U0001f50c"; + public const string ELEPHANT = "\U0001f418"; + public const string E_MAIL = "\U0001f4e7"; + public const string EMAIL = "\U0001f4e7"; + public const string END = "\U0001f51a"; + public const string ENVELOPE = "\U00002709"; + public const string ENVELOPE_WITH_ARROW = "\U0001f4e9"; + public const string EURO = "\U0001f4b6"; + public const string EUROPEAN_CASTLE = "\U0001f3f0"; + public const string EUROPEAN_POST_OFFICE = "\U0001f3e4"; + public const string EVERGREEN_TREE = "\U0001f332"; + public const string EXCLAMATION = "\U00002757"; + public const string EXPECTING_WOMAN = "\U0001f930"; + public const string EXPECTING_WOMAN_SKIN_TONE1 = "\U0001f930\U0001f3fb"; + public const string EXPECTING_WOMAN_SKIN_TONE2 = "\U0001f930\U0001f3fc"; + public const string EXPECTING_WOMAN_SKIN_TONE3 = "\U0001f930\U0001f3fd"; + public const string EXPECTING_WOMAN_SKIN_TONE4 = "\U0001f930\U0001f3fe"; + public const string EXPECTING_WOMAN_SKIN_TONE5 = "\U0001f930\U0001f3ff"; + public const string EXPRESSIONLESS = "\U0001f611"; + public const string EYE = "\U0001f441"; + public const string EYEGLASSES = "\U0001f453"; + public const string EYE_IN_SPEECH_BUBBLE = "\U0001f441\U0000200d\U0001f5e8"; + public const string EYES = "\U0001f440"; + public const string FACE_PALM = "\U0001f926"; + public const string FACEPALM = "\U0001f926"; + public const string FACE_PALM_SKIN_TONE1 = "\U0001f926\U0001f3fb"; + public const string FACEPALM_SKIN_TONE1 = "\U0001f926\U0001f3fb"; + public const string FACE_PALM_SKIN_TONE2 = "\U0001f926\U0001f3fc"; + public const string FACEPALM_SKIN_TONE2 = "\U0001f926\U0001f3fc"; + public const string FACE_PALM_SKIN_TONE3 = "\U0001f926\U0001f3fd"; + public const string FACEPALM_SKIN_TONE3 = "\U0001f926\U0001f3fd"; + public const string FACE_PALM_SKIN_TONE4 = "\U0001f926\U0001f3fe"; + public const string FACEPALM_SKIN_TONE4 = "\U0001f926\U0001f3fe"; + public const string FACE_PALM_SKIN_TONE5 = "\U0001f926\U0001f3ff"; + public const string FACEPALM_SKIN_TONE5 = "\U0001f926\U0001f3ff"; + public const string FACE_WITH_COWBOY_HAT = "\U0001f920"; + public const string FACE_WITH_HEAD_BANDAGE = "\U0001f915"; + public const string FACE_WITH_ROLLING_EYES = "\U0001f644"; + public const string FACE_WITH_THERMOMETER = "\U0001f912"; + public const string FACTORY = "\U0001f3ed"; + public const string FALLEN_LEAF = "\U0001f342"; + public const string FAMILY = "\U0001f46a"; + public const string FAMILY_MMB = "\U0001f468\U0000200d\U0001f468\U0000200d\U0001f466"; + public const string FAMILY_MMBB = "\U0001f468\U0000200d\U0001f468\U0000200d\U0001f466\U0000200d\U0001f466"; + public const string FAMILY_MMG = "\U0001f468\U0000200d\U0001f468\U0000200d\U0001f467"; + public const string FAMILY_MMGB = "\U0001f468\U0000200d\U0001f468\U0000200d\U0001f467\U0000200d\U0001f466"; + public const string FAMILY_MMGG = "\U0001f468\U0000200d\U0001f468\U0000200d\U0001f467\U0000200d\U0001f467"; + public const string FAMILY_MWBB = "\U0001f468\U0000200d\U0001f469\U0000200d\U0001f466\U0000200d\U0001f466"; + public const string FAMILY_MWG = "\U0001f468\U0000200d\U0001f469\U0000200d\U0001f467"; + public const string FAMILY_MWGB = "\U0001f468\U0000200d\U0001f469\U0000200d\U0001f467\U0000200d\U0001f466"; + public const string FAMILY_MWGG = "\U0001f468\U0000200d\U0001f469\U0000200d\U0001f467\U0000200d\U0001f467"; + public const string FAMILY_WWB = "\U0001f469\U0000200d\U0001f469\U0000200d\U0001f466"; + public const string FAMILY_WWBB = "\U0001f469\U0000200d\U0001f469\U0000200d\U0001f466\U0000200d\U0001f466"; + public const string FAMILY_WWG = "\U0001f469\U0000200d\U0001f469\U0000200d\U0001f467"; + public const string FAMILY_WWGB = "\U0001f469\U0000200d\U0001f469\U0000200d\U0001f467\U0000200d\U0001f466"; + public const string FAMILY_WWGG = "\U0001f469\U0000200d\U0001f469\U0000200d\U0001f467\U0000200d\U0001f467"; + public const string FAST_FORWARD = "\U000023e9"; + public const string FAX = "\U0001f4e0"; + public const string FEARFUL = "\U0001f628"; + public const string FEET = "\U0001f43e"; + public const string FENCER = "\U0001f93a"; + public const string FENCING = "\U0001f93a"; + public const string FERRIS_WHEEL = "\U0001f3a1"; + public const string FERRY = "\U000026f4"; + public const string FIELD_HOCKEY = "\U0001f3d1"; + public const string FILE_CABINET = "\U0001f5c4"; + public const string FILE_FOLDER = "\U0001f4c1"; + public const string FILM_FRAMES = "\U0001f39e"; + public const string FILM_PROJECTOR = "\U0001f4fd"; + public const string FINGERS_CROSSED = "\U0001f91e"; + public const string FINGERS_CROSSED_SKIN_TONE1 = "\U0001f91e\U0001f3fb"; + public const string FINGERS_CROSSED_SKIN_TONE2 = "\U0001f91e\U0001f3fc"; + public const string FINGERS_CROSSED_SKIN_TONE3 = "\U0001f91e\U0001f3fd"; + public const string FINGERS_CROSSED_SKIN_TONE4 = "\U0001f91e\U0001f3fe"; + public const string FINGERS_CROSSED_SKIN_TONE5 = "\U0001f91e\U0001f3ff"; + public const string FIRE = "\U0001f525"; + public const string FIRE_ENGINE = "\U0001f692"; + public const string FIREWORKS = "\U0001f386"; + public const string FIRST_PLACE = "\U0001f947"; + public const string FIRST_PLACE_MEDAL = "\U0001f947"; + public const string FIRST_QUARTER_MOON = "\U0001f313"; + public const string FIRST_QUARTER_MOON_WITH_FACE = "\U0001f31b"; + public const string FISH = "\U0001f41f"; + public const string FISH_CAKE = "\U0001f365"; + public const string FISHING_POLE_AND_FISH = "\U0001f3a3"; + public const string FIST = "\U0000270a"; + public const string FIST_SKIN_TONE1 = "\U0000270a\U0001f3fb"; + public const string FIST_SKIN_TONE2 = "\U0000270a\U0001f3fc"; + public const string FIST_SKIN_TONE3 = "\U0000270a\U0001f3fd"; + public const string FIST_SKIN_TONE4 = "\U0000270a\U0001f3fe"; + public const string FIST_SKIN_TONE5 = "\U0000270a\U0001f3ff"; + public const string FIVE = "\U00000035\U000020e3"; + public const string FLAG_AC = "\U0001f1e6\U0001f1e8"; + public const string FLAG_AD = "\U0001f1e6\U0001f1e9"; + public const string FLAG_AE = "\U0001f1e6\U0001f1ea"; + public const string FLAG_AF = "\U0001f1e6\U0001f1eb"; + public const string FLAG_AG = "\U0001f1e6\U0001f1ec"; + public const string FLAG_AI = "\U0001f1e6\U0001f1ee"; + public const string FLAG_AL = "\U0001f1e6\U0001f1f1"; + public const string FLAG_AM = "\U0001f1e6\U0001f1f2"; + public const string FLAG_AO = "\U0001f1e6\U0001f1f4"; + public const string FLAG_AQ = "\U0001f1e6\U0001f1f6"; + public const string FLAG_AR = "\U0001f1e6\U0001f1f7"; + public const string FLAG_AS = "\U0001f1e6\U0001f1f8"; + public const string FLAG_AT = "\U0001f1e6\U0001f1f9"; + public const string FLAG_AU = "\U0001f1e6\U0001f1fa"; + public const string FLAG_AW = "\U0001f1e6\U0001f1fc"; + public const string FLAG_AX = "\U0001f1e6\U0001f1fd"; + public const string FLAG_AZ = "\U0001f1e6\U0001f1ff"; + public const string FLAG_BA = "\U0001f1e7\U0001f1e6"; + public const string FLAG_BB = "\U0001f1e7\U0001f1e7"; + public const string FLAG_BD = "\U0001f1e7\U0001f1e9"; + public const string FLAG_BE = "\U0001f1e7\U0001f1ea"; + public const string FLAG_BF = "\U0001f1e7\U0001f1eb"; + public const string FLAG_BG = "\U0001f1e7\U0001f1ec"; + public const string FLAG_BH = "\U0001f1e7\U0001f1ed"; + public const string FLAG_BI = "\U0001f1e7\U0001f1ee"; + public const string FLAG_BJ = "\U0001f1e7\U0001f1ef"; + public const string FLAG_BL = "\U0001f1e7\U0001f1f1"; + public const string FLAG_BLACK = "\U0001f3f4"; + public const string FLAG_BM = "\U0001f1e7\U0001f1f2"; + public const string FLAG_BN = "\U0001f1e7\U0001f1f3"; + public const string FLAG_BO = "\U0001f1e7\U0001f1f4"; + public const string FLAG_BQ = "\U0001f1e7\U0001f1f6"; + public const string FLAG_BR = "\U0001f1e7\U0001f1f7"; + public const string FLAG_BS = "\U0001f1e7\U0001f1f8"; + public const string FLAG_BT = "\U0001f1e7\U0001f1f9"; + public const string FLAG_BV = "\U0001f1e7\U0001f1fb"; + public const string FLAG_BW = "\U0001f1e7\U0001f1fc"; + public const string FLAG_BY = "\U0001f1e7\U0001f1fe"; + public const string FLAG_BZ = "\U0001f1e7\U0001f1ff"; + public const string FLAG_CA = "\U0001f1e8\U0001f1e6"; + public const string FLAG_CC = "\U0001f1e8\U0001f1e8"; + public const string FLAG_CD = "\U0001f1e8\U0001f1e9"; + public const string FLAG_CF = "\U0001f1e8\U0001f1eb"; + public const string FLAG_CG = "\U0001f1e8\U0001f1ec"; + public const string FLAG_CH = "\U0001f1e8\U0001f1ed"; + public const string FLAG_CI = "\U0001f1e8\U0001f1ee"; + public const string FLAG_CK = "\U0001f1e8\U0001f1f0"; + public const string FLAG_CL = "\U0001f1e8\U0001f1f1"; + public const string FLAG_CM = "\U0001f1e8\U0001f1f2"; + public const string FLAG_CN = "\U0001f1e8\U0001f1f3"; + public const string FLAG_CO = "\U0001f1e8\U0001f1f4"; + public const string FLAG_CP = "\U0001f1e8\U0001f1f5"; + public const string FLAG_CR = "\U0001f1e8\U0001f1f7"; + public const string FLAG_CU = "\U0001f1e8\U0001f1fa"; + public const string FLAG_CV = "\U0001f1e8\U0001f1fb"; + public const string FLAG_CW = "\U0001f1e8\U0001f1fc"; + public const string FLAG_CX = "\U0001f1e8\U0001f1fd"; + public const string FLAG_CY = "\U0001f1e8\U0001f1fe"; + public const string FLAG_CZ = "\U0001f1e8\U0001f1ff"; + public const string FLAG_DE = "\U0001f1e9\U0001f1ea"; + public const string FLAG_DG = "\U0001f1e9\U0001f1ec"; + public const string FLAG_DJ = "\U0001f1e9\U0001f1ef"; + public const string FLAG_DK = "\U0001f1e9\U0001f1f0"; + public const string FLAG_DM = "\U0001f1e9\U0001f1f2"; + public const string FLAG_DO = "\U0001f1e9\U0001f1f4"; + public const string FLAG_DZ = "\U0001f1e9\U0001f1ff"; + public const string FLAG_EA = "\U0001f1ea\U0001f1e6"; + public const string FLAG_EC = "\U0001f1ea\U0001f1e8"; + public const string FLAG_EE = "\U0001f1ea\U0001f1ea"; + public const string FLAG_EG = "\U0001f1ea\U0001f1ec"; + public const string FLAG_EH = "\U0001f1ea\U0001f1ed"; + public const string FLAG_ER = "\U0001f1ea\U0001f1f7"; + public const string FLAG_ES = "\U0001f1ea\U0001f1f8"; + public const string FLAG_ET = "\U0001f1ea\U0001f1f9"; + public const string FLAG_EU = "\U0001f1ea\U0001f1fa"; + public const string FLAG_FI = "\U0001f1eb\U0001f1ee"; + public const string FLAG_FJ = "\U0001f1eb\U0001f1ef"; + public const string FLAG_FK = "\U0001f1eb\U0001f1f0"; + public const string FLAG_FM = "\U0001f1eb\U0001f1f2"; + public const string FLAG_FO = "\U0001f1eb\U0001f1f4"; + public const string FLAG_FR = "\U0001f1eb\U0001f1f7"; + public const string FLAG_GA = "\U0001f1ec\U0001f1e6"; + public const string FLAG_GB = "\U0001f1ec\U0001f1e7"; + public const string FLAG_GD = "\U0001f1ec\U0001f1e9"; + public const string FLAG_GE = "\U0001f1ec\U0001f1ea"; + public const string FLAG_GF = "\U0001f1ec\U0001f1eb"; + public const string FLAG_GG = "\U0001f1ec\U0001f1ec"; + public const string FLAG_GH = "\U0001f1ec\U0001f1ed"; + public const string FLAG_GI = "\U0001f1ec\U0001f1ee"; + public const string FLAG_GL = "\U0001f1ec\U0001f1f1"; + public const string FLAG_GM = "\U0001f1ec\U0001f1f2"; + public const string FLAG_GN = "\U0001f1ec\U0001f1f3"; + public const string FLAG_GP = "\U0001f1ec\U0001f1f5"; + public const string FLAG_GQ = "\U0001f1ec\U0001f1f6"; + public const string FLAG_GR = "\U0001f1ec\U0001f1f7"; + public const string FLAG_GS = "\U0001f1ec\U0001f1f8"; + public const string FLAG_GT = "\U0001f1ec\U0001f1f9"; + public const string FLAG_GU = "\U0001f1ec\U0001f1fa"; + public const string FLAG_GW = "\U0001f1ec\U0001f1fc"; + public const string FLAG_GY = "\U0001f1ec\U0001f1fe"; + public const string FLAG_HK = "\U0001f1ed\U0001f1f0"; + public const string FLAG_HM = "\U0001f1ed\U0001f1f2"; + public const string FLAG_HN = "\U0001f1ed\U0001f1f3"; + public const string FLAG_HR = "\U0001f1ed\U0001f1f7"; + public const string FLAG_HT = "\U0001f1ed\U0001f1f9"; + public const string FLAG_HU = "\U0001f1ed\U0001f1fa"; + public const string FLAG_IC = "\U0001f1ee\U0001f1e8"; + public const string FLAG_ID = "\U0001f1ee\U0001f1e9"; + public const string FLAG_IE = "\U0001f1ee\U0001f1ea"; + public const string FLAG_IL = "\U0001f1ee\U0001f1f1"; + public const string FLAG_IM = "\U0001f1ee\U0001f1f2"; + public const string FLAG_IN = "\U0001f1ee\U0001f1f3"; + public const string FLAG_IO = "\U0001f1ee\U0001f1f4"; + public const string FLAG_IQ = "\U0001f1ee\U0001f1f6"; + public const string FLAG_IR = "\U0001f1ee\U0001f1f7"; + public const string FLAG_IS = "\U0001f1ee\U0001f1f8"; + public const string FLAG_IT = "\U0001f1ee\U0001f1f9"; + public const string FLAG_JE = "\U0001f1ef\U0001f1ea"; + public const string FLAG_JM = "\U0001f1ef\U0001f1f2"; + public const string FLAG_JO = "\U0001f1ef\U0001f1f4"; + public const string FLAG_JP = "\U0001f1ef\U0001f1f5"; + public const string FLAG_KE = "\U0001f1f0\U0001f1ea"; + public const string FLAG_KG = "\U0001f1f0\U0001f1ec"; + public const string FLAG_KH = "\U0001f1f0\U0001f1ed"; + public const string FLAG_KI = "\U0001f1f0\U0001f1ee"; + public const string FLAG_KM = "\U0001f1f0\U0001f1f2"; + public const string FLAG_KN = "\U0001f1f0\U0001f1f3"; + public const string FLAG_KP = "\U0001f1f0\U0001f1f5"; + public const string FLAG_KR = "\U0001f1f0\U0001f1f7"; + public const string FLAG_KW = "\U0001f1f0\U0001f1fc"; + public const string FLAG_KY = "\U0001f1f0\U0001f1fe"; + public const string FLAG_KZ = "\U0001f1f0\U0001f1ff"; + public const string FLAG_LA = "\U0001f1f1\U0001f1e6"; + public const string FLAG_LB = "\U0001f1f1\U0001f1e7"; + public const string FLAG_LC = "\U0001f1f1\U0001f1e8"; + public const string FLAG_LI = "\U0001f1f1\U0001f1ee"; + public const string FLAG_LK = "\U0001f1f1\U0001f1f0"; + public const string FLAG_LR = "\U0001f1f1\U0001f1f7"; + public const string FLAG_LS = "\U0001f1f1\U0001f1f8"; + public const string FLAG_LT = "\U0001f1f1\U0001f1f9"; + public const string FLAG_LU = "\U0001f1f1\U0001f1fa"; + public const string FLAG_LV = "\U0001f1f1\U0001f1fb"; + public const string FLAG_LY = "\U0001f1f1\U0001f1fe"; + public const string FLAG_MA = "\U0001f1f2\U0001f1e6"; + public const string FLAG_MC = "\U0001f1f2\U0001f1e8"; + public const string FLAG_MD = "\U0001f1f2\U0001f1e9"; + public const string FLAG_ME = "\U0001f1f2\U0001f1ea"; + public const string FLAG_MF = "\U0001f1f2\U0001f1eb"; + public const string FLAG_MG = "\U0001f1f2\U0001f1ec"; + public const string FLAG_MH = "\U0001f1f2\U0001f1ed"; + public const string FLAG_MK = "\U0001f1f2\U0001f1f0"; + public const string FLAG_ML = "\U0001f1f2\U0001f1f1"; + public const string FLAG_MM = "\U0001f1f2\U0001f1f2"; + public const string FLAG_MN = "\U0001f1f2\U0001f1f3"; + public const string FLAG_MO = "\U0001f1f2\U0001f1f4"; + public const string FLAG_MP = "\U0001f1f2\U0001f1f5"; + public const string FLAG_MQ = "\U0001f1f2\U0001f1f6"; + public const string FLAG_MR = "\U0001f1f2\U0001f1f7"; + public const string FLAG_MS = "\U0001f1f2\U0001f1f8"; + public const string FLAG_MT = "\U0001f1f2\U0001f1f9"; + public const string FLAG_MU = "\U0001f1f2\U0001f1fa"; + public const string FLAG_MV = "\U0001f1f2\U0001f1fb"; + public const string FLAG_MW = "\U0001f1f2\U0001f1fc"; + public const string FLAG_MX = "\U0001f1f2\U0001f1fd"; + public const string FLAG_MY = "\U0001f1f2\U0001f1fe"; + public const string FLAG_MZ = "\U0001f1f2\U0001f1ff"; + public const string FLAG_NA = "\U0001f1f3\U0001f1e6"; + public const string FLAG_NC = "\U0001f1f3\U0001f1e8"; + public const string FLAG_NE = "\U0001f1f3\U0001f1ea"; + public const string FLAG_NF = "\U0001f1f3\U0001f1eb"; + public const string FLAG_NG = "\U0001f1f3\U0001f1ec"; + public const string FLAG_NI = "\U0001f1f3\U0001f1ee"; + public const string FLAG_NL = "\U0001f1f3\U0001f1f1"; + public const string FLAG_NO = "\U0001f1f3\U0001f1f4"; + public const string FLAG_NP = "\U0001f1f3\U0001f1f5"; + public const string FLAG_NR = "\U0001f1f3\U0001f1f7"; + public const string FLAG_NU = "\U0001f1f3\U0001f1fa"; + public const string FLAG_NZ = "\U0001f1f3\U0001f1ff"; + public const string FLAG_OM = "\U0001f1f4\U0001f1f2"; + public const string FLAG_PA = "\U0001f1f5\U0001f1e6"; + public const string FLAG_PE = "\U0001f1f5\U0001f1ea"; + public const string FLAG_PF = "\U0001f1f5\U0001f1eb"; + public const string FLAG_PG = "\U0001f1f5\U0001f1ec"; + public const string FLAG_PH = "\U0001f1f5\U0001f1ed"; + public const string FLAG_PK = "\U0001f1f5\U0001f1f0"; + public const string FLAG_PL = "\U0001f1f5\U0001f1f1"; + public const string FLAG_PM = "\U0001f1f5\U0001f1f2"; + public const string FLAG_PN = "\U0001f1f5\U0001f1f3"; + public const string FLAG_PR = "\U0001f1f5\U0001f1f7"; + public const string FLAG_PS = "\U0001f1f5\U0001f1f8"; + public const string FLAG_PT = "\U0001f1f5\U0001f1f9"; + public const string FLAG_PW = "\U0001f1f5\U0001f1fc"; + public const string FLAG_PY = "\U0001f1f5\U0001f1fe"; + public const string FLAG_QA = "\U0001f1f6\U0001f1e6"; + public const string FLAG_RE = "\U0001f1f7\U0001f1ea"; + public const string FLAG_RO = "\U0001f1f7\U0001f1f4"; + public const string FLAG_RS = "\U0001f1f7\U0001f1f8"; + public const string FLAG_RU = "\U0001f1f7\U0001f1fa"; + public const string FLAG_RW = "\U0001f1f7\U0001f1fc"; + public const string FLAGS = "\U0001f38f"; + public const string FLAG_SA = "\U0001f1f8\U0001f1e6"; + public const string FLAG_SB = "\U0001f1f8\U0001f1e7"; + public const string FLAG_SC = "\U0001f1f8\U0001f1e8"; + public const string FLAG_SD = "\U0001f1f8\U0001f1e9"; + public const string FLAG_SE = "\U0001f1f8\U0001f1ea"; + public const string FLAG_SG = "\U0001f1f8\U0001f1ec"; + public const string FLAG_SH = "\U0001f1f8\U0001f1ed"; + public const string FLAG_SI = "\U0001f1f8\U0001f1ee"; + public const string FLAG_SJ = "\U0001f1f8\U0001f1ef"; + public const string FLAG_SK = "\U0001f1f8\U0001f1f0"; + public const string FLAG_SL = "\U0001f1f8\U0001f1f1"; + public const string FLAG_SM = "\U0001f1f8\U0001f1f2"; + public const string FLAG_SN = "\U0001f1f8\U0001f1f3"; + public const string FLAG_SO = "\U0001f1f8\U0001f1f4"; + public const string FLAG_SR = "\U0001f1f8\U0001f1f7"; + public const string FLAG_SS = "\U0001f1f8\U0001f1f8"; + public const string FLAG_ST = "\U0001f1f8\U0001f1f9"; + public const string FLAG_SV = "\U0001f1f8\U0001f1fb"; + public const string FLAG_SX = "\U0001f1f8\U0001f1fd"; + public const string FLAG_SY = "\U0001f1f8\U0001f1fe"; + public const string FLAG_SZ = "\U0001f1f8\U0001f1ff"; + public const string FLAG_TA = "\U0001f1f9\U0001f1e6"; + public const string FLAG_TC = "\U0001f1f9\U0001f1e8"; + public const string FLAG_TD = "\U0001f1f9\U0001f1e9"; + public const string FLAG_TF = "\U0001f1f9\U0001f1eb"; + public const string FLAG_TG = "\U0001f1f9\U0001f1ec"; + public const string FLAG_TH = "\U0001f1f9\U0001f1ed"; + public const string FLAG_TJ = "\U0001f1f9\U0001f1ef"; + public const string FLAG_TK = "\U0001f1f9\U0001f1f0"; + public const string FLAG_TL = "\U0001f1f9\U0001f1f1"; + public const string FLAG_TM = "\U0001f1f9\U0001f1f2"; + public const string FLAG_TN = "\U0001f1f9\U0001f1f3"; + public const string FLAG_TO = "\U0001f1f9\U0001f1f4"; + public const string FLAG_TR = "\U0001f1f9\U0001f1f7"; + public const string FLAG_TT = "\U0001f1f9\U0001f1f9"; + public const string FLAG_TV = "\U0001f1f9\U0001f1fb"; + public const string FLAG_TW = "\U0001f1f9\U0001f1fc"; + public const string FLAG_TZ = "\U0001f1f9\U0001f1ff"; + public const string FLAG_UA = "\U0001f1fa\U0001f1e6"; + public const string FLAG_UG = "\U0001f1fa\U0001f1ec"; + public const string FLAG_UM = "\U0001f1fa\U0001f1f2"; + public const string FLAG_US = "\U0001f1fa\U0001f1f8"; + public const string FLAG_UY = "\U0001f1fa\U0001f1fe"; + public const string FLAG_UZ = "\U0001f1fa\U0001f1ff"; + public const string FLAG_VA = "\U0001f1fb\U0001f1e6"; + public const string FLAG_VC = "\U0001f1fb\U0001f1e8"; + public const string FLAG_VE = "\U0001f1fb\U0001f1ea"; + public const string FLAG_VG = "\U0001f1fb\U0001f1ec"; + public const string FLAG_VI = "\U0001f1fb\U0001f1ee"; + public const string FLAG_VN = "\U0001f1fb\U0001f1f3"; + public const string FLAG_VU = "\U0001f1fb\U0001f1fa"; + public const string FLAG_WF = "\U0001f1fc\U0001f1eb"; + public const string FLAG_WHITE = "\U0001f3f3"; + public const string FLAG_WS = "\U0001f1fc\U0001f1f8"; + public const string FLAG_XK = "\U0001f1fd\U0001f1f0"; + public const string FLAG_YE = "\U0001f1fe\U0001f1ea"; + public const string FLAG_YT = "\U0001f1fe\U0001f1f9"; + public const string FLAG_ZA = "\U0001f1ff\U0001f1e6"; + public const string FLAG_ZM = "\U0001f1ff\U0001f1f2"; + public const string FLAG_ZW = "\U0001f1ff\U0001f1fc"; + public const string FLAME = "\U0001f525"; + public const string FLAN = "\U0001f36e"; + public const string FLASHLIGHT = "\U0001f526"; + public const string FLEUR_DE_LIS = "\U0000269c"; + public const string FLOPPY_DISK = "\U0001f4be"; + public const string FLOWER_PLAYING_CARDS = "\U0001f3b4"; + public const string FLUSHED = "\U0001f633"; + public const string FOG = "\U0001f32b"; + public const string FOGGY = "\U0001f301"; + public const string FOOTBALL = "\U0001f3c8"; + public const string FOOTPRINTS = "\U0001f463"; + public const string FORK_AND_KNIFE = "\U0001f374"; + public const string FORK_AND_KNIFE_WITH_PLATE = "\U0001f37d"; + public const string FORK_KNIFE_PLATE = "\U0001f37d"; + public const string FOUNTAIN = "\U000026f2"; + public const string FOUR = "\U00000034\U000020e3"; + public const string FOUR_LEAF_CLOVER = "\U0001f340"; + public const string FOX = "\U0001f98a"; + public const string FOX_FACE = "\U0001f98a"; + public const string FRAME_PHOTO = "\U0001f5bc"; + public const string FRAME_WITH_PICTURE = "\U0001f5bc"; + public const string FREE = "\U0001f193"; + public const string FRENCH_BREAD = "\U0001f956"; + public const string FRIED_SHRIMP = "\U0001f364"; + public const string FRIES = "\U0001f35f"; + public const string FROG = "\U0001f438"; + public const string FROWNING = "\U0001f626"; + public const string FROWNING2 = "\U00002639"; + public const string FUELPUMP = "\U000026fd"; + public const string FULL_MOON = "\U0001f315"; + public const string FULL_MOON_WITH_FACE = "\U0001f31d"; + public const string FUNERAL_URN = "\U000026b1"; + public const string GAME_DIE = "\U0001f3b2"; + public const string GAY_PRIDE_FLAG = "\U0001f3f3\U0000fe0f\U0000200d\U0001f308"; + public const string GEAR = "\U00002699"; + public const string GEM = "\U0001f48e"; + public const string GEMINI = "\U0000264a"; + public const string GHOST = "\U0001f47b"; + public const string GIFT = "\U0001f381"; + public const string GIFT_HEART = "\U0001f49d"; + public const string GIRL = "\U0001f467"; + public const string GIRL_SKIN_TONE1 = "\U0001f467\U0001f3fb"; + public const string GIRL_SKIN_TONE2 = "\U0001f467\U0001f3fc"; + public const string GIRL_SKIN_TONE3 = "\U0001f467\U0001f3fd"; + public const string GIRL_SKIN_TONE4 = "\U0001f467\U0001f3fe"; + public const string GIRL_SKIN_TONE5 = "\U0001f467\U0001f3ff"; + public const string GLASS_OF_MILK = "\U0001f95b"; + public const string GLOBE_WITH_MERIDIANS = "\U0001f310"; + public const string GOAL = "\U0001f945"; + public const string GOAL_NET = "\U0001f945"; + public const string GOAT = "\U0001f410"; + public const string GOLF = "\U000026f3"; + public const string GOLFER = "\U0001f3cc"; + public const string GOLFER_SKIN_TONE1 = "\U0001f3cc\U0001f3fb"; + public const string GOLFER_SKIN_TONE2 = "\U0001f3cc\U0001f3fc"; + public const string GOLFER_SKIN_TONE3 = "\U0001f3cc\U0001f3fd"; + public const string GOLFER_SKIN_TONE4 = "\U0001f3cc\U0001f3fe"; + public const string GOLFER_SKIN_TONE5 = "\U0001f3cc\U0001f3ff"; + public const string GORILLA = "\U0001f98d"; + public const string GRANDMA = "\U0001f475"; + public const string GRANDMA_SKIN_TONE1 = "\U0001f475\U0001f3fb"; + public const string GRANDMA_SKIN_TONE2 = "\U0001f475\U0001f3fc"; + public const string GRANDMA_SKIN_TONE3 = "\U0001f475\U0001f3fd"; + public const string GRANDMA_SKIN_TONE4 = "\U0001f475\U0001f3fe"; + public const string GRANDMA_SKIN_TONE5 = "\U0001f475\U0001f3ff"; + public const string GRAPES = "\U0001f347"; + public const string GREEN_APPLE = "\U0001f34f"; + public const string GREEN_BOOK = "\U0001f4d7"; + public const string GREEN_HEART = "\U0001f49a"; + public const string GREEN_SALAD = "\U0001f957"; + public const string GREY_EXCLAMATION = "\U00002755"; + public const string GREY_QUESTION = "\U00002754"; + public const string GRIMACING = "\U0001f62c"; + public const string GRIN = "\U0001f601"; + public const string GRINNING = "\U0001f600"; + public const string GUARDSMAN = "\U0001f482"; + public const string GUARDSMAN_SKIN_TONE1 = "\U0001f482\U0001f3fb"; + public const string GUARDSMAN_SKIN_TONE2 = "\U0001f482\U0001f3fc"; + public const string GUARDSMAN_SKIN_TONE3 = "\U0001f482\U0001f3fd"; + public const string GUARDSMAN_SKIN_TONE4 = "\U0001f482\U0001f3fe"; + public const string GUARDSMAN_SKIN_TONE5 = "\U0001f482\U0001f3ff"; + public const string GUITAR = "\U0001f3b8"; + public const string GUN = "\U0001f52b"; + public const string HAIRCUT = "\U0001f487"; + public const string HAIRCUT_SKIN_TONE1 = "\U0001f487\U0001f3fb"; + public const string HAIRCUT_SKIN_TONE2 = "\U0001f487\U0001f3fc"; + public const string HAIRCUT_SKIN_TONE3 = "\U0001f487\U0001f3fd"; + public const string HAIRCUT_SKIN_TONE4 = "\U0001f487\U0001f3fe"; + public const string HAIRCUT_SKIN_TONE5 = "\U0001f487\U0001f3ff"; + public const string HAMBURGER = "\U0001f354"; + public const string HAMMER = "\U0001f528"; + public const string HAMMER_AND_PICK = "\U00002692"; + public const string HAMMER_AND_WRENCH = "\U0001f6e0"; + public const string HAMMER_PICK = "\U00002692"; + public const string HAMSTER = "\U0001f439"; + public const string HANDBAG = "\U0001f45c"; + public const string HANDBALL = "\U0001f93e"; + public const string HANDBALL_SKIN_TONE1 = "\U0001f93e\U0001f3fb"; + public const string HANDBALL_SKIN_TONE2 = "\U0001f93e\U0001f3fc"; + public const string HANDBALL_SKIN_TONE3 = "\U0001f93e\U0001f3fd"; + public const string HANDBALL_SKIN_TONE4 = "\U0001f93e\U0001f3fe"; + public const string HANDBALL_SKIN_TONE5 = "\U0001f93e\U0001f3ff"; + public const string HANDSHAKE = "\U0001f91d"; + public const string HAND_SPLAYED = "\U0001f590"; + public const string HAND_SPLAYED_SKIN_TONE1 = "\U0001f590\U0001f3fb"; + public const string HAND_SPLAYED_SKIN_TONE2 = "\U0001f590\U0001f3fc"; + public const string HAND_SPLAYED_SKIN_TONE3 = "\U0001f590\U0001f3fd"; + public const string HAND_SPLAYED_SKIN_TONE4 = "\U0001f590\U0001f3fe"; + public const string HAND_SPLAYED_SKIN_TONE5 = "\U0001f590\U0001f3ff"; + public const string HAND_WITH_INDEX_AND_MIDDLE_FINGER_CROSSED = "\U0001f91e"; + public const string HAND_WITH_INDEX_AND_MIDDLE_FINGER_CROSSED_SKIN_TONE1 = "\U0001f91e\U0001f3fb"; + public const string HAND_WITH_INDEX_AND_MIDDLE_FINGER_CROSSED_SKIN_TONE2 = "\U0001f91e\U0001f3fc"; + public const string HAND_WITH_INDEX_AND_MIDDLE_FINGER_CROSSED_SKIN_TONE3 = "\U0001f91e\U0001f3fd"; + public const string HAND_WITH_INDEX_AND_MIDDLE_FINGER_CROSSED_SKIN_TONE4 = "\U0001f91e\U0001f3fe"; + public const string HAND_WITH_INDEX_AND_MIDDLE_FINGER_CROSSED_SKIN_TONE5 = "\U0001f91e\U0001f3ff"; + public const string HANKEY = "\U0001f4a9"; + public const string HASH = "\U00000023\U000020e3"; + public const string HATCHED_CHICK = "\U0001f425"; + public const string HATCHING_CHICK = "\U0001f423"; + public const string HEAD_BANDAGE = "\U0001f915"; + public const string HEADPHONES = "\U0001f3a7"; + public const string HEAR_NO_EVIL = "\U0001f649"; + public const string HEART = "\U00002764"; + public const string HEARTBEAT = "\U0001f493"; + public const string HEART_DECORATION = "\U0001f49f"; + public const string HEART_EXCLAMATION = "\U00002763"; + public const string HEART_EYES = "\U0001f60d"; + public const string HEART_EYES_CAT = "\U0001f63b"; + public const string HEARTPULSE = "\U0001f497"; + public const string HEARTS = "\U00002665"; + public const string HEAVY_CHECK_MARK = "\U00002714"; + public const string HEAVY_DIVISION_SIGN = "\U00002797"; + public const string HEAVY_DOLLAR_SIGN = "\U0001f4b2"; + public const string HEAVY_HEART_EXCLAMATION_MARK_ORNAMENT = "\U00002763"; + public const string HEAVY_MINUS_SIGN = "\U00002796"; + public const string HEAVY_MULTIPLICATION_X = "\U00002716"; + public const string HEAVY_PLUS_SIGN = "\U00002795"; + public const string HELICOPTER = "\U0001f681"; + public const string HELMET_WITH_CROSS = "\U000026d1"; + public const string HELMET_WITH_WHITE_CROSS = "\U000026d1"; + public const string HERB = "\U0001f33f"; + public const string HIBISCUS = "\U0001f33a"; + public const string HIGH_BRIGHTNESS = "\U0001f506"; + public const string HIGH_HEEL = "\U0001f460"; + public const string HOCKEY = "\U0001f3d2"; + public const string HOLE = "\U0001f573"; + public const string HOMES = "\U0001f3d8"; + public const string HONEY_POT = "\U0001f36f"; + public const string HORSE = "\U0001f434"; + public const string HORSE_RACING = "\U0001f3c7"; + public const string HORSE_RACING_SKIN_TONE1 = "\U0001f3c7\U0001f3fb"; + public const string HORSE_RACING_SKIN_TONE2 = "\U0001f3c7\U0001f3fc"; + public const string HORSE_RACING_SKIN_TONE3 = "\U0001f3c7\U0001f3fd"; + public const string HORSE_RACING_SKIN_TONE4 = "\U0001f3c7\U0001f3fe"; + public const string HORSE_RACING_SKIN_TONE5 = "\U0001f3c7\U0001f3ff"; + public const string HOSPITAL = "\U0001f3e5"; + public const string HOTDOG = "\U0001f32d"; + public const string HOT_DOG = "\U0001f32d"; + public const string HOTEL = "\U0001f3e8"; + public const string HOT_PEPPER = "\U0001f336"; + public const string HOTSPRINGS = "\U00002668"; + public const string HOURGLASS = "\U0000231b"; + public const string HOURGLASS_FLOWING_SAND = "\U000023f3"; + public const string HOUSE = "\U0001f3e0"; + public const string HOUSE_ABANDONED = "\U0001f3da"; + public const string HOUSE_BUILDINGS = "\U0001f3d8"; + public const string HOUSE_WITH_GARDEN = "\U0001f3e1"; + public const string HUGGING = "\U0001f917"; + public const string HUGGING_FACE = "\U0001f917"; + public const string HUSHED = "\U0001f62f"; + public const string ICECREAM = "\U0001f366"; + public const string ICE_CREAM = "\U0001f368"; + public const string ICE_SKATE = "\U000026f8"; + public const string ID = "\U0001f194"; + public const string IDEOGRAPH_ADVANTAGE = "\U0001f250"; + public const string IMP = "\U0001f47f"; + public const string INBOX_TRAY = "\U0001f4e5"; + public const string INCOMING_ENVELOPE = "\U0001f4e8"; + public const string INFORMATION_DESK_PERSON = "\U0001f481"; + public const string INFORMATION_DESK_PERSON_SKIN_TONE1 = "\U0001f481\U0001f3fb"; + public const string INFORMATION_DESK_PERSON_SKIN_TONE2 = "\U0001f481\U0001f3fc"; + public const string INFORMATION_DESK_PERSON_SKIN_TONE3 = "\U0001f481\U0001f3fd"; + public const string INFORMATION_DESK_PERSON_SKIN_TONE4 = "\U0001f481\U0001f3fe"; + public const string INFORMATION_DESK_PERSON_SKIN_TONE5 = "\U0001f481\U0001f3ff"; + public const string INFORMATION_SOURCE = "\U00002139"; + public const string INNOCENT = "\U0001f607"; + public const string INTERROBANG = "\U00002049"; + public const string IPHONE = "\U0001f4f1"; + public const string ISLAND = "\U0001f3dd"; + public const string IZAKAYA_LANTERN = "\U0001f3ee"; + public const string JACK_O_LANTERN = "\U0001f383"; + public const string JAPAN = "\U0001f5fe"; + public const string JAPANESE_CASTLE = "\U0001f3ef"; + public const string JAPANESE_GOBLIN = "\U0001f47a"; + public const string JAPANESE_OGRE = "\U0001f479"; + public const string JEANS = "\U0001f456"; + public const string JOY = "\U0001f602"; + public const string JOY_CAT = "\U0001f639"; + public const string JOYSTICK = "\U0001f579"; + public const string JUGGLER = "\U0001f939"; + public const string JUGGLER_SKIN_TONE1 = "\U0001f939\U0001f3fb"; + public const string JUGGLER_SKIN_TONE2 = "\U0001f939\U0001f3fc"; + public const string JUGGLER_SKIN_TONE3 = "\U0001f939\U0001f3fd"; + public const string JUGGLER_SKIN_TONE4 = "\U0001f939\U0001f3fe"; + public const string JUGGLER_SKIN_TONE5 = "\U0001f939\U0001f3ff"; + public const string JUGGLING = "\U0001f939"; + public const string JUGGLING_SKIN_TONE1 = "\U0001f939\U0001f3fb"; + public const string JUGGLING_SKIN_TONE2 = "\U0001f939\U0001f3fc"; + public const string JUGGLING_SKIN_TONE3 = "\U0001f939\U0001f3fd"; + public const string JUGGLING_SKIN_TONE4 = "\U0001f939\U0001f3fe"; + public const string JUGGLING_SKIN_TONE5 = "\U0001f939\U0001f3ff"; + public const string KAABA = "\U0001f54b"; + public const string KARATE_UNIFORM = "\U0001f94b"; + public const string KAYAK = "\U0001f6f6"; + public const string KEY = "\U0001f511"; + public const string KEY2 = "\U0001f5dd"; + public const string KEYBOARD = "\U00002328"; + public const string KEYCAP_ASTERISK = "\U0000002a\U000020e3"; + public const string KEYCAP_TEN = "\U0001f51f"; + public const string KIMONO = "\U0001f458"; + public const string KISS = "\U0001f48b"; + public const string KISSING = "\U0001f617"; + public const string KISSING_CAT = "\U0001f63d"; + public const string KISSING_CLOSED_EYES = "\U0001f61a"; + public const string KISSING_HEART = "\U0001f618"; + public const string KISSING_SMILING_EYES = "\U0001f619"; + public const string KISS_MM = "\U0001f468\U0000200d\U00002764\U0000fe0f\U0000200d\U0001f48b\U0000200d\U0001f468"; + public const string KISS_WW = "\U0001f469\U0000200d\U00002764\U0000fe0f\U0000200d\U0001f48b\U0000200d\U0001f469"; + public const string KIWI = "\U0001f95d"; + public const string KIWIFRUIT = "\U0001f95d"; + public const string KNIFE = "\U0001f52a"; + public const string KOALA = "\U0001f428"; + public const string KOKO = "\U0001f201"; + public const string LABEL = "\U0001f3f7"; + public const string LARGE_BLUE_CIRCLE = "\U0001f535"; + public const string LARGE_BLUE_DIAMOND = "\U0001f537"; + public const string LARGE_ORANGE_DIAMOND = "\U0001f536"; + public const string LAST_QUARTER_MOON = "\U0001f317"; + public const string LAST_QUARTER_MOON_WITH_FACE = "\U0001f31c"; + public const string LATIN_CROSS = "\U0000271d"; + public const string LAUGHING = "\U0001f606"; + public const string LEAVES = "\U0001f343"; + public const string LEDGER = "\U0001f4d2"; + public const string LEFT_FACING_FIST = "\U0001f91b"; + public const string LEFT_FACING_FIST_SKIN_TONE1 = "\U0001f91b\U0001f3fb"; + public const string LEFT_FACING_FIST_SKIN_TONE2 = "\U0001f91b\U0001f3fc"; + public const string LEFT_FACING_FIST_SKIN_TONE3 = "\U0001f91b\U0001f3fd"; + public const string LEFT_FACING_FIST_SKIN_TONE4 = "\U0001f91b\U0001f3fe"; + public const string LEFT_FACING_FIST_SKIN_TONE5 = "\U0001f91b\U0001f3ff"; + public const string LEFT_FIST = "\U0001f91b"; + public const string LEFT_FIST_SKIN_TONE1 = "\U0001f91b\U0001f3fb"; + public const string LEFT_FIST_SKIN_TONE2 = "\U0001f91b\U0001f3fc"; + public const string LEFT_FIST_SKIN_TONE3 = "\U0001f91b\U0001f3fd"; + public const string LEFT_FIST_SKIN_TONE4 = "\U0001f91b\U0001f3fe"; + public const string LEFT_FIST_SKIN_TONE5 = "\U0001f91b\U0001f3ff"; + public const string LEFT_LUGGAGE = "\U0001f6c5"; + public const string LEFT_RIGHT_ARROW = "\U00002194"; + public const string LEFT_SPEECH_BUBBLE = "\U0001f5e8"; + public const string LEFTWARDS_ARROW_WITH_HOOK = "\U000021a9"; + public const string LEMON = "\U0001f34b"; + public const string LEO = "\U0000264c"; + public const string LEOPARD = "\U0001f406"; + public const string LEVEL_SLIDER = "\U0001f39a"; + public const string LEVITATE = "\U0001f574"; + public const string LEVITATE_SKIN_TONE1 = "\U0001f574\U0001f3fb"; + public const string LEVITATE_SKIN_TONE2 = "\U0001f574\U0001f3fc"; + public const string LEVITATE_SKIN_TONE3 = "\U0001f574\U0001f3fd"; + public const string LEVITATE_SKIN_TONE4 = "\U0001f574\U0001f3fe"; + public const string LEVITATE_SKIN_TONE5 = "\U0001f574\U0001f3ff"; + public const string LIAR = "\U0001f925"; + public const string LIBRA = "\U0000264e"; + public const string LIFTER = "\U0001f3cb"; + public const string LIFTER_SKIN_TONE1 = "\U0001f3cb\U0001f3fb"; + public const string LIFTER_SKIN_TONE2 = "\U0001f3cb\U0001f3fc"; + public const string LIFTER_SKIN_TONE3 = "\U0001f3cb\U0001f3fd"; + public const string LIFTER_SKIN_TONE4 = "\U0001f3cb\U0001f3fe"; + public const string LIFTER_SKIN_TONE5 = "\U0001f3cb\U0001f3ff"; + public const string LIGHT_RAIL = "\U0001f688"; + public const string LINK = "\U0001f517"; + public const string LINKED_PAPERCLIPS = "\U0001f587"; + public const string LION = "\U0001f981"; + public const string LION_FACE = "\U0001f981"; + public const string LIPS = "\U0001f444"; + public const string LIPSTICK = "\U0001f484"; + public const string LIZARD = "\U0001f98e"; + public const string LOCK = "\U0001f512"; + public const string LOCK_WITH_INK_PEN = "\U0001f50f"; + public const string LOLLIPOP = "\U0001f36d"; + public const string LOOP = "\U000027bf"; + public const string LOUD_SOUND = "\U0001f50a"; + public const string LOUDSPEAKER = "\U0001f4e2"; + public const string LOVE_HOTEL = "\U0001f3e9"; + public const string LOVE_LETTER = "\U0001f48c"; + public const string LOW_BRIGHTNESS = "\U0001f505"; + public const string LOWER_LEFT_BALLPOINT_PEN = "\U0001f58a"; + public const string LOWER_LEFT_CRAYON = "\U0001f58d"; + public const string LOWER_LEFT_FOUNTAIN_PEN = "\U0001f58b"; + public const string LOWER_LEFT_PAINTBRUSH = "\U0001f58c"; + public const string LYING_FACE = "\U0001f925"; public const string M = "\U000024c2"; - public const string Mag = "\U0001f50d"; - public const string MagRight = "\U0001f50e"; - public const string Mahjong = "\U0001f004"; - public const string Mailbox = "\U0001f4eb"; - public const string MailboxClosed = "\U0001f4ea"; - public const string MailboxWithMail = "\U0001f4ec"; - public const string MailboxWithNoMail = "\U0001f4ed"; - public const string MaleDancer = "\U0001f57a"; - public const string MaleDancerSkinTone1 = "\U0001f57a\U0001f3fb"; - public const string MaleDancerSkinTone2 = "\U0001f57a\U0001f3fc"; - public const string MaleDancerSkinTone3 = "\U0001f57a\U0001f3fd"; - public const string MaleDancerSkinTone4 = "\U0001f57a\U0001f3fe"; - public const string MaleDancerSkinTone5 = "\U0001f57a\U0001f3ff"; - public const string Man = "\U0001f468"; - public const string ManDancing = "\U0001f57a"; - public const string ManDancingSkinTone1 = "\U0001f57a\U0001f3fb"; - public const string ManDancingSkinTone2 = "\U0001f57a\U0001f3fc"; - public const string ManDancingSkinTone3 = "\U0001f57a\U0001f3fd"; - public const string ManDancingSkinTone4 = "\U0001f57a\U0001f3fe"; - public const string ManDancingSkinTone5 = "\U0001f57a\U0001f3ff"; - public const string ManInBusinessSuitLevitating = "\U0001f574"; - public const string ManInBusinessSuitLevitatingSkinTone1 = "\U0001f574\U0001f3fb"; - public const string ManInBusinessSuitLevitatingSkinTone2 = "\U0001f574\U0001f3fc"; - public const string ManInBusinessSuitLevitatingSkinTone3 = "\U0001f574\U0001f3fd"; - public const string ManInBusinessSuitLevitatingSkinTone4 = "\U0001f574\U0001f3fe"; - public const string ManInBusinessSuitLevitatingSkinTone5 = "\U0001f574\U0001f3ff"; - public const string ManInTuxedo = "\U0001f935"; - public const string ManInTuxedoSkinTone1 = "\U0001f935\U0001f3fb"; - public const string ManInTuxedoSkinTone2 = "\U0001f935\U0001f3fc"; - public const string ManInTuxedoSkinTone3 = "\U0001f935\U0001f3fd"; - public const string ManInTuxedoSkinTone4 = "\U0001f935\U0001f3fe"; - public const string ManInTuxedoSkinTone5 = "\U0001f935\U0001f3ff"; - public const string ManSkinTone1 = "\U0001f468\U0001f3fb"; - public const string ManSkinTone2 = "\U0001f468\U0001f3fc"; - public const string ManSkinTone3 = "\U0001f468\U0001f3fd"; - public const string ManSkinTone4 = "\U0001f468\U0001f3fe"; - public const string ManSkinTone5 = "\U0001f468\U0001f3ff"; - public const string MansShoe = "\U0001f45e"; - public const string MantlepieceClock = "\U0001f570"; - public const string ManWithGuaPiMao = "\U0001f472"; - public const string ManWithGuaPiMaoSkinTone1 = "\U0001f472\U0001f3fb"; - public const string ManWithGuaPiMaoSkinTone2 = "\U0001f472\U0001f3fc"; - public const string ManWithGuaPiMaoSkinTone3 = "\U0001f472\U0001f3fd"; - public const string ManWithGuaPiMaoSkinTone4 = "\U0001f472\U0001f3fe"; - public const string ManWithGuaPiMaoSkinTone5 = "\U0001f472\U0001f3ff"; - public const string ManWithTurban = "\U0001f473"; - public const string ManWithTurbanSkinTone1 = "\U0001f473\U0001f3fb"; - public const string ManWithTurbanSkinTone2 = "\U0001f473\U0001f3fc"; - public const string ManWithTurbanSkinTone3 = "\U0001f473\U0001f3fd"; - public const string ManWithTurbanSkinTone4 = "\U0001f473\U0001f3fe"; - public const string ManWithTurbanSkinTone5 = "\U0001f473\U0001f3ff"; - public const string Map = "\U0001f5fa"; - public const string MapleLeaf = "\U0001f341"; - public const string MartialArtsUniform = "\U0001f94b"; - public const string Mask = "\U0001f637"; - public const string Massage = "\U0001f486"; - public const string MassageSkinTone1 = "\U0001f486\U0001f3fb"; - public const string MassageSkinTone2 = "\U0001f486\U0001f3fc"; - public const string MassageSkinTone3 = "\U0001f486\U0001f3fd"; - public const string MassageSkinTone4 = "\U0001f486\U0001f3fe"; - public const string MassageSkinTone5 = "\U0001f486\U0001f3ff"; - public const string MeatOnBone = "\U0001f356"; - public const string Medal = "\U0001f3c5"; - public const string Mega = "\U0001f4e3"; - public const string Melon = "\U0001f348"; - public const string Menorah = "\U0001f54e"; - public const string Mens = "\U0001f6b9"; - public const string Metal = "\U0001f918"; - public const string MetalSkinTone1 = "\U0001f918\U0001f3fb"; - public const string MetalSkinTone2 = "\U0001f918\U0001f3fc"; - public const string MetalSkinTone3 = "\U0001f918\U0001f3fd"; - public const string MetalSkinTone4 = "\U0001f918\U0001f3fe"; - public const string MetalSkinTone5 = "\U0001f918\U0001f3ff"; - public const string Metro = "\U0001f687"; - public const string Microphone = "\U0001f3a4"; - public const string Microphone2 = "\U0001f399"; - public const string Microscope = "\U0001f52c"; - public const string MiddleFinger = "\U0001f595"; - public const string MiddleFingerSkinTone1 = "\U0001f595\U0001f3fb"; - public const string MiddleFingerSkinTone2 = "\U0001f595\U0001f3fc"; - public const string MiddleFingerSkinTone3 = "\U0001f595\U0001f3fd"; - public const string MiddleFingerSkinTone4 = "\U0001f595\U0001f3fe"; - public const string MiddleFingerSkinTone5 = "\U0001f595\U0001f3ff"; - public const string MilitaryMedal = "\U0001f396"; - public const string Milk = "\U0001f95b"; - public const string MilkyWay = "\U0001f30c"; - public const string Minibus = "\U0001f690"; - public const string Minidisc = "\U0001f4bd"; - public const string MobilePhoneOff = "\U0001f4f4"; - public const string Moneybag = "\U0001f4b0"; - public const string MoneyMouth = "\U0001f911"; - public const string MoneyMouthFace = "\U0001f911"; - public const string MoneyWithWings = "\U0001f4b8"; - public const string Monkey = "\U0001f412"; - public const string MonkeyFace = "\U0001f435"; - public const string Monorail = "\U0001f69d"; - public const string MortarBoard = "\U0001f393"; - public const string Mosque = "\U0001f54c"; - public const string MotherChristmas = "\U0001f936"; - public const string MotherChristmasSkinTone1 = "\U0001f936\U0001f3fb"; - public const string MotherChristmasSkinTone2 = "\U0001f936\U0001f3fc"; - public const string MotherChristmasSkinTone3 = "\U0001f936\U0001f3fd"; - public const string MotherChristmasSkinTone4 = "\U0001f936\U0001f3fe"; - public const string MotherChristmasSkinTone5 = "\U0001f936\U0001f3ff"; - public const string Motorbike = "\U0001f6f5"; - public const string Motorboat = "\U0001f6e5"; - public const string Motorcycle = "\U0001f3cd"; - public const string MotorScooter = "\U0001f6f5"; - public const string Motorway = "\U0001f6e3"; - public const string Mountain = "\U000026f0"; - public const string MountainBicyclist = "\U0001f6b5"; - public const string MountainBicyclistSkinTone1 = "\U0001f6b5\U0001f3fb"; - public const string MountainBicyclistSkinTone2 = "\U0001f6b5\U0001f3fc"; - public const string MountainBicyclistSkinTone3 = "\U0001f6b5\U0001f3fd"; - public const string MountainBicyclistSkinTone4 = "\U0001f6b5\U0001f3fe"; - public const string MountainBicyclistSkinTone5 = "\U0001f6b5\U0001f3ff"; - public const string MountainCableway = "\U0001f6a0"; - public const string MountainRailway = "\U0001f69e"; - public const string MountainSnow = "\U0001f3d4"; - public const string MountFuji = "\U0001f5fb"; - public const string Mouse = "\U0001f42d"; - public const string Mouse2 = "\U0001f401"; - public const string MouseThreeButton = "\U0001f5b1"; - public const string MovieCamera = "\U0001f3a5"; - public const string Moyai = "\U0001f5ff"; - public const string MrsClaus = "\U0001f936"; - public const string MrsClausSkinTone1 = "\U0001f936\U0001f3fb"; - public const string MrsClausSkinTone2 = "\U0001f936\U0001f3fc"; - public const string MrsClausSkinTone3 = "\U0001f936\U0001f3fd"; - public const string MrsClausSkinTone4 = "\U0001f936\U0001f3fe"; - public const string MrsClausSkinTone5 = "\U0001f936\U0001f3ff"; - public const string Muscle = "\U0001f4aa"; - public const string MuscleSkinTone1 = "\U0001f4aa\U0001f3fb"; - public const string MuscleSkinTone2 = "\U0001f4aa\U0001f3fc"; - public const string MuscleSkinTone3 = "\U0001f4aa\U0001f3fd"; - public const string MuscleSkinTone4 = "\U0001f4aa\U0001f3fe"; - public const string MuscleSkinTone5 = "\U0001f4aa\U0001f3ff"; - public const string Mushroom = "\U0001f344"; - public const string MusicalKeyboard = "\U0001f3b9"; - public const string MusicalNote = "\U0001f3b5"; - public const string MusicalScore = "\U0001f3bc"; - public const string Mute = "\U0001f507"; - public const string NailCare = "\U0001f485"; - public const string NailCareSkinTone1 = "\U0001f485\U0001f3fb"; - public const string NailCareSkinTone2 = "\U0001f485\U0001f3fc"; - public const string NailCareSkinTone3 = "\U0001f485\U0001f3fd"; - public const string NailCareSkinTone4 = "\U0001f485\U0001f3fe"; - public const string NailCareSkinTone5 = "\U0001f485\U0001f3ff"; - public const string NameBadge = "\U0001f4db"; - public const string NationalPark = "\U0001f3de"; - public const string NauseatedFace = "\U0001f922"; - public const string Necktie = "\U0001f454"; - public const string NegativeSquaredCrossMark = "\U0000274e"; - public const string Nerd = "\U0001f913"; - public const string NerdFace = "\U0001f913"; - public const string NeutralFace = "\U0001f610"; - public const string New = "\U0001f195"; - public const string NewMoon = "\U0001f311"; - public const string NewMoonWithFace = "\U0001f31a"; - public const string Newspaper = "\U0001f4f0"; - public const string Newspaper2 = "\U0001f5de"; - public const string NextTrack = "\U000023ed"; - public const string Ng = "\U0001f196"; - public const string NightWithStars = "\U0001f303"; - public const string Nine = "\U00000039\U000020e3"; - public const string NoBell = "\U0001f515"; - public const string NoBicycles = "\U0001f6b3"; - public const string NoEntry = "\U000026d4"; - public const string NoEntrySign = "\U0001f6ab"; - public const string NoGood = "\U0001f645"; - public const string NoGoodSkinTone1 = "\U0001f645\U0001f3fb"; - public const string NoGoodSkinTone2 = "\U0001f645\U0001f3fc"; - public const string NoGoodSkinTone3 = "\U0001f645\U0001f3fd"; - public const string NoGoodSkinTone4 = "\U0001f645\U0001f3fe"; - public const string NoGoodSkinTone5 = "\U0001f645\U0001f3ff"; - public const string NoMobilePhones = "\U0001f4f5"; - public const string NoMouth = "\U0001f636"; - public const string NonPotableWater = "\U0001f6b1"; - public const string NoPedestrians = "\U0001f6b7"; - public const string Nose = "\U0001f443"; - public const string NoseSkinTone1 = "\U0001f443\U0001f3fb"; - public const string NoseSkinTone2 = "\U0001f443\U0001f3fc"; - public const string NoseSkinTone3 = "\U0001f443\U0001f3fd"; - public const string NoseSkinTone4 = "\U0001f443\U0001f3fe"; - public const string NoseSkinTone5 = "\U0001f443\U0001f3ff"; - public const string NoSmoking = "\U0001f6ad"; - public const string Notebook = "\U0001f4d3"; - public const string NotebookWithDecorativeCover = "\U0001f4d4"; - public const string NotepadSpiral = "\U0001f5d2"; - public const string Notes = "\U0001f3b6"; - public const string NutAndBolt = "\U0001f529"; + public const string MAG = "\U0001f50d"; + public const string MAG_RIGHT = "\U0001f50e"; + public const string MAHJONG = "\U0001f004"; + public const string MAILBOX = "\U0001f4eb"; + public const string MAILBOX_CLOSED = "\U0001f4ea"; + public const string MAILBOX_WITH_MAIL = "\U0001f4ec"; + public const string MAILBOX_WITH_NO_MAIL = "\U0001f4ed"; + public const string MALE_DANCER = "\U0001f57a"; + public const string MALE_DANCER_SKIN_TONE1 = "\U0001f57a\U0001f3fb"; + public const string MALE_DANCER_SKIN_TONE2 = "\U0001f57a\U0001f3fc"; + public const string MALE_DANCER_SKIN_TONE3 = "\U0001f57a\U0001f3fd"; + public const string MALE_DANCER_SKIN_TONE4 = "\U0001f57a\U0001f3fe"; + public const string MALE_DANCER_SKIN_TONE5 = "\U0001f57a\U0001f3ff"; + public const string MAN = "\U0001f468"; + public const string MAN_DANCING = "\U0001f57a"; + public const string MAN_DANCING_SKIN_TONE1 = "\U0001f57a\U0001f3fb"; + public const string MAN_DANCING_SKIN_TONE2 = "\U0001f57a\U0001f3fc"; + public const string MAN_DANCING_SKIN_TONE3 = "\U0001f57a\U0001f3fd"; + public const string MAN_DANCING_SKIN_TONE4 = "\U0001f57a\U0001f3fe"; + public const string MAN_DANCING_SKIN_TONE5 = "\U0001f57a\U0001f3ff"; + public const string MAN_IN_BUSINESS_SUIT_LEVITATING = "\U0001f574"; + public const string MAN_IN_BUSINESS_SUIT_LEVITATING_SKIN_TONE1 = "\U0001f574\U0001f3fb"; + public const string MAN_IN_BUSINESS_SUIT_LEVITATING_SKIN_TONE2 = "\U0001f574\U0001f3fc"; + public const string MAN_IN_BUSINESS_SUIT_LEVITATING_SKIN_TONE3 = "\U0001f574\U0001f3fd"; + public const string MAN_IN_BUSINESS_SUIT_LEVITATING_SKIN_TONE4 = "\U0001f574\U0001f3fe"; + public const string MAN_IN_BUSINESS_SUIT_LEVITATING_SKIN_TONE5 = "\U0001f574\U0001f3ff"; + public const string MAN_IN_TUXEDO = "\U0001f935"; + public const string MAN_IN_TUXEDO_SKIN_TONE1 = "\U0001f935\U0001f3fb"; + public const string MAN_IN_TUXEDO_SKIN_TONE2 = "\U0001f935\U0001f3fc"; + public const string MAN_IN_TUXEDO_SKIN_TONE3 = "\U0001f935\U0001f3fd"; + public const string MAN_IN_TUXEDO_SKIN_TONE4 = "\U0001f935\U0001f3fe"; + public const string MAN_IN_TUXEDO_SKIN_TONE5 = "\U0001f935\U0001f3ff"; + public const string MAN_SKIN_TONE1 = "\U0001f468\U0001f3fb"; + public const string MAN_SKIN_TONE2 = "\U0001f468\U0001f3fc"; + public const string MAN_SKIN_TONE3 = "\U0001f468\U0001f3fd"; + public const string MAN_SKIN_TONE4 = "\U0001f468\U0001f3fe"; + public const string MAN_SKIN_TONE5 = "\U0001f468\U0001f3ff"; + public const string MANS_SHOE = "\U0001f45e"; + public const string MANTLEPIECE_CLOCK = "\U0001f570"; + public const string MAN_WITH_GUA_PI_MAO = "\U0001f472"; + public const string MAN_WITH_GUA_PI_MAO_SKIN_TONE1 = "\U0001f472\U0001f3fb"; + public const string MAN_WITH_GUA_PI_MAO_SKIN_TONE2 = "\U0001f472\U0001f3fc"; + public const string MAN_WITH_GUA_PI_MAO_SKIN_TONE3 = "\U0001f472\U0001f3fd"; + public const string MAN_WITH_GUA_PI_MAO_SKIN_TONE4 = "\U0001f472\U0001f3fe"; + public const string MAN_WITH_GUA_PI_MAO_SKIN_TONE5 = "\U0001f472\U0001f3ff"; + public const string MAN_WITH_TURBAN = "\U0001f473"; + public const string MAN_WITH_TURBAN_SKIN_TONE1 = "\U0001f473\U0001f3fb"; + public const string MAN_WITH_TURBAN_SKIN_TONE2 = "\U0001f473\U0001f3fc"; + public const string MAN_WITH_TURBAN_SKIN_TONE3 = "\U0001f473\U0001f3fd"; + public const string MAN_WITH_TURBAN_SKIN_TONE4 = "\U0001f473\U0001f3fe"; + public const string MAN_WITH_TURBAN_SKIN_TONE5 = "\U0001f473\U0001f3ff"; + public const string MAP = "\U0001f5fa"; + public const string MAPLE_LEAF = "\U0001f341"; + public const string MARTIAL_ARTS_UNIFORM = "\U0001f94b"; + public const string MASK = "\U0001f637"; + public const string MASSAGE = "\U0001f486"; + public const string MASSAGE_SKIN_TONE1 = "\U0001f486\U0001f3fb"; + public const string MASSAGE_SKIN_TONE2 = "\U0001f486\U0001f3fc"; + public const string MASSAGE_SKIN_TONE3 = "\U0001f486\U0001f3fd"; + public const string MASSAGE_SKIN_TONE4 = "\U0001f486\U0001f3fe"; + public const string MASSAGE_SKIN_TONE5 = "\U0001f486\U0001f3ff"; + public const string MEAT_ON_BONE = "\U0001f356"; + public const string MEDAL = "\U0001f3c5"; + public const string MEGA = "\U0001f4e3"; + public const string MELON = "\U0001f348"; + public const string MENORAH = "\U0001f54e"; + public const string MENS = "\U0001f6b9"; + public const string METAL = "\U0001f918"; + public const string METAL_SKIN_TONE1 = "\U0001f918\U0001f3fb"; + public const string METAL_SKIN_TONE2 = "\U0001f918\U0001f3fc"; + public const string METAL_SKIN_TONE3 = "\U0001f918\U0001f3fd"; + public const string METAL_SKIN_TONE4 = "\U0001f918\U0001f3fe"; + public const string METAL_SKIN_TONE5 = "\U0001f918\U0001f3ff"; + public const string METRO = "\U0001f687"; + public const string MICROPHONE = "\U0001f3a4"; + public const string MICROPHONE2 = "\U0001f399"; + public const string MICROSCOPE = "\U0001f52c"; + public const string MIDDLE_FINGER = "\U0001f595"; + public const string MIDDLE_FINGER_SKIN_TONE1 = "\U0001f595\U0001f3fb"; + public const string MIDDLE_FINGER_SKIN_TONE2 = "\U0001f595\U0001f3fc"; + public const string MIDDLE_FINGER_SKIN_TONE3 = "\U0001f595\U0001f3fd"; + public const string MIDDLE_FINGER_SKIN_TONE4 = "\U0001f595\U0001f3fe"; + public const string MIDDLE_FINGER_SKIN_TONE5 = "\U0001f595\U0001f3ff"; + public const string MILITARY_MEDAL = "\U0001f396"; + public const string MILK = "\U0001f95b"; + public const string MILKY_WAY = "\U0001f30c"; + public const string MINIBUS = "\U0001f690"; + public const string MINIDISC = "\U0001f4bd"; + public const string MOBILE_PHONE_OFF = "\U0001f4f4"; + public const string MONEYBAG = "\U0001f4b0"; + public const string MONEY_MOUTH = "\U0001f911"; + public const string MONEY_MOUTH_FACE = "\U0001f911"; + public const string MONEY_WITH_WINGS = "\U0001f4b8"; + public const string MONKEY = "\U0001f412"; + public const string MONKEY_FACE = "\U0001f435"; + public const string MONORAIL = "\U0001f69d"; + public const string MORTAR_BOARD = "\U0001f393"; + public const string MOSQUE = "\U0001f54c"; + public const string MOTHER_CHRISTMAS = "\U0001f936"; + public const string MOTHER_CHRISTMAS_SKIN_TONE1 = "\U0001f936\U0001f3fb"; + public const string MOTHER_CHRISTMAS_SKIN_TONE2 = "\U0001f936\U0001f3fc"; + public const string MOTHER_CHRISTMAS_SKIN_TONE3 = "\U0001f936\U0001f3fd"; + public const string MOTHER_CHRISTMAS_SKIN_TONE4 = "\U0001f936\U0001f3fe"; + public const string MOTHER_CHRISTMAS_SKIN_TONE5 = "\U0001f936\U0001f3ff"; + public const string MOTORBIKE = "\U0001f6f5"; + public const string MOTORBOAT = "\U0001f6e5"; + public const string MOTORCYCLE = "\U0001f3cd"; + public const string MOTOR_SCOOTER = "\U0001f6f5"; + public const string MOTORWAY = "\U0001f6e3"; + public const string MOUNTAIN = "\U000026f0"; + public const string MOUNTAIN_BICYCLIST = "\U0001f6b5"; + public const string MOUNTAIN_BICYCLIST_SKIN_TONE1 = "\U0001f6b5\U0001f3fb"; + public const string MOUNTAIN_BICYCLIST_SKIN_TONE2 = "\U0001f6b5\U0001f3fc"; + public const string MOUNTAIN_BICYCLIST_SKIN_TONE3 = "\U0001f6b5\U0001f3fd"; + public const string MOUNTAIN_BICYCLIST_SKIN_TONE4 = "\U0001f6b5\U0001f3fe"; + public const string MOUNTAIN_BICYCLIST_SKIN_TONE5 = "\U0001f6b5\U0001f3ff"; + public const string MOUNTAIN_CABLEWAY = "\U0001f6a0"; + public const string MOUNTAIN_RAILWAY = "\U0001f69e"; + public const string MOUNTAIN_SNOW = "\U0001f3d4"; + public const string MOUNT_FUJI = "\U0001f5fb"; + public const string MOUSE = "\U0001f42d"; + public const string MOUSE2 = "\U0001f401"; + public const string MOUSE_THREE_BUTTON = "\U0001f5b1"; + public const string MOVIE_CAMERA = "\U0001f3a5"; + public const string MOYAI = "\U0001f5ff"; + public const string MRS_CLAUS = "\U0001f936"; + public const string MRS_CLAUS_SKIN_TONE1 = "\U0001f936\U0001f3fb"; + public const string MRS_CLAUS_SKIN_TONE2 = "\U0001f936\U0001f3fc"; + public const string MRS_CLAUS_SKIN_TONE3 = "\U0001f936\U0001f3fd"; + public const string MRS_CLAUS_SKIN_TONE4 = "\U0001f936\U0001f3fe"; + public const string MRS_CLAUS_SKIN_TONE5 = "\U0001f936\U0001f3ff"; + public const string MUSCLE = "\U0001f4aa"; + public const string MUSCLE_SKIN_TONE1 = "\U0001f4aa\U0001f3fb"; + public const string MUSCLE_SKIN_TONE2 = "\U0001f4aa\U0001f3fc"; + public const string MUSCLE_SKIN_TONE3 = "\U0001f4aa\U0001f3fd"; + public const string MUSCLE_SKIN_TONE4 = "\U0001f4aa\U0001f3fe"; + public const string MUSCLE_SKIN_TONE5 = "\U0001f4aa\U0001f3ff"; + public const string MUSHROOM = "\U0001f344"; + public const string MUSICAL_KEYBOARD = "\U0001f3b9"; + public const string MUSICAL_NOTE = "\U0001f3b5"; + public const string MUSICAL_SCORE = "\U0001f3bc"; + public const string MUTE = "\U0001f507"; + public const string NAIL_CARE = "\U0001f485"; + public const string NAIL_CARE_SKIN_TONE1 = "\U0001f485\U0001f3fb"; + public const string NAIL_CARE_SKIN_TONE2 = "\U0001f485\U0001f3fc"; + public const string NAIL_CARE_SKIN_TONE3 = "\U0001f485\U0001f3fd"; + public const string NAIL_CARE_SKIN_TONE4 = "\U0001f485\U0001f3fe"; + public const string NAIL_CARE_SKIN_TONE5 = "\U0001f485\U0001f3ff"; + public const string NAME_BADGE = "\U0001f4db"; + public const string NATIONAL_PARK = "\U0001f3de"; + public const string NAUSEATED_FACE = "\U0001f922"; + public const string NECKTIE = "\U0001f454"; + public const string NEGATIVE_SQUARED_CROSS_MARK = "\U0000274e"; + public const string NERD = "\U0001f913"; + public const string NERD_FACE = "\U0001f913"; + public const string NEUTRAL_FACE = "\U0001f610"; + public const string NEW = "\U0001f195"; + public const string NEW_MOON = "\U0001f311"; + public const string NEW_MOON_WITH_FACE = "\U0001f31a"; + public const string NEWSPAPER = "\U0001f4f0"; + public const string NEWSPAPER2 = "\U0001f5de"; + public const string NEXT_TRACK = "\U000023ed"; + public const string NG = "\U0001f196"; + public const string NIGHT_WITH_STARS = "\U0001f303"; + public const string NINE = "\U00000039\U000020e3"; + public const string NO_BELL = "\U0001f515"; + public const string NO_BICYCLES = "\U0001f6b3"; + public const string NO_ENTRY = "\U000026d4"; + public const string NO_ENTRY_SIGN = "\U0001f6ab"; + public const string NO_GOOD = "\U0001f645"; + public const string NO_GOOD_SKIN_TONE1 = "\U0001f645\U0001f3fb"; + public const string NO_GOOD_SKIN_TONE2 = "\U0001f645\U0001f3fc"; + public const string NO_GOOD_SKIN_TONE3 = "\U0001f645\U0001f3fd"; + public const string NO_GOOD_SKIN_TONE4 = "\U0001f645\U0001f3fe"; + public const string NO_GOOD_SKIN_TONE5 = "\U0001f645\U0001f3ff"; + public const string NO_MOBILE_PHONES = "\U0001f4f5"; + public const string NO_MOUTH = "\U0001f636"; + public const string NON_POTABLE_WATER = "\U0001f6b1"; + public const string NO_PEDESTRIANS = "\U0001f6b7"; + public const string NOSE = "\U0001f443"; + public const string NOSE_SKIN_TONE1 = "\U0001f443\U0001f3fb"; + public const string NOSE_SKIN_TONE2 = "\U0001f443\U0001f3fc"; + public const string NOSE_SKIN_TONE3 = "\U0001f443\U0001f3fd"; + public const string NOSE_SKIN_TONE4 = "\U0001f443\U0001f3fe"; + public const string NOSE_SKIN_TONE5 = "\U0001f443\U0001f3ff"; + public const string NO_SMOKING = "\U0001f6ad"; + public const string NOTEBOOK = "\U0001f4d3"; + public const string NOTEBOOK_WITH_DECORATIVE_COVER = "\U0001f4d4"; + public const string NOTEPAD_SPIRAL = "\U0001f5d2"; + public const string NOTES = "\U0001f3b6"; + public const string NUT_AND_BOLT = "\U0001f529"; public const string O = "\U00002b55"; public const string O2 = "\U0001f17e"; - public const string Ocean = "\U0001f30a"; - public const string OctagonalSign = "\U0001f6d1"; - public const string Octopus = "\U0001f419"; - public const string Oden = "\U0001f362"; - public const string Office = "\U0001f3e2"; - public const string Oil = "\U0001f6e2"; - public const string OilDrum = "\U0001f6e2"; - public const string Ok = "\U0001f197"; - public const string OkHand = "\U0001f44c"; - public const string OkHandSkinTone1 = "\U0001f44c\U0001f3fb"; - public const string OkHandSkinTone2 = "\U0001f44c\U0001f3fc"; - public const string OkHandSkinTone3 = "\U0001f44c\U0001f3fd"; - public const string OkHandSkinTone4 = "\U0001f44c\U0001f3fe"; - public const string OkHandSkinTone5 = "\U0001f44c\U0001f3ff"; - public const string OkWoman = "\U0001f646"; - public const string OkWomanSkinTone1 = "\U0001f646\U0001f3fb"; - public const string OkWomanSkinTone2 = "\U0001f646\U0001f3fc"; - public const string OkWomanSkinTone3 = "\U0001f646\U0001f3fd"; - public const string OkWomanSkinTone4 = "\U0001f646\U0001f3fe"; - public const string OkWomanSkinTone5 = "\U0001f646\U0001f3ff"; - public const string OlderMan = "\U0001f474"; - public const string OlderManSkinTone1 = "\U0001f474\U0001f3fb"; - public const string OlderManSkinTone2 = "\U0001f474\U0001f3fc"; - public const string OlderManSkinTone3 = "\U0001f474\U0001f3fd"; - public const string OlderManSkinTone4 = "\U0001f474\U0001f3fe"; - public const string OlderManSkinTone5 = "\U0001f474\U0001f3ff"; - public const string OlderWoman = "\U0001f475"; - public const string OlderWomanSkinTone1 = "\U0001f475\U0001f3fb"; - public const string OlderWomanSkinTone2 = "\U0001f475\U0001f3fc"; - public const string OlderWomanSkinTone3 = "\U0001f475\U0001f3fd"; - public const string OlderWomanSkinTone4 = "\U0001f475\U0001f3fe"; - public const string OlderWomanSkinTone5 = "\U0001f475\U0001f3ff"; - public const string OldKey = "\U0001f5dd"; - public const string OmSymbol = "\U0001f549"; - public const string On = "\U0001f51b"; - public const string OncomingAutomobile = "\U0001f698"; - public const string OncomingBus = "\U0001f68d"; - public const string OncomingPoliceCar = "\U0001f694"; - public const string OncomingTaxi = "\U0001f696"; - public const string One = "\U00000031\U000020e3"; - public const string OpenFileFolder = "\U0001f4c2"; - public const string OpenHands = "\U0001f450"; - public const string OpenHandsSkinTone1 = "\U0001f450\U0001f3fb"; - public const string OpenHandsSkinTone2 = "\U0001f450\U0001f3fc"; - public const string OpenHandsSkinTone3 = "\U0001f450\U0001f3fd"; - public const string OpenHandsSkinTone4 = "\U0001f450\U0001f3fe"; - public const string OpenHandsSkinTone5 = "\U0001f450\U0001f3ff"; - public const string OpenMouth = "\U0001f62e"; - public const string Ophiuchus = "\U000026ce"; - public const string OrangeBook = "\U0001f4d9"; - public const string OrthodoxCross = "\U00002626"; - public const string OutboxTray = "\U0001f4e4"; - public const string Owl = "\U0001f989"; - public const string Ox = "\U0001f402"; - public const string Package = "\U0001f4e6"; - public const string Paella = "\U0001f958"; - public const string PageFacingUp = "\U0001f4c4"; - public const string Pager = "\U0001f4df"; - public const string PageWithCurl = "\U0001f4c3"; - public const string Paintbrush = "\U0001f58c"; - public const string PalmTree = "\U0001f334"; - public const string Pancakes = "\U0001f95e"; - public const string PandaFace = "\U0001f43c"; - public const string Paperclip = "\U0001f4ce"; - public const string Paperclips = "\U0001f587"; - public const string Park = "\U0001f3de"; - public const string Parking = "\U0001f17f"; - public const string PartAlternationMark = "\U0000303d"; - public const string PartlySunny = "\U000026c5"; - public const string PassengerShip = "\U0001f6f3"; - public const string PassportControl = "\U0001f6c2"; - public const string PauseButton = "\U000023f8"; - public const string PawPrints = "\U0001f43e"; - public const string Peace = "\U0000262e"; - public const string PeaceSymbol = "\U0000262e"; - public const string Peach = "\U0001f351"; - public const string Peanuts = "\U0001f95c"; - public const string Pear = "\U0001f350"; - public const string PenBallpoint = "\U0001f58a"; - public const string Pencil = "\U0001f4dd"; - public const string Pencil2 = "\U0000270f"; - public const string PenFountain = "\U0001f58b"; - public const string Penguin = "\U0001f427"; - public const string Pensive = "\U0001f614"; - public const string PerformingArts = "\U0001f3ad"; - public const string Persevere = "\U0001f623"; - public const string PersonDoingCartwheel = "\U0001f938"; - public const string PersonDoingCartwheelSkinTone1 = "\U0001f938\U0001f3fb"; - public const string PersonDoingCartwheelSkinTone2 = "\U0001f938\U0001f3fc"; - public const string PersonDoingCartwheelSkinTone3 = "\U0001f938\U0001f3fd"; - public const string PersonDoingCartwheelSkinTone4 = "\U0001f938\U0001f3fe"; - public const string PersonDoingCartwheelSkinTone5 = "\U0001f938\U0001f3ff"; - public const string PersonFrowning = "\U0001f64d"; - public const string PersonFrowningSkinTone1 = "\U0001f64d\U0001f3fb"; - public const string PersonFrowningSkinTone2 = "\U0001f64d\U0001f3fc"; - public const string PersonFrowningSkinTone3 = "\U0001f64d\U0001f3fd"; - public const string PersonFrowningSkinTone4 = "\U0001f64d\U0001f3fe"; - public const string PersonFrowningSkinTone5 = "\U0001f64d\U0001f3ff"; - public const string PersonWithBall = "\U000026f9"; - public const string PersonWithBallSkinTone1 = "\U000026f9\U0001f3fb"; - public const string PersonWithBallSkinTone2 = "\U000026f9\U0001f3fc"; - public const string PersonWithBallSkinTone3 = "\U000026f9\U0001f3fd"; - public const string PersonWithBallSkinTone4 = "\U000026f9\U0001f3fe"; - public const string PersonWithBallSkinTone5 = "\U000026f9\U0001f3ff"; - public const string PersonWithBlondHair = "\U0001f471"; - public const string PersonWithBlondHairSkinTone1 = "\U0001f471\U0001f3fb"; - public const string PersonWithBlondHairSkinTone2 = "\U0001f471\U0001f3fc"; - public const string PersonWithBlondHairSkinTone3 = "\U0001f471\U0001f3fd"; - public const string PersonWithBlondHairSkinTone4 = "\U0001f471\U0001f3fe"; - public const string PersonWithBlondHairSkinTone5 = "\U0001f471\U0001f3ff"; - public const string PersonWithPoutingFace = "\U0001f64e"; - public const string PersonWithPoutingFaceSkinTone1 = "\U0001f64e\U0001f3fb"; - public const string PersonWithPoutingFaceSkinTone2 = "\U0001f64e\U0001f3fc"; - public const string PersonWithPoutingFaceSkinTone3 = "\U0001f64e\U0001f3fd"; - public const string PersonWithPoutingFaceSkinTone4 = "\U0001f64e\U0001f3fe"; - public const string PersonWithPoutingFaceSkinTone5 = "\U0001f64e\U0001f3ff"; - public const string Pick = "\U000026cf"; - public const string Pig = "\U0001f437"; - public const string Pig2 = "\U0001f416"; - public const string PigNose = "\U0001f43d"; - public const string Pill = "\U0001f48a"; - public const string Pineapple = "\U0001f34d"; - public const string PingPong = "\U0001f3d3"; - public const string Pisces = "\U00002653"; - public const string Pizza = "\U0001f355"; - public const string PlaceOfWorship = "\U0001f6d0"; - public const string PlayPause = "\U000023ef"; - public const string Plus1 = "\U0001f44d"; - public const string Plus1SkinTone1 = "\U0001f44d\U0001f3fb"; - public const string Plus1SkinTone2 = "\U0001f44d\U0001f3fc"; - public const string Plus1SkinTone3 = "\U0001f44d\U0001f3fd"; - public const string Plus1SkinTone4 = "\U0001f44d\U0001f3fe"; - public const string Plus1SkinTone5 = "\U0001f44d\U0001f3ff"; - public const string PointDown = "\U0001f447"; - public const string PointDownSkinTone1 = "\U0001f447\U0001f3fb"; - public const string PointDownSkinTone2 = "\U0001f447\U0001f3fc"; - public const string PointDownSkinTone3 = "\U0001f447\U0001f3fd"; - public const string PointDownSkinTone4 = "\U0001f447\U0001f3fe"; - public const string PointDownSkinTone5 = "\U0001f447\U0001f3ff"; - public const string PointLeft = "\U0001f448"; - public const string PointLeftSkinTone1 = "\U0001f448\U0001f3fb"; - public const string PointLeftSkinTone2 = "\U0001f448\U0001f3fc"; - public const string PointLeftSkinTone3 = "\U0001f448\U0001f3fd"; - public const string PointLeftSkinTone4 = "\U0001f448\U0001f3fe"; - public const string PointLeftSkinTone5 = "\U0001f448\U0001f3ff"; - public const string PointRight = "\U0001f449"; - public const string PointRightSkinTone1 = "\U0001f449\U0001f3fb"; - public const string PointRightSkinTone2 = "\U0001f449\U0001f3fc"; - public const string PointRightSkinTone3 = "\U0001f449\U0001f3fd"; - public const string PointRightSkinTone4 = "\U0001f449\U0001f3fe"; - public const string PointRightSkinTone5 = "\U0001f449\U0001f3ff"; - public const string PointUp = "\U0000261d"; - public const string PointUp2 = "\U0001f446"; - public const string PointUp2SkinTone1 = "\U0001f446\U0001f3fb"; - public const string PointUp2SkinTone2 = "\U0001f446\U0001f3fc"; - public const string PointUp2SkinTone3 = "\U0001f446\U0001f3fd"; - public const string PointUp2SkinTone4 = "\U0001f446\U0001f3fe"; - public const string PointUp2SkinTone5 = "\U0001f446\U0001f3ff"; - public const string PointUpSkinTone1 = "\U0000261d\U0001f3fb"; - public const string PointUpSkinTone2 = "\U0000261d\U0001f3fc"; - public const string PointUpSkinTone3 = "\U0000261d\U0001f3fd"; - public const string PointUpSkinTone4 = "\U0000261d\U0001f3fe"; - public const string PointUpSkinTone5 = "\U0000261d\U0001f3ff"; - public const string PoliceCar = "\U0001f693"; - public const string Poo = "\U0001f4a9"; - public const string Poodle = "\U0001f429"; - public const string Poop = "\U0001f4a9"; - public const string Popcorn = "\U0001f37f"; - public const string PostalHorn = "\U0001f4ef"; - public const string Postbox = "\U0001f4ee"; - public const string PostOffice = "\U0001f3e3"; - public const string PotableWater = "\U0001f6b0"; - public const string Potato = "\U0001f954"; - public const string Pouch = "\U0001f45d"; - public const string PoultryLeg = "\U0001f357"; - public const string Pound = "\U0001f4b7"; - public const string PoutingCat = "\U0001f63e"; - public const string Pray = "\U0001f64f"; - public const string PrayerBeads = "\U0001f4ff"; - public const string PraySkinTone1 = "\U0001f64f\U0001f3fb"; - public const string PraySkinTone2 = "\U0001f64f\U0001f3fc"; - public const string PraySkinTone3 = "\U0001f64f\U0001f3fd"; - public const string PraySkinTone4 = "\U0001f64f\U0001f3fe"; - public const string PraySkinTone5 = "\U0001f64f\U0001f3ff"; - public const string PregnantWoman = "\U0001f930"; - public const string PregnantWomanSkinTone1 = "\U0001f930\U0001f3fb"; - public const string PregnantWomanSkinTone2 = "\U0001f930\U0001f3fc"; - public const string PregnantWomanSkinTone3 = "\U0001f930\U0001f3fd"; - public const string PregnantWomanSkinTone4 = "\U0001f930\U0001f3fe"; - public const string PregnantWomanSkinTone5 = "\U0001f930\U0001f3ff"; - public const string PreviousTrack = "\U000023ee"; - public const string Prince = "\U0001f934"; - public const string PrinceSkinTone1 = "\U0001f934\U0001f3fb"; - public const string PrinceSkinTone2 = "\U0001f934\U0001f3fc"; - public const string PrinceSkinTone3 = "\U0001f934\U0001f3fd"; - public const string PrinceSkinTone4 = "\U0001f934\U0001f3fe"; - public const string PrinceSkinTone5 = "\U0001f934\U0001f3ff"; - public const string Princess = "\U0001f478"; - public const string PrincessSkinTone1 = "\U0001f478\U0001f3fb"; - public const string PrincessSkinTone2 = "\U0001f478\U0001f3fc"; - public const string PrincessSkinTone3 = "\U0001f478\U0001f3fd"; - public const string PrincessSkinTone4 = "\U0001f478\U0001f3fe"; - public const string PrincessSkinTone5 = "\U0001f478\U0001f3ff"; - public const string Printer = "\U0001f5a8"; - public const string Projector = "\U0001f4fd"; - public const string Pudding = "\U0001f36e"; - public const string Punch = "\U0001f44a"; - public const string PunchSkinTone1 = "\U0001f44a\U0001f3fb"; - public const string PunchSkinTone2 = "\U0001f44a\U0001f3fc"; - public const string PunchSkinTone3 = "\U0001f44a\U0001f3fd"; - public const string PunchSkinTone4 = "\U0001f44a\U0001f3fe"; - public const string PunchSkinTone5 = "\U0001f44a\U0001f3ff"; - public const string PurpleHeart = "\U0001f49c"; - public const string Purse = "\U0001f45b"; - public const string Pushpin = "\U0001f4cc"; - public const string PutLitterInItsPlace = "\U0001f6ae"; - public const string Question = "\U00002753"; - public const string Rabbit = "\U0001f430"; - public const string Rabbit2 = "\U0001f407"; - public const string RaceCar = "\U0001f3ce"; - public const string Racehorse = "\U0001f40e"; - public const string RacingCar = "\U0001f3ce"; - public const string RacingMotorcycle = "\U0001f3cd"; - public const string Radio = "\U0001f4fb"; - public const string Radioactive = "\U00002622"; - public const string RadioactiveSign = "\U00002622"; - public const string RadioButton = "\U0001f518"; - public const string Rage = "\U0001f621"; - public const string RailroadTrack = "\U0001f6e4"; - public const string RailwayCar = "\U0001f683"; - public const string RailwayTrack = "\U0001f6e4"; - public const string Rainbow = "\U0001f308"; - public const string RainbowFlag = "\U0001f3f3\U0000fe0f\U0000200d\U0001f308"; - public const string RaisedBackOfHand = "\U0001f91a"; - public const string RaisedBackOfHandSkinTone1 = "\U0001f91a\U0001f3fb"; - public const string RaisedBackOfHandSkinTone2 = "\U0001f91a\U0001f3fc"; - public const string RaisedBackOfHandSkinTone3 = "\U0001f91a\U0001f3fd"; - public const string RaisedBackOfHandSkinTone4 = "\U0001f91a\U0001f3fe"; - public const string RaisedBackOfHandSkinTone5 = "\U0001f91a\U0001f3ff"; - public const string RaisedHand = "\U0000270b"; - public const string RaisedHands = "\U0001f64c"; - public const string RaisedHandSkinTone1 = "\U0000270b\U0001f3fb"; - public const string RaisedHandSkinTone2 = "\U0000270b\U0001f3fc"; - public const string RaisedHandSkinTone3 = "\U0000270b\U0001f3fd"; - public const string RaisedHandSkinTone4 = "\U0000270b\U0001f3fe"; - public const string RaisedHandSkinTone5 = "\U0000270b\U0001f3ff"; - public const string RaisedHandsSkinTone1 = "\U0001f64c\U0001f3fb"; - public const string RaisedHandsSkinTone2 = "\U0001f64c\U0001f3fc"; - public const string RaisedHandsSkinTone3 = "\U0001f64c\U0001f3fd"; - public const string RaisedHandsSkinTone4 = "\U0001f64c\U0001f3fe"; - public const string RaisedHandsSkinTone5 = "\U0001f64c\U0001f3ff"; - public const string RaisedHandWithFingersSplayed = "\U0001f590"; - public const string RaisedHandWithFingersSplayedSkinTone1 = "\U0001f590\U0001f3fb"; - public const string RaisedHandWithFingersSplayedSkinTone2 = "\U0001f590\U0001f3fc"; - public const string RaisedHandWithFingersSplayedSkinTone3 = "\U0001f590\U0001f3fd"; - public const string RaisedHandWithFingersSplayedSkinTone4 = "\U0001f590\U0001f3fe"; - public const string RaisedHandWithFingersSplayedSkinTone5 = "\U0001f590\U0001f3ff"; - public const string RaisedHandWithPartBetweenMiddleAndRingFingers = "\U0001f596"; - public const string RaisedHandWithPartBetweenMiddleAndRingFingersSkinTone1 = "\U0001f596\U0001f3fb"; - public const string RaisedHandWithPartBetweenMiddleAndRingFingersSkinTone2 = "\U0001f596\U0001f3fc"; - public const string RaisedHandWithPartBetweenMiddleAndRingFingersSkinTone3 = "\U0001f596\U0001f3fd"; - public const string RaisedHandWithPartBetweenMiddleAndRingFingersSkinTone4 = "\U0001f596\U0001f3fe"; - public const string RaisedHandWithPartBetweenMiddleAndRingFingersSkinTone5 = "\U0001f596\U0001f3ff"; - public const string RaisingHand = "\U0001f64b"; - public const string RaisingHandSkinTone1 = "\U0001f64b\U0001f3fb"; - public const string RaisingHandSkinTone2 = "\U0001f64b\U0001f3fc"; - public const string RaisingHandSkinTone3 = "\U0001f64b\U0001f3fd"; - public const string RaisingHandSkinTone4 = "\U0001f64b\U0001f3fe"; - public const string RaisingHandSkinTone5 = "\U0001f64b\U0001f3ff"; - public const string Ram = "\U0001f40f"; - public const string Ramen = "\U0001f35c"; - public const string Rat = "\U0001f400"; - public const string RecordButton = "\U000023fa"; - public const string Recycle = "\U0000267b"; - public const string RedCar = "\U0001f697"; - public const string RedCircle = "\U0001f534"; - public const string RegionalIndicatorA = "\U0001f1e6"; - public const string RegionalIndicatorB = "\U0001f1e7"; - public const string RegionalIndicatorC = "\U0001f1e8"; - public const string RegionalIndicatorD = "\U0001f1e9"; - public const string RegionalIndicatorE = "\U0001f1ea"; - public const string RegionalIndicatorF = "\U0001f1eb"; - public const string RegionalIndicatorG = "\U0001f1ec"; - public const string RegionalIndicatorH = "\U0001f1ed"; - public const string RegionalIndicatorI = "\U0001f1ee"; - public const string RegionalIndicatorJ = "\U0001f1ef"; - public const string RegionalIndicatorK = "\U0001f1f0"; - public const string RegionalIndicatorL = "\U0001f1f1"; - public const string RegionalIndicatorM = "\U0001f1f2"; - public const string RegionalIndicatorN = "\U0001f1f3"; - public const string RegionalIndicatorO = "\U0001f1f4"; - public const string RegionalIndicatorP = "\U0001f1f5"; - public const string RegionalIndicatorQ = "\U0001f1f6"; - public const string RegionalIndicatorR = "\U0001f1f7"; - public const string RegionalIndicatorS = "\U0001f1f8"; - public const string RegionalIndicatorT = "\U0001f1f9"; - public const string RegionalIndicatorU = "\U0001f1fa"; - public const string RegionalIndicatorV = "\U0001f1fb"; - public const string RegionalIndicatorW = "\U0001f1fc"; - public const string RegionalIndicatorX = "\U0001f1fd"; - public const string RegionalIndicatorY = "\U0001f1fe"; - public const string RegionalIndicatorZ = "\U0001f1ff"; - public const string Registered = "\U000000ae"; - public const string Relaxed = "\U0000263a"; - public const string Relieved = "\U0001f60c"; - public const string ReminderRibbon = "\U0001f397"; - public const string Repeat = "\U0001f501"; - public const string RepeatOne = "\U0001f502"; - public const string Restroom = "\U0001f6bb"; - public const string ReversedHandWithMiddleFingerExtended = "\U0001f595"; - public const string ReversedHandWithMiddleFingerExtendedSkinTone1 = "\U0001f595\U0001f3fb"; - public const string ReversedHandWithMiddleFingerExtendedSkinTone2 = "\U0001f595\U0001f3fc"; - public const string ReversedHandWithMiddleFingerExtendedSkinTone3 = "\U0001f595\U0001f3fd"; - public const string ReversedHandWithMiddleFingerExtendedSkinTone4 = "\U0001f595\U0001f3fe"; - public const string ReversedHandWithMiddleFingerExtendedSkinTone5 = "\U0001f595\U0001f3ff"; - public const string RevolvingHearts = "\U0001f49e"; - public const string Rewind = "\U000023ea"; - public const string Rhino = "\U0001f98f"; - public const string Rhinoceros = "\U0001f98f"; - public const string Ribbon = "\U0001f380"; - public const string Rice = "\U0001f35a"; - public const string RiceBall = "\U0001f359"; - public const string RiceCracker = "\U0001f358"; - public const string RiceScene = "\U0001f391"; - public const string RightAngerBubble = "\U0001f5ef"; - public const string RightFacingFist = "\U0001f91c"; - public const string RightFacingFistSkinTone1 = "\U0001f91c\U0001f3fb"; - public const string RightFacingFistSkinTone2 = "\U0001f91c\U0001f3fc"; - public const string RightFacingFistSkinTone3 = "\U0001f91c\U0001f3fd"; - public const string RightFacingFistSkinTone4 = "\U0001f91c\U0001f3fe"; - public const string RightFacingFistSkinTone5 = "\U0001f91c\U0001f3ff"; - public const string RightFist = "\U0001f91c"; - public const string RightFistSkinTone1 = "\U0001f91c\U0001f3fb"; - public const string RightFistSkinTone2 = "\U0001f91c\U0001f3fc"; - public const string RightFistSkinTone3 = "\U0001f91c\U0001f3fd"; - public const string RightFistSkinTone4 = "\U0001f91c\U0001f3fe"; - public const string RightFistSkinTone5 = "\U0001f91c\U0001f3ff"; - public const string Ring = "\U0001f48d"; - public const string Robot = "\U0001f916"; - public const string RobotFace = "\U0001f916"; - public const string Rocket = "\U0001f680"; - public const string Rofl = "\U0001f923"; - public const string RolledUpNewspaper = "\U0001f5de"; - public const string RollerCoaster = "\U0001f3a2"; - public const string RollingEyes = "\U0001f644"; - public const string RollingOnTheFloorLaughing = "\U0001f923"; - public const string Rooster = "\U0001f413"; - public const string Rose = "\U0001f339"; - public const string Rosette = "\U0001f3f5"; - public const string RotatingLight = "\U0001f6a8"; - public const string RoundPushpin = "\U0001f4cd"; - public const string Rowboat = "\U0001f6a3"; - public const string RowboatSkinTone1 = "\U0001f6a3\U0001f3fb"; - public const string RowboatSkinTone2 = "\U0001f6a3\U0001f3fc"; - public const string RowboatSkinTone3 = "\U0001f6a3\U0001f3fd"; - public const string RowboatSkinTone4 = "\U0001f6a3\U0001f3fe"; - public const string RowboatSkinTone5 = "\U0001f6a3\U0001f3ff"; - public const string RugbyFootball = "\U0001f3c9"; - public const string Runner = "\U0001f3c3"; - public const string RunnerSkinTone1 = "\U0001f3c3\U0001f3fb"; - public const string RunnerSkinTone2 = "\U0001f3c3\U0001f3fc"; - public const string RunnerSkinTone3 = "\U0001f3c3\U0001f3fd"; - public const string RunnerSkinTone4 = "\U0001f3c3\U0001f3fe"; - public const string RunnerSkinTone5 = "\U0001f3c3\U0001f3ff"; - public const string RunningShirtWithSash = "\U0001f3bd"; - public const string Sa = "\U0001f202"; - public const string Sagittarius = "\U00002650"; - public const string Sailboat = "\U000026f5"; - public const string Sake = "\U0001f376"; - public const string Salad = "\U0001f957"; - public const string Sandal = "\U0001f461"; - public const string Santa = "\U0001f385"; - public const string SantaSkinTone1 = "\U0001f385\U0001f3fb"; - public const string SantaSkinTone2 = "\U0001f385\U0001f3fc"; - public const string SantaSkinTone3 = "\U0001f385\U0001f3fd"; - public const string SantaSkinTone4 = "\U0001f385\U0001f3fe"; - public const string SantaSkinTone5 = "\U0001f385\U0001f3ff"; - public const string Satellite = "\U0001f4e1"; - public const string SatelliteOrbital = "\U0001f6f0"; - public const string Satisfied = "\U0001f606"; - public const string Saxophone = "\U0001f3b7"; - public const string Scales = "\U00002696"; - public const string School = "\U0001f3eb"; - public const string SchoolSatchel = "\U0001f392"; - public const string Scissors = "\U00002702"; - public const string Scooter = "\U0001f6f4"; - public const string Scorpion = "\U0001f982"; - public const string Scorpius = "\U0000264f"; - public const string Scream = "\U0001f631"; - public const string ScreamCat = "\U0001f640"; - public const string Scroll = "\U0001f4dc"; - public const string Seat = "\U0001f4ba"; - public const string SecondPlace = "\U0001f948"; - public const string SecondPlaceMedal = "\U0001f948"; - public const string Secret = "\U00003299"; - public const string Seedling = "\U0001f331"; - public const string SeeNoEvil = "\U0001f648"; - public const string Selfie = "\U0001f933"; - public const string SelfieSkinTone1 = "\U0001f933\U0001f3fb"; - public const string SelfieSkinTone2 = "\U0001f933\U0001f3fc"; - public const string SelfieSkinTone3 = "\U0001f933\U0001f3fd"; - public const string SelfieSkinTone4 = "\U0001f933\U0001f3fe"; - public const string SelfieSkinTone5 = "\U0001f933\U0001f3ff"; - public const string Seven = "\U00000037\U000020e3"; - public const string ShakingHands = "\U0001f91d"; - public const string ShallowPanOfFood = "\U0001f958"; - public const string Shamrock = "\U00002618"; - public const string Shark = "\U0001f988"; - public const string ShavedIce = "\U0001f367"; - public const string Sheep = "\U0001f411"; - public const string Shell = "\U0001f41a"; - public const string ShelledPeanut = "\U0001f95c"; - public const string Shield = "\U0001f6e1"; - public const string ShintoShrine = "\U000026e9"; - public const string Ship = "\U0001f6a2"; - public const string Shirt = "\U0001f455"; - public const string Shit = "\U0001f4a9"; - public const string ShoppingBags = "\U0001f6cd"; - public const string ShoppingCart = "\U0001f6d2"; - public const string ShoppingTrolley = "\U0001f6d2"; - public const string Shower = "\U0001f6bf"; - public const string Shrimp = "\U0001f990"; - public const string Shrug = "\U0001f937"; - public const string ShrugSkinTone1 = "\U0001f937\U0001f3fb"; - public const string ShrugSkinTone2 = "\U0001f937\U0001f3fc"; - public const string ShrugSkinTone3 = "\U0001f937\U0001f3fd"; - public const string ShrugSkinTone4 = "\U0001f937\U0001f3fe"; - public const string ShrugSkinTone5 = "\U0001f937\U0001f3ff"; - public const string Sick = "\U0001f922"; - public const string SignalStrength = "\U0001f4f6"; - public const string SignOfTheHorns = "\U0001f918"; - public const string SignOfTheHornsSkinTone1 = "\U0001f918\U0001f3fb"; - public const string SignOfTheHornsSkinTone2 = "\U0001f918\U0001f3fc"; - public const string SignOfTheHornsSkinTone3 = "\U0001f918\U0001f3fd"; - public const string SignOfTheHornsSkinTone4 = "\U0001f918\U0001f3fe"; - public const string SignOfTheHornsSkinTone5 = "\U0001f918\U0001f3ff"; - public const string Six = "\U00000036\U000020e3"; - public const string SixPointedStar = "\U0001f52f"; - public const string Skeleton = "\U0001f480"; - public const string Ski = "\U0001f3bf"; - public const string Skier = "\U000026f7"; - public const string SkierSkinTone1 = "\U000026f7\U0001f3fb"; - public const string SkierSkinTone2 = "\U000026f7\U0001f3fc"; - public const string SkierSkinTone3 = "\U000026f7\U0001f3fd"; - public const string SkierSkinTone4 = "\U000026f7\U0001f3fe"; - public const string SkierSkinTone5 = "\U000026f7\U0001f3ff"; - public const string Skull = "\U0001f480"; - public const string SkullAndCrossbones = "\U00002620"; - public const string SkullCrossbones = "\U00002620"; - public const string Sleeping = "\U0001f634"; - public const string SleepingAccommodation = "\U0001f6cc"; - public const string SleepingAccommodationSkinTone1 = "\U0001f6cc\U0001f3fb"; - public const string SleepingAccommodationSkinTone2 = "\U0001f6cc\U0001f3fc"; - public const string SleepingAccommodationSkinTone3 = "\U0001f6cc\U0001f3fd"; - public const string SleepingAccommodationSkinTone4 = "\U0001f6cc\U0001f3fe"; - public const string SleepingAccommodationSkinTone5 = "\U0001f6cc\U0001f3ff"; - public const string Sleepy = "\U0001f62a"; - public const string SleuthOrSpy = "\U0001f575"; - public const string SleuthOrSpySkinTone1 = "\U0001f575\U0001f3fb"; - public const string SleuthOrSpySkinTone2 = "\U0001f575\U0001f3fc"; - public const string SleuthOrSpySkinTone3 = "\U0001f575\U0001f3fd"; - public const string SleuthOrSpySkinTone4 = "\U0001f575\U0001f3fe"; - public const string SleuthOrSpySkinTone5 = "\U0001f575\U0001f3ff"; - public const string SlightFrown = "\U0001f641"; - public const string SlightlyFrowningFace = "\U0001f641"; - public const string SlightlySmilingFace = "\U0001f642"; - public const string SlightSmile = "\U0001f642"; - public const string SlotMachine = "\U0001f3b0"; - public const string SmallAirplane = "\U0001f6e9"; - public const string SmallBlueDiamond = "\U0001f539"; - public const string SmallOrangeDiamond = "\U0001f538"; - public const string SmallRedTriangle = "\U0001f53a"; - public const string SmallRedTriangleDown = "\U0001f53b"; - public const string Smile = "\U0001f604"; - public const string SmileCat = "\U0001f638"; - public const string Smiley = "\U0001f603"; - public const string SmileyCat = "\U0001f63a"; - public const string SmilingImp = "\U0001f608"; - public const string Smirk = "\U0001f60f"; - public const string SmirkCat = "\U0001f63c"; - public const string Smoking = "\U0001f6ac"; - public const string Snail = "\U0001f40c"; - public const string Snake = "\U0001f40d"; - public const string Sneeze = "\U0001f927"; - public const string SneezingFace = "\U0001f927"; - public const string Snowboarder = "\U0001f3c2"; - public const string SnowboarderSkinTone1 = "\U0001f3c2\U0001f3fb"; - public const string SnowboarderSkinTone2 = "\U0001f3c2\U0001f3fc"; - public const string SnowboarderSkinTone3 = "\U0001f3c2\U0001f3fd"; - public const string SnowboarderSkinTone4 = "\U0001f3c2\U0001f3fe"; - public const string SnowboarderSkinTone5 = "\U0001f3c2\U0001f3ff"; - public const string SnowCappedMountain = "\U0001f3d4"; - public const string Snowflake = "\U00002744"; - public const string Snowman = "\U000026c4"; - public const string Snowman2 = "\U00002603"; - public const string Sob = "\U0001f62d"; - public const string Soccer = "\U000026bd"; - public const string Soon = "\U0001f51c"; - public const string Sos = "\U0001f198"; - public const string Sound = "\U0001f509"; - public const string SpaceInvader = "\U0001f47e"; - public const string Spades = "\U00002660"; - public const string Spaghetti = "\U0001f35d"; - public const string Sparkle = "\U00002747"; - public const string Sparkler = "\U0001f387"; - public const string Sparkles = "\U00002728"; - public const string SparklingHeart = "\U0001f496"; - public const string Speaker = "\U0001f508"; - public const string SpeakingHead = "\U0001f5e3"; - public const string SpeakingHeadInSilhouette = "\U0001f5e3"; - public const string SpeakNoEvil = "\U0001f64a"; - public const string SpeechBalloon = "\U0001f4ac"; - public const string SpeechLeft = "\U0001f5e8"; - public const string Speedboat = "\U0001f6a4"; - public const string Spider = "\U0001f577"; - public const string SpiderWeb = "\U0001f578"; - public const string SpiralCalendarPad = "\U0001f5d3"; - public const string SpiralNotePad = "\U0001f5d2"; - public const string Spoon = "\U0001f944"; - public const string SportsMedal = "\U0001f3c5"; - public const string Spy = "\U0001f575"; - public const string SpySkinTone1 = "\U0001f575\U0001f3fb"; - public const string SpySkinTone2 = "\U0001f575\U0001f3fc"; - public const string SpySkinTone3 = "\U0001f575\U0001f3fd"; - public const string SpySkinTone4 = "\U0001f575\U0001f3fe"; - public const string SpySkinTone5 = "\U0001f575\U0001f3ff"; - public const string Squid = "\U0001f991"; - public const string Stadium = "\U0001f3df"; - public const string Star = "\U00002b50"; - public const string Star2 = "\U0001f31f"; - public const string StarAndCrescent = "\U0000262a"; - public const string StarOfDavid = "\U00002721"; - public const string Stars = "\U0001f320"; - public const string Station = "\U0001f689"; - public const string StatueOfLiberty = "\U0001f5fd"; - public const string SteamLocomotive = "\U0001f682"; - public const string Stew = "\U0001f372"; - public const string StopButton = "\U000023f9"; - public const string StopSign = "\U0001f6d1"; - public const string Stopwatch = "\U000023f1"; - public const string StraightRuler = "\U0001f4cf"; - public const string Strawberry = "\U0001f353"; - public const string StuckOutTongue = "\U0001f61b"; - public const string StuckOutTongueClosedEyes = "\U0001f61d"; - public const string StuckOutTongueWinkingEye = "\U0001f61c"; - public const string StudioMicrophone = "\U0001f399"; - public const string StuffedFlatbread = "\U0001f959"; - public const string StuffedPita = "\U0001f959"; - public const string Sunflower = "\U0001f33b"; - public const string Sunglasses = "\U0001f60e"; - public const string Sunny = "\U00002600"; - public const string Sunrise = "\U0001f305"; - public const string SunriseOverMountains = "\U0001f304"; - public const string SunWithFace = "\U0001f31e"; - public const string Surfer = "\U0001f3c4"; - public const string SurferSkinTone1 = "\U0001f3c4\U0001f3fb"; - public const string SurferSkinTone2 = "\U0001f3c4\U0001f3fc"; - public const string SurferSkinTone3 = "\U0001f3c4\U0001f3fd"; - public const string SurferSkinTone4 = "\U0001f3c4\U0001f3fe"; - public const string SurferSkinTone5 = "\U0001f3c4\U0001f3ff"; - public const string Sushi = "\U0001f363"; - public const string SuspensionRailway = "\U0001f69f"; - public const string Sweat = "\U0001f613"; - public const string SweatDrops = "\U0001f4a6"; - public const string SweatSmile = "\U0001f605"; - public const string SweetPotato = "\U0001f360"; - public const string Swimmer = "\U0001f3ca"; - public const string SwimmerSkinTone1 = "\U0001f3ca\U0001f3fb"; - public const string SwimmerSkinTone2 = "\U0001f3ca\U0001f3fc"; - public const string SwimmerSkinTone3 = "\U0001f3ca\U0001f3fd"; - public const string SwimmerSkinTone4 = "\U0001f3ca\U0001f3fe"; - public const string SwimmerSkinTone5 = "\U0001f3ca\U0001f3ff"; - public const string Symbols = "\U0001f523"; - public const string Synagogue = "\U0001f54d"; - public const string Syringe = "\U0001f489"; - public const string TableTennis = "\U0001f3d3"; - public const string Taco = "\U0001f32e"; - public const string Tada = "\U0001f389"; - public const string TanabataTree = "\U0001f38b"; - public const string Tangerine = "\U0001f34a"; - public const string Taurus = "\U00002649"; - public const string Taxi = "\U0001f695"; - public const string Tea = "\U0001f375"; - public const string Telephone = "\U0000260e"; - public const string TelephoneReceiver = "\U0001f4de"; - public const string Telescope = "\U0001f52d"; - public const string Tennis = "\U0001f3be"; - public const string Tent = "\U000026fa"; - public const string Thermometer = "\U0001f321"; - public const string ThermometerFace = "\U0001f912"; - public const string Thinking = "\U0001f914"; - public const string ThinkingFace = "\U0001f914"; - public const string ThirdPlace = "\U0001f949"; - public const string ThirdPlaceMedal = "\U0001f949"; - public const string ThoughtBalloon = "\U0001f4ad"; - public const string Three = "\U00000033\U000020e3"; - public const string ThreeButtonMouse = "\U0001f5b1"; - public const string Thumbdown = "\U0001f44e"; - public const string ThumbdownSkinTone1 = "\U0001f44e\U0001f3fb"; - public const string ThumbdownSkinTone2 = "\U0001f44e\U0001f3fc"; - public const string ThumbdownSkinTone3 = "\U0001f44e\U0001f3fd"; - public const string ThumbdownSkinTone4 = "\U0001f44e\U0001f3fe"; - public const string ThumbdownSkinTone5 = "\U0001f44e\U0001f3ff"; - public const string Thumbsdown = "\U0001f44e"; - public const string ThumbsdownSkinTone1 = "\U0001f44e\U0001f3fb"; - public const string ThumbsdownSkinTone2 = "\U0001f44e\U0001f3fc"; - public const string ThumbsdownSkinTone3 = "\U0001f44e\U0001f3fd"; - public const string ThumbsdownSkinTone4 = "\U0001f44e\U0001f3fe"; - public const string ThumbsdownSkinTone5 = "\U0001f44e\U0001f3ff"; - public const string Thumbsup = "\U0001f44d"; - public const string ThumbsupSkinTone1 = "\U0001f44d\U0001f3fb"; - public const string ThumbsupSkinTone2 = "\U0001f44d\U0001f3fc"; - public const string ThumbsupSkinTone3 = "\U0001f44d\U0001f3fd"; - public const string ThumbsupSkinTone4 = "\U0001f44d\U0001f3fe"; - public const string ThumbsupSkinTone5 = "\U0001f44d\U0001f3ff"; - public const string Thumbup = "\U0001f44d"; - public const string ThumbupSkinTone1 = "\U0001f44d\U0001f3fb"; - public const string ThumbupSkinTone2 = "\U0001f44d\U0001f3fc"; - public const string ThumbupSkinTone3 = "\U0001f44d\U0001f3fd"; - public const string ThumbupSkinTone4 = "\U0001f44d\U0001f3fe"; - public const string ThumbupSkinTone5 = "\U0001f44d\U0001f3ff"; - public const string ThunderCloudAndRain = "\U000026c8"; - public const string ThunderCloudRain = "\U000026c8"; - public const string Ticket = "\U0001f3ab"; - public const string Tickets = "\U0001f39f"; - public const string Tiger = "\U0001f42f"; - public const string Tiger2 = "\U0001f405"; - public const string Timer = "\U000023f2"; - public const string TimerClock = "\U000023f2"; - public const string TiredFace = "\U0001f62b"; - public const string Tm = "\U00002122"; - public const string Toilet = "\U0001f6bd"; - public const string TokyoTower = "\U0001f5fc"; - public const string Tomato = "\U0001f345"; - public const string Tongue = "\U0001f445"; - public const string Tools = "\U0001f6e0"; - public const string Top = "\U0001f51d"; - public const string Tophat = "\U0001f3a9"; - public const string Trackball = "\U0001f5b2"; - public const string TrackNext = "\U000023ed"; - public const string TrackPrevious = "\U000023ee"; - public const string Tractor = "\U0001f69c"; - public const string TrafficLight = "\U0001f6a5"; - public const string Train = "\U0001f68b"; - public const string Train2 = "\U0001f686"; - public const string Tram = "\U0001f68a"; - public const string TriangularFlagOnPost = "\U0001f6a9"; - public const string TriangularRuler = "\U0001f4d0"; - public const string Trident = "\U0001f531"; - public const string Triumph = "\U0001f624"; - public const string Trolleybus = "\U0001f68e"; - public const string Trophy = "\U0001f3c6"; - public const string TropicalDrink = "\U0001f379"; - public const string TropicalFish = "\U0001f420"; - public const string Truck = "\U0001f69a"; - public const string Trumpet = "\U0001f3ba"; - public const string Tulip = "\U0001f337"; - public const string TumblerGlass = "\U0001f943"; - public const string Turkey = "\U0001f983"; - public const string Turtle = "\U0001f422"; - public const string Tv = "\U0001f4fa"; - public const string TwistedRightwardsArrows = "\U0001f500"; - public const string Two = "\U00000032\U000020e3"; - public const string TwoHearts = "\U0001f495"; - public const string TwoMenHoldingHands = "\U0001f46c"; - public const string TwoWomenHoldingHands = "\U0001f46d"; + public const string OCEAN = "\U0001f30a"; + public const string OCTAGONAL_SIGN = "\U0001f6d1"; + public const string OCTOPUS = "\U0001f419"; + public const string ODEN = "\U0001f362"; + public const string OFFICE = "\U0001f3e2"; + public const string OIL = "\U0001f6e2"; + public const string OIL_DRUM = "\U0001f6e2"; + public const string OK = "\U0001f197"; + public const string OK_HAND = "\U0001f44c"; + public const string OK_HAND_SKIN_TONE1 = "\U0001f44c\U0001f3fb"; + public const string OK_HAND_SKIN_TONE2 = "\U0001f44c\U0001f3fc"; + public const string OK_HAND_SKIN_TONE3 = "\U0001f44c\U0001f3fd"; + public const string OK_HAND_SKIN_TONE4 = "\U0001f44c\U0001f3fe"; + public const string OK_HAND_SKIN_TONE5 = "\U0001f44c\U0001f3ff"; + public const string OK_WOMAN = "\U0001f646"; + public const string OK_WOMAN_SKIN_TONE1 = "\U0001f646\U0001f3fb"; + public const string OK_WOMAN_SKIN_TONE2 = "\U0001f646\U0001f3fc"; + public const string OK_WOMAN_SKIN_TONE3 = "\U0001f646\U0001f3fd"; + public const string OK_WOMAN_SKIN_TONE4 = "\U0001f646\U0001f3fe"; + public const string OK_WOMAN_SKIN_TONE5 = "\U0001f646\U0001f3ff"; + public const string OLDER_MAN = "\U0001f474"; + public const string OLDER_MAN_SKIN_TONE1 = "\U0001f474\U0001f3fb"; + public const string OLDER_MAN_SKIN_TONE2 = "\U0001f474\U0001f3fc"; + public const string OLDER_MAN_SKIN_TONE3 = "\U0001f474\U0001f3fd"; + public const string OLDER_MAN_SKIN_TONE4 = "\U0001f474\U0001f3fe"; + public const string OLDER_MAN_SKIN_TONE5 = "\U0001f474\U0001f3ff"; + public const string OLDER_WOMAN = "\U0001f475"; + public const string OLDER_WOMAN_SKIN_TONE1 = "\U0001f475\U0001f3fb"; + public const string OLDER_WOMAN_SKIN_TONE2 = "\U0001f475\U0001f3fc"; + public const string OLDER_WOMAN_SKIN_TONE3 = "\U0001f475\U0001f3fd"; + public const string OLDER_WOMAN_SKIN_TONE4 = "\U0001f475\U0001f3fe"; + public const string OLDER_WOMAN_SKIN_TONE5 = "\U0001f475\U0001f3ff"; + public const string OLD_KEY = "\U0001f5dd"; + public const string OM_SYMBOL = "\U0001f549"; + public const string ON = "\U0001f51b"; + public const string ONCOMING_AUTOMOBILE = "\U0001f698"; + public const string ONCOMING_BUS = "\U0001f68d"; + public const string ONCOMING_POLICE_CAR = "\U0001f694"; + public const string ONCOMING_TAXI = "\U0001f696"; + public const string ONE = "\U00000031\U000020e3"; + public const string OPEN_FILE_FOLDER = "\U0001f4c2"; + public const string OPEN_HANDS = "\U0001f450"; + public const string OPEN_HANDS_SKIN_TONE1 = "\U0001f450\U0001f3fb"; + public const string OPEN_HANDS_SKIN_TONE2 = "\U0001f450\U0001f3fc"; + public const string OPEN_HANDS_SKIN_TONE3 = "\U0001f450\U0001f3fd"; + public const string OPEN_HANDS_SKIN_TONE4 = "\U0001f450\U0001f3fe"; + public const string OPEN_HANDS_SKIN_TONE5 = "\U0001f450\U0001f3ff"; + public const string OPEN_MOUTH = "\U0001f62e"; + public const string OPHIUCHUS = "\U000026ce"; + public const string ORANGE_BOOK = "\U0001f4d9"; + public const string ORTHODOX_CROSS = "\U00002626"; + public const string OUTBOX_TRAY = "\U0001f4e4"; + public const string OWL = "\U0001f989"; + public const string OX = "\U0001f402"; + public const string PACKAGE = "\U0001f4e6"; + public const string PAELLA = "\U0001f958"; + public const string PAGE_FACING_UP = "\U0001f4c4"; + public const string PAGER = "\U0001f4df"; + public const string PAGE_WITH_CURL = "\U0001f4c3"; + public const string PAINTBRUSH = "\U0001f58c"; + public const string PALM_TREE = "\U0001f334"; + public const string PANCAKES = "\U0001f95e"; + public const string PANDA_FACE = "\U0001f43c"; + public const string PAPERCLIP = "\U0001f4ce"; + public const string PAPERCLIPS = "\U0001f587"; + public const string PARK = "\U0001f3de"; + public const string PARKING = "\U0001f17f"; + public const string PART_ALTERNATION_MARK = "\U0000303d"; + public const string PARTLY_SUNNY = "\U000026c5"; + public const string PASSENGER_SHIP = "\U0001f6f3"; + public const string PASSPORT_CONTROL = "\U0001f6c2"; + public const string PAUSE_BUTTON = "\U000023f8"; + public const string PAW_PRINTS = "\U0001f43e"; + public const string PEACE = "\U0000262e"; + public const string PEACE_SYMBOL = "\U0000262e"; + public const string PEACH = "\U0001f351"; + public const string PEANUTS = "\U0001f95c"; + public const string PEAR = "\U0001f350"; + public const string PEN_BALLPOINT = "\U0001f58a"; + public const string PENCIL = "\U0001f4dd"; + public const string PENCIL2 = "\U0000270f"; + public const string PEN_FOUNTAIN = "\U0001f58b"; + public const string PENGUIN = "\U0001f427"; + public const string PENSIVE = "\U0001f614"; + public const string PERFORMING_ARTS = "\U0001f3ad"; + public const string PERSEVERE = "\U0001f623"; + public const string PERSON_DOING_CARTWHEEL = "\U0001f938"; + public const string PERSON_DOING_CARTWHEEL_SKIN_TONE1 = "\U0001f938\U0001f3fb"; + public const string PERSON_DOING_CARTWHEEL_SKIN_TONE2 = "\U0001f938\U0001f3fc"; + public const string PERSON_DOING_CARTWHEEL_SKIN_TONE3 = "\U0001f938\U0001f3fd"; + public const string PERSON_DOING_CARTWHEEL_SKIN_TONE4 = "\U0001f938\U0001f3fe"; + public const string PERSON_DOING_CARTWHEEL_SKIN_TONE5 = "\U0001f938\U0001f3ff"; + public const string PERSON_FROWNING = "\U0001f64d"; + public const string PERSON_FROWNING_SKIN_TONE1 = "\U0001f64d\U0001f3fb"; + public const string PERSON_FROWNING_SKIN_TONE2 = "\U0001f64d\U0001f3fc"; + public const string PERSON_FROWNING_SKIN_TONE3 = "\U0001f64d\U0001f3fd"; + public const string PERSON_FROWNING_SKIN_TONE4 = "\U0001f64d\U0001f3fe"; + public const string PERSON_FROWNING_SKIN_TONE5 = "\U0001f64d\U0001f3ff"; + public const string PERSON_WITH_BALL = "\U000026f9"; + public const string PERSON_WITH_BALL_SKIN_TONE1 = "\U000026f9\U0001f3fb"; + public const string PERSON_WITH_BALL_SKIN_TONE2 = "\U000026f9\U0001f3fc"; + public const string PERSON_WITH_BALL_SKIN_TONE3 = "\U000026f9\U0001f3fd"; + public const string PERSON_WITH_BALL_SKIN_TONE4 = "\U000026f9\U0001f3fe"; + public const string PERSON_WITH_BALL_SKIN_TONE5 = "\U000026f9\U0001f3ff"; + public const string PERSON_WITH_BLOND_HAIR = "\U0001f471"; + public const string PERSON_WITH_BLOND_HAIR_SKIN_TONE1 = "\U0001f471\U0001f3fb"; + public const string PERSON_WITH_BLOND_HAIR_SKIN_TONE2 = "\U0001f471\U0001f3fc"; + public const string PERSON_WITH_BLOND_HAIR_SKIN_TONE3 = "\U0001f471\U0001f3fd"; + public const string PERSON_WITH_BLOND_HAIR_SKIN_TONE4 = "\U0001f471\U0001f3fe"; + public const string PERSON_WITH_BLOND_HAIR_SKIN_TONE5 = "\U0001f471\U0001f3ff"; + public const string PERSON_WITH_POUTING_FACE = "\U0001f64e"; + public const string PERSON_WITH_POUTING_FACE_SKIN_TONE1 = "\U0001f64e\U0001f3fb"; + public const string PERSON_WITH_POUTING_FACE_SKIN_TONE2 = "\U0001f64e\U0001f3fc"; + public const string PERSON_WITH_POUTING_FACE_SKIN_TONE3 = "\U0001f64e\U0001f3fd"; + public const string PERSON_WITH_POUTING_FACE_SKIN_TONE4 = "\U0001f64e\U0001f3fe"; + public const string PERSON_WITH_POUTING_FACE_SKIN_TONE5 = "\U0001f64e\U0001f3ff"; + public const string PICK = "\U000026cf"; + public const string PIG = "\U0001f437"; + public const string PIG2 = "\U0001f416"; + public const string PIG_NOSE = "\U0001f43d"; + public const string PILL = "\U0001f48a"; + public const string PINEAPPLE = "\U0001f34d"; + public const string PING_PONG = "\U0001f3d3"; + public const string PISCES = "\U00002653"; + public const string PIZZA = "\U0001f355"; + public const string PLACE_OF_WORSHIP = "\U0001f6d0"; + public const string PLAY_PAUSE = "\U000023ef"; + public const string PLUS1 = "\U0001f44d"; + public const string PLUS1_SKIN_TONE1 = "\U0001f44d\U0001f3fb"; + public const string PLUS1_SKIN_TONE2 = "\U0001f44d\U0001f3fc"; + public const string PLUS1_SKIN_TONE3 = "\U0001f44d\U0001f3fd"; + public const string PLUS1_SKIN_TONE4 = "\U0001f44d\U0001f3fe"; + public const string PLUS1_SKIN_TONE5 = "\U0001f44d\U0001f3ff"; + public const string POINT_DOWN = "\U0001f447"; + public const string POINT_DOWN_SKIN_TONE1 = "\U0001f447\U0001f3fb"; + public const string POINT_DOWN_SKIN_TONE2 = "\U0001f447\U0001f3fc"; + public const string POINT_DOWN_SKIN_TONE3 = "\U0001f447\U0001f3fd"; + public const string POINT_DOWN_SKIN_TONE4 = "\U0001f447\U0001f3fe"; + public const string POINT_DOWN_SKIN_TONE5 = "\U0001f447\U0001f3ff"; + public const string POINT_LEFT = "\U0001f448"; + public const string POINT_LEFT_SKIN_TONE1 = "\U0001f448\U0001f3fb"; + public const string POINT_LEFT_SKIN_TONE2 = "\U0001f448\U0001f3fc"; + public const string POINT_LEFT_SKIN_TONE3 = "\U0001f448\U0001f3fd"; + public const string POINT_LEFT_SKIN_TONE4 = "\U0001f448\U0001f3fe"; + public const string POINT_LEFT_SKIN_TONE5 = "\U0001f448\U0001f3ff"; + public const string POINT_RIGHT = "\U0001f449"; + public const string POINT_RIGHT_SKIN_TONE1 = "\U0001f449\U0001f3fb"; + public const string POINT_RIGHT_SKIN_TONE2 = "\U0001f449\U0001f3fc"; + public const string POINT_RIGHT_SKIN_TONE3 = "\U0001f449\U0001f3fd"; + public const string POINT_RIGHT_SKIN_TONE4 = "\U0001f449\U0001f3fe"; + public const string POINT_RIGHT_SKIN_TONE5 = "\U0001f449\U0001f3ff"; + public const string POINT_UP = "\U0000261d"; + public const string POINT_UP2 = "\U0001f446"; + public const string POINT_UP2_SKIN_TONE1 = "\U0001f446\U0001f3fb"; + public const string POINT_UP2_SKIN_TONE2 = "\U0001f446\U0001f3fc"; + public const string POINT_UP2_SKIN_TONE3 = "\U0001f446\U0001f3fd"; + public const string POINT_UP2_SKIN_TONE4 = "\U0001f446\U0001f3fe"; + public const string POINT_UP2_SKIN_TONE5 = "\U0001f446\U0001f3ff"; + public const string POINT_UP_SKIN_TONE1 = "\U0000261d\U0001f3fb"; + public const string POINT_UP_SKIN_TONE2 = "\U0000261d\U0001f3fc"; + public const string POINT_UP_SKIN_TONE3 = "\U0000261d\U0001f3fd"; + public const string POINT_UP_SKIN_TONE4 = "\U0000261d\U0001f3fe"; + public const string POINT_UP_SKIN_TONE5 = "\U0000261d\U0001f3ff"; + public const string POLICE_CAR = "\U0001f693"; + public const string POO = "\U0001f4a9"; + public const string POODLE = "\U0001f429"; + public const string POOP = "\U0001f4a9"; + public const string POPCORN = "\U0001f37f"; + public const string POSTAL_HORN = "\U0001f4ef"; + public const string POSTBOX = "\U0001f4ee"; + public const string POST_OFFICE = "\U0001f3e3"; + public const string POTABLE_WATER = "\U0001f6b0"; + public const string POTATO = "\U0001f954"; + public const string POUCH = "\U0001f45d"; + public const string POULTRY_LEG = "\U0001f357"; + public const string POUND = "\U0001f4b7"; + public const string POUTING_CAT = "\U0001f63e"; + public const string PRAY = "\U0001f64f"; + public const string PRAYER_BEADS = "\U0001f4ff"; + public const string PRAY_SKIN_TONE1 = "\U0001f64f\U0001f3fb"; + public const string PRAY_SKIN_TONE2 = "\U0001f64f\U0001f3fc"; + public const string PRAY_SKIN_TONE3 = "\U0001f64f\U0001f3fd"; + public const string PRAY_SKIN_TONE4 = "\U0001f64f\U0001f3fe"; + public const string PRAY_SKIN_TONE5 = "\U0001f64f\U0001f3ff"; + public const string PREGNANT_WOMAN = "\U0001f930"; + public const string PREGNANT_WOMAN_SKIN_TONE1 = "\U0001f930\U0001f3fb"; + public const string PREGNANT_WOMAN_SKIN_TONE2 = "\U0001f930\U0001f3fc"; + public const string PREGNANT_WOMAN_SKIN_TONE3 = "\U0001f930\U0001f3fd"; + public const string PREGNANT_WOMAN_SKIN_TONE4 = "\U0001f930\U0001f3fe"; + public const string PREGNANT_WOMAN_SKIN_TONE5 = "\U0001f930\U0001f3ff"; + public const string PREVIOUS_TRACK = "\U000023ee"; + public const string PRINCE = "\U0001f934"; + public const string PRINCE_SKIN_TONE1 = "\U0001f934\U0001f3fb"; + public const string PRINCE_SKIN_TONE2 = "\U0001f934\U0001f3fc"; + public const string PRINCE_SKIN_TONE3 = "\U0001f934\U0001f3fd"; + public const string PRINCE_SKIN_TONE4 = "\U0001f934\U0001f3fe"; + public const string PRINCE_SKIN_TONE5 = "\U0001f934\U0001f3ff"; + public const string PRINCESS = "\U0001f478"; + public const string PRINCESS_SKIN_TONE1 = "\U0001f478\U0001f3fb"; + public const string PRINCESS_SKIN_TONE2 = "\U0001f478\U0001f3fc"; + public const string PRINCESS_SKIN_TONE3 = "\U0001f478\U0001f3fd"; + public const string PRINCESS_SKIN_TONE4 = "\U0001f478\U0001f3fe"; + public const string PRINCESS_SKIN_TONE5 = "\U0001f478\U0001f3ff"; + public const string PRINTER = "\U0001f5a8"; + public const string PROJECTOR = "\U0001f4fd"; + public const string PUDDING = "\U0001f36e"; + public const string PUNCH = "\U0001f44a"; + public const string PUNCH_SKIN_TONE1 = "\U0001f44a\U0001f3fb"; + public const string PUNCH_SKIN_TONE2 = "\U0001f44a\U0001f3fc"; + public const string PUNCH_SKIN_TONE3 = "\U0001f44a\U0001f3fd"; + public const string PUNCH_SKIN_TONE4 = "\U0001f44a\U0001f3fe"; + public const string PUNCH_SKIN_TONE5 = "\U0001f44a\U0001f3ff"; + public const string PURPLE_HEART = "\U0001f49c"; + public const string PURSE = "\U0001f45b"; + public const string PUSHPIN = "\U0001f4cc"; + public const string PUT_LITTER_IN_ITS_PLACE = "\U0001f6ae"; + public const string QUESTION = "\U00002753"; + public const string RABBIT = "\U0001f430"; + public const string RABBIT2 = "\U0001f407"; + public const string RACE_CAR = "\U0001f3ce"; + public const string RACEHORSE = "\U0001f40e"; + public const string RACING_CAR = "\U0001f3ce"; + public const string RACING_MOTORCYCLE = "\U0001f3cd"; + public const string RADIO = "\U0001f4fb"; + public const string RADIOACTIVE = "\U00002622"; + public const string RADIOACTIVE_SIGN = "\U00002622"; + public const string RADIO_BUTTON = "\U0001f518"; + public const string RAGE = "\U0001f621"; + public const string RAILROAD_TRACK = "\U0001f6e4"; + public const string RAILWAY_CAR = "\U0001f683"; + public const string RAILWAY_TRACK = "\U0001f6e4"; + public const string RAINBOW = "\U0001f308"; + public const string RAINBOW_FLAG = "\U0001f3f3\U0000fe0f\U0000200d\U0001f308"; + public const string RAISED_BACK_OF_HAND = "\U0001f91a"; + public const string RAISED_BACK_OF_HAND_SKIN_TONE1 = "\U0001f91a\U0001f3fb"; + public const string RAISED_BACK_OF_HAND_SKIN_TONE2 = "\U0001f91a\U0001f3fc"; + public const string RAISED_BACK_OF_HAND_SKIN_TONE3 = "\U0001f91a\U0001f3fd"; + public const string RAISED_BACK_OF_HAND_SKIN_TONE4 = "\U0001f91a\U0001f3fe"; + public const string RAISED_BACK_OF_HAND_SKIN_TONE5 = "\U0001f91a\U0001f3ff"; + public const string RAISED_HAND = "\U0000270b"; + public const string RAISED_HANDS = "\U0001f64c"; + public const string RAISED_HAND_SKIN_TONE1 = "\U0000270b\U0001f3fb"; + public const string RAISED_HAND_SKIN_TONE2 = "\U0000270b\U0001f3fc"; + public const string RAISED_HAND_SKIN_TONE3 = "\U0000270b\U0001f3fd"; + public const string RAISED_HAND_SKIN_TONE4 = "\U0000270b\U0001f3fe"; + public const string RAISED_HAND_SKIN_TONE5 = "\U0000270b\U0001f3ff"; + public const string RAISED_HANDS_SKIN_TONE1 = "\U0001f64c\U0001f3fb"; + public const string RAISED_HANDS_SKIN_TONE2 = "\U0001f64c\U0001f3fc"; + public const string RAISED_HANDS_SKIN_TONE3 = "\U0001f64c\U0001f3fd"; + public const string RAISED_HANDS_SKIN_TONE4 = "\U0001f64c\U0001f3fe"; + public const string RAISED_HANDS_SKIN_TONE5 = "\U0001f64c\U0001f3ff"; + public const string RAISED_HAND_WITH_FINGERS_SPLAYED = "\U0001f590"; + public const string RAISED_HAND_WITH_FINGERS_SPLAYED_SKIN_TONE1 = "\U0001f590\U0001f3fb"; + public const string RAISED_HAND_WITH_FINGERS_SPLAYED_SKIN_TONE2 = "\U0001f590\U0001f3fc"; + public const string RAISED_HAND_WITH_FINGERS_SPLAYED_SKIN_TONE3 = "\U0001f590\U0001f3fd"; + public const string RAISED_HAND_WITH_FINGERS_SPLAYED_SKIN_TONE4 = "\U0001f590\U0001f3fe"; + public const string RAISED_HAND_WITH_FINGERS_SPLAYED_SKIN_TONE5 = "\U0001f590\U0001f3ff"; + public const string RAISED_HAND_WITH_PART_BETWEEN_MIDDLE_AND_RING_FINGERS = "\U0001f596"; + public const string RAISED_HAND_WITH_PART_BETWEEN_MIDDLE_AND_RING_FINGERS_SKIN_TONE1 = "\U0001f596\U0001f3fb"; + public const string RAISED_HAND_WITH_PART_BETWEEN_MIDDLE_AND_RING_FINGERS_SKIN_TONE2 = "\U0001f596\U0001f3fc"; + public const string RAISED_HAND_WITH_PART_BETWEEN_MIDDLE_AND_RING_FINGERS_SKIN_TONE3 = "\U0001f596\U0001f3fd"; + public const string RAISED_HAND_WITH_PART_BETWEEN_MIDDLE_AND_RING_FINGERS_SKIN_TONE4 = "\U0001f596\U0001f3fe"; + public const string RAISED_HAND_WITH_PART_BETWEEN_MIDDLE_AND_RING_FINGERS_SKIN_TONE5 = "\U0001f596\U0001f3ff"; + public const string RAISING_HAND = "\U0001f64b"; + public const string RAISING_HAND_SKIN_TONE1 = "\U0001f64b\U0001f3fb"; + public const string RAISING_HAND_SKIN_TONE2 = "\U0001f64b\U0001f3fc"; + public const string RAISING_HAND_SKIN_TONE3 = "\U0001f64b\U0001f3fd"; + public const string RAISING_HAND_SKIN_TONE4 = "\U0001f64b\U0001f3fe"; + public const string RAISING_HAND_SKIN_TONE5 = "\U0001f64b\U0001f3ff"; + public const string RAM = "\U0001f40f"; + public const string RAMEN = "\U0001f35c"; + public const string RAT = "\U0001f400"; + public const string RECORD_BUTTON = "\U000023fa"; + public const string RECYCLE = "\U0000267b"; + public const string RED_CAR = "\U0001f697"; + public const string RED_CIRCLE = "\U0001f534"; + public const string REGIONAL_INDICATOR_A = "\U0001f1e6"; + public const string REGIONAL_INDICATOR_B = "\U0001f1e7"; + public const string REGIONAL_INDICATOR_C = "\U0001f1e8"; + public const string REGIONAL_INDICATOR_D = "\U0001f1e9"; + public const string REGIONAL_INDICATOR_E = "\U0001f1ea"; + public const string REGIONAL_INDICATOR_F = "\U0001f1eb"; + public const string REGIONAL_INDICATOR_G = "\U0001f1ec"; + public const string REGIONAL_INDICATOR_H = "\U0001f1ed"; + public const string REGIONAL_INDICATOR_I = "\U0001f1ee"; + public const string REGIONAL_INDICATOR_J = "\U0001f1ef"; + public const string REGIONAL_INDICATOR_K = "\U0001f1f0"; + public const string REGIONAL_INDICATOR_L = "\U0001f1f1"; + public const string REGIONAL_INDICATOR_M = "\U0001f1f2"; + public const string REGIONAL_INDICATOR_N = "\U0001f1f3"; + public const string REGIONAL_INDICATOR_O = "\U0001f1f4"; + public const string REGIONAL_INDICATOR_P = "\U0001f1f5"; + public const string REGIONAL_INDICATOR_Q = "\U0001f1f6"; + public const string REGIONAL_INDICATOR_R = "\U0001f1f7"; + public const string REGIONAL_INDICATOR_S = "\U0001f1f8"; + public const string REGIONAL_INDICATOR_T = "\U0001f1f9"; + public const string REGIONAL_INDICATOR_U = "\U0001f1fa"; + public const string REGIONAL_INDICATOR_V = "\U0001f1fb"; + public const string REGIONAL_INDICATOR_W = "\U0001f1fc"; + public const string REGIONAL_INDICATOR_X = "\U0001f1fd"; + public const string REGIONAL_INDICATOR_Y = "\U0001f1fe"; + public const string REGIONAL_INDICATOR_Z = "\U0001f1ff"; + public const string REGISTERED = "\U000000ae"; + public const string RELAXED = "\U0000263a"; + public const string RELIEVED = "\U0001f60c"; + public const string REMINDER_RIBBON = "\U0001f397"; + public const string REPEAT = "\U0001f501"; + public const string REPEAT_ONE = "\U0001f502"; + public const string RESTROOM = "\U0001f6bb"; + public const string REVERSED_HAND_WITH_MIDDLE_FINGER_EXTENDED = "\U0001f595"; + public const string REVERSED_HAND_WITH_MIDDLE_FINGER_EXTENDED_SKIN_TONE1 = "\U0001f595\U0001f3fb"; + public const string REVERSED_HAND_WITH_MIDDLE_FINGER_EXTENDED_SKIN_TONE2 = "\U0001f595\U0001f3fc"; + public const string REVERSED_HAND_WITH_MIDDLE_FINGER_EXTENDED_SKIN_TONE3 = "\U0001f595\U0001f3fd"; + public const string REVERSED_HAND_WITH_MIDDLE_FINGER_EXTENDED_SKIN_TONE4 = "\U0001f595\U0001f3fe"; + public const string REVERSED_HAND_WITH_MIDDLE_FINGER_EXTENDED_SKIN_TONE5 = "\U0001f595\U0001f3ff"; + public const string REVOLVING_HEARTS = "\U0001f49e"; + public const string REWIND = "\U000023ea"; + public const string RHINO = "\U0001f98f"; + public const string RHINOCEROS = "\U0001f98f"; + public const string RIBBON = "\U0001f380"; + public const string RICE = "\U0001f35a"; + public const string RICE_BALL = "\U0001f359"; + public const string RICE_CRACKER = "\U0001f358"; + public const string RICE_SCENE = "\U0001f391"; + public const string RIGHT_ANGER_BUBBLE = "\U0001f5ef"; + public const string RIGHT_FACING_FIST = "\U0001f91c"; + public const string RIGHT_FACING_FIST_SKIN_TONE1 = "\U0001f91c\U0001f3fb"; + public const string RIGHT_FACING_FIST_SKIN_TONE2 = "\U0001f91c\U0001f3fc"; + public const string RIGHT_FACING_FIST_SKIN_TONE3 = "\U0001f91c\U0001f3fd"; + public const string RIGHT_FACING_FIST_SKIN_TONE4 = "\U0001f91c\U0001f3fe"; + public const string RIGHT_FACING_FIST_SKIN_TONE5 = "\U0001f91c\U0001f3ff"; + public const string RIGHT_FIST = "\U0001f91c"; + public const string RIGHT_FIST_SKIN_TONE1 = "\U0001f91c\U0001f3fb"; + public const string RIGHT_FIST_SKIN_TONE2 = "\U0001f91c\U0001f3fc"; + public const string RIGHT_FIST_SKIN_TONE3 = "\U0001f91c\U0001f3fd"; + public const string RIGHT_FIST_SKIN_TONE4 = "\U0001f91c\U0001f3fe"; + public const string RIGHT_FIST_SKIN_TONE5 = "\U0001f91c\U0001f3ff"; + public const string RING = "\U0001f48d"; + public const string ROBOT = "\U0001f916"; + public const string ROBOT_FACE = "\U0001f916"; + public const string ROCKET = "\U0001f680"; + public const string ROFL = "\U0001f923"; + public const string ROLLED_UP_NEWSPAPER = "\U0001f5de"; + public const string ROLLER_COASTER = "\U0001f3a2"; + public const string ROLLING_EYES = "\U0001f644"; + public const string ROLLING_ON_THE_FLOOR_LAUGHING = "\U0001f923"; + public const string ROOSTER = "\U0001f413"; + public const string ROSE = "\U0001f339"; + public const string ROSETTE = "\U0001f3f5"; + public const string ROTATING_LIGHT = "\U0001f6a8"; + public const string ROUND_PUSHPIN = "\U0001f4cd"; + public const string ROWBOAT = "\U0001f6a3"; + public const string ROWBOAT_SKIN_TONE1 = "\U0001f6a3\U0001f3fb"; + public const string ROWBOAT_SKIN_TONE2 = "\U0001f6a3\U0001f3fc"; + public const string ROWBOAT_SKIN_TONE3 = "\U0001f6a3\U0001f3fd"; + public const string ROWBOAT_SKIN_TONE4 = "\U0001f6a3\U0001f3fe"; + public const string ROWBOAT_SKIN_TONE5 = "\U0001f6a3\U0001f3ff"; + public const string RUGBY_FOOTBALL = "\U0001f3c9"; + public const string RUNNER = "\U0001f3c3"; + public const string RUNNER_SKIN_TONE1 = "\U0001f3c3\U0001f3fb"; + public const string RUNNER_SKIN_TONE2 = "\U0001f3c3\U0001f3fc"; + public const string RUNNER_SKIN_TONE3 = "\U0001f3c3\U0001f3fd"; + public const string RUNNER_SKIN_TONE4 = "\U0001f3c3\U0001f3fe"; + public const string RUNNER_SKIN_TONE5 = "\U0001f3c3\U0001f3ff"; + public const string RUNNING_SHIRT_WITH_SASH = "\U0001f3bd"; + public const string SA = "\U0001f202"; + public const string SAGITTARIUS = "\U00002650"; + public const string SAILBOAT = "\U000026f5"; + public const string SAKE = "\U0001f376"; + public const string SALAD = "\U0001f957"; + public const string SANDAL = "\U0001f461"; + public const string SANTA = "\U0001f385"; + public const string SANTA_SKIN_TONE1 = "\U0001f385\U0001f3fb"; + public const string SANTA_SKIN_TONE2 = "\U0001f385\U0001f3fc"; + public const string SANTA_SKIN_TONE3 = "\U0001f385\U0001f3fd"; + public const string SANTA_SKIN_TONE4 = "\U0001f385\U0001f3fe"; + public const string SANTA_SKIN_TONE5 = "\U0001f385\U0001f3ff"; + public const string SATELLITE = "\U0001f4e1"; + public const string SATELLITE_ORBITAL = "\U0001f6f0"; + public const string SATISFIED = "\U0001f606"; + public const string SAXOPHONE = "\U0001f3b7"; + public const string SCALES = "\U00002696"; + public const string SCHOOL = "\U0001f3eb"; + public const string SCHOOL_SATCHEL = "\U0001f392"; + public const string SCISSORS = "\U00002702"; + public const string SCOOTER = "\U0001f6f4"; + public const string SCORPION = "\U0001f982"; + public const string SCORPIUS = "\U0000264f"; + public const string SCREAM = "\U0001f631"; + public const string SCREAM_CAT = "\U0001f640"; + public const string SCROLL = "\U0001f4dc"; + public const string SEAT = "\U0001f4ba"; + public const string SECOND_PLACE = "\U0001f948"; + public const string SECOND_PLACE_MEDAL = "\U0001f948"; + public const string SECRET = "\U00003299"; + public const string SEEDLING = "\U0001f331"; + public const string SEE_NO_EVIL = "\U0001f648"; + public const string SELFIE = "\U0001f933"; + public const string SELFIE_SKIN_TONE1 = "\U0001f933\U0001f3fb"; + public const string SELFIE_SKIN_TONE2 = "\U0001f933\U0001f3fc"; + public const string SELFIE_SKIN_TONE3 = "\U0001f933\U0001f3fd"; + public const string SELFIE_SKIN_TONE4 = "\U0001f933\U0001f3fe"; + public const string SELFIE_SKIN_TONE5 = "\U0001f933\U0001f3ff"; + public const string SEVEN = "\U00000037\U000020e3"; + public const string SHAKING_HANDS = "\U0001f91d"; + public const string SHALLOW_PAN_OF_FOOD = "\U0001f958"; + public const string SHAMROCK = "\U00002618"; + public const string SHARK = "\U0001f988"; + public const string SHAVED_ICE = "\U0001f367"; + public const string SHEEP = "\U0001f411"; + public const string SHELL = "\U0001f41a"; + public const string SHELLED_PEANUT = "\U0001f95c"; + public const string SHIELD = "\U0001f6e1"; + public const string SHINTO_SHRINE = "\U000026e9"; + public const string SHIP = "\U0001f6a2"; + public const string SHIRT = "\U0001f455"; + public const string SHIT = "\U0001f4a9"; + public const string SHOPPING_BAGS = "\U0001f6cd"; + public const string SHOPPING_CART = "\U0001f6d2"; + public const string SHOPPING_TROLLEY = "\U0001f6d2"; + public const string SHOWER = "\U0001f6bf"; + public const string SHRIMP = "\U0001f990"; + public const string SHRUG = "\U0001f937"; + public const string SHRUG_SKIN_TONE1 = "\U0001f937\U0001f3fb"; + public const string SHRUG_SKIN_TONE2 = "\U0001f937\U0001f3fc"; + public const string SHRUG_SKIN_TONE3 = "\U0001f937\U0001f3fd"; + public const string SHRUG_SKIN_TONE4 = "\U0001f937\U0001f3fe"; + public const string SHRUG_SKIN_TONE5 = "\U0001f937\U0001f3ff"; + public const string SICK = "\U0001f922"; + public const string SIGNAL_STRENGTH = "\U0001f4f6"; + public const string SIGN_OF_THE_HORNS = "\U0001f918"; + public const string SIGN_OF_THE_HORNS_SKIN_TONE1 = "\U0001f918\U0001f3fb"; + public const string SIGN_OF_THE_HORNS_SKIN_TONE2 = "\U0001f918\U0001f3fc"; + public const string SIGN_OF_THE_HORNS_SKIN_TONE3 = "\U0001f918\U0001f3fd"; + public const string SIGN_OF_THE_HORNS_SKIN_TONE4 = "\U0001f918\U0001f3fe"; + public const string SIGN_OF_THE_HORNS_SKIN_TONE5 = "\U0001f918\U0001f3ff"; + public const string SIX = "\U00000036\U000020e3"; + public const string SIX_POINTED_STAR = "\U0001f52f"; + public const string SKELETON = "\U0001f480"; + public const string SKI = "\U0001f3bf"; + public const string SKIER = "\U000026f7"; + public const string SKIER_SKIN_TONE1 = "\U000026f7\U0001f3fb"; + public const string SKIER_SKIN_TONE2 = "\U000026f7\U0001f3fc"; + public const string SKIER_SKIN_TONE3 = "\U000026f7\U0001f3fd"; + public const string SKIER_SKIN_TONE4 = "\U000026f7\U0001f3fe"; + public const string SKIER_SKIN_TONE5 = "\U000026f7\U0001f3ff"; + public const string SKULL = "\U0001f480"; + public const string SKULL_AND_CROSSBONES = "\U00002620"; + public const string SKULL_CROSSBONES = "\U00002620"; + public const string SLEEPING = "\U0001f634"; + public const string SLEEPING_ACCOMMODATION = "\U0001f6cc"; + public const string SLEEPING_ACCOMMODATION_SKIN_TONE1 = "\U0001f6cc\U0001f3fb"; + public const string SLEEPING_ACCOMMODATION_SKIN_TONE2 = "\U0001f6cc\U0001f3fc"; + public const string SLEEPING_ACCOMMODATION_SKIN_TONE3 = "\U0001f6cc\U0001f3fd"; + public const string SLEEPING_ACCOMMODATION_SKIN_TONE4 = "\U0001f6cc\U0001f3fe"; + public const string SLEEPING_ACCOMMODATION_SKIN_TONE5 = "\U0001f6cc\U0001f3ff"; + public const string SLEEPY = "\U0001f62a"; + public const string SLEUTH_OR_SPY = "\U0001f575"; + public const string SLEUTH_OR_SPY_SKIN_TONE1 = "\U0001f575\U0001f3fb"; + public const string SLEUTH_OR_SPY_SKIN_TONE2 = "\U0001f575\U0001f3fc"; + public const string SLEUTH_OR_SPY_SKIN_TONE3 = "\U0001f575\U0001f3fd"; + public const string SLEUTH_OR_SPY_SKIN_TONE4 = "\U0001f575\U0001f3fe"; + public const string SLEUTH_OR_SPY_SKIN_TONE5 = "\U0001f575\U0001f3ff"; + public const string SLIGHT_FROWN = "\U0001f641"; + public const string SLIGHTLY_FROWNING_FACE = "\U0001f641"; + public const string SLIGHTLY_SMILING_FACE = "\U0001f642"; + public const string SLIGHT_SMILE = "\U0001f642"; + public const string SLOT_MACHINE = "\U0001f3b0"; + public const string SMALL_AIRPLANE = "\U0001f6e9"; + public const string SMALL_BLUE_DIAMOND = "\U0001f539"; + public const string SMALL_ORANGE_DIAMOND = "\U0001f538"; + public const string SMALL_RED_TRIANGLE = "\U0001f53a"; + public const string SMALL_RED_TRIANGLE_DOWN = "\U0001f53b"; + public const string SMILE = "\U0001f604"; + public const string SMILE_CAT = "\U0001f638"; + public const string SMILEY = "\U0001f603"; + public const string SMILEY_CAT = "\U0001f63a"; + public const string SMILING_IMP = "\U0001f608"; + public const string SMIRK = "\U0001f60f"; + public const string SMIRK_CAT = "\U0001f63c"; + public const string SMOKING = "\U0001f6ac"; + public const string SNAIL = "\U0001f40c"; + public const string SNAKE = "\U0001f40d"; + public const string SNEEZE = "\U0001f927"; + public const string SNEEZING_FACE = "\U0001f927"; + public const string SNOWBOARDER = "\U0001f3c2"; + public const string SNOWBOARDER_SKIN_TONE1 = "\U0001f3c2\U0001f3fb"; + public const string SNOWBOARDER_SKIN_TONE2 = "\U0001f3c2\U0001f3fc"; + public const string SNOWBOARDER_SKIN_TONE3 = "\U0001f3c2\U0001f3fd"; + public const string SNOWBOARDER_SKIN_TONE4 = "\U0001f3c2\U0001f3fe"; + public const string SNOWBOARDER_SKIN_TONE5 = "\U0001f3c2\U0001f3ff"; + public const string SNOW_CAPPED_MOUNTAIN = "\U0001f3d4"; + public const string SNOWFLAKE = "\U00002744"; + public const string SNOWMAN = "\U000026c4"; + public const string SNOWMAN2 = "\U00002603"; + public const string SOB = "\U0001f62d"; + public const string SOCCER = "\U000026bd"; + public const string SOON = "\U0001f51c"; + public const string SOS = "\U0001f198"; + public const string SOUND = "\U0001f509"; + public const string SPACE_INVADER = "\U0001f47e"; + public const string SPADES = "\U00002660"; + public const string SPAGHETTI = "\U0001f35d"; + public const string SPARKLE = "\U00002747"; + public const string SPARKLER = "\U0001f387"; + public const string SPARKLES = "\U00002728"; + public const string SPARKLING_HEART = "\U0001f496"; + public const string SPEAKER = "\U0001f508"; + public const string SPEAKING_HEAD = "\U0001f5e3"; + public const string SPEAKING_HEAD_IN_SILHOUETTE = "\U0001f5e3"; + public const string SPEAK_NO_EVIL = "\U0001f64a"; + public const string SPEECH_BALLOON = "\U0001f4ac"; + public const string SPEECH_LEFT = "\U0001f5e8"; + public const string SPEEDBOAT = "\U0001f6a4"; + public const string SPIDER = "\U0001f577"; + public const string SPIDER_WEB = "\U0001f578"; + public const string SPIRAL_CALENDAR_PAD = "\U0001f5d3"; + public const string SPIRAL_NOTE_PAD = "\U0001f5d2"; + public const string SPOON = "\U0001f944"; + public const string SPORTS_MEDAL = "\U0001f3c5"; + public const string SPY = "\U0001f575"; + public const string SPY_SKIN_TONE1 = "\U0001f575\U0001f3fb"; + public const string SPY_SKIN_TONE2 = "\U0001f575\U0001f3fc"; + public const string SPY_SKIN_TONE3 = "\U0001f575\U0001f3fd"; + public const string SPY_SKIN_TONE4 = "\U0001f575\U0001f3fe"; + public const string SPY_SKIN_TONE5 = "\U0001f575\U0001f3ff"; + public const string SQUID = "\U0001f991"; + public const string STADIUM = "\U0001f3df"; + public const string STAR = "\U00002b50"; + public const string STAR2 = "\U0001f31f"; + public const string STAR_AND_CRESCENT = "\U0000262a"; + public const string STAR_OF_DAVID = "\U00002721"; + public const string STARS = "\U0001f320"; + public const string STATION = "\U0001f689"; + public const string STATUE_OF_LIBERTY = "\U0001f5fd"; + public const string STEAM_LOCOMOTIVE = "\U0001f682"; + public const string STEW = "\U0001f372"; + public const string STOP_BUTTON = "\U000023f9"; + public const string STOP_SIGN = "\U0001f6d1"; + public const string STOPWATCH = "\U000023f1"; + public const string STRAIGHT_RULER = "\U0001f4cf"; + public const string STRAWBERRY = "\U0001f353"; + public const string STUCK_OUT_TONGUE = "\U0001f61b"; + public const string STUCK_OUT_TONGUE_CLOSED_EYES = "\U0001f61d"; + public const string STUCK_OUT_TONGUE_WINKING_EYE = "\U0001f61c"; + public const string STUDIO_MICROPHONE = "\U0001f399"; + public const string STUFFED_FLATBREAD = "\U0001f959"; + public const string STUFFED_PITA = "\U0001f959"; + public const string SUNFLOWER = "\U0001f33b"; + public const string SUNGLASSES = "\U0001f60e"; + public const string SUNNY = "\U00002600"; + public const string SUNRISE = "\U0001f305"; + public const string SUNRISE_OVER_MOUNTAINS = "\U0001f304"; + public const string SUN_WITH_FACE = "\U0001f31e"; + public const string SURFER = "\U0001f3c4"; + public const string SURFER_SKIN_TONE1 = "\U0001f3c4\U0001f3fb"; + public const string SURFER_SKIN_TONE2 = "\U0001f3c4\U0001f3fc"; + public const string SURFER_SKIN_TONE3 = "\U0001f3c4\U0001f3fd"; + public const string SURFER_SKIN_TONE4 = "\U0001f3c4\U0001f3fe"; + public const string SURFER_SKIN_TONE5 = "\U0001f3c4\U0001f3ff"; + public const string SUSHI = "\U0001f363"; + public const string SUSPENSION_RAILWAY = "\U0001f69f"; + public const string SWEAT = "\U0001f613"; + public const string SWEAT_DROPS = "\U0001f4a6"; + public const string SWEAT_SMILE = "\U0001f605"; + public const string SWEET_POTATO = "\U0001f360"; + public const string SWIMMER = "\U0001f3ca"; + public const string SWIMMER_SKIN_TONE1 = "\U0001f3ca\U0001f3fb"; + public const string SWIMMER_SKIN_TONE2 = "\U0001f3ca\U0001f3fc"; + public const string SWIMMER_SKIN_TONE3 = "\U0001f3ca\U0001f3fd"; + public const string SWIMMER_SKIN_TONE4 = "\U0001f3ca\U0001f3fe"; + public const string SWIMMER_SKIN_TONE5 = "\U0001f3ca\U0001f3ff"; + public const string SYMBOLS = "\U0001f523"; + public const string SYNAGOGUE = "\U0001f54d"; + public const string SYRINGE = "\U0001f489"; + public const string TABLE_TENNIS = "\U0001f3d3"; + public const string TACO = "\U0001f32e"; + public const string TADA = "\U0001f389"; + public const string TANABATA_TREE = "\U0001f38b"; + public const string TANGERINE = "\U0001f34a"; + public const string TAURUS = "\U00002649"; + public const string TAXI = "\U0001f695"; + public const string TEA = "\U0001f375"; + public const string TELEPHONE = "\U0000260e"; + public const string TELEPHONE_RECEIVER = "\U0001f4de"; + public const string TELESCOPE = "\U0001f52d"; + public const string TENNIS = "\U0001f3be"; + public const string TENT = "\U000026fa"; + public const string THERMOMETER = "\U0001f321"; + public const string THERMOMETER_FACE = "\U0001f912"; + public const string THINKING = "\U0001f914"; + public const string THINKING_FACE = "\U0001f914"; + public const string THIRD_PLACE = "\U0001f949"; + public const string THIRD_PLACE_MEDAL = "\U0001f949"; + public const string THOUGHT_BALLOON = "\U0001f4ad"; + public const string THREE = "\U00000033\U000020e3"; + public const string THREE_BUTTON_MOUSE = "\U0001f5b1"; + public const string THUMBDOWN = "\U0001f44e"; + public const string THUMBDOWN_SKIN_TONE1 = "\U0001f44e\U0001f3fb"; + public const string THUMBDOWN_SKIN_TONE2 = "\U0001f44e\U0001f3fc"; + public const string THUMBDOWN_SKIN_TONE3 = "\U0001f44e\U0001f3fd"; + public const string THUMBDOWN_SKIN_TONE4 = "\U0001f44e\U0001f3fe"; + public const string THUMBDOWN_SKIN_TONE5 = "\U0001f44e\U0001f3ff"; + public const string THUMBSDOWN = "\U0001f44e"; + public const string THUMBSDOWN_SKIN_TONE1 = "\U0001f44e\U0001f3fb"; + public const string THUMBSDOWN_SKIN_TONE2 = "\U0001f44e\U0001f3fc"; + public const string THUMBSDOWN_SKIN_TONE3 = "\U0001f44e\U0001f3fd"; + public const string THUMBSDOWN_SKIN_TONE4 = "\U0001f44e\U0001f3fe"; + public const string THUMBSDOWN_SKIN_TONE5 = "\U0001f44e\U0001f3ff"; + public const string THUMBSUP = "\U0001f44d"; + public const string THUMBSUP_SKIN_TONE1 = "\U0001f44d\U0001f3fb"; + public const string THUMBSUP_SKIN_TONE2 = "\U0001f44d\U0001f3fc"; + public const string THUMBSUP_SKIN_TONE3 = "\U0001f44d\U0001f3fd"; + public const string THUMBSUP_SKIN_TONE4 = "\U0001f44d\U0001f3fe"; + public const string THUMBSUP_SKIN_TONE5 = "\U0001f44d\U0001f3ff"; + public const string THUMBUP = "\U0001f44d"; + public const string THUMBUP_SKIN_TONE1 = "\U0001f44d\U0001f3fb"; + public const string THUMBUP_SKIN_TONE2 = "\U0001f44d\U0001f3fc"; + public const string THUMBUP_SKIN_TONE3 = "\U0001f44d\U0001f3fd"; + public const string THUMBUP_SKIN_TONE4 = "\U0001f44d\U0001f3fe"; + public const string THUMBUP_SKIN_TONE5 = "\U0001f44d\U0001f3ff"; + public const string THUNDER_CLOUD_AND_RAIN = "\U000026c8"; + public const string THUNDER_CLOUD_RAIN = "\U000026c8"; + public const string TICKET = "\U0001f3ab"; + public const string TICKETS = "\U0001f39f"; + public const string TIGER = "\U0001f42f"; + public const string TIGER2 = "\U0001f405"; + public const string TIMER = "\U000023f2"; + public const string TIMER_CLOCK = "\U000023f2"; + public const string TIRED_FACE = "\U0001f62b"; + public const string TM = "\U00002122"; + public const string TOILET = "\U0001f6bd"; + public const string TOKYO_TOWER = "\U0001f5fc"; + public const string TOMATO = "\U0001f345"; + public const string TONGUE = "\U0001f445"; + public const string TOOLS = "\U0001f6e0"; + public const string TOP = "\U0001f51d"; + public const string TOPHAT = "\U0001f3a9"; + public const string TRACKBALL = "\U0001f5b2"; + public const string TRACK_NEXT = "\U000023ed"; + public const string TRACK_PREVIOUS = "\U000023ee"; + public const string TRACTOR = "\U0001f69c"; + public const string TRAFFIC_LIGHT = "\U0001f6a5"; + public const string TRAIN = "\U0001f68b"; + public const string TRAIN2 = "\U0001f686"; + public const string TRAM = "\U0001f68a"; + public const string TRIANGULAR_FLAG_ON_POST = "\U0001f6a9"; + public const string TRIANGULAR_RULER = "\U0001f4d0"; + public const string TRIDENT = "\U0001f531"; + public const string TRIUMPH = "\U0001f624"; + public const string TROLLEYBUS = "\U0001f68e"; + public const string TROPHY = "\U0001f3c6"; + public const string TROPICAL_DRINK = "\U0001f379"; + public const string TROPICAL_FISH = "\U0001f420"; + public const string TRUCK = "\U0001f69a"; + public const string TRUMPET = "\U0001f3ba"; + public const string TULIP = "\U0001f337"; + public const string TUMBLER_GLASS = "\U0001f943"; + public const string TURKEY = "\U0001f983"; + public const string TURTLE = "\U0001f422"; + public const string TV = "\U0001f4fa"; + public const string TWISTED_RIGHTWARDS_ARROWS = "\U0001f500"; + public const string TWO = "\U00000032\U000020e3"; + public const string TWO_HEARTS = "\U0001f495"; + public const string TWO_MEN_HOLDING_HANDS = "\U0001f46c"; + public const string TWO_WOMEN_HOLDING_HANDS = "\U0001f46d"; public const string U5272 = "\U0001f239"; public const string U5408 = "\U0001f234"; - public const string U55b6 = "\U0001f23a"; + public const string U55_B6 = "\U0001f23a"; public const string U6307 = "\U0001f22f"; public const string U6708 = "\U0001f237"; public const string U6709 = "\U0001f236"; - public const string U6e80 = "\U0001f235"; + public const string U6_E80 = "\U0001f235"; public const string U7121 = "\U0001f21a"; public const string U7533 = "\U0001f238"; public const string U7981 = "\U0001f232"; - public const string U7a7a = "\U0001f233"; - public const string Umbrella = "\U00002614"; - public const string Umbrella2 = "\U00002602"; - public const string UmbrellaOnGround = "\U000026f1"; - public const string Unamused = "\U0001f612"; - public const string Underage = "\U0001f51e"; - public const string Unicorn = "\U0001f984"; - public const string UnicornFace = "\U0001f984"; - public const string Unlock = "\U0001f513"; - public const string Up = "\U0001f199"; - public const string UpsideDown = "\U0001f643"; - public const string UpsideDownFace = "\U0001f643"; - public const string Urn = "\U000026b1"; + public const string U7_A7_A = "\U0001f233"; + public const string UMBRELLA = "\U00002614"; + public const string UMBRELLA2 = "\U00002602"; + public const string UMBRELLA_ON_GROUND = "\U000026f1"; + public const string UNAMUSED = "\U0001f612"; + public const string UNDERAGE = "\U0001f51e"; + public const string UNICORN = "\U0001f984"; + public const string UNICORN_FACE = "\U0001f984"; + public const string UNLOCK = "\U0001f513"; + public const string UP = "\U0001f199"; + public const string UPSIDE_DOWN = "\U0001f643"; + public const string UPSIDE_DOWN_FACE = "\U0001f643"; + public const string URN = "\U000026b1"; public const string V = "\U0000270c"; - public const string VerticalTrafficLight = "\U0001f6a6"; - public const string Vhs = "\U0001f4fc"; - public const string VibrationMode = "\U0001f4f3"; - public const string VideoCamera = "\U0001f4f9"; - public const string VideoGame = "\U0001f3ae"; - public const string Violin = "\U0001f3bb"; - public const string Virgo = "\U0000264d"; - public const string Volcano = "\U0001f30b"; - public const string Volleyball = "\U0001f3d0"; - public const string Vs = "\U0001f19a"; - public const string VSkinTone1 = "\U0000270c\U0001f3fb"; - public const string VSkinTone2 = "\U0000270c\U0001f3fc"; - public const string VSkinTone3 = "\U0000270c\U0001f3fd"; - public const string VSkinTone4 = "\U0000270c\U0001f3fe"; - public const string VSkinTone5 = "\U0000270c\U0001f3ff"; - public const string Vulcan = "\U0001f596"; - public const string VulcanSkinTone1 = "\U0001f596\U0001f3fb"; - public const string VulcanSkinTone2 = "\U0001f596\U0001f3fc"; - public const string VulcanSkinTone3 = "\U0001f596\U0001f3fd"; - public const string VulcanSkinTone4 = "\U0001f596\U0001f3fe"; - public const string VulcanSkinTone5 = "\U0001f596\U0001f3ff"; - public const string Walking = "\U0001f6b6"; - public const string WalkingSkinTone1 = "\U0001f6b6\U0001f3fb"; - public const string WalkingSkinTone2 = "\U0001f6b6\U0001f3fc"; - public const string WalkingSkinTone3 = "\U0001f6b6\U0001f3fd"; - public const string WalkingSkinTone4 = "\U0001f6b6\U0001f3fe"; - public const string WalkingSkinTone5 = "\U0001f6b6\U0001f3ff"; - public const string WaningCrescentMoon = "\U0001f318"; - public const string WaningGibbousMoon = "\U0001f316"; - public const string Warning = "\U000026a0"; - public const string Wastebasket = "\U0001f5d1"; - public const string Watch = "\U0000231a"; - public const string WaterBuffalo = "\U0001f403"; - public const string Watermelon = "\U0001f349"; - public const string WaterPolo = "\U0001f93d"; - public const string WaterPoloSkinTone1 = "\U0001f93d\U0001f3fb"; - public const string WaterPoloSkinTone2 = "\U0001f93d\U0001f3fc"; - public const string WaterPoloSkinTone3 = "\U0001f93d\U0001f3fd"; - public const string WaterPoloSkinTone4 = "\U0001f93d\U0001f3fe"; - public const string WaterPoloSkinTone5 = "\U0001f93d\U0001f3ff"; - public const string Wave = "\U0001f44b"; - public const string WaveSkinTone1 = "\U0001f44b\U0001f3fb"; - public const string WaveSkinTone2 = "\U0001f44b\U0001f3fc"; - public const string WaveSkinTone3 = "\U0001f44b\U0001f3fd"; - public const string WaveSkinTone4 = "\U0001f44b\U0001f3fe"; - public const string WaveSkinTone5 = "\U0001f44b\U0001f3ff"; - public const string WavyDash = "\U00003030"; - public const string WaxingCrescentMoon = "\U0001f312"; - public const string WaxingGibbousMoon = "\U0001f314"; - public const string Wc = "\U0001f6be"; - public const string Weary = "\U0001f629"; - public const string Wedding = "\U0001f492"; - public const string WeightLifter = "\U0001f3cb"; - public const string WeightLifterSkinTone1 = "\U0001f3cb\U0001f3fb"; - public const string WeightLifterSkinTone2 = "\U0001f3cb\U0001f3fc"; - public const string WeightLifterSkinTone3 = "\U0001f3cb\U0001f3fd"; - public const string WeightLifterSkinTone4 = "\U0001f3cb\U0001f3fe"; - public const string WeightLifterSkinTone5 = "\U0001f3cb\U0001f3ff"; - public const string Whale = "\U0001f433"; - public const string Whale2 = "\U0001f40b"; - public const string Wheelchair = "\U0000267f"; - public const string WheelOfDharma = "\U00002638"; - public const string Whisky = "\U0001f943"; - public const string WhiteCheckMark = "\U00002705"; - public const string WhiteCircle = "\U000026aa"; - public const string WhiteFlower = "\U0001f4ae"; - public const string WhiteFrowningFace = "\U00002639"; - public const string WhiteLargeSquare = "\U00002b1c"; - public const string WhiteMediumSmallSquare = "\U000025fd"; - public const string WhiteMediumSquare = "\U000025fb"; - public const string WhiteSmallSquare = "\U000025ab"; - public const string WhiteSquareButton = "\U0001f533"; - public const string WhiteSunBehindCloud = "\U0001f325"; - public const string WhiteSunBehindCloudWithRain = "\U0001f326"; - public const string WhiteSunCloud = "\U0001f325"; - public const string WhiteSunRainCloud = "\U0001f326"; - public const string WhiteSunSmallCloud = "\U0001f324"; - public const string WhiteSunWithSmallCloud = "\U0001f324"; - public const string WiltedFlower = "\U0001f940"; - public const string WiltedRose = "\U0001f940"; - public const string WindBlowingFace = "\U0001f32c"; - public const string WindChime = "\U0001f390"; - public const string WineGlass = "\U0001f377"; - public const string Wink = "\U0001f609"; - public const string Wolf = "\U0001f43a"; - public const string Woman = "\U0001f469"; - public const string WomansClothes = "\U0001f45a"; - public const string WomansHat = "\U0001f452"; - public const string WomanSkinTone1 = "\U0001f469\U0001f3fb"; - public const string WomanSkinTone2 = "\U0001f469\U0001f3fc"; - public const string WomanSkinTone3 = "\U0001f469\U0001f3fd"; - public const string WomanSkinTone4 = "\U0001f469\U0001f3fe"; - public const string WomanSkinTone5 = "\U0001f469\U0001f3ff"; - public const string Womens = "\U0001f6ba"; - public const string WorldMap = "\U0001f5fa"; - public const string Worried = "\U0001f61f"; - public const string WorshipSymbol = "\U0001f6d0"; - public const string Wrench = "\U0001f527"; - public const string Wrestlers = "\U0001f93c"; - public const string Wrestling = "\U0001f93c"; - public const string WritingHand = "\U0000270d"; - public const string WritingHandSkinTone1 = "\U0000270d\U0001f3fb"; - public const string WritingHandSkinTone2 = "\U0000270d\U0001f3fc"; - public const string WritingHandSkinTone3 = "\U0000270d\U0001f3fd"; - public const string WritingHandSkinTone4 = "\U0000270d\U0001f3fe"; - public const string WritingHandSkinTone5 = "\U0000270d\U0001f3ff"; + public const string VERTICAL_TRAFFIC_LIGHT = "\U0001f6a6"; + public const string VHS = "\U0001f4fc"; + public const string VIBRATION_MODE = "\U0001f4f3"; + public const string VIDEO_CAMERA = "\U0001f4f9"; + public const string VIDEO_GAME = "\U0001f3ae"; + public const string VIOLIN = "\U0001f3bb"; + public const string VIRGO = "\U0000264d"; + public const string VOLCANO = "\U0001f30b"; + public const string VOLLEYBALL = "\U0001f3d0"; + public const string VS = "\U0001f19a"; + public const string V_SKIN_TONE1 = "\U0000270c\U0001f3fb"; + public const string V_SKIN_TONE2 = "\U0000270c\U0001f3fc"; + public const string V_SKIN_TONE3 = "\U0000270c\U0001f3fd"; + public const string V_SKIN_TONE4 = "\U0000270c\U0001f3fe"; + public const string V_SKIN_TONE5 = "\U0000270c\U0001f3ff"; + public const string VULCAN = "\U0001f596"; + public const string VULCAN_SKIN_TONE1 = "\U0001f596\U0001f3fb"; + public const string VULCAN_SKIN_TONE2 = "\U0001f596\U0001f3fc"; + public const string VULCAN_SKIN_TONE3 = "\U0001f596\U0001f3fd"; + public const string VULCAN_SKIN_TONE4 = "\U0001f596\U0001f3fe"; + public const string VULCAN_SKIN_TONE5 = "\U0001f596\U0001f3ff"; + public const string WALKING = "\U0001f6b6"; + public const string WALKING_SKIN_TONE1 = "\U0001f6b6\U0001f3fb"; + public const string WALKING_SKIN_TONE2 = "\U0001f6b6\U0001f3fc"; + public const string WALKING_SKIN_TONE3 = "\U0001f6b6\U0001f3fd"; + public const string WALKING_SKIN_TONE4 = "\U0001f6b6\U0001f3fe"; + public const string WALKING_SKIN_TONE5 = "\U0001f6b6\U0001f3ff"; + public const string WANING_CRESCENT_MOON = "\U0001f318"; + public const string WANING_GIBBOUS_MOON = "\U0001f316"; + public const string WARNING = "\U000026a0"; + public const string WASTEBASKET = "\U0001f5d1"; + public const string WATCH = "\U0000231a"; + public const string WATER_BUFFALO = "\U0001f403"; + public const string WATERMELON = "\U0001f349"; + public const string WATER_POLO = "\U0001f93d"; + public const string WATER_POLO_SKIN_TONE1 = "\U0001f93d\U0001f3fb"; + public const string WATER_POLO_SKIN_TONE2 = "\U0001f93d\U0001f3fc"; + public const string WATER_POLO_SKIN_TONE3 = "\U0001f93d\U0001f3fd"; + public const string WATER_POLO_SKIN_TONE4 = "\U0001f93d\U0001f3fe"; + public const string WATER_POLO_SKIN_TONE5 = "\U0001f93d\U0001f3ff"; + public const string WAVE = "\U0001f44b"; + public const string WAVE_SKIN_TONE1 = "\U0001f44b\U0001f3fb"; + public const string WAVE_SKIN_TONE2 = "\U0001f44b\U0001f3fc"; + public const string WAVE_SKIN_TONE3 = "\U0001f44b\U0001f3fd"; + public const string WAVE_SKIN_TONE4 = "\U0001f44b\U0001f3fe"; + public const string WAVE_SKIN_TONE5 = "\U0001f44b\U0001f3ff"; + public const string WAVY_DASH = "\U00003030"; + public const string WAXING_CRESCENT_MOON = "\U0001f312"; + public const string WAXING_GIBBOUS_MOON = "\U0001f314"; + public const string WC = "\U0001f6be"; + public const string WEARY = "\U0001f629"; + public const string WEDDING = "\U0001f492"; + public const string WEIGHT_LIFTER = "\U0001f3cb"; + public const string WEIGHT_LIFTER_SKIN_TONE1 = "\U0001f3cb\U0001f3fb"; + public const string WEIGHT_LIFTER_SKIN_TONE2 = "\U0001f3cb\U0001f3fc"; + public const string WEIGHT_LIFTER_SKIN_TONE3 = "\U0001f3cb\U0001f3fd"; + public const string WEIGHT_LIFTER_SKIN_TONE4 = "\U0001f3cb\U0001f3fe"; + public const string WEIGHT_LIFTER_SKIN_TONE5 = "\U0001f3cb\U0001f3ff"; + public const string WHALE = "\U0001f433"; + public const string WHALE2 = "\U0001f40b"; + public const string WHEELCHAIR = "\U0000267f"; + public const string WHEEL_OF_DHARMA = "\U00002638"; + public const string WHISKY = "\U0001f943"; + public const string WHITE_CHECK_MARK = "\U00002705"; + public const string WHITE_CIRCLE = "\U000026aa"; + public const string WHITE_FLOWER = "\U0001f4ae"; + public const string WHITE_FROWNING_FACE = "\U00002639"; + public const string WHITE_LARGE_SQUARE = "\U00002b1c"; + public const string WHITE_MEDIUM_SMALL_SQUARE = "\U000025fd"; + public const string WHITE_MEDIUM_SQUARE = "\U000025fb"; + public const string WHITE_SMALL_SQUARE = "\U000025ab"; + public const string WHITE_SQUARE_BUTTON = "\U0001f533"; + public const string WHITE_SUN_BEHIND_CLOUD = "\U0001f325"; + public const string WHITE_SUN_BEHIND_CLOUD_WITH_RAIN = "\U0001f326"; + public const string WHITE_SUN_CLOUD = "\U0001f325"; + public const string WHITE_SUN_RAIN_CLOUD = "\U0001f326"; + public const string WHITE_SUN_SMALL_CLOUD = "\U0001f324"; + public const string WHITE_SUN_WITH_SMALL_CLOUD = "\U0001f324"; + public const string WILTED_FLOWER = "\U0001f940"; + public const string WILTED_ROSE = "\U0001f940"; + public const string WIND_BLOWING_FACE = "\U0001f32c"; + public const string WIND_CHIME = "\U0001f390"; + public const string WINE_GLASS = "\U0001f377"; + public const string WINK = "\U0001f609"; + public const string WOLF = "\U0001f43a"; + public const string WOMAN = "\U0001f469"; + public const string WOMANS_CLOTHES = "\U0001f45a"; + public const string WOMANS_HAT = "\U0001f452"; + public const string WOMAN_SKIN_TONE1 = "\U0001f469\U0001f3fb"; + public const string WOMAN_SKIN_TONE2 = "\U0001f469\U0001f3fc"; + public const string WOMAN_SKIN_TONE3 = "\U0001f469\U0001f3fd"; + public const string WOMAN_SKIN_TONE4 = "\U0001f469\U0001f3fe"; + public const string WOMAN_SKIN_TONE5 = "\U0001f469\U0001f3ff"; + public const string WOMENS = "\U0001f6ba"; + public const string WORLD_MAP = "\U0001f5fa"; + public const string WORRIED = "\U0001f61f"; + public const string WORSHIP_SYMBOL = "\U0001f6d0"; + public const string WRENCH = "\U0001f527"; + public const string WRESTLERS = "\U0001f93c"; + public const string WRESTLING = "\U0001f93c"; + public const string WRITING_HAND = "\U0000270d"; + public const string WRITING_HAND_SKIN_TONE1 = "\U0000270d\U0001f3fb"; + public const string WRITING_HAND_SKIN_TONE2 = "\U0000270d\U0001f3fc"; + public const string WRITING_HAND_SKIN_TONE3 = "\U0000270d\U0001f3fd"; + public const string WRITING_HAND_SKIN_TONE4 = "\U0000270d\U0001f3fe"; + public const string WRITING_HAND_SKIN_TONE5 = "\U0000270d\U0001f3ff"; public const string X = "\U0000274c"; - public const string YellowHeart = "\U0001f49b"; - public const string Yen = "\U0001f4b4"; - public const string YinYang = "\U0000262f"; - public const string Yum = "\U0001f60b"; - public const string Zap = "\U000026a1"; - public const string Zero = "\U00000030\U000020e3"; - public const string ZipperMouth = "\U0001f910"; - public const string ZipperMouthFace = "\U0001f910"; - public const string Zzz = "\U0001f4a4"; + public const string YELLOW_HEART = "\U0001f49b"; + public const string YEN = "\U0001f4b4"; + public const string YIN_YANG = "\U0000262f"; + public const string YUM = "\U0001f60b"; + public const string ZAP = "\U000026a1"; + public const string ZERO = "\U00000030\U000020e3"; + public const string ZIPPER_MOUTH = "\U0001f910"; + public const string ZIPPER_MOUTH_FACE = "\U0001f910"; + public const string ZZZ = "\U0001f4a4"; #pragma warning restore IDE1006 #pragma warning restore ConstFieldDocumentationHeader } } diff --git a/DisCatSharp/Entities/Guild/DiscordGuild.cs b/DisCatSharp/Entities/Guild/DiscordGuild.cs index 4d78f6341..2282d9cea 100644 --- a/DisCatSharp/Entities/Guild/DiscordGuild.cs +++ b/DisCatSharp/Entities/Guild/DiscordGuild.cs @@ -1,3574 +1,3574 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. #pragma warning disable CS0618 using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Globalization; using System.IO; using System.Linq; using System.Threading.Tasks; using DisCatSharp.Enums; using DisCatSharp.Net; using DisCatSharp.Net.Abstractions; using DisCatSharp.Net.Models; using DisCatSharp.Net.Serialization; using Microsoft.Extensions.Logging; using Newtonsoft.Json; using Newtonsoft.Json.Linq; namespace DisCatSharp.Entities { /// /// Represents a Discord guild. /// public class DiscordGuild : SnowflakeObject, IEquatable { /// /// Gets the guild's name. /// [JsonProperty("name", NullValueHandling = NullValueHandling.Ignore)] public string Name { get; internal set; } /// /// Gets the guild icon's hash. /// [JsonProperty("icon", NullValueHandling = NullValueHandling.Ignore)] public string IconHash { get; internal set; } /// /// Gets the guild icon's url. /// [JsonIgnore] public string IconUrl => !string.IsNullOrWhiteSpace(this.IconHash) ? $"{DiscordDomain.GetDomain(CoreDomain.DiscordCdn).Url}{Endpoints.ICONS}/{this.Id.ToString(CultureInfo.InvariantCulture)}/{this.IconHash}.{(this.IconHash.StartsWith("a_") ? "gif" : "png")}?size=1024" : null; /// /// Gets the guild splash's hash. /// [JsonProperty("splash", NullValueHandling = NullValueHandling.Ignore)] public string SplashHash { get; internal set; } /// /// Gets the guild splash's url. /// [JsonIgnore] public string SplashUrl => !string.IsNullOrWhiteSpace(this.SplashHash) ? $"{DiscordDomain.GetDomain(CoreDomain.DiscordCdn).Url}{Endpoints.SPLASHES}/{this.Id.ToString(CultureInfo.InvariantCulture)}/{this.SplashHash}.png?size=1024" : null; /// /// Gets the guild discovery splash's hash. /// [JsonProperty("discovery_splash", NullValueHandling = NullValueHandling.Ignore)] public string DiscoverySplashHash { get; internal set; } /// /// Gets the guild discovery splash's url. /// [JsonIgnore] public string DiscoverySplashUrl => !string.IsNullOrWhiteSpace(this.DiscoverySplashHash) ? $"{DiscordDomain.GetDomain(CoreDomain.DiscordCdn).Url}{Endpoints.GUILD_DISCOVERY_SPLASHES}/{this.Id.ToString(CultureInfo.InvariantCulture)}/{this.DiscoverySplashHash}.png?size=1024" : null; /// /// Gets the preferred locale of this guild. /// This is used for server discovery and notices from Discord. Defaults to en-US. /// [JsonProperty("preferred_locale", NullValueHandling = NullValueHandling.Ignore)] public string PreferredLocale { get; internal set; } /// /// Gets the ID of the guild's owner. /// [JsonProperty("owner_id", NullValueHandling = NullValueHandling.Ignore)] public ulong OwnerId { get; internal set; } /// /// Gets the guild's owner. /// [JsonIgnore] public DiscordMember Owner => this.Members.TryGetValue(this.OwnerId, out var owner) ? owner : this.Discord.ApiClient.GetGuildMemberAsync(this.Id, this.OwnerId).ConfigureAwait(false).GetAwaiter().GetResult(); /// /// Gets permissions for the user in the guild (does not include channel overrides) /// [JsonProperty("permissions", NullValueHandling = NullValueHandling.Ignore)] public Permissions? Permissions { get; set; } /// /// Gets the guild's voice region ID. /// [JsonProperty("region", NullValueHandling = NullValueHandling.Ignore)] internal string VoiceRegionId { get; set; } /// /// Gets the guild's voice region. /// [JsonIgnore] public DiscordVoiceRegion VoiceRegion => this.Discord.VoiceRegions[this.VoiceRegionId]; /// /// Gets the guild's AFK voice channel ID. /// [JsonProperty("afk_channel_id", NullValueHandling = NullValueHandling.Ignore)] internal ulong AfkChannelId { get; set; } = 0; /// /// Gets the guild's AFK voice channel. /// [JsonIgnore] public DiscordChannel AfkChannel => this.GetChannel(this.AfkChannelId); /// /// List of . /// Null if DisCatSharp.ApplicationCommands is not used or no guild commands are registered. /// [JsonIgnore] public ReadOnlyCollection RegisteredApplicationCommands => new(this.InternalRegisteredApplicationCommands); [JsonIgnore] internal List InternalRegisteredApplicationCommands { get; set; } = null; /// /// Gets the guild's AFK timeout. /// [JsonProperty("afk_timeout", NullValueHandling = NullValueHandling.Ignore)] public int AfkTimeout { get; internal set; } /// /// Gets the guild's verification level. /// [JsonProperty("verification_level", NullValueHandling = NullValueHandling.Ignore)] public VerificationLevel VerificationLevel { get; internal set; } /// /// Gets the guild's default notification settings. /// [JsonProperty("default_message_notifications", NullValueHandling = NullValueHandling.Ignore)] public DefaultMessageNotifications DefaultMessageNotifications { get; internal set; } /// /// Gets the guild's explicit content filter settings. /// [JsonProperty("explicit_content_filter")] public ExplicitContentFilter ExplicitContentFilter { get; internal set; } /// /// Gets the guild's nsfw level. /// [JsonProperty("nsfw_level")] public NsfwLevel NsfwLevel { get; internal set; } /// /// Gets the system channel id. /// [JsonProperty("system_channel_id", NullValueHandling = NullValueHandling.Include)] internal ulong? SystemChannelId { get; set; } /// /// Gets the channel where system messages (such as boost and welcome messages) are sent. /// [JsonIgnore] public DiscordChannel SystemChannel => this.SystemChannelId.HasValue ? this.GetChannel(this.SystemChannelId.Value) : null; /// /// Gets the settings for this guild's system channel. /// [JsonProperty("system_channel_flags")] public SystemChannelFlags SystemChannelFlags { get; internal set; } /// /// Gets whether this guild's widget is enabled. /// [JsonProperty("widget_enabled", NullValueHandling = NullValueHandling.Ignore)] public bool? WidgetEnabled { get; internal set; } /// /// Gets the widget channel id. /// [JsonProperty("widget_channel_id", NullValueHandling = NullValueHandling.Ignore)] internal ulong? WidgetChannelId { get; set; } /// /// Gets the widget channel for this guild. /// [JsonIgnore] public DiscordChannel WidgetChannel => this.WidgetChannelId.HasValue ? this.GetChannel(this.WidgetChannelId.Value) : null; /// /// Gets the rules channel id. /// [JsonProperty("rules_channel_id")] internal ulong? RulesChannelId { get; set; } /// /// Gets the rules channel for this guild. /// This is only available if the guild is considered "discoverable". /// [JsonIgnore] public DiscordChannel RulesChannel => this.RulesChannelId.HasValue ? this.GetChannel(this.RulesChannelId.Value) : null; /// /// Gets the public updates channel id. /// [JsonProperty("public_updates_channel_id")] internal ulong? PublicUpdatesChannelId { get; set; } /// /// Gets the public updates channel (where admins and moderators receive messages from Discord) for this guild. /// This is only available if the guild is considered "discoverable". /// [JsonIgnore] public DiscordChannel PublicUpdatesChannel => this.PublicUpdatesChannelId.HasValue ? this.GetChannel(this.PublicUpdatesChannelId.Value) : null; /// /// Gets the application id of this guild if it is bot created. /// [JsonProperty("application_id")] public ulong? ApplicationId { get; internal set; } /// /// Gets a collection of this guild's roles. /// [JsonIgnore] - public IReadOnlyDictionary Roles => new ReadOnlyConcurrentDictionary(this._roles); + public IReadOnlyDictionary Roles => new ReadOnlyConcurrentDictionary(this.RolesInternal); [JsonProperty("roles", NullValueHandling = NullValueHandling.Ignore)] [JsonConverter(typeof(SnowflakeArrayAsDictionaryJsonConverter))] - internal ConcurrentDictionary _roles; + internal ConcurrentDictionary RolesInternal; /// /// Gets a collection of this guild's stickers. /// [JsonIgnore] - public IReadOnlyDictionary Stickers => new ReadOnlyConcurrentDictionary(this._stickers); + public IReadOnlyDictionary Stickers => new ReadOnlyConcurrentDictionary(this.StickersInternal); [JsonProperty("stickers", NullValueHandling = NullValueHandling.Ignore)] [JsonConverter(typeof(SnowflakeArrayAsDictionaryJsonConverter))] - internal ConcurrentDictionary _stickers; + internal ConcurrentDictionary StickersInternal; /// /// Gets a collection of this guild's emojis. /// [JsonIgnore] - public IReadOnlyDictionary Emojis => new ReadOnlyConcurrentDictionary(this._emojis); + public IReadOnlyDictionary Emojis => new ReadOnlyConcurrentDictionary(this.EmojisInternal); [JsonProperty("emojis", NullValueHandling = NullValueHandling.Ignore)] [JsonConverter(typeof(SnowflakeArrayAsDictionaryJsonConverter))] - internal ConcurrentDictionary _emojis; + 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; } #pragma warning disable CS1734 /// /// Gets the approximate number of members in this guild, when using and having set to true. /// [JsonProperty("approximate_member_count", NullValueHandling = NullValueHandling.Ignore)] public int? ApproximateMemberCount { get; internal set; } /// /// Gets the approximate number of presences in this guild, when using and having set to true. /// [JsonProperty("approximate_presence_count", NullValueHandling = NullValueHandling.Ignore)] public int? ApproximatePresenceCount { get; internal set; } #pragma warning restore CS1734 /// /// Gets the maximum amount of users allowed per video channel. /// [JsonProperty("max_video_channel_users", NullValueHandling = NullValueHandling.Ignore)] public int? MaxVideoChannelUsers { get; internal set; } /// /// Gets a dictionary of all the voice states for this guilds. The key for this dictionary is the ID of the user /// the voice state corresponds to. /// [JsonIgnore] - public IReadOnlyDictionary VoiceStates => new ReadOnlyConcurrentDictionary(this._voiceStates); + public IReadOnlyDictionary VoiceStates => new ReadOnlyConcurrentDictionary(this.VoiceStatesInternal); [JsonProperty("voice_states", NullValueHandling = NullValueHandling.Ignore)] [JsonConverter(typeof(SnowflakeArrayAsDictionaryJsonConverter))] - internal ConcurrentDictionary _voiceStates; + internal ConcurrentDictionary VoiceStatesInternal; /// /// Gets a dictionary of all the members that belong to this guild. The dictionary's key is the member ID. /// [JsonIgnore] // TODO overhead of => vs Lazy? it's a struct - public IReadOnlyDictionary Members => new ReadOnlyConcurrentDictionary(this._members); + public IReadOnlyDictionary Members => new ReadOnlyConcurrentDictionary(this.MembersInternal); [JsonProperty("members", NullValueHandling = NullValueHandling.Ignore)] [JsonConverter(typeof(SnowflakeArrayAsDictionaryJsonConverter))] - internal ConcurrentDictionary _members; + 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._channels); + public IReadOnlyDictionary Channels => new ReadOnlyConcurrentDictionary(this.ChannelsInternal); [JsonProperty("channels", NullValueHandling = NullValueHandling.Ignore)] [JsonConverter(typeof(SnowflakeArrayAsDictionaryJsonConverter))] - internal ConcurrentDictionary _channels; + internal ConcurrentDictionary ChannelsInternal; - internal ConcurrentDictionary _invites; + internal ConcurrentDictionary Invites; /// /// Gets a dictionary of all the active threads associated with this guild the user has permission to view. The dictionary's key is the channel ID. /// [JsonIgnore] public IReadOnlyDictionary Threads { get; internal set; } [JsonProperty("threads", NullValueHandling = NullValueHandling.Ignore)] [JsonConverter(typeof(SnowflakeArrayAsDictionaryJsonConverter))] - internal ConcurrentDictionary _threads = new(); + 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 _stageInstances = new(); + 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 _scheduledEvents = new(); + internal ConcurrentDictionary ScheduledEventsInternal = new(); /// /// Gets the guild member for current user. /// [JsonIgnore] public DiscordMember CurrentMember - => this._current_member_lazy.Value; + => this._currentMemberLazy.Value; [JsonIgnore] - private readonly Lazy _current_member_lazy; + private readonly Lazy _currentMemberLazy; /// /// Gets the @everyone role for this guild. /// [JsonIgnore] public DiscordRole EveryoneRole => this.GetRole(this.Id); [JsonIgnore] - internal bool _isOwner; + internal bool IsOwnerInternal; /// /// Gets whether the current user is the guild's owner. /// [JsonProperty("owner", NullValueHandling = NullValueHandling.Ignore)] public bool IsOwner { - get => this._isOwner || this.OwnerId == this.Discord.CurrentUser.Id; - internal set => this._isOwner = value; + get => this.IsOwnerInternal || this.OwnerId == this.Discord.CurrentUser.Id; + internal set => this.IsOwnerInternal = value; } /// /// Gets the vanity URL code for this guild, when applicable. /// [JsonProperty("vanity_url_code")] public string VanityUrlCode { get; internal set; } /// /// Gets the guild description, when applicable. /// [JsonProperty("description")] public string Description { get; internal set; } /// /// Gets this guild's banner hash, when applicable. /// [JsonProperty("banner")] public string BannerHash { get; internal set; } /// /// Gets this guild's banner in url form. /// [JsonIgnore] public string BannerUrl => !string.IsNullOrWhiteSpace(this.BannerHash) ? $"{DiscordDomain.GetDomain(CoreDomain.DiscordCdn).Uri}{Endpoints.BANNERS}/{this.Id.ToString(CultureInfo.InvariantCulture)}/{this.BannerHash}.{(this.BannerHash.StartsWith("a_") ? "gif" : "png")}" : null; /// /// Whether this guild has the community feature enabled. /// [JsonIgnore] public bool IsCommunity => this.Features.HasCommunityEnabled; /// /// Whether this guild has enabled the welcome screen. /// [JsonIgnore] public bool HasWelcomeScreen => this.Features.HasWelcomeScreenEnabled; /// /// Whether this guild has enabled membership screening. /// [JsonIgnore] public bool HasMemberVerificationGate => this.Features.HasMembershipScreeningEnabled; /// /// Gets this guild's premium tier (Nitro boosting). /// [JsonProperty("premium_tier")] public PremiumTier PremiumTier { get; internal set; } /// /// Gets the amount of members that boosted this guild. /// [JsonProperty("premium_subscription_count", NullValueHandling = NullValueHandling.Ignore)] public int? PremiumSubscriptionCount { get; internal set; } /// /// Whether the premium progress bar is enabled. /// [JsonProperty("premium_progress_bar_enabled", NullValueHandling = NullValueHandling.Ignore)] public bool PremiumProgressBarEnabled { get; internal set; } /// /// Gets whether this guild is designated as NSFW. /// [JsonProperty("nsfw", NullValueHandling = NullValueHandling.Ignore)] - public bool IsNSFW { get; internal set; } + 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 raw_channels = this._channels.Values.ToList(); + IReadOnlyList rawChannels = this.ChannelsInternal.Values.ToList(); - Dictionary> ordered_channels = new(); + Dictionary> orderedChannels = new(); - ordered_channels.Add(0, new List()); + orderedChannels.Add(0, new List()); - foreach (var channel in raw_channels.Where(c => c.Type == ChannelType.Category).OrderBy(c => c.Position)) + foreach (var channel in rawChannels.Where(c => c.Type == ChannelType.Category).OrderBy(c => c.Position)) { - ordered_channels.Add(channel.Id, new List()); + orderedChannels.Add(channel.Id, new List()); } - foreach (var channel in raw_channels.Where(c => c.ParentId.HasValue && (c.Type == ChannelType.Text || c.Type == ChannelType.News)).OrderBy(c => c.Position)) + foreach (var channel in rawChannels.Where(c => c.ParentId.HasValue && (c.Type == ChannelType.Text || c.Type == ChannelType.News)).OrderBy(c => c.Position)) { - ordered_channels[channel.ParentId.Value].Add(channel); + orderedChannels[channel.ParentId.Value].Add(channel); } - foreach (var channel in raw_channels.Where(c => c.ParentId.HasValue && (c.Type == ChannelType.Voice || c.Type == ChannelType.Stage)).OrderBy(c => c.Position)) + foreach (var channel in rawChannels.Where(c => c.ParentId.HasValue && (c.Type == ChannelType.Voice || c.Type == ChannelType.Stage)).OrderBy(c => c.Position)) { - ordered_channels[channel.ParentId.Value].Add(channel); + orderedChannels[channel.ParentId.Value].Add(channel); } - foreach (var channel in raw_channels.Where(c => !c.ParentId.HasValue && c.Type != ChannelType.Category && (c.Type == ChannelType.Text || c.Type == ChannelType.News)).OrderBy(c => c.Position)) + foreach (var channel in rawChannels.Where(c => !c.ParentId.HasValue && c.Type != ChannelType.Category && (c.Type == ChannelType.Text || c.Type == ChannelType.News)).OrderBy(c => c.Position)) { - ordered_channels[0].Add(channel); + orderedChannels[0].Add(channel); } - foreach (var channel in raw_channels.Where(c => !c.ParentId.HasValue && c.Type != ChannelType.Category && (c.Type == ChannelType.Voice || c.Type == ChannelType.Stage)).OrderBy(c => c.Position)) + 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)) { - ordered_channels[0].Add(channel); + orderedChannels[0].Add(channel); } - return ordered_channels; + 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 raw_channels = await this.Discord.ApiClient.GetGuildChannelsAsync(this.Id); + var rawChannels = await this.Discord.ApiClient.GetGuildChannelsAsync(this.Id); - Dictionary> ordered_channels = new(); + Dictionary> orderedChannels = new(); - ordered_channels.Add(0, new List()); + orderedChannels.Add(0, new List()); - foreach (var channel in raw_channels.Where(c => c.Type == ChannelType.Category).OrderBy(c => c.Position)) + foreach (var channel in rawChannels.Where(c => c.Type == ChannelType.Category).OrderBy(c => c.Position)) { - ordered_channels.Add(channel.Id, new List()); + orderedChannels.Add(channel.Id, new List()); } - foreach (var channel in raw_channels.Where(c => c.ParentId.HasValue && (c.Type == ChannelType.Text || c.Type == ChannelType.News)).OrderBy(c => c.Position)) + foreach (var channel in rawChannels.Where(c => c.ParentId.HasValue && (c.Type == ChannelType.Text || c.Type == ChannelType.News)).OrderBy(c => c.Position)) { - ordered_channels[channel.ParentId.Value].Add(channel); + orderedChannels[channel.ParentId.Value].Add(channel); } - foreach (var channel in raw_channels.Where(c => c.ParentId.HasValue && (c.Type == ChannelType.Voice || c.Type == ChannelType.Stage)).OrderBy(c => c.Position)) + foreach (var channel in rawChannels.Where(c => c.ParentId.HasValue && (c.Type == ChannelType.Voice || c.Type == ChannelType.Stage)).OrderBy(c => c.Position)) { - ordered_channels[channel.ParentId.Value].Add(channel); + orderedChannels[channel.ParentId.Value].Add(channel); } - foreach (var channel in raw_channels.Where(c => !c.ParentId.HasValue && c.Type != ChannelType.Category && (c.Type == ChannelType.Text || c.Type == ChannelType.News)).OrderBy(c => c.Position)) + foreach (var channel in rawChannels.Where(c => !c.ParentId.HasValue && c.Type != ChannelType.Category && (c.Type == ChannelType.Text || c.Type == ChannelType.News)).OrderBy(c => c.Position)) { - ordered_channels[0].Add(channel); + orderedChannels[0].Add(channel); } - foreach (var channel in raw_channels.Where(c => !c.ParentId.HasValue && c.Type != ChannelType.Category && (c.Type == ChannelType.Voice || c.Type == ChannelType.Stage)).OrderBy(c => c.Position)) + 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)) { - ordered_channels[0].Add(channel); + orderedChannels[0].Add(channel); } - return ordered_channels; + return orderedChannels; } /// /// Whether it is synced. /// [JsonIgnore] internal bool IsSynced { get; set; } /// /// Initializes a new instance of the class. /// internal DiscordGuild() { - this._current_member_lazy = new Lazy(() => (this._members != null && this._members.TryGetValue(this.Discord.CurrentUser.Id, out var member)) ? member : null); - this._invites = new ConcurrentDictionary(); - this.Threads = new ReadOnlyConcurrentDictionary(this._threads); - this.StageInstances = new ReadOnlyConcurrentDictionary(this._stageInstances); - this.ScheduledEvents = new ReadOnlyConcurrentDictionary(this._scheduledEvents); + 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) + /// 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 the or is not found. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. - public Task AddMemberAsync(DiscordUser user, string access_token, string nickname = null, IEnumerable roles = null, + 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, access_token, nickname, roles, muted, deaf); + => this.Discord.ApiClient.AddGuildMemberAsync(this.Id, user.Id, accessToken, nickname, roles, muted, deaf); /// /// Deletes this guild. Requires the caller to be the owner of the guild. /// /// /// Thrown when the client is not the owner of the guild. /// Thrown when Discord is unable to process the request. public Task DeleteAsync() => this.Discord.ApiClient.DeleteGuildAsync(this.Id); /// /// Modifies this guild. /// /// Action to perform on this guild.. /// The modified guild object. /// Thrown when the client does not have the permission. /// Thrown when the guild does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public async Task ModifyAsync(Action action) { var mdl = new GuildEditModel(); action(mdl); var afkChannelId = Optional.FromNoValue(); if (mdl.AfkChannel.HasValue && mdl.AfkChannel.Value.Type != ChannelType.Voice && mdl.AfkChannel.Value != null) throw new ArgumentException("AFK channel needs to be a voice channel."); else if (mdl.AfkChannel.HasValue && mdl.AfkChannel.Value != null) afkChannelId = mdl.AfkChannel.Value.Id; else if (mdl.AfkChannel.HasValue) afkChannelId = null; var rulesChannelId = Optional.FromNoValue(); if (mdl.RulesChannel.HasValue && mdl.RulesChannel.Value != null && mdl.RulesChannel.Value.Type != ChannelType.Text && mdl.RulesChannel.Value.Type != ChannelType.News) throw new ArgumentException("Rules channel needs to be a text channel."); else if (mdl.RulesChannel.HasValue && mdl.RulesChannel.Value != null) rulesChannelId = mdl.RulesChannel.Value.Id; else if (mdl.RulesChannel.HasValue) rulesChannelId = null; var publicUpdatesChannelId = Optional.FromNoValue(); if (mdl.PublicUpdatesChannel.HasValue && mdl.PublicUpdatesChannel.Value != null && mdl.PublicUpdatesChannel.Value.Type != ChannelType.Text && mdl.PublicUpdatesChannel.Value.Type != ChannelType.News) throw new ArgumentException("Public updates channel needs to be a text channel."); else if (mdl.PublicUpdatesChannel.HasValue && mdl.PublicUpdatesChannel.Value != null) publicUpdatesChannelId = mdl.PublicUpdatesChannel.Value.Id; else if (mdl.PublicUpdatesChannel.HasValue) publicUpdatesChannelId = null; var systemChannelId = Optional.FromNoValue(); if (mdl.SystemChannel.HasValue && mdl.SystemChannel.Value != null && mdl.SystemChannel.Value.Type != ChannelType.Text && mdl.SystemChannel.Value.Type != ChannelType.News) throw new ArgumentException("Public updates channel needs to be a text channel."); else if (mdl.SystemChannel.HasValue && mdl.SystemChannel.Value != null) systemChannelId = mdl.SystemChannel.Value.Id; else if (mdl.SystemChannel.HasValue) systemChannelId = null; var iconb64 = Optional.FromNoValue(); if (mdl.Icon.HasValue && mdl.Icon.Value != null) using (var imgtool = new ImageTool(mdl.Icon.Value)) iconb64 = imgtool.GetBase64(); else if (mdl.Icon.HasValue) iconb64 = null; var splashb64 = Optional.FromNoValue(); if (mdl.Splash.HasValue && mdl.Splash.Value != null) using (var imgtool = new ImageTool(mdl.Splash.Value)) splashb64 = imgtool.GetBase64(); else if (mdl.Splash.HasValue) splashb64 = null; var bannerb64 = Optional.FromNoValue(); if (mdl.Banner.HasValue && mdl.Banner.Value != null) using (var imgtool = new ImageTool(mdl.Banner.Value)) bannerb64 = imgtool.GetBase64(); else if (mdl.Banner.HasValue) bannerb64 = null; var discoverySplash64 = Optional.FromNoValue(); if (mdl.DiscoverySplash.HasValue && mdl.DiscoverySplash.Value != null) using (var imgtool = new ImageTool(mdl.DiscoverySplash.Value)) discoverySplash64 = imgtool.GetBase64(); else if (mdl.DiscoverySplash.HasValue) discoverySplash64 = null; var description = Optional.FromNoValue(); if (mdl.Description.HasValue && mdl.Description.Value != null) description = mdl.Description; else if (mdl.Description.HasValue) description = null; return await this.Discord.ApiClient.ModifyGuildAsync(this.Id, mdl.Name, mdl.VerificationLevel, mdl.DefaultMessageNotifications, mdl.MfaLevel, mdl.ExplicitContentFilter, afkChannelId, mdl.AfkTimeout, iconb64, mdl.Owner.IfPresent(e => e.Id), splashb64, systemChannelId, mdl.SystemChannelFlags, publicUpdatesChannelId, rulesChannelId, description, bannerb64, discoverySplash64, mdl.PreferredLocale, mdl.PremiumProgressBarEnabled, mdl.AuditLogReason).ConfigureAwait(false); } /// /// Modifies the community settings async. /// This sets if not highest and . /// /// If true, enable . /// The rules channel. /// The public updates channel. /// The preferred locale. Defaults to en-US. /// The description. /// The default message notifications. Defaults to /// The auditlog reason. /// Thrown when the client does not have the permission. /// Thrown when the guild does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public async Task ModifyCommunitySettingsAsync(bool enabled, DiscordChannel rulesChannel = null, DiscordChannel publicUpdatesChannel = null, string preferredLocale = "en-US", string description = null, DefaultMessageNotifications defaultMessageNotifications = DefaultMessageNotifications.MentionsOnly, string reason = null) { var verificationLevel = this.VerificationLevel; if (this.VerificationLevel != VerificationLevel.Highest) { verificationLevel = VerificationLevel.High; } var explicitContentFilter = ExplicitContentFilter.AllMembers; var rulesChannelId = Optional.FromNoValue(); if (rulesChannel != null && rulesChannel.Type != ChannelType.Text && rulesChannel.Type != ChannelType.News) throw new ArgumentException("Rules channel needs to be a text channel."); else if (rulesChannel != null) rulesChannelId = rulesChannel.Id; else if (rulesChannel == null) rulesChannelId = null; var publicUpdatesChannelId = Optional.FromNoValue(); if (publicUpdatesChannel != null && publicUpdatesChannel.Type != ChannelType.Text && publicUpdatesChannel.Type != ChannelType.News) throw new ArgumentException("Public updates channel needs to be a text channel."); else if (publicUpdatesChannel != null) publicUpdatesChannelId = publicUpdatesChannel.Id; else if (publicUpdatesChannel == null) publicUpdatesChannelId = null; List features = new(); var rfeatures = this.RawFeatures.ToList(); if (this.RawFeatures.Contains("COMMUNITY") && enabled) { features = rfeatures; } else if (!this.RawFeatures.Contains("COMMUNITY") && enabled) { rfeatures.Add("COMMUNITY"); features = rfeatures; } else if (this.RawFeatures.Contains("COMMUNITY") && !enabled) { rfeatures.Remove("COMMUNITY"); features = rfeatures; } else if (!this.RawFeatures.Contains("COMMUNITY") && !enabled) { features = rfeatures; } return await this.Discord.ApiClient.ModifyGuildCommunitySettingsAsync(this.Id, features, rulesChannelId, publicUpdatesChannelId, preferredLocale, description, defaultMessageNotifications, explicitContentFilter, verificationLevel, reason).ConfigureAwait(false); } /// /// Timeout a specified member in this guild. /// - /// Member to timeout. + /// 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 member_id, 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, member_id, until, reason); + 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. + /// 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 member_id, TimeSpan until, string reason = null) - => this.TimeoutAsync(member_id, DateTimeOffset.UtcNow + until, reason); + 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. + /// 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 member_id, DateTime until, string reason = null) - => this.TimeoutAsync(member_id, until.ToUniversalTime() - DateTime.UtcNow, reason); + 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. + /// 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 member_id, string reason = null) => this.Discord.ApiClient.ModifyTimeoutAsync(this.Id, member_id, null, reason); + 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. + /// 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 delete_message_days = 0, string reason = null) - => this.Discord.ApiClient.CreateGuildBanAsync(this.Id, member.Id, delete_message_days, reason); + 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. + /// 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 user_id, int delete_message_days = 0, string reason = null) - => this.Discord.ApiClient.CreateGuildBanAsync(this.Id, user_id, delete_message_days, reason); + 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. + /// 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 user_id, string reason = null) - => this.Discord.ApiClient.RemoveGuildBanAsync(this.Id, user_id, reason); + 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. /// /// Collection of bans in this guild. /// Thrown when the client does not have the permission. /// Thrown when Discord is unable to process the request. public Task> GetBansAsync() => this.Discord.ApiClient.GetGuildBansAsync(this.Id); /// /// Gets a ban for a specific user. /// /// The Id of the user to get the ban for. /// Thrown when the specified user is not banned. /// The requested ban object. public Task GetBanAsync(ulong userId) => this.Discord.ApiClient.GetGuildBanAsync(this.Id, userId); /// /// Gets a ban for a specific user. /// /// The user to get the ban for. /// Thrown when the specified user is not banned. /// The requested ban object. public Task GetBanAsync(DiscordUser user) => this.GetBanAsync(user.Id); #region Sheduled Events /// /// Creates a scheduled event. /// /// The name. /// The scheduled start time. /// The scheduled end time. /// The channel. /// The metadata. /// The description. /// The type. /// The 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, string reason = null) => 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, 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 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, string reason = null) => await this.Discord.ApiClient.CreateGuildScheduledEventAsync(this.Id, null, new DiscordScheduledEventEntityMetadata(location), name, scheduledStartTime, scheduledEndTime, description, ScheduledEventEntityType.External, 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._scheduledEvents.TryGetValue(scheduledEventId, out var ev) ? ev : await this.Discord.ApiClient.GetGuildScheduledEventAsync(this.Id, scheduledEventId, withUserCount); + => this.ScheduledEventsInternal.TryGetValue(scheduledEventId, out var ev) ? ev : await this.Discord.ApiClient.GetGuildScheduledEventAsync(this.Id, scheduledEventId, withUserCount); /// /// Gets a specific scheduled events. /// /// The event to get. /// Whether to include user count. /// A sheduled event. /// Thrown when the guild does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public async Task GetScheduledEventAsync(DiscordScheduledEvent scheduledEvent, bool? withUserCount = null) => await this.GetScheduledEventAsync(scheduledEvent.Id, withUserCount); /// /// Gets the guilds scheduled events. /// /// Whether to include user count. /// A list of the guilds scheduled events. /// Thrown when the guild does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public async Task> GetScheduledEventsAsync(bool? withUserCount = null) => await this.Discord.ApiClient.ListGuildScheduledEventsAsync(this.Id, withUserCount); #endregion /// /// Creates a new text channel in this guild. /// /// Name of the new channel. /// Category to put this channel in. /// Topic of the channel. /// Permission overwrites for this channel. /// Whether the channel is to be flagged as not safe for work. /// Reason for audit logs. /// Slow mode timeout for users. /// The newly-created channel. /// Thrown when the client does not have the permission. /// Thrown when the guild does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task CreateTextChannelAsync(string name, DiscordChannel parent = null, Optional topic = default, IEnumerable overwrites = null, bool? nsfw = null, Optional perUserRateLimit = default, string reason = null) => this.CreateChannelAsync(name, ChannelType.Text, parent, topic, null, null, overwrites, nsfw, perUserRateLimit, null, reason); /// /// Creates a new channel category in this guild. /// /// Name of the new category. /// Permission overwrites for this category. /// Reason for audit logs. /// The newly-created channel category. /// Thrown when the client does not have the permission. /// Thrown when the guild does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task CreateChannelCategoryAsync(string name, IEnumerable overwrites = null, string reason = null) => this.CreateChannelAsync(name, ChannelType.Category, null, Optional.FromNoValue(), null, null, overwrites, null, Optional.FromNoValue(), null, reason); /// /// Creates a new stage channel in this guild. /// /// Name of the new stage channel. /// Permission overwrites for this stage channel. /// Reason for audit logs. /// The newly-created stage channel. /// Thrown when the client does not have the . /// Thrown when the guild does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. /// Thrown when the guilds has not enabled community. public Task CreateStageChannelAsync(string name, IEnumerable overwrites = null, string reason = null) => this.Features.HasCommunityEnabled ? this.CreateChannelAsync(name, ChannelType.Stage, null, Optional.FromNoValue(), null, null, overwrites, null, Optional.FromNoValue(), null, reason) : throw new NotSupportedException("Guild has not enabled community. Can not create a stage channel."); /// /// Creates a new news channel in this guild. /// /// Name of the new stage channel. /// Permission overwrites for this news channel. /// Reason for audit logs. /// The newly-created news channel. /// Thrown when the client does not have the . /// Thrown when the guild does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. /// Thrown when the guilds has not enabled community. public Task CreateNewsChannelAsync(string name, IEnumerable overwrites = null, string reason = null) => this.Features.HasCommunityEnabled ? this.CreateChannelAsync(name, ChannelType.News, null, Optional.FromNoValue(), null, null, overwrites, null, Optional.FromNoValue(), null, reason) : throw new NotSupportedException("Guild has not enabled community. Can not create a news channel."); /// /// Creates a new voice channel in this guild. /// /// Name of the new channel. /// Category to put this channel in. /// Bitrate of the channel. - /// Maximum number of users in the channel. + /// Maximum number of users in the channel. /// Permission overwrites for this channel. /// Video quality mode of the channel. /// Reason for audit logs. /// The newly-created channel. /// Thrown when the client does not have the permission. /// Thrown when the guild does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. - public Task CreateVoiceChannelAsync(string name, DiscordChannel parent = null, int? bitrate = null, int? user_limit = null, IEnumerable overwrites = null, VideoQualityMode? qualityMode = null, string reason = null) - => this.CreateChannelAsync(name, ChannelType.Voice, parent, Optional.FromNoValue(), bitrate, user_limit, overwrites, null, Optional.FromNoValue(), qualityMode, reason); + public Task CreateVoiceChannelAsync(string name, DiscordChannel parent = null, int? bitrate = null, int? userLimit = null, IEnumerable overwrites = null, VideoQualityMode? qualityMode = null, string reason = null) + => this.CreateChannelAsync(name, ChannelType.Voice, parent, Optional.FromNoValue(), bitrate, userLimit, overwrites, null, Optional.FromNoValue(), qualityMode, reason); /// /// Creates a new channel in this guild. /// /// Name of the new channel. /// Type of the new channel. /// Category to put this channel in. /// Topic of the channel. /// Bitrate of the channel. Applies to voice only. /// Maximum number of users in the channel. Applies to voice only. /// Permission overwrites for this channel. /// Whether the channel is to be flagged as not safe for work. Applies to text only. /// Slow mode timeout for users. /// Video quality mode of the channel. Applies to voice only. /// Reason for audit logs. /// The newly-created channel. /// Thrown when the client does not have the permission. /// Thrown when the guild does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task CreateChannelAsync(string name, ChannelType type, DiscordChannel parent = null, Optional topic = default, int? bitrate = null, int? userLimit = null, IEnumerable overwrites = null, bool? nsfw = null, Optional perUserRateLimit = default, VideoQualityMode? qualityMode = null, string reason = null) { // technically you can create news/store channels but not always return type != ChannelType.Text && type != ChannelType.Voice && type != ChannelType.Category && type != ChannelType.News && type != ChannelType.Store && type != ChannelType.Stage ? throw new ArgumentException("Channel type must be text, voice, stage, or category.", nameof(type)) : type == ChannelType.Category && parent != null ? throw new ArgumentException("Cannot specify parent of a channel category.", nameof(parent)) : this.Discord.ApiClient.CreateGuildChannelAsync(this.Id, name, type, parent?.Id, topic, bitrate, userLimit, overwrites, nsfw, perUserRateLimit, qualityMode, reason); } /// /// Gets active threads. Can contain more threads. /// If the result's value 'HasMore' is true, you need to recall this function to get older threads. /// /// Thrown when the thread does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task GetActiveThreadsAsync() => this.Discord.ApiClient.GetActiveThreadsAsync(this.Id); // this is to commemorate the Great DAPI Channel Massacre of 2017-11-19. /// /// Deletes all channels in this guild. /// Note that this is irreversible. Use carefully! /// /// public Task DeleteAllChannelsAsync() { var tasks = this.Channels.Values.Select(xc => xc.DeleteAsync()); return Task.WhenAll(tasks); } /// /// Estimates the number of users to be pruned. /// /// Minimum number of inactivity days required for users to be pruned. Defaults to 7. /// The roles to be included in the prune. /// Number of users that will be pruned. /// Thrown when the client does not have the permission. /// Thrown when the guild does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task GetPruneCountAsync(int days = 7, IEnumerable includedRoles = null) { if (includedRoles != null) { includedRoles = includedRoles.Where(r => r != null); var roleCount = includedRoles.Count(); var roleArr = includedRoles.ToArray(); var rawRoleIds = new List(); for (var i = 0; i < roleCount; i++) { - if (this._roles.ContainsKey(roleArr[i].Id)) + if (this.RolesInternal.ContainsKey(roleArr[i].Id)) rawRoleIds.Add(roleArr[i].Id); } return this.Discord.ApiClient.GetGuildPruneCountAsync(this.Id, days, rawRoleIds); } return this.Discord.ApiClient.GetGuildPruneCountAsync(this.Id, days, null); } /// /// Prunes inactive users from this guild. /// /// Minimum number of inactivity days required for users to be pruned. Defaults to 7. /// Whether to return the prune count after this method completes. This is discouraged for larger guilds. /// The roles to be included in the prune. /// Reason for audit logs. /// Number of users pruned. /// Thrown when the client does not have the permission. /// Thrown when the guild does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task PruneAsync(int days = 7, bool computePruneCount = true, IEnumerable includedRoles = null, string reason = null) { if (includedRoles != null) { includedRoles = includedRoles.Where(r => r != null); var roleCount = includedRoles.Count(); var roleArr = includedRoles.ToArray(); var rawRoleIds = new List(); for (var i = 0; i < roleCount; i++) { - if (this._roles.ContainsKey(roleArr[i].Id)) + if (this.RolesInternal.ContainsKey(roleArr[i].Id)) rawRoleIds.Add(roleArr[i].Id); } return this.Discord.ApiClient.BeginGuildPruneAsync(this.Id, days, computePruneCount, rawRoleIds, reason); } return this.Discord.ApiClient.BeginGuildPruneAsync(this.Id, days, computePruneCount, null, reason); } /// /// Gets integrations attached to this guild. /// /// Collection of integrations attached to this guild. /// Thrown when the client does not have the permission. /// Thrown when the guild does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task> GetIntegrationsAsync() => this.Discord.ApiClient.GetGuildIntegrationsAsync(this.Id); /// /// Attaches an integration from current user to this guild. /// /// Integration to attach. /// The integration after being attached to the guild. /// Thrown when the client does not have the permission. /// Thrown when the guild does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task AttachUserIntegrationAsync(DiscordIntegration integration) => this.Discord.ApiClient.CreateGuildIntegrationAsync(this.Id, integration.Type, integration.Id); /// /// Modifies an integration in this guild. /// /// Integration to modify. - /// Number of days after which the integration expires. - /// Length of grace period which allows for renewing the integration. - /// Whether emotes should be synced from this integration. + /// 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 expire_behaviour, int expire_grace_period, bool enable_emoticons) - => this.Discord.ApiClient.ModifyGuildIntegrationAsync(this.Id, integration.Id, expire_behaviour, expire_grace_period, enable_emoticons); + 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; + => this.Invites.TryGetValue(code, out var invite) ? invite : null; /// /// Gets all the invites created for all the channels in this guild. /// /// A collection of invites. /// Thrown when Discord is unable to process the request. public async Task> GetInvitesAsync() { var res = await this.Discord.ApiClient.GetGuildInvitesAsync(this.Id).ConfigureAwait(false); var intents = this.Discord.Configuration.Intents; if (!intents.HasIntent(DiscordIntents.GuildInvites)) { for (var i = 0; i < res.Count; i++) - this._invites[res[i].Code] = res[i]; + this.Invites[res[i].Code] = res[i]; } return res; } /// /// Gets the vanity invite for this guild. /// /// A partial vanity invite. /// Thrown when the client does not have the permission. /// Thrown when Discord is unable to process the request. public Task GetVanityInviteAsync() => this.Discord.ApiClient.GetGuildVanityUrlAsync(this.Id); /// /// Gets all the webhooks created for all the channels in this guild. /// /// A collection of webhooks this guild has. /// Thrown when the client does not have the permission. /// Thrown when Discord is unable to process the request. public Task> GetWebhooksAsync() => this.Discord.ApiClient.GetGuildWebhooksAsync(this.Id); /// /// Gets this guild's widget image. /// /// The format of the widget. /// The URL of the widget image. public string GetWidgetImage(WidgetType bannerType = WidgetType.Shield) { var param = bannerType switch { WidgetType.Banner1 => "banner1", WidgetType.Banner2 => "banner2", WidgetType.Banner3 => "banner3", WidgetType.Banner4 => "banner4", _ => "shield", }; return $"{Endpoints.BASE_URI}{Endpoints.GUILDS}/{this.Id}{Endpoints.WIDGET_PNG}?style={param}"; } /// /// Gets a member of this guild by their user ID. /// /// ID of the member to get. /// The requested member. /// Thrown when Discord is unable to process the request. public async Task GetMemberAsync(ulong userId) { - if (this._members != null && this._members.TryGetValue(userId, out var mbr)) + if (this.MembersInternal != null && this.MembersInternal.TryGetValue(userId, out var mbr)) return mbr; mbr = await this.Discord.ApiClient.GetGuildMemberAsync(this.Id, userId).ConfigureAwait(false); var intents = this.Discord.Configuration.Intents; if (intents.HasIntent(DiscordIntents.GuildMembers)) { - if (this._members != null) + if (this.MembersInternal != null) { - this._members[userId] = mbr; + this.MembersInternal[userId] = mbr; } } return mbr; } /// /// Retrieves a full list of members from Discord. This method will bypass cache. /// /// A collection of all members in this guild. /// Thrown when Discord is unable to process the request. public async Task> GetAllMembersAsync() { var recmbr = new HashSet(); var recd = 1000; var last = 0ul; while (recd > 0) { var tms = await this.Discord.ApiClient.ListGuildMembersAsync(this.Id, 1000, last == 0 ? null : (ulong?)last).ConfigureAwait(false); recd = tms.Count; foreach (var xtm in tms) { var usr = new DiscordUser(xtm.User) { Discord = this.Discord }; usr = this.Discord.UserCache.AddOrUpdate(xtm.User.Id, usr, (id, old) => { old.Username = usr.Username; old.Discord = usr.Discord; old.AvatarHash = usr.AvatarHash; return old; }); - recmbr.Add(new DiscordMember(xtm) { Discord = this.Discord, _guild_id = this.Id }); + 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._roles.TryGetValue(id, out var role) ? role : null; + => 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._channels != null && this._channels.TryGetValue(id, out var channel)) ? channel : null; + => (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._threads != null && this._threads.TryGetValue(id, out var thread)) ? thread : null; + => (this.ThreadsInternal != null && this.ThreadsInternal.TryGetValue(id, out var thread)) ? thread : null; /// /// Gets audit log entries for this guild. /// /// Maximum number of entries to fetch. - /// Filter by member responsible. - /// Filter by action type. + /// Filter by member responsible. + /// Filter by action type. /// A collection of requested audit log entries. /// Thrown when the client does not have the permission. /// Thrown when Discord is unable to process the request. - public async Task> GetAuditLogsAsync(int? limit = null, DiscordMember by_member = null, AuditLogActionType? action_type = null) + public async Task> GetAuditLogsAsync(int? limit = null, DiscordMember byMember = null, AuditLogActionType? actionType = null) { var alrs = new List(); int ac = 1, tc = 0, rmn = 100; var last = 0ul; while (ac > 0) { rmn = limit != null ? limit.Value - tc : 100; rmn = Math.Min(100, rmn); if (rmn <= 0) break; - var alr = await this.Discord.ApiClient.GetAuditLogsAsync(this.Id, rmn, null, last == 0 ? null : (ulong?)last, by_member?.Id, (int?)action_type).ConfigureAwait(false); + var alr = await this.Discord.ApiClient.GetAuditLogsAsync(this.Id, rmn, null, last == 0 ? null : (ulong?)last, byMember?.Id, (int?)actionType).ConfigureAwait(false); ac = alr.Entries.Count(); tc += ac; if (ac > 0) { last = alr.Entries.Last().Id; alrs.Add(alr); } } var amr = alrs.SelectMany(xa => xa.Users) .GroupBy(xu => xu.Id) .Select(xgu => xgu.First()); foreach (var xau in amr) { if (this.Discord.UserCache.ContainsKey(xau.Id)) continue; var xtu = new TransportUser { Id = xau.Id, Username = xau.Username, Discriminator = xau.Discriminator, AvatarHash = xau.AvatarHash }; var xu = new DiscordUser(xtu) { Discord = this.Discord }; xu = this.Discord.UserCache.AddOrUpdate(xu.Id, xu, (id, old) => { old.Username = xu.Username; old.Discriminator = xu.Discriminator; old.AvatarHash = xu.AvatarHash; return old; }); } var atgse = alrs.SelectMany(xa => xa.ScheduledEvents) .GroupBy(xse => xse.Id) .Select(xgse => xgse.First()); var ath = alrs.SelectMany(xa => xa.Threads) .GroupBy(xt => xt.Id) .Select(xgt => xgt.First()); var aig = alrs.SelectMany(xa => xa.Integrations) .GroupBy(xi => xi.Id) .Select(xgi => xgi.First()); var ahr = alrs.SelectMany(xa => xa.Webhooks) .GroupBy(xh => xh.Id) .Select(xgh => xgh.First()); - var ams = amr.Select(xau => (this._members != null && this._members.TryGetValue(xau.Id, out var member)) ? member : new DiscordMember { Discord = this.Discord, Id = xau.Id, _guild_id = this.Id }); + var ams = amr.Select(xau => (this.MembersInternal != null && this.MembersInternal.TryGetValue(xau.Id, out var member)) ? member : new DiscordMember { Discord = this.Discord, Id = xau.Id, GuildId = this.Id }); var amd = ams.ToDictionary(xm => xm.Id, xm => xm); #pragma warning disable CS0219 Dictionary dtc = null; Dictionary di = null; Dictionary dse = null; #pragma warning restore Dictionary ahd = null; if (ahr.Any()) { var whr = await this.GetWebhooksAsync().ConfigureAwait(false); var whs = whr.ToDictionary(xh => xh.Id, xh => xh); var amh = ahr.Select(xah => whs.TryGetValue(xah.Id, out var webhook) ? webhook : new DiscordWebhook { Discord = this.Discord, Name = xah.Name, Id = xah.Id, AvatarHash = xah.AvatarHash, ChannelId = xah.ChannelId, GuildId = xah.GuildId, Token = xah.Token }); ahd = amh.ToDictionary(xh => xh.Id, xh => xh); } var acs = alrs.SelectMany(xa => xa.Entries).OrderByDescending(xa => xa.Id); var entries = new List(); foreach (var xac in acs) { DiscordAuditLogEntry entry = null; ulong t1, t2; int t3, t4; long t5, t6; bool p1, p2; switch (xac.ActionType) { case AuditLogActionType.GuildUpdate: entry = new DiscordAuditLogGuildEntry { Target = this }; var entrygld = entry as DiscordAuditLogGuildEntry; foreach (var xc in xac.Changes) { switch (xc.Key.ToLowerInvariant()) { case "name": entrygld.NameChange = new PropertyChange { Before = xc.OldValueString, After = xc.NewValueString }; break; case "owner_id": entrygld.OwnerChange = new PropertyChange { - Before = (this._members != null && this._members.TryGetValue(xc.OldValueUlong, out var oldMember)) ? oldMember : await this.GetMemberAsync(xc.OldValueUlong).ConfigureAwait(false), - After = (this._members != null && this._members.TryGetValue(xc.NewValueUlong, out var newMember)) ? newMember : await this.GetMemberAsync(xc.NewValueUlong).ConfigureAwait(false) + Before = (this.MembersInternal != null && this.MembersInternal.TryGetValue(xc.OldValueUlong, out var oldMember)) ? oldMember : await this.GetMemberAsync(xc.OldValueUlong).ConfigureAwait(false), + After = (this.MembersInternal != null && this.MembersInternal.TryGetValue(xc.NewValueUlong, out var newMember)) ? newMember : await this.GetMemberAsync(xc.NewValueUlong).ConfigureAwait(false) }; break; case "icon_hash": entrygld.IconChange = new PropertyChange { Before = xc.OldValueString != null ? $"{DiscordDomain.GetDomain(CoreDomain.DiscordCdn).Url}{Endpoints.ICONS}/{this.Id}/{xc.OldValueString}.webp" : null, After = xc.OldValueString != null ? $"{DiscordDomain.GetDomain(CoreDomain.DiscordCdn).Url}{Endpoints.ICONS}/{this.Id}/{xc.NewValueString}.webp" : null }; break; case "verification_level": entrygld.VerificationLevelChange = new PropertyChange { Before = (VerificationLevel)(long)xc.OldValue, After = (VerificationLevel)(long)xc.NewValue }; break; case "afk_channel_id": ulong.TryParse(xc.NewValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t1); ulong.TryParse(xc.OldValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t2); entrygld.AfkChannelChange = new PropertyChange { Before = this.GetChannel(t1) ?? new DiscordChannel { Id = t1, Discord = this.Discord, GuildId = this.Id }, After = this.GetChannel(t2) ?? new DiscordChannel { Id = t1, Discord = this.Discord, GuildId = this.Id } }; break; case "widget_channel_id": ulong.TryParse(xc.NewValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t1); ulong.TryParse(xc.OldValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t2); entrygld.EmbedChannelChange = new PropertyChange { Before = this.GetChannel(t1) ?? new DiscordChannel { Id = t1, Discord = this.Discord, GuildId = this.Id }, After = this.GetChannel(t2) ?? new DiscordChannel { Id = t1, Discord = this.Discord, GuildId = this.Id } }; break; case "splash_hash": entrygld.SplashChange = new PropertyChange { Before = xc.OldValueString != null ? $"{DiscordDomain.GetDomain(CoreDomain.DiscordCdn).Url}{Endpoints.SPLASHES}/{this.Id}/{xc.OldValueString}.webp?size=2048" : null, After = xc.NewValueString != null ? $"{DiscordDomain.GetDomain(CoreDomain.DiscordCdn).Url}{Endpoints.SPLASHES}/{this.Id}/{xc.NewValueString}.webp?size=2048" : null }; break; case "default_message_notifications": entrygld.NotificationSettingsChange = new PropertyChange { Before = (DefaultMessageNotifications)(long)xc.OldValue, After = (DefaultMessageNotifications)(long)xc.NewValue }; break; case "system_channel_id": ulong.TryParse(xc.NewValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t1); ulong.TryParse(xc.OldValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t2); entrygld.SystemChannelChange = new PropertyChange { Before = this.GetChannel(t1) ?? new DiscordChannel { Id = t1, Discord = this.Discord, GuildId = this.Id }, After = this.GetChannel(t2) ?? new DiscordChannel { Id = t1, Discord = this.Discord, GuildId = this.Id } }; break; case "explicit_content_filter": entrygld.ExplicitContentFilterChange = new PropertyChange { Before = (ExplicitContentFilter)(long)xc.OldValue, After = (ExplicitContentFilter)(long)xc.NewValue }; break; case "mfa_level": entrygld.MfaLevelChange = new PropertyChange { Before = (MfaLevel)(long)xc.OldValue, After = (MfaLevel)(long)xc.NewValue }; break; case "region": entrygld.RegionChange = new PropertyChange { Before = xc.OldValueString, After = xc.NewValueString }; break; case "premium_progress_bar_enabled": entrygld.PremiumProgressBarChange = new PropertyChange { Before = (bool)xc.OldValue, After = (bool)xc.NewValue }; break; default: this.Discord.Logger.LogWarning(LoggerEvents.AuditLog, "Unknown key in guild update: {0} - this should be reported to library developers", xc.Key); break; } } break; case AuditLogActionType.ChannelCreate: case AuditLogActionType.ChannelDelete: case AuditLogActionType.ChannelUpdate: entry = new DiscordAuditLogChannelEntry { Target = this.GetChannel(xac.TargetId.Value) ?? new DiscordChannel { Id = xac.TargetId.Value, Discord = this.Discord, GuildId = this.Id } }; var entrychn = entry as DiscordAuditLogChannelEntry; foreach (var xc in xac.Changes) { switch (xc.Key.ToLowerInvariant()) { case "name": entrychn.NameChange = new PropertyChange { Before = xc.OldValue != null ? xc.OldValueString : null, After = xc.NewValue != null ? xc.NewValueString : null }; break; case "type": p1 = ulong.TryParse(xc.NewValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t1); p2 = ulong.TryParse(xc.OldValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t2); entrychn.TypeChange = new PropertyChange { Before = p1 ? (ChannelType?)t1 : null, After = p2 ? (ChannelType?)t2 : null }; break; case "permission_overwrites": var olds = xc.OldValues?.OfType() ?.Select(xjo => xjo.ToObject()) ?.Select(xo => { xo.Discord = this.Discord; return xo; }); var news = xc.NewValues?.OfType() ?.Select(xjo => xjo.ToObject()) ?.Select(xo => { xo.Discord = this.Discord; return xo; }); entrychn.OverwriteChange = new PropertyChange> { Before = olds != null ? new ReadOnlyCollection(new List(olds)) : null, After = news != null ? new ReadOnlyCollection(new List(news)) : null }; break; case "topic": entrychn.TopicChange = new PropertyChange { Before = xc.OldValueString, After = xc.NewValueString }; break; case "nsfw": entrychn.NsfwChange = new PropertyChange { Before = (bool?)xc.OldValue, After = (bool?)xc.NewValue }; break; case "bitrate": entrychn.BitrateChange = new PropertyChange { Before = (int?)(long?)xc.OldValue, After = (int?)(long?)xc.NewValue }; break; case "rate_limit_per_user": entrychn.PerUserRateLimitChange = new PropertyChange { Before = (int?)(long?)xc.OldValue, After = (int?)(long?)xc.NewValue }; break; default: this.Discord.Logger.LogWarning(LoggerEvents.AuditLog, "Unknown key in channel update: {0} - this should be reported to library developers", xc.Key); break; } } break; case AuditLogActionType.OverwriteCreate: case AuditLogActionType.OverwriteDelete: case AuditLogActionType.OverwriteUpdate: entry = new DiscordAuditLogOverwriteEntry { Target = this.GetChannel(xac.TargetId.Value)?.PermissionOverwrites.FirstOrDefault(xo => xo.Id == xac.Options.Id), Channel = this.GetChannel(xac.TargetId.Value) }; var entryovr = entry as DiscordAuditLogOverwriteEntry; foreach (var xc in xac.Changes) { switch (xc.Key.ToLowerInvariant()) { case "deny": p1 = ulong.TryParse(xc.OldValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t1); p2 = ulong.TryParse(xc.OldValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t2); entryovr.DenyChange = new PropertyChange { Before = p1 ? (Permissions?)t1 : null, After = p2 ? (Permissions?)t2 : null }; break; case "allow": p1 = ulong.TryParse(xc.OldValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t1); p2 = ulong.TryParse(xc.OldValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t2); entryovr.AllowChange = new PropertyChange { Before = p1 ? (Permissions?)t1 : null, After = p2 ? (Permissions?)t2 : null }; break; case "type": entryovr.TypeChange = new PropertyChange { Before = xc.OldValueString, After = xc.NewValueString }; break; case "id": p1 = ulong.TryParse(xc.OldValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t1); p2 = ulong.TryParse(xc.NewValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t2); entryovr.TargetIdChange = new PropertyChange { Before = p1 ? (ulong?)t1 : null, After = p2 ? (ulong?)t2 : null }; break; default: this.Discord.Logger.LogWarning(LoggerEvents.AuditLog, "Unknown key in overwrite update: {0} - this should be reported to library developers", xc.Key); break; } } break; case AuditLogActionType.Kick: entry = new DiscordAuditLogKickEntry { - Target = amd.TryGetValue(xac.TargetId.Value, out var kickMember) ? kickMember : new DiscordMember { Id = xac.TargetId.Value, Discord = this.Discord, _guild_id = this.Id } + Target = amd.TryGetValue(xac.TargetId.Value, out var kickMember) ? kickMember : new DiscordMember { Id = xac.TargetId.Value, Discord = this.Discord, GuildId = this.Id } }; break; case AuditLogActionType.Prune: entry = new DiscordAuditLogPruneEntry { Days = xac.Options.DeleteMemberDays, Toll = xac.Options.MembersRemoved }; break; case AuditLogActionType.Ban: case AuditLogActionType.Unban: entry = new DiscordAuditLogBanEntry { - Target = amd.TryGetValue(xac.TargetId.Value, out var unbanMember) ? unbanMember : new DiscordMember { Id = xac.TargetId.Value, Discord = this.Discord, _guild_id = this.Id } + Target = amd.TryGetValue(xac.TargetId.Value, out var unbanMember) ? unbanMember : new DiscordMember { Id = xac.TargetId.Value, Discord = this.Discord, GuildId = this.Id } }; break; case AuditLogActionType.MemberUpdate: case AuditLogActionType.MemberRoleUpdate: entry = new DiscordAuditLogMemberUpdateEntry { - Target = amd.TryGetValue(xac.TargetId.Value, out var roleUpdMember) ? roleUpdMember : new DiscordMember { Id = xac.TargetId.Value, Discord = this.Discord, _guild_id = this.Id } + Target = amd.TryGetValue(xac.TargetId.Value, out var roleUpdMember) ? roleUpdMember : new DiscordMember { Id = xac.TargetId.Value, Discord = this.Discord, GuildId = this.Id } }; var entrymbu = entry as DiscordAuditLogMemberUpdateEntry; foreach (var xc in xac.Changes) { switch (xc.Key.ToLowerInvariant()) { case "nick": entrymbu.NicknameChange = new PropertyChange { Before = xc.OldValueString, After = xc.NewValueString }; break; case "deaf": entrymbu.DeafenChange = new PropertyChange { Before = (bool?)xc.OldValue, After = (bool?)xc.NewValue }; break; case "mute": entrymbu.MuteChange = new PropertyChange { Before = (bool?)xc.OldValue, After = (bool?)xc.NewValue }; break; case "communication_disabled_until": entrymbu.CommunicationDisabledUntilChange = new PropertyChange { Before = (DateTime?)xc.OldValue, After = (DateTime?)xc.NewValue }; break; case "$add": entrymbu.AddedRoles = new ReadOnlyCollection(xc.NewValues.Select(xo => (ulong)xo["id"]).Select(this.GetRole).ToList()); break; case "$remove": entrymbu.RemovedRoles = new ReadOnlyCollection(xc.NewValues.Select(xo => (ulong)xo["id"]).Select(this.GetRole).ToList()); break; default: this.Discord.Logger.LogWarning(LoggerEvents.AuditLog, "Unknown key in member update: {0} - this should be reported to library developers", xc.Key); break; } } break; case AuditLogActionType.RoleCreate: case AuditLogActionType.RoleDelete: case AuditLogActionType.RoleUpdate: entry = new DiscordAuditLogRoleUpdateEntry { Target = this.GetRole(xac.TargetId.Value) ?? new DiscordRole { Id = xac.TargetId.Value, Discord = this.Discord } }; var entryrol = entry as DiscordAuditLogRoleUpdateEntry; foreach (var xc in xac.Changes) { switch (xc.Key.ToLowerInvariant()) { case "name": entryrol.NameChange = new PropertyChange { Before = xc.OldValueString, After = xc.NewValueString }; break; case "color": p1 = int.TryParse(xc.OldValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t3); p2 = int.TryParse(xc.NewValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t4); entryrol.ColorChange = new PropertyChange { Before = p1 ? (int?)t3 : null, After = p2 ? (int?)t4 : null }; break; case "permissions": entryrol.PermissionChange = new PropertyChange { Before = xc.OldValue != null ? (Permissions?)long.Parse((string)xc.OldValue) : null, After = xc.NewValue != null ? (Permissions?)long.Parse((string)xc.NewValue) : null }; break; case "position": entryrol.PositionChange = new PropertyChange { Before = xc.OldValue != null ? (int?)(long)xc.OldValue : null, After = xc.NewValue != null ? (int?)(long)xc.NewValue : null, }; break; case "mentionable": entryrol.MentionableChange = new PropertyChange { Before = xc.OldValue != null ? (bool?)xc.OldValue : null, After = xc.NewValue != null ? (bool?)xc.NewValue : null }; break; case "hoist": entryrol.HoistChange = new PropertyChange { Before = (bool?)xc.OldValue, After = (bool?)xc.NewValue }; break; default: this.Discord.Logger.LogWarning(LoggerEvents.AuditLog, "Unknown key in role update: {0} - this should be reported to library developers", xc.Key); break; } } break; case AuditLogActionType.InviteCreate: case AuditLogActionType.InviteDelete: case AuditLogActionType.InviteUpdate: entry = new DiscordAuditLogInviteEntry(); var inv = new DiscordInvite { Discord = this.Discord, Guild = new DiscordInviteGuild { Discord = this.Discord, Id = this.Id, Name = this.Name, SplashHash = this.SplashHash } }; var entryinv = entry as DiscordAuditLogInviteEntry; foreach (var xc in xac.Changes) { switch (xc.Key.ToLowerInvariant()) { case "max_age": p1 = int.TryParse(xc.OldValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t3); p2 = int.TryParse(xc.OldValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t4); entryinv.MaxAgeChange = new PropertyChange { Before = p1 ? (int?)t3 : null, After = p2 ? (int?)t4 : null }; break; case "code": inv.Code = xc.OldValueString ?? xc.NewValueString; entryinv.CodeChange = new PropertyChange { Before = xc.OldValueString, After = xc.NewValueString }; break; case "temporary": entryinv.TemporaryChange = new PropertyChange { Before = xc.OldValue != null ? (bool?)xc.OldValue : null, After = xc.NewValue != null ? (bool?)xc.NewValue : null }; break; case "inviter_id": p1 = ulong.TryParse(xc.OldValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t1); p2 = ulong.TryParse(xc.NewValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t2); entryinv.InviterChange = new PropertyChange { - Before = amd.TryGetValue(t1, out var propBeforeMember) ? propBeforeMember : new DiscordMember { Id = t1, Discord = this.Discord, _guild_id = this.Id }, - After = amd.TryGetValue(t2, out var propAfterMember) ? propAfterMember : new DiscordMember { Id = t1, Discord = this.Discord, _guild_id = this.Id }, + Before = amd.TryGetValue(t1, out var propBeforeMember) ? propBeforeMember : new DiscordMember { Id = t1, Discord = this.Discord, GuildId = this.Id }, + After = amd.TryGetValue(t2, out var propAfterMember) ? propAfterMember : new DiscordMember { Id = t1, Discord = this.Discord, GuildId = this.Id }, }; break; case "channel_id": p1 = ulong.TryParse(xc.OldValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t1); p2 = ulong.TryParse(xc.NewValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t2); entryinv.ChannelChange = new PropertyChange { Before = p1 ? this.GetChannel(t1) ?? new DiscordChannel { Id = t1, Discord = this.Discord, GuildId = this.Id } : null, After = p2 ? this.GetChannel(t2) ?? new DiscordChannel { Id = t1, Discord = this.Discord, GuildId = this.Id } : null }; var ch = entryinv.ChannelChange.Before ?? entryinv.ChannelChange.After; var cht = ch?.Type; inv.Channel = new DiscordInviteChannel { Discord = this.Discord, Id = p1 ? t1 : t2, Name = ch?.Name, Type = cht != null ? cht.Value : ChannelType.Unknown }; break; case "uses": p1 = int.TryParse(xc.OldValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t3); p2 = int.TryParse(xc.OldValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t4); entryinv.UsesChange = new PropertyChange { Before = p1 ? (int?)t3 : null, After = p2 ? (int?)t4 : null }; break; case "max_uses": p1 = int.TryParse(xc.OldValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t3); p2 = int.TryParse(xc.OldValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t4); entryinv.MaxUsesChange = new PropertyChange { Before = p1 ? (int?)t3 : null, After = p2 ? (int?)t4 : null }; break; // TODO: Add changes for target application default: this.Discord.Logger.LogWarning(LoggerEvents.AuditLog, "Unknown key in invite update: {0} - this should be reported to library developers", xc.Key); break; } } entryinv.Target = inv; break; case AuditLogActionType.WebhookCreate: case AuditLogActionType.WebhookDelete: case AuditLogActionType.WebhookUpdate: entry = new DiscordAuditLogWebhookEntry { Target = ahd.TryGetValue(xac.TargetId.Value, out var webhook) ? webhook : new DiscordWebhook { Id = xac.TargetId.Value, Discord = this.Discord } }; var entrywhk = entry as DiscordAuditLogWebhookEntry; foreach (var xc in xac.Changes) { switch (xc.Key.ToLowerInvariant()) { case "name": entrywhk.NameChange = new PropertyChange { Before = xc.OldValueString, After = xc.NewValueString }; break; case "channel_id": p1 = ulong.TryParse(xc.OldValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t1); p2 = ulong.TryParse(xc.NewValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t2); entrywhk.ChannelChange = new PropertyChange { Before = p1 ? this.GetChannel(t1) ?? new DiscordChannel { Id = t1, Discord = this.Discord, GuildId = this.Id } : null, After = p2 ? this.GetChannel(t2) ?? new DiscordChannel { Id = t1, Discord = this.Discord, GuildId = this.Id } : null }; break; case "type": // ??? p1 = int.TryParse(xc.OldValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t3); p2 = int.TryParse(xc.NewValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t4); entrywhk.TypeChange = new PropertyChange { Before = p1 ? (int?)t3 : null, After = p2 ? (int?)t4 : null }; break; case "avatar_hash": entrywhk.AvatarHashChange = new PropertyChange { Before = xc.OldValueString, After = xc.NewValueString }; break; default: this.Discord.Logger.LogWarning(LoggerEvents.AuditLog, "Unknown key in webhook update: {0} - this should be reported to library developers", xc.Key); break; } } break; case AuditLogActionType.EmojiCreate: case AuditLogActionType.EmojiDelete: case AuditLogActionType.EmojiUpdate: entry = new DiscordAuditLogEmojiEntry { - Target = this._emojis.TryGetValue(xac.TargetId.Value, out var target) ? target : new DiscordEmoji { Id = xac.TargetId.Value, Discord = this.Discord } + Target = this.EmojisInternal.TryGetValue(xac.TargetId.Value, out var target) ? target : new DiscordEmoji { Id = xac.TargetId.Value, Discord = this.Discord } }; var entryemo = entry as DiscordAuditLogEmojiEntry; foreach (var xc in xac.Changes) { switch (xc.Key.ToLowerInvariant()) { case "name": entryemo.NameChange = new PropertyChange { Before = xc.OldValueString, After = xc.NewValueString }; break; default: this.Discord.Logger.LogWarning(LoggerEvents.AuditLog, "Unknown key in emote update: {0} - this should be reported to library developers", xc.Key); break; } } break; case AuditLogActionType.StageInstanceCreate: case AuditLogActionType.StageInstanceDelete: case AuditLogActionType.StageInstanceUpdate: entry = new DiscordAuditLogStageEntry { - Target = this._stageInstances.TryGetValue(xac.TargetId.Value, out var stage) ? stage : new DiscordStageInstance { Id = xac.TargetId.Value, Discord = this.Discord } + Target = this.StageInstancesInternal.TryGetValue(xac.TargetId.Value, out var stage) ? stage : new DiscordStageInstance { Id = xac.TargetId.Value, Discord = this.Discord } }; var entrysta = entry as DiscordAuditLogStageEntry; foreach (var xc in xac.Changes) { switch (xc.Key.ToLowerInvariant()) { case "topic": entrysta.TopicChange = new PropertyChange { Before = xc.OldValueString, After = xc.NewValueString }; break; case "privacy_level": entrysta.PrivacyLevelChange = new PropertyChange { Before = long.TryParse(xc.OldValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t5) ? (StagePrivacyLevel?)t5 : null, After = long.TryParse(xc.NewValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t6) ? (StagePrivacyLevel?)t6 : null, }; break; default: this.Discord.Logger.LogWarning(LoggerEvents.AuditLog, "Unknown key in stage instance update: {0} - this should be reported to library developers", xc.Key); break; } } break; case AuditLogActionType.StickerCreate: case AuditLogActionType.StickerDelete: case AuditLogActionType.StickerUpdate: entry = new DiscordAuditLogStickerEntry { - Target = this._stickers.TryGetValue(xac.TargetId.Value, out var sticker) ? sticker : new DiscordSticker { Id = xac.TargetId.Value, Discord = this.Discord } + Target = this.StickersInternal.TryGetValue(xac.TargetId.Value, out var sticker) ? sticker : new DiscordSticker { Id = xac.TargetId.Value, Discord = this.Discord } }; var entrysti = entry as DiscordAuditLogStickerEntry; foreach (var xc in xac.Changes) { switch (xc.Key.ToLowerInvariant()) { case "name": entrysti.NameChange = new PropertyChange { Before = xc.OldValueString, After = xc.NewValueString }; break; case "description": entrysti.DescriptionChange = new PropertyChange { Before = xc.OldValueString, After = xc.NewValueString }; break; case "tags": entrysti.TagsChange = new PropertyChange { Before = xc.OldValueString, After = xc.NewValueString }; break; case "guild_id": entrysti.GuildIdChange = new PropertyChange { Before = ulong.TryParse(xc.OldValueString, out var ogid) ? ogid : null, After = ulong.TryParse(xc.NewValueString, out var ngid) ? ngid : null }; break; case "available": entrysti.AvailabilityChange = new PropertyChange { Before = (bool?)xc.OldValue, After = (bool?)xc.NewValue, }; break; case "asset": entrysti.AssetChange = new PropertyChange { Before = xc.OldValueString, After = xc.NewValueString }; break; case "id": entrysti.IdChange = new PropertyChange { Before = ulong.TryParse(xc.OldValueString, out var oid) ? oid : null, After = ulong.TryParse(xc.NewValueString, out var nid) ? nid : null }; break; case "type": p1 = long.TryParse(xc.OldValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t5); p2 = long.TryParse(xc.NewValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t6); entrysti.TypeChange = new PropertyChange { Before = p1 ? (StickerType?)t5 : null, After = p2 ? (StickerType?)t6 : null }; break; case "format_type": p1 = long.TryParse(xc.OldValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t5); p2 = long.TryParse(xc.NewValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t6); entrysti.FormatChange = new PropertyChange { Before = p1 ? (StickerFormat?)t5 : null, After = p2 ? (StickerFormat?)t6 : null }; break; default: this.Discord.Logger.LogWarning(LoggerEvents.AuditLog, "Unknown key in sticker update: {0} - this should be reported to library developers", xc.Key); break; } } break; case AuditLogActionType.MessageDelete: case AuditLogActionType.MessageBulkDelete: { entry = new DiscordAuditLogMessageEntry(); var entrymsg = entry as DiscordAuditLogMessageEntry; if (xac.Options != null) { entrymsg.Channel = this.GetChannel(xac.Options.ChannelId) ?? new DiscordChannel { Id = xac.Options.ChannelId, Discord = this.Discord, GuildId = this.Id }; entrymsg.MessageCount = xac.Options.Count; } if (entrymsg.Channel != null) { entrymsg.Target = this.Discord is DiscordClient dc && dc.MessageCache != null && dc.MessageCache.TryGet(xm => xm.Id == xac.TargetId.Value && xm.ChannelId == entrymsg.Channel.Id, out var msg) ? msg : new DiscordMessage { Discord = this.Discord, Id = xac.TargetId.Value }; } break; } case AuditLogActionType.MessagePin: case AuditLogActionType.MessageUnpin: { entry = new DiscordAuditLogMessagePinEntry(); var entrypin = entry as DiscordAuditLogMessagePinEntry; if (this.Discord is not DiscordClient dc) { break; } if (xac.Options != null) { DiscordMessage message = default; dc.MessageCache?.TryGet(x => x.Id == xac.Options.MessageId && x.ChannelId == xac.Options.ChannelId, out message); entrypin.Channel = this.GetChannel(xac.Options.ChannelId) ?? new DiscordChannel { Id = xac.Options.ChannelId, Discord = this.Discord, GuildId = this.Id }; entrypin.Message = message ?? new DiscordMessage { Id = xac.Options.MessageId, Discord = this.Discord }; } if (xac.TargetId.HasValue) { dc.UserCache.TryGetValue(xac.TargetId.Value, out var user); entrypin.Target = user ?? new DiscordUser { Id = user.Id, Discord = this.Discord }; } break; } case AuditLogActionType.BotAdd: { entry = new DiscordAuditLogBotAddEntry(); if (!(this.Discord is DiscordClient dc && xac.TargetId.HasValue)) { break; } dc.UserCache.TryGetValue(xac.TargetId.Value, out var bot); (entry as DiscordAuditLogBotAddEntry).TargetBot = bot ?? new DiscordUser { Id = xac.TargetId.Value, Discord = this.Discord }; break; } case AuditLogActionType.MemberMove: entry = new DiscordAuditLogMemberMoveEntry(); if (xac.Options == null) { break; } var moveentry = entry as DiscordAuditLogMemberMoveEntry; moveentry.UserCount = xac.Options.Count; moveentry.Channel = this.GetChannel(xac.Options.ChannelId) ?? new DiscordChannel { Id = xac.Options.ChannelId, Discord = this.Discord, GuildId = this.Id }; break; case AuditLogActionType.MemberDisconnect: entry = new DiscordAuditLogMemberDisconnectEntry { UserCount = xac.Options?.Count ?? 0 }; break; case AuditLogActionType.IntegrationCreate: case AuditLogActionType.IntegrationDelete: case AuditLogActionType.IntegrationUpdate: entry = new DiscordAuditLogIntegrationEntry(); var integentry = entry as DiscordAuditLogIntegrationEntry; foreach (var xc in xac.Changes) { switch (xc.Key.ToLowerInvariant()) { case "enable_emoticons": integentry.EnableEmoticons = new PropertyChange { Before = (bool?)xc.OldValue, After = (bool?)xc.NewValue }; break; case "expire_behavior": integentry.ExpireBehavior = new PropertyChange { Before = (int?)xc.OldValue, After = (int?)xc.NewValue }; break; case "expire_grace_period": integentry.ExpireBehavior = new PropertyChange { Before = (int?)xc.OldValue, After = (int?)xc.NewValue }; break; default: this.Discord.Logger.LogWarning(LoggerEvents.AuditLog, "Unknown key in integration update: {0} - this should be reported to library developers", xc.Key); break; } } break; case AuditLogActionType.ThreadCreate: case AuditLogActionType.ThreadDelete: case AuditLogActionType.ThreadUpdate: entry = new DiscordAuditLogThreadEntry { - Target = this._threads.TryGetValue(xac.TargetId.Value, out var thread) ? thread : new DiscordThreadChannel { Id = xac.TargetId.Value, Discord = this.Discord } + Target = this.ThreadsInternal.TryGetValue(xac.TargetId.Value, out var thread) ? thread : new DiscordThreadChannel { Id = xac.TargetId.Value, Discord = this.Discord } }; var entrythr = entry as DiscordAuditLogThreadEntry; foreach (var xc in xac.Changes) { switch (xc.Key.ToLowerInvariant()) { case "name": entrythr.NameChange = new PropertyChange { Before = xc.OldValue != null ? xc.OldValueString : null, After = xc.NewValue != null ? xc.NewValueString : null }; break; case "type": p1 = ulong.TryParse(xc.NewValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t1); p2 = ulong.TryParse(xc.OldValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t2); entrythr.TypeChange = new PropertyChange { Before = p1 ? (ChannelType?)t1 : null, After = p2 ? (ChannelType?)t2 : null }; break; case "archived": entrythr.ArchivedChange = new PropertyChange { Before = xc.OldValue != null ? (bool?)xc.OldValue : null, After = xc.NewValue != null ? (bool?)xc.NewValue : null }; break; case "locked": entrythr.LockedChange = new PropertyChange { Before = xc.OldValue != null ? (bool?)xc.OldValue : null, After = xc.NewValue != null ? (bool?)xc.NewValue : null }; break; case "auto_archive_duration": p1 = long.TryParse(xc.OldValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t5); p2 = long.TryParse(xc.NewValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t6); entrythr.AutoArchiveDurationChange = new PropertyChange { Before = p1 ? (ThreadAutoArchiveDuration?)t5 : null, After = p2 ? (ThreadAutoArchiveDuration?)t6 : null }; break; case "rate_limit_per_user": entrythr.PerUserRateLimitChange = new PropertyChange { Before = (int?)(long?)xc.OldValue, After = (int?)(long?)xc.NewValue }; break; default: this.Discord.Logger.LogWarning(LoggerEvents.AuditLog, "Unknown key in thread update: {0} - this should be reported to library developers", xc.Key); break; } } break; case AuditLogActionType.GuildScheduledEventCreate: case AuditLogActionType.GuildScheduledEventDelete: case AuditLogActionType.GuildScheduledEventUpdate: entry = new DiscordAuditLogGuildScheduledEventEntry { - Target = this._scheduledEvents.TryGetValue(xac.TargetId.Value, out var scheduled_event) ? scheduled_event : new DiscordScheduledEvent { Id = xac.TargetId.Value, Discord = this.Discord } + Target = this.ScheduledEventsInternal.TryGetValue(xac.TargetId.Value, out var scheduledEvent) ? scheduledEvent : new DiscordScheduledEvent { Id = xac.TargetId.Value, Discord = this.Discord } }; var entryse = entry as DiscordAuditLogGuildScheduledEventEntry; foreach (var xc in xac.Changes) { switch (xc.Key.ToLowerInvariant()) { case "channel_id": entryse.ChannelIdChange = new PropertyChange { Before = ulong.TryParse(xc.OldValueString, out var ogid) ? ogid : null, After = ulong.TryParse(xc.NewValueString, out var ngid) ? ngid : null }; break; case "description": entryse.DescriptionChange = new PropertyChange { Before = xc.OldValue != null ? xc.OldValueString : null, After = xc.NewValue != null ? xc.NewValueString : null }; break; case "location": entryse.LocationChange = new PropertyChange { Before = xc.OldValue != null ? xc.OldValueString : null, After = xc.NewValue != null ? xc.NewValueString : null }; break; case "privacy_level": p1 = long.TryParse(xc.OldValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t5); p2 = long.TryParse(xc.NewValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t6); entryse.PrivacyLevelChange = new PropertyChange { Before = p1 ? (ScheduledEventPrivacyLevel?)t5 : null, After = p2 ? (ScheduledEventPrivacyLevel?)t6 : null }; break; case "entity_type": p1 = long.TryParse(xc.OldValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t5); p2 = long.TryParse(xc.NewValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t6); entryse.EntityTypeChange = new PropertyChange { Before = p1 ? (ScheduledEventEntityType?)t5 : null, After = p2 ? (ScheduledEventEntityType?)t6 : null }; break; case "status": p1 = long.TryParse(xc.OldValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t5); p2 = long.TryParse(xc.NewValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t6); entryse.StatusChange = new PropertyChange { Before = p1 ? (ScheduledEventStatus?)t5 : null, After = p2 ? (ScheduledEventStatus?)t6 : null }; break; default: this.Discord.Logger.LogWarning(LoggerEvents.AuditLog, "Unknown key in scheduled event update: {0} - this should be reported to library developers", xc.Key); break; } } break; default: this.Discord.Logger.LogWarning(LoggerEvents.AuditLog, "Unknown audit log action type: {0} - this should be reported to library developers", (int)xac.ActionType); break; } if (entry == null) continue; entry.ActionCategory = xac.ActionType switch { AuditLogActionType.ChannelCreate or AuditLogActionType.EmojiCreate or AuditLogActionType.InviteCreate or AuditLogActionType.OverwriteCreate or AuditLogActionType.RoleCreate or AuditLogActionType.WebhookCreate or AuditLogActionType.IntegrationCreate or AuditLogActionType.StickerCreate or AuditLogActionType.StageInstanceCreate or AuditLogActionType.ThreadCreate or AuditLogActionType.GuildScheduledEventCreate => AuditLogActionCategory.Create, AuditLogActionType.ChannelDelete or AuditLogActionType.EmojiDelete or AuditLogActionType.InviteDelete or AuditLogActionType.MessageDelete or AuditLogActionType.MessageBulkDelete or AuditLogActionType.OverwriteDelete or AuditLogActionType.RoleDelete or AuditLogActionType.WebhookDelete or AuditLogActionType.IntegrationDelete or AuditLogActionType.StickerDelete or AuditLogActionType.StageInstanceDelete or AuditLogActionType.ThreadDelete or AuditLogActionType.GuildScheduledEventDelete => AuditLogActionCategory.Delete, AuditLogActionType.ChannelUpdate or AuditLogActionType.EmojiUpdate or AuditLogActionType.InviteUpdate or AuditLogActionType.MemberRoleUpdate or AuditLogActionType.MemberUpdate or AuditLogActionType.OverwriteUpdate or AuditLogActionType.RoleUpdate or AuditLogActionType.WebhookUpdate or AuditLogActionType.IntegrationUpdate or AuditLogActionType.StickerUpdate or AuditLogActionType.StageInstanceUpdate or AuditLogActionType.ThreadUpdate or AuditLogActionType.GuildScheduledEventUpdate => AuditLogActionCategory.Update, _ => AuditLogActionCategory.Other, }; entry.Discord = this.Discord; entry.ActionType = xac.ActionType; entry.Id = xac.Id; entry.Reason = xac.Reason; entry.UserResponsible = amd[xac.UserId]; entries.Add(entry); } return new ReadOnlyCollection(entries); } /// /// Gets all of this guild's custom emojis. /// /// All of this guild's custom emojis. /// Thrown when Discord is unable to process the request. public Task> GetEmojisAsync() => this.Discord.ApiClient.GetGuildEmojisAsync(this.Id); /// /// Gets this guild's specified custom emoji. /// /// ID of the emoji to get. /// The requested custom emoji. /// Thrown when Discord is unable to process the request. public Task GetEmojiAsync(ulong id) => this.Discord.ApiClient.GetGuildEmojiAsync(this.Id, id); /// /// Creates a new custom emoji for this guild. /// /// Name of the new emoji. /// Image to use as the emoji. /// Roles for which the emoji will be available. This works only if your application is whitelisted as integration. /// Reason for audit log. /// The newly-created emoji. /// Thrown when the client does not have the permission. /// Thrown when Discord is unable to process the request. public Task CreateEmojiAsync(string name, Stream image, IEnumerable roles = null, string reason = null) { if (string.IsNullOrWhiteSpace(name)) throw new ArgumentNullException(nameof(name)); name = name.Trim(); if (name.Length < 2 || name.Length > 50) throw new ArgumentException("Emoji name needs to be between 2 and 50 characters long."); if (image == null) throw new ArgumentNullException(nameof(image)); string image64 = null; using (var imgtool = new ImageTool(image)) image64 = imgtool.GetBase64(); 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) { return 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._stickers.AddOrUpdate(xstr.Id, xstr, (id, old) => + this.StickersInternal.AddOrUpdate(xstr.Id, xstr, (id, old) => { old.Name = xstr.Name; old.Description = xstr.Description; - old._internalTags = xstr._internalTags; + 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 sticker_id) - => this.Discord.ApiClient.GetGuildStickerAsync(this.Id, sticker_id); + 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", + 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", + 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("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) { string uemoji = null; - if (!this._stickers.TryGetValue(sticker, out var stickerobj) || stickerobj.Guild.Id != this.Id) + 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."); else 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._stickers.TryGetValue(usticker.Id, out var old)) - this._stickers.TryUpdate(usticker.Id, usticker, old); + 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) { - return !this._stickers.TryGetValue(sticker, out var stickerobj) + return !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() { - return this._channels?.Values.Where(xc => xc.Type == ChannelType.Text) + return this.ChannelsInternal?.Values.Where(xc => xc.Type == ChannelType.Text) .OrderBy(xc => xc.Position) .FirstOrDefault(xc => (xc.PermissionsFor(this.CurrentMember) & DisCatSharp.Permissions.AccessChannels) == DisCatSharp.Permissions.AccessChannels); } /// /// Gets the guild's widget /// /// The guild's widget public Task GetWidgetAsync() => this.Discord.ApiClient.GetGuildWidgetAsync(this.Id); /// /// Gets the guild's widget settings /// /// The guild's widget settings public Task GetWidgetSettingsAsync() => this.Discord.ApiClient.GetGuildWidgetSettingsAsync(this.Id); /// /// Modifies the guild's widget settings /// /// If the widget is enabled or not /// Widget channel /// Reason the widget settings were modified /// The newly modified widget settings public Task ModifyWidgetSettingsAsync(bool? isEnabled = null, DiscordChannel channel = null, string reason = null) => this.Discord.ApiClient.ModifyGuildWidgetSettingsAsync(this.Id, isEnabled, channel?.Id, reason); /// /// Gets all of this guild's templates. /// /// All of the guild's templates. /// Throws when the client does not have the permission. /// Thrown when Discord is unable to process the request. public Task> GetTemplatesAsync() => this.Discord.ApiClient.GetGuildTemplatesAsync(this.Id); /// /// Creates a guild template. /// /// Name of the template. /// Description of the template. /// The template created. /// Throws when a template already exists for the guild or a null parameter is provided for the name. /// Throws when the client does not have the permission. /// Thrown when Discord is unable to process the request. public Task CreateTemplateAsync(string name, string description = null) => this.Discord.ApiClient.CreateGuildTemplateAsync(this.Id, name, description); /// /// Syncs the template to the current guild's state. /// /// The code of the template to sync. /// The template synced. /// Throws when the template for the code cannot be found /// Throws when the client does not have the permission. /// Thrown when Discord is unable to process the request. public Task SyncTemplateAsync(string code) => this.Discord.ApiClient.SyncGuildTemplateAsync(this.Id, code); /// /// Modifies the template's metadata. /// /// The template's code. /// Name of the template. /// Description of the template. /// The template modified. /// Throws when the template for the code cannot be found /// Throws when the client does not have the permission. /// Thrown when Discord is unable to process the request. public Task ModifyTemplateAsync(string code, string name = null, string description = null) => this.Discord.ApiClient.ModifyGuildTemplateAsync(this.Id, code, name, description); /// /// Deletes the template. /// /// The code of the template to delete. /// The deleted template. /// Throws when the template for the code cannot be found /// Throws when the client does not have the permission. /// Thrown when Discord is unable to process the request. public Task DeleteTemplateAsync(string code) => this.Discord.ApiClient.DeleteGuildTemplateAsync(this.Id, code); /// /// Gets this guild's membership screening form. /// /// This guild's membership screening form. /// Thrown when Discord is unable to process the request. public Task GetMembershipScreeningFormAsync() => this.Discord.ApiClient.GetGuildMembershipScreeningFormAsync(this.Id); /// /// Modifies this guild's membership screening form. /// /// Action to perform /// The modified screening form. /// Thrown when the client doesn't have the permission, or community is not enabled on this guild. /// Thrown when Discord is unable to process the request. public async Task ModifyMembershipScreeningFormAsync(Action action) { var mdl = new MembershipScreeningEditModel(); action(mdl); return await this.Discord.ApiClient.ModifyGuildMembershipScreeningFormAsync(this.Id, mdl.Enabled, mdl.Fields, mdl.Description); } /// /// Gets all the application commands in this guild. /// /// A list of application commands in this guild. public Task> GetApplicationCommandsAsync() => this.Discord.ApiClient.GetGuildApplicationCommandsAsync(this.Discord.CurrentApplication.Id, this.Id); /// /// Overwrites the existing application commands in this guild. New commands are automatically created and missing commands are automatically delete /// /// The list of commands to overwrite with. /// The list of guild commands public Task> BulkOverwriteApplicationCommandsAsync(IEnumerable commands) => this.Discord.ApiClient.BulkOverwriteGuildApplicationCommandsAsync(this.Discord.CurrentApplication.Id, this.Id, commands); /// /// Creates or overwrites a application command in this guild. /// /// The command to create. /// The created command. public Task CreateApplicationCommandAsync(DiscordApplicationCommand command) => this.Discord.ApiClient.CreateGuildApplicationCommandAsync(this.Discord.CurrentApplication.Id, this.Id, command); /// /// Edits a application command in this guild. /// /// The id of the command to edit. /// Action to perform. /// The edit command. public async Task EditApplicationCommandAsync(ulong commandId, Action action) { var mdl = new ApplicationCommandEditModel(); action(mdl); return await this.Discord.ApiClient.EditGuildApplicationCommandAsync(this.Discord.CurrentApplication.Id, this.Id, commandId, mdl.Name, mdl.Description, mdl.Options, mdl.DefaultPermission, mdl.NameLocalizations, mdl.DescriptionLocalizations).ConfigureAwait(false); } /// /// Gets this guild's welcome screen. /// /// This guild's welcome screen object. /// Thrown when Discord is unable to process the request. public Task GetWelcomeScreenAsync() => this.Discord.ApiClient.GetGuildWelcomeScreenAsync(this.Id); /// /// Modifies this guild's welcome screen. /// /// Action to perform. /// The modified welcome screen. /// Thrown when the client doesn't have the permission, or community is not enabled on this guild. /// Thrown when Discord is unable to process the request. public async Task ModifyWelcomeScreenAsync(Action action) { var mdl = new WelcomeScreenEditModel(); action(mdl); return await this.Discord.ApiClient.ModifyGuildWelcomeScreenAsync(this.Id, mdl.Enabled, mdl.WelcomeChannels, mdl.Description).ConfigureAwait(false); } #endregion /// /// Returns a string representation of this guild. /// /// String representation of this guild. public override string ToString() => $"Guild {this.Id}; {this.Name}"; /// /// Checks whether this is equal to another object. /// /// Object to compare to. /// Whether the object is equal to this . public override bool Equals(object obj) => this.Equals(obj as DiscordGuild); /// /// Checks whether this is equal to another . /// /// to compare to. /// Whether the is equal to this . public bool Equals(DiscordGuild e) => e is not null && (ReferenceEquals(this, e) || this.Id == e.Id); /// /// Gets the hash code for this . /// /// The hash code for this . public override int GetHashCode() => this.Id.GetHashCode(); /// /// Gets whether the two objects are equal. /// /// First guild to compare. /// Second guild to compare. /// Whether the two guilds are equal. public static bool operator ==(DiscordGuild e1, DiscordGuild e2) { var o1 = e1 as object; var o2 = e2 as object; return (o1 != null || o2 == null) && (o1 == null || o2 != null) && ((o1 == null && o2 == null) || e1.Id == e2.Id); } /// /// Gets whether the two objects are not equal. /// /// First guild to compare. /// Second guild to compare. /// Whether the two guilds are not equal. public static bool operator !=(DiscordGuild e1, DiscordGuild e2) => !(e1 == e2); } /// /// Represents guild verification level. /// public enum VerificationLevel : int { /// /// No verification. Anyone can join and chat right away. /// None = 0, /// /// Low verification level. Users are required to have a verified email attached to their account in order to be able to chat. /// Low = 1, /// /// Medium verification level. Users are required to have a verified email attached to their account, and account age need to be at least 5 minutes in order to be able to chat. /// Medium = 2, /// /// High verification level. Users are required to have a verified email attached to their account, account age need to be at least 5 minutes, and they need to be in the server for at least 10 minutes in order to be able to chat. /// High = 3, /// /// Highest verification level. Users are required to have a verified phone number attached to their account. /// Highest = 4 } /// /// Represents default notification level for a guild. /// public enum DefaultMessageNotifications : int { /// /// All messages will trigger push notifications. /// AllMessages = 0, /// /// Only messages that mention the user (or a role he's in) will trigger push notifications. /// MentionsOnly = 1 } /// /// Represents multi-factor authentication level required by a guild to use administrator functionality. /// public enum MfaLevel : int { /// /// Multi-factor authentication is not required to use administrator functionality. /// Disabled = 0, /// /// Multi-factor authentication is required to use administrator functionality. /// Enabled = 1 } /// /// Represents the value of explicit content filter in a guild. /// public enum ExplicitContentFilter : int { /// /// Explicit content filter is disabled. /// Disabled = 0, /// /// Only messages from members without any roles are scanned. /// MembersWithoutRoles = 1, /// /// Messages from all members are scanned. /// AllMembers = 2 } /// /// Represents the formats for a guild widget. /// public enum WidgetType : int { /// /// The widget is represented in shield format. /// This is the default widget type. /// Shield = 0, /// /// The widget is represented as the first banner type. /// Banner1 = 1, /// /// The widget is represented as the second banner type. /// Banner2 = 2, /// /// The widget is represented as the third banner type. /// Banner3 = 3, /// /// The widget is represented in the fourth banner type. /// Banner4 = 4 } /// /// Represents the guild features. /// public class GuildFeatures { /// /// Guild has access to set an animated guild icon. /// public bool CanSetAnimatedIcon { get; } /// /// Guild has access to set a guild banner image. /// public bool CanSetBanner { get; } /// /// Guild has access to use commerce features (i.e. create store channels) /// public bool CanCreateStoreChannels { get; } /// /// Guild can enable Welcome Screen, Membership Screening, Stage Channels, News Channels and receives community updates. /// Furthermore the guild can apply as a partner and for the discovery (if the prerequisites are given). /// and is usable. /// public bool HasCommunityEnabled { get; } /// /// Guild is able to be discovered in the discovery. /// public bool IsDiscoverable { get; } /// /// Guild is able to be featured in the discovery. /// public bool IsFeatureable { get; } /// /// Guild has access to set an invite splash background. /// public bool CanSetInviteSplash { get; } /// /// Guild has enabled Membership Screening. /// public bool HasMembershipScreeningEnabled { get; } /// /// Guild has access to create news channels. /// is usable. /// public bool CanCreateNewsChannels { get; } /// /// Guild is partnered. /// public bool IsPartnered { get; } /// /// Guild has increased custom emoji slots. /// public bool CanUploadMoreEmojis { get; } /// /// Guild can be previewed before joining via Membership Screening or the discovery. /// public bool HasPreviewEnabled { get; } /// /// Guild has access to set a vanity URL. /// public bool CanSetVanityUrl { get; } /// /// Guild is verified. /// public bool IsVerified { get; } /// /// Guild has access to set 384kbps bitrate in voice (previously VIP voice servers). /// public bool CanAccessVipRegions { get; } /// /// Guild has enabled the welcome screen. /// public bool HasWelcomeScreenEnabled { get; } /// /// Guild has enabled ticketed events. /// public bool HasTicketedEventsEnabled { get; } /// /// Guild has enabled monetization. /// public bool HasMonetizationEnabled { get; } /// /// Guild has increased custom sticker slots. /// public bool CanUploadMoreStickers { get; } /// /// Guild has access to the three day archive time for threads. /// Needs Premium Tier 1 (). /// public bool CanSetThreadArchiveDurationThreeDays { get; } /// /// Guild has access to the seven day archive time for threads. /// Needs Premium Tier 2 (). /// public bool CanSetThreadArchiveDurationSevenDays { get; } /// /// Guild has access to create private threads. /// Needs Premium Tier 2 (). /// public bool CanCreatePrivateThreads { get; } /// /// Guild is a hub. /// is usable. /// public bool IsHub { get; } /// /// Guild is in a hub. /// https://github.com/discord/discord-api-docs/pull/3757/commits/4932d92c9d0c783861bc715bf7ebbabb15114e34 /// public bool HasDirectoryEntry { get; } /// /// Guild is linked to a hub. /// public bool IsLinkedToHub { get; } /// /// Guild has full access to threads. /// Old Feature. /// public bool HasThreadTestingEnabled { get; } /// /// Guild has access to threads. /// public bool HasThreadsEnabled { get; } /// /// Guild can set role icons. /// public bool CanSetRoleIcons { get; } /// /// Guild has the new thread permissions. /// Old Feature. /// public bool HasNewThreadPermissions { get; } /// /// Guild can set thread default auto archive duration. /// Old Feature. /// public bool CanSetThreadDefaultAutoArchiveDuration { get; } /// /// Guild has enabled role subsriptions. /// public bool HasRoleSubscriptionsEnabled { get; } /// /// Guild role subsriptions as purchaseable. /// public bool RoleSubscriptionsIsAvaiableForPurchase { get; } /// /// Guild has premium tier 3 override. /// public bool PremiumTierThreeOverride { get; } /// /// Guild has access to text in voice. /// Restricted to . /// public bool TextInVoiceEnabled { get; } /// /// Guild can set an animated banner. /// Needs Premium Tier 3 (). /// public bool CanSetAnimatedBanner { get; } /// /// Guild can set an animated banner. /// Needs Premium Tier 3 (). /// public bool CanSetChannelBanner { get; } /// /// Allows members to customize their avatar, banner and bio for that server. /// public bool HasMemberProfiles { get; } /// /// Guild is restricted to users with the badge. /// public bool IsStaffOnly { get; } /// /// String of guild features. /// public string FeatureString { get; } /// /// Checks the guild features and constructs a new object. /// /// Guild to check public GuildFeatures(DiscordGuild guild) { this.CanSetAnimatedIcon = guild.RawFeatures.Contains("ANIMATED_ICON"); this.CanSetAnimatedBanner = guild.RawFeatures.Contains("ANIMATED_BANNER"); this.CanSetBanner = guild.RawFeatures.Contains("BANNER"); this.CanSetChannelBanner = guild.RawFeatures.Contains("CHANNEL_BANNER"); this.CanCreateStoreChannels = guild.RawFeatures.Contains("COMMERCE"); this.HasCommunityEnabled = guild.RawFeatures.Contains("COMMUNITY"); this.IsDiscoverable = !guild.RawFeatures.Contains("DISCOVERABLE_DISABLED") && guild.RawFeatures.Contains("DISCOVERABLE"); this.IsFeatureable = guild.RawFeatures.Contains("FEATURABLE"); this.CanSetInviteSplash = guild.RawFeatures.Contains("INVITE_SPLASH"); this.HasMembershipScreeningEnabled = guild.RawFeatures.Contains("MEMBER_VERIFICATION_GATE_ENABLED"); this.CanCreateNewsChannels = guild.RawFeatures.Contains("NEWS"); this.IsPartnered = guild.RawFeatures.Contains("PARTNERED"); this.CanUploadMoreEmojis = guild.RawFeatures.Contains("MORE_EMOJI"); this.HasPreviewEnabled = guild.RawFeatures.Contains("PREVIEW_ENABLED"); this.CanSetVanityUrl = guild.RawFeatures.Contains("VANITY_URL"); this.IsVerified = guild.RawFeatures.Contains("VERIFIED"); this.CanAccessVipRegions = guild.RawFeatures.Contains("VIP_REGIONS"); this.HasWelcomeScreenEnabled = guild.RawFeatures.Contains("WELCOME_SCREEN_ENABLED"); this.HasTicketedEventsEnabled = guild.RawFeatures.Contains("TICKETED_EVENTS_ENABLED"); this.HasMonetizationEnabled = guild.RawFeatures.Contains("MONETIZATION_ENABLED"); this.CanUploadMoreStickers = guild.RawFeatures.Contains("MORE_STICKERS"); this.CanSetThreadArchiveDurationThreeDays = guild.RawFeatures.Contains("THREE_DAY_THREAD_ARCHIVE"); this.CanSetThreadArchiveDurationSevenDays = guild.RawFeatures.Contains("SEVEN_DAY_THREAD_ARCHIVE"); this.CanCreatePrivateThreads = guild.RawFeatures.Contains("PRIVATE_THREADS"); this.IsHub = guild.RawFeatures.Contains("HUB"); this.HasThreadTestingEnabled = guild.RawFeatures.Contains("THREADS_ENABLED_TESTING"); this.HasThreadsEnabled = guild.RawFeatures.Contains("THREADS_ENABLED"); this.CanSetRoleIcons = guild.RawFeatures.Contains("ROLE_ICONS"); this.HasNewThreadPermissions = guild.RawFeatures.Contains("NEW_THREAD_PERMISSIONS"); this.HasRoleSubscriptionsEnabled = guild.RawFeatures.Contains("ROLE_SUBSCRIPTIONS_ENABLED"); this.PremiumTierThreeOverride = guild.RawFeatures.Contains("PREMIUM_TIER_3_OVERRIDE"); this.CanSetThreadDefaultAutoArchiveDuration = guild.RawFeatures.Contains("THREAD_DEFAULT_AUTO_ARCHIVE_DURATION"); this.TextInVoiceEnabled = guild.RawFeatures.Contains("TEXT_IN_VOICE_ENABLED"); this.HasDirectoryEntry = guild.RawFeatures.Contains("HAS_DIRECTORY_ENTRY"); this.IsLinkedToHub = guild.RawFeatures.Contains("LINKED_TO_HUB"); this.HasMemberProfiles = guild.RawFeatures.Contains("MEMBER_PROFILES"); this.IsStaffOnly = guild.RawFeatures.Contains("INTERNAL_EMPLOYEE_ONLY"); this.RoleSubscriptionsIsAvaiableForPurchase = guild.RawFeatures.Contains("ROLE_SUBSCRIPTIONS_AVAILABLE_FOR_PURCHASE"); - var _features = guild.RawFeatures.Any() ? "" : "None"; + var features = guild.RawFeatures.Any() ? "" : "None"; foreach (var feature in guild.RawFeatures) { - _features += feature + " "; + features += feature + " "; } - this.FeatureString = _features; + this.FeatureString = features; } } } diff --git a/DisCatSharp/Entities/Guild/DiscordGuildPreview.cs b/DisCatSharp/Entities/Guild/DiscordGuildPreview.cs index 9bcf7197c..daac020ae 100644 --- a/DisCatSharp/Entities/Guild/DiscordGuildPreview.cs +++ b/DisCatSharp/Entities/Guild/DiscordGuildPreview.cs @@ -1,134 +1,134 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System.Collections.Concurrent; using System.Collections.Generic; using System.Globalization; using DisCatSharp.Enums; using DisCatSharp.Net; using DisCatSharp.Net.Serialization; using Newtonsoft.Json; namespace DisCatSharp.Entities { /// /// Represents the guild preview. /// public class DiscordGuildPreview : SnowflakeObject { /// /// Gets the guild 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 a collection of this guild's emojis. /// [JsonIgnore] - public IReadOnlyDictionary Emojis => new ReadOnlyConcurrentDictionary(this._emojis); + public IReadOnlyDictionary Emojis => new ReadOnlyConcurrentDictionary(this.EmojisInternal); [JsonProperty("emojis", NullValueHandling = NullValueHandling.Ignore)] [JsonConverter(typeof(SnowflakeArrayAsDictionaryJsonConverter))] - internal ConcurrentDictionary _emojis; + internal ConcurrentDictionary EmojisInternal; /// /// Gets a collection of this guild's features. /// [JsonProperty("features", NullValueHandling = NullValueHandling.Ignore)] public IReadOnlyList Features { get; internal set; } /// /// Gets the approximate member count. /// [JsonProperty("approximate_member_count")] public int ApproximateMemberCount { get; internal set; } /// /// Gets the approximate presence count. /// [JsonProperty("approximate_presence_count")] public int ApproximatePresenceCount { get; internal set; } /// /// Gets the description for the guild, if the guild is discoverable. /// [JsonProperty("description", NullValueHandling = NullValueHandling.Ignore)] public string Description { get; internal set; } /// /// Gets the system channel flags for the guild. /// [JsonProperty("system_channel_flags", NullValueHandling = NullValueHandling.Ignore)] public SystemChannelFlags SystemChannelFlags { get; internal set; } /// /// Gets this hub type for the guild, if the guild is a hub. /// [JsonProperty("hub_type", NullValueHandling = NullValueHandling.Ignore)] public HubType HubType { get; internal set; } /// /// Initializes a new instance of the class. /// internal DiscordGuildPreview() { } } } diff --git a/DisCatSharp/Entities/Guild/DiscordMember.cs b/DisCatSharp/Entities/Guild/DiscordMember.cs index d072cc67d..39c019e1f 100644 --- a/DisCatSharp/Entities/Guild/DiscordMember.cs +++ b/DisCatSharp/Entities/Guild/DiscordMember.cs @@ -1,781 +1,781 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Globalization; using System.Linq; using System.Threading.Tasks; using DisCatSharp.Enums; using DisCatSharp.Net; using DisCatSharp.Net.Abstractions; using DisCatSharp.Net.Models; using Newtonsoft.Json; namespace DisCatSharp.Entities { /// /// Represents a Discord guild member. /// public class DiscordMember : DiscordUser, IEquatable { /// /// Initializes a new instance of the class. /// internal DiscordMember() { - this._role_ids_lazy = new Lazy>(() => new ReadOnlyCollection(this._role_ids)); + this._roleIdsLazy = new Lazy>(() => new ReadOnlyCollection(this.RoleIdsInternal)); } /// /// Initializes a new instance of the class. /// /// The user. internal DiscordMember(DiscordUser user) { this.Discord = user.Discord; this.Id = user.Id; - this._role_ids = new List(); - this._role_ids_lazy = new Lazy>(() => new ReadOnlyCollection(this._role_ids)); + this.RoleIdsInternal = new List(); + this._roleIdsLazy = new Lazy>(() => new ReadOnlyCollection(this.RoleIdsInternal)); } /// /// Initializes a new instance of the class. /// /// The mbr. internal DiscordMember(TransportMember mbr) { this.Id = mbr.User.Id; this.IsDeafened = mbr.IsDeafened; this.IsMuted = mbr.IsMuted; this.JoinedAt = mbr.JoinedAt; this.Nickname = mbr.Nickname; this.PremiumSince = mbr.PremiumSince; this.IsPending = mbr.IsPending; this.GuildAvatarHash = mbr.GuildAvatarHash; this.GuildBannerHash = mbr.GuildBannerHash; this.GuildBio = mbr.GuildBio; this.CommunicationDisabledUntil = mbr.CommunicationDisabledUntil; - this._avatarHash = mbr.AvatarHash; - this._role_ids = mbr.Roles ?? new List(); - this._role_ids_lazy = new Lazy>(() => new ReadOnlyCollection(this._role_ids)); + this.AvatarHashInternal = mbr.AvatarHash; + this.RoleIdsInternal = mbr.Roles ?? new List(); + this._roleIdsLazy = new Lazy>(() => new ReadOnlyCollection(this.RoleIdsInternal)); } /// /// Gets the members avatar hash. /// [JsonProperty("avatar", NullValueHandling = NullValueHandling.Ignore)] public virtual string GuildAvatarHash { get; internal set; } /// /// Gets the members avatar URL. /// [JsonIgnore] public string GuildAvatarUrl - => string.IsNullOrWhiteSpace(this.GuildAvatarHash) ? this.User.AvatarUrl : $"{DiscordDomain.GetDomain(CoreDomain.DiscordCdn).Url}{Endpoints.GUILDS}/{this._guild_id.ToString(CultureInfo.InvariantCulture)}{Endpoints.USERS}/{this.Id.ToString(CultureInfo.InvariantCulture)}{Endpoints.AVATARS}/{this.GuildAvatarHash}.{(this.GuildAvatarHash.StartsWith("a_") ? "gif" : "png")}?size=1024"; + => string.IsNullOrWhiteSpace(this.GuildAvatarHash) ? this.User.AvatarUrl : $"{DiscordDomain.GetDomain(CoreDomain.DiscordCdn).Url}{Endpoints.GUILDS}/{this.GuildId.ToString(CultureInfo.InvariantCulture)}{Endpoints.USERS}/{this.Id.ToString(CultureInfo.InvariantCulture)}{Endpoints.AVATARS}/{this.GuildAvatarHash}.{(this.GuildAvatarHash.StartsWith("a_") ? "gif" : "png")}?size=1024"; /// /// Gets this member's banner url. /// [JsonIgnore] #pragma warning disable CS0108 // Member hides inherited member; missing new keyword public string BannerUrl => this.User.BannerUrl; #pragma warning restore CS0108 // Member hides inherited member; missing new keyword /// /// Gets the member's banner hash. /// [JsonIgnore] public override string BannerHash { get => this.User.BannerHash; internal set => this.User.BannerHash = value; } /// /// Gets the members banner hash. /// [JsonProperty("banner", NullValueHandling = NullValueHandling.Ignore)] public virtual string GuildBannerHash { get; internal set; } /// /// Gets the members banner URL. /// [JsonIgnore] public string GuildBannerUrl - => string.IsNullOrWhiteSpace(this.GuildBannerHash) ? this.User.BannerUrl : $"{DiscordDomain.GetDomain(CoreDomain.DiscordCdn).Url}{Endpoints.GUILDS}/{this._guild_id.ToString(CultureInfo.InvariantCulture)}{Endpoints.USERS}/{this.Id.ToString(CultureInfo.InvariantCulture)}{Endpoints.BANNERS}/{this.GuildBannerHash}.{(this.GuildBannerHash.StartsWith("a_") ? "gif" : "png")}?size=1024"; + => string.IsNullOrWhiteSpace(this.GuildBannerHash) ? this.User.BannerUrl : $"{DiscordDomain.GetDomain(CoreDomain.DiscordCdn).Url}{Endpoints.GUILDS}/{this.GuildId.ToString(CultureInfo.InvariantCulture)}{Endpoints.USERS}/{this.Id.ToString(CultureInfo.InvariantCulture)}{Endpoints.BANNERS}/{this.GuildBannerHash}.{(this.GuildBannerHash.StartsWith("a_") ? "gif" : "png")}?size=1024"; /// /// The color of this member's banner. Mutually exclusive with . /// [JsonIgnore] public override DiscordColor? BannerColor => this.User.BannerColor; /// /// Gets this member's nickname. /// [JsonProperty("nick", NullValueHandling = NullValueHandling.Ignore)] public string Nickname { get; internal set; } /// /// Gets the members guild bio. /// This is not available to bots tho. /// [JsonProperty("bio", NullValueHandling = NullValueHandling.Ignore)] public string GuildBio { get; internal set; } [JsonIgnore] - internal string _avatarHash; + internal string AvatarHashInternal; /// /// Gets this member's display name. /// [JsonIgnore] public string DisplayName => this.Nickname ?? this.Username; /// /// List of role ids /// [JsonIgnore] internal IReadOnlyList RoleIds - => this._role_ids_lazy.Value; + => this._roleIdsLazy.Value; [JsonProperty("roles", NullValueHandling = NullValueHandling.Ignore)] - internal List _role_ids; + internal List RoleIdsInternal; [JsonIgnore] - private readonly Lazy> _role_ids_lazy; + private readonly Lazy> _roleIdsLazy; /// /// Gets the list of roles associated with this member. /// [JsonIgnore] public IEnumerable Roles => this.RoleIds.Select(id => this.Guild.GetRole(id)).Where(x => x != null); /// /// Gets the color associated with this user's top color-giving role, otherwise 0 (no color). /// [JsonIgnore] public DiscordColor Color { get { var role = this.Roles.OrderByDescending(xr => xr.Position).FirstOrDefault(xr => xr.Color.Value != 0); return role != null ? role.Color : new DiscordColor(); } } /// /// Date the user joined the guild /// [JsonProperty("joined_at", NullValueHandling = NullValueHandling.Ignore)] public DateTimeOffset JoinedAt { get; internal set; } /// /// Date the user started boosting this server /// [JsonProperty("premium_since", NullValueHandling = NullValueHandling.Ignore)] public DateTimeOffset? PremiumSince { get; internal set; } /// /// Date until the can communicate again. /// [JsonProperty("communication_disabled_until", NullValueHandling = NullValueHandling.Include)] public DateTimeOffset? CommunicationDisabledUntil { get; internal set; } /// /// If the user is deafened /// [JsonProperty("is_deafened", NullValueHandling = NullValueHandling.Ignore)] public bool IsDeafened { get; internal set; } /// /// If the user is muted /// [JsonProperty("is_muted", NullValueHandling = NullValueHandling.Ignore)] public bool IsMuted { get; internal set; } /// /// Whether the user has not passed the guild's Membership Screening requirements yet. /// [JsonProperty("pending", NullValueHandling = NullValueHandling.Ignore)] public bool? IsPending { get; internal set; } /// /// Gets this member's voice state. /// [JsonIgnore] public DiscordVoiceState VoiceState - => this.Discord.Guilds[this._guild_id].VoiceStates.TryGetValue(this.Id, out var voiceState) ? voiceState : null; + => this.Discord.Guilds[this.GuildId].VoiceStates.TryGetValue(this.Id, out var voiceState) ? voiceState : null; [JsonIgnore] - internal ulong _guild_id = 0; + internal ulong GuildId = 0; /// /// Gets the guild of which this member is a part of. /// [JsonIgnore] public DiscordGuild Guild - => this.Discord.Guilds[this._guild_id]; + => this.Discord.Guilds[this.GuildId]; /// /// Gets whether this member is the Guild owner. /// [JsonIgnore] public bool IsOwner => this.Id == this.Guild.OwnerId; /// /// Gets the member's position in the role hierarchy, which is the member's highest role's position. Returns for the guild's owner. /// [JsonIgnore] public int Hierarchy => this.IsOwner ? int.MaxValue : this.RoleIds.Count == 0 ? 0 : this.Roles.Max(x => x.Position); /// /// Gets the permissions for the current member. /// [JsonIgnore] public Permissions Permissions => this.GetPermissions(); #region Overridden user properties /// /// Gets the user. /// [JsonIgnore] internal DiscordUser User => this.Discord.UserCache[this.Id]; /// /// Gets this member's username. /// public override string Username { get => this.User.Username; internal set => this.User.Username = value; } /// /// Gets the member's 4-digit discriminator. /// public override string Discriminator { get => this.User.Discriminator; internal set => this.User.Discriminator = value; } /// /// Gets the member's avatar hash. /// [JsonIgnore] public override string AvatarHash { get => this.User.AvatarHash; internal set => this.User.AvatarHash = value; } /// /// Gets whether the member is a bot. /// public override bool IsBot { get => this.User.IsBot; internal set => this.User.IsBot = value; } /// /// Gets the member's email address. /// This is only present in OAuth. /// public override string Email { get => this.User.Email; internal set => this.User.Email = value; } /// /// Gets whether the member has multi-factor authentication enabled. /// public override bool? MfaEnabled { get => this.User.MfaEnabled; internal set => this.User.MfaEnabled = value; } /// /// Gets whether the member is verified. /// This is only present in OAuth. /// public override bool? Verified { get => this.User.Verified; internal set => this.User.Verified = value; } /// /// Gets the member's chosen language /// public override string Locale { get => this.User.Locale; internal set => this.User.Locale = value; } /// /// Gets the user's flags. /// public override UserFlags? OAuthFlags { get => this.User.OAuthFlags; internal set => this.User.OAuthFlags = value; } /// /// Gets the member's flags for OAuth. /// public override UserFlags? Flags { get => this.User.Flags; internal set => this.User.Flags = value; } #endregion /// /// Creates a direct message channel to this member. /// /// Direct message channel to this member. /// Thrown when the member has the bot blocked, the member is no longer in the guild, or if the member has Allow DM from server members off. /// Thrown when the member does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task CreateDmChannelAsync() => this.Discord.ApiClient.CreateDmAsync(this.Id); /// /// Sends a direct message to this member. Creates a direct message channel if one does not exist already. /// /// Content of the message to send. /// The sent message. /// Thrown when the member has the bot blocked, the member is no longer in the guild, or if the member has Allow DM from server members off. /// Thrown when the member does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public async Task SendMessageAsync(string content) { if (this.IsBot && this.Discord.CurrentUser.IsBot) throw new ArgumentException("Bots cannot DM each other."); var chn = await this.CreateDmChannelAsync().ConfigureAwait(false); return await chn.SendMessageAsync(content).ConfigureAwait(false); } /// /// Sends a direct message to this member. Creates a direct message channel if one does not exist already. /// /// Embed to attach to the message. /// The sent message. /// Thrown when the member has the bot blocked, the member is no longer in the guild, or if the member has Allow DM from server members off. /// Thrown when the member does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public async Task SendMessageAsync(DiscordEmbed embed) { if (this.IsBot && this.Discord.CurrentUser.IsBot) throw new ArgumentException("Bots cannot DM each other."); var chn = await this.CreateDmChannelAsync().ConfigureAwait(false); return await chn.SendMessageAsync(embed).ConfigureAwait(false); } /// /// Sends a direct message to this member. Creates a direct message channel if one does not exist already. /// /// Content of the message to send. /// Embed to attach to the message. /// The sent message. /// Thrown when the member has the bot blocked, the member is no longer in the guild, or if the member has Allow DM from server members off. /// Thrown when the member does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public async Task SendMessageAsync(string content, DiscordEmbed embed) { if (this.IsBot && this.Discord.CurrentUser.IsBot) throw new ArgumentException("Bots cannot DM each other."); var chn = await this.CreateDmChannelAsync().ConfigureAwait(false); return await chn.SendMessageAsync(content, embed).ConfigureAwait(false); } /// /// Sends a direct message to this member. Creates a direct message channel if one does not exist already. /// /// Builder to with the message. /// The sent message. /// Thrown when the member has the bot blocked, the member is no longer in the guild, or if the member has Allow DM from server members off. /// Thrown when the member does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public async Task SendMessageAsync(DiscordMessageBuilder message) { if (this.IsBot && this.Discord.CurrentUser.IsBot) throw new ArgumentException("Bots cannot DM each other."); var chn = await this.CreateDmChannelAsync().ConfigureAwait(false); return await chn.SendMessageAsync(message).ConfigureAwait(false); } /// /// Sets this member's voice mute status. /// /// Whether the member is to be muted. /// Reason for audit logs. /// /// Thrown when the client does not have the permission. /// Thrown when the member does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task SetMuteAsync(bool mute, string reason = null) - => this.Discord.ApiClient.ModifyGuildMemberAsync(this._guild_id, this.Id, default, default, mute, default, default, reason); + => this.Discord.ApiClient.ModifyGuildMemberAsync(this.GuildId, this.Id, default, default, mute, default, default, reason); /// /// Sets this member's voice deaf status. /// /// Whether the member is to be deafened. /// Reason for audit logs. /// /// Thrown when the client does not have the permission. /// Thrown when the member does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task SetDeafAsync(bool deaf, string reason = null) - => this.Discord.ApiClient.ModifyGuildMemberAsync(this._guild_id, this.Id, default, default, default, deaf, default, reason); + => this.Discord.ApiClient.ModifyGuildMemberAsync(this.GuildId, this.Id, default, default, default, deaf, default, reason); /// /// Modifies this member. /// /// Action to perform on this member. /// /// Thrown when the client does not have the permission. /// Thrown when the member does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public async Task ModifyAsync(Action action) { var mdl = new MemberEditModel(); action(mdl); if (mdl.VoiceChannel.HasValue && mdl.VoiceChannel.Value != null && mdl.VoiceChannel.Value.Type != ChannelType.Voice && mdl.VoiceChannel.Value.Type != ChannelType.Stage) throw new ArgumentException("Given channel is not a voice or stage channel.", nameof(mdl.VoiceChannel)); if (mdl.Nickname.HasValue && this.Discord.CurrentUser.Id == this.Id) { await this.Discord.ApiClient.ModifyCurrentMemberNicknameAsync(this.Guild.Id, mdl.Nickname.Value, mdl.AuditLogReason).ConfigureAwait(false); await this.Discord.ApiClient.ModifyGuildMemberAsync(this.Guild.Id, this.Id, Optional.FromNoValue(), mdl.Roles.IfPresent(e => e.Select(xr => xr.Id)), mdl.Muted, mdl.Deafened, mdl.VoiceChannel.IfPresent(e => e?.Id), mdl.AuditLogReason).ConfigureAwait(false); } else { await this.Discord.ApiClient.ModifyGuildMemberAsync(this.Guild.Id, this.Id, mdl.Nickname, mdl.Roles.IfPresent(e => e.Select(xr => xr.Id)), mdl.Muted, mdl.Deafened, mdl.VoiceChannel.IfPresent(e => e?.Id), mdl.AuditLogReason).ConfigureAwait(false); } } /// /// Adds a timeout to a member. /// /// The datetime offset to time out the user. Up to 28 days. /// Reason for audit logs. /// /// Thrown when the client does not have the permission. /// Thrown when the member does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task TimeoutAsync(DateTimeOffset until, string reason = null) => until.Subtract(DateTimeOffset.UtcNow).Days > 28 ? throw new ArgumentException("Timeout can not be longer than 28 days") : this.Discord.ApiClient.ModifyTimeoutAsync(this.Guild.Id, this.Id, until, reason); /// /// Adds a timeout to a member. /// /// The timespan to time out the user. Up to 28 days. /// Reason for audit logs. /// /// Thrown when the client does not have the permission. /// Thrown when the member does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task TimeoutAsync(TimeSpan until, string reason = null) => this.TimeoutAsync(DateTimeOffset.UtcNow + until, reason); /// /// Adds a timeout to a member. /// /// The datetime to time out the user. Up to 28 days. /// Reason for audit logs. /// /// Thrown when the client does not have the permission. /// Thrown when the member does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task TimeoutAsync(DateTime until, string reason = null) => this.TimeoutAsync(until.ToUniversalTime() - DateTime.UtcNow, reason); /// /// Removes the timeout from a member. /// /// Reason for audit logs. /// /// Thrown when the client does not have the permission. /// Thrown when the member does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task RemoveTimeoutAsync(string reason = null) => this.Discord.ApiClient.ModifyTimeoutAsync(this.Guild.Id, this.Id, null, reason); /// /// Grants a role to the member. /// /// Role to grant. /// Reason for audit logs. /// /// Thrown when the client does not have the permission. /// Thrown when the member does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task GrantRoleAsync(DiscordRole role, string reason = null) => this.Discord.ApiClient.AddGuildMemberRoleAsync(this.Guild.Id, this.Id, role.Id, reason); /// /// Revokes a role from a member. /// /// Role to revoke. /// Reason for audit logs. /// /// Thrown when the client does not have the permission. /// Thrown when the member does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task RevokeRoleAsync(DiscordRole role, string reason = null) => this.Discord.ApiClient.RemoveGuildMemberRoleAsync(this.Guild.Id, this.Id, role.Id, reason); /// /// Sets the member's roles to ones specified. /// /// Roles to set. /// Reason for audit logs. /// /// Thrown when the client does not have the permission. /// Thrown when the member does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task ReplaceRolesAsync(IEnumerable roles, string reason = null) => this.Discord.ApiClient.ModifyGuildMemberAsync(this.Guild.Id, this.Id, default, new Optional>(roles.Select(xr => xr.Id)), default, default, default, reason); /// /// Bans this member from their guild. /// - /// How many days to remove messages from. + /// How many days to remove messages from. /// Reason for audit logs. /// /// Thrown when the client does not have the permission. /// Thrown when the member does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. - public Task BanAsync(int delete_message_days = 0, string reason = null) - => this.Guild.BanMemberAsync(this, delete_message_days, reason); + public Task BanAsync(int deleteMessageDays = 0, string reason = null) + => this.Guild.BanMemberAsync(this, deleteMessageDays, reason); /// /// Unbans this member from their guild. /// /// Thrown when the client does not have the permission. /// Thrown when the member does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task UnbanAsync(string reason = null) => this.Guild.UnbanMemberAsync(this, reason); /// /// Kicks this member from their guild. /// /// Reason for audit logs. /// /// [alias="KickAsync"] /// Thrown when the client does not have the permission. /// Thrown when the member does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task RemoveAsync(string reason = null) - => this.Discord.ApiClient.RemoveGuildMemberAsync(this._guild_id, this.Id, reason); + => this.Discord.ApiClient.RemoveGuildMemberAsync(this.GuildId, this.Id, reason); /// /// Moves this member to the specified voice channel /// /// /// /// Thrown when the client does not have the permission. /// Thrown when the member does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task PlaceInAsync(DiscordChannel channel) => channel.PlaceMemberAsync(this); /// /// Updates the member's suppress state in a stage channel. /// /// The channel the member is currently in. /// Toggles the member's suppress state. /// Thrown when the channel in not a voice channel. public async Task UpdateVoiceStateAsync(DiscordChannel channel, bool? suppress) { if (channel.Type != ChannelType.Stage) throw new ArgumentException("Voice state can only be updated in a stage channel."); await this.Discord.ApiClient.UpdateUserVoiceStateAsync(this.Guild.Id, this.Id, channel.Id, suppress).ConfigureAwait(false); } /// /// Makes the user a speaker. /// /// Thrown when the user is not inside an stage channel. /// Thrown when the client does not have the permission. /// Thrown when the member does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public async Task MakeSpeakerAsync() { var vs = this.VoiceState; if (vs == null || vs.Channel.Type != ChannelType.Stage) throw new ArgumentException("Voice state can only be updated when the user is inside an stage channel."); await this.Discord.ApiClient.UpdateUserVoiceStateAsync(this.Guild.Id, this.Id, vs.Channel.Id, false).ConfigureAwait(false); } /// /// Moves the user to audience. /// /// Thrown when the user is not inside an stage channel. /// Thrown when the client does not have the permission. /// Thrown when the member does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public async Task MoveToAudienceAsync() { var vs = this.VoiceState; if (vs == null || vs.Channel.Type != ChannelType.Stage) throw new ArgumentException("Voice state can only be updated when the user is inside an stage channel."); await this.Discord.ApiClient.UpdateUserVoiceStateAsync(this.Guild.Id, this.Id, vs.Channel.Id, true).ConfigureAwait(false); } /// /// Calculates permissions in a given channel for this member. /// /// Channel to calculate permissions for. /// Calculated permissions for this member in the channel. public Permissions PermissionsIn(DiscordChannel channel) => channel.PermissionsFor(this); /// /// Get's the current member's roles based on the sum of the permissions of their given roles. /// private Permissions GetPermissions() { if (this.Guild.OwnerId == this.Id) - return PermissionMethods.FULL_PERMS; + return PermissionMethods.FullPerms; Permissions perms; // assign @everyone permissions var everyoneRole = this.Guild.EveryoneRole; perms = everyoneRole.Permissions; // assign permissions from member's roles (in order) perms |= this.Roles.Aggregate(Permissions.None, (c, role) => c | role.Permissions); // Adminstrator grants all permissions and cannot be overridden - return (perms & Permissions.Administrator) == Permissions.Administrator ? PermissionMethods.FULL_PERMS : perms; + return (perms & Permissions.Administrator) == Permissions.Administrator ? PermissionMethods.FullPerms : perms; } /// /// Returns a string representation of this member. /// /// String representation of this member. public override string ToString() => $"Member {this.Id}; {this.Username}#{this.Discriminator} ({this.DisplayName})"; /// /// Checks whether this is equal to another object. /// /// Object to compare to. /// Whether the object is equal to this . public override bool Equals(object obj) => this.Equals(obj as DiscordMember); /// /// Checks whether this is equal to another . /// /// to compare to. /// Whether the is equal to this . - public bool Equals(DiscordMember e) => e is not null && (ReferenceEquals(this, e) || (this.Id == e.Id && this._guild_id == e._guild_id)); + public bool Equals(DiscordMember e) => e is not null && (ReferenceEquals(this, e) || (this.Id == e.Id && this.GuildId == e.GuildId)); /// /// Gets the hash code for this . /// /// The hash code for this . public override int GetHashCode() { var hash = 13; hash = (hash * 7) + this.Id.GetHashCode(); - hash = (hash * 7) + this._guild_id.GetHashCode(); + hash = (hash * 7) + this.GuildId.GetHashCode(); return hash; } /// /// Gets whether the two objects are equal. /// /// First member to compare. /// Second member to compare. /// Whether the two members are equal. public static bool operator ==(DiscordMember e1, DiscordMember e2) { var o1 = e1 as object; var o2 = e2 as object; - return (o1 != null || o2 == null) && (o1 == null || o2 != null) && ((o1 == null && o2 == null) || (e1.Id == e2.Id && e1._guild_id == e2._guild_id)); + return (o1 != null || o2 == null) && (o1 == null || o2 != null) && ((o1 == null && o2 == null) || (e1.Id == e2.Id && e1.GuildId == e2.GuildId)); } /// /// Gets whether the two objects are not equal. /// /// First member to compare. /// Second member to compare. /// Whether the two members are not equal. public static bool operator !=(DiscordMember e1, DiscordMember e2) => !(e1 == e2); } } diff --git a/DisCatSharp/Entities/Guild/DiscordRole.cs b/DisCatSharp/Entities/Guild/DiscordRole.cs index 340b52ed0..7502db80d 100644 --- a/DisCatSharp/Entities/Guild/DiscordRole.cs +++ b/DisCatSharp/Entities/Guild/DiscordRole.cs @@ -1,276 +1,276 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System; using System.Globalization; using System.Linq; using System.Threading.Tasks; using DisCatSharp.Enums; using DisCatSharp.Net; using DisCatSharp.Net.Abstractions; using DisCatSharp.Net.Models; using Newtonsoft.Json; namespace DisCatSharp.Entities { /// /// Represents a discord role, to which users can be assigned. /// public class DiscordRole : SnowflakeObject, IEquatable { /// /// Gets the name of this role. /// [JsonProperty("name", NullValueHandling = NullValueHandling.Ignore)] public string Name { get; internal set; } /// /// Gets the color of this role. /// [JsonIgnore] public DiscordColor Color - => new(this._color); + => new(this.ColorInternal); [JsonProperty("color", NullValueHandling = NullValueHandling.Ignore)] - internal int _color; + internal int ColorInternal; /// /// Gets whether this role is hoisted. /// [JsonProperty("hoist", NullValueHandling = NullValueHandling.Ignore)] public bool IsHoisted { get; internal set; } /// /// Gets the position of this role in the role hierarchy. /// [JsonProperty("position", NullValueHandling = NullValueHandling.Ignore)] public int Position { get; internal set; } /// /// Gets the permissions set for this role. /// [JsonProperty("permissions", NullValueHandling = NullValueHandling.Ignore)] public Permissions Permissions { get; internal set; } /// /// Gets whether this role is managed by an integration. /// [JsonProperty("managed", NullValueHandling = NullValueHandling.Ignore)] public bool IsManaged { get; internal set; } /// /// Gets whether this role is mentionable. /// [JsonProperty("mentionable", NullValueHandling = NullValueHandling.Ignore)] public bool IsMentionable { get; internal set; } /// /// Gets the tags this role has. /// [JsonProperty("tags", NullValueHandling = NullValueHandling.Ignore)] public DiscordRoleTags Tags { get; internal set; } /// /// Gets the role icon's hash. /// [JsonProperty("icon", NullValueHandling = NullValueHandling.Ignore)] public string IconHash { get; internal set; } /// /// Gets the role icon's url. /// [JsonIgnore] public string IconUrl => !string.IsNullOrWhiteSpace(this.IconHash) ? $"{DiscordDomain.GetDomain(CoreDomain.DiscordCdn).Url}{Endpoints.ROLE_ICONS}/{this.Id.ToString(CultureInfo.InvariantCulture)}/{this.IconHash}.png?size=64" : null; /// /// Gets the role unicode_emoji. /// [JsonProperty("unicode_emoji", NullValueHandling = NullValueHandling.Ignore)] - internal string _unicodeEmojiString; + internal string UnicodeEmojiString; /// /// Gets the unicode emoji. /// public DiscordEmoji UnicodeEmoji - => this._unicodeEmojiString != null ? DiscordEmoji.FromName(this.Discord, $":{this._unicodeEmojiString}:", false) : null; + => this.UnicodeEmojiString != null ? DiscordEmoji.FromName(this.Discord, $":{this.UnicodeEmojiString}:", false) : null; [JsonIgnore] - internal ulong _guild_id = 0; + internal ulong GuildId = 0; /// /// Gets a mention string for this role. If the role is mentionable, this string will mention all the users that belong to this role. /// public string Mention => Formatter.Mention(this); #region Methods /// /// Modifies this role's position. /// /// New position /// Reason why we moved it /// /// Thrown when the client does not have the permission. /// Thrown when the role does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task ModifyPositionAsync(int position, string reason = null) { - var roles = this.Discord.Guilds[this._guild_id].Roles.Values.OrderByDescending(xr => xr.Position).ToArray(); + var roles = this.Discord.Guilds[this.GuildId].Roles.Values.OrderByDescending(xr => xr.Position).ToArray(); var pmds = new RestGuildRoleReorderPayload[roles.Length]; for (var i = 0; i < roles.Length; i++) { pmds[i] = new RestGuildRoleReorderPayload { RoleId = roles[i].Id }; pmds[i].Position = roles[i].Id == this.Id ? position : roles[i].Position <= position ? roles[i].Position - 1 : roles[i].Position; } - return this.Discord.ApiClient.ModifyGuildRolePositionAsync(this._guild_id, pmds, reason); + return this.Discord.ApiClient.ModifyGuildRolePositionAsync(this.GuildId, pmds, reason); } /// /// Updates this role. /// /// New role name /// New role permissions /// New role color /// New role hoist /// Whether this role is mentionable /// Reason why we made this change /// /// Thrown when the client does not have the permission. /// Thrown when the role does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task ModifyAsync(string name = null, Permissions? permissions = null, DiscordColor? color = null, bool? hoist = null, bool? mentionable = null, string reason = null) - => this.Discord.ApiClient.ModifyGuildRoleAsync(this._guild_id, this.Id, name, permissions, color?.Value, hoist, mentionable, null, null, reason); + => this.Discord.ApiClient.ModifyGuildRoleAsync(this.GuildId, this.Id, name, permissions, color?.Value, hoist, mentionable, null, null, reason); /// /// Updates this role. /// /// The action. /// Thrown when the client does not have the permission. /// Thrown when the role does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task ModifyAsync(Action action) { var mdl = new RoleEditModel(); action(mdl); var iconb64 = Optional.FromNoValue(); var emoji = Optional.FromNoValue(); - var can_continue = true; + var canContinue = true; if (mdl.Icon.HasValue || mdl.UnicodeEmoji.HasValue) - can_continue = this.Discord.Guilds[this._guild_id].Features.CanSetRoleIcons; + canContinue = this.Discord.Guilds[this.GuildId].Features.CanSetRoleIcons; if (mdl.Icon.HasValue && mdl.Icon.Value != null) using (var imgtool = new ImageTool(mdl.Icon.Value)) iconb64 = imgtool.GetBase64(); else if (mdl.Icon.HasValue) iconb64 = null; if (mdl.UnicodeEmoji.HasValue && mdl.UnicodeEmoji.Value != null) emoji = mdl.UnicodeEmoji.Value.Id == 0 ? mdl.UnicodeEmoji.Value.Name : throw new ArgumentException("Emoji must be unicode"); else if (mdl.UnicodeEmoji.HasValue) emoji = null; - return can_continue ? this.Discord.ApiClient.ModifyGuildRoleAsync(this._guild_id, this.Id, mdl.Name, mdl.Permissions, mdl.Color?.Value, mdl.Hoist, mdl.Mentionable, iconb64, emoji, mdl.AuditLogReason) : throw new NotSupportedException($"Cannot modify role icon. Guild needs boost tier two."); + return canContinue ? this.Discord.ApiClient.ModifyGuildRoleAsync(this.GuildId, this.Id, mdl.Name, mdl.Permissions, mdl.Color?.Value, mdl.Hoist, mdl.Mentionable, iconb64, emoji, mdl.AuditLogReason) : throw new NotSupportedException($"Cannot modify role icon. Guild needs boost tier two."); } /// /// Deletes this role. /// /// Reason as to why this role has been deleted. /// /// Thrown when the client does not have the permission. /// Thrown when the role does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. - public Task DeleteAsync(string reason = null) => this.Discord.ApiClient.DeleteRoleAsync(this._guild_id, this.Id, reason); + public Task DeleteAsync(string reason = null) => this.Discord.ApiClient.DeleteRoleAsync(this.GuildId, this.Id, reason); #endregion /// /// Initializes a new instance of the class. /// internal DiscordRole() { } /// /// Checks whether this role has specific permissions. /// /// Permissions to check for. /// Whether the permissions are allowed or not. public PermissionLevel CheckPermission(Permissions permission) => (this.Permissions & permission) != 0 ? PermissionLevel.Allowed : PermissionLevel.Unset; /// /// Returns a string representation of this role. /// /// String representation of this role. public override string ToString() => $"Role {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 DiscordRole); /// /// Checks whether this is equal to another . /// /// to compare to. /// Whether the is equal to this . public bool Equals(DiscordRole e) => e switch { null => false, _ => 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 role to compare. /// Second role to compare. /// Whether the two roles are equal. public static bool operator ==(DiscordRole e1, DiscordRole e2) => e1 is null == e2 is null && ((e1 is null && e2 is null) || e1.Id == e2.Id); /// /// Gets whether the two objects are not equal. /// /// First role to compare. /// Second role to compare. /// Whether the two roles are not equal. public static bool operator !=(DiscordRole e1, DiscordRole e2) => !(e1 == e2); } } diff --git a/DisCatSharp/Entities/Guild/DiscordRoleTags.cs b/DisCatSharp/Entities/Guild/DiscordRoleTags.cs index c2d890bd9..946bf8432 100644 --- a/DisCatSharp/Entities/Guild/DiscordRoleTags.cs +++ b/DisCatSharp/Entities/Guild/DiscordRoleTags.cs @@ -1,55 +1,55 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using Newtonsoft.Json; namespace DisCatSharp.Entities { /// /// Represents a discord role tags. /// public class DiscordRoleTags { /// /// Gets the id of the bot this role belongs to. /// [JsonProperty("bot_id", NullValueHandling = NullValueHandling.Ignore)] public ulong? BotId { get; internal set; } /// /// Gets the id of the integration this role belongs to. /// [JsonProperty("integration_id", NullValueHandling = NullValueHandling.Ignore)] public ulong? IntegrationId { get; internal set; } /// /// Gets whether this is the guild's premium subscriber role. /// [JsonIgnore] public bool IsPremiumSubscriber - => this._premiumSubscriber.HasValue && this._premiumSubscriber.Value is null; + => this.PremiumSubscriber.HasValue && this.PremiumSubscriber.Value is null; [JsonProperty("premium_subscriber", NullValueHandling = NullValueHandling.Include)] - internal Optional _premiumSubscriber = false; + internal Optional PremiumSubscriber = false; } } diff --git a/DisCatSharp/Entities/Guild/ScheduledEvent/DiscordScheduledEvent.cs b/DisCatSharp/Entities/Guild/ScheduledEvent/DiscordScheduledEvent.cs index 58c5d4281..66bea02ec 100644 --- a/DisCatSharp/Entities/Guild/ScheduledEvent/DiscordScheduledEvent.cs +++ b/DisCatSharp/Entities/Guild/ScheduledEvent/DiscordScheduledEvent.cs @@ -1,328 +1,328 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System; using System.Collections.Generic; using System.Globalization; using System.Threading.Tasks; using DisCatSharp.Net.Models; using Newtonsoft.Json; namespace DisCatSharp.Entities { /// /// Represents an scheduled event. /// public class DiscordScheduledEvent : SnowflakeObject, IEquatable { /// /// Gets the guild id of the associated scheduled event. /// [JsonProperty("guild_id", NullValueHandling = NullValueHandling.Ignore)] public ulong GuildId { get; internal set; } /// /// Gets the guild to which this scheduled event belongs. /// [JsonIgnore] public DiscordGuild Guild => this.Discord.Guilds.TryGetValue(this.GuildId, out var guild) ? guild : null; /// /// Gets the associated channel. /// [JsonIgnore] public Task Channel => this.ChannelId.HasValue ? this.Discord.ApiClient.GetChannelAsync(this.ChannelId.Value) : null; /// /// Gets id of the associated channel id. /// [JsonProperty("channel_id", NullValueHandling = NullValueHandling.Ignore)] public ulong? ChannelId { get; internal set; } /// /// Gets the ID of the user that created the scheduled event. /// [JsonProperty("creator_id")] public ulong CreatorId { get; internal set; } /// /// Gets the user that created the scheduled event. /// [JsonProperty("creator")] public DiscordUser Creator { get; internal set; } /// /// Gets the member that created the scheduled event. /// [JsonIgnore] public DiscordMember CreatorMember - => this.Guild._members.TryGetValue(this.CreatorId, out var owner) + => this.Guild.MembersInternal.TryGetValue(this.CreatorId, out var owner) ? owner : this.Discord.ApiClient.GetGuildMemberAsync(this.GuildId, this.CreatorId).Result; /// /// Gets the name of the scheduled event. /// [JsonProperty("name", NullValueHandling = NullValueHandling.Ignore)] public string Name { get; internal set; } /// /// Gets the description of the scheduled event. /// [JsonProperty("description", NullValueHandling = NullValueHandling.Ignore)] public string Description { get; internal set; } /* TODO|INFO: Is not available yet to users / clients / bots. /// /// Gets this event's cover hash, when applicable. /// [JsonProperty("image", NullValueHandling = NullValueHandling.Ignore)] public string ImageHash { get; internal set; } /// /// Gets this event's cover in url form. /// [JsonIgnore] public string ImageUrl => !string.IsNullOrWhiteSpace(this.ImageHash) ? $"{DiscordDomain.GetDomain(CoreDomain.DiscordCdn).Uri}{Endpoints.EVENTS}/{this.GuildId.ToString(CultureInfo.InvariantCulture)}/images/{this.ImageHash}.{(this.ImageHash.StartsWith("a_") ? "gif" : "png")}" : null; */ /// /// Gets the scheduled start time of the scheduled event. /// [JsonIgnore] public DateTimeOffset? ScheduledStartTime => !string.IsNullOrWhiteSpace(this.ScheduledStartTimeRaw) && DateTimeOffset.TryParse(this.ScheduledStartTimeRaw, CultureInfo.InvariantCulture, DateTimeStyles.None, out var dto) ? dto : null; /// /// Gets the scheduled start time of the scheduled event as raw string. /// [JsonProperty("scheduled_start_time", NullValueHandling = NullValueHandling.Ignore)] internal string ScheduledStartTimeRaw { get; set; } /// /// Gets the scheduled end time of the scheduled event. /// [JsonIgnore] public DateTimeOffset? ScheduledEndTime => !string.IsNullOrWhiteSpace(this.ScheduledEndTimeRaw) && DateTimeOffset.TryParse(this.ScheduledEndTimeRaw, CultureInfo.InvariantCulture, DateTimeStyles.None, out var dto) ? dto : null; /// /// Gets the scheduled end time of the scheduled event as raw string. /// [JsonProperty("scheduled_end_time", NullValueHandling = NullValueHandling.Ignore)] internal string ScheduledEndTimeRaw { get; set; } /// /// Gets the privacy level of the scheduled event. /// [JsonProperty("privacy_level", NullValueHandling = NullValueHandling.Ignore)] internal ScheduledEventPrivacyLevel PrivacyLevel { get; set; } /// /// Gets the status of the scheduled event. /// [JsonProperty("status", NullValueHandling = NullValueHandling.Ignore)] public ScheduledEventStatus Status { get; internal set; } /// /// Gets the entity type. /// [JsonProperty("entity_type", NullValueHandling = NullValueHandling.Ignore)] public ScheduledEventEntityType EntityType { get; internal set; } /// /// Gets id of the entity. /// [JsonProperty("entity_id", NullValueHandling = NullValueHandling.Ignore)] public ulong? EntityId { get; internal set; } /// /// Gets metadata of the entity. /// [JsonProperty("entity_metadata", NullValueHandling = NullValueHandling.Ignore)] public DiscordScheduledEventEntityMetadata EntityMetadata { get; internal set; } /* This isn't used. * See https://github.com/discord/discord-api-docs/pull/3586#issuecomment-969066061. * Was originally for paid stages. /// /// Gets the sku ids of the scheduled event. /// [JsonProperty("sku_ids", NullValueHandling = NullValueHandling.Ignore)] public IReadOnlyList SkuIds { get; internal set; }*/ /// /// Gets the total number of users subscribed to the scheduled event. /// [JsonProperty("user_count", NullValueHandling = NullValueHandling.Ignore)] public int UserCount { get; internal set; } /// /// Initializes a new instance of the class. /// internal DiscordScheduledEvent() { } #region Methods /// /// Modifies the current scheduled event. /// /// Action to perform on this thread /// Thrown when the client does not have the permission. /// Thrown when the event 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 ScheduledEventEditModel(); action(mdl); var channelId = Optional.FromNoValue(); if (mdl.Channel.HasValue && (mdl.Channel.Value.Type != ChannelType.Voice || mdl.Channel.Value.Type != ChannelType.Stage) && mdl.Channel.Value != null) throw new ArgumentException("Channel needs to be a voice or stage channel."); else if (mdl.Channel.HasValue && mdl.Channel.Value != null) channelId = mdl.Channel.Value.Id; if (this.EntityType != ScheduledEventEntityType.External && mdl.EntityType == ScheduledEventEntityType.External) channelId = null; var description = Optional.FromNoValue(); if (mdl.Description.HasValue && mdl.Description.Value != null) description = mdl.Description; else if (mdl.Description.HasValue) description = null; var scheduledEndTime = Optional.FromNoValue(); if (mdl.ScheduledEndTime.HasValue && mdl.ScheduledEndTime.Value != null && mdl.EntityType.HasValue ? mdl.EntityType == ScheduledEventEntityType.External : this.EntityType == ScheduledEventEntityType.External) scheduledEndTime = mdl.ScheduledEndTime.Value; await this.Discord.ApiClient.ModifyGuildScheduledEventAsync(this.GuildId, this.Id, channelId, this.EntityType == ScheduledEventEntityType.External ? new DiscordScheduledEventEntityMetadata(mdl.Location.Value) : null, mdl.Name, mdl.ScheduledStartTime, scheduledEndTime, description, mdl.EntityType, mdl.Status, mdl.AuditLogReason); } /// /// Starts the current scheduled event. /// /// Thrown when the client does not have the permission. /// Thrown when the event does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public async Task StartAsync(string reason = null) => this.Status == ScheduledEventStatus.Scheduled ? await this.Discord.ApiClient.ModifyGuildScheduledEventStatusAsync(this.GuildId, this.Id, ScheduledEventStatus.Active, reason) : throw new InvalidOperationException("You can only start scheduled events"); /// /// Cancels the current scheduled event. /// /// The audit log reason. /// Thrown when the client does not have the permission. /// Thrown when the event does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public async Task CancelAsync(string reason = null) => this.Status == ScheduledEventStatus.Scheduled ? await this.Discord.ApiClient.ModifyGuildScheduledEventStatusAsync(this.GuildId, this.Id, ScheduledEventStatus.Canceled, reason) : throw new InvalidOperationException("You can only cancel scheduled events"); /// /// Ends the current scheduled event. /// /// The audit log reason. /// Thrown when the client does not have the permission. /// Thrown when the event does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public async Task EndAsync(string reason = null) => this.Status == ScheduledEventStatus.Active ? await this.Discord.ApiClient.ModifyGuildScheduledEventStatusAsync(this.GuildId, this.Id, ScheduledEventStatus.Completed, reason) : throw new InvalidOperationException("You can only stop active events"); /// /// Gets a list of users RSVP'd to the scheduled event. /// /// The limit how many users to receive from the event. /// Get results of before the given snowflake. /// Get results of after the given snowflake. /// Wether to include guild member data. /// Thrown when the client does not have the correct permissions. /// Thrown when the event does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public async Task> GetUsersAsync(int? limit = null, ulong? before = null, ulong? after = null, bool? withMember = null) - => await this.Discord.ApiClient.GetGuildScheduledEventRSPVUsersAsync(this.GuildId, this.Id, limit, before, after, withMember); + => await this.Discord.ApiClient.GetGuildScheduledEventRspvUsersAsync(this.GuildId, this.Id, limit, before, after, withMember); /// /// Deletes a scheduled event. /// /// The audit log reason. /// Thrown when the client does not have the permission. /// Thrown when the event does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public async Task DeleteAsync(string reason = null) => await this.Discord.ApiClient.DeleteGuildScheduledEventAsync(this.GuildId, this.Id, reason); #endregion /// /// Checks whether this is equal to another object. /// /// Object to compare to. /// Whether the object is equal to this . public override bool Equals(object obj) => this.Equals(obj as DiscordScheduledEvent); /// /// Checks whether this is equal to another . /// /// to compare to. /// Whether the is equal to this . public bool Equals(DiscordScheduledEvent 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 event to compare. /// Second ecent to compare. /// Whether the two events are equal. public static bool operator ==(DiscordScheduledEvent e1, DiscordScheduledEvent 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 event to compare. /// Second event to compare. /// Whether the two events are not equal. public static bool operator !=(DiscordScheduledEvent e1, DiscordScheduledEvent e2) => !(e1 == e2); } } diff --git a/DisCatSharp/Entities/Interaction/Components/Text/DiscordTextComponent.cs b/DisCatSharp/Entities/Interaction/Components/Text/DiscordTextComponent.cs index 9dc786d69..b91e57fbb 100644 --- a/DisCatSharp/Entities/Interaction/Components/Text/DiscordTextComponent.cs +++ b/DisCatSharp/Entities/Interaction/Components/Text/DiscordTextComponent.cs @@ -1,153 +1,153 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using DisCatSharp.Enums; using Newtonsoft.Json; namespace DisCatSharp.Entities { /// /// Represents a text component that can be submitted. Fires event when submitted. /// public sealed class DiscordTextComponent : DiscordComponent { /// /// The style of the text component. /// [JsonProperty("style", NullValueHandling = NullValueHandling.Ignore)] public TextComponentStyle Style { get; internal set; } /// /// The text description to apply to the text component. /// [JsonProperty("label", NullValueHandling = NullValueHandling.Ignore)] public string Label { get; internal set; } /// /// The placeholder for the text component. /// [JsonProperty("placeholder", NullValueHandling = NullValueHandling.Ignore)] public string Placeholder { get; internal set; } /// /// The pre-filled value for the text component. /// [JsonProperty("value", NullValueHandling = NullValueHandling.Ignore)] public string Value { get; internal set; } /// /// The minimal length of text input. /// [JsonProperty("min_length", NullValueHandling = NullValueHandling.Ignore)] public int? MinLength { get; internal set; } /// /// The maximal length of text input. /// [JsonProperty("max_length", NullValueHandling = NullValueHandling.Ignore)] public int? MaxLength { get; internal set; } // NOTE: Probably will be introdruced in future /*/// /// Whether this text component can be used. /// [JsonProperty("disabled", NullValueHandling = NullValueHandling.Ignore)] public bool Disabled { get; internal set; }*/ /// /// Whether this text component is required. /// [JsonProperty("required", NullValueHandling = NullValueHandling.Ignore)] public bool Required { get; internal set; } /*/// /// Enables this component if it was disabled before. /// /// The current component. public DiscordTextComponent Enable() { this.Disabled = false; return this; } /// /// Disables this component. /// /// The current component. public DiscordTextComponent Disable() { this.Disabled = true; return this; }*/ /// /// Constructs a new . /// internal DiscordTextComponent() { this.Type = ComponentType.InputText; } /// /// Constucts a new text component based on another text component. /// /// The button to copy. public DiscordTextComponent(DiscordTextComponent other) : this() { this.CustomId = other.CustomId; this.Style = other.Style; this.Label = other.Label; //this.Disabled = other.Disabled; this.MinLength = other.MinLength; this.MaxLength = other.MaxLength; this.Placeholder = other.Placeholder; this.Required = other.Required; this.Value = other.Value; } /// /// Constructs a new text component field with the specified options. /// /// The style of the text component. /// The Id to assign to the text component. This is sent back when a user presses it. /// The text to display before the text component, up to 80 characters. /// The placeholder for the text input. /// The minimal length of text input. /// The maximal length of text input. /// Whether this text component should be required. /// Pre-filled value for text field. - public DiscordTextComponent(TextComponentStyle style, string customId, string label, string placeholder = null, int? minLength = null, int? maxLength = null, bool required = true, string default_value = null) + public DiscordTextComponent(TextComponentStyle style, string customId, string label, string placeholder = null, int? minLength = null, int? maxLength = null, bool required = true, string defaultValue = null) { this.Style = style; this.Label = label; this.CustomId = customId; this.MinLength = minLength; this.MaxLength = maxLength; this.Placeholder = placeholder; //this.Disabled = disabled; this.Required = required; - this.Value = default_value; + this.Value = defaultValue; this.Type = ComponentType.InputText; } } } diff --git a/DisCatSharp/Entities/Interaction/DiscordFollowupMessageBuilder.cs b/DisCatSharp/Entities/Interaction/DiscordFollowupMessageBuilder.cs index 56644d382..6f64d0ee5 100644 --- a/DisCatSharp/Entities/Interaction/DiscordFollowupMessageBuilder.cs +++ b/DisCatSharp/Entities/Interaction/DiscordFollowupMessageBuilder.cs @@ -1,315 +1,315 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System; using System.Collections.Generic; using System.IO; using System.Linq; namespace DisCatSharp.Entities { /// /// Constructs a followup message to an interaction. /// public sealed class DiscordFollowupMessageBuilder { /// /// Whether this followup message is text-to-speech. /// - public bool IsTTS { get; set; } + public bool IsTts { get; set; } /// /// Whether this followup message should be ephemeral. /// public bool IsEphemeral { get; set; } /// /// Indicates this message is emphemeral. /// internal int? Flags => this.IsEphemeral ? 64 : null; /// /// Message to send on followup message. /// public string Content { get => this._content; set { if (value != null && value.Length > 2000) throw new ArgumentException("Content length cannot exceed 2000 characters.", nameof(value)); this._content = value; } } private string _content; /// /// Embeds to send on followup message. /// public IReadOnlyList Embeds => this._embeds; private readonly List _embeds = new(); /// /// Files to send on this followup message. /// public IReadOnlyList Files => this._files; private readonly List _files = new(); /// /// Components to send on this followup message. /// public IReadOnlyList Components => this._components; private readonly List _components = new(); /// /// Mentions to send on this followup message. /// public IReadOnlyList Mentions => this._mentions; private readonly List _mentions = new(); /// /// Appends a collection of components to the message. /// /// The collection of components to add. /// The builder to chain calls with. /// contained more than 5 components. public DiscordFollowupMessageBuilder AddComponents(params DiscordComponent[] components) => this.AddComponents((IEnumerable)components); /// /// Appends several rows of components to the message /// /// The rows of components to add, holding up to five each. /// public DiscordFollowupMessageBuilder AddComponents(IEnumerable components) { var ara = components.ToArray(); if (ara.Length + this._components.Count > 5) throw new ArgumentException("ActionRow count exceeds maximum of five."); foreach (var ar in ara) this._components.Add(ar); return this; } /// /// Appends a collection of components to the message. /// /// The collection of components to add. /// The builder to chain calls with. /// contained more than 5 components. public DiscordFollowupMessageBuilder AddComponents(IEnumerable components) { var compArr = components.ToArray(); var count = compArr.Length; if (count > 5) throw new ArgumentException("Cannot add more than 5 components per action row!"); var arc = new DiscordActionRowComponent(compArr); this._components.Add(arc); return this; } /// /// Indicates if the followup message must use text-to-speech. /// /// Text-to-speech /// The builder to chain calls with. - public DiscordFollowupMessageBuilder WithTTS(bool tts) + public DiscordFollowupMessageBuilder WithTts(bool tts) { - this.IsTTS = tts; + this.IsTts = tts; return this; } /// /// Sets the message to send with the followup message.. /// /// Message to send. /// The builder to chain calls with. public DiscordFollowupMessageBuilder WithContent(string content) { this.Content = content; return this; } /// /// Adds an embed to the followup message. /// /// Embed to add. /// The builder to chain calls with. public DiscordFollowupMessageBuilder AddEmbed(DiscordEmbed embed) { this._embeds.Add(embed); return this; } /// /// Adds the given embeds to the followup message. /// /// Embeds to add. /// The builder to chain calls with. public DiscordFollowupMessageBuilder AddEmbeds(IEnumerable embeds) { this._embeds.AddRange(embeds); return this; } /// /// Adds a file to the followup message. /// /// Name of the file. /// File data. /// Tells the API Client to reset the stream position to what it was after the file is sent. /// Description of the file. /// The builder to chain calls with. public DiscordFollowupMessageBuilder AddFile(string filename, Stream data, bool resetStreamPosition = false, string description = null) { if (this.Files.Count >= 10) throw new ArgumentException("Cannot send more than 10 files with a single message."); if (this._files.Any(x => x.FileName == filename)) throw new ArgumentException("A File with that filename already exists"); if (resetStreamPosition) this._files.Add(new DiscordMessageFile(filename, data, data.Position, description: description)); else this._files.Add(new DiscordMessageFile(filename, data, null, description: description)); return this; } /// /// Sets if the message has files to be sent. /// /// The Stream to the file. /// Tells the API Client to reset the stream position to what it was after the file is sent. /// Description of the file. /// The builder to chain calls with. public DiscordFollowupMessageBuilder AddFile(FileStream stream, bool resetStreamPosition = false, string description = null) { if (this.Files.Count >= 10) throw new ArgumentException("Cannot send more than 10 files with a single message."); if (this._files.Any(x => x.FileName == stream.Name)) throw new ArgumentException("A File with that filename already exists"); if (resetStreamPosition) this._files.Add(new DiscordMessageFile(stream.Name, stream, stream.Position, description: description)); else this._files.Add(new DiscordMessageFile(stream.Name, stream, null, description: description)); return this; } /// /// Adds the given files to the followup message. /// /// Dictionary of file name and file data. /// Tells the API Client to reset the stream position to what it was after the file is sent. /// The builder to chain calls with. public DiscordFollowupMessageBuilder AddFiles(Dictionary files, bool resetStreamPosition = false) { if (this.Files.Count + files.Count > 10) throw new ArgumentException("Cannot send more than 10 files with a single message."); foreach (var file in files) { if (this._files.Any(x => x.FileName == file.Key)) throw new ArgumentException("A File with that filename already exists"); if (resetStreamPosition) this._files.Add(new DiscordMessageFile(file.Key, file.Value, file.Value.Position)); else this._files.Add(new DiscordMessageFile(file.Key, file.Value, null)); } return this; } /// /// Adds the mention to the mentions to parse, etc. with the followup message. /// /// Mention to add. /// The builder to chain calls with. public DiscordFollowupMessageBuilder AddMention(IMention mention) { this._mentions.Add(mention); return this; } /// /// Adds the mentions to the mentions to parse, etc. with the followup message. /// /// Mentions to add. /// The builder to chain calls with. public DiscordFollowupMessageBuilder AddMentions(IEnumerable mentions) { this._mentions.AddRange(mentions); return this; } /// /// Sets the followup message to be ephemeral. /// /// Ephemeral. /// The builder to chain calls with. public DiscordFollowupMessageBuilder AsEphemeral(bool ephemeral) { this.IsEphemeral = ephemeral; return this; } /// /// Clears all message components on this builder. /// public void ClearComponents() => this._components.Clear(); /// /// Allows for clearing the Followup Message builder so that it can be used again to send a new message. /// public void Clear() { this.Content = ""; this._embeds.Clear(); - this.IsTTS = false; + this.IsTts = false; this._mentions.Clear(); this._files.Clear(); this.IsEphemeral = false; this._components.Clear(); } /// /// Validates the builder. /// internal void Validate() { if (this.Files?.Count == 0 && string.IsNullOrEmpty(this.Content) && !this.Embeds.Any()) throw new ArgumentException("You must specify content, an embed, or at least one file."); } } } diff --git a/DisCatSharp/Entities/Interaction/DiscordInteraction.cs b/DisCatSharp/Entities/Interaction/DiscordInteraction.cs index 18164ed05..5d20d64a3 100644 --- a/DisCatSharp/Entities/Interaction/DiscordInteraction.cs +++ b/DisCatSharp/Entities/Interaction/DiscordInteraction.cs @@ -1,219 +1,219 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System; using System.Threading.Tasks; using Newtonsoft.Json; namespace DisCatSharp.Entities { /// /// Represents an interaction that was invoked. /// public sealed class DiscordInteraction : SnowflakeObject { /// /// Gets the type of interaction invoked. /// [JsonProperty("type")] public InteractionType Type { get; internal set; } /// /// Gets the command data for this interaction. /// [JsonProperty("data", NullValueHandling = NullValueHandling.Ignore)] public DiscordInteractionData Data { get; internal set; } /// /// Gets the Id of the guild that invoked this interaction. /// [JsonIgnore] public ulong? GuildId { get; internal set; } /// /// Gets the guild that invoked this interaction. /// [JsonIgnore] public DiscordGuild Guild => (this.Discord as DiscordClient).InternalGetCachedGuild(this.GuildId); /// /// Gets the Id of the channel that invoked this interaction. /// [JsonIgnore] public ulong ChannelId { get; internal set; } /// /// Gets the channel that invoked this interaction. /// [JsonIgnore] public DiscordChannel Channel => (this.Discord as DiscordClient).InternalGetCachedChannel(this.ChannelId) ?? (DiscordChannel)(this.Discord as DiscordClient).InternalGetCachedThread(this.ChannelId) ?? (this.Guild == null ? new DiscordDmChannel { Id = this.ChannelId, Type = ChannelType.Private, Discord = this.Discord } : null); /// /// Gets the user that invoked this interaction. /// This can be cast to a if created in a guild. /// [JsonIgnore] public DiscordUser User { get; internal set; } /// /// Gets the continuation token for responding to this interaction. /// [JsonProperty("token")] public string Token { get; internal set; } /// /// Gets the version number for this interaction type. /// [JsonProperty("version")] public int Version { get; internal set; } /// /// Gets the ID of the application that created this interaction. /// [JsonProperty("application_id")] public ulong ApplicationId { get; internal set; } /// /// The message this interaction was created with, if any. /// [JsonProperty("message")] internal DiscordMessage Message { get; set; } /// /// Gets the invoking user locale. /// [JsonProperty("locale", NullValueHandling = NullValueHandling.Ignore)] public string Locale { get; internal set; } /// /// Gets the guild locale if applicable. /// [JsonProperty("guild_locale", NullValueHandling = NullValueHandling.Ignore)] public string GuildLocale { get; internal set; } /// /// Creates a response to this interaction. /// /// The type of the response. /// The data, if any, to send. public Task CreateResponseAsync(InteractionResponseType type, DiscordInteractionResponseBuilder builder = null) => this.Discord.ApiClient.CreateInteractionResponseAsync(this.Id, this.Token, type, builder); /// /// Creates a modal response to this interaction. /// /// The data to send. public Task CreateInteractionModalResponseAsync(DiscordInteractionModalBuilder builder) => this.Type != InteractionType.Ping && this.Type != InteractionType.ModalSubmit ? this.Discord.ApiClient.CreateInteractionModalResponseAsync(this.Id, this.Token, InteractionResponseType.Modal, builder) : throw new NotSupportedException("You can't respond to an PING with a modal."); /// /// Gets the original interaction response. /// /// The origingal message that was sent. This does not work on ephemeral messages. public Task GetOriginalResponseAsync() => this.Discord.ApiClient.GetOriginalInteractionResponseAsync(this.Discord.CurrentApplication.Id, this.Token); /// /// Edits the original interaction response. /// /// The webhook builder. /// The edited . public async Task EditOriginalResponseAsync(DiscordWebhookBuilder builder) { builder.Validate(isInteractionResponse: true); - if (builder._keepAttachments.HasValue && builder._keepAttachments.Value) + if (builder.KeepAttachmentsInternal.HasValue && builder.KeepAttachmentsInternal.Value) { var attachments = this.Discord.ApiClient.GetOriginalInteractionResponseAsync(this.Discord.CurrentApplication.Id, this.Token).Result.Attachments; if (attachments?.Count > 0) { - builder._attachments.AddRange(attachments); + builder.AttachmentsInternal.AddRange(attachments); } } - else if (builder._keepAttachments.HasValue) + else if (builder.KeepAttachmentsInternal.HasValue) { - builder._attachments.Clear(); + builder.AttachmentsInternal.Clear(); } return await this.Discord.ApiClient.EditOriginalInteractionResponseAsync(this.Discord.CurrentApplication.Id, this.Token, builder).ConfigureAwait(false); } /// /// Deletes the original interaction response. /// > public Task DeleteOriginalResponseAsync() => this.Discord.ApiClient.DeleteOriginalInteractionResponseAsync(this.Discord.CurrentApplication.Id, this.Token); /// /// Creates a follow up message to this interaction. /// /// The webhook builder. /// The created . public async Task CreateFollowupMessageAsync(DiscordFollowupMessageBuilder builder) { builder.Validate(); return await this.Discord.ApiClient.CreateFollowupMessageAsync(this.Discord.CurrentApplication.Id, this.Token, builder).ConfigureAwait(false); } /// /// Gets a follow up message. /// /// The id of the follow up message. public Task GetFollowupMessageAsync(ulong messageId) => this.Discord.ApiClient.GetFollowupMessageAsync(this.Discord.CurrentApplication.Id, this.Token, messageId); /// /// Edits a follow up message. /// /// The id of the follow up message. /// The webhook builder. /// The edited . public async Task EditFollowupMessageAsync(ulong messageId, DiscordWebhookBuilder builder) { builder.Validate(isFollowup: true); - if (builder._keepAttachments.HasValue && builder._keepAttachments.Value) + if (builder.KeepAttachmentsInternal.HasValue && builder.KeepAttachmentsInternal.Value) { var attachments = this.Discord.ApiClient.GetFollowupMessageAsync(this.Discord.CurrentApplication.Id, this.Token, messageId).Result.Attachments; if (attachments?.Count > 0) { - builder._attachments.AddRange(attachments); + builder.AttachmentsInternal.AddRange(attachments); } } - else if (builder._keepAttachments.HasValue) + else if (builder.KeepAttachmentsInternal.HasValue) { - builder._attachments.Clear(); + builder.AttachmentsInternal.Clear(); } return await this.Discord.ApiClient.EditFollowupMessageAsync(this.Discord.CurrentApplication.Id, this.Token, messageId, builder).ConfigureAwait(false); } /// /// Deletes a follow up message. /// /// The id of the follow up message. public Task DeleteFollowupMessageAsync(ulong messageId) => this.Discord.ApiClient.DeleteFollowupMessageAsync(this.Discord.CurrentApplication.Id, this.Token, messageId); } } diff --git a/DisCatSharp/Entities/Interaction/DiscordInteractionApplicationCommandCallbackData.cs b/DisCatSharp/Entities/Interaction/DiscordInteractionApplicationCommandCallbackData.cs index 97d34f519..3e7dd8419 100644 --- a/DisCatSharp/Entities/Interaction/DiscordInteractionApplicationCommandCallbackData.cs +++ b/DisCatSharp/Entities/Interaction/DiscordInteractionApplicationCommandCallbackData.cs @@ -1,105 +1,105 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System.Collections.Generic; using Newtonsoft.Json; namespace DisCatSharp.Entities { /// /// Represants a interactions application command callback data. /// internal class DiscordInteractionApplicationCommandCallbackData { /// /// Wheter this message is tts /// [JsonProperty("tts", NullValueHandling = NullValueHandling.Ignore)] - public bool? IsTTS { get; internal set; } + public bool? IsTts { get; internal set; } /// /// Gets the content. /// [JsonProperty("content", NullValueHandling = NullValueHandling.Ignore)] public string Content { get; internal set; } /// /// Gets the embeds. /// [JsonProperty("embeds", NullValueHandling = NullValueHandling.Ignore)] public IEnumerable Embeds { get; internal set; } /// /// Gets the mentions. /// [JsonProperty("allowed_mentions", NullValueHandling = NullValueHandling.Ignore)] public IEnumerable Mentions { get; internal set; } /// /// Gets the flags. /// [JsonProperty("flags", NullValueHandling = NullValueHandling.Ignore)] public MessageFlags? Flags { get; internal set; } /// /// Gets the components. /// [JsonProperty("components", NullValueHandling = NullValueHandling.Ignore)] public IReadOnlyCollection Components { get; internal set; } /// /// Gets the autocomplete choices. /// [JsonProperty("choices", NullValueHandling = NullValueHandling.Ignore)] public IReadOnlyCollection Choices { get; internal set; } /// /// Gets the attachments. /// [JsonProperty("attachments", NullValueHandling = NullValueHandling.Ignore)] public List Attachments { get; set; } } /// /// Represants a interactions application command callback data. /// internal class DiscordInteractionApplicationCommandModalCallbackData { /// /// Gets the custom id. /// [JsonProperty("custom_id", NullValueHandling = NullValueHandling.Ignore)] public string CustomId { get; internal set; } /// /// Gets the content. /// [JsonProperty("title", NullValueHandling = NullValueHandling.Ignore)] public string Title { get; internal set; } /// /// Gets the components. /// [JsonProperty("components", NullValueHandling = NullValueHandling.Ignore)] public IReadOnlyCollection ModalComponents { get; internal set; } } } diff --git a/DisCatSharp/Entities/Interaction/DiscordInteractionResponseBuilder.cs b/DisCatSharp/Entities/Interaction/DiscordInteractionResponseBuilder.cs index 2f5026df5..d1bcf6bc1 100644 --- a/DisCatSharp/Entities/Interaction/DiscordInteractionResponseBuilder.cs +++ b/DisCatSharp/Entities/Interaction/DiscordInteractionResponseBuilder.cs @@ -1,353 +1,353 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System; using System.Collections.Generic; using System.IO; using System.Linq; namespace DisCatSharp.Entities { /// /// Constructs an interaction response. /// public sealed class DiscordInteractionResponseBuilder { /// /// Whether this interaction response is text-to-speech. /// - public bool IsTTS { get; set; } + public bool IsTts { get; set; } /// /// Whether this interaction response should be ephemeral. /// public bool IsEphemeral { get; set; } /// /// Content of the message to send. /// public string Content { get => this._content; set { if (value != null && value.Length > 2000) throw new ArgumentException("Content length cannot exceed 2000 characters.", nameof(value)); this._content = value; } } private string _content; /// /// Embeds to send on this interaction response. /// public IReadOnlyList Embeds => this._embeds; private readonly List _embeds = new(); /// /// Files to send on this interaction response. /// public IReadOnlyList Files => this._files; private readonly List _files = new(); /// /// Components to send on this interaction response. /// public IReadOnlyList Components => this._components; private readonly List _components = new(); /// /// The choices to send on this interaction response. /// Mutually exclusive with content, embed, and components. /// public IReadOnlyList Choices => this._choices; private readonly List _choices = new(); /// /// Mentions to send on this interaction response. /// public IEnumerable Mentions => this._mentions; private readonly List _mentions = new(); /// /// Constructs a new empty interaction response builder. /// public DiscordInteractionResponseBuilder() { } /// /// Constructs a new based on an existing . /// /// The builder to copy. public DiscordInteractionResponseBuilder(DiscordMessageBuilder builder) { this._content = builder.Content; this._mentions = builder.Mentions; this._embeds.AddRange(builder.Embeds); this._components.AddRange(builder.Components); } /// /// Appends a collection of components to the builder. Each call will append to a new row. /// /// The components to append. Up to five. /// The current builder to chain calls with. /// Thrown when passing more than 5 components. public DiscordInteractionResponseBuilder AddComponents(params DiscordComponent[] components) => this.AddComponents((IEnumerable)components); /// /// Appends several rows of components to the message /// /// The rows of components to add, holding up to five each. /// public DiscordInteractionResponseBuilder AddComponents(IEnumerable components) { var ara = components.ToArray(); if (ara.Length + this._components.Count > 5) throw new ArgumentException("ActionRow count exceeds maximum of five."); foreach (var ar in ara) this._components.Add(ar); return this; } /// /// Appends a collection of components to the builder. Each call will append to a new row. /// /// The components to append. Up to five. /// The current builder to chain calls with. /// Thrown when passing more than 5 components. public DiscordInteractionResponseBuilder AddComponents(IEnumerable components) { var compArr = components.ToArray(); var count = compArr.Length; if (count > 5) throw new ArgumentException("Cannot add more than 5 components per action row!"); var arc = new DiscordActionRowComponent(compArr); this._components.Add(arc); return this; } /// /// Indicates if the interaction response will be text-to-speech. /// /// Text-to-speech - public DiscordInteractionResponseBuilder WithTTS(bool tts) + public DiscordInteractionResponseBuilder WithTts(bool tts) { - this.IsTTS = tts; + this.IsTts = tts; return this; } /// /// Sets the interaction response to be ephemeral. /// /// Ephemeral. public DiscordInteractionResponseBuilder AsEphemeral(bool ephemeral) { this.IsEphemeral = ephemeral; return this; } /// /// Sets the content of the message to send. /// /// Content to send. public DiscordInteractionResponseBuilder WithContent(string content) { this.Content = content; return this; } /// /// Adds an embed to send with the interaction response. /// /// Embed to add. public DiscordInteractionResponseBuilder AddEmbed(DiscordEmbed embed) { if (embed != null) this._embeds.Add(embed); // Interactions will 400 silently // return this; } /// /// Adds the given embeds to send with the interaction response. /// /// Embeds to add. public DiscordInteractionResponseBuilder AddEmbeds(IEnumerable embeds) { this._embeds.AddRange(embeds); return this; } /// /// Adds a file to the interaction response. /// /// Name of the file. /// File data. /// Tells the API Client to reset the stream position to what it was after the file is sent. /// Description of the file. /// The builder to chain calls with. public DiscordInteractionResponseBuilder AddFile(string filename, Stream data, bool resetStreamPosition = false, string description = null) { if (this.Files.Count >= 10) throw new ArgumentException("Cannot send more than 10 files with a single message."); if (this._files.Any(x => x.FileName == filename)) throw new ArgumentException("A File with that filename already exists"); if (resetStreamPosition) this._files.Add(new DiscordMessageFile(filename, data, data.Position, description: description)); else this._files.Add(new DiscordMessageFile(filename, data, null, description: description)); return this; } /// /// Sets if the message has files to be sent. /// /// The Stream to the file. /// Tells the API Client to reset the stream position to what it was after the file is sent. /// Description of the file. /// The builder to chain calls with. public DiscordInteractionResponseBuilder AddFile(FileStream stream, bool resetStreamPosition = false, string description = null) { if (this.Files.Count >= 10) throw new ArgumentException("Cannot send more than 10 files with a single message."); if (this._files.Any(x => x.FileName == stream.Name)) throw new ArgumentException("A File with that filename already exists"); if (resetStreamPosition) this._files.Add(new DiscordMessageFile(stream.Name, stream, stream.Position, description: description)); else this._files.Add(new DiscordMessageFile(stream.Name, stream, null, description: description)); return this; } /// /// Adds the given files to the interaction response builder. /// /// Dictionary of file name and file data. /// Tells the API Client to reset the stream position to what it was after the file is sent. /// The builder to chain calls with. public DiscordInteractionResponseBuilder AddFiles(Dictionary files, bool resetStreamPosition = false) { if (this.Files.Count + files.Count > 10) throw new ArgumentException("Cannot send more than 10 files with a single message."); foreach (var file in files) { if (this._files.Any(x => x.FileName == file.Key)) throw new ArgumentException("A File with that filename already exists"); if (resetStreamPosition) this._files.Add(new DiscordMessageFile(file.Key, file.Value, file.Value.Position)); else this._files.Add(new DiscordMessageFile(file.Key, file.Value, null)); } return this; } /// /// Adds the mention to the mentions to parse, etc. with the interaction response. /// /// Mention to add. public DiscordInteractionResponseBuilder AddMention(IMention mention) { this._mentions.Add(mention); return this; } /// /// Adds the mentions to the mentions to parse, etc. with the interaction response. /// /// Mentions to add. public DiscordInteractionResponseBuilder AddMentions(IEnumerable mentions) { this._mentions.AddRange(mentions); return this; } /// /// Adds a single auto-complete choice to the builder. /// /// The choice to add. /// The current builder to chain calls with. public DiscordInteractionResponseBuilder AddAutoCompleteChoice(DiscordApplicationCommandAutocompleteChoice choice) { this._choices.Add(choice); return this; } /// /// Adds auto-complete choices to the builder. /// /// The choices to add. /// The current builder to chain calls with. public DiscordInteractionResponseBuilder AddAutoCompleteChoices(IEnumerable choices) { this._choices.AddRange(choices); return this; } /// /// Adds auto-complete choices to the builder. /// /// The choices to add. /// The current builder to chain calls with. public DiscordInteractionResponseBuilder AddAutoCompleteChoices(params DiscordApplicationCommandAutocompleteChoice[] choices) => this.AddAutoCompleteChoices((IEnumerable)choices); /// /// Clears all message components on this builder. /// public void ClearComponents() => this._components.Clear(); /// /// Allows for clearing the Interaction Response Builder so that it can be used again to send a new response. /// public void Clear() { this.Content = ""; this._embeds.Clear(); - this.IsTTS = false; + this.IsTts = false; this.IsEphemeral = false; this._mentions.Clear(); this._components.Clear(); this._choices.Clear(); this._files.Clear(); } } } diff --git a/DisCatSharp/Entities/Invite/DiscordInviteStage.cs b/DisCatSharp/Entities/Invite/DiscordInviteStage.cs index 64d7b2a25..aea6280f6 100644 --- a/DisCatSharp/Entities/Invite/DiscordInviteStage.cs +++ b/DisCatSharp/Entities/Invite/DiscordInviteStage.cs @@ -1,71 +1,71 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System.Collections.Concurrent; using System.Collections.Generic; using DisCatSharp.Net.Serialization; using Newtonsoft.Json; namespace DisCatSharp.Entities { /// /// Represents a stage instance to which the user is invited. /// public class DiscordInviteStage : SnowflakeObject { /// /// Gets the members speaking in the Stage. /// [JsonIgnore] public IReadOnlyDictionary Members { get; internal set; } [JsonProperty("members", NullValueHandling = NullValueHandling.Ignore)] [JsonConverter(typeof(SnowflakeArrayAsDictionaryJsonConverter))] - internal ConcurrentDictionary _members = new(); + internal ConcurrentDictionary MembersInternal = new(); /// /// Gets the number of users in the Stage. /// [JsonProperty("participant_count", NullValueHandling = NullValueHandling.Ignore)] public int ParticipantCount { get; internal set; } /// /// Gets the number of users speaking in the Stage. /// [JsonProperty("speaker_count", NullValueHandling = NullValueHandling.Ignore)] public int SpeakerCount { get; internal set; } /// /// Gets the topic of the Stage instance. /// [JsonProperty("topic", NullValueHandling = NullValueHandling.Ignore)] public string Topic { get; internal set; } /// /// Initializes a new instance of the class. /// internal DiscordInviteStage() { - this.Members = new ReadOnlyConcurrentDictionary(this._members); + this.Members = new ReadOnlyConcurrentDictionary(this.MembersInternal); } } } diff --git a/DisCatSharp/Entities/Message/DiscordMessage.cs b/DisCatSharp/Entities/Message/DiscordMessage.cs index f70db07c3..ca659b4ae 100644 --- a/DisCatSharp/Entities/Message/DiscordMessage.cs +++ b/DisCatSharp/Entities/Message/DiscordMessage.cs @@ -1,888 +1,888 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Globalization; using System.Linq; using System.Threading.Tasks; using Newtonsoft.Json; namespace DisCatSharp.Entities { /// /// Represents a Discord text message. /// public class DiscordMessage : SnowflakeObject, IEquatable { /// /// Initializes a new instance of the class. /// internal DiscordMessage() { - this._attachmentsLazy = new Lazy>(() => new ReadOnlyCollection(this._attachments)); - this._embedsLazy = new Lazy>(() => new ReadOnlyCollection(this._embeds)); - this._mentionedChannelsLazy = new Lazy>(() => this._mentionedChannels != null - ? new ReadOnlyCollection(this._mentionedChannels) + this._attachmentsLazy = new Lazy>(() => new ReadOnlyCollection(this.AttachmentsInternal)); + this._embedsLazy = new Lazy>(() => new ReadOnlyCollection(this.EmbedsInternal)); + this._mentionedChannelsLazy = new Lazy>(() => this.MentionedChannelsInternal != null + ? new ReadOnlyCollection(this.MentionedChannelsInternal) : Array.Empty()); - this._mentionedRolesLazy = new Lazy>(() => this._mentionedRoles != null ? new ReadOnlyCollection(this._mentionedRoles) : Array.Empty()); - this._mentionedUsersLazy = new Lazy>(() => new ReadOnlyCollection(this._mentionedUsers)); - this._reactionsLazy = new Lazy>(() => new ReadOnlyCollection(this._reactions)); - this._stickersLazy = new Lazy>(() => new ReadOnlyCollection(this._stickers)); + this._mentionedRolesLazy = new Lazy>(() => this.MentionedRolesInternal != null ? new ReadOnlyCollection(this.MentionedRolesInternal) : Array.Empty()); + this.MentionedUsersLazy = new Lazy>(() => new ReadOnlyCollection(this.MentionedUsersInternal)); + this._reactionsLazy = new Lazy>(() => new ReadOnlyCollection(this.ReactionsInternal)); + this._stickersLazy = new Lazy>(() => new ReadOnlyCollection(this.StickersInternal)); this._jumpLink = new Lazy(() => { var gid = this.Channel != null ? this.Channel is DiscordDmChannel ? "@me" : this.Channel.GuildId.Value.ToString(CultureInfo.InvariantCulture) : this.InternalThread.GuildId.Value.ToString(CultureInfo.InvariantCulture); var cid = this.ChannelId.ToString(CultureInfo.InvariantCulture); var mid = this.Id.ToString(CultureInfo.InvariantCulture); return new Uri($"https://{(this.Discord.Configuration.UseCanary ? "canary.discord.com" : "discord.com")}/channels/{gid}/{cid}/{mid}"); }); } /// /// Initializes a new instance of the class. /// /// The other. internal DiscordMessage(DiscordMessage other) : this() { this.Discord = other.Discord; - this._attachments = other._attachments; // the attachments cannot change, thus no need to copy and reallocate. - this._embeds = new List(other._embeds); + this.AttachmentsInternal = other.AttachmentsInternal; // the attachments cannot change, thus no need to copy and reallocate. + this.EmbedsInternal = new List(other.EmbedsInternal); - if (other._mentionedChannels != null) - this._mentionedChannels = new List(other._mentionedChannels); - if (other._mentionedRoles != null) - this._mentionedRoles = new List(other._mentionedRoles); - if (other._mentionedRoleIds != null) - this._mentionedRoleIds = new List(other._mentionedRoleIds); - this._mentionedUsers = new List(other._mentionedUsers); - this._reactions = new List(other._reactions); - this._stickers = new List(other._stickers); + if (other.MentionedChannelsInternal != null) + this.MentionedChannelsInternal = new List(other.MentionedChannelsInternal); + if (other.MentionedRolesInternal != null) + this.MentionedRolesInternal = new List(other.MentionedRolesInternal); + if (other.MentionedRoleIds != null) + this.MentionedRoleIds = new List(other.MentionedRoleIds); + this.MentionedUsersInternal = new List(other.MentionedUsersInternal); + this.ReactionsInternal = new List(other.ReactionsInternal); + this.StickersInternal = new List(other.StickersInternal); this.Author = other.Author; this.ChannelId = other.ChannelId; this.Content = other.Content; this.EditedTimestampRaw = other.EditedTimestampRaw; this.Id = other.Id; - this.IsTTS = other.IsTTS; + this.IsTts = other.IsTts; this.MessageType = other.MessageType; this.Pinned = other.Pinned; this.TimestampRaw = other.TimestampRaw; this.WebhookId = other.WebhookId; } /// /// Gets the channel in which the message was sent. /// [JsonIgnore] public DiscordChannel Channel { get => (this.Discord as DiscordClient)?.InternalGetCachedChannel(this.ChannelId) ?? this._channel; internal set => this._channel = value; } private DiscordChannel _channel; /// /// Gets the thread in which the message was sent. /// [JsonIgnore] private DiscordThreadChannel InternalThread { get => (this.Discord as DiscordClient)?.InternalGetCachedThread(this.ChannelId) ?? this._thread; set => this._thread = value; } private DiscordThreadChannel _thread; /// /// Gets the ID of the channel in which the message was sent. /// [JsonProperty("channel_id", NullValueHandling = NullValueHandling.Ignore)] public ulong ChannelId { get; internal set; } /// /// Gets the components this message was sent with. /// [JsonProperty("components", NullValueHandling = NullValueHandling.Ignore)] public IReadOnlyCollection Components { get; internal set; } /// /// Gets the user or member that sent the message. /// [JsonProperty("author", NullValueHandling = NullValueHandling.Ignore)] public DiscordUser Author { get; internal set; } /// /// Gets the message's content. /// [JsonProperty("content", NullValueHandling = NullValueHandling.Ignore)] public string Content { get; internal set; } /// /// Gets the message's creation timestamp. /// [JsonIgnore] public DateTimeOffset Timestamp => !string.IsNullOrWhiteSpace(this.TimestampRaw) && DateTimeOffset.TryParse(this.TimestampRaw, CultureInfo.InvariantCulture, DateTimeStyles.None, out var dto) ? dto : this.CreationTimestamp; /// /// Gets the message's creation timestamp as raw string. /// [JsonProperty("timestamp", NullValueHandling = NullValueHandling.Ignore)] internal string TimestampRaw { get; set; } /// /// Gets the message's edit timestamp. Will be null if the message was not edited. /// [JsonIgnore] public DateTimeOffset? EditedTimestamp => !string.IsNullOrWhiteSpace(this.EditedTimestampRaw) && DateTimeOffset.TryParse(this.EditedTimestampRaw, CultureInfo.InvariantCulture, DateTimeStyles.None, out var dto) ? (DateTimeOffset?)dto : null; /// /// Gets the message's edit timestamp as raw string. Will be null if the message was not edited. /// [JsonProperty("edited_timestamp", NullValueHandling = NullValueHandling.Ignore)] internal string EditedTimestampRaw { get; set; } /// /// Gets whether this message was edited. /// [JsonIgnore] public bool IsEdited => !string.IsNullOrWhiteSpace(this.EditedTimestampRaw); /// /// Gets whether the message is a text-to-speech message. /// [JsonProperty("tts", NullValueHandling = NullValueHandling.Ignore)] - public bool IsTTS { get; internal set; } + public bool IsTts { get; internal set; } /// /// Gets whether the message mentions everyone. /// [JsonProperty("mention_everyone", NullValueHandling = NullValueHandling.Ignore)] public bool MentionEveryone { get; internal set; } /// /// Gets users or members mentioned by this message. /// [JsonIgnore] public IReadOnlyList MentionedUsers - => this._mentionedUsersLazy.Value; + => this.MentionedUsersLazy.Value; [JsonProperty("mentions", NullValueHandling = NullValueHandling.Ignore)] - internal List _mentionedUsers; + internal List MentionedUsersInternal; [JsonIgnore] - internal readonly Lazy> _mentionedUsersLazy; + internal readonly Lazy> MentionedUsersLazy; // TODO this will probably throw an exception in DMs since it tries to wrap around a null List... // this is probably low priority but need to find out a clean way to solve it... /// /// Gets roles mentioned by this message. /// [JsonIgnore] public IReadOnlyList MentionedRoles => this._mentionedRolesLazy.Value; [JsonIgnore] - internal List _mentionedRoles; + internal List MentionedRolesInternal; [JsonProperty("mention_roles")] - internal List _mentionedRoleIds; + internal List MentionedRoleIds; [JsonIgnore] private readonly Lazy> _mentionedRolesLazy; /// /// Gets channels mentioned by this message. /// [JsonIgnore] public IReadOnlyList MentionedChannels => this._mentionedChannelsLazy.Value; [JsonIgnore] - internal List _mentionedChannels; + internal List MentionedChannelsInternal; [JsonIgnore] private readonly Lazy> _mentionedChannelsLazy; /// /// Gets files attached to this message. /// [JsonIgnore] public IReadOnlyList Attachments => this._attachmentsLazy.Value; [JsonProperty("attachments", NullValueHandling = NullValueHandling.Ignore)] - internal List _attachments = new(); + internal List AttachmentsInternal = new(); [JsonIgnore] private readonly Lazy> _attachmentsLazy; /// /// Gets embeds attached to this message. /// [JsonIgnore] public IReadOnlyList Embeds => this._embedsLazy.Value; [JsonProperty("embeds", NullValueHandling = NullValueHandling.Ignore)] - internal List _embeds = new(); + internal List EmbedsInternal = new(); [JsonIgnore] private readonly Lazy> _embedsLazy; /// /// Gets reactions used on this message. /// [JsonIgnore] public IReadOnlyList Reactions => this._reactionsLazy.Value; [JsonProperty("reactions", NullValueHandling = NullValueHandling.Ignore)] - internal List _reactions = new(); + internal List ReactionsInternal = new(); [JsonIgnore] private readonly Lazy> _reactionsLazy; /* /// /// Gets the nonce sent with the message, if the message was sent by the client. /// [JsonProperty("nonce", NullValueHandling = NullValueHandling.Ignore)] public ulong? Nonce { get; internal set; } */ /// /// Gets whether the message is pinned. /// [JsonProperty("pinned", NullValueHandling = NullValueHandling.Ignore)] public bool Pinned { get; internal set; } /// /// Gets the id of the webhook that generated this message. /// [JsonProperty("webhook_id", NullValueHandling = NullValueHandling.Ignore)] public ulong? WebhookId { get; internal set; } /// /// Gets the type of the message. /// [JsonProperty("type", NullValueHandling = NullValueHandling.Ignore)] public MessageType? MessageType { get; internal set; } /// /// Gets the message activity in the Rich Presence embed. /// [JsonProperty("activity", NullValueHandling = NullValueHandling.Ignore)] public DiscordMessageActivity Activity { get; internal set; } /// /// Gets the message application in the Rich Presence embed. /// [JsonProperty("application", NullValueHandling = NullValueHandling.Ignore)] public DiscordMessageApplication Application { get; internal set; } /// /// Gets the message application id in the Rich Presence embed. /// [JsonProperty("application_id", NullValueHandling = NullValueHandling.Ignore)] public ulong ApplicationId { get; internal set; } /// /// Gets the internal reference. /// [JsonProperty("message_reference", NullValueHandling = NullValueHandling.Ignore)] internal InternalDiscordMessageReference? InternalReference { get; set; } /// /// Gets the original message reference from the crossposted message. /// [JsonIgnore] public DiscordMessageReference Reference => this.InternalReference.HasValue ? this?.InternalBuildMessageReference() : null; /// /// Gets the bitwise flags for this message. /// [JsonProperty("flags", NullValueHandling = NullValueHandling.Ignore)] public MessageFlags? Flags { get; internal set; } /// /// Gets whether the message originated from a webhook. /// [JsonIgnore] public bool WebhookMessage => this.WebhookId != null; /// /// Gets the jump link to this message. /// [JsonIgnore] public Uri JumpLink => this._jumpLink.Value; private readonly Lazy _jumpLink; /// /// Gets stickers for this message. /// [JsonIgnore] public IReadOnlyList Stickers => this._stickersLazy.Value; [JsonProperty("sticker_items", NullValueHandling = NullValueHandling.Ignore)] - internal List _stickers = new(); + internal List StickersInternal = new(); [JsonIgnore] private readonly Lazy> _stickersLazy; /// /// Gets the guild id. /// [JsonProperty("guild_id", NullValueHandling = NullValueHandling.Ignore)] internal ulong? GuildId { get; set; } /// /// Gets the message object for the referenced message /// [JsonProperty("referenced_message", NullValueHandling = NullValueHandling.Ignore)] public DiscordMessage ReferencedMessage { get; internal set; } /// /// Gets whether the message is a response to an interaction. /// [JsonProperty("interaction", NullValueHandling = NullValueHandling.Ignore)] public DiscordMessageInteraction Interaction { get; internal set; } /// /// Gets the thread that was started from this message. /// [JsonProperty("thread", NullValueHandling = NullValueHandling.Ignore)] public DiscordThreadChannel Thread { get; internal set; } /// /// Build the message reference. /// internal DiscordMessageReference InternalBuildMessageReference() { var client = this.Discord as DiscordClient; var guildId = this.InternalReference.Value.GuildId; var channelId = this.InternalReference.Value.ChannelId; var messageId = this.InternalReference.Value.MessageId; var reference = new DiscordMessageReference(); if (guildId.HasValue) - reference.Guild = client._guilds.TryGetValue(guildId.Value, out var g) + reference.Guild = client.GuildsInternal.TryGetValue(guildId.Value, out var g) ? g : new DiscordGuild { Id = guildId.Value, Discord = client }; var channel = client.InternalGetCachedChannel(channelId.Value); if (channel == null) { reference.Channel = new DiscordChannel { Id = channelId.Value, Discord = client }; if (guildId.HasValue) reference.Channel.GuildId = guildId.Value; } else reference.Channel = channel; if (client.MessageCache != null && client.MessageCache.TryGet(m => m.Id == messageId.Value && m.ChannelId == channelId, out var msg)) reference.Message = msg; else { reference.Message = new DiscordMessage { ChannelId = this.ChannelId, Discord = client }; if (messageId.HasValue) reference.Message.Id = messageId.Value; } return reference; } /// /// Gets the mentions. /// /// An array of IMentions. private IMention[] GetMentions() { var mentions = new List(); - if (this.ReferencedMessage != null && this._mentionedUsers.Contains(this.ReferencedMessage.Author)) + if (this.ReferencedMessage != null && this.MentionedUsersInternal.Contains(this.ReferencedMessage.Author)) mentions.Add(new RepliedUserMention()); // Return null to allow all mentions - if (this._mentionedUsers.Any()) - mentions.AddRange(this._mentionedUsers.Select(m => (IMention)new UserMention(m))); + if (this.MentionedUsersInternal.Any()) + mentions.AddRange(this.MentionedUsersInternal.Select(m => (IMention)new UserMention(m))); - if (this._mentionedRoleIds.Any()) - mentions.AddRange(this._mentionedRoleIds.Select(r => (IMention)new RoleMention(r))); + if (this.MentionedRoleIds.Any()) + mentions.AddRange(this.MentionedRoleIds.Select(r => (IMention)new RoleMention(r))); return mentions.ToArray(); } /// /// Populates the mentions. /// internal void PopulateMentions() { var guild = this.Channel?.Guild; - this._mentionedUsers ??= new List(); - this._mentionedRoles ??= new List(); - this._mentionedChannels ??= new List(); + this.MentionedUsersInternal ??= new List(); + this.MentionedRolesInternal ??= new List(); + this.MentionedChannelsInternal ??= new List(); var mentionedUsers = new HashSet(new DiscordUserComparer()); if (guild != null) { - foreach (var usr in this._mentionedUsers) + foreach (var usr in this.MentionedUsersInternal) { usr.Discord = this.Discord; this.Discord.UserCache.AddOrUpdate(usr.Id, usr, (id, old) => { old.Username = usr.Username; old.Discriminator = usr.Discriminator; old.AvatarHash = usr.AvatarHash; return old; }); - mentionedUsers.Add(guild._members.TryGetValue(usr.Id, out var member) ? member : usr); + mentionedUsers.Add(guild.MembersInternal.TryGetValue(usr.Id, out var member) ? member : usr); } } if (!string.IsNullOrWhiteSpace(this.Content)) { //mentionedUsers.UnionWith(Utilities.GetUserMentions(this).Select(this.Discord.GetCachedOrEmptyUserInternal)); if (guild != null) { //this._mentionedRoles = this._mentionedRoles.Union(Utilities.GetRoleMentions(this).Select(xid => guild.GetRole(xid))).ToList(); - this._mentionedRoles = this._mentionedRoles.Union(this._mentionedRoleIds.Select(xid => guild.GetRole(xid))).ToList(); - this._mentionedChannels = this._mentionedChannels.Union(Utilities.GetChannelMentions(this).Select(xid => guild.GetChannel(xid))).ToList(); + this.MentionedRolesInternal = this.MentionedRolesInternal.Union(this.MentionedRoleIds.Select(xid => guild.GetRole(xid))).ToList(); + this.MentionedChannelsInternal = this.MentionedChannelsInternal.Union(Utilities.GetChannelMentions(this).Select(xid => guild.GetChannel(xid))).ToList(); } } - this._mentionedUsers = mentionedUsers.ToList(); + this.MentionedUsersInternal = mentionedUsers.ToList(); } /// /// Edits the message. /// /// New content. /// /// Thrown when the client tried to modify a message not sent by them. /// 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 ModifyAsync(Optional content) => this.Discord.ApiClient.EditMessageAsync(this.ChannelId, this.Id, content, default, this.GetMentions(), default, default, Array.Empty(), default); /// /// Edits the message. /// /// New embed. /// /// Thrown when the client tried to modify a message not sent by them. /// 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 ModifyAsync(Optional embed = default) => this.Discord.ApiClient.EditMessageAsync(this.ChannelId, this.Id, default, embed.HasValue ? new[] { embed.Value } : Array.Empty(), this.GetMentions(), default, default, Array.Empty(), default); /// /// Edits the message. /// /// New content. /// New embed. /// /// Thrown when the client tried to modify a message not sent by them. /// 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 ModifyAsync(Optional content, Optional embed = default) => this.Discord.ApiClient.EditMessageAsync(this.ChannelId, this.Id, content, embed.HasValue ? new[] { embed.Value } : Array.Empty(), this.GetMentions(), default, default, Array.Empty(), default); /// /// Edits the message. /// /// New content. /// New embeds. /// /// Thrown when the client tried to modify a message not sent by them. /// 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 ModifyAsync(Optional content, Optional> embeds = default) => this.Discord.ApiClient.EditMessageAsync(this.ChannelId, this.Id, content, embeds, this.GetMentions(), default, default, Array.Empty(), default); /// /// Edits the message. /// /// The builder of the message to edit. /// /// Thrown when the client tried to modify a message not sent by them. /// Thrown when the member does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public async Task ModifyAsync(DiscordMessageBuilder builder) { builder.Validate(true); - return await this.Discord.ApiClient.EditMessageAsync(this.ChannelId, this.Id, builder.Content, new Optional>(builder.Embeds), builder.Mentions, builder.Components, builder.Suppressed, builder.Files, builder.Attachments.Count > 0 ? new Optional>(builder.Attachments) : builder._keepAttachments.HasValue ? builder._keepAttachments.Value ? new Optional>(this.Attachments) : Array.Empty() : null); + return await this.Discord.ApiClient.EditMessageAsync(this.ChannelId, this.Id, builder.Content, new Optional>(builder.Embeds), builder.Mentions, builder.Components, builder.Suppressed, builder.Files, builder.Attachments.Count > 0 ? new Optional>(builder.Attachments) : builder.KeepAttachmentsInternal.HasValue ? builder.KeepAttachmentsInternal.Value ? new Optional>(this.Attachments) : Array.Empty() : null); } /// /// Edits the message embed suppression. /// /// Suppress embeds. /// /// Thrown when the client tried to modify a message not sent by them. /// 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 ModifySuppressionAsync(bool suppress = false) => this.Discord.ApiClient.EditMessageAsync(this.ChannelId, this.Id, default, default, this.GetMentions(), default, suppress, default, default); /// /// Clears all attachments from the message. /// /// public Task ClearAttachmentsAsync() => this.Discord.ApiClient.EditMessageAsync(this.ChannelId, this.Id, default, default, this.GetMentions(), default, default, default, Array.Empty()); /// /// Edits the message. /// /// The builder of the message to edit. /// /// Thrown when the client tried to modify a message not sent by them. /// Thrown when the member does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public async Task ModifyAsync(Action action) { var builder = new DiscordMessageBuilder(); action(builder); builder.Validate(true); - return await this.Discord.ApiClient.EditMessageAsync(this.ChannelId, this.Id, builder.Content, new Optional>(builder.Embeds), builder.Mentions, builder.Components, builder.Suppressed, builder.Files, builder.Attachments.Count > 0 ? new Optional>(builder.Attachments) : builder._keepAttachments.HasValue ? builder._keepAttachments.Value ? new Optional>(this.Attachments) : Array.Empty() : null); + return await this.Discord.ApiClient.EditMessageAsync(this.ChannelId, this.Id, builder.Content, new Optional>(builder.Embeds), builder.Mentions, builder.Components, builder.Suppressed, builder.Files, builder.Attachments.Count > 0 ? new Optional>(builder.Attachments) : builder.KeepAttachmentsInternal.HasValue ? builder.KeepAttachmentsInternal.Value ? new Optional>(this.Attachments) : Array.Empty() : null); } /// /// Deletes the message. /// /// /// 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 DeleteAsync(string reason = null) => this.Discord.ApiClient.DeleteMessageAsync(this.ChannelId, this.Id, reason); /// /// Creates a thread. /// Depending on the of the parent channel it's either a or a . /// /// The name of the thread. - /// till it gets archived. Defaults to - /// The per user ratelimit, aka slowdown. + /// till it gets archived. Defaults to + /// The per user ratelimit, aka slowdown. /// The reason. /// /// Thrown when the client does not have the or permission. /// Thrown when the channel does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. /// Thrown when the cannot be modified. - public async Task CreateThreadAsync(string name, ThreadAutoArchiveDuration auto_archive_duration = ThreadAutoArchiveDuration.OneHour, int? rate_limit_per_user = null, string reason = null) + public async Task CreateThreadAsync(string name, ThreadAutoArchiveDuration autoArchiveDuration = ThreadAutoArchiveDuration.OneHour, int? rateLimitPerUser = null, string reason = null) { - return Utilities.CheckThreadAutoArchiveDurationFeature(this.Channel.Guild, auto_archive_duration) - ? await this.Discord.ApiClient.CreateThreadWithMessageAsync(this.ChannelId, this.Id, name, auto_archive_duration, rate_limit_per_user, reason) - : throw new NotSupportedException($"Cannot modify ThreadAutoArchiveDuration. Guild needs boost tier {(auto_archive_duration == ThreadAutoArchiveDuration.ThreeDays ? "one" : "two")}."); + return Utilities.CheckThreadAutoArchiveDurationFeature(this.Channel.Guild, autoArchiveDuration) + ? await this.Discord.ApiClient.CreateThreadWithMessageAsync(this.ChannelId, this.Id, name, autoArchiveDuration, rateLimitPerUser, reason) + : throw new NotSupportedException($"Cannot modify ThreadAutoArchiveDuration. Guild needs boost tier {(autoArchiveDuration == ThreadAutoArchiveDuration.ThreeDays ? "one" : "two")}."); } /// /// Pins the message in its channel. /// /// /// Thrown when the client does not have the permission. /// Thrown when the member does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task PinAsync() => this.Discord.ApiClient.PinMessageAsync(this.ChannelId, this.Id); /// /// Unpins the message in its channel. /// /// /// Thrown when the client does not have the permission. /// Thrown when the member does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task UnpinAsync() => this.Discord.ApiClient.UnpinMessageAsync(this.ChannelId, this.Id); /// /// Responds to the message. This produces a reply. /// /// Message content to respond with. /// The sent message. /// 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 RespondAsync(string content) => this.Discord.ApiClient.CreateMessageAsync(this.ChannelId, content, null, sticker: null, replyMessageId: this.Id, mentionReply: false, failOnInvalidReply: false); /// /// Responds to the message. This produces a reply. /// /// Embed to attach to the message. /// The sent message. /// 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 RespondAsync(DiscordEmbed embed) => this.Discord.ApiClient.CreateMessageAsync(this.ChannelId, null, embed != null ? new[] { embed } : null, sticker: null, replyMessageId: this.Id, mentionReply: false, failOnInvalidReply: false); /// /// Responds to the message. This produces a reply. /// /// Message content to respond with. /// Embed to attach to the message. /// The sent message. /// 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 RespondAsync(string content, DiscordEmbed embed) => this.Discord.ApiClient.CreateMessageAsync(this.ChannelId, content, embed != null ? new[] { embed } : null, sticker: null, replyMessageId: this.Id, mentionReply: false, failOnInvalidReply: false); /// /// Responds to the message. This produces a reply. /// /// The Discord message builder. /// The sent message. /// 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 RespondAsync(DiscordMessageBuilder builder) => this.Discord.ApiClient.CreateMessageAsync(this.ChannelId, builder.WithReply(this.Id, mention: false, failOnInvalidReply: false)); /// /// Responds to the message. This produces a reply. /// /// The Discord message builder. /// The sent message. /// 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 RespondAsync(Action action) { var builder = new DiscordMessageBuilder(); action(builder); return this.Discord.ApiClient.CreateMessageAsync(this.ChannelId, builder.WithReply(this.Id, mention: false, failOnInvalidReply: false)); } /// /// Creates a reaction to this message. /// /// The emoji you want to react with, either an emoji or name:id /// /// Thrown when the client does not have the permission. /// Thrown when the emoji does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task CreateReactionAsync(DiscordEmoji emoji) => this.Discord.ApiClient.CreateReactionAsync(this.ChannelId, this.Id, emoji.ToReactionString()); /// /// Deletes your own reaction /// /// Emoji for the reaction you want to remove, either an emoji or name:id /// /// Thrown when the emoji does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task DeleteOwnReactionAsync(DiscordEmoji emoji) => this.Discord.ApiClient.DeleteOwnReactionAsync(this.ChannelId, this.Id, emoji.ToReactionString()); /// /// Deletes another user's reaction. /// /// Emoji for the reaction you want to remove, either an emoji or name:id. /// Member you want to remove the reaction for /// Reason for audit logs. /// /// Thrown when the client does not have the permission. /// Thrown when the emoji does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task DeleteReactionAsync(DiscordEmoji emoji, DiscordUser user, string reason = null) => this.Discord.ApiClient.DeleteUserReactionAsync(this.ChannelId, this.Id, user.Id, emoji.ToReactionString(), reason); /// /// Gets users that reacted with this emoji. /// /// Emoji to react with. /// Limit of users to fetch. /// Fetch users after this user's id. /// /// Thrown when the emoji does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task> GetReactionsAsync(DiscordEmoji emoji, int limit = 25, ulong? after = null) => this.GetReactionsInternalAsync(emoji, limit, after); /// /// Deletes all reactions for this message. /// /// Reason for audit logs. /// /// Thrown when the client does not have the permission. /// Thrown when the emoji does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task DeleteAllReactionsAsync(string reason = null) => this.Discord.ApiClient.DeleteAllReactionsAsync(this.ChannelId, this.Id, reason); /// /// Deletes all reactions of a specific reaction for this message. /// /// The emoji to clear, either an emoji or name:id. /// /// Thrown when the client does not have the permission. /// Thrown when the emoji does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task DeleteReactionsEmojiAsync(DiscordEmoji emoji) => this.Discord.ApiClient.DeleteReactionsEmojiAsync(this.ChannelId, this.Id, emoji.ToReactionString()); /// /// Gets the reactions. /// /// The emoji to search for. /// The limit of results. /// Get the reasctions after snowflake. private async Task> GetReactionsInternalAsync(DiscordEmoji emoji, int limit = 25, ulong? after = null) { if (limit < 0) throw new ArgumentException("Cannot get a negative number of reactions' users."); if (limit == 0) return Array.Empty(); var users = new List(limit); var remaining = limit; var last = after; int lastCount; do { var fetchSize = remaining > 100 ? 100 : remaining; var fetch = await this.Discord.ApiClient.GetReactionsAsync(this.Channel.Id, this.Id, emoji.ToReactionString(), last, fetchSize).ConfigureAwait(false); lastCount = fetch.Count; remaining -= lastCount; users.AddRange(fetch); last = fetch.LastOrDefault()?.Id; } while (remaining > 0 && lastCount > 0); return new ReadOnlyCollection(users); } /// /// Returns a string representation of this message. /// /// String representation of this message. - public override string ToString() => $"Message {this.Id}; Attachment count: {this._attachments.Count}; Embed count: {this._embeds.Count}; Contents: {this.Content}"; + public override string ToString() => $"Message {this.Id}; Attachment count: {this.AttachmentsInternal.Count}; Embed count: {this.EmbedsInternal.Count}; Contents: {this.Content}"; /// /// 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 DiscordMessage); /// /// Checks whether this is equal to another . /// /// to compare to. /// Whether the is equal to this . public bool Equals(DiscordMessage e) => e is not null && (ReferenceEquals(this, e) || (this.Id == e.Id && this.ChannelId == e.ChannelId)); /// /// Gets the hash code for this . /// /// The hash code for this . public override int GetHashCode() { var hash = 13; hash = (hash * 7) + this.Id.GetHashCode(); hash = (hash * 7) + this.ChannelId.GetHashCode(); return hash; } /// /// Gets whether the two objects are equal. /// /// First message to compare. /// Second message to compare. /// Whether the two messages are equal. public static bool operator ==(DiscordMessage e1, DiscordMessage e2) { var o1 = e1 as object; var o2 = e2 as object; return (o1 != null || o2 == null) && (o1 == null || o2 != null) && ((o1 == null && o2 == null) || (e1.Id == e2.Id && e1.ChannelId == e2.ChannelId)); } /// /// Gets whether the two objects are not equal. /// /// First message to compare. /// Second message to compare. /// Whether the two messages are not equal. public static bool operator !=(DiscordMessage e1, DiscordMessage e2) => !(e1 == e2); } } diff --git a/DisCatSharp/Entities/Message/DiscordMessageBuilder.cs b/DisCatSharp/Entities/Message/DiscordMessageBuilder.cs index ef0f7d5f5..74a202877 100644 --- a/DisCatSharp/Entities/Message/DiscordMessageBuilder.cs +++ b/DisCatSharp/Entities/Message/DiscordMessageBuilder.cs @@ -1,460 +1,460 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; namespace DisCatSharp.Entities { /// /// Constructs a Message to be sent. /// public sealed class DiscordMessageBuilder { /// /// Gets or Sets the Message to be sent. /// public string Content { get => this._content; set { if (value != null && value.Length > 2000) throw new ArgumentException("Content cannot exceed 2000 characters.", nameof(value)); this._content = value; } } private string _content; /// /// Gets or sets the embed for the builder. This will always set the builder to have one embed. /// public DiscordEmbed Embed { get => this._embeds.Count > 0 ? this._embeds[0] : null; set { this._embeds.Clear(); this._embeds.Add(value); } } /// /// Gets the Sticker to be send. /// public DiscordSticker Sticker { get; set; } /// /// Gets the Embeds to be sent. /// public IReadOnlyList Embeds => this._embeds; private readonly List _embeds = new(); /// /// Gets or Sets if the message should be TTS. /// - public bool IsTTS { get; set; } = false; + public bool IsTts { get; set; } = false; /// /// Whether to keep previous attachments. /// - internal bool? _keepAttachments = null; + internal bool? KeepAttachmentsInternal = null; /// /// Gets the Allowed Mentions for the message to be sent. /// public List Mentions { get; private set; } = null; /// /// Gets the Files to be sent in the Message. /// - public IReadOnlyCollection Files => this._files; - internal readonly List _files = new(); + public IReadOnlyCollection Files => this.FilesInternal; + internal readonly List FilesInternal = new(); /// /// Gets the components that will be attached to the message. /// - public IReadOnlyList Components => this._components; - internal readonly List _components = new(5); + public IReadOnlyList Components => this.ComponentsInternal; + internal readonly List ComponentsInternal = new(5); /// /// Gets the Attachments to be sent in the Message. /// - public IReadOnlyList Attachments => this._attachments; - internal readonly List _attachments = new(); + public IReadOnlyList Attachments => this.AttachmentsInternal; + internal readonly List AttachmentsInternal = new(); /// /// Gets the Reply Message ID. /// public ulong? ReplyId { get; private set; } = null; /// /// Gets if the Reply should mention the user. /// public bool MentionOnReply { get; private set; } = false; /// /// Gets if the embeds should be suppressed. /// public bool Suppressed { get; private set; } = false; /// /// Gets if the Reply will error if the Reply Message Id does not reference a valid message. /// If set to false, invalid replies are send as a regular message. /// Defaults to false. /// public bool FailOnInvalidReply { get; set; } /// /// Sets the Content of the Message. /// /// The content to be set. /// The current builder to be chained. public DiscordMessageBuilder WithContent(string content) { this.Content = content; return this; } /// /// Adds a sticker to the message. Sticker must be from current guild. /// /// The sticker to add. /// The current builder to be chained. public DiscordMessageBuilder WithSticker(DiscordSticker sticker) { this.Sticker = sticker; return this; } /// /// Adds a row of components to a message, up to 5 components per row, and up to 5 rows per message. /// /// The components to add to the message. /// The current builder to be chained. /// No components were passed. public DiscordMessageBuilder AddComponents(params DiscordComponent[] components) => this.AddComponents((IEnumerable)components); /// /// Appends several rows of components to the message /// /// The rows of components to add, holding up to five each. /// public DiscordMessageBuilder AddComponents(IEnumerable components) { var ara = components.ToArray(); - if (ara.Length + this._components.Count > 5) + if (ara.Length + this.ComponentsInternal.Count > 5) throw new ArgumentException("ActionRow count exceeds maximum of five."); foreach (var ar in ara) - this._components.Add(ar); + this.ComponentsInternal.Add(ar); return this; } /// /// Adds a row of components to a message, up to 5 components per row, and up to 5 rows per message. /// /// The components to add to the message. /// The current builder to be chained. /// No components were passed. public DiscordMessageBuilder AddComponents(IEnumerable components) { var cmpArr = components.ToArray(); var count = cmpArr.Length; if (!cmpArr.Any()) throw new ArgumentOutOfRangeException(nameof(components), "You must provide at least one component"); if (count > 5) throw new ArgumentException("Cannot add more than 5 components per action row!"); var comp = new DiscordActionRowComponent(cmpArr); - this._components.Add(comp); + this.ComponentsInternal.Add(comp); return this; } /// /// Sets if the message should be TTS. /// - /// If TTS should be set. + /// If TTS should be set. /// The current builder to be chained. - public DiscordMessageBuilder HasTTS(bool isTTS) + public DiscordMessageBuilder HasTts(bool isTts) { - this.IsTTS = isTTS; + this.IsTts = isTts; return this; } /// /// Sets the embed for the current builder. /// /// The embed that should be set. /// The current builder to be chained. public DiscordMessageBuilder WithEmbed(DiscordEmbed embed) { if (embed == null) return this; this.Embed = embed; return this; } /// /// Appends an embed to the current builder. /// /// The embed that should be appended. /// The current builder to be chained. public DiscordMessageBuilder AddEmbed(DiscordEmbed embed) { if (embed == null) return this; //Providing null embeds will produce a 400 response from Discord.// this._embeds.Add(embed); return this; } /// /// Appends several embeds to the current builder. /// /// The embeds that should be appended. /// The current builder to be chained. public DiscordMessageBuilder AddEmbeds(IEnumerable embeds) { this._embeds.AddRange(embeds); return this; } /// /// Sets if the message has allowed mentions. /// /// The allowed Mention that should be sent. /// The current builder to be chained. public DiscordMessageBuilder WithAllowedMention(IMention allowedMention) { if (this.Mentions != null) this.Mentions.Add(allowedMention); else this.Mentions = new List { allowedMention }; return this; } /// /// Sets if the message has allowed mentions. /// /// The allowed Mentions that should be sent. /// The current builder to be chained. public DiscordMessageBuilder WithAllowedMentions(IEnumerable allowedMentions) { if (this.Mentions != null) this.Mentions.AddRange(allowedMentions); else this.Mentions = allowedMentions.ToList(); return this; } /// /// Sets if the message has files to be sent. /// /// The fileName that the file should be sent as. /// The Stream to the file. /// Tells the API Client to reset the stream position to what it was after the file is sent. /// Description of the file. /// The current builder to be chained. public DiscordMessageBuilder WithFile(string fileName, Stream stream, bool resetStreamPosition = false, string description = null) { if (this.Files.Count > 10) throw new ArgumentException("Cannot send more than 10 files with a single message."); - if (this._files.Any(x => x.FileName == fileName)) + if (this.FilesInternal.Any(x => x.FileName == fileName)) throw new ArgumentException("A File with that filename already exists"); if (resetStreamPosition) - this._files.Add(new DiscordMessageFile(fileName, stream, stream.Position, description: description)); + this.FilesInternal.Add(new DiscordMessageFile(fileName, stream, stream.Position, description: description)); else - this._files.Add(new DiscordMessageFile(fileName, stream, null, description: description)); + this.FilesInternal.Add(new DiscordMessageFile(fileName, stream, null, description: description)); return this; } /// /// Sets if the message has files to be sent. /// /// The Stream to the file. /// Tells the API Client to reset the stream position to what it was after the file is sent. /// Description of the file. /// The current builder to be chained. public DiscordMessageBuilder WithFile(FileStream stream, bool resetStreamPosition = false, string description = null) { if (this.Files.Count > 10) throw new ArgumentException("Cannot send more than 10 files with a single message."); - if (this._files.Any(x => x.FileName == stream.Name)) + if (this.FilesInternal.Any(x => x.FileName == stream.Name)) throw new ArgumentException("A File with that filename already exists"); if (resetStreamPosition) - this._files.Add(new DiscordMessageFile(stream.Name, stream, stream.Position, description: description)); + this.FilesInternal.Add(new DiscordMessageFile(stream.Name, stream, stream.Position, description: description)); else - this._files.Add(new DiscordMessageFile(stream.Name, stream, null, description: description)); + this.FilesInternal.Add(new DiscordMessageFile(stream.Name, stream, null, description: description)); return this; } /// /// Sets if the message has files to be sent. /// /// The Files that should be sent. /// Tells the API Client to reset the stream position to what it was after the file is sent. /// The current builder to be chained. public DiscordMessageBuilder WithFiles(Dictionary files, bool resetStreamPosition = false) { if (this.Files.Count + files.Count > 10) throw new ArgumentException("Cannot send more than 10 files with a single message."); foreach (var file in files) { - if (this._files.Any(x => x.FileName == file.Key)) + if (this.FilesInternal.Any(x => x.FileName == file.Key)) throw new ArgumentException("A File with that filename already exists"); if (resetStreamPosition) - this._files.Add(new DiscordMessageFile(file.Key, file.Value, file.Value.Position)); + this.FilesInternal.Add(new DiscordMessageFile(file.Key, file.Value, file.Value.Position)); else - this._files.Add(new DiscordMessageFile(file.Key, file.Value, null)); + this.FilesInternal.Add(new DiscordMessageFile(file.Key, file.Value, null)); } return this; } /// /// Modifies the given attachments on edit. /// /// Attachments to edit. /// public DiscordMessageBuilder ModifyAttachments(IEnumerable attachments) { - this._attachments.AddRange(attachments); + this.AttachmentsInternal.AddRange(attachments); return this; } /// /// Whether to keep the message attachments, if new ones are added. /// /// public DiscordMessageBuilder KeepAttachments(bool keep) { - this._keepAttachments = keep; + this.KeepAttachmentsInternal = keep; return this; } /// /// Sets if the message is a reply /// /// The ID of the message to reply to. /// If we should mention the user in the reply. /// Whether sending a reply that references an invalid message should be /// The current builder to be chained. public DiscordMessageBuilder WithReply(ulong messageId, bool mention = false, bool failOnInvalidReply = false) { this.ReplyId = messageId; this.MentionOnReply = mention; this.FailOnInvalidReply = failOnInvalidReply; if (mention) { this.Mentions ??= new List(); this.Mentions.Add(new RepliedUserMention()); } return this; } /// /// Sends the Message to a specific channel /// /// The channel the message should be sent to. /// The current builder to be chained. public Task SendAsync(DiscordChannel channel) => channel.SendMessageAsync(this); /// /// Sends the modified message. /// Note: Message replies cannot be modified. To clear the reply, simply pass to . /// /// The original Message to modify. /// The current builder to be chained. public Task ModifyAsync(DiscordMessage msg) => msg.ModifyAsync(this); /// /// Clears all message components on this builder. /// public void ClearComponents() - => this._components.Clear(); + => this.ComponentsInternal.Clear(); /// /// Allows for clearing the Message Builder so that it can be used again to send a new message. /// public void Clear() { this.Content = ""; this._embeds.Clear(); - this.IsTTS = false; + this.IsTts = false; this.Mentions = null; - this._files.Clear(); + this.FilesInternal.Clear(); this.ReplyId = null; this.MentionOnReply = false; - this._components.Clear(); + this.ComponentsInternal.Clear(); this.Suppressed = false; this.Sticker = null; - this._attachments.Clear(); - this._keepAttachments = false; + this.AttachmentsInternal.Clear(); + this.KeepAttachmentsInternal = false; } /// /// Does the validation before we send a the Create/Modify request. /// /// Tells the method to perform the Modify Validation or Create Validation. internal void Validate(bool isModify = false) { if (this._embeds.Count > 10) throw new ArgumentException("A message can only have up to 10 embeds."); if (!isModify) { if (this.Files?.Count == 0 && string.IsNullOrEmpty(this.Content) && (!this.Embeds?.Any() ?? true) && this.Sticker is null) throw new ArgumentException("You must specify content, an embed, a sticker or at least one file."); if (this.Components.Count > 5) throw new InvalidOperationException("You can only have 5 action rows per message."); if (this.Components.Any(c => c.Components.Count > 5)) throw new InvalidOperationException("Action rows can only have 5 components"); } } } } diff --git a/DisCatSharp/Entities/Sticker/DiscordSticker.cs b/DisCatSharp/Entities/Sticker/DiscordSticker.cs index 86dcd05fe..07fe85d55 100644 --- a/DisCatSharp/Entities/Sticker/DiscordSticker.cs +++ b/DisCatSharp/Entities/Sticker/DiscordSticker.cs @@ -1,215 +1,215 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System; using System.Collections.Generic; using System.Threading.Tasks; using DisCatSharp.Enums; using DisCatSharp.Net; using Newtonsoft.Json; namespace DisCatSharp.Entities { /// /// Represents a Discord Sticker. /// public class DiscordSticker : SnowflakeObject, IEquatable { /// /// Gets the Pack ID of this sticker. /// [JsonProperty("pack_id", NullValueHandling = NullValueHandling.Ignore)] public ulong? PackId { get; internal set; } /// /// Gets the Name of the sticker. /// [JsonProperty("name")] public string Name { get; internal set; } /// /// Gets the Description of the sticker. /// [JsonProperty("description", NullValueHandling = NullValueHandling.Ignore)] public string Description { get; internal set; } /// /// Gets the type of sticker. /// [JsonProperty("type")] public StickerType Type { get; internal set; } /// /// For guild stickers, gets the user that made the sticker. /// [JsonProperty("user", NullValueHandling = NullValueHandling.Ignore)] public DiscordUser User { get; internal set; } /// /// Gets the guild associated with this sticker, if any. /// public DiscordGuild Guild => (this.Discord as DiscordClient).InternalGetCachedGuild(this.GuildId); /// /// Gets the guild id the sticker belongs too. /// [JsonProperty("guild_id", NullValueHandling = NullValueHandling.Ignore)] public ulong? GuildId { get; internal set; } /// /// Gets whether this sticker is available. Only applicable to guild stickers. /// [JsonProperty("available", NullValueHandling = NullValueHandling.Ignore)] public bool Available { get; internal set; } /// /// Gets the sticker's sort order, if it's in a pack. /// [JsonProperty("sort_value", NullValueHandling = NullValueHandling.Ignore)] public int? SortValue { get; internal set; } /// /// Gets the list of tags for the sticker. /// [JsonIgnore] public IEnumerable Tags - => this._internalTags != null ? this._internalTags.Split(',') : Array.Empty(); + => this.InternalTags != null ? this.InternalTags.Split(',') : Array.Empty(); /// /// Gets the asset hash of the sticker. /// [JsonProperty("asset", NullValueHandling = NullValueHandling.Ignore)] public string Asset { get; internal set; } /// /// Gets the preview asset hash of the sticker. /// [JsonProperty("preview_asset", NullValueHandling = NullValueHandling.Ignore)] public string PreviewAsset { get; internal set; } /// /// Gets the Format type of the sticker. /// [JsonProperty("format_type")] public StickerFormat FormatType { get; internal set; } /// /// Gets the tags of the sticker. /// [JsonProperty("tags", NullValueHandling = NullValueHandling.Ignore)] [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "")] - internal string _internalTags { get; set; } + internal string InternalTags { get; set; } /// /// Gets the url of the sticker. /// [JsonIgnore] - public string Url => $"{DiscordDomain.GetDomain(CoreDomain.DiscordCdn).Url}{Endpoints.STICKERS}/{this.Id}.{(this.FormatType == StickerFormat.LOTTIE ? "json" : "png")}"; + public string Url => $"{DiscordDomain.GetDomain(CoreDomain.DiscordCdn).Url}{Endpoints.STICKERS}/{this.Id}.{(this.FormatType == StickerFormat.Lottie ? "json" : "png")}"; /// /// Initializes a new instance of the class. /// internal DiscordSticker() { } /// /// Whether to stickers are equal. /// /// DiscordSticker /// public bool Equals(DiscordSticker other) => this.Id == other.Id; /// /// Gets the sticker in readable format. /// public override string ToString() => $"Sticker {this.Id}; {this.Name}; {this.FormatType}"; /// /// Modifies the sticker /// /// The name of the sticker /// The description of the sticker /// The name of a unicode emoji representing the sticker's expression /// 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 ModifyAsync(Optional name, Optional description, Optional tags, string reason = null) { return !this.GuildId.HasValue ? throw new ArgumentException("This sticker does not belong to a guild.") : name.HasValue && (name.Value.Length < 2 || name.Value.Length > 30) ? throw new ArgumentException("Sticker name needs to be between 2 and 30 characters long.") : description.HasValue && (description.Value.Length < 1 || description.Value.Length > 100) ? throw new ArgumentException("Sticker description needs to be between 1 and 100 characters long.") : tags.HasValue && !DiscordEmoji.TryFromUnicode(this.Discord, tags.Value, out var emoji) ? throw new ArgumentException("Sticker tags needs to be a unicode emoji.") : this.Discord.ApiClient.ModifyGuildStickerAsync(this.GuildId.Value, this.Id, name, description, tags, reason); } /// /// Deletes the sticker /// /// 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 DeleteAsync(string reason = null) => this.GuildId.HasValue ? this.Discord.ApiClient.DeleteGuildStickerAsync(this.GuildId.Value, this.Id, reason) : throw new ArgumentException("The requested sticker is no guild sticker."); } /// /// The sticker type /// public enum StickerType : long { /// /// Standard nitro sticker /// Standard = 1, /// /// Custom guild sticker /// Guild = 2 } /// /// The sticker type /// public enum StickerFormat : long { /// /// Sticker is a png /// - PNG = 1, + Png = 1, /// /// Sticker is a animated png /// - APNG = 2, + Apng = 2, /// /// Sticker is lottie /// - LOTTIE = 3 + Lottie = 3 } } diff --git a/DisCatSharp/Entities/Sticker/DiscordStickerPack.cs b/DisCatSharp/Entities/Sticker/DiscordStickerPack.cs index 9f6372088..1ef45b9df 100644 --- a/DisCatSharp/Entities/Sticker/DiscordStickerPack.cs +++ b/DisCatSharp/Entities/Sticker/DiscordStickerPack.cs @@ -1,83 +1,83 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System.Collections.Generic; using System.Threading.Tasks; using DisCatSharp.Enums; using DisCatSharp.Net; using Newtonsoft.Json; namespace DisCatSharp.Entities { /// /// Represents a Discord sticker pack. /// public sealed class DiscordStickerPack : SnowflakeObject { /// /// Gets the stickers contained in this pack. /// - public IReadOnlyList Stickers => this._stickers; + public IReadOnlyList Stickers => this.StickersInternal; [JsonProperty("stickers")] - internal List _stickers = new(); + internal List StickersInternal = new(); /// /// Gets the name of this sticker pack. /// [JsonProperty("name")] public string Name { get; internal set; } /// /// Gets the sku id. /// [JsonProperty("sku_id")] public ulong SkuId { get; internal set; } /// /// Gets the Id of this pack's cover sticker. /// [JsonProperty("cover_sticker_id")] public ulong CoverStickerId { get; internal set; } /// /// Gets the pack's cover sticker. /// public Task CoverSticker => this.Discord.ApiClient.GetStickerAsync(this.CoverStickerId); /// /// Gets the Id of this pack's banner. /// [JsonProperty("banner_asset_id")] public ulong BannerAssetId { get; internal set; } /// /// Gets the pack's banner url. /// public string BannerUrl => $"{DiscordDomain.GetDomain(CoreDomain.DiscordCdn).Url}{Endpoints.APP_ASSETS}{Endpoints.STICKER_APPLICATION}{Endpoints.STORE}/{this.BannerAssetId}.png?size=4096"; /// /// Initializes a new instance of the class. /// internal DiscordStickerPack() { } } } diff --git a/DisCatSharp/Entities/Thread/DiscordThreadChannel.cs b/DisCatSharp/Entities/Thread/DiscordThreadChannel.cs index 9839bd0c6..6691d1631 100644 --- a/DisCatSharp/Entities/Thread/DiscordThreadChannel.cs +++ b/DisCatSharp/Entities/Thread/DiscordThreadChannel.cs @@ -1,660 +1,660 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Globalization; using System.Linq; using System.Threading.Tasks; using DisCatSharp.Net.Models; using DisCatSharp.Net.Serialization; using Newtonsoft.Json; namespace DisCatSharp.Entities { /// /// Represents a discord thread channel. /// public class DiscordThreadChannel : DiscordChannel, IEquatable { /// /// Gets ID of the owner that started this thread. /// [JsonProperty("owner_id", NullValueHandling = NullValueHandling.Ignore)] public ulong OwnerId { get; internal set; } /// /// Gets the name of this thread. /// [JsonProperty("name", NullValueHandling = NullValueHandling.Ignore)] public new string Name { get; internal set; } /// /// Gets the type of this thread. /// [JsonProperty("type", NullValueHandling = NullValueHandling.Ignore)] public new ChannelType Type { get; internal set; } /// /// Gets whether this thread is private. /// [JsonIgnore] public new bool IsPrivate => this.Type == ChannelType.PrivateThread; /// /// Gets the ID of the last message sent in this thread. /// [JsonProperty("last_message_id", NullValueHandling = NullValueHandling.Ignore)] public new ulong? LastMessageId { get; internal set; } /// /// Gets the slowmode delay configured for this thread. /// All bots, as well as users with or permissions in the channel are exempt from slowmode. /// [JsonProperty("rate_limit_per_user", NullValueHandling = NullValueHandling.Ignore)] public new int? PerUserRateLimit { get; internal set; } /// /// Gets an approximate count of messages in a thread, stops counting at 50. /// [JsonProperty("message_count", NullValueHandling = NullValueHandling.Ignore)] public int? MessageCount { get; internal set; } /// /// Gets an approximate count of users in a thread, stops counting at 50. /// [JsonProperty("member_count", NullValueHandling = NullValueHandling.Ignore)] public int? MemberCount { get; internal set; } /// /// Represents the current member for this thread. This will have a value if the user has joined the thread. /// [JsonProperty("member", NullValueHandling = NullValueHandling.Ignore)] public DiscordThreadChannelMember CurrentMember { get; internal set; } /// /// Gets when the last pinned message was pinned in this thread. /// [JsonIgnore] public new DateTimeOffset? LastPinTimestamp => !string.IsNullOrWhiteSpace(this.LastPinTimestampRaw) && DateTimeOffset.TryParse(this.LastPinTimestampRaw, CultureInfo.InvariantCulture, DateTimeStyles.None, out var dto) ? dto : null; /// /// Gets when the last pinned message was pinned in this thread as raw string. /// [JsonProperty("last_pin_timestamp", NullValueHandling = NullValueHandling.Ignore)] internal new string LastPinTimestampRaw { get; set; } /// /// Gets the threads metadata. /// [JsonProperty("thread_metadata", NullValueHandling = NullValueHandling.Ignore)] public DiscordThreadChannelMetadata ThreadMetadata { get; internal set; } /// /// Gets the thread members object. /// [JsonIgnore] - public IReadOnlyDictionary ThreadMembers => new ReadOnlyConcurrentDictionary(this._threadMembers); + public IReadOnlyDictionary ThreadMembers => new ReadOnlyConcurrentDictionary(this.ThreadMembersInternal); [JsonProperty("thread_member", NullValueHandling = NullValueHandling.Ignore)] [JsonConverter(typeof(SnowflakeArrayAsDictionaryJsonConverter))] - internal ConcurrentDictionary _threadMembers; + internal ConcurrentDictionary ThreadMembersInternal; /// /// Initializes a new instance of the class. /// internal DiscordThreadChannel() { } #region Methods /// /// Deletes a thread. /// /// Reason for audit logs. /// Thrown when the client does not have the permission. /// Thrown when the thread does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public new Task DeleteAsync(string reason = null) => this.Discord.ApiClient.DeleteThreadAsync(this.Id, reason); /// /// Modifies the current thread. /// /// Action to perform on this thread /// Thrown when the client does not have the permission. /// Thrown when the thread does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. /// Thrown when the cannot be modified. This happens, when the guild hasn't reached a certain boost . public Task ModifyAsync(Action action) { var mdl = new ThreadEditModel(); action(mdl); - var can_continue = !mdl.AutoArchiveDuration.HasValue || !mdl.AutoArchiveDuration.Value.HasValue || Utilities.CheckThreadAutoArchiveDurationFeature(this.Guild, mdl.AutoArchiveDuration.Value.Value); + var canContinue = !mdl.AutoArchiveDuration.HasValue || !mdl.AutoArchiveDuration.Value.HasValue || Utilities.CheckThreadAutoArchiveDurationFeature(this.Guild, mdl.AutoArchiveDuration.Value.Value); if (mdl.Invitable.HasValue) { - can_continue = this.Guild.Features.CanCreatePrivateThreads; + canContinue = this.Guild.Features.CanCreatePrivateThreads; } - return can_continue ? this.Discord.ApiClient.ModifyThreadAsync(this.Id, mdl.Name, mdl.Locked, mdl.Archived, mdl.AutoArchiveDuration, mdl.PerUserRateLimit, mdl.Invitable, mdl.AuditLogReason) : throw new NotSupportedException($"Cannot modify ThreadAutoArchiveDuration. Guild needs boost tier {(mdl.AutoArchiveDuration.Value.Value == ThreadAutoArchiveDuration.ThreeDays ? "one" : "two")}."); + return canContinue ? this.Discord.ApiClient.ModifyThreadAsync(this.Id, mdl.Name, mdl.Locked, mdl.Archived, mdl.AutoArchiveDuration, mdl.PerUserRateLimit, mdl.Invitable, mdl.AuditLogReason) : throw new NotSupportedException($"Cannot modify ThreadAutoArchiveDuration. Guild needs boost tier {(mdl.AutoArchiveDuration.Value.Value == ThreadAutoArchiveDuration.ThreeDays ? "one" : "two")}."); } /// /// Archives a thread. /// /// Whether the thread should be locked. /// Reason for audit logs. /// Thrown when the client does not have the permission. /// 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 ArchiveAsync(bool locked = true, string reason = null) => this.Discord.ApiClient.ModifyThreadAsync(this.Id, null, locked, true, null, null, null, reason: reason); /// /// Unarchives a thread. /// /// Reason for audit logs. /// 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 UnarchiveAsync(string reason = null) => this.Discord.ApiClient.ModifyThreadAsync(this.Id, null, null, false, null, null, null, reason: reason); /// /// Gets the members of a thread. Needs the intent. /// /// Thrown when the thread does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public async Task> GetMembersAsync() => await this.Discord.ApiClient.GetThreadMembersAsync(this.Id); /// /// Adds a member to this thread. /// - /// The member id to be added. + /// The member id to be added. /// 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 AddMemberAsync(ulong member_id) - => this.Discord.ApiClient.AddThreadMemberAsync(this.Id, member_id); + public Task AddMemberAsync(ulong memberId) + => this.Discord.ApiClient.AddThreadMemberAsync(this.Id, memberId); /// /// Adds a member to this thread. /// /// The member to be added. /// 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 AddMemberAsync(DiscordMember member) => this.AddMemberAsync(member.Id); /// /// Gets a member in this thread. /// - /// The member to be added. + /// The member to be added. /// Thrown when the member is not part of the thread. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. - public Task GetMemberAsync(ulong member_id) - => this.Discord.ApiClient.GetThreadMemberAsync(this.Id, member_id); + public Task GetMemberAsync(ulong memberId) + => this.Discord.ApiClient.GetThreadMemberAsync(this.Id, memberId); /// /// Gets a member in this thread. /// /// The member to be added. /// Thrown when the member is not part of the thread. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task GetMemberAsync(DiscordMember member) => this.Discord.ApiClient.GetThreadMemberAsync(this.Id, member.Id); /// /// Removes a member from this thread. /// - /// The member id to be removed. + /// The member id to be removed. /// 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 RemoveMemberAsync(ulong member_id) - => this.Discord.ApiClient.RemoveThreadMemberAsync(this.Id, member_id); + public Task RemoveMemberAsync(ulong memberId) + => this.Discord.ApiClient.RemoveThreadMemberAsync(this.Id, memberId); /// /// Removes a member from this thread. Only applicable to private threads. /// /// The member to be removed. /// 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 RemoveMemberAsync(DiscordMember member) => this.RemoveMemberAsync(member.Id); /// /// Adds a role to this thread. Only applicable to private threads. /// - /// The role id to be added. + /// The role id to be added. /// Thrown when the thread does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. - public async Task AddRoleAsync(ulong role_id) + public async Task AddRoleAsync(ulong roleId) { - var role = this.Guild.GetRole(role_id); + var role = this.Guild.GetRole(roleId); var members = await this.Guild.GetAllMembersAsync(); var roleMembers = members.Where(m => m.Roles.Contains(role)); foreach (var member in roleMembers) { await this.Discord.ApiClient.AddThreadMemberAsync(this.Id, member.Id); } } /// /// Adds a role to this thread. Only applicable to private threads. /// /// The role to be added. /// 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 AddRoleAsync(DiscordRole role) => this.AddRoleAsync(role.Id); /// /// Removes a role from this thread. Only applicable to private threads. /// - /// The role id to be removed. + /// The role id to be removed. /// Thrown when the thread does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. - public async Task RemoveRoleAsync(ulong role_id) + public async Task RemoveRoleAsync(ulong roleId) { - var role = this.Guild.GetRole(role_id); + var role = this.Guild.GetRole(roleId); var members = await this.Guild.GetAllMembersAsync(); var roleMembers = members.Where(m => m.Roles.Contains(role)); foreach (var member in roleMembers) { await this.Discord.ApiClient.RemoveThreadMemberAsync(this.Id, member.Id); } } /// /// Removes a role to from thread. Only applicable to private threads. /// /// The role to be removed. /// 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 RemoveRoleAsync(DiscordRole role) => this.RemoveRoleAsync(role.Id); /// /// Joins a thread. /// /// Thrown when the client has no access to this thread. /// Thrown when the thread does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task JoinAsync() => this.Discord.ApiClient.JoinThreadAsync(this.Id); /// /// Leaves a thread. /// /// Thrown when the client has no access to this thread. /// Thrown when the thread does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task LeaveAsync() => this.Discord.ApiClient.LeaveThreadAsync(this.Id); /// /// Sends a message to this thread. /// /// Content of the message to send. /// The sent message. /// Thrown when the client does not have the permission and if TTS is true or the thread is locked. /// Thrown when the thread does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public new Task SendMessageAsync(string content) { return !this.IsWriteable() ? throw new ArgumentException("Cannot send a text message to a non-thread channel.") : this.Discord.ApiClient.CreateMessageAsync(this.Id, content, null, sticker: null, replyMessageId: null, mentionReply: false, failOnInvalidReply: false); } /// /// Sends a message to this thread. /// /// Embed to attach to the message. /// The sent message. /// Thrown when the client does not have the permission and if TTS is true or the thread is locked. /// Thrown when the thread does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public new Task SendMessageAsync(DiscordEmbed embed) { return !this.IsWriteable() ? throw new ArgumentException("Cannot send a text message to a non-thread channel.") : this.Discord.ApiClient.CreateMessageAsync(this.Id, null, new[] { embed }, sticker: null, replyMessageId: null, mentionReply: false, failOnInvalidReply: false); } /// /// Sends a message to this thread. /// /// Content of the message to send. /// Embed to attach to the message. /// The sent message. /// Thrown when the client does not have the permission and if TTS is true or the thread is locked. /// Thrown when the thread does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public new Task SendMessageAsync(string content, DiscordEmbed embed) { return !this.IsWriteable() ? throw new ArgumentException("Cannot send a text message to a non-thread channel.") : this.Discord.ApiClient.CreateMessageAsync(this.Id, content, new[] { embed }, sticker: null, replyMessageId: null, mentionReply: false, failOnInvalidReply: false); } /// /// Sends a message to this thread. /// /// The builder with all the items to thread. /// The sent message. /// Thrown when the client does not have the permission and if TTS is true or the thread is locked. /// Thrown when the thread does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public new Task SendMessageAsync(DiscordMessageBuilder builder) => this.Discord.ApiClient.CreateMessageAsync(this.Id, builder); /// /// Sends a message to this channel. /// /// The builder with all the items to send. /// The sent message. /// Thrown when the client does not have the permission and if TTS is true or the thread is locked. /// Thrown when the thread does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public new Task SendMessageAsync(Action action) { var builder = new DiscordMessageBuilder(); action(builder); return !this.IsWriteable() ? throw new ArgumentException("Cannot send a text message to a non-text channel.") : this.Discord.ApiClient.CreateMessageAsync(this.Id, builder); } /// /// Returns a specific message /// /// The id of the message /// Thrown when the client does not have the permission and if TTS is true or the thread is locked. /// Thrown when the thread does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public new async Task GetMessageAsync(ulong id) { return this.Discord.Configuration.MessageCacheSize > 0 && this.Discord is DiscordClient dc && dc.MessageCache != null && dc.MessageCache.TryGet(xm => xm.Id == id && xm.ChannelId == this.Id, out var msg) ? msg : await this.Discord.ApiClient.GetMessageAsync(this.Id, id).ConfigureAwait(false); } /// /// Returns a list of messages before a certain message. /// The amount of messages to fetch. /// Message to fetch before from. /// /// Thrown when the client does not have the or the permission. /// Thrown when the thread does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public new Task> GetMessagesBeforeAsync(ulong before, int limit = 100) => this.GetMessagesInternalAsync(limit, before, null, null); /// /// Returns a list of messages after a certain message. /// The amount of messages to fetch. /// Message to fetch after from. /// /// Thrown when the client does not have the or the permission. /// Thrown when the thread does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public new Task> GetMessagesAfterAsync(ulong after, int limit = 100) => this.GetMessagesInternalAsync(limit, null, after, null); /// /// Returns a list of messages around a certain message. /// The amount of messages to fetch. /// Message to fetch around from. /// /// Thrown when the client does not have the or the permission. /// Thrown when the thread does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public new Task> GetMessagesAroundAsync(ulong around, int limit = 100) => this.GetMessagesInternalAsync(limit, null, null, around); /// /// Returns a list of messages from the last message in the thread. /// The amount of messages to fetch. /// /// Thrown when the client does not have the or the permission. /// Thrown when the thread does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public new Task> GetMessagesAsync(int limit = 100) => this.GetMessagesInternalAsync(limit, null, null, null); /// /// Returns a list of messages /// /// How many messages should be returned. /// Get messages before snowflake. /// Get messages after snowflake. /// Get messages around snowflake. private async Task> GetMessagesInternalAsync(int limit = 100, ulong? before = null, ulong? after = null, ulong? around = null) { if (this.Type != ChannelType.PublicThread && this.Type != ChannelType.PrivateThread && this.Type != ChannelType.NewsThread) throw new ArgumentException("Cannot get the messages of a non-thread channel."); if (limit < 0) throw new ArgumentException("Cannot get a negative number of messages."); if (limit == 0) return Array.Empty(); //return this.Discord.ApiClient.GetChannelMessagesAsync(this.Id, limit, before, after, around); if (limit > 100 && around != null) throw new InvalidOperationException("Cannot get more than 100 messages around the specified ID."); var msgs = new List(limit); var remaining = limit; ulong? last = null; var isAfter = after != null; int lastCount; do { var fetchSize = remaining > 100 ? 100 : remaining; var fetch = await this.Discord.ApiClient.GetChannelMessagesAsync(this.Id, fetchSize, !isAfter ? last ?? before : null, isAfter ? last ?? after : null, around).ConfigureAwait(false); lastCount = fetch.Count; remaining -= lastCount; if (!isAfter) { msgs.AddRange(fetch); last = fetch.LastOrDefault()?.Id; } else { msgs.InsertRange(0, fetch); last = fetch.FirstOrDefault()?.Id; } } while (remaining > 0 && lastCount > 0); return new ReadOnlyCollection(msgs); } /// /// Deletes multiple messages if they are less than 14 days old. If they are older, none of the messages will be deleted and you will receive a error. /// /// A collection of messages to delete. /// Reason for audit logs. /// Thrown when the client does not have the permission. /// Thrown when the thread does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public new async Task DeleteMessagesAsync(IEnumerable messages, string reason = null) { // don't enumerate more than once var msgs = messages.Where(x => x.Channel.Id == this.Id).Select(x => x.Id).ToArray(); if (messages == null || !msgs.Any()) throw new ArgumentException("You need to specify at least one message to delete."); if (msgs.Count() < 2) { await this.Discord.ApiClient.DeleteMessageAsync(this.Id, msgs.Single(), reason).ConfigureAwait(false); return; } for (var i = 0; i < msgs.Count(); i += 100) await this.Discord.ApiClient.DeleteMessagesAsync(this.Id, msgs.Skip(i).Take(100), reason).ConfigureAwait(false); } /// /// Deletes a message /// /// The message to be deleted. /// Reason for audit logs. /// Thrown when the client does not have the permission. /// Thrown when the thread does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public new Task DeleteMessageAsync(DiscordMessage message, string reason = null) => this.Discord.ApiClient.DeleteMessageAsync(this.Id, message.Id, reason); /// /// Post a typing indicator /// /// Thrown when the thread does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public new Task TriggerTypingAsync() { return this.Type != ChannelType.PublicThread && this.Type != ChannelType.PrivateThread && this.Type != ChannelType.NewsThread ? throw new ArgumentException("Cannot start typing in a non-text channel.") : this.Discord.ApiClient.TriggerTypingAsync(this.Id); } /// /// Returns all pinned messages /// /// Thrown when the client does not have the permission or the client is missing . /// Thrown when the thread does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public new Task> GetPinnedMessagesAsync() { return this.Type != ChannelType.PublicThread && this.Type != ChannelType.PrivateThread && this.Type != ChannelType.News ? throw new ArgumentException("A non-thread channel does not have pinned messages.") : this.Discord.ApiClient.GetPinnedMessagesAsync(this.Id); } /// /// Returns a string representation of this thread. /// /// String representation of this thread. public override string ToString() { var threadchannel = (object)this.Type switch { ChannelType.NewsThread => $"News thread {this.Name} ({this.Id})", ChannelType.PublicThread => $"Thread {this.Name} ({this.Id})", ChannelType.PrivateThread => $"Private thread {this.Name} ({this.Id})", _ => $"Thread {this.Name} ({this.Id})", }; return threadchannel; } #endregion /// /// Checks whether this is equal to another object. /// /// Object to compare to. /// Whether the object is equal to this . public override bool Equals(object obj) => this.Equals(obj as DiscordThreadChannel); /// /// Checks whether this is equal to another . /// /// to compare to. /// Whether the is equal to this . public bool Equals(DiscordThreadChannel e) => e is not null && (ReferenceEquals(this, e) || this.Id == e.Id); /// /// Gets the hash code for this . /// /// The hash code for this . public override int GetHashCode() => this.Id.GetHashCode(); /// /// Gets whether the two objects are equal. /// /// First channel to compare. /// Second channel to compare. /// Whether the two channels are equal. public static bool operator ==(DiscordThreadChannel e1, DiscordThreadChannel e2) { var o1 = e1 as object; var o2 = e2 as object; return (o1 != null || o2 == null) && (o1 == null || o2 != null) && ((o1 == null && o2 == null) || e1.Id == e2.Id); } /// /// Gets whether the two objects are not equal. /// /// First channel to compare. /// Second channel to compare. /// Whether the two channels are not equal. public static bool operator !=(DiscordThreadChannel e1, DiscordThreadChannel e2) => !(e1 == e2); } } diff --git a/DisCatSharp/Entities/Thread/DiscordThreadChannelMember.cs b/DisCatSharp/Entities/Thread/DiscordThreadChannelMember.cs index eb28f3d87..8ca671212 100644 --- a/DisCatSharp/Entities/Thread/DiscordThreadChannelMember.cs +++ b/DisCatSharp/Entities/Thread/DiscordThreadChannelMember.cs @@ -1,137 +1,137 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System; using System.Globalization; using Newtonsoft.Json; namespace DisCatSharp.Entities { /// /// Represents a discord thread member object. /// public class DiscordThreadChannelMember : SnowflakeObject, IEquatable { /// /// Gets the id of the user. /// [JsonProperty("user_id", NullValueHandling = NullValueHandling.Ignore)] public ulong UserId { get; internal set; } /// /// Gets the member object of the user. /// [JsonProperty("member", NullValueHandling = NullValueHandling.Ignore)] public DiscordMember Member { get; internal set; } /// /// Gets the presence of the user. /// [JsonProperty("presence", NullValueHandling = NullValueHandling.Ignore)] public DiscordPresence Presence { get; internal set; } /// /// Gets the timestamp when the user joined the thread. /// [JsonIgnore] public DateTimeOffset? JoinTimeStamp => !string.IsNullOrWhiteSpace(this.JoinTimeStampRaw) && DateTimeOffset.TryParse(this.JoinTimeStampRaw, CultureInfo.InvariantCulture, DateTimeStyles.None, out var dto) ? dto : null; /// /// Gets the timestamp when the user joined the thread as raw string. /// [JsonProperty("join_timestamp", NullValueHandling = NullValueHandling.Ignore)] internal string JoinTimeStampRaw { get; set; } /// /// Gets the thread member flags. /// [JsonProperty("flags", NullValueHandling = NullValueHandling.Ignore)] public ThreadMemberFlags Flags { get; internal set; } /// /// Gets the category that contains this channel. For threads, gets the channel this thread was created in. /// [JsonIgnore] public DiscordChannel Thread - => this.Guild != null ? (this.Guild._threads.TryGetValue(this.Id, out var thread) ? thread : null) : null; + => this.Guild != null ? (this.Guild.ThreadsInternal.TryGetValue(this.Id, out var thread) ? thread : null) : null; /// /// Gets the guild to which this channel belongs. /// [JsonIgnore] public DiscordGuild Guild - => this.Discord.Guilds.TryGetValue(this._guild_id, out var guild) ? guild : null; + => this.Discord.Guilds.TryGetValue(this.GuildId, out var guild) ? guild : null; [JsonIgnore] - internal ulong _guild_id; + internal ulong GuildId; /// /// 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 DiscordThreadChannelMember); /// /// Checks whether this is equal to another . /// /// to compare to. /// Whether the is equal to this . public bool Equals(DiscordThreadChannelMember e) => e is not null && (ReferenceEquals(this, e) || (this.Id == e.Id && this.UserId == e.UserId)); /// /// Gets the hash code for this . /// /// The hash code for this . public override int GetHashCode() => HashCode.Combine(this.Id.GetHashCode(), this.UserId.GetHashCode()); /// /// Gets whether the two objects are equal. /// /// First channel to compare. /// Second channel to compare. /// Whether the two channels are equal. public static bool operator ==(DiscordThreadChannelMember e1, DiscordThreadChannelMember e2) { var o1 = e1 as object; var o2 = e2 as object; return (o1 != null || o2 == null) && (o1 == null || o2 != null) && ((o1 == null && o2 == null) || (e1.Id == e2.Id && e1.UserId == e2.UserId)); } /// /// Gets whether the two objects are not equal. /// /// First channel to compare. /// Second channel to compare. /// Whether the two channels are not equal. public static bool operator !=(DiscordThreadChannelMember e1, DiscordThreadChannelMember e2) => !(e1 == e2); /// /// Initializes a new instance of the class. /// internal DiscordThreadChannelMember() { } } } diff --git a/DisCatSharp/Entities/User/DiscordPresence.cs b/DisCatSharp/Entities/User/DiscordPresence.cs index 318b49216..ce10a652d 100644 --- a/DisCatSharp/Entities/User/DiscordPresence.cs +++ b/DisCatSharp/Entities/User/DiscordPresence.cs @@ -1,149 +1,149 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System.Collections.Generic; using DisCatSharp.Net.Abstractions; using Newtonsoft.Json; namespace DisCatSharp.Entities { /// /// Represents a user presence. /// public sealed class DiscordPresence { /// /// Gets the discord client. /// [JsonIgnore] internal DiscordClient Discord { get; set; } // "The user object within this event can be partial, the only field which must be sent is the id field, everything else is optional." /// /// Gets the internal user. /// [JsonProperty("user", NullValueHandling = NullValueHandling.Ignore)] internal TransportUser InternalUser { get; set; } /// /// Gets the user that owns this presence. /// [JsonIgnore] public DiscordUser User => this.Discord.GetCachedOrEmptyUserInternal(this.InternalUser.Id); /// /// Gets the user's current activity. /// [JsonIgnore] public DiscordActivity Activity { get; internal set; } /// /// Gets the raw activity. /// internal TransportActivity RawActivity { get; set; } /// /// Gets the user's current activities. /// [JsonIgnore] - public IReadOnlyList Activities => this._internalActivities; + public IReadOnlyList Activities => this.InternalActivities; [JsonIgnore] - internal DiscordActivity[] _internalActivities; + internal DiscordActivity[] InternalActivities; /// /// Gets the raw activities. /// [JsonProperty("activities", NullValueHandling = NullValueHandling.Ignore)] internal TransportActivity[] RawActivities { get; set; } /// /// Gets this user's status. /// [JsonProperty("status", NullValueHandling = NullValueHandling.Ignore)] public UserStatus Status { get; internal set; } /// /// Gets the guild id for which this presence was set. /// [JsonProperty("guild_id", NullValueHandling = NullValueHandling.Ignore)] internal ulong GuildId { get; set; } /// /// Gets the guild for which this presence was set. /// [JsonIgnore] public DiscordGuild Guild - => this.GuildId != 0 ? this.Discord._guilds[this.GuildId] : null; + => this.GuildId != 0 ? this.Discord.GuildsInternal[this.GuildId] : null; /// /// Gets this user's platform-dependent status. /// [JsonProperty("client_status", NullValueHandling = NullValueHandling.Ignore)] public DiscordClientStatus ClientStatus { get; internal set; } /// /// Initializes a new instance of the class. /// internal DiscordPresence() { } /// /// Initializes a new instance of the class. /// /// The other. internal DiscordPresence(DiscordPresence other) { this.Discord = other.Discord; this.Activity = other.Activity; this.RawActivity = other.RawActivity; - this._internalActivities = (DiscordActivity[])other._internalActivities?.Clone(); + this.InternalActivities = (DiscordActivity[])other.InternalActivities?.Clone(); this.RawActivities = (TransportActivity[])other.RawActivities?.Clone(); this.Status = other.Status; this.InternalUser = new TransportUser(other.InternalUser); } } /// /// Represents a client status. /// public sealed class DiscordClientStatus { /// /// Gets the user's status set for an active desktop (Windows, Linux, Mac) application session. /// [JsonProperty("desktop", NullValueHandling = NullValueHandling.Ignore)] public Optional Desktop { get; internal set; } /// /// Gets the user's status set for an active mobile (iOS, Android) application session. /// [JsonProperty("mobile", NullValueHandling = NullValueHandling.Ignore)] public Optional Mobile { get; internal set; } /// /// Gets the user's status set for an active web (browser, bot account) application session. /// [JsonProperty("web", NullValueHandling = NullValueHandling.Ignore)] public Optional Web { get; internal set; } } } diff --git a/DisCatSharp/Entities/User/DiscordUser.cs b/DisCatSharp/Entities/User/DiscordUser.cs index 6b83b4ddc..661a7b1b2 100644 --- a/DisCatSharp/Entities/User/DiscordUser.cs +++ b/DisCatSharp/Entities/User/DiscordUser.cs @@ -1,433 +1,433 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System; using System.Collections.Generic; using System.Globalization; using System.Threading.Tasks; using DisCatSharp.Enums; using DisCatSharp.Exceptions; using DisCatSharp.Net; using DisCatSharp.Net.Abstractions; using Newtonsoft.Json; namespace DisCatSharp.Entities { /// /// Represents a Discord user. /// public class DiscordUser : SnowflakeObject, IEquatable { /// /// Initializes a new instance of the class. /// internal DiscordUser() { } /// /// Initializes a new instance of the class. /// /// The transport. internal DiscordUser(TransportUser transport) { this.Id = transport.Id; this.Username = transport.Username; this.Discriminator = transport.Discriminator; this.AvatarHash = transport.AvatarHash; this.BannerHash = transport.BannerHash; - this._bannerColor = transport.BannerColor; + this.BannerColorInternal = transport.BannerColor; this.IsBot = transport.IsBot; this.MfaEnabled = transport.MfaEnabled; this.Verified = transport.Verified; this.Email = transport.Email; this.PremiumType = transport.PremiumType; this.Locale = transport.Locale; this.Flags = transport.Flags; this.OAuthFlags = transport.OAuthFlags; this.Bio = transport.Bio; } /// /// Gets this user's username. /// [JsonProperty("username", NullValueHandling = NullValueHandling.Ignore)] public virtual string Username { get; internal set; } /// /// Gets this user's username with the discriminator. /// Example: Discord#0000 /// [JsonIgnore] public virtual string UsernameWithDiscriminator => $"{this.Username}#{this.Discriminator}"; /// /// Gets the user's 4-digit discriminator. /// [JsonProperty("discriminator", NullValueHandling = NullValueHandling.Ignore)] public virtual string Discriminator { get; internal set; } /// /// Gets the discriminator integer. /// [JsonIgnore] internal int DiscriminatorInt => int.Parse(this.Discriminator, NumberStyles.Integer, CultureInfo.InvariantCulture); /// /// Gets the user's banner color, if set. Mutually exclusive with . /// public virtual DiscordColor? BannerColor - => !this._bannerColor.HasValue ? null : new DiscordColor(this._bannerColor.Value); + => !this.BannerColorInternal.HasValue ? null : new DiscordColor(this.BannerColorInternal.Value); [JsonProperty("accent_color")] - internal int? _bannerColor; + internal int? BannerColorInternal; /// /// Gets the user's banner url /// [JsonIgnore] public string BannerUrl => string.IsNullOrWhiteSpace(this.BannerHash) ? null : $"{DiscordDomain.GetDomain(CoreDomain.DiscordCdn).Url}{Endpoints.BANNERS}/{this.Id.ToString(CultureInfo.InvariantCulture)}/{this.BannerHash}.{(this.BannerHash.StartsWith("a_") ? "gif" : "png")}?size=4096"; /// /// Gets the user's profile banner hash. Mutually exclusive with . /// [JsonProperty("banner", NullValueHandling = NullValueHandling.Ignore)] public virtual string BannerHash { get; internal set; } /// /// Gets the users bio. /// This is not available to bots tho. /// [JsonProperty("bio", NullValueHandling = NullValueHandling.Ignore)] public virtual string Bio { get; internal set; } /// /// Gets the user's avatar hash. /// [JsonProperty("avatar", NullValueHandling = NullValueHandling.Ignore)] public virtual string AvatarHash { get; internal set; } /// - /// Returns a uri to this users profile. + /// Returns a uri to this users profile. /// public Uri ProfileUri => new($"{DiscordDomain.GetDomain(CoreDomain.Discord).Url}{Endpoints.USERS}/{this.Id}"); /// - /// Returns a string representing the direct URL to this users profile. + /// Returns a string representing the direct URL to this users profile. /// /// The URL of this users profile. public string ProfileUrl => this.ProfileUri.AbsoluteUri; /// /// Gets the user's avatar URL.s /// [JsonIgnore] public string AvatarUrl => string.IsNullOrWhiteSpace(this.AvatarHash) ? this.DefaultAvatarUrl : $"{DiscordDomain.GetDomain(CoreDomain.DiscordCdn).Url}{Endpoints.AVATARS}/{this.Id.ToString(CultureInfo.InvariantCulture)}/{this.AvatarHash}.{(this.AvatarHash.StartsWith("a_") ? "gif" : "png")}?size=1024"; /// /// Gets the URL of default avatar for this user. /// [JsonIgnore] public string DefaultAvatarUrl => $"{DiscordDomain.GetDomain(CoreDomain.DiscordCdn).Url}{Endpoints.EMBED}{Endpoints.AVATARS}/{(this.DiscriminatorInt % 5).ToString(CultureInfo.InvariantCulture)}.png?size=1024"; /// /// Gets whether the user is a bot. /// [JsonProperty("bot", NullValueHandling = NullValueHandling.Ignore)] public virtual bool IsBot { get; internal set; } /// /// Gets whether the user has multi-factor authentication enabled. /// [JsonProperty("mfa_enabled", NullValueHandling = NullValueHandling.Ignore)] public virtual bool? MfaEnabled { get; internal set; } /// /// Gets whether the user is an official Discord system user. /// [JsonProperty("system", NullValueHandling = NullValueHandling.Ignore)] public bool? IsSystem { get; internal set; } /// /// Gets whether the user is verified. /// This is only present in OAuth. /// [JsonProperty("verified", NullValueHandling = NullValueHandling.Ignore)] public virtual bool? Verified { get; internal set; } /// /// Gets the user's email address. /// This is only present in OAuth. /// [JsonProperty("email", NullValueHandling = NullValueHandling.Ignore)] public virtual string Email { get; internal set; } /// /// Gets the user's premium type. /// [JsonProperty("premium_type", NullValueHandling = NullValueHandling.Ignore)] public virtual PremiumType? PremiumType { get; internal set; } /// /// Gets the user's chosen language /// [JsonProperty("locale", NullValueHandling = NullValueHandling.Ignore)] public virtual string Locale { get; internal set; } /// /// Gets the user's flags for OAuth. /// [JsonProperty("flags", NullValueHandling = NullValueHandling.Ignore)] public virtual UserFlags? OAuthFlags { get; internal set; } /// /// Gets the user's flags. /// [JsonProperty("public_flags", NullValueHandling = NullValueHandling.Ignore)] public virtual UserFlags? Flags { get; internal set; } /// /// Gets the user's mention string. /// [JsonIgnore] public string Mention => Formatter.Mention(this, this is DiscordMember); /// /// Gets whether this user is the Client which created this object. /// [JsonIgnore] public bool IsCurrent => this.Id == this.Discord.CurrentUser.Id; #region Extension of DiscordUser /// /// Whether this member is a /// /// [JsonIgnore] public bool IsMod => this.Flags.HasValue && this.Flags.Value.HasFlag(UserFlags.CertifiedModerator); /// /// Whether this member is a /// /// [JsonIgnore] public bool IsPartner => this.Flags.HasValue && this.Flags.Value.HasFlag(UserFlags.Partner); /// /// Whether this member is a /// /// [JsonIgnore] public bool IsVerifiedBot => this.Flags.HasValue && this.Flags.Value.HasFlag(UserFlags.VerifiedBot); /// /// Whether this member is a /// /// [JsonIgnore] public bool IsBotDev => this.Flags.HasValue && this.Flags.Value.HasFlag(UserFlags.VerifiedDeveloper); /// /// Whether this member is a /// /// [JsonIgnore] public bool IsStaff => this.Flags.HasValue && this.Flags.Value.HasFlag(UserFlags.Staff); #endregion /// /// Whether this user is in a /// /// /// /// DiscordGuild guild = await Client.GetGuildAsync(806675511555915806); /// DiscordUser user = await Client.GetUserAsync(469957180968271873); /// Console.WriteLine($"{user.Username} {(user.IsInGuild(guild) ? "is a" : "is not a")} member of {guild.Name}"); /// /// results to J_M_Lutra is a member of Project Nyaw~. /// /// /// [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "")] public async Task IsInGuild(DiscordGuild guild) { try { var member = await guild.GetMemberAsync(this.Id); return member is not null; } catch (NotFoundException) { return false; } } /// /// Whether this user is not in a /// /// /// [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "")] public async Task IsNotInGuild(DiscordGuild guild) => !await this.IsInGuild(guild); /// /// Unbans this user from a guild. /// /// Guild to unban this user from. /// 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 UnbanAsync(DiscordGuild guild, string reason = null) => guild.UnbanMemberAsync(this, reason); /// /// Gets this user's presence. /// [JsonIgnore] public DiscordPresence Presence => this.Discord is DiscordClient dc ? dc.Presences.TryGetValue(this.Id, out var presence) ? presence : null : null; /// /// Gets the user's avatar URL, in requested format and size. /// /// Format of the avatar to get. /// Maximum size of the avatar. Must be a power of two, minimum 16, maximum 2048. /// URL of the user's avatar. public string GetAvatarUrl(ImageFormat fmt, ushort size = 1024) { if (fmt == ImageFormat.Unknown) throw new ArgumentException("You must specify valid image format.", nameof(fmt)); if (size < 16 || size > 2048) throw new ArgumentOutOfRangeException(nameof(size)); var log = Math.Log(size, 2); if (log < 4 || log > 11 || log % 1 != 0) throw new ArgumentOutOfRangeException(nameof(size)); var sfmt = ""; sfmt = fmt switch { ImageFormat.Gif => "gif", ImageFormat.Jpeg => "jpg", ImageFormat.Png => "png", ImageFormat.WebP => "webp", ImageFormat.Auto => !string.IsNullOrWhiteSpace(this.AvatarHash) ? (this.AvatarHash.StartsWith("a_") ? "gif" : "png") : "png", _ => throw new ArgumentOutOfRangeException(nameof(fmt)), }; var ssize = size.ToString(CultureInfo.InvariantCulture); if (!string.IsNullOrWhiteSpace(this.AvatarHash)) { var id = this.Id.ToString(CultureInfo.InvariantCulture); return $"{DiscordDomain.GetDomain(CoreDomain.DiscordCdn).Url}{Endpoints.AVATARS}/{id}/{this.AvatarHash}.{sfmt}?size={ssize}"; } else { var type = (this.DiscriminatorInt % 5).ToString(CultureInfo.InvariantCulture); return $"{DiscordDomain.GetDomain(CoreDomain.DiscordCdn).Url}{Endpoints.EMBED}{Endpoints.AVATARS}/{type}.{sfmt}?size={ssize}"; } } /// /// Returns a string representation of this user. /// /// String representation of this user. public override string ToString() => $"User {this.Id}; {this.Username}#{this.Discriminator}"; /// /// 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 DiscordUser); /// /// Checks whether this is equal to another . /// /// to compare to. /// Whether the is equal to this . public bool Equals(DiscordUser 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 user to compare. /// Second user to compare. /// Whether the two users are equal. public static bool operator ==(DiscordUser e1, DiscordUser 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 user to compare. /// Second user to compare. /// Whether the two users are not equal. public static bool operator !=(DiscordUser e1, DiscordUser e2) => !(e1 == e2); } /// /// Represents a user comparer. /// internal class DiscordUserComparer : IEqualityComparer { /// /// Whether the users are equal. /// /// The first user /// The second user. public bool Equals(DiscordUser x, DiscordUser y) => x.Equals(y); /// /// Gets the hash code. /// /// The user. public int GetHashCode(DiscordUser obj) => obj.Id.GetHashCode(); } } diff --git a/DisCatSharp/Entities/Voice/DiscordVoiceState.cs b/DisCatSharp/Entities/Voice/DiscordVoiceState.cs index 12a7f2582..c8dcbcd30 100644 --- a/DisCatSharp/Entities/Voice/DiscordVoiceState.cs +++ b/DisCatSharp/Entities/Voice/DiscordVoiceState.cs @@ -1,213 +1,213 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System; using System.Globalization; using DisCatSharp.Net.Abstractions; using Newtonsoft.Json; namespace DisCatSharp.Entities { /// /// Represents a Discord voice state. /// public class DiscordVoiceState { /// /// Gets the discord client. /// internal DiscordClient Discord { get; set; } /// /// Gets ID of the guild this voice state is associated with. /// [JsonProperty("guild_id", NullValueHandling = NullValueHandling.Ignore)] internal ulong? GuildId { get; set; } /// /// Gets the guild associated with this voice state. /// [JsonIgnore] public DiscordGuild Guild => this.GuildId != null ? this.Discord.Guilds[this.GuildId.Value] : this.Channel?.Guild; /// /// Gets ID of the channel this user is connected to. /// [JsonProperty("channel_id", NullValueHandling = NullValueHandling.Include)] internal ulong? ChannelId { get; set; } /// /// Gets the channel this user is connected to. /// [JsonIgnore] public DiscordChannel Channel => this.ChannelId != null && this.ChannelId.Value != 0 ? this.Discord.InternalGetCachedChannel(this.ChannelId.Value) : null; /// /// Gets ID of the user to which this voice state belongs. /// [JsonProperty("user_id", NullValueHandling = NullValueHandling.Ignore)] internal ulong UserId { get; set; } /// /// Gets the user associated with this voice state. /// This can be cast to a if this voice state was in a guild. /// [JsonIgnore] public DiscordUser User { get { var usr = null as DiscordUser; if (this.Guild != null) - usr = this.Guild._members.TryGetValue(this.UserId, out var member) ? member : null; + usr = this.Guild.MembersInternal.TryGetValue(this.UserId, out var member) ? member : null; if (usr == null) usr = this.Discord.GetCachedOrEmptyUserInternal(this.UserId); return usr; } } /// /// Gets ID of the session of this voice state. /// [JsonProperty("session_id", NullValueHandling = NullValueHandling.Ignore)] internal string SessionId { get; set; } /// /// Gets whether this user is deafened. /// [JsonProperty("deaf", NullValueHandling = NullValueHandling.Ignore)] public bool IsServerDeafened { get; internal set; } /// /// Gets whether this user is muted. /// [JsonProperty("mute", NullValueHandling = NullValueHandling.Ignore)] public bool IsServerMuted { get; internal set; } /// /// Gets whether this user is locally deafened. /// [JsonProperty("self_deaf", NullValueHandling = NullValueHandling.Ignore)] public bool IsSelfDeafened { get; internal set; } /// /// Gets whether this user is locally muted. /// [JsonProperty("self_mute", NullValueHandling = NullValueHandling.Ignore)] public bool IsSelfMuted { get; internal set; } /// /// Gets whether this user's camera is enabled. /// [JsonProperty("self_video", NullValueHandling = NullValueHandling.Ignore)] public bool IsSelfVideo { get; internal set; } /// /// Gets whether this user is using the Go Live feature. /// [JsonProperty("self_stream", NullValueHandling = NullValueHandling.Ignore)] public bool IsSelfStream { get; internal set; } /// /// Gets whether the current user has suppressed this user. /// [JsonProperty("suppress", NullValueHandling = NullValueHandling.Ignore)] public bool IsSuppressed { get; internal set; } /// /// Gets the time at which this user requested to speak. /// [JsonProperty("request_to_speak_timestamp", NullValueHandling = NullValueHandling.Ignore)] internal DateTimeOffset? RequestToSpeakTimestamp { get; set; } /// /// Gets the member this voice state belongs to. /// [JsonIgnore] public DiscordMember Member => this.Guild.Members.TryGetValue(this.TransportMember.User.Id, out var member) ? member : new DiscordMember(this.TransportMember) { Discord = this.Discord }; /// /// Gets the transport member. /// [JsonProperty("member", NullValueHandling = NullValueHandling.Ignore)] internal TransportMember TransportMember { get; set; } /// /// Initializes a new instance of the class. /// internal DiscordVoiceState() { } // copy constructor for reduced boilerplate /// /// Initializes a new instance of the class. /// /// The other. internal DiscordVoiceState(DiscordVoiceState other) { this.Discord = other.Discord; this.UserId = other.UserId; this.ChannelId = other.ChannelId; this.GuildId = other.GuildId; this.IsServerDeafened = other.IsServerDeafened; this.IsServerMuted = other.IsServerMuted; this.IsSuppressed = other.IsSuppressed; this.IsSelfDeafened = other.IsSelfDeafened; this.IsSelfMuted = other.IsSelfMuted; this.IsSelfStream = other.IsSelfStream; this.IsSelfVideo = other.IsSelfVideo; this.SessionId = other.SessionId; this.RequestToSpeakTimestamp = other.RequestToSpeakTimestamp; } /// /// Initializes a new instance of the class. /// /// The m. internal DiscordVoiceState(DiscordMember m) { this.Discord = m.Discord as DiscordClient; this.UserId = m.Id; this.ChannelId = 0; - this.GuildId = m._guild_id; + this.GuildId = m.GuildId; this.IsServerDeafened = m.IsDeafened; this.IsServerMuted = m.IsMuted; // Values not filled out are values that are not known from a DiscordMember } /// /// Gets a readable voice state string. /// public override string ToString() => $"{this.UserId.ToString(CultureInfo.InvariantCulture)} in {(this.GuildId ?? this.Channel.GuildId.Value).ToString(CultureInfo.InvariantCulture)}"; } } diff --git a/DisCatSharp/Entities/Webhook/DiscordWebhook.cs b/DisCatSharp/Entities/Webhook/DiscordWebhook.cs index 77c5de1fd..ef91e91a2 100644 --- a/DisCatSharp/Entities/Webhook/DiscordWebhook.cs +++ b/DisCatSharp/Entities/Webhook/DiscordWebhook.cs @@ -1,287 +1,287 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System; using System.IO; using System.Threading.Tasks; using DisCatSharp.Enums; using DisCatSharp.Net; using Newtonsoft.Json; namespace DisCatSharp.Entities { /// /// Represents information about a Discord webhook. /// public class DiscordWebhook : SnowflakeObject, IEquatable { /// /// Gets the api client. /// internal DiscordApiClient ApiClient { get; set; } /// /// Gets the ID of the guild this webhook belongs to. /// [JsonProperty("guild_id", NullValueHandling = NullValueHandling.Ignore)] public ulong GuildId { get; internal set; } /// /// Gets the ID of the channel this webhook belongs to. /// [JsonProperty("channel_id", NullValueHandling = NullValueHandling.Ignore)] public ulong ChannelId { get; internal set; } /// /// Gets the user this webhook was created by. /// [JsonProperty("user", NullValueHandling = NullValueHandling.Ignore)] public DiscordUser User { get; internal set; } /// /// Gets the default name of this webhook. /// [JsonProperty("name", NullValueHandling = NullValueHandling.Ignore)] public string Name { get; internal set; } /// /// Gets hash of the default avatar for this webhook. /// [JsonProperty("avatar", NullValueHandling = NullValueHandling.Ignore)] internal string AvatarHash { get; set; } /// /// Gets the partial source guild for this webhook (For Channel Follower Webhooks). /// [JsonProperty("source_guild", NullValueHandling = NullValueHandling.Ignore)] public DiscordGuild SourceGuild { get; set; } /// /// Gets the partial source channel for this webhook (For Channel Follower Webhooks). /// [JsonProperty("source_channel", NullValueHandling = NullValueHandling.Ignore)] public DiscordChannel SourceChannel { get; set; } /// /// Gets the url used for executing the webhook. /// [JsonProperty("url", NullValueHandling = NullValueHandling.Ignore)] public string Url { get; set; } /// /// Gets the default avatar url for this webhook. /// public string AvatarUrl => !string.IsNullOrWhiteSpace(this.AvatarHash) ? $"{DiscordDomain.GetDomain(CoreDomain.DiscordCdn).Url}{Endpoints.AVATARS}/{this.Id}/{this.AvatarHash}.png?size=1024" : null; /// /// Gets the secure token of this webhook. /// [JsonProperty("token", NullValueHandling = NullValueHandling.Ignore)] public string Token { get; internal set; } /// /// Initializes a new instance of the class. /// internal DiscordWebhook() { } /// /// Modifies this webhook. /// /// New default name for this webhook. /// New avatar for this webhook. /// The new channel id to move the webhook to. /// Reason for audit logs. /// The modified webhook. /// Thrown when the client does not have the permission. /// Thrown when the webhook does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task ModifyAsync(string name = null, Optional avatar = default, ulong? channelId = null, string reason = null) { var avatarb64 = Optional.FromNoValue(); if (avatar.HasValue && avatar.Value != null) using (var imgtool = new ImageTool(avatar.Value)) avatarb64 = imgtool.GetBase64(); else if (avatar.HasValue) avatarb64 = null; var newChannelId = channelId ?? this.ChannelId; return this.Discord.ApiClient.ModifyWebhookAsync(this.Id, newChannelId, name, avatarb64, reason); } /// /// Gets a previously-sent webhook message. /// /// Thrown when the webhook does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public async Task GetMessageAsync(ulong messageId) => await (this.Discord?.ApiClient ?? this.ApiClient).GetWebhookMessageAsync(this.Id, this.Token, messageId).ConfigureAwait(false); /// /// Gets a previously-sent webhook message. /// /// Thrown when the webhook does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public async Task GetMessageAsync(ulong messageId, ulong threadId) => await (this.Discord?.ApiClient ?? this.ApiClient).GetWebhookMessageAsync(this.Id, this.Token, messageId, threadId).ConfigureAwait(false); /// /// Permanently deletes this webhook. /// /// /// Thrown when the client does not have the permission. /// Thrown when the webhook does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task DeleteAsync() => this.Discord.ApiClient.DeleteWebhookAsync(this.Id, this.Token); /// /// Executes this webhook with the given . /// /// Webhook builder filled with data to send. /// Target thread id (Optional). Defaults to null. /// Thrown when the webhook does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. - public Task ExecuteAsync(DiscordWebhookBuilder builder, string thread_id = null) - => (this.Discord?.ApiClient ?? this.ApiClient).ExecuteWebhookAsync(this.Id, this.Token, builder, thread_id); + public Task ExecuteAsync(DiscordWebhookBuilder builder, string threadId = null) + => (this.Discord?.ApiClient ?? this.ApiClient).ExecuteWebhookAsync(this.Id, this.Token, builder, threadId); /// /// Executes this webhook in Slack compatibility mode. /// /// JSON containing Slack-compatible payload for this webhook. /// Target thread id (Optional). Defaults to null. /// /// Thrown when the webhook does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. - public Task ExecuteSlackAsync(string json, string thread_id = null) - => (this.Discord?.ApiClient ?? this.ApiClient).ExecuteWebhookSlackAsync(this.Id, this.Token, json, thread_id); + public Task ExecuteSlackAsync(string json, string threadId = null) + => (this.Discord?.ApiClient ?? this.ApiClient).ExecuteWebhookSlackAsync(this.Id, this.Token, json, threadId); /// /// Executes this webhook in GitHub compatibility mode. /// /// JSON containing GitHub-compatible payload for this webhook. /// Target thread id (Optional). Defaults to null. /// /// Thrown when the webhook does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. - public Task ExecuteGithubAsync(string json, string thread_id = null) - => (this.Discord?.ApiClient ?? this.ApiClient).ExecuteWebhookGithubAsync(this.Id, this.Token, json, thread_id); + public Task ExecuteGithubAsync(string json, string threadId = null) + => (this.Discord?.ApiClient ?? this.ApiClient).ExecuteWebhookGithubAsync(this.Id, this.Token, json, threadId); /// /// Edits a previously-sent webhook message. /// /// The id of the message to edit. /// The builder of the message to edit. /// Target thread id (Optional). Defaults to null. /// The modified /// Thrown when the webhook does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. - public async Task EditMessageAsync(ulong messageId, DiscordWebhookBuilder builder, string thread_id = null) + public async Task EditMessageAsync(ulong messageId, DiscordWebhookBuilder builder, string threadId = null) { builder.Validate(true); - if (builder._keepAttachments.HasValue && builder._keepAttachments.Value) + if (builder.KeepAttachmentsInternal.HasValue && builder.KeepAttachmentsInternal.Value) { - builder._attachments.AddRange(this.ApiClient.GetWebhookMessageAsync(this.Id, this.Token, messageId.ToString(), thread_id).Result.Attachments); + builder.AttachmentsInternal.AddRange(this.ApiClient.GetWebhookMessageAsync(this.Id, this.Token, messageId.ToString(), threadId).Result.Attachments); } - else if (builder._keepAttachments.HasValue) + else if (builder.KeepAttachmentsInternal.HasValue) { - builder._attachments.Clear(); + builder.AttachmentsInternal.Clear(); } - return await (this.Discord?.ApiClient ?? this.ApiClient).EditWebhookMessageAsync(this.Id, this.Token, messageId.ToString(), builder, thread_id).ConfigureAwait(false); + return await (this.Discord?.ApiClient ?? this.ApiClient).EditWebhookMessageAsync(this.Id, this.Token, messageId.ToString(), builder, threadId).ConfigureAwait(false); } /// /// Deletes a message that was created by the webhook. /// /// The id of the message to delete /// /// Thrown when the webhook does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task DeleteMessageAsync(ulong messageId) => (this.Discord?.ApiClient ?? this.ApiClient).DeleteWebhookMessageAsync(this.Id, this.Token, messageId); /// /// Deletes a message that was created by the webhook. /// /// The id of the message to delete /// Target thread id (Optional). Defaults to null. /// /// Thrown when the webhook does not exist. /// Thrown when an invalid parameter was provided. /// Thrown when Discord is unable to process the request. public Task DeleteMessageAsync(ulong messageId, ulong threadId) => (this.Discord?.ApiClient ?? this.ApiClient).DeleteWebhookMessageAsync(this.Id, this.Token, messageId, threadId); /// /// 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 DiscordWebhook); /// /// Checks whether this is equal to another . /// /// to compare to. /// Whether the is equal to this . public bool Equals(DiscordWebhook 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 webhook to compare. /// Second webhook to compare. /// Whether the two webhooks are equal. public static bool operator ==(DiscordWebhook e1, DiscordWebhook 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 webhook to compare. /// Second webhook to compare. /// Whether the two webhooks are not equal. public static bool operator !=(DiscordWebhook e1, DiscordWebhook e2) => !(e1 == e2); } } diff --git a/DisCatSharp/Entities/Webhook/DiscordWebhookBuilder.cs b/DisCatSharp/Entities/Webhook/DiscordWebhookBuilder.cs index 384912093..f2170ef18 100644 --- a/DisCatSharp/Entities/Webhook/DiscordWebhookBuilder.cs +++ b/DisCatSharp/Entities/Webhook/DiscordWebhookBuilder.cs @@ -1,442 +1,442 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; namespace DisCatSharp.Entities { /// /// Constructs ready-to-send webhook requests. /// public sealed class DiscordWebhookBuilder { /// /// Username to use for this webhook request. /// public Optional Username { get; set; } /// /// Avatar url to use for this webhook request. /// public Optional AvatarUrl { get; set; } /// /// Whether this webhook request is text-to-speech. /// - public bool IsTTS { get; set; } + public bool IsTts { get; set; } /// /// Message to send on this webhook request. /// public string Content { get => this._content; set { if (value != null && value.Length > 2000) throw new ArgumentException("Content length cannot exceed 2000 characters.", nameof(value)); this._content = value; } } private string _content; /// /// Whether to keep previous attachments. /// - internal bool? _keepAttachments = null; + internal bool? KeepAttachmentsInternal = null; /// /// Embeds to send on this webhook request. /// public IReadOnlyList Embeds => this._embeds; private readonly List _embeds = new(); /// /// Files to send on this webhook request. /// public IReadOnlyList Files => this._files; private readonly List _files = new(); /// /// Mentions to send on this webhook request. /// public IReadOnlyList Mentions => this._mentions; private readonly List _mentions = new(); /// /// Gets the components. /// public IReadOnlyList Components => this._components; private readonly List _components = new(); /// /// Attachments to keep on this webhook request. /// - public IEnumerable Attachments => this._attachments; - internal readonly List _attachments = new(); + public IEnumerable Attachments => this.AttachmentsInternal; + internal readonly List AttachmentsInternal = new(); /// /// Constructs a new empty webhook request builder. /// public DiscordWebhookBuilder() { } // I still see no point in initializing collections with empty collections. // /// /// Adds a row of components to the builder, up to 5 components per row, and up to 5 rows per message. /// /// The components to add to the builder. /// The current builder to be chained. /// No components were passed. public DiscordWebhookBuilder AddComponents(params DiscordComponent[] components) => this.AddComponents((IEnumerable)components); /// /// Appends several rows of components to the builder /// /// The rows of components to add, holding up to five each. /// public DiscordWebhookBuilder AddComponents(IEnumerable components) { var ara = components.ToArray(); if (ara.Length + this._components.Count > 5) throw new ArgumentException("ActionRow count exceeds maximum of five."); foreach (var ar in ara) this._components.Add(ar); return this; } /// /// Adds a row of components to the builder, up to 5 components per row, and up to 5 rows per message. /// /// The components to add to the builder. /// The current builder to be chained. /// No components were passed. public DiscordWebhookBuilder AddComponents(IEnumerable components) { var cmpArr = components.ToArray(); var count = cmpArr.Length; if (!cmpArr.Any()) throw new ArgumentOutOfRangeException(nameof(components), "You must provide at least one component"); if (count > 5) throw new ArgumentException("Cannot add more than 5 components per action row!"); var comp = new DiscordActionRowComponent(cmpArr); this._components.Add(comp); return this; } /// /// Sets the username for this webhook builder. /// /// Username of the webhook public DiscordWebhookBuilder WithUsername(string username) { this.Username = username; return this; } /// /// Sets the avatar of this webhook builder from its url. /// /// Avatar url of the webhook public DiscordWebhookBuilder WithAvatarUrl(string avatarUrl) { this.AvatarUrl = avatarUrl; return this; } /// /// Indicates if the webhook must use text-to-speech. /// /// Text-to-speech - public DiscordWebhookBuilder WithTTS(bool tts) + public DiscordWebhookBuilder WithTts(bool tts) { - this.IsTTS = tts; + this.IsTts = tts; return this; } /// /// Sets the message to send at the execution of the webhook. /// /// Message to send. public DiscordWebhookBuilder WithContent(string content) { this.Content = content; return this; } /// /// Adds an embed to send at the execution of the webhook. /// /// Embed to add. public DiscordWebhookBuilder AddEmbed(DiscordEmbed embed) { if (embed != null) this._embeds.Add(embed); return this; } /// /// Adds the given embeds to send at the execution of the webhook. /// /// Embeds to add. public DiscordWebhookBuilder AddEmbeds(IEnumerable embeds) { this._embeds.AddRange(embeds); return this; } /// /// Adds a file to send at the execution of the webhook. /// /// Name of the file. /// File data. /// Tells the API Client to reset the stream position to what it was after the file is sent. /// Description of the file. public DiscordWebhookBuilder AddFile(string filename, Stream data, bool resetStreamPosition = false, string description = null) { if (this.Files.Count() > 10) throw new ArgumentException("Cannot send more than 10 files with a single message."); if (this._files.Any(x => x.FileName == filename)) throw new ArgumentException("A File with that filename already exists"); if (resetStreamPosition) this._files.Add(new DiscordMessageFile(filename, data, data.Position, description: description)); else this._files.Add(new DiscordMessageFile(filename, data, null, description: description)); return this; } /// /// Sets if the message has files to be sent. /// /// The Stream to the file. /// Tells the API Client to reset the stream position to what it was after the file is sent. /// Description of the file. /// public DiscordWebhookBuilder AddFile(FileStream stream, bool resetStreamPosition = false, string description = null) { if (this.Files.Count() > 10) throw new ArgumentException("Cannot send more than 10 files with a single message."); if (this._files.Any(x => x.FileName == stream.Name)) throw new ArgumentException("A File with that filename already exists"); if (resetStreamPosition) this._files.Add(new DiscordMessageFile(stream.Name, stream, stream.Position, description: description)); else this._files.Add(new DiscordMessageFile(stream.Name, stream, null, description: description)); return this; } /// /// Adds the given files to send at the execution of the webhook. /// /// Dictionary of file name and file data. /// Tells the API Client to reset the stream position to what it was after the file is sent. public DiscordWebhookBuilder AddFiles(Dictionary files, bool resetStreamPosition = false) { if (this.Files.Count() + files.Count() > 10) throw new ArgumentException("Cannot send more than 10 files with a single message."); foreach (var file in files) { if (this._files.Any(x => x.FileName == file.Key)) throw new ArgumentException("A File with that filename already exists"); if (resetStreamPosition) this._files.Add(new DiscordMessageFile(file.Key, file.Value, file.Value.Position)); else this._files.Add(new DiscordMessageFile(file.Key, file.Value, null)); } return this; } /// /// Modifies the given attachments on edit. /// /// Attachments to edit. /// public DiscordWebhookBuilder ModifyAttachments(IEnumerable attachments) { - this._attachments.AddRange(attachments); + this.AttachmentsInternal.AddRange(attachments); return this; } /// /// Whether to keep the message attachments, if new ones are added. /// /// public DiscordWebhookBuilder KeepAttachments(bool keep) { - this._keepAttachments = keep; + this.KeepAttachmentsInternal = keep; return this; } /// /// Adds the mention to the mentions to parse, etc. at the execution of the webhook. /// /// Mention to add. public DiscordWebhookBuilder AddMention(IMention mention) { this._mentions.Add(mention); return this; } /// /// Adds the mentions to the mentions to parse, etc. at the execution of the webhook. /// /// Mentions to add. public DiscordWebhookBuilder AddMentions(IEnumerable mentions) { this._mentions.AddRange(mentions); return this; } /// /// Executes a webhook. /// /// The webhook that should be executed. /// The message sent public async Task SendAsync(DiscordWebhook webhook) => await webhook.ExecuteAsync(this).ConfigureAwait(false); /// /// Executes a webhook. /// /// The webhook that should be executed. /// Target thread id. /// The message sent public async Task SendAsync(DiscordWebhook webhook, ulong threadId) => await webhook.ExecuteAsync(this, threadId.ToString()).ConfigureAwait(false); /// /// Sends the modified webhook message. /// /// The webhook that should be executed. /// The message to modify. /// The modified message public async Task ModifyAsync(DiscordWebhook webhook, DiscordMessage message) => await this.ModifyAsync(webhook, message.Id).ConfigureAwait(false); /// /// Sends the modified webhook message. /// /// The webhook that should be executed. /// The id of the message to modify. /// The modified message public async Task ModifyAsync(DiscordWebhook webhook, ulong messageId) => await webhook.EditMessageAsync(messageId, this).ConfigureAwait(false); /// /// Sends the modified webhook message. /// /// The webhook that should be executed. /// The message to modify. /// Target thread. /// The modified message public async Task ModifyAsync(DiscordWebhook webhook, DiscordMessage message, DiscordThreadChannel thread) => await this.ModifyAsync(webhook, message.Id, thread.Id).ConfigureAwait(false); /// /// Sends the modified webhook message. /// /// The webhook that should be executed. /// The id of the message to modify. /// Target thread id. /// The modified message public async Task ModifyAsync(DiscordWebhook webhook, ulong messageId, ulong threadId) => await webhook.EditMessageAsync(messageId, this, threadId.ToString()).ConfigureAwait(false); /// /// Clears all message components on this builder. /// public void ClearComponents() => this._components.Clear(); /// /// Allows for clearing the Webhook Builder so that it can be used again to send a new message. /// public void Clear() { this.Content = ""; this._embeds.Clear(); - this.IsTTS = false; + this.IsTts = false; this._mentions.Clear(); this._files.Clear(); - this._attachments.Clear(); + this.AttachmentsInternal.Clear(); this._components.Clear(); - this._keepAttachments = false; + this.KeepAttachmentsInternal = false; } /// /// Does the validation before we send a the Create/Modify request. /// /// Tells the method to perform the Modify Validation or Create Validation. /// Tells the method to perform the follow up message validation. /// Tells the method to perform the interaction response validation. internal void Validate(bool isModify = false, bool isFollowup = false, bool isInteractionResponse = false) { if (isModify) { if (this.Username.HasValue) throw new ArgumentException("You cannot change the username of a message."); if (this.AvatarUrl.HasValue) throw new ArgumentException("You cannot change the avatar of a message."); } else if (isFollowup) { if (this.Username.HasValue) throw new ArgumentException("You cannot change the username of a follow up message."); if (this.AvatarUrl.HasValue) throw new ArgumentException("You cannot change the avatar of a follow up message."); } else if (isInteractionResponse) { if (this.Username.HasValue) throw new ArgumentException("You cannot change the username of an interaction response."); if (this.AvatarUrl.HasValue) throw new ArgumentException("You cannot change the avatar of an interaction response."); } else { if (this.Files?.Count == 0 && string.IsNullOrEmpty(this.Content) && !this.Embeds.Any()) throw new ArgumentException("You must specify content, an embed, or at least one file."); } } } } diff --git a/DisCatSharp/Enums/Discord/DiscordDomain.cs b/DisCatSharp/Enums/Discord/DiscordDomain.cs index dd292bbf4..50263b720 100644 --- a/DisCatSharp/Enums/Discord/DiscordDomain.cs +++ b/DisCatSharp/Enums/Discord/DiscordDomain.cs @@ -1,269 +1,269 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System; using System.Linq; namespace DisCatSharp.Enums { /// /// Core Domains /// public enum CoreDomain { /// /// dis.gd /// [DomainHelp("Marketing URL shortner", "dis.gd")] DiscordMarketing = 1, /// /// discord.co /// [DomainHelp("Admin panel, internal tools", "discord.co")] DiscordAdmin = 2, /// /// discord.com /// [DomainHelp("New app, marketing website, API host", "discord.com")] Discord = 3, /// /// discord.design /// [DomainHelp("Dribbble profile shortlink", "discord.design")] DiscordDesign = 4, /// /// discord.dev /// [DomainHelp("Developer site shortlinks", "discord.dev")] DiscordDev = 5, /// /// discord.gg /// [DomainHelp("Invite shortlinks", "discord.gg")] DiscordShortlink = 6, /// /// discord.gift /// [DomainHelp("Gift shortlinks", "discord.gift")] DiscordGift = 7, /// /// discord.media /// [DomainHelp("Voice servers", "discord.media")] DiscordMedia = 8, /// /// discord.new /// [DomainHelp("Template shortlinks", "discord.new")] DiscordTemplate = 9, /// /// discord.store /// [DomainHelp("Merch store", "discord.store")] DiscordMerch = 10, /// /// discord.tools /// [DomainHelp("Internal tools", "discord.tools")] DiscordTools = 11, /// /// discordapp.com /// [DomainHelp("Old app, marketing website, and API; CDN", "discordapp.com")] DiscordAppOld = 12, /// /// discordapp.net /// [DomainHelp("Media Proxy", "discordapp.net")] DiscordAppMediaProxy = 13, /// /// discordmerch.com /// [DomainHelp("Merch store", "discordmerch.com")] DiscordMerchOld = 14, /// /// discordpartygames.com /// [DomainHelp("Voice channel activity API host", "discordpartygames.com")] DiscordActivityAlt = 15, /// /// discord-activities.com /// [DomainHelp("Voice channel activity API host", "discord-activities.com")] DiscordActivityAlt2 = 16, /// /// discordsays.com /// [DomainHelp("Voice channel activity host", "discordsays.com")] DiscordActivity = 17, /// /// discordstatus.com /// [DomainHelp("Status page", "discordstatus.com")] DiscordStatus = 18, /// /// cdn.discordapp.com /// [DomainHelp("CDN", "cdn.discordapp.com")] DiscordCdn = 19, } /// /// Other Domains /// public enum OtherDomain { /// /// airhorn.solutions /// [DomainHelp("API implementation example", "airhorn.solutions")] Airhorn = 1, /// /// airhornbot.com /// [DomainHelp("API implementation example", "airhornbot.com")] AirhornAlt = 2, /// /// bigbeans.solutions /// [DomainHelp("April Fools 2017", "bigbeans.solutions")] AprilFools = 3, /// /// watchanimeattheoffice.com /// [DomainHelp("HypeSquad form placeholder/meme", "watchanimeattheoffice.com")] HypeSquadMeme = 4 } /// /// Core Domains /// public enum UnusedDomain { /// /// discordapp.io /// [Obsolete("Not in use", false)] [DomainHelp("IO domain for discord", "discordapp.io")] DiscordAppIo = 1, /// /// discordcdn.com /// [Obsolete("Not in use", false)] [DomainHelp("Alternative CDN domain", "discordcdn.com")] DiscordCdnCom = 2 } /// /// Represents a discord domain. /// public static class DiscordDomain { /// /// Gets a domain. /// Valid types: , and . /// - /// The domain type. + /// The domain type. /// A DomainHelpAttribute. - public static DomainHelpAttribute GetDomain(Enum DomainEnum) + public static DomainHelpAttribute GetDomain(Enum domainEnum) { - if (DomainEnum is not CoreDomain && DomainEnum is not OtherDomain && DomainEnum is not UnusedDomain) - throw new NotSupportedException($"Invalid type. Found: {DomainEnum.GetType()} Expected: CoreDomain or OtherDomain or UnusedDomain"); + if (domainEnum is not CoreDomain && domainEnum is not OtherDomain && domainEnum is not UnusedDomain) + throw new NotSupportedException($"Invalid type. Found: {domainEnum.GetType()} Expected: CoreDomain or OtherDomain or UnusedDomain"); - if (DomainEnum is CoreDomain domain && (domain == CoreDomain.DiscordAdmin || domain == CoreDomain.DiscordTools)) + if (domainEnum is CoreDomain domain && (domain == CoreDomain.DiscordAdmin || domain == CoreDomain.DiscordTools)) throw new UnauthorizedAccessException("You don't have access to this domains"); - var memberInfo = DomainEnum.GetType().GetMember(DomainEnum.ToString()).FirstOrDefault(); + var memberInfo = domainEnum.GetType().GetMember(domainEnum.ToString()).FirstOrDefault(); if (memberInfo != null) { var attribute = (DomainHelpAttribute)memberInfo.GetCustomAttributes(typeof(DomainHelpAttribute), false).FirstOrDefault(); return attribute; } return null; } } /// /// Defines a description and url for this domain. /// [AttributeUsage(AttributeTargets.All, AllowMultiple = false)] public class DomainHelpAttribute : Attribute { /// /// Gets the Description for this domain. /// public string Description { get; } /// /// Gets the Uri for this domain. /// public Uri Uri { get; } /// /// Gets the Domain for this domain. /// public string Domain { get; } /// /// Gets the Url for this domain. /// public string Url { get; } /// /// Defines a description and URIs for this domain. /// /// Description for this domain. /// Url for this domain. public DomainHelpAttribute(string desc, string domain) { this.Description = desc; this.Domain = domain; var url = $"https://{domain}"; this.Url = url; this.Uri = new(url); } } } diff --git a/DisCatSharp/Enums/Guild/NsfwLevel.cs b/DisCatSharp/Enums/Guild/NsfwLevel.cs index a71a47378..86956e3fe 100644 --- a/DisCatSharp/Enums/Guild/NsfwLevel.cs +++ b/DisCatSharp/Enums/Guild/NsfwLevel.cs @@ -1,50 +1,51 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. +// ReSharper disable InconsistentNaming namespace DisCatSharp { /// /// Represents a guild's conetent level. /// public enum NsfwLevel { /// /// Indicates the guild has no special NSFW level. /// Default = 0, /// /// Indicates the guild has extremely suggestive or mature content that would only be suitable for users over 18 /// Explicit = 1, /// /// Indicates the guild has no content that could be deemed NSFW. It is SFW. /// Safe = 2, /// /// Indicates the guild has mildly NSFW content that may not be suitable for users under 18. /// Age_Restricted = 3 } } diff --git a/DisCatSharp/Enums/Invite/TargetActivity.cs b/DisCatSharp/Enums/Invite/TargetActivity.cs index 9d92d9ba6..d13d7acbf 100644 --- a/DisCatSharp/Enums/Invite/TargetActivity.cs +++ b/DisCatSharp/Enums/Invite/TargetActivity.cs @@ -1,215 +1,216 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. +// ReSharper disable InconsistentNaming namespace DisCatSharp { /// /// Represents the activity this invite is for. /// public enum TargetActivity : ulong { /// /// Represents no embedded application. /// None = 0, /// /// Represents the embedded application Betrayal.io. /// Betrayal = 773336526917861400, /// /// Represents the embedded application Chess in the park. /// Dev? /// ChessInThePark = 832012774040141894, /// /// Represents the embedded application Doodle Crew. /// DoodleCrew = 878067389634314250, /// /// Represents the embedded application Fishington.io. /// Fishington = 814288819477020702, /// /// Represents the embedded application Letter Tile. /// LetterTile = 879863686565621790, /// /// Represents the embedded application Poker Night. /// PokerNight = 755827207812677713, /// /// Represents the embedded application Spell Cast. /// SpellCast = 852509694341283871, /// /// Represents the embedded application Watch Together. /// WatchTogether = 880218394199220334, /// /// Represents the embedded application Watch Together. /// This is the dev version. /// WatchTogetherDev = 880218832743055411, /// /// Represents the embedded application Word Snacks. /// WordSnacks = 879863976006127627, /// /// Represents the embedded application YouTube Together. /// YouTubeTogether = 755600276941176913, #region New Prod /// /// Represents the embedded application Awkword. /// Awkword = 879863881349087252, /// /// Represents the embedded application Putts. /// Putts = 832012854282158180, /// /// Represents the embedded application CG3 Prod. /// CG3Prod = 832013003968348200, /// /// Represents the embedded application CG4 Prod. /// CG4Prod = 832025144389533716, /// /// Represents the embedded application Sketchy Artist. /// SketchyArtist = 879864070101172255, #endregion #region New Dev /// /// Represents the embedded application Sketchy Artist. /// This is the dev version. /// SketchyArtistDev = 879864104980979792, /// /// Represents the embedded application Word Snacks. /// This is the dev version. /// WordSnacksDev = 879864010126786570, /// /// Represents the embedded application Doodle Crew. /// This is the dev version. /// DoodleCrewDev = 878067427668275241, /// /// Represents the embedded application Chess in the park. /// This is the dev version. /// ChessInTheParkDev = 832012586023256104, /// /// Represents the embedded application CG3 Dev. /// This is the dev version. /// CG3Dev = 832012682520428625, /// /// Represents the embedded application CG4 Dev. /// This is the dev version. /// CG4kDev = 832013108234289153, /// /// Represents the embedded application Decoders. /// This is the dev version. /// DecodersDev = 891001866073296967, #endregion #region New Staging /// /// Represents the embedded application PN. /// This is the staging version. /// PNStaging = 763116274876022855, /// /// Represents the embedded application CG2. /// This is the staging version. /// CG2Staging = 832012730599735326, /// /// Represents the embedded application CG3. /// This is the staging version. /// CG3Staging = 832012938398400562, /// /// Represents the embedded application CG4. /// This is the staging version. /// CG4Staging = 832025061657280566, #endregion #region New QA /// /// Represents the embedded application Poker. /// This is the QA version. /// PokerQA = 801133024841957428, /// /// Represents the embedded application CG2. /// This is the QA version. /// CG2QA = 832012815819604009, /// /// Represents the embedded application CG 3. /// This is the QA version. /// CG3QA = 832012894068801636, /// /// Represents the embedded application CG 4. /// This is the QA version. /// CG4QA = 832025114077298718, #endregion } } diff --git a/DisCatSharp/Enums/OAuth.cs b/DisCatSharp/Enums/OAuth.cs index 02fc5c1fa..f0606566f 100644 --- a/DisCatSharp/Enums/OAuth.cs +++ b/DisCatSharp/Enums/OAuth.cs @@ -1,119 +1,120 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. +// ReSharper disable InconsistentNaming namespace DisCatSharp.Enums { /// /// The oauth scopes. /// public static class OAuth { /// /// The default scopes for bots. /// private const string BOT_DEFAULT = "bot applications.commands applications.commands.permissions.update"; /// /// The bot minimal scopes. /// private const string BOT_MINIMAL = "bot applications.commands"; /// /// The bot only scope. /// private const string BOT_ONLY = "bot"; /// /// The basic identify scopes. /// private const string IDENTIFY_BASIC = "identify email"; /// /// The extended identify scopes. /// private const string IDENTIFY_EXTENDED = "identify email guilds connections"; /// /// All scopes for bots and identify. /// private const string ALL = BOT_DEFAULT + " " + IDENTIFY_EXTENDED; /// /// The oauth scope. /// /// /// Resolves the scopes. /// /// The scope. /// A string representing the scopes. public static string ResolveScopes(OAuthScopes scope) { return scope switch { OAuthScopes.BOT_DEFAULT => BOT_DEFAULT, OAuthScopes.BOT_MINIMAL => BOT_MINIMAL, OAuthScopes.BOT_ONLY => BOT_ONLY, OAuthScopes.IDENTIFY_BASIC => IDENTIFY_BASIC, OAuthScopes.IDENTIFY_EXTENDED => IDENTIFY_EXTENDED, OAuthScopes.ALL => ALL, _ => BOT_DEFAULT, }; } } /// /// The oauth scopes. /// public enum OAuthScopes { /// /// Scopes: bot applications.commands applications.commands.permissions.update /// BOT_DEFAULT = 0, /// /// Scopes: bot applications.commands /// BOT_MINIMAL = 1, /// /// Scopes: bot /// BOT_ONLY = 2, /// /// Scopes: identify email /// IDENTIFY_BASIC = 3, /// /// Scopes: identify email guilds connections /// IDENTIFY_EXTENDED = 4, /// /// Scopes: bot applications.commands applications.commands.permissions.update identify email guilds connections /// ALL = 5 } } diff --git a/DisCatSharp/Enums/Permission.cs b/DisCatSharp/Enums/Permission.cs index 703c34316..852bdeb45 100644 --- a/DisCatSharp/Enums/Permission.cs +++ b/DisCatSharp/Enums/Permission.cs @@ -1,363 +1,363 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System; namespace DisCatSharp { /// /// Represents permission methods. /// public static class PermissionMethods { /// /// Gets the full permissions enum (long). /// - internal static Permissions FULL_PERMS { get; } = (Permissions)2199023255551L; + internal static Permissions FullPerms { get; } = (Permissions)2199023255551L; /// /// Calculates whether this permission set contains the given permission. /// /// The permissions to calculate from /// permission you want to check /// public static bool HasPermission(this Permissions p, Permissions permission) => p.HasFlag(Permissions.Administrator) || (p & permission) == permission; /// /// Grants permissions. /// /// The permissions to add to. /// Permission to add. /// public static Permissions Grant(this Permissions p, Permissions grant) => p | grant; /// /// Revokes permissions. /// /// The permissions to take from. /// Permission to take. /// public static Permissions Revoke(this Permissions p, Permissions revoke) => p & ~revoke; } /// /// Whether a permission is allowed, denied or unset /// public enum PermissionLevel { /// /// Said permission is Allowed /// Allowed, /// /// Said permission is Denied /// Denied, /// /// Said permission is Unset /// Unset } /// /// Bitwise permission flags. /// [Flags] public enum Permissions : long { /// /// Indicates no permissions given. /// [PermissionString("No permissions")] None = 0x0000000000000000, /// /// Indicates all permissions are granted /// [PermissionString("All permissions")] All = 2199023255551, /// /// Allows creation of instant channel invites. /// [PermissionString("Create instant invites")] CreateInstantInvite = 0x0000000000000001, /// /// Allows kicking members. /// [PermissionString("Kick members")] KickMembers = 0x0000000000000002, /// /// Allows banning and unbanning members. /// [PermissionString("Ban members")] BanMembers = 0x0000000000000004, /// /// Enables full access on a given guild. This also overrides other permissions. /// [PermissionString("Administrator")] Administrator = 0x0000000000000008, /// /// Allows managing channels. /// [PermissionString("Manage channels")] ManageChannels = 0x0000000000000010, /// /// Allows managing the guild. /// [PermissionString("Manage guild")] ManageGuild = 0x0000000000000020, /// /// Allows adding reactions to messages. /// [PermissionString("Add reactions")] AddReactions = 0x0000000000000040, /// /// Allows viewing audit log entries. /// [PermissionString("View audit log")] ViewAuditLog = 0x0000000000000080, /// /// Allows the use of priority speaker. /// [PermissionString("Use priority speaker")] PrioritySpeaker = 0x0000000000000100, /// /// Allows accessing text and voice channels. Disabling this permission hides channels. /// [PermissionString("Read messages")] AccessChannels = 0x0000000000000400, /// /// Allows sending messages (does not allow sending messages in threads). /// [PermissionString("Send messages")] SendMessages = 0x0000000000000800, /// /// Allows sending text-to-speech messages. /// [PermissionString("Send TTS messages")] SendTtsMessages = 0x0000000000001000, /// /// Allows managing messages of other users. /// [PermissionString("Manage messages")] ManageMessages = 0x0000000000002000, /// /// Allows embedding content in messages. /// [PermissionString("Use embeds")] EmbedLinks = 0x0000000000004000, /// /// Allows uploading files. /// [PermissionString("Attach files")] AttachFiles = 0x0000000000008000, /// /// Allows reading message history. /// [PermissionString("Read message history")] ReadMessageHistory = 0x0000000000010000, /// /// Allows using @everyone and @here mentions. /// [PermissionString("Mention everyone")] MentionEveryone = 0x0000000000020000, /// /// Allows using emojis from external servers, such as twitch or nitro emojis. /// [PermissionString("Use external emojis")] UseExternalEmojis = 0x0000000000040000, /// /// Allows connecting to voice chat. /// [PermissionString("Use voice chat")] UseVoice = 0x0000000000100000, /// /// Allows speaking in voice chat. /// [PermissionString("Speak")] Speak = 0x0000000000200000, /// /// Allows muting other members in voice chat. /// [PermissionString("Mute voice chat members")] MuteMembers = 0x0000000000400000, /// /// Allows deafening other members in voice chat. /// [PermissionString("Deafen voice chat members")] DeafenMembers = 0x0000000000800000, /// /// Allows moving voice chat members. /// [PermissionString("Move voice chat members")] MoveMembers = 0x0000000001000000, /// /// Allows using voice activation in voice chat. Revoking this will usage of push-to-talk. /// [PermissionString("Use voice activity detection")] UseVoiceDetection = 0x0000000002000000, /// /// Allows changing of own nickname. /// [PermissionString("Change own nickname")] ChangeNickname = 0x0000000004000000, /// /// Allows managing nicknames of other members. /// [PermissionString("Manage nicknames")] ManageNicknames = 0x0000000008000000, /// /// Allows managing roles in a guild. /// [PermissionString("Manage roles")] ManageRoles = 0x0000000010000000, /// /// Allows managing webhooks in a guild. /// [PermissionString("Manage webhooks")] ManageWebhooks = 0x0000000020000000, /// /// Allows managing guild emojis and stickers. /// [PermissionString("Manage emojis & stickers")] ManageEmojisAndStickers = 0x0000000040000000, /// /// Allows the user to go live. /// [PermissionString("Allow stream")] Stream = 0x0000000000000200, /// /// Allows the user to use slash commands. /// [PermissionString("Use application commands")] UseApplicationCommands = 0x0000000080000000, /// /// Allows for requesting to speak in stage channels. /// [PermissionString("Request to speak")] RequestToSpeak = 0x0000000100000000, /// /// Allows managing guild events. /// [PermissionString("Manage Events")] ManageEvents = 0x0000000200000000, /// /// Allows for deleting and archiving threads, and viewing all private threads. /// [PermissionString("Manage Threads")] ManageThreads = 0x0000000400000000, /// /// Allows for creating threads. /// [PermissionString("Create Public Threads")] CreatePublicThreads = 0x0000000800000000, /// /// Allows for creating private threads. /// [PermissionString("Create Private Threads")] CreatePrivateThreads = 0x0000001000000000, /// /// Allows the usage of custom stickers from other servers. /// [PermissionString("Use external Stickers")] UseExternalStickers = 0x0000002000000000, /// /// Allows for sending messages in threads. /// [PermissionString("Send messages in Threads")] SendMessagesInThreads = 0x0000004000000000, /// - /// Allows for launching activities (applications with the `EMBEDDED` flag) in a voice channel. + /// Allows for launching activities (applications with the `EMBEDDED` flag) in a voice channel. /// [PermissionString("Start Embedded Activities")] StartEmbeddedActivities = 0x0000008000000000, /// - /// Allows to perform limited moderation actions (timeout). + /// Allows to perform limited moderation actions (timeout). /// [PermissionString("Moderate Members")] ModerateMembers = 0x0000010000000000 } /// /// Defines a readable name for this permission. /// [AttributeUsage(AttributeTargets.Field, AllowMultiple = false)] public sealed class PermissionStringAttribute : Attribute { /// /// Gets the readable name for this permission. /// public string String { get; } /// /// Defines a readable name for this permission. /// /// Readable name for this permission. public PermissionStringAttribute(string str) { this.String = str; } } } diff --git a/DisCatSharp/EventArgs/User/UserSpeakingEventArgs.cs b/DisCatSharp/EventArgs/User/UserSpeakingEventArgs.cs index c283c24b2..a2f31c0a4 100644 --- a/DisCatSharp/EventArgs/User/UserSpeakingEventArgs.cs +++ b/DisCatSharp/EventArgs/User/UserSpeakingEventArgs.cs @@ -1,53 +1,53 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System; using DisCatSharp.Entities; namespace DisCatSharp.EventArgs { /// /// Represents arguments for UserSpeaking event. /// public class UserSpeakingEventArgs : DiscordEventArgs { /// /// Gets the users whose speaking state changed. /// public DiscordUser User { get; internal set; } /// /// Gets the SSRC of the audio source. /// - public uint SSRC { get; internal set; } + public uint Ssrc { get; internal set; } /// /// Gets whether this user is speaking. /// public bool Speaking { get; internal set; } /// /// Initializes a new instance of the class. /// internal UserSpeakingEventArgs(IServiceProvider provider) : base(provider) { } } } diff --git a/DisCatSharp/Formatter.cs b/DisCatSharp/Formatter.cs index 33e69da50..9efca18d2 100644 --- a/DisCatSharp/Formatter.cs +++ b/DisCatSharp/Formatter.cs @@ -1,205 +1,205 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System; using System.Globalization; using System.Text.RegularExpressions; using DisCatSharp.Entities; namespace DisCatSharp { /// /// Contains markdown formatting helpers. /// public static class Formatter { /// /// Gets the md sanitize regex. /// - private static Regex MdSanitizeRegex { get; } = new Regex(@"([`\*_~<>\[\]\(\)""@\!\&#:\|])", RegexOptions.ECMAScript); + private static Regex s_mdSanitizeRegex { get; } = new Regex(@"([`\*_~<>\[\]\(\)""@\!\&#:\|])", RegexOptions.ECMAScript); /// /// Gets the md strip regex. /// - private static Regex MdStripRegex { get; } = new Regex(@"([`\*_~\[\]\(\)""\|]|<@\!?\d+>|<#\d+>|<@\&\d+>|<:[a-zA-Z0-9_\-]:\d+>)", RegexOptions.ECMAScript); + private static Regex s_mdStripRegex { get; } = new Regex(@"([`\*_~\[\]\(\)""\|]|<@\!?\d+>|<#\d+>|<@\&\d+>|<:[a-zA-Z0-9_\-]:\d+>)", RegexOptions.ECMAScript); /// /// Creates a block of code. /// /// Contents of the block. /// Language to use for highlighting. /// Formatted block of code. public static string BlockCode(string content, string language = "") => $"```{language}\n{content}\n```"; /// /// Creates inline code snippet. /// /// Contents of the snippet. /// Formatted inline code snippet. public static string InlineCode(string content) => $"`{content}`"; /// /// Creates a rendered timestamp. /// /// The time from now. /// The format to render the timestamp in. Defaults to relative. /// A formatted timestamp. public static string Timestamp(TimeSpan time, TimestampFormat format = TimestampFormat.RelativeTime) => Timestamp(DateTimeOffset.UtcNow + time, format); /// /// Creates a rendered timestamp. /// /// Timestamp to format. /// The format to render the timestamp in. Defaults to relative. /// A formatted timestamp. public static string Timestamp(DateTimeOffset time, TimestampFormat format = TimestampFormat.RelativeTime) => $""; /// /// Creates a rendered timestamp. /// /// The time from now. /// The format to render the timestamp in. Defaults to relative. /// A formatted timestamp relative to now. public static string Timestamp(DateTime time, TimestampFormat format = TimestampFormat.RelativeTime) => Timestamp(time.ToUniversalTime() - DateTime.UtcNow, format); /// /// Creates bold text. /// /// Text to bolden. /// Formatted text. public static string Bold(string content) => $"**{content}**"; /// /// Creates italicized text. /// /// Text to italicize. /// Formatted text. public static string Italic(string content) => $"*{content}*"; /// /// Creates spoiler from text. /// /// Text to spoilerize. /// Formatted text. public static string Spoiler(string content) => $"||{content}||"; /// /// Creates underlined text. /// /// Text to underline. /// Formatted text. public static string Underline(string content) => $"__{content}__"; /// /// Creates strikethrough text. /// /// Text to strikethrough. /// Formatted text. public static string Strike(string content) => $"~~{content}~~"; /// /// Creates a URL that won't create a link preview. /// /// Url to prevent from being previewed. /// Formatted url. public static string EmbedlessUrl(Uri url) => $"<{url}>"; /// /// Creates a masked link. This link will display as specified text, and alternatively provided alt text. This can only be used in embeds. /// /// Text to display the link as. /// Url that the link will lead to. - /// Alt text to display on hover. + /// Alt text to display on hover. /// Formatted url. - public static string MaskedUrl(string text, Uri url, string alt_text = "") - => $"[{text}]({url}{(!string.IsNullOrWhiteSpace(alt_text) ? $" \"{alt_text}\"" : "")})"; + public static string MaskedUrl(string text, Uri url, string altText = "") + => $"[{text}]({url}{(!string.IsNullOrWhiteSpace(altText) ? $" \"{altText}\"" : "")})"; /// /// Escapes all markdown formatting from specified text. /// /// Text to sanitize. /// Sanitized text. public static string Sanitize(string text) - => MdSanitizeRegex.Replace(text, m => $"\\{m.Groups[1].Value}"); + => s_mdSanitizeRegex.Replace(text, m => $"\\{m.Groups[1].Value}"); /// /// Removes all markdown formatting from specified text. /// /// Text to strip of formatting. /// Formatting-stripped text. public static string Strip(string text) - => MdStripRegex.Replace(text, m => string.Empty); + => s_mdStripRegex.Replace(text, m => string.Empty); /// /// Creates a mention for specified user or member. Can optionally specify to resolve nicknames. /// /// User to create mention for. /// Whether the mention should resolve nicknames or not. /// Formatted mention. public static string Mention(DiscordUser user, bool nickname = false) => nickname ? $"<@!{user.Id.ToString(CultureInfo.InvariantCulture)}>" : $"<@{user.Id.ToString(CultureInfo.InvariantCulture)}>"; /// /// Creates a mention for specified channel. /// /// Channel to mention. /// Formatted mention. public static string Mention(DiscordChannel channel) => $"<#{channel.Id.ToString(CultureInfo.InvariantCulture)}>"; /// /// Creates a mention for specified role. /// /// Role to mention. /// Formatted mention. public static string Mention(DiscordRole role) => $"<@&{role.Id.ToString(CultureInfo.InvariantCulture)}>"; /// /// Creates a custom emoji string. /// /// Emoji to display. /// Formatted emoji. public static string Emoji(DiscordEmoji emoji) => $"<:{emoji.Name}:{emoji.Id.ToString(CultureInfo.InvariantCulture)}>"; /// /// Creates a url for using attachments in embeds. This can only be used as an Image URL, Thumbnail URL, Author icon URL or Footer icon URL. /// /// Name of attached image to display /// public static string AttachedImageUrl(string filename) => $"attachment://{filename}"; } } diff --git a/DisCatSharp/ImageTool.cs b/DisCatSharp/ImageTool.cs index ad21c62fc..7900824ee 100644 --- a/DisCatSharp/ImageTool.cs +++ b/DisCatSharp/ImageTool.cs @@ -1,210 +1,210 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System; using System.IO; using System.Text; namespace DisCatSharp { /// /// Tool to detect image formats and convert from binary data to base64 strings. /// public sealed class ImageTool : IDisposable { /// /// The png magic . /// private const ulong PNG_MAGIC = 0x0A1A_0A0D_474E_5089; /// /// The jpeg magic 1. /// private const ushort JPEG_MAGIC_1 = 0xD8FF; /// /// The jpeg magic 2. /// private const ushort JPEG_MAGIC_2 = 0xD9FF; /// /// The gif magic 1 /// private const ulong GIF_MAGIC_1 = 0x0000_6139_3846_4947; /// /// The gif magic 2. /// private const ulong GIF_MAGIC_2 = 0x0000_6137_3846_4947; /// /// The webp magic 1. /// private const uint WEBP_MAGIC_1 = 0x4646_4952; /// /// The webp magic 2. /// private const uint WEBP_MAGIC_2 = 0x5042_4557; /// /// The gif mask. /// private const ulong GIF_MASK = 0x0000_FFFF_FFFF_FFFF; /// /// The mask 32. /// private const ulong MASK32 = 0x0000_0000_FFFF_FFFF; /// /// The mask 16. /// private const uint MASK16 = 0x0000_FFFF; /// /// Gets the stream this tool is operating on. /// public Stream SourceStream { get; } private ImageFormat _ifcache; - private string _b64cache; + private string _b64Cache; /// /// Creates a new image tool from given stream. /// /// Stream to work with. public ImageTool(Stream stream) { if (stream == null) throw new ArgumentNullException(nameof(stream)); if (!stream.CanRead || !stream.CanSeek) throw new ArgumentException("The stream needs to be both readable and seekable.", nameof(stream)); this.SourceStream = stream; this.SourceStream.Seek(0, SeekOrigin.Begin); this._ifcache = 0; - this._b64cache = null; + this._b64Cache = null; } /// /// Detects the format of this image. /// /// Detected format. public ImageFormat GetFormat() { if (this._ifcache != ImageFormat.Unknown) return this._ifcache; using (var br = new BinaryReader(this.SourceStream, Utilities.UTF8, true)) { var bgn64 = br.ReadUInt64(); if (bgn64 == PNG_MAGIC) return this._ifcache = ImageFormat.Png; bgn64 &= GIF_MASK; if (bgn64 == GIF_MAGIC_1 || bgn64 == GIF_MAGIC_2) return this._ifcache = ImageFormat.Gif; var bgn32 = (uint)(bgn64 & MASK32); if (bgn32 == WEBP_MAGIC_1 && br.ReadUInt32() == WEBP_MAGIC_2) return this._ifcache = ImageFormat.WebP; var bgn16 = (ushort)(bgn32 & MASK16); if (bgn16 == JPEG_MAGIC_1) { this.SourceStream.Seek(-2, SeekOrigin.End); if (br.ReadUInt16() == JPEG_MAGIC_2) return this._ifcache = ImageFormat.Jpeg; } } throw new InvalidDataException("The data within the stream was not valid image data."); } /// /// Converts this image into base64 data format string. /// /// Data-scheme base64 string. public string GetBase64() { - if (this._b64cache != null) - return this._b64cache; + if (this._b64Cache != null) + return this._b64Cache; var fmt = this.GetFormat(); var sb = new StringBuilder(); sb.Append("data:image/") .Append(fmt.ToString().ToLowerInvariant()) .Append(";base64,"); this.SourceStream.Seek(0, SeekOrigin.Begin); var buff = new byte[this.SourceStream.Length]; var br = 0; while (br < buff.Length) br += this.SourceStream.Read(buff, br, (int)this.SourceStream.Length - br); sb.Append(Convert.ToBase64String(buff)); - return this._b64cache = sb.ToString(); + return this._b64Cache = sb.ToString(); } /// /// Disposes this image tool. /// public void Dispose() { if (this.SourceStream != null) this.SourceStream.Dispose(); } } /// /// Represents format of an image. /// public enum ImageFormat : int { /// /// The format is unknown /// Unknown = 0, /// /// The format is a jpeg /// Jpeg = 1, /// /// The format is a png /// Png = 2, /// /// The format is a gif /// Gif = 3, /// /// The format is a webp /// WebP = 4, /// /// The format will be automatically detected /// Auto = 5 } } diff --git a/DisCatSharp/Internals.cs b/DisCatSharp/Internals.cs index a03216788..bfa437701 100644 --- a/DisCatSharp/Internals.cs +++ b/DisCatSharp/Internals.cs @@ -1,93 +1,93 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System.Collections.Generic; using System.Text; using DisCatSharp.Entities; namespace DisCatSharp { /// /// Internal tools. /// public static class Internals { /// /// Gets the version of the library /// - private static string VersionHeader + private static string s_versionHeader => Utilities.VersionHeader; /// /// Gets the permission strings. /// - private static Dictionary PermissionStrings + private static Dictionary s_permissionStrings => Utilities.PermissionStrings; /// /// Gets the utf8 encoding /// - internal static UTF8Encoding UTF8 + internal static UTF8Encoding Utf8 => Utilities.UTF8; /// /// Initializes a new instance of the class. /// static Internals() { } /// /// Whether the is joinable via voice. /// /// The channel. internal static bool IsVoiceJoinable(this DiscordChannel channel) => channel.Type == ChannelType.Voice || channel.Type == ChannelType.Stage; /// /// Whether the can have threads. /// /// The channel. internal static bool IsThreadHolder(this DiscordChannel channel) => channel.Type == ChannelType.Text || channel.Type == ChannelType.News || channel.Type == ChannelType.GuildForum; /// /// Whether the is related to threads. /// /// The channel. internal static bool IsThread(this DiscordChannel channel) => channel.Type == ChannelType.PublicThread || channel.Type == ChannelType.PrivateThread || channel.Type == ChannelType.NewsThread; /// /// Whether users can write the . /// /// The channel. internal static bool IsWriteable(this DiscordChannel channel) => channel.Type == ChannelType.PublicThread || channel.Type == ChannelType.PrivateThread || channel.Type == ChannelType.NewsThread || channel.Type == ChannelType.Text || channel.Type == ChannelType.News || channel.Type == ChannelType.Group || channel.Type == ChannelType.Private || channel.Type == ChannelType.Voice; /// /// Whether the is moveable in a parent. /// /// The channel. internal static bool IsMovableInParent(this DiscordChannel channel) => channel.Type == ChannelType.Voice || channel.Type == ChannelType.Stage || channel.Type == ChannelType.Text || channel.Type == ChannelType.GuildForum || channel.Type == ChannelType.News || channel.Type == ChannelType.Store; /// /// Whether the is moveable. /// /// The channel. internal static bool IsMovable(this DiscordChannel channel) => channel.Type == ChannelType.Voice || channel.Type == ChannelType.Stage || channel.Type == ChannelType.Text || channel.Type == ChannelType.Category || channel.Type == ChannelType.GuildForum || channel.Type == ChannelType.News || channel.Type == ChannelType.Store; } } diff --git a/DisCatSharp/Logging/DefaultLoggerProvider.cs b/DisCatSharp/Logging/DefaultLoggerProvider.cs index 463b738eb..f3b8b629a 100644 --- a/DisCatSharp/Logging/DefaultLoggerProvider.cs +++ b/DisCatSharp/Logging/DefaultLoggerProvider.cs @@ -1,89 +1,89 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System; using Microsoft.Extensions.Logging; namespace DisCatSharp { /// /// Represents a default logger provider. /// internal class DefaultLoggerProvider : ILoggerProvider { /// /// Gets the minimum log level. /// private LogLevel MinimumLevel { get; } /// /// Gets the timestamp format. /// private string TimestampFormat { get; } private bool _isDisposed = false; /// /// Initializes a new instance of the class. /// /// The client. internal DefaultLoggerProvider(BaseDiscordClient client) : this(client.Configuration.MinimumLogLevel, client.Configuration.LogTimestampFormat) { } /// /// Initializes a new instance of the class. /// /// The client. internal DefaultLoggerProvider(DiscordWebhookClient client) - : this(client._minimumLogLevel, client._logTimestampFormat) + : this(client.MinimumLogLevel, client.LogTimestampFormat) { } /// /// Initializes a new instance of the class. /// /// The min level. /// The timestamp format. internal DefaultLoggerProvider(LogLevel minLevel = LogLevel.Information, string timestampFormat = "yyyy-MM-dd HH:mm:ss zzz") { this.MinimumLevel = minLevel; this.TimestampFormat = timestampFormat; } /// /// Creates the logger. /// /// The category name. public ILogger CreateLogger(string categoryName) { return this._isDisposed ? throw new InvalidOperationException("This logger provider is already disposed.") : categoryName != typeof(BaseDiscordClient).FullName && categoryName != typeof(DiscordWebhookClient).FullName ? throw new ArgumentException($"This provider can only provide instances of loggers for {typeof(BaseDiscordClient).FullName} or {typeof(DiscordWebhookClient).FullName}.", nameof(categoryName)) : new DefaultLogger(this.MinimumLevel, this.TimestampFormat); } /// /// Disposes the logger. /// public void Dispose() => this._isDisposed = true; } } diff --git a/DisCatSharp/Net/Abstractions/Rest/RestApplicationCommandPayloads.cs b/DisCatSharp/Net/Abstractions/Rest/RestApplicationCommandPayloads.cs index 7ebf4f1d4..ce0166424 100644 --- a/DisCatSharp/Net/Abstractions/Rest/RestApplicationCommandPayloads.cs +++ b/DisCatSharp/Net/Abstractions/Rest/RestApplicationCommandPayloads.cs @@ -1,251 +1,251 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System.Collections.Generic; using DisCatSharp.Entities; using DisCatSharp.Enums; using Newtonsoft.Json; namespace DisCatSharp.Net.Abstractions { /// /// Represents a application command create payload. /// internal class RestApplicationCommandCreatePayload { /// /// Gets the type. /// [JsonProperty("type")] public ApplicationCommandType Type { get; set; } /// /// Gets the name. /// [JsonProperty("name")] public string Name { get; set; } [JsonProperty("name_localizations", NullValueHandling = NullValueHandling.Ignore)] public Optional> NameLocalizations { get; set; } /// /// Gets the description. /// [JsonProperty("description", NullValueHandling = NullValueHandling.Ignore)] public string Description { get; set; } [JsonProperty("description_localizations", NullValueHandling = NullValueHandling.Ignore)] public Optional> DescriptionLocalizations { get; set; } /// /// Gets the options. /// [JsonProperty("options", NullValueHandling = NullValueHandling.Ignore)] public IEnumerable Options { get; set; } /// /// Whether the command is allowed for everyone. /// [JsonProperty("default_permission")] public bool DefaultPermission { get; set; } /// /// The command needed permissions. /// [JsonProperty("default_member_permissions", NullValueHandling = NullValueHandling.Ignore)] public Permissions? DefaultMemberPermission { get; set; } /// /// Whether the command is allowed for dms. /// [JsonProperty("dm_permission", NullValueHandling = NullValueHandling.Ignore)] public bool? DmPermission { get; set; } } /// /// Represents a application command edit payload. /// internal class RestApplicationCommandEditPayload { /// /// Gets the name. /// [JsonProperty("name")] public Optional Name { get; set; } [JsonProperty("name_localizations")] public Optional> NameLocalizations { get; set; } /// /// Gets the description. /// [JsonProperty("description")] public Optional Description { get; set; } [JsonProperty("description_localizations")] public Optional> DescriptionLocalizations { get; set; } /// /// Gets the options. /// [JsonProperty("options")] public Optional> Options { get; set; } /// /// Gets the default permission. /// [JsonProperty("default_permission")] public Optional DefaultPermission { get; set; } /// /// The command needed permissions. /// [JsonProperty("default_member_permissions", NullValueHandling = NullValueHandling.Ignore)] public Optional DefaultMemberPermission { get; set; } /// /// Whether the command is allowed for dms. /// [JsonProperty("dm_permission", NullValueHandling = NullValueHandling.Ignore)] public Optional DmPermission { get; set; } } /// /// Represents a interaction response payload. /// internal class RestInteractionResponsePayload { /// /// Gets the type. /// [JsonProperty("type", NullValueHandling = NullValueHandling.Ignore)] public InteractionResponseType Type { get; set; } /// /// Gets the data. /// [JsonProperty("data", NullValueHandling = NullValueHandling.Ignore)] public DiscordInteractionApplicationCommandCallbackData Data { get; set; } /// /// Gets the attachments. /// [JsonProperty("attachments", NullValueHandling = NullValueHandling.Ignore)] public List Attachments { get; set; } } /// /// Represents a interaction response payload. /// internal class RestInteractionModalResponsePayload { /// /// Gets the type. /// [JsonProperty("type", NullValueHandling = NullValueHandling.Ignore)] public InteractionResponseType Type { get; set; } /// /// Gets the data. /// [JsonProperty("data", NullValueHandling = NullValueHandling.Ignore)] public DiscordInteractionApplicationCommandModalCallbackData Data { get; set; } } /// /// Represents a followup message create payload. /// internal class RestFollowupMessageCreatePayload { /// /// Gets the content. /// [JsonProperty("content", NullValueHandling = NullValueHandling.Ignore)] public string Content { get; set; } /// /// Get whether the message is tts. /// [JsonProperty("tts", NullValueHandling = NullValueHandling.Ignore)] - public bool? IsTTS { get; set; } + public bool? IsTts { get; set; } /// /// Gets the embeds. /// [JsonProperty("embeds", NullValueHandling = NullValueHandling.Ignore)] public IEnumerable Embeds { get; set; } /// /// Gets the mentions. /// [JsonProperty("allowed_mentions", NullValueHandling = NullValueHandling.Ignore)] public DiscordMentions Mentions { get; set; } /// /// Gets the flags. /// [JsonProperty("flags", NullValueHandling = NullValueHandling.Ignore)] public int? Flags { get; set; } /// /// Gets the components. /// [JsonProperty("components", NullValueHandling = NullValueHandling.Ignore)] public IReadOnlyCollection Components { get; set; } /// /// Gets attachments. /// [JsonProperty("attachments", NullValueHandling = NullValueHandling.Ignore)] public List Attachments { get; set; } } /// /// Represents a application command permission edit payload. /// internal class RestApplicationCommandPermissionEditPayload { /// /// Gets the permissions. /// [JsonProperty("permissions")] public IEnumerable Permissions { get; set; } } /// /// Represents a guild application command permission edit payload. /// internal class RestGuildApplicationCommandPermissionEditPayload { /// /// Gets the command id. /// [JsonProperty("id")] public ulong CommandId { get; set; } /// /// Gets the permissions. /// [JsonProperty("permissions")] public IEnumerable Permissions { get; set; } } } diff --git a/DisCatSharp/Net/Abstractions/Rest/RestChannelPayloads.cs b/DisCatSharp/Net/Abstractions/Rest/RestChannelPayloads.cs index 930b6fb53..3896ef1eb 100644 --- a/DisCatSharp/Net/Abstractions/Rest/RestChannelPayloads.cs +++ b/DisCatSharp/Net/Abstractions/Rest/RestChannelPayloads.cs @@ -1,503 +1,503 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System.Collections.Generic; using DisCatSharp.Entities; using Newtonsoft.Json; namespace DisCatSharp.Net.Abstractions { /// /// Represents a channel create payload. /// internal sealed class RestChannelCreatePayload { /// /// Gets or sets the name. /// [JsonProperty("name")] public string Name { get; set; } /// /// Gets or sets the type. /// [JsonProperty("type")] public ChannelType Type { get; set; } /// /// Gets or sets the parent. /// [JsonProperty("parent_id", NullValueHandling = NullValueHandling.Ignore)] public ulong? Parent { get; set; } /// /// Gets or sets the topic. /// [JsonProperty("topic")] public Optional Topic { get; set; } /// /// Gets or sets the bitrate. /// [JsonProperty("bitrate", NullValueHandling = NullValueHandling.Ignore)] public int? Bitrate { get; set; } /// /// Gets or sets the user limit. /// [JsonProperty("user_limit", NullValueHandling = NullValueHandling.Ignore)] public int? UserLimit { get; set; } /// /// Gets or sets the permission overwrites. /// [JsonProperty("permission_overwrites", NullValueHandling = NullValueHandling.Ignore)] public IEnumerable PermissionOverwrites { get; set; } /// /// Gets or sets a value indicating whether nsfw. /// [JsonProperty("nsfw", NullValueHandling = NullValueHandling.Ignore)] public bool? Nsfw { get; set; } /// /// Gets or sets the per user rate limit. /// [JsonProperty("rate_limit_per_user")] public Optional PerUserRateLimit { get; set; } /// /// Gets or sets the quality mode. /// [JsonProperty("video_quality_mode", NullValueHandling = NullValueHandling.Ignore)] public VideoQualityMode? QualityMode { get; set; } /// /// Gets or sets the default auto archive duration. /// [JsonProperty("default_auto_archive_duration", NullValueHandling = NullValueHandling.Ignore)] public ThreadAutoArchiveDuration? DefaultAutoArchiveDuration { get; set; } } /// /// Represents a channel modify payload. /// internal sealed class RestChannelModifyPayload { /// /// Gets or sets the name. /// [JsonProperty("name", NullValueHandling = NullValueHandling.Ignore)] public string Name { get; set; } /// /// Gets or sets the type. /// [JsonProperty("type")] public Optional Type { get; set; } /// /// Gets or sets the position. /// [JsonProperty("position", NullValueHandling = NullValueHandling.Ignore)] public int? Position { get; set; } /// /// Gets or sets the topic. /// [JsonProperty("topic")] public Optional Topic { get; set; } /// /// Gets or sets a value indicating whether nsfw. /// [JsonProperty("nsfw", NullValueHandling = NullValueHandling.Ignore)] public bool? Nsfw { get; set; } /// /// Gets or sets the parent. /// [JsonProperty("parent_id")] public Optional Parent { get; set; } /// /// Gets or sets the bitrate. /// [JsonProperty("bitrate", NullValueHandling = NullValueHandling.Ignore)] public int? Bitrate { get; set; } /// /// Gets or sets the user limit. /// [JsonProperty("user_limit", NullValueHandling = NullValueHandling.Ignore)] public int? UserLimit { get; set; } /// /// Gets or sets the per user rate limit. /// [JsonProperty("rate_limit_per_user")] public Optional PerUserRateLimit { get; set; } /// /// Gets or sets the rtc region. /// [JsonProperty("rtc_region")] public Optional RtcRegion { get; set; } /// /// Gets or sets the quality mode. /// [JsonProperty("video_quality_mode", NullValueHandling = NullValueHandling.Ignore)] public VideoQualityMode? QualityMode { get; set; } /// /// Gets or sets the default auto archive duration. /// [JsonProperty("default_auto_archive_duration", NullValueHandling = NullValueHandling.Ignore)] public ThreadAutoArchiveDuration? DefaultAutoArchiveDuration { get; set; } /// /// Gets or sets the permission overwrites. /// [JsonProperty("permission_overwrites", NullValueHandling = NullValueHandling.Ignore)] public IEnumerable PermissionOverwrites { get; set; } /// /// Gets or sets the banner base64. /// [JsonProperty("banner")] public Optional BannerBase64 { get; set; } } /// /// Represents a channel message edit payload. /// internal class RestChannelMessageEditPayload { /// /// Gets or sets the content. /// [JsonProperty("content", NullValueHandling = NullValueHandling.Include)] public string Content { get; set; } /// /// Gets or sets a value indicating whether has content. /// [JsonIgnore] public bool HasContent { get; set; } /// /// Gets or sets the embeds. /// [JsonProperty("embeds", NullValueHandling = NullValueHandling.Ignore)] public IEnumerable Embeds { get; set; } /// /// Gets or sets the mentions. /// [JsonProperty("allowed_mentions", NullValueHandling = NullValueHandling.Ignore)] public DiscordMentions Mentions { get; set; } /// /// Gets or sets the attachments. /// [JsonProperty("attachments", NullValueHandling = NullValueHandling.Ignore)] public IEnumerable Attachments { get; set; } /// /// Gets or sets the flags. /// [JsonProperty("flags", NullValueHandling = NullValueHandling.Ignore)] public MessageFlags? Flags { get; set; } /// /// Gets or sets the components. /// [JsonProperty("components", NullValueHandling = NullValueHandling.Ignore)] public IReadOnlyCollection Components { get; set; } /// /// Gets or sets a value indicating whether has embed. /// [JsonIgnore] public bool HasEmbed { get; set; } /// /// Should serialize the content. /// public bool ShouldSerializeContent() => this.HasContent; /// /// Should serialize the embed. /// public bool ShouldSerializeEmbed() => this.HasEmbed; } /// /// Represents a channel message create payload. /// internal sealed class RestChannelMessageCreatePayload : RestChannelMessageEditPayload { /// /// Gets or sets a value indicating whether t t is s. /// [JsonProperty("tts", NullValueHandling = NullValueHandling.Ignore)] - public bool? IsTTS { get; set; } + public bool? IsTts { get; set; } /// /// Gets or sets the stickers ids. /// [JsonProperty("sticker_ids", NullValueHandling = NullValueHandling.Ignore)] public IEnumerable StickersIds { get; set; } /// /// Gets or sets the message reference. /// [JsonProperty("message_reference", NullValueHandling = NullValueHandling.Ignore)] public InternalDiscordMessageReference? MessageReference { get; set; } } /// /// Represents a channel message create multipart payload. /// internal sealed class RestChannelMessageCreateMultipartPayload { /// /// Gets or sets the content. /// [JsonProperty("content", NullValueHandling = NullValueHandling.Ignore)] public string Content { get; set; } /// /// Gets or sets a value indicating whether t t is s. /// [JsonProperty("tts", NullValueHandling = NullValueHandling.Ignore)] - public bool? IsTTS { get; set; } + public bool? IsTts { get; set; } /// /// Gets or sets the embeds. /// [JsonProperty("embeds", NullValueHandling = NullValueHandling.Ignore)] public IEnumerable Embeds { get; set; } /// /// Gets or sets the mentions. /// [JsonProperty("allowed_mentions", NullValueHandling = NullValueHandling.Ignore)] public DiscordMentions Mentions { get; set; } /// /// Gets or sets the message reference. /// [JsonProperty("message_reference", NullValueHandling = NullValueHandling.Ignore)] public InternalDiscordMessageReference? MessageReference { get; set; } } /// /// Represents a channel message bulk delete payload. /// internal sealed class RestChannelMessageBulkDeletePayload { /// /// Gets or sets the messages. /// [JsonProperty("messages", NullValueHandling = NullValueHandling.Ignore)] public IEnumerable Messages { get; set; } } /// /// Represents a channel invite create payload. /// internal sealed class RestChannelInviteCreatePayload { /// /// Gets or sets the max age. /// [JsonProperty("max_age", NullValueHandling = NullValueHandling.Ignore)] public int MaxAge { get; set; } /// /// Gets or sets the max uses. /// [JsonProperty("max_uses", NullValueHandling = NullValueHandling.Ignore)] public int MaxUses { get; set; } /// /// Gets or sets the target type. /// [JsonProperty("target_type", NullValueHandling = NullValueHandling.Ignore)] public TargetType? TargetType { get; set; } /// /// Gets or sets the target application. /// [JsonProperty("target_application_id", NullValueHandling = NullValueHandling.Ignore)] public TargetActivity? TargetApplication { get; set; } /// /// Gets or sets the target user id. /// [JsonProperty("target_user_id", NullValueHandling = NullValueHandling.Ignore)] public ulong? TargetUserId { get; set; } /// /// Gets or sets a value indicating whether temporary. /// [JsonProperty("temporary", NullValueHandling = NullValueHandling.Ignore)] public bool Temporary { get; set; } /// /// Gets or sets a value indicating whether unique. /// [JsonProperty("unique", NullValueHandling = NullValueHandling.Ignore)] public bool Unique { get; set; } } /// /// Represents a channel permission edit payload. /// internal sealed class RestChannelPermissionEditPayload { /// /// Gets or sets the allow. /// [JsonProperty("allow", NullValueHandling = NullValueHandling.Ignore)] public Permissions Allow { get; set; } /// /// Gets or sets the deny. /// [JsonProperty("deny", NullValueHandling = NullValueHandling.Ignore)] public Permissions Deny { get; set; } /// /// Gets or sets the type. /// [JsonProperty("type", NullValueHandling = NullValueHandling.Ignore)] public string Type { get; set; } } /// /// Represents a channel group dm recipient add payload. /// internal sealed class RestChannelGroupDmRecipientAddPayload : 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; } } /// /// The acknowledge payload. /// internal sealed class AcknowledgePayload { /// /// Gets or sets the token. /// [JsonProperty("token", NullValueHandling = NullValueHandling.Include)] public string Token { get; set; } } /// /// Represents a thread channel create payload. /// internal sealed class RestThreadChannelCreatePayload { /// /// Gets or sets the name. /// [JsonProperty("name")] public string Name { get; set; } /// /// Gets or sets the auto archive duration. /// [JsonProperty("auto_archive_duration")] public ThreadAutoArchiveDuration AutoArchiveDuration { get; set; } /// /// Gets or sets the rate limit per user. /// [JsonProperty("rate_limit_per_user")] public int? PerUserRateLimit { get; set; } /// /// Gets or sets the thread type. /// [JsonProperty("type")] public ChannelType Type { get; set; } } /// /// Represents a thread channel modify payload. /// internal sealed class RestThreadChannelModifyPayload { /// /// Gets or sets the name. /// [JsonProperty("name", NullValueHandling = NullValueHandling.Ignore)] public string Name { get; set; } /// /// Gets or sets the archived. /// [JsonProperty("archived", NullValueHandling = NullValueHandling.Ignore)] public Optional Archived { get; set; } /// /// Gets or sets the auto archive duration. /// [JsonProperty("auto_archive_duration", NullValueHandling = NullValueHandling.Ignore)] public Optional AutoArchiveDuration { get; set; } /// /// Gets or sets the locked. /// [JsonProperty("locked", NullValueHandling = NullValueHandling.Ignore)] public Optional Locked { get; set; } /// /// Gets or sets the per user rate limit. /// [JsonProperty("rate_limit_per_user", NullValueHandling = NullValueHandling.Ignore)] public Optional PerUserRateLimit { get; set; } /// /// Gets or sets the threads's invitable state. /// [JsonProperty("invitable", NullValueHandling = NullValueHandling.Ignore)] public Optional Invitable { internal get; set; } } } diff --git a/DisCatSharp/Net/Abstractions/Rest/RestWebhookPayloads.cs b/DisCatSharp/Net/Abstractions/Rest/RestWebhookPayloads.cs index 3b649a613..549e7bfe9 100644 --- a/DisCatSharp/Net/Abstractions/Rest/RestWebhookPayloads.cs +++ b/DisCatSharp/Net/Abstractions/Rest/RestWebhookPayloads.cs @@ -1,154 +1,154 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System.Collections.Generic; using DisCatSharp.Entities; using Newtonsoft.Json; namespace DisCatSharp.Net.Abstractions { /// /// Represents a webhook payload. /// internal sealed class RestWebhookPayload { /// /// Gets or sets the name. /// [JsonProperty("name")] public string Name { get; set; } /// /// Gets or sets the avatar base64. /// [JsonProperty("avatar", NullValueHandling = NullValueHandling.Include)] public string AvatarBase64 { get; set; } /// /// Gets or sets the channel id. /// [JsonProperty("channel_id")] public ulong ChannelId { get; set; } /// /// Gets whether an avatar is set. /// [JsonProperty] public bool AvatarSet { get; set; } /// /// Gets whether the avatar should be serialized. /// public bool ShouldSerializeAvatarBase64() => this.AvatarSet; } /// /// Represents a webhook execute payload. /// internal sealed class RestWebhookExecutePayload { /// /// Gets or sets the content. /// [JsonProperty("content", NullValueHandling = NullValueHandling.Ignore)] public string Content { get; set; } /// /// Gets or sets the username. /// [JsonProperty("username", NullValueHandling = NullValueHandling.Ignore)] public string Username { get; set; } /// /// Gets or sets the avatar url. /// [JsonProperty("avatar_url", NullValueHandling = NullValueHandling.Ignore)] public string AvatarUrl { get; set; } /// /// Whether this message is tts. /// [JsonProperty("tts", NullValueHandling = NullValueHandling.Ignore)] - public bool? IsTTS { get; set; } + public bool? IsTts { get; set; } /// /// Gets or sets the embeds. /// [JsonProperty("embeds", NullValueHandling = NullValueHandling.Ignore)] public IEnumerable Embeds { get; set; } /// /// Gets or sets the mentions. /// [JsonProperty("allowed_mentions", NullValueHandling = NullValueHandling.Ignore)] public DiscordMentions Mentions { get; set; } /// /// Gets or sets the components. /// [JsonProperty("components", NullValueHandling = NullValueHandling.Ignore)] public IEnumerable Components { get; set; } /// /// Gets or sets the attachments. /// [JsonProperty("attachments", NullValueHandling = NullValueHandling.Ignore)] public List Attachments { get; set; } } /// /// Represents a webhook message edit payload. /// internal sealed class RestWebhookMessageEditPayload { /// /// Gets or sets the content. /// [JsonProperty("content", NullValueHandling = NullValueHandling.Ignore)] public Optional Content { get; set; } /// /// Gets or sets the embeds. /// [JsonProperty("embeds", NullValueHandling = NullValueHandling.Ignore)] public IEnumerable Embeds { get; set; } /// /// Gets or sets the mentions. /// [JsonProperty("allowed_mentions", NullValueHandling = NullValueHandling.Ignore)] public IEnumerable Mentions { get; set; } /// /// Gets or sets the attachments. /// [JsonProperty("attachments", NullValueHandling = NullValueHandling.Ignore)] public IEnumerable Attachments { get; set; } /// /// Gets or sets the components. /// [JsonProperty("components", NullValueHandling = NullValueHandling.Ignore)] public IEnumerable Components { get; set; } } } diff --git a/DisCatSharp/Net/Abstractions/StatusUpdate.cs b/DisCatSharp/Net/Abstractions/StatusUpdate.cs index 617b3df99..ac86d3100 100644 --- a/DisCatSharp/Net/Abstractions/StatusUpdate.cs +++ b/DisCatSharp/Net/Abstractions/StatusUpdate.cs @@ -1,79 +1,79 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using DisCatSharp.Entities; using Newtonsoft.Json; namespace DisCatSharp.Net.Abstractions { /// /// Represents data for websocket status update payload. /// internal sealed class StatusUpdate { /// /// Gets or sets the unix millisecond timestamp of when the user went idle. /// [JsonProperty("since", NullValueHandling = NullValueHandling.Include)] public long? IdleSince { get; set; } /// /// Gets or sets whether the user is AFK. /// [JsonProperty("afk")] - public bool IsAFK { get; set; } + public bool IsAfk { get; set; } /// /// Gets or sets the status of the user. /// [JsonIgnore] public UserStatus Status { get; set; } = UserStatus.Online; /// /// Gets the status string of the user. /// [JsonProperty("status")] internal string StatusString { get { return this.Status switch { UserStatus.Online => "online", UserStatus.Idle => "idle", UserStatus.DoNotDisturb => "dnd", UserStatus.Invisible or UserStatus.Offline => "invisible", UserStatus.Streaming => "streaming", _ => "online", }; } } /// /// Gets or sets the game the user is playing. /// [JsonProperty("game", NullValueHandling = NullValueHandling.Ignore)] public TransportActivity Activity { get; set; } - internal DiscordActivity _activity; + internal DiscordActivity ActivityInternal; } } diff --git a/DisCatSharp/Net/Abstractions/Transport/TransportActivity.cs b/DisCatSharp/Net/Abstractions/Transport/TransportActivity.cs index e6ec5b38a..d7357f0fc 100644 --- a/DisCatSharp/Net/Abstractions/Transport/TransportActivity.cs +++ b/DisCatSharp/Net/Abstractions/Transport/TransportActivity.cs @@ -1,374 +1,374 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System; using System.Collections.Generic; using System.Globalization; using DisCatSharp.Entities; using Newtonsoft.Json; using Newtonsoft.Json.Linq; namespace DisCatSharp.Net.Abstractions { /// /// Represents a game a user is playing. /// internal sealed class TransportActivity { /// /// Gets or sets the id of user's activity. /// [JsonProperty("id", NullValueHandling = NullValueHandling.Ignore)] public string Id { get; internal set; } /// /// Gets or sets the name of the game the user is playing. /// [JsonProperty("name", NullValueHandling = NullValueHandling.Include)] public string Name { get; internal set; } /// /// Gets or sets the stream URI, if applicable. /// [JsonProperty("url", NullValueHandling = NullValueHandling.Ignore)] public string StreamUrl { get; internal set; } /// /// Gets or sets the livesteam type. /// [JsonProperty("type", NullValueHandling = NullValueHandling.Ignore)] public ActivityType ActivityType { get; internal set; } /// /// Gets or sets the details. /// /// This is a component of the rich presence, and, as such, can only be used by regular users. /// [JsonProperty("details", NullValueHandling = NullValueHandling.Ignore)] public string Details { get; internal set; } /// /// Gets or sets game state. /// /// This is a component of the rich presence, and, as such, can only be used by regular users. /// [JsonProperty("state", NullValueHandling = NullValueHandling.Ignore)] public string State { get; internal set; } /// /// Gets the emoji details for a custom status, if any. /// [JsonProperty("emoji", NullValueHandling = NullValueHandling.Ignore)] public DiscordEmoji Emoji { get; internal set; } /// /// Gets ID of the application for which this rich presence is for. /// /// This is a component of the rich presence, and, as such, can only be used by regular users. /// [JsonIgnore] public ulong? ApplicationId { get => this.ApplicationIdStr != null ? (ulong?)ulong.Parse(this.ApplicationIdStr, CultureInfo.InvariantCulture) : null; internal set => this.ApplicationIdStr = value?.ToString(CultureInfo.InvariantCulture); } /// /// Gets or sets the application id string. /// [JsonProperty("application_id", NullValueHandling = NullValueHandling.Ignore)] internal string ApplicationIdStr { get; set; } /// /// Gets or sets instance status. /// /// This is a component of the rich presence, and, as such, can only be used by regular users. /// [JsonProperty("instance", NullValueHandling = NullValueHandling.Ignore)] public bool? Instance { get; internal set; } /// /// Gets or sets information about the current game's party. /// /// This is a component of the rich presence, and, as such, can only be used by regular users. /// [JsonProperty("party", NullValueHandling = NullValueHandling.Ignore)] public GameParty Party { get; internal set; } /// /// Gets or sets information about assets related to this rich presence. /// /// This is a component of the rich presence, and, as such, can only be used by regular users. /// [JsonProperty("assets", NullValueHandling = NullValueHandling.Ignore)] public PresenceAssets Assets { get; internal set; } /// /// Gets or sets information about buttons in this rich presence. /// /// This is a component of the rich presence, and, as such, can only be used by regular users. /// [JsonProperty("buttons", NullValueHandling = NullValueHandling.Ignore)] public IReadOnlyList Buttons { get; internal set; } /// /// Gets or sets platform in this rich presence. /// /// This is a component of the rich presence, and, as such, can only be used by regular users. /// [JsonProperty("platform", NullValueHandling = NullValueHandling.Ignore)] public string Platform { get; internal set; } /// /// Gets or sets sync_id in this rich presence. /// /// This is a component of the rich presence, and, as such, can only be used by regular users. /// [JsonProperty("sync_id", NullValueHandling = NullValueHandling.Ignore)] public string SyncId { get; internal set; } /// /// Gets or sets session_id in this rich presence. /// /// This is a component of the rich presence, and, as such, can only be used by regular users. /// [JsonProperty("session_id", NullValueHandling = NullValueHandling.Ignore)] public string SessionId { get; internal set; } /// /// Gets or sets infromation about current game's timestamps. /// /// This is a component of the rich presence, and, as such, can only be used by regular users. /// [JsonProperty("timestamps", NullValueHandling = NullValueHandling.Ignore)] public GameTimestamps Timestamps { get; internal set; } /// /// Gets or sets information about current game's secret values. /// /// This is a component of the rich presence, and, as such, can only be used by regular users. /// [JsonProperty("secrets", NullValueHandling = NullValueHandling.Ignore)] public GameSecrets Secrets { get; internal set; } /// /// Initializes a new instance of the class. /// internal TransportActivity() { } /// /// Initializes a new instance of the class. /// /// The game. internal TransportActivity(DiscordActivity game) { if (game == null) return; this.Name = game.Name; this.ActivityType = game.ActivityType; this.StreamUrl = game.StreamUrl; } /// /// Whether this activity is a rich presence. /// public bool IsRichPresence() => this.Details != null || this.State != null || this.ApplicationId != null || this.Instance != null || this.Party != null || this.Assets != null || this.Secrets != null || this.Timestamps != null || this.Buttons != null; /// /// Whether this activity is a custom status. /// public bool IsCustomStatus() => this.Name == "Custom Status"; /// /// Represents information about assets attached to a rich presence. /// public class PresenceAssets { /// /// Gets the large image asset ID. /// [JsonProperty("large_image")] public string LargeImage { get; set; } /// /// Gets the large image text. /// [JsonProperty("large_text", NullValueHandling = NullValueHandling.Ignore)] public string LargeImageText { get; internal set; } /// /// Gets the small image asset ID. /// [JsonProperty("small_image")] internal string SmallImage { get; set; } /// /// Gets the small image text. /// [JsonProperty("small_text", NullValueHandling = NullValueHandling.Ignore)] public string SmallImageText { get; internal set; } } /// /// Represents information about rich presence game party. /// public class GameParty { /// /// Gets the game party ID. /// [JsonProperty("id", NullValueHandling = NullValueHandling.Ignore)] public string Id { get; internal set; } /// /// Gets the size of the party. /// [JsonProperty("size", NullValueHandling = NullValueHandling.Ignore)] public GamePartySize Size { get; internal set; } /// /// Represents information about party size. /// [JsonConverter(typeof(GamePartySizeConverter))] public class GamePartySize { /// /// Gets the current number of players in the party. /// public long Current { get; internal set; } /// /// Gets the maximum party size. /// public long Maximum { get; internal set; } } } /// /// Represents information about the game state's timestamps. /// public class GameTimestamps { /// /// Gets the time the game has started. /// [JsonIgnore] public DateTimeOffset? Start - => this._start != null ? (DateTimeOffset?)Utilities.GetDateTimeOffsetFromMilliseconds(this._start.Value, false) : null; + => this.StartInternal != null ? (DateTimeOffset?)Utilities.GetDateTimeOffsetFromMilliseconds(this.StartInternal.Value, false) : null; [JsonProperty("start", NullValueHandling = NullValueHandling.Ignore)] - internal long? _start; + internal long? StartInternal; /// /// Gets the time the game is going to end. /// [JsonIgnore] public DateTimeOffset? End - => this._end != null ? (DateTimeOffset?)Utilities.GetDateTimeOffsetFromMilliseconds(this._end.Value, false) : null; + => this.EndInternal != null ? (DateTimeOffset?)Utilities.GetDateTimeOffsetFromMilliseconds(this.EndInternal.Value, false) : null; [JsonProperty("end", NullValueHandling = NullValueHandling.Ignore)] - internal long? _end; + internal long? EndInternal; } /// /// Represents information about secret values for the Join, Spectate, and Match actions. /// public class GameSecrets { /// /// Gets the secret value for join action. /// [JsonProperty("join", NullValueHandling = NullValueHandling.Ignore)] public string Join { get; internal set; } /// /// Gets the secret value for match action. /// [JsonProperty("match", NullValueHandling = NullValueHandling.Ignore)] public string Match { get; internal set; } /// /// Gets the secret value for spectate action. /// [JsonProperty("spectate", NullValueHandling = NullValueHandling.Ignore)] public string Spectate { get; internal set; } } } /// /// Represents a game party size converter. /// internal sealed class GamePartySizeConverter : JsonConverter { /// /// Writes the json. /// /// The writer. /// The value. /// The serializer. public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { var obj = value is TransportActivity.GameParty.GamePartySize sinfo ? new object[] { sinfo.Current, sinfo.Maximum } : null; serializer.Serialize(writer, obj); } /// /// Reads the json. /// /// The reader. /// The object type. /// The existing value. /// The serializer. public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { var arr = this.ReadArrayObject(reader, serializer); return new TransportActivity.GameParty.GamePartySize { Current = (long)arr[0], Maximum = (long)arr[1], }; } /// /// Reads the array object. /// /// The reader. /// The serializer. private JArray ReadArrayObject(JsonReader reader, JsonSerializer serializer) { return serializer.Deserialize(reader) is not JArray arr || arr.Count != 2 ? throw new JsonSerializationException("Expected array of length 2") : arr; } /// /// Whether it can convert. /// /// The object type. public override bool CanConvert(Type objectType) => objectType == typeof(TransportActivity.GameParty.GamePartySize); } } diff --git a/DisCatSharp/Net/Rest/DiscordApiClient.cs b/DisCatSharp/Net/Rest/DiscordApiClient.cs index 873e3a8f6..bdc43d6e1 100644 --- a/DisCatSharp/Net/Rest/DiscordApiClient.cs +++ b/DisCatSharp/Net/Rest/DiscordApiClient.cs @@ -1,5411 +1,5411 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Globalization; using System.Linq; using System.Net; using System.Text; using System.Threading.Tasks; using DisCatSharp.Entities; using DisCatSharp.Net.Abstractions; using DisCatSharp.Net.Serialization; using Microsoft.Extensions.Logging; using Newtonsoft.Json; using Newtonsoft.Json.Linq; 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. /// If true, post. /// A string. private static string BuildQueryString(IDictionary values, bool post = false) { if (values == null || values.Count == 0) return string.Empty; - var vals_collection = values.Select(xkvp => + var valsCollection = values.Select(xkvp => $"{WebUtility.UrlEncode(xkvp.Key)}={WebUtility.UrlEncode(xkvp.Value)}"); - var vals = string.Join("&", vals_collection); + var vals = string.Join("&", valsCollection); return !post ? $"?{vals}" : vals; } /// /// Prepares the message. /// - /// The msg_raw. + /// The msg_raw. /// A DiscordMessage. - private DiscordMessage PrepareMessage(JToken msg_raw) + private DiscordMessage PrepareMessage(JToken msgRaw) { - var author = msg_raw["author"].ToObject(); - var ret = msg_raw.ToDiscordObject(); + var author = msgRaw["author"].ToObject(); + var ret = msgRaw.ToDiscordObject(); ret.Discord = this.Discord; this.PopulateMessage(author, ret); - var referencedMsg = msg_raw["referenced_message"]; + 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 ret. 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, _guild_id = guild.Id }; + mbr = new DiscordMember(usr) { Discord = this.Discord, GuildId = guild.Id }; ret.Author = mbr; } else { ret.Author = usr; } } ret.PopulateMentions(); - if (ret._reactions == null) - ret._reactions = new List(); - foreach (var xr in ret._reactions) + if (ret.ReactionsInternal == null) + 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"); 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 guild_id. /// The name. /// The limit. - internal async Task> SearchMembersAsync(ulong guild_id, string name, int? 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 }, out var path); + 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, _guild_id = guild_id }); + 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 guild_id, ulong user_id) + /// 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, user_id}, out var path); + 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 region_id. /// The iconb64. - /// The verification_level. - /// The default_message_notifications. - /// The system_channel_flags. - internal async Task CreateGuildAsync(string name, string region_id, Optional iconb64, VerificationLevel? verification_level, - DefaultMessageNotifications? default_message_notifications, SystemChannelFlags? system_channel_flags) + /// 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 = region_id, - DefaultMessageNotifications = default_message_notifications, - VerificationLevel = verification_level, + RegionId = regionId, + DefaultMessageNotifications = defaultMessageNotifications, + VerificationLevel = verificationLevel, IconBase64 = iconb64, - SystemChannelFlags = system_channel_flags + 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 raw_members = (JArray)json["members"]; + var rawMembers = (JArray)json["members"]; var guild = json.ToDiscordObject(); if (this.Discord is DiscordClient dc) - await dc.OnGuildCreateEventAsync(guild, raw_members, null).ConfigureAwait(false); + await dc.OnGuildCreateEventAsync(guild, rawMembers, null).ConfigureAwait(false); return guild; } /// /// Creates the guild from template async. /// - /// The template_code. + /// The template_code. /// The name. /// The iconb64. - internal async Task CreateGuildFromTemplateAsync(string template_code, string name, Optional 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 }, out var path); + 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 raw_members = (JArray)json["members"]; + var rawMembers = (JArray)json["members"]; var guild = json.ToDiscordObject(); if (this.Discord is DiscordClient dc) - await dc.OnGuildCreateEventAsync(guild, raw_members, null).ConfigureAwait(false); + await dc.OnGuildCreateEventAsync(guild, rawMembers, null).ConfigureAwait(false); return guild; } /// /// Deletes the guild async. /// - /// The guild_id. - internal async Task DeleteGuildAsync(ulong guild_id) + /// 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 }, out var path); + 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._guilds[guild_id]; + 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 explicitContentFilter, Optional afkChannelId, Optional afkTimeout, Optional iconb64, Optional ownerId, Optional splashb64, Optional systemChannelId, Optional systemChannelFlags, Optional publicUpdatesChannelId, Optional rulesChannelId, Optional description, Optional bannerb64, Optional discorverySplashb64, Optional preferredLocale, Optional premiumProgressBarEnabled, string reason) { var pld = new RestGuildModifyPayload { Name = name, VerificationLevel = verificationLevel, DefaultMessageNotifications = defaultMessageNotifications, MfaLevel = mfaLevel, ExplicitContentFilter = explicitContentFilter, AfkChannelId = afkChannelId, AfkTimeout = afkTimeout, IconBase64 = iconb64, SplashBase64 = splashb64, BannerBase64 = bannerb64, DiscoverySplashBase64 = discorverySplashb64, OwnerId = ownerId, SystemChannelId = systemChannelId, SystemChannelFlags = systemChannelFlags, RulesChannelId = rulesChannelId, PublicUpdatesChannelId = publicUpdatesChannelId, PreferredLocale = preferredLocale, Description = description, PremiumProgressBarEnabled = premiumProgressBarEnabled }; var headers = Utilities.GetBaseHeaders(); if (!string.IsNullOrWhiteSpace(reason)) headers.Add(REASON_HEADER_NAME, reason); var route = $"{Endpoints.GUILDS}/:guild_id"; var bucket = this.Rest.GetBucket(RestRequestMethod.PATCH, route, new { guild_id = guildId }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.PATCH, route, headers, DiscordJson.SerializeObject(pld)).ConfigureAwait(false); var json = JObject.Parse(res.Response); var rawMembers = (JArray)json["members"]; var guild = json.ToDiscordObject(); - foreach (var r in guild._roles.Values) - r._guild_id = guild.Id; + foreach (var r in guild.RolesInternal.Values) + r.GuildId = guild.Id; if (this.Discord is DiscordClient dc) await dc.OnGuildUpdateEventAsync(guild, rawMembers).ConfigureAwait(false); return guild; } /// /// Modifies the guild community settings. /// /// The guild id. /// The guild features. /// The rules channel id. /// The public updates channel id. /// The preferred locale. /// The description. /// The default message notifications. /// The explicit content filter. /// The verification level. /// The reason. internal async Task ModifyGuildCommunitySettingsAsync(ulong guildId, List features, Optional rulesChannelId, Optional publicUpdatesChannelId, string preferredLocale, string description, DefaultMessageNotifications defaultMessageNotifications, ExplicitContentFilter explicitContentFilter, VerificationLevel verificationLevel, string reason) { var pld = new RestGuildCommunityModifyPayload { VerificationLevel = verificationLevel, DefaultMessageNotifications = defaultMessageNotifications, ExplicitContentFilter = explicitContentFilter, RulesChannelId = rulesChannelId, PublicUpdatesChannelId = publicUpdatesChannelId, PreferredLocale = preferredLocale, Description = description ?? Optional.FromNoValue(), Features = features }; var headers = Utilities.GetBaseHeaders(); if (!string.IsNullOrWhiteSpace(reason)) headers.Add(REASON_HEADER_NAME, reason); var route = $"{Endpoints.GUILDS}/:guild_id"; var bucket = this.Rest.GetBucket(RestRequestMethod.PATCH, route, new { guild_id = guildId }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.PATCH, route, headers, DiscordJson.SerializeObject(pld)).ConfigureAwait(false); var json = JObject.Parse(res.Response); var rawMembers = (JArray)json["members"]; var guild = json.ToDiscordObject(); - foreach (var r in guild._roles.Values) - r._guild_id = guild.Id; + foreach (var r in guild.RolesInternal.Values) + r.GuildId = guild.Id; if (this.Discord is DiscordClient dc) await dc.OnGuildUpdateEventAsync(guild, rawMembers).ConfigureAwait(false); return guild; } /// /// Gets the guild bans async. /// - /// The guild_id. + /// The guild_id. /// A Task. - internal async Task> GetGuildBansAsync(ulong guild_id) + internal async Task> GetGuildBansAsync(ulong guildId) { var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.BANS}"; - var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new { guild_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new {guild_id = guildId }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.GET, route).ConfigureAwait(false); - var bans_raw = JsonConvert.DeserializeObject>(res.Response).Select(xb => + var bansRaw = JsonConvert.DeserializeObject>(res.Response).Select(xb => { if (!this.Discord.TryGetCachedUserInternal(xb.RawUser.Id, out var usr)) { usr = new DiscordUser(xb.RawUser) { Discord = this.Discord }; usr = this.Discord.UserCache.AddOrUpdate(usr.Id, usr, (id, old) => { old.Username = usr.Username; old.Discriminator = usr.Discriminator; old.AvatarHash = usr.AvatarHash; return old; }); } xb.User = usr; return xb; }); - var bans = new ReadOnlyCollection(new List(bans_raw)); + var bans = new ReadOnlyCollection(new List(bansRaw)); return bans; } /// /// Creates the guild ban async. /// - /// The guild_id. - /// The user_id. - /// The delete_message_days. + /// The guild_id. + /// The user_id. + /// The delete_message_days. /// The reason. - internal Task CreateGuildBanAsync(ulong guild_id, ulong user_id, int delete_message_days, string reason) + internal Task CreateGuildBanAsync(ulong guildId, ulong userId, int deleteMessageDays, string reason) { - if (delete_message_days < 0 || delete_message_days > 7) - throw new ArgumentException("Delete message days must be a number between 0 and 7.", nameof(delete_message_days)); + if (deleteMessageDays < 0 || deleteMessageDays > 7) + throw new ArgumentException("Delete message days must be a number between 0 and 7.", nameof(deleteMessageDays)); var urlparams = new Dictionary { - ["delete_message_days"] = delete_message_days.ToString(CultureInfo.InvariantCulture) + ["delete_message_days"] = deleteMessageDays.ToString(CultureInfo.InvariantCulture) }; var headers = Utilities.GetBaseHeaders(); if (!string.IsNullOrWhiteSpace(reason)) headers.Add(REASON_HEADER_NAME, reason); var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.BANS}/:user_id"; - var bucket = this.Rest.GetBucket(RestRequestMethod.PUT, route, new { guild_id, user_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.PUT, route, new {guild_id = guildId, user_id = userId }, out var path); var url = Utilities.GetApiUriFor(path, BuildQueryString(urlparams), this.Discord.Configuration); return this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.PUT, route, headers); } /// /// Removes the guild ban async. /// - /// The guild_id. - /// The user_id. + /// The guild_id. + /// The user_id. /// The reason. /// A Task. - internal Task RemoveGuildBanAsync(ulong guild_id, ulong user_id, string reason) + internal Task RemoveGuildBanAsync(ulong guildId, ulong userId, string reason) { var headers = Utilities.GetBaseHeaders(); if (!string.IsNullOrWhiteSpace(reason)) headers.Add(REASON_HEADER_NAME, reason); var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.BANS}/:user_id"; - var bucket = this.Rest.GetBucket(RestRequestMethod.DELETE, route, new { guild_id, user_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.DELETE, route, new {guild_id = guildId, user_id = userId }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); return this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.DELETE, route, headers); } /// /// Leaves the guild async. /// - /// The guild_id. + /// The guild_id. /// A Task. - internal Task LeaveGuildAsync(ulong guild_id) + internal Task LeaveGuildAsync(ulong guildId) { var route = $"{Endpoints.USERS}{Endpoints.ME}{Endpoints.GUILDS}/:guild_id"; - var bucket = this.Rest.GetBucket(RestRequestMethod.DELETE, route, new { guild_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.DELETE, route, new {guild_id = guildId }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); return this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.DELETE, route); } /// /// Adds the guild member async. /// - /// The guild_id. - /// The user_id. - /// The access_token. + /// The guild_id. + /// The user_id. + /// The access_token. /// The nick. /// The roles. /// If true, muted. /// If true, deafened. /// A Task. - internal async Task AddGuildMemberAsync(ulong guild_id, ulong user_id, string access_token, string nick, IEnumerable roles, bool muted, bool deafened) + internal async Task AddGuildMemberAsync(ulong guildId, ulong userId, string accessToken, string nick, IEnumerable roles, bool muted, bool deafened) { var pld = new RestGuildMemberAddPayload { - AccessToken = access_token, + AccessToken = accessToken, Nickname = nick ?? "", Roles = roles ?? new List(), Deaf = deafened, Mute = muted }; var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.MEMBERS}/:user_id"; - var bucket = this.Rest.GetBucket(RestRequestMethod.PUT, route, new { guild_id, user_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.PUT, route, new {guild_id = guildId, user_id = userId }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.PUT, route, payload: DiscordJson.SerializeObject(pld)).ConfigureAwait(false); var tm = JsonConvert.DeserializeObject(res.Response); - return new DiscordMember(tm) { Discord = this.Discord, _guild_id = guild_id }; + return new DiscordMember(tm) { Discord = this.Discord, GuildId = guildId }; } /// /// Lists the guild members async. /// - /// The guild_id. + /// The guild_id. /// The limit. /// The after. /// A Task. - internal async Task> ListGuildMembersAsync(ulong guild_id, int? limit, ulong? after) + internal async Task> ListGuildMembersAsync(ulong guildId, int? limit, ulong? after) { var urlparams = new Dictionary(); if (limit != null && limit > 0) urlparams["limit"] = limit.Value.ToString(CultureInfo.InvariantCulture); if (after != null) urlparams["after"] = after.Value.ToString(CultureInfo.InvariantCulture); var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.MEMBERS}"; - var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new { guild_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new {guild_id = guildId }, out var path); var url = Utilities.GetApiUriFor(path, urlparams.Any() ? BuildQueryString(urlparams) : "", this.Discord.Configuration); var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.GET, route).ConfigureAwait(false); - var members_raw = JsonConvert.DeserializeObject>(res.Response); - return new ReadOnlyCollection(members_raw); + var membersRaw = JsonConvert.DeserializeObject>(res.Response); + return new ReadOnlyCollection(membersRaw); } /// /// Adds the guild member role async. /// - /// The guild_id. - /// The user_id. - /// The role_id. + /// The guild_id. + /// The user_id. + /// The role_id. /// The reason. /// A Task. - internal Task AddGuildMemberRoleAsync(ulong guild_id, ulong user_id, ulong role_id, string reason) + internal Task AddGuildMemberRoleAsync(ulong guildId, ulong userId, ulong roleId, string reason) { var headers = Utilities.GetBaseHeaders(); if (!string.IsNullOrWhiteSpace(reason)) headers.Add(REASON_HEADER_NAME, reason); var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.MEMBERS}/:user_id{Endpoints.ROLES}/:role_id"; - var bucket = this.Rest.GetBucket(RestRequestMethod.PUT, route, new { guild_id, user_id, role_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.PUT, route, new {guild_id = guildId, user_id = userId, role_id = roleId }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); return this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.PUT, route, headers); } /// /// Removes the guild member role async. /// - /// The guild_id. - /// The user_id. - /// The role_id. + /// The guild_id. + /// The user_id. + /// The role_id. /// The reason. /// A Task. - internal Task RemoveGuildMemberRoleAsync(ulong guild_id, ulong user_id, ulong role_id, string reason) + internal Task RemoveGuildMemberRoleAsync(ulong guildId, ulong userId, ulong roleId, string reason) { var headers = Utilities.GetBaseHeaders(); if (!string.IsNullOrWhiteSpace(reason)) headers.Add(REASON_HEADER_NAME, reason); var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.MEMBERS}/:user_id{Endpoints.ROLES}/:role_id"; - var bucket = this.Rest.GetBucket(RestRequestMethod.DELETE, route, new { guild_id, user_id, role_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.DELETE, route, new {guild_id = guildId, user_id = userId, role_id = roleId }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); return this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.DELETE, route, headers); } /// /// Modifies the guild channel position async. /// - /// The guild_id. + /// The guild_id. /// The pld. /// The reason. /// A Task. - internal Task ModifyGuildChannelPositionAsync(ulong guild_id, IEnumerable pld, string reason) + internal Task ModifyGuildChannelPositionAsync(ulong guildId, IEnumerable pld, string reason) { var headers = Utilities.GetBaseHeaders(); if (!string.IsNullOrWhiteSpace(reason)) headers.Add(REASON_HEADER_NAME, reason); var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.CHANNELS}"; - var bucket = this.Rest.GetBucket(RestRequestMethod.PATCH, route, new { guild_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.PATCH, route, new {guild_id = guildId }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); return this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.PATCH, route, headers, DiscordJson.SerializeObject(pld)); } /// /// Modifies the guild channel parent async. /// - /// The guild_id. + /// The guild_id. /// The pld. /// The reason. /// A Task. - internal Task ModifyGuildChannelParentAsync(ulong guild_id, IEnumerable pld, string reason) + internal Task ModifyGuildChannelParentAsync(ulong guildId, IEnumerable pld, string reason) { var headers = Utilities.GetBaseHeaders(); if (!string.IsNullOrWhiteSpace(reason)) headers.Add(REASON_HEADER_NAME, reason); var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.CHANNELS}"; - var bucket = this.Rest.GetBucket(RestRequestMethod.PATCH, route, new { guild_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.PATCH, route, new {guild_id = guildId }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); return this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.PATCH, route, headers, DiscordJson.SerializeObject(pld)); } /// /// Detaches the guild channel parent async. /// - /// The guild_id. + /// The guild_id. /// The pld. /// The reason. /// A Task. - internal Task DetachGuildChannelParentAsync(ulong guild_id, IEnumerable pld, string reason) + internal Task DetachGuildChannelParentAsync(ulong guildId, IEnumerable pld, string reason) { var headers = Utilities.GetBaseHeaders(); if (!string.IsNullOrWhiteSpace(reason)) headers.Add(REASON_HEADER_NAME, reason); var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.CHANNELS}"; - var bucket = this.Rest.GetBucket(RestRequestMethod.PATCH, route, new { guild_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.PATCH, route, new {guild_id = guildId }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); return this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.PATCH, route, headers, DiscordJson.SerializeObject(pld)); } /// /// Modifies the guild role position async. /// - /// The guild_id. + /// The guild_id. /// The pld. /// The reason. /// A Task. - internal Task ModifyGuildRolePositionAsync(ulong guild_id, IEnumerable pld, string reason) + internal Task ModifyGuildRolePositionAsync(ulong guildId, IEnumerable pld, string reason) { var headers = Utilities.GetBaseHeaders(); if (!string.IsNullOrWhiteSpace(reason)) headers.Add(REASON_HEADER_NAME, reason); var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.ROLES}"; - var bucket = this.Rest.GetBucket(RestRequestMethod.PATCH, route, new { guild_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.PATCH, route, new {guild_id = guildId }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); return this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.PATCH, route, headers, DiscordJson.SerializeObject(pld)); } /// /// Gets the audit logs async. /// - /// The guild_id. + /// The guild_id. /// The limit. /// The after. /// The before. /// The responsible. - /// The action_type. + /// The action_type. /// A Task. - internal async Task GetAuditLogsAsync(ulong guild_id, int limit, ulong? after, ulong? before, ulong? responsible, int? action_type) + internal async Task GetAuditLogsAsync(ulong guildId, int limit, ulong? after, ulong? before, ulong? responsible, int? actionType) { var urlparams = new Dictionary { ["limit"] = limit.ToString(CultureInfo.InvariantCulture) }; if (after != null) urlparams["after"] = after?.ToString(CultureInfo.InvariantCulture); if (before != null) urlparams["before"] = before?.ToString(CultureInfo.InvariantCulture); if (responsible != null) urlparams["user_id"] = responsible?.ToString(CultureInfo.InvariantCulture); - if (action_type != null) - urlparams["action_type"] = action_type?.ToString(CultureInfo.InvariantCulture); + if (actionType != null) + urlparams["action_type"] = actionType?.ToString(CultureInfo.InvariantCulture); var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.AUDIT_LOGS}"; - var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new { guild_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new {guild_id = guildId }, out var path); var url = Utilities.GetApiUriFor(path, urlparams.Any() ? BuildQueryString(urlparams) : "", this.Discord.Configuration); var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.GET, route).ConfigureAwait(false); - var audit_log_data_raw = JsonConvert.DeserializeObject(res.Response); + var auditLogDataRaw = JsonConvert.DeserializeObject(res.Response); - return audit_log_data_raw; + return auditLogDataRaw; } /// /// Gets the guild vanity url async. /// - /// The guild_id. + /// The guild_id. /// A Task. - internal async Task GetGuildVanityUrlAsync(ulong guild_id) + internal async Task GetGuildVanityUrlAsync(ulong guildId) { var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.VANITY_URL}"; - var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new { guild_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new {guild_id = guildId }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.GET, route).ConfigureAwait(false); var invite = JsonConvert.DeserializeObject(res.Response); return invite; } /// /// Gets the guild widget async. /// - /// The guild_id. + /// The guild_id. /// A Task. - internal async Task GetGuildWidgetAsync(ulong guild_id) + internal async Task GetGuildWidgetAsync(ulong guildId) { var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.WIDGET_JSON}"; - var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new { guild_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new {guild_id = guildId }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.GET, route).ConfigureAwait(false); var json = JObject.Parse(res.Response); var rawChannels = (JArray)json["channels"]; var ret = json.ToDiscordObject(); ret.Discord = this.Discord; - ret.Guild = this.Discord.Guilds[guild_id]; + ret.Guild = this.Discord.Guilds[guildId]; ret.Channels = ret.Guild == null ? rawChannels.Select(r => new DiscordChannel { Id = (ulong)r["id"], Name = r["name"].ToString(), Position = (int)r["position"] }).ToList() : rawChannels.Select(r => { var c = ret.Guild.GetChannel((ulong)r["id"]); c.Position = (int)r["position"]; return c; }).ToList(); return ret; } /// /// Gets the guild widget settings async. /// - /// The guild_id. + /// The guild_id. /// A Task. - internal async Task GetGuildWidgetSettingsAsync(ulong guild_id) + internal async Task GetGuildWidgetSettingsAsync(ulong guildId) { var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.WIDGET}"; - var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new { guild_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new {guild_id = guildId }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.GET, route).ConfigureAwait(false); var ret = JsonConvert.DeserializeObject(res.Response); - ret.Guild = this.Discord.Guilds[guild_id]; + ret.Guild = this.Discord.Guilds[guildId]; return ret; } /// /// Modifies the guild widget settings async. /// - /// The guild_id. + /// The guild_id. /// If true, is enabled. /// The channel id. /// The reason. /// A Task. - internal async Task ModifyGuildWidgetSettingsAsync(ulong guild_id, bool? isEnabled, ulong? channelId, string reason) + internal async Task ModifyGuildWidgetSettingsAsync(ulong guildId, bool? isEnabled, ulong? channelId, string reason) { var pld = new RestGuildWidgetSettingsPayload { Enabled = isEnabled, ChannelId = channelId }; var headers = Utilities.GetBaseHeaders(); if (!string.IsNullOrWhiteSpace(reason)) headers.Add(REASON_HEADER_NAME, reason); var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.WIDGET}"; - var bucket = this.Rest.GetBucket(RestRequestMethod.PATCH, route, new { guild_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.PATCH, route, new {guild_id = guildId }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.PATCH, route, headers, DiscordJson.SerializeObject(pld)).ConfigureAwait(false); var ret = JsonConvert.DeserializeObject(res.Response); - ret.Guild = this.Discord.Guilds[guild_id]; + ret.Guild = this.Discord.Guilds[guildId]; return ret; } /// /// Gets the guild templates async. /// - /// The guild_id. + /// The guild_id. /// A Task. - internal async Task> GetGuildTemplatesAsync(ulong guild_id) + internal async Task> GetGuildTemplatesAsync(ulong guildId) { var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.TEMPLATES}"; - var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new { guild_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new {guild_id = guildId }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.GET, route).ConfigureAwait(false); - var templates_raw = JsonConvert.DeserializeObject>(res.Response); + var templatesRaw = JsonConvert.DeserializeObject>(res.Response); - return new ReadOnlyCollection(new List(templates_raw)); + return new ReadOnlyCollection(new List(templatesRaw)); } /// /// Creates the guild template async. /// - /// The guild_id. + /// The guild_id. /// The name. /// The description. /// A Task. - internal async Task CreateGuildTemplateAsync(ulong guild_id, string name, string description) + internal async Task CreateGuildTemplateAsync(ulong guildId, string name, string description) { var pld = new RestGuildTemplateCreateOrModifyPayload { Name = name, Description = description }; var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.TEMPLATES}"; - var bucket = this.Rest.GetBucket(RestRequestMethod.POST, route, new { guild_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.POST, route, new {guild_id = guildId }, 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 ret = JsonConvert.DeserializeObject(res.Response); return ret; } /// /// Syncs the guild template async. /// - /// The guild_id. - /// The template_code. + /// The guild_id. + /// The template_code. /// A Task. - internal async Task SyncGuildTemplateAsync(ulong guild_id, string template_code) + internal async Task SyncGuildTemplateAsync(ulong guildId, string templateCode) { var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.TEMPLATES}/:template_code"; - var bucket = this.Rest.GetBucket(RestRequestMethod.PUT, route, new { guild_id, template_code }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.PUT, route, new {guild_id = guildId, template_code = templateCode }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.PUT, route).ConfigureAwait(false); - var template_raw = JsonConvert.DeserializeObject(res.Response); + var templateRaw = JsonConvert.DeserializeObject(res.Response); - return template_raw; + return templateRaw; } /// /// Modifies the guild template async. /// - /// The guild_id. - /// The template_code. + /// The guild_id. + /// The template_code. /// The name. /// The description. /// A Task. - internal async Task ModifyGuildTemplateAsync(ulong guild_id, string template_code, string name, string description) + internal async Task ModifyGuildTemplateAsync(ulong guildId, string templateCode, string name, string description) { var pld = new RestGuildTemplateCreateOrModifyPayload { Name = name, Description = description }; var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.TEMPLATES}/:template_code"; - var bucket = this.Rest.GetBucket(RestRequestMethod.PATCH, route, new { guild_id, template_code }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.PATCH, route, new {guild_id = guildId, template_code = templateCode }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.PATCH, route, payload: DiscordJson.SerializeObject(pld)).ConfigureAwait(false); - var template_raw = JsonConvert.DeserializeObject(res.Response); + var templateRaw = JsonConvert.DeserializeObject(res.Response); - return template_raw; + return templateRaw; } /// /// Deletes the guild template async. /// - /// The guild_id. - /// The template_code. + /// The guild_id. + /// The template_code. /// A Task. - internal async Task DeleteGuildTemplateAsync(ulong guild_id, string template_code) + internal async Task DeleteGuildTemplateAsync(ulong guildId, string templateCode) { var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.TEMPLATES}/:template_code"; - var bucket = this.Rest.GetBucket(RestRequestMethod.DELETE, route, new { guild_id, template_code }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.DELETE, route, new {guild_id = guildId, template_code = templateCode }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.DELETE, route).ConfigureAwait(false); - var template_raw = JsonConvert.DeserializeObject(res.Response); + var templateRaw = JsonConvert.DeserializeObject(res.Response); - return template_raw; + return templateRaw; } /// /// Gets the guild membership screening form async. /// - /// The guild_id. + /// The guild_id. /// A Task. - internal async Task GetGuildMembershipScreeningFormAsync(ulong guild_id) + internal async Task GetGuildMembershipScreeningFormAsync(ulong guildId) { var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.MEMBER_VERIFICATION}"; - var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new { guild_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new {guild_id = guildId }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.GET, route).ConfigureAwait(false); - var screening_raw = JsonConvert.DeserializeObject(res.Response); + var screeningRaw = JsonConvert.DeserializeObject(res.Response); - return screening_raw; + return screeningRaw; } /// /// Modifies the guild membership screening form async. /// - /// The guild_id. + /// The guild_id. /// The enabled. /// The fields. /// The description. /// A Task. - internal async Task ModifyGuildMembershipScreeningFormAsync(ulong guild_id, Optional enabled, Optional fields, Optional description) + internal async Task ModifyGuildMembershipScreeningFormAsync(ulong guildId, Optional enabled, Optional fields, Optional description) { var pld = new RestGuildMembershipScreeningFormModifyPayload { Enabled = enabled, Description = description, Fields = fields }; var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.MEMBER_VERIFICATION}"; - var bucket = this.Rest.GetBucket(RestRequestMethod.PATCH, route, new { guild_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.PATCH, route, new {guild_id = guildId }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.PATCH, route, payload: DiscordJson.SerializeObject(pld)).ConfigureAwait(false); - var screening_raw = JsonConvert.DeserializeObject(res.Response); + var screeningRaw = JsonConvert.DeserializeObject(res.Response); - return screening_raw; + return screeningRaw; } /// /// Gets the guild welcome screen async. /// - /// The guild_id. + /// The guild_id. /// A Task. - internal async Task GetGuildWelcomeScreenAsync(ulong guild_id) + internal async Task GetGuildWelcomeScreenAsync(ulong guildId) { var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.WELCOME_SCREEN}"; - var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new { guild_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new {guild_id = guildId }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.GET, route); var ret = JsonConvert.DeserializeObject(res.Response); return ret; } /// /// Modifies the guild welcome screen async. /// - /// The guild_id. + /// The guild_id. /// The enabled. /// The welcome channels. /// The description. /// A Task. - internal async Task ModifyGuildWelcomeScreenAsync(ulong guild_id, Optional enabled, Optional> welcomeChannels, Optional description) + internal async Task ModifyGuildWelcomeScreenAsync(ulong guildId, Optional enabled, Optional> welcomeChannels, Optional description) { var pld = new RestGuildWelcomeScreenModifyPayload { Enabled = enabled, WelcomeChannels = welcomeChannels, Description = description }; var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.WELCOME_SCREEN}"; - var bucket = this.Rest.GetBucket(RestRequestMethod.PATCH, route, new { guild_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.PATCH, route, new {guild_id = guildId }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.PATCH, route, payload: DiscordJson.SerializeObject(pld)); var ret = JsonConvert.DeserializeObject(res.Response); return ret; } /// /// Updates the current user voice state async. /// - /// The guild_id. + /// The guild_id. /// The channel id. /// If true, suppress. /// The request to speak timestamp. /// A Task. - internal async Task UpdateCurrentUserVoiceStateAsync(ulong guild_id, ulong channelId, bool? suppress, DateTimeOffset? requestToSpeakTimestamp) + internal async Task UpdateCurrentUserVoiceStateAsync(ulong guildId, ulong channelId, bool? suppress, DateTimeOffset? requestToSpeakTimestamp) { var pld = new RestGuildUpdateCurrentUserVoiceStatePayload { ChannelId = channelId, Suppress = suppress, RequestToSpeakTimestamp = requestToSpeakTimestamp }; var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.VOICE_STATES}/@me"; - var bucket = this.Rest.GetBucket(RestRequestMethod.PATCH, route, new { guild_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.PATCH, route, new {guild_id = guildId }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.PATCH, route, payload: DiscordJson.SerializeObject(pld)); } /// /// Updates the user voice state async. /// - /// The guild_id. - /// The user_id. + /// The guild_id. + /// The user_id. /// The channel id. /// If true, suppress. /// A Task. - internal async Task UpdateUserVoiceStateAsync(ulong guild_id, ulong user_id, ulong channelId, bool? suppress) + internal async Task UpdateUserVoiceStateAsync(ulong guildId, ulong userId, ulong channelId, bool? suppress) { var pld = new RestGuildUpdateUserVoiceStatePayload { ChannelId = channelId, Suppress = suppress }; var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.VOICE_STATES}/:user_id"; - var bucket = this.Rest.GetBucket(RestRequestMethod.PATCH, route, new { guild_id, user_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.PATCH, route, new {guild_id = guildId, user_id = userId }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.PATCH, route, payload: DiscordJson.SerializeObject(pld)); } #endregion #region Guild Scheduled Events /// /// Creates a scheduled event. /// - internal async Task CreateGuildScheduledEventAsync(ulong guild_id, ulong? channel_id, DiscordScheduledEventEntityMetadata metadata, string name, DateTimeOffset scheduled_start_time, DateTimeOffset? scheduled_end_time, string description, ScheduledEventEntityType type, string reason = null) + internal async Task CreateGuildScheduledEventAsync(ulong guildId, ulong? channelId, DiscordScheduledEventEntityMetadata metadata, string name, DateTimeOffset scheduledStartTime, DateTimeOffset? scheduledEndTime, string description, ScheduledEventEntityType type, string reason = null) { var pld = new RestGuildScheduledEventCreatePayload { - ChannelId = channel_id, + ChannelId = channelId, EntityMetadata = metadata, Name = name, - ScheduledStartTime = scheduled_start_time, - ScheduledEndTime = scheduled_end_time, + ScheduledStartTime = scheduledStartTime, + ScheduledEndTime = scheduledEndTime, Description = description, EntityType = type }; var headers = Utilities.GetBaseHeaders(); if (!string.IsNullOrWhiteSpace(reason)) headers[REASON_HEADER_NAME] = reason; var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.SCHEDULED_EVENTS}"; - var bucket = this.Rest.GetBucket(RestRequestMethod.POST, route, new { guild_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.POST, route, new {guild_id = guildId }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.POST, route, headers, DiscordJson.SerializeObject(pld)); - var scheduled_event = JsonConvert.DeserializeObject(res.Response); - var guild = this.Discord.Guilds[guild_id]; + var scheduledEvent = JsonConvert.DeserializeObject(res.Response); + var guild = this.Discord.Guilds[guildId]; - scheduled_event.Discord = this.Discord; + scheduledEvent.Discord = this.Discord; - if (scheduled_event.Creator != null) - scheduled_event.Creator.Discord = this.Discord; + if (scheduledEvent.Creator != null) + scheduledEvent.Creator.Discord = this.Discord; if (this.Discord is DiscordClient dc) - await dc.OnGuildScheduledEventCreateEventAsync(scheduled_event, guild); + await dc.OnGuildScheduledEventCreateEventAsync(scheduledEvent, guild); - return scheduled_event; + return scheduledEvent; } /// /// Modifies a scheduled event. /// - internal async Task ModifyGuildScheduledEventAsync(ulong guild_id, ulong scheduled_event_id, Optional channel_id, Optional metadata, Optional name, Optional scheduled_start_time, Optional scheduled_end_time, Optional description, Optional type, Optional status, string reason = null) + internal async Task ModifyGuildScheduledEventAsync(ulong guildId, ulong scheduledEventId, Optional channelId, Optional metadata, Optional name, Optional scheduledStartTime, Optional scheduledEndTime, Optional description, Optional type, Optional status, string reason = null) { var pld = new RestGuildSheduledEventModifyPayload { - ChannelId = channel_id, + ChannelId = channelId, EntityMetadata = metadata, Name = name, - ScheduledStartTime = scheduled_start_time, - ScheduledEndTime = scheduled_end_time, + ScheduledStartTime = scheduledStartTime, + ScheduledEndTime = scheduledEndTime, Description = description, EntityType = type, Status = status }; var headers = Utilities.GetBaseHeaders(); if (!string.IsNullOrWhiteSpace(reason)) headers[REASON_HEADER_NAME] = reason; var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.SCHEDULED_EVENTS}/:scheduled_event_id"; - var bucket = this.Rest.GetBucket(RestRequestMethod.PATCH, route, new { guild_id, scheduled_event_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.PATCH, route, new {guild_id = guildId, scheduled_event_id = scheduledEventId }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.PATCH, route, headers, DiscordJson.SerializeObject(pld)); - var scheduled_event = JsonConvert.DeserializeObject(res.Response); - var guild = this.Discord.Guilds[guild_id]; + var scheduledEvent = JsonConvert.DeserializeObject(res.Response); + var guild = this.Discord.Guilds[guildId]; - scheduled_event.Discord = this.Discord; + scheduledEvent.Discord = this.Discord; - if (scheduled_event.Creator != null) + if (scheduledEvent.Creator != null) { - scheduled_event.Creator.Discord = this.Discord; - this.Discord.UserCache.AddOrUpdate(scheduled_event.Creator.Id, scheduled_event.Creator, (id, old) => + scheduledEvent.Creator.Discord = this.Discord; + this.Discord.UserCache.AddOrUpdate(scheduledEvent.Creator.Id, scheduledEvent.Creator, (id, old) => { - old.Username = scheduled_event.Creator.Username; - old.Discriminator = scheduled_event.Creator.Discriminator; - old.AvatarHash = scheduled_event.Creator.AvatarHash; - old.Flags = scheduled_event.Creator.Flags; + old.Username = scheduledEvent.Creator.Username; + old.Discriminator = scheduledEvent.Creator.Discriminator; + old.AvatarHash = scheduledEvent.Creator.AvatarHash; + old.Flags = scheduledEvent.Creator.Flags; return old; }); } if (this.Discord is DiscordClient dc) - await dc.OnGuildScheduledEventUpdateEventAsync(scheduled_event, guild); + await dc.OnGuildScheduledEventUpdateEventAsync(scheduledEvent, guild); - return scheduled_event; + return scheduledEvent; } /// /// Modifies a scheduled event. /// - internal async Task ModifyGuildScheduledEventStatusAsync(ulong guild_id, ulong scheduled_event_id, ScheduledEventStatus status, string reason = null) + internal async Task ModifyGuildScheduledEventStatusAsync(ulong guildId, ulong scheduledEventId, ScheduledEventStatus status, string reason = null) { var pld = new RestGuildSheduledEventModifyPayload { Status = status }; var headers = Utilities.GetBaseHeaders(); if (!string.IsNullOrWhiteSpace(reason)) headers[REASON_HEADER_NAME] = reason; var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.SCHEDULED_EVENTS}/:scheduled_event_id"; - var bucket = this.Rest.GetBucket(RestRequestMethod.PATCH, route, new { guild_id, scheduled_event_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.PATCH, route, new {guild_id = guildId, scheduled_event_id = scheduledEventId }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.PATCH, route, headers, DiscordJson.SerializeObject(pld)); - var scheduled_event = JsonConvert.DeserializeObject(res.Response); - var guild = this.Discord.Guilds[guild_id]; + var scheduledEvent = JsonConvert.DeserializeObject(res.Response); + var guild = this.Discord.Guilds[guildId]; - scheduled_event.Discord = this.Discord; + scheduledEvent.Discord = this.Discord; - if (scheduled_event.Creator != null) + if (scheduledEvent.Creator != null) { - scheduled_event.Creator.Discord = this.Discord; - this.Discord.UserCache.AddOrUpdate(scheduled_event.Creator.Id, scheduled_event.Creator, (id, old) => + scheduledEvent.Creator.Discord = this.Discord; + this.Discord.UserCache.AddOrUpdate(scheduledEvent.Creator.Id, scheduledEvent.Creator, (id, old) => { - old.Username = scheduled_event.Creator.Username; - old.Discriminator = scheduled_event.Creator.Discriminator; - old.AvatarHash = scheduled_event.Creator.AvatarHash; - old.Flags = scheduled_event.Creator.Flags; + old.Username = scheduledEvent.Creator.Username; + old.Discriminator = scheduledEvent.Creator.Discriminator; + old.AvatarHash = scheduledEvent.Creator.AvatarHash; + old.Flags = scheduledEvent.Creator.Flags; return old; }); } if (this.Discord is DiscordClient dc) - await dc.OnGuildScheduledEventUpdateEventAsync(scheduled_event, guild); + await dc.OnGuildScheduledEventUpdateEventAsync(scheduledEvent, guild); - return scheduled_event; + return scheduledEvent; } /// /// Gets a scheduled event. /// - /// The guild_id. - /// The event id. - /// Whether to include user count. - internal async Task GetGuildScheduledEventAsync(ulong guild_id, ulong scheduled_event_id, bool? with_user_count) + /// The guild_id. + /// The event id. + /// Whether to include user count. + internal async Task GetGuildScheduledEventAsync(ulong guildId, ulong scheduledEventId, bool? withUserCount) { var urlparams = new Dictionary(); - if (with_user_count.HasValue) - urlparams["with_user_count"] = with_user_count?.ToString(); + if (withUserCount.HasValue) + urlparams["with_user_count"] = withUserCount?.ToString(); var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.SCHEDULED_EVENTS}/:scheduled_event_id"; - var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new { guild_id, scheduled_event_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new {guild_id = guildId, scheduled_event_id = scheduledEventId }, out var path); var url = Utilities.GetApiUriFor(path, urlparams.Any() ? BuildQueryString(urlparams) : "", this.Discord.Configuration); var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.GET, route); - var scheduled_event = JsonConvert.DeserializeObject(res.Response); - var guild = this.Discord.Guilds[guild_id]; + var scheduledEvent = JsonConvert.DeserializeObject(res.Response); + var guild = this.Discord.Guilds[guildId]; - scheduled_event.Discord = this.Discord; + scheduledEvent.Discord = this.Discord; - if (scheduled_event.Creator != null) + if (scheduledEvent.Creator != null) { - scheduled_event.Creator.Discord = this.Discord; - this.Discord.UserCache.AddOrUpdate(scheduled_event.Creator.Id, scheduled_event.Creator, (id, old) => + scheduledEvent.Creator.Discord = this.Discord; + this.Discord.UserCache.AddOrUpdate(scheduledEvent.Creator.Id, scheduledEvent.Creator, (id, old) => { - old.Username = scheduled_event.Creator.Username; - old.Discriminator = scheduled_event.Creator.Discriminator; - old.AvatarHash = scheduled_event.Creator.AvatarHash; - old.Flags = scheduled_event.Creator.Flags; + old.Username = scheduledEvent.Creator.Username; + old.Discriminator = scheduledEvent.Creator.Discriminator; + old.AvatarHash = scheduledEvent.Creator.AvatarHash; + old.Flags = scheduledEvent.Creator.Flags; return old; }); } - return scheduled_event; + return scheduledEvent; } /// /// Gets the guilds scheduled events. /// - /// The guild_id. - /// Whether to include the count of users subscribed to the scheduled event. - internal async Task> ListGuildScheduledEventsAsync(ulong guild_id, bool? with_user_count) + /// The guild_id. + /// Whether to include the count of users subscribed to the scheduled event. + internal async Task> ListGuildScheduledEventsAsync(ulong guildId, bool? withUserCount) { var urlparams = new Dictionary(); - if (with_user_count.HasValue) - urlparams["with_user_count"] = with_user_count?.ToString(); + if (withUserCount.HasValue) + urlparams["with_user_count"] = withUserCount?.ToString(); var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.SCHEDULED_EVENTS}"; - var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new { guild_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new {guild_id = guildId }, out var path); var url = Utilities.GetApiUriFor(path, urlparams.Any() ? BuildQueryString(urlparams) : "", this.Discord.Configuration); var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.GET, route); var events = new Dictionary(); - var events_raw = JsonConvert.DeserializeObject>(res.Response); - var guild = this.Discord.Guilds[guild_id]; + var eventsRaw = JsonConvert.DeserializeObject>(res.Response); + var guild = this.Discord.Guilds[guildId]; - foreach (var ev in events_raw) + foreach (var ev in eventsRaw) { ev.Discord = this.Discord; if (ev.Creator != null) { ev.Creator.Discord = this.Discord; this.Discord.UserCache.AddOrUpdate(ev.Creator.Id, ev.Creator, (id, old) => { old.Username = ev.Creator.Username; old.Discriminator = ev.Creator.Discriminator; old.AvatarHash = ev.Creator.AvatarHash; old.Flags = ev.Creator.Flags; return old; }); } events.Add(ev.Id, ev); } return new ReadOnlyDictionary(new Dictionary(events)); } /// /// Deletes a guild sheduled event. /// - /// The guild_id. - /// The sheduled event id. + /// The guild_id. + /// The sheduled event id. /// The reason. - internal Task DeleteGuildScheduledEventAsync(ulong guild_id, ulong scheduled_event_id, string reason) + internal Task DeleteGuildScheduledEventAsync(ulong guildId, ulong scheduledEventId, string reason) { var headers = Utilities.GetBaseHeaders(); if (!string.IsNullOrWhiteSpace(reason)) headers.Add(REASON_HEADER_NAME, reason); var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.SCHEDULED_EVENTS}/:scheduled_event_id"; - var bucket = this.Rest.GetBucket(RestRequestMethod.DELETE, route, new { guild_id, scheduled_event_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.DELETE, route, new {guild_id = guildId, scheduled_event_id = scheduledEventId }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); return this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.DELETE, route, headers); } /// /// Gets the users who RSVP'd to a sheduled event. /// Optional with member objects. /// This endpoint is paginated. /// - /// The guild_id. - /// The sheduled event id. + /// The guild_id. + /// The sheduled event id. /// The limit how many users to receive from the event. /// Get results before the given id. /// Get results after the given id. - /// Wether to include guild member data. attaches guild_member property to the user object. - internal async Task> GetGuildScheduledEventRSPVUsersAsync(ulong guild_id, ulong scheduled_event_id, int? limit, ulong? before, ulong? after, bool? with_member) + /// Wether to include guild member data. attaches guild_member property to the user object. + internal async Task> GetGuildScheduledEventRspvUsersAsync(ulong guildId, ulong scheduledEventId, int? limit, ulong? before, ulong? after, bool? withMember) { var urlparams = new Dictionary(); if (limit != null && limit > 0) urlparams["limit"] = limit.Value.ToString(CultureInfo.InvariantCulture); if (before != null) urlparams["before"] = before.Value.ToString(CultureInfo.InvariantCulture); if (after != null) urlparams["after"] = after.Value.ToString(CultureInfo.InvariantCulture); - if (with_member != null) - urlparams["with_member"] = with_member.Value.ToString(CultureInfo.InvariantCulture); + if (withMember != null) + urlparams["with_member"] = withMember.Value.ToString(CultureInfo.InvariantCulture); var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.SCHEDULED_EVENTS}/:scheduled_event_id{Endpoints.USERS}"; - var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new { guild_id, scheduled_event_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new {guild_id = guildId, scheduled_event_id = scheduledEventId }, out var path); var url = Utilities.GetApiUriFor(path, urlparams.Any() ? BuildQueryString(urlparams) : "", this.Discord.Configuration); var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.GET, route).ConfigureAwait(false); - var rspv_users = JsonConvert.DeserializeObject>(res.Response); + var rspvUsers = JsonConvert.DeserializeObject>(res.Response); Dictionary rspv = new(); - foreach (var rspv_user in rspv_users) + foreach (var rspvUser in rspvUsers) { - rspv_user.Discord = this.Discord; - rspv_user.GuildId = guild_id; + rspvUser.Discord = this.Discord; + rspvUser.GuildId = guildId; - rspv_user.User.Discord = this.Discord; - rspv_user.User = this.Discord.UserCache.AddOrUpdate(rspv_user.User.Id, rspv_user.User, (id, old) => + rspvUser.User.Discord = this.Discord; + rspvUser.User = this.Discord.UserCache.AddOrUpdate(rspvUser.User.Id, rspvUser.User, (id, old) => { - old.Username = rspv_user.User.Username; - old.Discriminator = rspv_user.User.Discriminator; - old.AvatarHash = rspv_user.User.AvatarHash; - old.BannerHash = rspv_user.User.BannerHash; - old._bannerColor = rspv_user.User._bannerColor; + old.Username = rspvUser.User.Username; + old.Discriminator = rspvUser.User.Discriminator; + old.AvatarHash = rspvUser.User.AvatarHash; + old.BannerHash = rspvUser.User.BannerHash; + old.BannerColorInternal = rspvUser.User.BannerColorInternal; return old; }); /*if (with_member.HasValue && with_member.Value && rspv_user.Member != null) { rspv_user.Member.Discord = this.Discord; }*/ - rspv.Add(rspv_user.User.Id, rspv_user); + rspv.Add(rspvUser.User.Id, rspvUser); } return new ReadOnlyDictionary(new Dictionary(rspv)); } #endregion #region Channel /// /// Creates the guild channel async. /// - /// The guild_id. + /// The guild_id. /// The name. /// The type. /// The parent. /// The topic. /// The bitrate. - /// The user_limit. + /// The user_limit. /// The overwrites. /// If true, nsfw. /// The per user rate limit. /// The quality mode. /// The reason. /// A Task. - internal async Task CreateGuildChannelAsync(ulong guild_id, string name, ChannelType type, ulong? parent, Optional topic, int? bitrate, int? user_limit, IEnumerable overwrites, bool? nsfw, Optional perUserRateLimit, VideoQualityMode? qualityMode, string reason) + internal async Task CreateGuildChannelAsync(ulong guildId, string name, ChannelType type, ulong? parent, Optional topic, int? bitrate, int? userLimit, IEnumerable overwrites, bool? nsfw, Optional perUserRateLimit, VideoQualityMode? qualityMode, string reason) { var restoverwrites = new List(); if (overwrites != null) foreach (var ow in overwrites) restoverwrites.Add(ow.Build()); var pld = new RestChannelCreatePayload { Name = name, Type = type, Parent = parent, Topic = topic, Bitrate = bitrate, - UserLimit = user_limit, + UserLimit = userLimit, PermissionOverwrites = restoverwrites, Nsfw = nsfw, PerUserRateLimit = perUserRateLimit, QualityMode = qualityMode }; var headers = Utilities.GetBaseHeaders(); if (!string.IsNullOrWhiteSpace(reason)) headers.Add(REASON_HEADER_NAME, reason); var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.CHANNELS}"; - var bucket = this.Rest.GetBucket(RestRequestMethod.POST, route, new { guild_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.POST, route, new {guild_id = guildId }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.POST, route, headers, DiscordJson.SerializeObject(pld)).ConfigureAwait(false); var ret = JsonConvert.DeserializeObject(res.Response); ret.Discord = this.Discord; - foreach (var xo in ret._permissionOverwrites) + foreach (var xo in ret.PermissionOverwritesInternal) { xo.Discord = this.Discord; - xo._channel_id = ret.Id; + xo.ChannelId = ret.Id; } return ret; } /// /// Modifies the channel async. /// - /// The channel_id. + /// The channel_id. /// The name. /// The position. /// The topic. /// If true, nsfw. /// The parent. /// The bitrate. - /// The user_limit. + /// The user_limit. /// The per user rate limit. /// The rtc region. /// The quality mode. /// The default auto archive duration. /// The type. /// The permission overwrites. /// The banner. /// The reason. - internal Task ModifyChannelAsync(ulong channel_id, string name, int? position, Optional topic, bool? nsfw, Optional parent, int? bitrate, int? user_limit, Optional perUserRateLimit, Optional rtcRegion, VideoQualityMode? qualityMode, ThreadAutoArchiveDuration? autoArchiveDuration, Optional type, IEnumerable permissionOverwrites, Optional bannerb64, string reason) + internal Task ModifyChannelAsync(ulong channelId, string name, int? position, Optional topic, bool? nsfw, Optional parent, int? bitrate, int? userLimit, Optional perUserRateLimit, Optional rtcRegion, VideoQualityMode? qualityMode, ThreadAutoArchiveDuration? autoArchiveDuration, Optional type, IEnumerable permissionOverwrites, Optional bannerb64, string reason) { List restoverwrites = null; if (permissionOverwrites != null) { restoverwrites = new List(); foreach (var ow in permissionOverwrites) restoverwrites.Add(ow.Build()); } var pld = new RestChannelModifyPayload { Name = name, Position = position, Topic = topic, Nsfw = nsfw, Parent = parent, Bitrate = bitrate, - UserLimit = user_limit, + UserLimit = userLimit, PerUserRateLimit = perUserRateLimit, RtcRegion = rtcRegion, QualityMode = qualityMode, DefaultAutoArchiveDuration = autoArchiveDuration, Type = type, PermissionOverwrites = restoverwrites, BannerBase64 = bannerb64 }; var headers = Utilities.GetBaseHeaders(); if (!string.IsNullOrWhiteSpace(reason)) headers.Add(REASON_HEADER_NAME, reason); var route = $"{Endpoints.CHANNELS}/:channel_id"; - var bucket = this.Rest.GetBucket(RestRequestMethod.PATCH, route, new { channel_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.PATCH, route, new {channel_id = channelId }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); return this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.PATCH, route, headers, DiscordJson.SerializeObject(pld)); } /// /// Gets the channel async. /// - /// The channel_id. + /// The channel_id. /// A Task. - internal async Task GetChannelAsync(ulong channel_id) + internal async Task GetChannelAsync(ulong channelId) { var route = $"{Endpoints.CHANNELS}/:channel_id"; - var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new { channel_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new {channel_id = channelId }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.GET, route).ConfigureAwait(false); var ret = JsonConvert.DeserializeObject(res.Response); ret.Discord = this.Discord; - foreach (var xo in ret._permissionOverwrites) + foreach (var xo in ret.PermissionOverwritesInternal) { xo.Discord = this.Discord; - xo._channel_id = ret.Id; + xo.ChannelId = ret.Id; } return ret; } /// /// Deletes the channel async. /// - /// The channel_id. + /// The channel_id. /// The reason. /// A Task. - internal Task DeleteChannelAsync(ulong channel_id, string reason) + internal Task DeleteChannelAsync(ulong channelId, string reason) { var headers = Utilities.GetBaseHeaders(); if (!string.IsNullOrWhiteSpace(reason)) headers.Add(REASON_HEADER_NAME, reason); var route = $"{Endpoints.CHANNELS}/:channel_id"; - var bucket = this.Rest.GetBucket(RestRequestMethod.DELETE, route, new { channel_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.DELETE, route, new {channel_id = channelId }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); return this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.DELETE, route, headers); } /// /// Gets the message async. /// - /// The channel_id. - /// The message_id. + /// The channel_id. + /// The message_id. /// A Task. - internal async Task GetMessageAsync(ulong channel_id, ulong message_id) + internal async Task GetMessageAsync(ulong channelId, ulong messageId) { var route = $"{Endpoints.CHANNELS}/:channel_id{Endpoints.MESSAGES}/:message_id"; - var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new { channel_id, message_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new {channel_id = channelId, message_id = messageId }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.GET, route).ConfigureAwait(false); var ret = this.PrepareMessage(JObject.Parse(res.Response)); return ret; } /// /// Creates the message async. /// - /// The channel_id. + /// The channel_id. /// The content. /// The embeds. /// The sticker. /// The reply message id. /// If true, mention reply. /// If true, fail on invalid reply. /// A Task. - internal async Task CreateMessageAsync(ulong channel_id, string content, IEnumerable embeds, DiscordSticker sticker, ulong? replyMessageId, bool mentionReply, bool failOnInvalidReply) + internal async Task CreateMessageAsync(ulong channelId, string content, IEnumerable embeds, DiscordSticker sticker, ulong? replyMessageId, bool mentionReply, bool failOnInvalidReply) { if (content != null && content.Length > 2000) throw new ArgumentException("Message content length cannot exceed 2000 characters."); if (!embeds?.Any() ?? true) { if (content == null && sticker == null) throw new ArgumentException("You must specify message content, a sticker or an embed."); if (content.Length == 0) throw new ArgumentException("Message content must not be empty."); } if (embeds != null) foreach (var embed in embeds) if (embed.Timestamp != null) embed.Timestamp = embed.Timestamp.Value.ToUniversalTime(); var pld = new RestChannelMessageCreatePayload { HasContent = content != null, Content = content, StickersIds = sticker is null ? Array.Empty() : new[] {sticker.Id}, - IsTTS = false, + IsTts = false, HasEmbed = embeds?.Any() ?? false, Embeds = embeds }; if (replyMessageId != null) pld.MessageReference = new InternalDiscordMessageReference { MessageId = replyMessageId, FailIfNotExists = failOnInvalidReply }; if (replyMessageId != null) pld.Mentions = new DiscordMentions(Mentions.All, true, mentionReply); var route = $"{Endpoints.CHANNELS}/:channel_id{Endpoints.MESSAGES}"; - var bucket = this.Rest.GetBucket(RestRequestMethod.POST, route, new { channel_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.POST, route, new {channel_id = channelId }, 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 ret = this.PrepareMessage(JObject.Parse(res.Response)); return ret; } /// /// Creates the message async. /// - /// The channel_id. + /// The channel_id. /// The builder. /// A Task. - internal async Task CreateMessageAsync(ulong channel_id, DiscordMessageBuilder builder) + internal async Task CreateMessageAsync(ulong channelId, DiscordMessageBuilder builder) { builder.Validate(); if (builder.Embeds != null) foreach (var embed in builder.Embeds) if (embed?.Timestamp != null) embed.Timestamp = embed.Timestamp.Value.ToUniversalTime(); var pld = new RestChannelMessageCreatePayload { HasContent = builder.Content != null, Content = builder.Content, StickersIds = builder.Sticker is null ? Array.Empty() : new[] {builder.Sticker.Id}, - IsTTS = builder.IsTTS, + IsTts = builder.IsTts, HasEmbed = builder.Embeds != null, Embeds = builder.Embeds, Components = builder.Components }; if (builder.ReplyId != null) pld.MessageReference = new InternalDiscordMessageReference { MessageId = builder.ReplyId, FailIfNotExists = builder.FailOnInvalidReply }; pld.Mentions = new DiscordMentions(builder.Mentions ?? Mentions.All, builder.Mentions?.Any() ?? false, builder.MentionOnReply); if (builder.Files.Count == 0) { var route = $"{Endpoints.CHANNELS}/:channel_id{Endpoints.MESSAGES}"; - var bucket = this.Rest.GetBucket(RestRequestMethod.POST, route, new { channel_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.POST, route, new {channel_id = channelId }, 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 ret = this.PrepareMessage(JObject.Parse(res.Response)); return ret; } else { - ulong file_id = 0; + ulong fileId = 0; List attachments = new(builder.Files.Count); foreach (var file in builder.Files) { DiscordAttachment att = new() { - Id = file_id, + Id = fileId, Discord = this.Discord, Description = file.Description, FileName = file.FileName }; attachments.Add(att); - file_id++; + fileId++; } pld.Attachments = attachments; var values = new Dictionary { ["payload_json"] = DiscordJson.SerializeObject(pld) }; var route = $"{Endpoints.CHANNELS}/:channel_id{Endpoints.MESSAGES}"; - var bucket = this.Rest.GetBucket(RestRequestMethod.POST, route, new { channel_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.POST, route, new {channel_id = channelId }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); var res = await this.DoMultipartAsync(this.Discord, bucket, url, RestRequestMethod.POST, route, values: values, files: builder.Files).ConfigureAwait(false); var ret = this.PrepareMessage(JObject.Parse(res.Response)); - foreach (var file in builder._files.Where(x => x.ResetPositionTo.HasValue)) + foreach (var file in builder.FilesInternal.Where(x => x.ResetPositionTo.HasValue)) { file.Stream.Position = file.ResetPositionTo.Value; } return ret; } } /// /// Gets the guild channels async. /// - /// The guild_id. + /// The guild_id. /// A Task. - internal async Task> GetGuildChannelsAsync(ulong guild_id) + internal async Task> GetGuildChannelsAsync(ulong guildId) { var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.CHANNELS}"; - var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new { guild_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new {guild_id = guildId }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.GET, route).ConfigureAwait(false); - var channels_raw = JsonConvert.DeserializeObject>(res.Response).Select(xc => { xc.Discord = this.Discord; return xc; }); + var channelsRaw = JsonConvert.DeserializeObject>(res.Response).Select(xc => { xc.Discord = this.Discord; return xc; }); - foreach (var ret in channels_raw) - foreach (var xo in ret._permissionOverwrites) + foreach (var ret in channelsRaw) + foreach (var xo in ret.PermissionOverwritesInternal) { xo.Discord = this.Discord; - xo._channel_id = ret.Id; + xo.ChannelId = ret.Id; } - return new ReadOnlyCollection(new List(channels_raw)); + return new ReadOnlyCollection(new List(channelsRaw)); } /// /// Creates the stage instance async. /// - /// The channel_id. + /// The channel_id. /// The topic. - /// Whether everyone should be notified about the stage. - /// The privacy_level. + /// Whether everyone should be notified about the stage. + /// The privacy_level. /// The reason. - internal async Task CreateStageInstanceAsync(ulong channel_id, string topic, bool send_start_notification, StagePrivacyLevel privacy_level, string reason) + internal async Task CreateStageInstanceAsync(ulong channelId, string topic, bool sendStartNotification, StagePrivacyLevel privacyLevel, string reason) { var pld = new RestStageInstanceCreatePayload { - ChannelId = channel_id, + ChannelId = channelId, Topic = topic, - PrivacyLevel = privacy_level, - SendStartNotification = send_start_notification + PrivacyLevel = privacyLevel, + SendStartNotification = sendStartNotification }; var route = $"{Endpoints.STAGE_INSTANCES}"; var bucket = this.Rest.GetBucket(RestRequestMethod.POST, route, new { }, out var path); var headers = Utilities.GetBaseHeaders(); if (!string.IsNullOrWhiteSpace(reason)) headers.Add(REASON_HEADER_NAME, reason); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.POST, route, headers, DiscordJson.SerializeObject(pld)).ConfigureAwait(false); var stageInstance = JsonConvert.DeserializeObject(res.Response); return stageInstance; } /// /// Gets the stage instance async. /// - /// The channel_id. - internal async Task GetStageInstanceAsync(ulong channel_id) + /// The channel_id. + internal async Task GetStageInstanceAsync(ulong channelId) { var route = $"{Endpoints.STAGE_INSTANCES}/:channel_id"; - var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new { channel_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new {channel_id = channelId }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.GET, route).ConfigureAwait(false); var stageInstance = JsonConvert.DeserializeObject(res.Response); return stageInstance; } /// /// Modifies the stage instance async. /// - /// The channel_id. + /// The channel_id. /// The topic. - /// The privacy_level. + /// The privacy_level. /// The reason. - internal Task ModifyStageInstanceAsync(ulong channel_id, Optional topic, Optional privacy_level, string reason) + internal Task ModifyStageInstanceAsync(ulong channelId, Optional topic, Optional privacyLevel, string reason) { var pld = new RestStageInstanceModifyPayload { Topic = topic, - PrivacyLevel = privacy_level + PrivacyLevel = privacyLevel }; var route = $"{Endpoints.STAGE_INSTANCES}/:channel_id"; - var bucket = this.Rest.GetBucket(RestRequestMethod.PATCH, route, new { channel_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.PATCH, route, new {channel_id = channelId }, out var path); var headers = Utilities.GetBaseHeaders(); if (!string.IsNullOrWhiteSpace(reason)) headers.Add(REASON_HEADER_NAME, reason); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); return this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.PATCH, route, headers, DiscordJson.SerializeObject(pld)); } /// /// Deletes the stage instance async. /// - /// The channel_id. + /// The channel_id. /// The reason. - internal Task DeleteStageInstanceAsync(ulong channel_id, string reason) + internal Task DeleteStageInstanceAsync(ulong channelId, string reason) { var route = $"{Endpoints.STAGE_INSTANCES}/:channel_id"; - var bucket = this.Rest.GetBucket(RestRequestMethod.DELETE, route, new { channel_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.DELETE, route, new {channel_id = channelId }, out var path); var headers = Utilities.GetBaseHeaders(); if (!string.IsNullOrWhiteSpace(reason)) headers.Add(REASON_HEADER_NAME, reason); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); return this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.DELETE, route, headers); } /// /// Gets the channel messages async. /// - /// The channel id. + /// The channel id. /// The limit. /// The before. /// The after. /// The around. /// A Task. - internal async Task> GetChannelMessagesAsync(ulong channel_id, int limit, ulong? before, ulong? after, ulong? around) + internal async Task> GetChannelMessagesAsync(ulong channelId, int limit, ulong? before, ulong? after, ulong? around) { var urlparams = new Dictionary(); if (around != null) urlparams["around"] = around?.ToString(CultureInfo.InvariantCulture); if (before != null) urlparams["before"] = before?.ToString(CultureInfo.InvariantCulture); if (after != null) urlparams["after"] = after?.ToString(CultureInfo.InvariantCulture); if (limit > 0) urlparams["limit"] = limit.ToString(CultureInfo.InvariantCulture); var route = $"{Endpoints.CHANNELS}/:channel_id{Endpoints.MESSAGES}"; - var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new { channel_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new {channel_id = channelId }, out var path); var url = Utilities.GetApiUriFor(path, urlparams.Any() ? BuildQueryString(urlparams) : "", this.Discord.Configuration); var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.GET, route).ConfigureAwait(false); - var msgs_raw = JArray.Parse(res.Response); + var msgsRaw = JArray.Parse(res.Response); var msgs = new List(); - foreach (var xj in msgs_raw) + foreach (var xj in msgsRaw) msgs.Add(this.PrepareMessage(xj)); return new ReadOnlyCollection(new List(msgs)); } /// /// Gets the channel message async. /// - /// The channel_id. - /// The message_id. + /// The channel_id. + /// The message_id. /// A Task. - internal async Task GetChannelMessageAsync(ulong channel_id, ulong message_id) + internal async Task GetChannelMessageAsync(ulong channelId, ulong messageId) { var route = $"{Endpoints.CHANNELS}/:channel_id{Endpoints.MESSAGES}/:message_id"; - var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new { channel_id, message_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new {channel_id = channelId, message_id = messageId }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.GET, route).ConfigureAwait(false); var ret = this.PrepareMessage(JObject.Parse(res.Response)); return ret; } /// /// Edits the message async. /// - /// The channel_id. - /// The message_id. + /// The channel_id. + /// The message_id. /// The content. /// The embeds. /// The mentions. /// The components. - /// The suppress_embed. + /// The suppress_embed. /// The files. /// The attachments to keep. /// A Task. - internal async Task EditMessageAsync(ulong channel_id, ulong message_id, Optional content, Optional> embeds, IEnumerable mentions, IReadOnlyList components, Optional suppress_embed, IReadOnlyCollection files, Optional> attachments) + internal async Task EditMessageAsync(ulong channelId, ulong messageId, Optional content, Optional> embeds, IEnumerable mentions, IReadOnlyList components, Optional suppressEmbed, IReadOnlyCollection files, Optional> attachments) { if (embeds.HasValue && embeds.Value != null) foreach (var embed in embeds.Value) if (embed.Timestamp != null) embed.Timestamp = embed.Timestamp.Value.ToUniversalTime(); var pld = new RestChannelMessageEditPayload { HasContent = content.HasValue, Content = content.HasValue ? (string)content : null, HasEmbed = embeds.HasValue && (embeds.Value?.Any() ?? false), Embeds = embeds.HasValue && (embeds.Value?.Any() ?? false) ? embeds.Value : null, Components = components ?? null, - Flags = suppress_embed.HasValue ? (bool)suppress_embed ? MessageFlags.SuppressedEmbeds : null : null + Flags = suppressEmbed.HasValue ? (bool)suppressEmbed ? MessageFlags.SuppressedEmbeds : null : null }; pld.Mentions = new DiscordMentions(mentions ?? Mentions.None, false, mentions?.OfType().Any() ?? false); if (files?.Count > 0) { - ulong file_id = 0; - List attachments_new = new(); + ulong fileId = 0; + List attachmentsNew = new(); foreach (var file in files) { DiscordAttachment att = new() { - Id = file_id, + Id = fileId, Discord = this.Discord, Description = file.Description, FileName = file.FileName }; - attachments_new.Add(att); - file_id++; + attachmentsNew.Add(att); + fileId++; } if (attachments.HasValue && attachments.Value.Any()) - attachments_new.AddRange(attachments.Value); + attachmentsNew.AddRange(attachments.Value); - pld.Attachments = attachments_new; + pld.Attachments = attachmentsNew; var values = new Dictionary { ["payload_json"] = DiscordJson.SerializeObject(pld) }; var route = $"{Endpoints.CHANNELS}/:channel_id{Endpoints.MESSAGES}/:message_id"; - var bucket = this.Rest.GetBucket(RestRequestMethod.PATCH, route, new { channel_id, message_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.PATCH, route, new {channel_id = channelId, message_id = messageId }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); var res = await this.DoMultipartAsync(this.Discord, bucket, url, RestRequestMethod.PATCH, route, values: values, files: files).ConfigureAwait(false); var ret = this.PrepareMessage(JObject.Parse(res.Response)); foreach (var file in files.Where(x => x.ResetPositionTo.HasValue)) { file.Stream.Position = file.ResetPositionTo.Value; } return ret; } else { pld.Attachments = attachments.HasValue ? attachments.Value : null; var route = $"{Endpoints.CHANNELS}/:channel_id{Endpoints.MESSAGES}/:message_id"; - var bucket = this.Rest.GetBucket(RestRequestMethod.PATCH, route, new { channel_id, message_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.PATCH, route, new {channel_id = channelId, message_id = messageId }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.PATCH, route, payload: DiscordJson.SerializeObject(pld)).ConfigureAwait(false); var ret = this.PrepareMessage(JObject.Parse(res.Response)); return ret; } } /// /// Deletes the message async. /// - /// The channel_id. - /// The message_id. + /// The channel_id. + /// The message_id. /// The reason. /// A Task. - internal Task DeleteMessageAsync(ulong channel_id, ulong message_id, string reason) + internal Task DeleteMessageAsync(ulong channelId, ulong messageId, string reason) { var headers = Utilities.GetBaseHeaders(); if (!string.IsNullOrWhiteSpace(reason)) headers.Add(REASON_HEADER_NAME, reason); var route = $"{Endpoints.CHANNELS}/:channel_id{Endpoints.MESSAGES}/:message_id"; - var bucket = this.Rest.GetBucket(RestRequestMethod.DELETE, route, new { channel_id, message_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.DELETE, route, new {channel_id = channelId, message_id = messageId }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); return this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.DELETE, route, headers); } /// /// Deletes the messages async. /// - /// The channel_id. - /// The message_ids. + /// The channel_id. + /// The message_ids. /// The reason. /// A Task. - internal Task DeleteMessagesAsync(ulong channel_id, IEnumerable message_ids, string reason) + internal Task DeleteMessagesAsync(ulong channelId, IEnumerable messageIds, string reason) { var pld = new RestChannelMessageBulkDeletePayload { - Messages = message_ids + Messages = messageIds }; var headers = Utilities.GetBaseHeaders(); if (!string.IsNullOrWhiteSpace(reason)) headers.Add(REASON_HEADER_NAME, reason); var route = $"{Endpoints.CHANNELS}/:channel_id{Endpoints.MESSAGES}{Endpoints.BULK_DELETE}"; - var bucket = this.Rest.GetBucket(RestRequestMethod.POST, route, new { channel_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.POST, route, new {channel_id = channelId }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); return this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.POST, route, headers, DiscordJson.SerializeObject(pld)); } /// /// Gets the channel invites async. /// - /// The channel_id. + /// The channel_id. /// A Task. - internal async Task> GetChannelInvitesAsync(ulong channel_id) + internal async Task> GetChannelInvitesAsync(ulong channelId) { var route = $"{Endpoints.CHANNELS}/:channel_id{Endpoints.INVITES}"; - var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new { channel_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new {channel_id = channelId }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.GET, route).ConfigureAwait(false); - var invites_raw = JsonConvert.DeserializeObject>(res.Response).Select(xi => { xi.Discord = this.Discord; return xi; }); + var invitesRaw = JsonConvert.DeserializeObject>(res.Response).Select(xi => { xi.Discord = this.Discord; return xi; }); - return new ReadOnlyCollection(new List(invites_raw)); + return new ReadOnlyCollection(new List(invitesRaw)); } /// /// Creates the channel invite async. /// - /// The channel_id. - /// The max_age. - /// The max_uses. - /// The target_type. - /// The target_application. - /// The target_user. + /// The channel_id. + /// The max_age. + /// The max_uses. + /// The target_type. + /// The target_application. + /// The target_user. /// If true, temporary. /// If true, unique. /// The reason. /// A Task. - internal async Task CreateChannelInviteAsync(ulong channel_id, int max_age, int max_uses, TargetType? target_type, TargetActivity? target_application, ulong? target_user, bool temporary, bool unique, string reason) + internal async Task CreateChannelInviteAsync(ulong channelId, int maxAge, int maxUses, TargetType? targetType, TargetActivity? targetApplication, ulong? targetUser, bool temporary, bool unique, string reason) { var pld = new RestChannelInviteCreatePayload { - MaxAge = max_age, - MaxUses = max_uses, - TargetType = target_type, - TargetApplication = target_application, - TargetUserId = target_user, + MaxAge = maxAge, + MaxUses = maxUses, + TargetType = targetType, + TargetApplication = targetApplication, + TargetUserId = targetUser, Temporary = temporary, Unique = unique }; var headers = Utilities.GetBaseHeaders(); if (!string.IsNullOrWhiteSpace(reason)) headers.Add(REASON_HEADER_NAME, reason); var route = $"{Endpoints.CHANNELS}/:channel_id{Endpoints.INVITES}"; - var bucket = this.Rest.GetBucket(RestRequestMethod.POST, route, new { channel_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.POST, route, new {channel_id = channelId }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.POST, route, headers, DiscordJson.SerializeObject(pld)).ConfigureAwait(false); var ret = JsonConvert.DeserializeObject(res.Response); ret.Discord = this.Discord; return ret; } /// /// Deletes the channel permission async. /// - /// The channel_id. - /// The overwrite_id. + /// The channel_id. + /// The overwrite_id. /// The reason. /// A Task. - internal Task DeleteChannelPermissionAsync(ulong channel_id, ulong overwrite_id, string reason) + internal Task DeleteChannelPermissionAsync(ulong channelId, ulong overwriteId, string reason) { var headers = Utilities.GetBaseHeaders(); if (!string.IsNullOrWhiteSpace(reason)) headers.Add(REASON_HEADER_NAME, reason); var route = $"{Endpoints.CHANNELS}/:channel_id{Endpoints.PERMISSIONS}/:overwrite_id"; - var bucket = this.Rest.GetBucket(RestRequestMethod.DELETE, route, new { channel_id, overwrite_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.DELETE, route, new {channel_id = channelId, overwrite_id = overwriteId }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); return this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.DELETE, route, headers); } /// /// Edits the channel permissions async. /// - /// The channel_id. - /// The overwrite_id. + /// The channel_id. + /// The overwrite_id. /// The allow. /// The deny. /// The type. /// The reason. /// A Task. - internal Task EditChannelPermissionsAsync(ulong channel_id, ulong overwrite_id, Permissions allow, Permissions deny, string type, string reason) + internal Task EditChannelPermissionsAsync(ulong channelId, ulong overwriteId, Permissions allow, Permissions deny, string type, string reason) { var pld = new RestChannelPermissionEditPayload { Type = type, - Allow = allow & PermissionMethods.FULL_PERMS, - Deny = deny & PermissionMethods.FULL_PERMS + Allow = allow & PermissionMethods.FullPerms, + Deny = deny & PermissionMethods.FullPerms }; var headers = Utilities.GetBaseHeaders(); if (!string.IsNullOrWhiteSpace(reason)) headers.Add(REASON_HEADER_NAME, reason); var route = $"{Endpoints.CHANNELS}/:channel_id{Endpoints.PERMISSIONS}/:overwrite_id"; - var bucket = this.Rest.GetBucket(RestRequestMethod.PUT, route, new { channel_id, overwrite_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.PUT, route, new {channel_id = channelId, overwrite_id = overwriteId }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); return this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.PUT, route, headers, DiscordJson.SerializeObject(pld)); } /// /// Triggers the typing async. /// - /// The channel_id. + /// The channel_id. /// A Task. - internal Task TriggerTypingAsync(ulong channel_id) + internal Task TriggerTypingAsync(ulong channelId) { var route = $"{Endpoints.CHANNELS}/:channel_id{Endpoints.TYPING}"; - var bucket = this.Rest.GetBucket(RestRequestMethod.POST, route, new { channel_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.POST, route, new {channel_id = channelId }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); return this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.POST, route); } /// /// Gets the pinned messages async. /// - /// The channel_id. + /// The channel_id. /// A Task. - internal async Task> GetPinnedMessagesAsync(ulong channel_id) + internal async Task> GetPinnedMessagesAsync(ulong channelId) { var route = $"{Endpoints.CHANNELS}/:channel_id{Endpoints.PINS}"; - var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new { channel_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new {channel_id = channelId }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.GET, route).ConfigureAwait(false); - var msgs_raw = JArray.Parse(res.Response); + var msgsRaw = JArray.Parse(res.Response); var msgs = new List(); - foreach (var xj in msgs_raw) + foreach (var xj in msgsRaw) msgs.Add(this.PrepareMessage(xj)); return new ReadOnlyCollection(new List(msgs)); } /// /// Pins the message async. /// - /// The channel_id. - /// The message_id. + /// The channel_id. + /// The message_id. /// A Task. - internal Task PinMessageAsync(ulong channel_id, ulong message_id) + internal Task PinMessageAsync(ulong channelId, ulong messageId) { var route = $"{Endpoints.CHANNELS}/:channel_id{Endpoints.PINS}/:message_id"; - var bucket = this.Rest.GetBucket(RestRequestMethod.PUT, route, new { channel_id, message_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.PUT, route, new {channel_id = channelId, message_id = messageId }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); return this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.PUT, route); } /// /// Unpins the message async. /// - /// The channel_id. - /// The message_id. + /// The channel_id. + /// The message_id. /// A Task. - internal Task UnpinMessageAsync(ulong channel_id, ulong message_id) + internal Task UnpinMessageAsync(ulong channelId, ulong messageId) { var route = $"{Endpoints.CHANNELS}/:channel_id{Endpoints.PINS}/:message_id"; - var bucket = this.Rest.GetBucket(RestRequestMethod.DELETE, route, new { channel_id, message_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.DELETE, route, new {channel_id = channelId, message_id = messageId }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); return this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.DELETE, route); } /// /// Adds the group dm recipient async. /// - /// The channel_id. - /// The user_id. - /// The access_token. + /// The channel_id. + /// The user_id. + /// The access_token. /// The nickname. /// A Task. - internal Task AddGroupDmRecipientAsync(ulong channel_id, ulong user_id, string access_token, string nickname) + internal Task AddGroupDmRecipientAsync(ulong channelId, ulong userId, string accessToken, string nickname) { var pld = new RestChannelGroupDmRecipientAddPayload { - AccessToken = access_token, + AccessToken = accessToken, Nickname = nickname }; var route = $"{Endpoints.USERS}{Endpoints.ME}{Endpoints.CHANNELS}/:channel_id{Endpoints.RECIPIENTS}/:user_id"; - var bucket = this.Rest.GetBucket(RestRequestMethod.PUT, route, new { channel_id, user_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.PUT, route, new {channel_id = channelId, user_id = userId }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); return this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.PUT, route, payload: DiscordJson.SerializeObject(pld)); } /// /// Removes the group dm recipient async. /// - /// The channel_id. - /// The user_id. + /// The channel_id. + /// The user_id. /// A Task. - internal Task RemoveGroupDmRecipientAsync(ulong channel_id, ulong user_id) + internal Task RemoveGroupDmRecipientAsync(ulong channelId, ulong userId) { var route = $"{Endpoints.USERS}{Endpoints.ME}{Endpoints.CHANNELS}/:channel_id{Endpoints.RECIPIENTS}/:user_id"; - var bucket = this.Rest.GetBucket(RestRequestMethod.DELETE, route, new { channel_id, user_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.DELETE, route, new {channel_id = channelId, user_id = userId }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); return this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.DELETE, route); } /// /// Creates the group dm async. /// - /// The access_tokens. + /// The access_tokens. /// The nicks. /// A Task. - internal async Task CreateGroupDmAsync(IEnumerable access_tokens, IDictionary nicks) + internal async Task CreateGroupDmAsync(IEnumerable accessTokens, IDictionary nicks) { var pld = new RestUserGroupDmCreatePayload { - AccessTokens = access_tokens, + AccessTokens = accessTokens, Nicknames = nicks }; var route = $"{Endpoints.USERS}{Endpoints.ME}{Endpoints.CHANNELS}"; 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 ret = JsonConvert.DeserializeObject(res.Response); ret.Discord = this.Discord; return ret; } /// /// Creates the dm async. /// - /// The recipient_id. + /// The recipient_id. /// A Task. - internal async Task CreateDmAsync(ulong recipient_id) + internal async Task CreateDmAsync(ulong recipientId) { var pld = new RestUserDmCreatePayload { - Recipient = recipient_id + Recipient = recipientId }; var route = $"{Endpoints.USERS}{Endpoints.ME}{Endpoints.CHANNELS}"; 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 ret = JsonConvert.DeserializeObject(res.Response); ret.Discord = this.Discord; return ret; } /// /// Follows the channel async. /// - /// The channel_id. - /// The webhook_channel_id. + /// The channel_id. + /// The webhook_channel_id. /// A Task. - internal async Task FollowChannelAsync(ulong channel_id, ulong webhook_channel_id) + internal async Task FollowChannelAsync(ulong channelId, ulong webhookChannelId) { var pld = new FollowedChannelAddPayload { - WebhookChannelId = webhook_channel_id + WebhookChannelId = webhookChannelId }; var route = $"{Endpoints.CHANNELS}/:channel_id{Endpoints.FOLLOWERS}"; - var bucket = this.Rest.GetBucket(RestRequestMethod.POST, route, new { channel_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.POST, route, new {channel_id = channelId }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); var response = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.POST, route, payload: DiscordJson.SerializeObject(pld)).ConfigureAwait(false); return JsonConvert.DeserializeObject(response.Response); } /// /// Crossposts the message async. /// - /// The channel_id. - /// The message_id. + /// The channel_id. + /// The message_id. /// A Task. - internal async Task CrosspostMessageAsync(ulong channel_id, ulong message_id) + internal async Task CrosspostMessageAsync(ulong channelId, ulong messageId) { var route = $"{Endpoints.CHANNELS}/:channel_id{Endpoints.MESSAGES}/:message_id{Endpoints.CROSSPOST}"; - var bucket = this.Rest.GetBucket(RestRequestMethod.POST, route, new { channel_id, message_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.POST, route, new {channel_id = channelId, message_id = messageId }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); var response = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.POST, route).ConfigureAwait(false); return JsonConvert.DeserializeObject(response.Response); } #endregion #region Member /// /// Gets the current user async. /// /// A Task. internal Task GetCurrentUserAsync() => this.GetUserAsync("@me"); /// /// Gets the user async. /// - /// The user_id. + /// The user_id. /// A Task. - internal Task GetUserAsync(ulong user_id) - => this.GetUserAsync(user_id.ToString(CultureInfo.InvariantCulture)); + internal Task GetUserAsync(ulong userId) + => this.GetUserAsync(userId.ToString(CultureInfo.InvariantCulture)); /// /// Gets the user async. /// - /// The user_id. + /// The user_id. /// A Task. - internal async Task GetUserAsync(string user_id) + internal async Task GetUserAsync(string userId) { var route = $"{Endpoints.USERS}/:user_id"; - var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new { user_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new {user_id = userId }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.GET, route).ConfigureAwait(false); - var user_raw = JsonConvert.DeserializeObject(res.Response); - var duser = new DiscordUser(user_raw) { Discord = this.Discord }; + var userRaw = JsonConvert.DeserializeObject(res.Response); + var duser = new DiscordUser(userRaw) { Discord = this.Discord }; return duser; } /// /// Gets the guild member async. /// - /// The guild_id. - /// The user_id. + /// The guild_id. + /// The user_id. /// A Task. - internal async Task GetGuildMemberAsync(ulong guild_id, ulong user_id) + internal async Task GetGuildMemberAsync(ulong guildId, ulong userId) { var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.MEMBERS}/:user_id"; - var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new { guild_id, user_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new {guild_id = guildId, user_id = userId }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.GET, route).ConfigureAwait(false); var tm = JsonConvert.DeserializeObject(res.Response); var usr = new DiscordUser(tm.User) { Discord = this.Discord }; usr = this.Discord.UserCache.AddOrUpdate(tm.User.Id, usr, (id, old) => { old.Username = usr.Username; old.Discriminator = usr.Discriminator; old.AvatarHash = usr.AvatarHash; return old; }); return new DiscordMember(tm) { Discord = this.Discord, - _guild_id = guild_id + GuildId = guildId }; } /// /// Removes the guild member async. /// - /// The guild_id. - /// The user_id. + /// The guild_id. + /// The user_id. /// The reason. /// A Task. - internal Task RemoveGuildMemberAsync(ulong guild_id, ulong user_id, string reason) + internal Task RemoveGuildMemberAsync(ulong guildId, ulong userId, string reason) { var urlparams = new Dictionary(); if (reason != null) urlparams["reason"] = reason; var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.MEMBERS}/:user_id"; - var bucket = this.Rest.GetBucket(RestRequestMethod.DELETE, route, new { guild_id, user_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.DELETE, route, new {guild_id = guildId, user_id = userId }, out var path); var url = Utilities.GetApiUriFor(path, BuildQueryString(urlparams), this.Discord.Configuration); return this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.DELETE, route); } /// /// Modifies the current user async. /// /// The username. - /// The base64_avatar. + /// The base64_avatar. /// A Task. - internal async Task ModifyCurrentUserAsync(string username, Optional base64_avatar) + internal async Task ModifyCurrentUserAsync(string username, Optional base64Avatar) { var pld = new RestUserUpdateCurrentPayload { Username = username, - AvatarBase64 = base64_avatar.HasValue ? base64_avatar.Value : null, - AvatarSet = base64_avatar.HasValue + AvatarBase64 = base64Avatar.HasValue ? base64Avatar.Value : null, + AvatarSet = base64Avatar.HasValue }; var route = $"{Endpoints.USERS}{Endpoints.ME}"; var bucket = this.Rest.GetBucket(RestRequestMethod.PATCH, route, new { }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.PATCH, route, payload: DiscordJson.SerializeObject(pld)).ConfigureAwait(false); - var user_raw = JsonConvert.DeserializeObject(res.Response); + var userRaw = JsonConvert.DeserializeObject(res.Response); - return user_raw; + return userRaw; } /// /// Gets the current user guilds async. /// /// The limit. /// The before. /// The after. /// A Task. internal async Task> GetCurrentUserGuildsAsync(int limit = 100, ulong? before = null, ulong? after = null) { var route = $"{Endpoints.USERS}{Endpoints.ME}{Endpoints.GUILDS}"; var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new { }, out var path); var url = Utilities.GetApiUriBuilderFor(path, this.Discord.Configuration) .AddParameter($"limit", limit.ToString(CultureInfo.InvariantCulture)); if (before != null) url.AddParameter("before", before.Value.ToString(CultureInfo.InvariantCulture)); if (after != null) url.AddParameter("after", after.Value.ToString(CultureInfo.InvariantCulture)); var res = await this.DoRequestAsync(this.Discord, bucket, url.Build(), RestRequestMethod.GET, route).ConfigureAwait(false); if (this.Discord is DiscordClient) { - var guilds_raw = JsonConvert.DeserializeObject>(res.Response); - var glds = guilds_raw.Select(xug => (this.Discord as DiscordClient)?._guilds[xug.Id]); + var guildsRaw = JsonConvert.DeserializeObject>(res.Response); + var glds = guildsRaw.Select(xug => (this.Discord as DiscordClient)?.GuildsInternal[xug.Id]); return new ReadOnlyCollection(new List(glds)); } else { return new ReadOnlyCollection(JsonConvert.DeserializeObject>(res.Response)); } } /// /// Modifies the guild member async. /// - /// The guild_id. - /// The user_id. + /// The guild_id. + /// The user_id. /// The nick. - /// The role_ids. + /// The role_ids. /// The mute. /// The deaf. - /// The voice_channel_id. + /// The voice_channel_id. /// The reason. /// A Task. - internal Task ModifyGuildMemberAsync(ulong guild_id, ulong user_id, Optional nick, - Optional> role_ids, Optional mute, Optional deaf, - Optional voice_channel_id, string reason) + internal Task ModifyGuildMemberAsync(ulong guildId, ulong userId, Optional nick, + Optional> roleIds, Optional mute, Optional deaf, + Optional voiceChannelId, string reason) { var headers = Utilities.GetBaseHeaders(); if (!string.IsNullOrWhiteSpace(reason)) headers[REASON_HEADER_NAME] = reason; var pld = new RestGuildMemberModifyPayload { Nickname = nick, - RoleIds = role_ids, + RoleIds = roleIds, Deafen = deaf, Mute = mute, - VoiceChannelId = voice_channel_id + VoiceChannelId = voiceChannelId }; var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.MEMBERS}/:user_id"; - var bucket = this.Rest.GetBucket(RestRequestMethod.PATCH, route, new { guild_id, user_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.PATCH, route, new {guild_id = guildId, user_id = userId }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); return this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.PATCH, route, headers, payload: DiscordJson.SerializeObject(pld)); } /// /// Modifies the time out of a guild member. /// - /// The guild_id. - /// The user_id. + /// The guild_id. + /// The user_id. /// Datetime offset. /// The reason. /// A Task. - internal Task ModifyTimeoutAsync(ulong guild_id, ulong user_id, DateTimeOffset? until, string reason) + internal Task ModifyTimeoutAsync(ulong guildId, ulong userId, DateTimeOffset? until, string reason) { var headers = Utilities.GetBaseHeaders(); if (!string.IsNullOrWhiteSpace(reason)) headers[REASON_HEADER_NAME] = reason; var pld = new RestGuildMemberTimeoutModifyPayload { CommunicationDisabledUntil = until }; var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.MEMBERS}/:user_id"; - var bucket = this.Rest.GetBucket(RestRequestMethod.PATCH, route, new { guild_id, user_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.PATCH, route, new {guild_id = guildId, user_id = userId }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); return this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.PATCH, route, headers, payload: DiscordJson.SerializeObject(pld)); } /// /// Modifies the current member nickname async. /// - /// The guild_id. + /// The guild_id. /// The nick. /// The reason. /// A Task. - internal Task ModifyCurrentMemberNicknameAsync(ulong guild_id, string nick, string reason) + internal Task ModifyCurrentMemberNicknameAsync(ulong guildId, string nick, string reason) { var headers = Utilities.GetBaseHeaders(); if (!string.IsNullOrWhiteSpace(reason)) headers[REASON_HEADER_NAME] = reason; var pld = new RestGuildMemberModifyPayload { Nickname = nick }; var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.MEMBERS}{Endpoints.ME}{Endpoints.NICK}"; - var bucket = this.Rest.GetBucket(RestRequestMethod.PATCH, route, new { guild_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.PATCH, route, new {guild_id = guildId }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); return this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.PATCH, route, headers, payload: DiscordJson.SerializeObject(pld)); } #endregion #region Roles /// /// Gets the guild roles async. /// - /// The guild_id. + /// The guild_id. /// A Task. - internal async Task> GetGuildRolesAsync(ulong guild_id) + internal async Task> GetGuildRolesAsync(ulong guildId) { var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.ROLES}"; - var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new { guild_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new {guild_id = guildId }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.GET, route).ConfigureAwait(false); - var roles_raw = JsonConvert.DeserializeObject>(res.Response).Select(xr => { xr.Discord = this.Discord; xr._guild_id = guild_id; return xr; }); + var rolesRaw = JsonConvert.DeserializeObject>(res.Response).Select(xr => { xr.Discord = this.Discord; xr.GuildId = guildId; return xr; }); - return new ReadOnlyCollection(new List(roles_raw)); + return new ReadOnlyCollection(new List(rolesRaw)); } /// /// Gets the guild async. /// /// The guild id. - /// If true, with_counts. + /// If true, with_counts. /// A Task. - internal async Task GetGuildAsync(ulong guildId, bool? with_counts) + internal async Task GetGuildAsync(ulong guildId, bool? withCounts) { var urlparams = new Dictionary(); - if (with_counts.HasValue) - urlparams["with_counts"] = with_counts?.ToString(); + if (withCounts.HasValue) + urlparams["with_counts"] = withCounts?.ToString(); var route = $"{Endpoints.GUILDS}/:guild_id"; var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new { guild_id = guildId }, out var path); var url = Utilities.GetApiUriFor(path, urlparams.Any() ? BuildQueryString(urlparams) : "", this.Discord.Configuration); var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.GET, route, urlparams).ConfigureAwait(false); var json = JObject.Parse(res.Response); var rawMembers = (JArray)json["members"]; var guildRest = json.ToDiscordObject(); - foreach (var r in guildRest._roles.Values) - r._guild_id = guildRest.Id; + foreach (var r in guildRest.RolesInternal.Values) + r.GuildId = guildRest.Id; if (this.Discord is DiscordClient dc) { await dc.OnGuildUpdateEventAsync(guildRest, rawMembers).ConfigureAwait(false); - return dc._guilds[guildRest.Id]; + return dc.GuildsInternal[guildRest.Id]; } else { guildRest.Discord = this.Discord; return guildRest; } } /// /// Modifies the guild role async. /// - /// The guild_id. - /// The role_id. + /// The guild_id. + /// The role_id. /// The name. /// The permissions. /// The color. /// If true, hoist. /// If true, mentionable. /// The icon. /// The unicode emoji icon. /// The reason. - internal async Task ModifyGuildRoleAsync(ulong guild_id, ulong role_id, string name, Permissions? permissions, int? color, bool? hoist, bool? mentionable, Optional iconb64, Optional emoji, string reason) + internal async Task ModifyGuildRoleAsync(ulong guildId, ulong roleId, string name, Permissions? permissions, int? color, bool? hoist, bool? mentionable, Optional iconb64, Optional emoji, string reason) { var pld = new RestGuildRolePayload { Name = name, - Permissions = permissions & PermissionMethods.FULL_PERMS, + Permissions = permissions & PermissionMethods.FullPerms, Color = color, Hoist = hoist, Mentionable = mentionable, }; if (emoji.HasValue && !iconb64.HasValue) pld.UnicodeEmoji = emoji; if (emoji.HasValue && iconb64.HasValue) { pld.IconBase64 = null; pld.UnicodeEmoji = emoji; } if (iconb64.HasValue) pld.IconBase64 = iconb64; var headers = Utilities.GetBaseHeaders(); if (!string.IsNullOrWhiteSpace(reason)) headers[REASON_HEADER_NAME] = reason; var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.ROLES}/:role_id"; - var bucket = this.Rest.GetBucket(RestRequestMethod.PATCH, route, new { guild_id, role_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.PATCH, route, new {guild_id = guildId, role_id = roleId }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.PATCH, route, headers, DiscordJson.SerializeObject(pld)).ConfigureAwait(false); var ret = JsonConvert.DeserializeObject(res.Response); ret.Discord = this.Discord; - ret._guild_id = guild_id; + ret.GuildId = guildId; return ret; } /// /// Deletes the role async. /// - /// The guild_id. - /// The role_id. + /// The guild_id. + /// The role_id. /// The reason. /// A Task. - internal Task DeleteRoleAsync(ulong guild_id, ulong role_id, string reason) + internal Task DeleteRoleAsync(ulong guildId, ulong roleId, string reason) { var headers = Utilities.GetBaseHeaders(); if (!string.IsNullOrWhiteSpace(reason)) headers[REASON_HEADER_NAME] = reason; var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.ROLES}/:role_id"; - var bucket = this.Rest.GetBucket(RestRequestMethod.DELETE, route, new { guild_id, role_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.DELETE, route, new {guild_id = guildId, role_id = roleId }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); return this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.DELETE, route, headers); } /// /// Creates the guild role async. /// - /// The guild_id. + /// The guild_id. /// The name. /// The permissions. /// The color. /// If true, hoist. /// If true, mentionable. /// The reason. /// A Task. - internal async Task CreateGuildRoleAsync(ulong guild_id, string name, Permissions? permissions, int? color, bool? hoist, bool? mentionable, string reason) + internal async Task CreateGuildRoleAsync(ulong guildId, string name, Permissions? permissions, int? color, bool? hoist, bool? mentionable, string reason) { var pld = new RestGuildRolePayload { Name = name, - Permissions = permissions & PermissionMethods.FULL_PERMS, + Permissions = permissions & PermissionMethods.FullPerms, Color = color, Hoist = hoist, Mentionable = mentionable }; var headers = Utilities.GetBaseHeaders(); if (!string.IsNullOrWhiteSpace(reason)) headers[REASON_HEADER_NAME] = reason; var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.ROLES}"; - var bucket = this.Rest.GetBucket(RestRequestMethod.POST, route, new { guild_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.POST, route, new {guild_id = guildId }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.POST, route, headers, DiscordJson.SerializeObject(pld)).ConfigureAwait(false); var ret = JsonConvert.DeserializeObject(res.Response); ret.Discord = this.Discord; - ret._guild_id = guild_id; + ret.GuildId = guildId; return ret; } #endregion #region Prune /// /// Gets the guild prune count async. /// - /// The guild_id. + /// The guild_id. /// The days. - /// The include_roles. + /// The include_roles. /// A Task. - internal async Task GetGuildPruneCountAsync(ulong guild_id, int days, IEnumerable include_roles) + internal async Task GetGuildPruneCountAsync(ulong guildId, int days, IEnumerable includeRoles) { if (days < 0 || days > 30) throw new ArgumentException("Prune inactivity days must be a number between 0 and 30.", nameof(days)); var urlparams = new Dictionary { ["days"] = days.ToString(CultureInfo.InvariantCulture) }; var sb = new StringBuilder(); - if (include_roles != null) + if (includeRoles != null) { - var roleArray = include_roles.ToArray(); + var roleArray = includeRoles.ToArray(); var roleArrayCount = roleArray.Count(); for (var i = 0; i < roleArrayCount; i++) sb.Append($"&include_roles={roleArray[i]}"); } var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.PRUNE}"; - var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new { guild_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new {guild_id = guildId }, out var path); var url = Utilities.GetApiUriFor(path, $"{BuildQueryString(urlparams)}{sb}", this.Discord.Configuration); var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.GET, route).ConfigureAwait(false); var pruned = JsonConvert.DeserializeObject(res.Response); return pruned.Pruned.Value; } /// /// Begins the guild prune async. /// - /// The guild_id. + /// The guild_id. /// The days. - /// If true, compute_prune_count. - /// The include_roles. + /// If true, compute_prune_count. + /// The include_roles. /// The reason. /// A Task. - internal async Task BeginGuildPruneAsync(ulong guild_id, int days, bool compute_prune_count, IEnumerable include_roles, string reason) + internal async Task BeginGuildPruneAsync(ulong guildId, int days, bool computePruneCount, IEnumerable includeRoles, string reason) { if (days < 0 || days > 30) throw new ArgumentException("Prune inactivity days must be a number between 0 and 30.", nameof(days)); var urlparams = new Dictionary { ["days"] = days.ToString(CultureInfo.InvariantCulture), - ["compute_prune_count"] = compute_prune_count.ToString() + ["compute_prune_count"] = computePruneCount.ToString() }; var sb = new StringBuilder(); - if (include_roles != null) + if (includeRoles != null) { - var roleArray = include_roles.ToArray(); + var roleArray = includeRoles.ToArray(); var roleArrayCount = roleArray.Count(); for (var i = 0; i < roleArrayCount; i++) sb.Append($"&include_roles={roleArray[i]}"); } var headers = Utilities.GetBaseHeaders(); if (!string.IsNullOrWhiteSpace(reason)) headers.Add(REASON_HEADER_NAME, reason); var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.PRUNE}"; - var bucket = this.Rest.GetBucket(RestRequestMethod.POST, route, new { guild_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.POST, route, new {guild_id = guildId }, out var path); var url = Utilities.GetApiUriFor(path, $"{BuildQueryString(urlparams)}{sb}", this.Discord.Configuration); var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.POST, route, headers).ConfigureAwait(false); var pruned = JsonConvert.DeserializeObject(res.Response); return pruned.Pruned; } #endregion #region GuildVarious /// /// Gets the template async. /// /// The code. /// A Task. internal async Task GetTemplateAsync(string code) { var route = $"{Endpoints.GUILDS}{Endpoints.TEMPLATES}/:code"; var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new { code }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.GET, route).ConfigureAwait(false); - var templates_raw = JsonConvert.DeserializeObject(res.Response); + var templatesRaw = JsonConvert.DeserializeObject(res.Response); - return templates_raw; + return templatesRaw; } /// /// Gets the guild integrations async. /// - /// The guild_id. + /// The guild_id. /// A Task. - internal async Task> GetGuildIntegrationsAsync(ulong guild_id) + internal async Task> GetGuildIntegrationsAsync(ulong guildId) { var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.INTEGRATIONS}"; - var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new { guild_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new {guild_id = guildId }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.GET, route).ConfigureAwait(false); - var integrations_raw = JsonConvert.DeserializeObject>(res.Response).Select(xi => { xi.Discord = this.Discord; return xi; }); + var integrationsRaw = JsonConvert.DeserializeObject>(res.Response).Select(xi => { xi.Discord = this.Discord; return xi; }); - return new ReadOnlyCollection(new List(integrations_raw)); + return new ReadOnlyCollection(new List(integrationsRaw)); } /// /// Gets the guild preview async. /// - /// The guild_id. + /// The guild_id. /// A Task. - internal async Task GetGuildPreviewAsync(ulong guild_id) + internal async Task GetGuildPreviewAsync(ulong guildId) { var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.PREVIEW}"; - var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new { guild_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new {guild_id = guildId }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.GET, route).ConfigureAwait(false); var ret = JsonConvert.DeserializeObject(res.Response); ret.Discord = this.Discord; return ret; } /// /// Creates the guild integration async. /// - /// The guild_id. + /// The guild_id. /// The type. /// The id. /// A Task. - internal async Task CreateGuildIntegrationAsync(ulong guild_id, string type, ulong id) + internal async Task CreateGuildIntegrationAsync(ulong guildId, string type, ulong id) { var pld = new RestGuildIntegrationAttachPayload { Type = type, Id = id }; var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.INTEGRATIONS}"; - var bucket = this.Rest.GetBucket(RestRequestMethod.POST, route, new { guild_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.POST, route, new {guild_id = guildId }, 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 ret = JsonConvert.DeserializeObject(res.Response); ret.Discord = this.Discord; return ret; } /// /// Modifies the guild integration async. /// - /// The guild_id. - /// The integration_id. - /// The expire_behaviour. - /// The expire_grace_period. - /// If true, enable_emoticons. + /// The guild_id. + /// The integration_id. + /// The expire_behaviour. + /// The expire_grace_period. + /// If true, enable_emoticons. /// A Task. - internal async Task ModifyGuildIntegrationAsync(ulong guild_id, ulong integration_id, int expire_behaviour, int expire_grace_period, bool enable_emoticons) + internal async Task ModifyGuildIntegrationAsync(ulong guildId, ulong integrationId, int expireBehaviour, int expireGracePeriod, bool enableEmoticons) { var pld = new RestGuildIntegrationModifyPayload { - ExpireBehavior = expire_behaviour, - ExpireGracePeriod = expire_grace_period, - EnableEmoticons = enable_emoticons + ExpireBehavior = expireBehaviour, + ExpireGracePeriod = expireGracePeriod, + EnableEmoticons = enableEmoticons }; var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.INTEGRATIONS}/:integration_id"; - var bucket = this.Rest.GetBucket(RestRequestMethod.PATCH, route, new { guild_id, integration_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.PATCH, route, new {guild_id = guildId, integration_id = integrationId }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.PATCH, route, payload: DiscordJson.SerializeObject(pld)).ConfigureAwait(false); var ret = JsonConvert.DeserializeObject(res.Response); ret.Discord = this.Discord; return ret; } /// /// Deletes the guild integration async. /// - /// The guild_id. + /// The guild_id. /// The integration. /// A Task. - internal Task DeleteGuildIntegrationAsync(ulong guild_id, DiscordIntegration integration) + internal Task DeleteGuildIntegrationAsync(ulong guildId, DiscordIntegration integration) { var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.INTEGRATIONS}/:integration_id"; - var bucket = this.Rest.GetBucket(RestRequestMethod.DELETE, route, new { guild_id, integration_id = integration.Id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.DELETE, route, new {guild_id = guildId, integration_id = integration.Id }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); return this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.DELETE, route, payload: DiscordJson.SerializeObject(integration)); } /// /// Syncs the guild integration async. /// - /// The guild_id. - /// The integration_id. + /// The guild_id. + /// The integration_id. /// A Task. - internal Task SyncGuildIntegrationAsync(ulong guild_id, ulong integration_id) + internal Task SyncGuildIntegrationAsync(ulong guildId, ulong integrationId) { var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.INTEGRATIONS}/:integration_id{Endpoints.SYNC}"; - var bucket = this.Rest.GetBucket(RestRequestMethod.POST, route, new { guild_id, integration_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.POST, route, new {guild_id = guildId, integration_id = integrationId }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); return this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.POST, route); } /// /// Gets the guild voice regions async. /// - /// The guild_id. + /// The guild_id. /// A Task. - internal async Task> GetGuildVoiceRegionsAsync(ulong guild_id) + internal async Task> GetGuildVoiceRegionsAsync(ulong guildId) { var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.REGIONS}"; - var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new { guild_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new {guild_id = guildId }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.GET, route).ConfigureAwait(false); - var regions_raw = JsonConvert.DeserializeObject>(res.Response); + var regionsRaw = JsonConvert.DeserializeObject>(res.Response); - return new ReadOnlyCollection(new List(regions_raw)); + return new ReadOnlyCollection(new List(regionsRaw)); } /// /// Gets the guild invites async. /// - /// The guild_id. + /// The guild_id. /// A Task. - internal async Task> GetGuildInvitesAsync(ulong guild_id) + internal async Task> GetGuildInvitesAsync(ulong guildId) { var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.INVITES}"; - var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new { guild_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new {guild_id = guildId }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.GET, route).ConfigureAwait(false); - var invites_raw = JsonConvert.DeserializeObject>(res.Response).Select(xi => { xi.Discord = this.Discord; return xi; }); + var invitesRaw = JsonConvert.DeserializeObject>(res.Response).Select(xi => { xi.Discord = this.Discord; return xi; }); - return new ReadOnlyCollection(new List(invites_raw)); + return new ReadOnlyCollection(new List(invitesRaw)); } #endregion #region Invite /// /// Gets the invite async. /// - /// The invite_code. - /// If true, with_counts. - /// If true, with_expiration. - /// The scheduled event id to get. + /// The invite_code. + /// If true, with_counts. + /// If true, with_expiration. + /// The scheduled event id to get. /// A Task. - internal async Task GetInviteAsync(string invite_code, bool? with_counts, bool? with_expiration, ulong? guild_scheduled_event_id) + internal async Task GetInviteAsync(string inviteCode, bool? withCounts, bool? withExpiration, ulong? guildScheduledEventId) { var urlparams = new Dictionary(); - if (with_counts.HasValue) - urlparams["with_counts"] = with_counts?.ToString(); - if (with_expiration.HasValue) - urlparams["with_expiration"] = with_expiration?.ToString(); - if (guild_scheduled_event_id.HasValue) - urlparams["guild_scheduled_event_id"] = guild_scheduled_event_id?.ToString(); + if (withCounts.HasValue) + urlparams["with_counts"] = withCounts?.ToString(); + if (withExpiration.HasValue) + urlparams["with_expiration"] = withExpiration?.ToString(); + if (guildScheduledEventId.HasValue) + urlparams["guild_scheduled_event_id"] = guildScheduledEventId?.ToString(); var route = $"{Endpoints.INVITES}/:invite_code"; - var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new { invite_code }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new {invite_code = inviteCode }, out var path); var url = Utilities.GetApiUriFor(path, urlparams.Any() ? BuildQueryString(urlparams) : "", this.Discord.Configuration); var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.GET, route).ConfigureAwait(false); var ret = JsonConvert.DeserializeObject(res.Response); ret.Discord = this.Discord; return ret; } /// /// Deletes the invite async. /// - /// The invite_code. + /// The invite_code. /// The reason. /// A Task. - internal async Task DeleteInviteAsync(string invite_code, string reason) + internal async Task DeleteInviteAsync(string inviteCode, string reason) { var headers = Utilities.GetBaseHeaders(); if (!string.IsNullOrWhiteSpace(reason)) headers[REASON_HEADER_NAME] = reason; var route = $"{Endpoints.INVITES}/:invite_code"; - var bucket = this.Rest.GetBucket(RestRequestMethod.DELETE, route, new { invite_code }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.DELETE, route, new {invite_code = inviteCode }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.DELETE, route, headers).ConfigureAwait(false); var ret = JsonConvert.DeserializeObject(res.Response); ret.Discord = this.Discord; return ret; } /* * Disabled due to API restrictions * * internal async Task InternalAcceptInvite(string invite_code) * { * this.Discord.DebugLogger.LogMessage(LogLevel.Warning, "REST API", "Invite accept endpoint was used; this account is now likely unverified", DateTime.Now); * * var url = new Uri($"{Utils.GetApiBaseUri(this.Configuration), Endpoints.INVITES}/{invite_code)); * var bucket = this.Rest.GetBucket(0, MajorParameterType.Unbucketed, url, HttpRequestMethod.POST); * var res = await this.DoRequestAsync(this.Discord, bucket, url, HttpRequestMethod.POST).ConfigureAwait(false); * * var ret = JsonConvert.DeserializeObject(res.Response); * ret.Discord = this.Discord; * * return ret; * } */ #endregion #region Connections /// /// Gets the users connections async. /// /// A Task. internal async Task> GetUsersConnectionsAsync() { var route = $"{Endpoints.USERS}{Endpoints.ME}{Endpoints.CONNECTIONS}"; var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new { }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.GET, route).ConfigureAwait(false); - var connections_raw = JsonConvert.DeserializeObject>(res.Response).Select(xc => { xc.Discord = this.Discord; return xc; }); + var connectionsRaw = JsonConvert.DeserializeObject>(res.Response).Select(xc => { xc.Discord = this.Discord; return xc; }); - return new ReadOnlyCollection(new List(connections_raw)); + return new ReadOnlyCollection(new List(connectionsRaw)); } #endregion #region Voice /// /// Lists the voice regions async. /// /// A Task. internal async Task> ListVoiceRegionsAsync() { var route = $"{Endpoints.VOICE}{Endpoints.REGIONS}"; var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new { }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.GET, route).ConfigureAwait(false); var regions = JsonConvert.DeserializeObject>(res.Response); return new ReadOnlyCollection(new List(regions)); } #endregion #region Webhooks /// /// Creates the webhook async. /// - /// The channel_id. + /// The channel_id. /// The name. - /// The base64_avatar. + /// The base64_avatar. /// The reason. /// A Task. - internal async Task CreateWebhookAsync(ulong channel_id, string name, Optional base64_avatar, string reason) + internal async Task CreateWebhookAsync(ulong channelId, string name, Optional base64Avatar, string reason) { var pld = new RestWebhookPayload { Name = name, - AvatarBase64 = base64_avatar.HasValue ? base64_avatar.Value : null, - AvatarSet = base64_avatar.HasValue + AvatarBase64 = base64Avatar.HasValue ? base64Avatar.Value : null, + AvatarSet = base64Avatar.HasValue }; var headers = new Dictionary(); if (!string.IsNullOrWhiteSpace(reason)) headers[REASON_HEADER_NAME] = reason; var route = $"{Endpoints.CHANNELS}/:channel_id{Endpoints.WEBHOOKS}"; - var bucket = this.Rest.GetBucket(RestRequestMethod.POST, route, new { channel_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.POST, route, new {channel_id = channelId }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.POST, route, headers, DiscordJson.SerializeObject(pld)).ConfigureAwait(false); var ret = JsonConvert.DeserializeObject(res.Response); ret.Discord = this.Discord; ret.ApiClient = this; return ret; } /// /// Gets the channel webhooks async. /// - /// The channel_id. + /// The channel_id. /// A Task. - internal async Task> GetChannelWebhooksAsync(ulong channel_id) + internal async Task> GetChannelWebhooksAsync(ulong channelId) { var route = $"{Endpoints.CHANNELS}/:channel_id{Endpoints.WEBHOOKS}"; - var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new { channel_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new {channel_id = channelId }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.GET, route).ConfigureAwait(false); - var webhooks_raw = JsonConvert.DeserializeObject>(res.Response).Select(xw => { xw.Discord = this.Discord; xw.ApiClient = this; return xw; }); + var webhooksRaw = JsonConvert.DeserializeObject>(res.Response).Select(xw => { xw.Discord = this.Discord; xw.ApiClient = this; return xw; }); - return new ReadOnlyCollection(new List(webhooks_raw)); + return new ReadOnlyCollection(new List(webhooksRaw)); } /// /// Gets the guild webhooks async. /// - /// The guild_id. + /// The guild_id. /// A Task. - internal async Task> GetGuildWebhooksAsync(ulong guild_id) + internal async Task> GetGuildWebhooksAsync(ulong guildId) { var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.WEBHOOKS}"; - var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new { guild_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new {guild_id = guildId }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.GET, route).ConfigureAwait(false); - var webhooks_raw = JsonConvert.DeserializeObject>(res.Response).Select(xw => { xw.Discord = this.Discord; xw.ApiClient = this; return xw; }); + var webhooksRaw = JsonConvert.DeserializeObject>(res.Response).Select(xw => { xw.Discord = this.Discord; xw.ApiClient = this; return xw; }); - return new ReadOnlyCollection(new List(webhooks_raw)); + return new ReadOnlyCollection(new List(webhooksRaw)); } /// /// Gets the webhook async. /// - /// The webhook_id. + /// The webhook_id. /// A Task. - internal async Task GetWebhookAsync(ulong webhook_id) + internal async Task GetWebhookAsync(ulong webhookId) { var route = $"{Endpoints.WEBHOOKS}/:webhook_id"; - var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new { webhook_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new {webhook_id = webhookId }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.GET, route).ConfigureAwait(false); var ret = JsonConvert.DeserializeObject(res.Response); ret.Discord = this.Discord; ret.ApiClient = this; return ret; } /// /// Gets the webhook with token async. /// - /// The webhook_id. - /// The webhook_token. + /// The webhook_id. + /// The webhook_token. /// A Task. - internal async Task GetWebhookWithTokenAsync(ulong webhook_id, string webhook_token) + internal async Task GetWebhookWithTokenAsync(ulong webhookId, string webhookToken) { var route = $"{Endpoints.WEBHOOKS}/:webhook_id/:webhook_token"; - var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new { webhook_id, webhook_token }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new {webhook_id = webhookId, webhook_token = webhookToken }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.GET, route).ConfigureAwait(false); var ret = JsonConvert.DeserializeObject(res.Response); - ret.Token = webhook_token; - ret.Id = webhook_id; + ret.Token = webhookToken; + ret.Id = webhookId; ret.Discord = this.Discord; ret.ApiClient = this; return ret; } /// /// Modifies the webhook async. /// - /// The webhook_id. + /// The webhook_id. /// The channel id. /// The name. - /// The base64_avatar. + /// The base64_avatar. /// The reason. /// A Task. - internal async Task ModifyWebhookAsync(ulong webhook_id, ulong channelId, string name, Optional base64_avatar, string reason) + internal async Task ModifyWebhookAsync(ulong webhookId, ulong channelId, string name, Optional base64Avatar, string reason) { var pld = new RestWebhookPayload { Name = name, - AvatarBase64 = base64_avatar.HasValue ? base64_avatar.Value : null, - AvatarSet = base64_avatar.HasValue, + AvatarBase64 = base64Avatar.HasValue ? base64Avatar.Value : null, + AvatarSet = base64Avatar.HasValue, ChannelId = channelId }; var headers = new Dictionary(); if (!string.IsNullOrWhiteSpace(reason)) headers[REASON_HEADER_NAME] = reason; var route = $"{Endpoints.WEBHOOKS}/:webhook_id"; - var bucket = this.Rest.GetBucket(RestRequestMethod.PATCH, route, new { webhook_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.PATCH, route, new {webhook_id = webhookId }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.PATCH, route, headers, DiscordJson.SerializeObject(pld)).ConfigureAwait(false); var ret = JsonConvert.DeserializeObject(res.Response); ret.Discord = this.Discord; ret.ApiClient = this; return ret; } /// /// Modifies the webhook async. /// - /// The webhook_id. + /// The webhook_id. /// The name. - /// The base64_avatar. - /// The webhook_token. + /// The base64_avatar. + /// The webhook_token. /// The reason. /// A Task. - internal async Task ModifyWebhookAsync(ulong webhook_id, string name, string base64_avatar, string webhook_token, string reason) + internal async Task ModifyWebhookAsync(ulong webhookId, string name, string base64Avatar, string webhookToken, string reason) { var pld = new RestWebhookPayload { Name = name, - AvatarBase64 = base64_avatar + AvatarBase64 = base64Avatar }; var headers = new Dictionary(); if (!string.IsNullOrWhiteSpace(reason)) headers[REASON_HEADER_NAME] = reason; var route = $"{Endpoints.WEBHOOKS}/:webhook_id/:webhook_token"; - var bucket = this.Rest.GetBucket(RestRequestMethod.PATCH, route, new { webhook_id, webhook_token }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.PATCH, route, new {webhook_id = webhookId, webhook_token = webhookToken }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.PATCH, route, headers, DiscordJson.SerializeObject(pld)).ConfigureAwait(false); var ret = JsonConvert.DeserializeObject(res.Response); ret.Discord = this.Discord; ret.ApiClient = this; return ret; } /// /// Deletes the webhook async. /// - /// The webhook_id. + /// The webhook_id. /// The reason. /// A Task. - internal Task DeleteWebhookAsync(ulong webhook_id, string reason) + internal Task DeleteWebhookAsync(ulong webhookId, string reason) { var headers = new Dictionary(); if (!string.IsNullOrWhiteSpace(reason)) headers[REASON_HEADER_NAME] = reason; var route = $"{Endpoints.WEBHOOKS}/:webhook_id"; - var bucket = this.Rest.GetBucket(RestRequestMethod.DELETE, route, new { webhook_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.DELETE, route, new {webhook_id = webhookId }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); return this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.DELETE, route, headers); } /// /// Deletes the webhook async. /// - /// The webhook_id. - /// The webhook_token. + /// The webhook_id. + /// The webhook_token. /// The reason. /// A Task. - internal Task DeleteWebhookAsync(ulong webhook_id, string webhook_token, string reason) + internal Task DeleteWebhookAsync(ulong webhookId, string webhookToken, string reason) { var headers = new Dictionary(); if (!string.IsNullOrWhiteSpace(reason)) headers[REASON_HEADER_NAME] = reason; var route = $"{Endpoints.WEBHOOKS}/:webhook_id/:webhook_token"; - var bucket = this.Rest.GetBucket(RestRequestMethod.DELETE, route, new { webhook_id, webhook_token }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.DELETE, route, new {webhook_id = webhookId, webhook_token = webhookToken }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); return this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.DELETE, route, headers); } /// /// Executes the webhook async. /// - /// The webhook_id. - /// The webhook_token. + /// The webhook_id. + /// The webhook_token. /// The builder. - /// The thread_id. + /// The thread_id. /// A Task. - internal async Task ExecuteWebhookAsync(ulong webhook_id, string webhook_token, DiscordWebhookBuilder builder, string thread_id) + internal async Task ExecuteWebhookAsync(ulong webhookId, string webhookToken, DiscordWebhookBuilder builder, string threadId) { builder.Validate(); if (builder.Embeds != null) foreach (var embed in builder.Embeds) if (embed.Timestamp != null) embed.Timestamp = embed.Timestamp.Value.ToUniversalTime(); var values = new Dictionary(); var pld = new RestWebhookExecutePayload { Content = builder.Content, Username = builder.Username.HasValue ? builder.Username.Value : null, AvatarUrl = builder.AvatarUrl.HasValue ? builder.AvatarUrl.Value : null, - IsTTS = builder.IsTTS, + IsTts = builder.IsTts, Embeds = builder.Embeds, Components = builder.Components }; if (builder.Mentions != null) pld.Mentions = new DiscordMentions(builder.Mentions, builder.Mentions.Any()); if (builder.Files?.Count > 0) { - ulong file_id = 0; + ulong fileId = 0; List attachments = new(); foreach (var file in builder.Files) { DiscordAttachment att = new() { - Id = file_id, + Id = fileId, Discord = this.Discord, Description = file.Description, FileName = file.FileName, FileSize = null }; attachments.Add(att); - file_id++; + fileId++; } pld.Attachments = attachments; } - if (!string.IsNullOrEmpty(builder.Content) || builder.Embeds?.Count() > 0 || builder.Files?.Count > 0 || builder.IsTTS == true || builder.Mentions != null) + if (!string.IsNullOrEmpty(builder.Content) || builder.Embeds?.Count() > 0 || builder.Files?.Count > 0 || builder.IsTts == true || builder.Mentions != null) values["payload_json"] = DiscordJson.SerializeObject(pld); var route = $"{Endpoints.WEBHOOKS}/:webhook_id/:webhook_token"; - var bucket = this.Rest.GetBucket(RestRequestMethod.POST, route, new { webhook_id, webhook_token }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.POST, route, new {webhook_id = webhookId, webhook_token = webhookToken }, out var path); var qub = Utilities.GetApiUriBuilderFor(path, this.Discord.Configuration).AddParameter("wait", "true"); - if (thread_id != null) - qub.AddParameter("thread_id", thread_id); + if (threadId != null) + qub.AddParameter("thread_id", threadId); var url = qub.Build(); var res = await this.DoMultipartAsync(this.Discord, bucket, url, RestRequestMethod.POST, route, values: values, files: builder.Files).ConfigureAwait(false); var ret = JsonConvert.DeserializeObject(res.Response); foreach (var att in ret.Attachments) att.Discord = this.Discord; foreach (var file in builder.Files.Where(x => x.ResetPositionTo.HasValue)) { file.Stream.Position = file.ResetPositionTo.Value; } ret.Discord = this.Discord; return ret; } /// /// Executes the webhook slack async. /// - /// The webhook_id. - /// The webhook_token. - /// The json_payload. - /// The thread_id. + /// The webhook_id. + /// The webhook_token. + /// The json_payload. + /// The thread_id. /// A Task. - internal async Task ExecuteWebhookSlackAsync(ulong webhook_id, string webhook_token, string json_payload, string thread_id) + internal async Task ExecuteWebhookSlackAsync(ulong webhookId, string webhookToken, string jsonPayload, string threadId) { var route = $"{Endpoints.WEBHOOKS}/:webhook_id/:webhook_token{Endpoints.SLACK}"; - var bucket = this.Rest.GetBucket(RestRequestMethod.POST, route, new { webhook_id, webhook_token }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.POST, route, new {webhook_id = webhookId, webhook_token = webhookToken }, out var path); var qub = Utilities.GetApiUriBuilderFor(path, this.Discord.Configuration).AddParameter("wait", "true"); - if (thread_id != null) - qub.AddParameter("thread_id", thread_id); + if (threadId != null) + qub.AddParameter("thread_id", threadId); var url = qub.Build(); - var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.POST, route, payload: json_payload).ConfigureAwait(false); + var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.POST, route, payload: jsonPayload).ConfigureAwait(false); var ret = JsonConvert.DeserializeObject(res.Response); ret.Discord = this.Discord; return ret; } /// /// Executes the webhook github async. /// - /// The webhook_id. - /// The webhook_token. - /// The json_payload. - /// The thread_id. + /// The webhook_id. + /// The webhook_token. + /// The json_payload. + /// The thread_id. /// A Task. - internal async Task ExecuteWebhookGithubAsync(ulong webhook_id, string webhook_token, string json_payload, string thread_id) + internal async Task ExecuteWebhookGithubAsync(ulong webhookId, string webhookToken, string jsonPayload, string threadId) { var route = $"{Endpoints.WEBHOOKS}/:webhook_id/:webhook_token{Endpoints.GITHUB}"; - var bucket = this.Rest.GetBucket(RestRequestMethod.POST, route, new { webhook_id, webhook_token }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.POST, route, new {webhook_id = webhookId, webhook_token = webhookToken }, out var path); var qub = Utilities.GetApiUriBuilderFor(path, this.Discord.Configuration).AddParameter("wait", "true"); - if (thread_id != null) - qub.AddParameter("thread_id", thread_id); + if (threadId != null) + qub.AddParameter("thread_id", threadId); var url = qub.Build(); - var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.POST, route, payload: json_payload).ConfigureAwait(false); + var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.POST, route, payload: jsonPayload).ConfigureAwait(false); var ret = JsonConvert.DeserializeObject(res.Response); ret.Discord = this.Discord; return ret; } /// /// Edits the webhook message async. /// - /// The webhook_id. - /// The webhook_token. - /// The message_id. + /// The webhook_id. + /// The webhook_token. + /// The message_id. /// The builder. - /// The thread_id. + /// The thread_id. /// A Task. - internal async Task EditWebhookMessageAsync(ulong webhook_id, string webhook_token, string message_id, DiscordWebhookBuilder builder, string thread_id) + internal async Task EditWebhookMessageAsync(ulong webhookId, string webhookToken, string messageId, DiscordWebhookBuilder builder, string threadId) { builder.Validate(true); var pld = new RestWebhookMessageEditPayload { Content = builder.Content, Embeds = builder.Embeds, Mentions = builder.Mentions, Components = builder.Components, }; if (builder.Files?.Count > 0) { - ulong file_id = 0; + ulong fileId = 0; List attachments = new(); foreach (var file in builder.Files) { DiscordAttachment att = new() { - Id = file_id, + Id = fileId, Discord = this.Discord, Description = file.Description, FileName = file.FileName, FileSize = null }; attachments.Add(att); - file_id++; + fileId++; } if (builder.Attachments != null && builder.Attachments?.Count() > 0) attachments.AddRange(builder.Attachments); pld.Attachments = attachments; var values = new Dictionary { ["payload_json"] = DiscordJson.SerializeObject(pld) }; var route = $"{Endpoints.WEBHOOKS}/:webhook_id/:webhook_token{Endpoints.MESSAGES}/:message_id"; - var bucket = this.Rest.GetBucket(RestRequestMethod.PATCH, route, new { webhook_id, webhook_token, message_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.PATCH, route, new {webhook_id = webhookId, webhook_token = webhookToken, message_id = messageId }, out var path); var qub = Utilities.GetApiUriBuilderFor(path, this.Discord.Configuration); - if (thread_id != null) - qub.AddParameter("thread_id", thread_id); + if (threadId != null) + qub.AddParameter("thread_id", threadId); var url = qub.Build(); var res = await this.DoMultipartAsync(this.Discord, bucket, url, RestRequestMethod.PATCH, route, values: values, files: builder.Files); var ret = JsonConvert.DeserializeObject(res.Response); ret.Discord = this.Discord; - foreach (var att in ret._attachments) + foreach (var att in ret.AttachmentsInternal) att.Discord = this.Discord; foreach (var file in builder.Files.Where(x => x.ResetPositionTo.HasValue)) { file.Stream.Position = file.ResetPositionTo.Value; } return ret; } else { pld.Attachments = builder.Attachments; var route = $"{Endpoints.WEBHOOKS}/:webhook_id/:webhook_token{Endpoints.MESSAGES}/:message_id"; - var bucket = this.Rest.GetBucket(RestRequestMethod.PATCH, route, new { webhook_id, webhook_token, message_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.PATCH, route, new {webhook_id = webhookId, webhook_token = webhookToken, message_id = messageId }, out var path); var qub = Utilities.GetApiUriBuilderFor(path, this.Discord.Configuration); - if (thread_id != null) - qub.AddParameter("thread_id", thread_id); + if (threadId != null) + qub.AddParameter("thread_id", threadId); var url = qub.Build(); var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.PATCH, route, payload: DiscordJson.SerializeObject(pld)); var ret = JsonConvert.DeserializeObject(res.Response); ret.Discord = this.Discord; - foreach (var att in ret._attachments) + foreach (var att in ret.AttachmentsInternal) att.Discord = this.Discord; return ret; } } /// /// Edits the webhook message async. /// - /// The webhook_id. - /// The webhook_token. - /// The message_id. + /// The webhook_id. + /// The webhook_token. + /// The message_id. /// The builder. - /// The thread_id. + /// The thread_id. /// A Task. - internal Task EditWebhookMessageAsync(ulong webhook_id, string webhook_token, ulong message_id, DiscordWebhookBuilder builder, ulong thread_id) => - this.EditWebhookMessageAsync(webhook_id, webhook_token, message_id.ToString(), builder, thread_id.ToString()); + internal Task EditWebhookMessageAsync(ulong webhookId, string webhookToken, ulong messageId, DiscordWebhookBuilder builder, ulong threadId) => + this.EditWebhookMessageAsync(webhookId, webhookToken, messageId.ToString(), builder, threadId.ToString()); /// /// Gets the webhook message async. /// - /// The webhook_id. - /// The webhook_token. - /// The message_id. - /// The thread_id. + /// The webhook_id. + /// The webhook_token. + /// The message_id. + /// The thread_id. /// A Task. - internal async Task GetWebhookMessageAsync(ulong webhook_id, string webhook_token, string message_id, string thread_id) + internal async Task GetWebhookMessageAsync(ulong webhookId, string webhookToken, string messageId, string threadId) { var route = $"{Endpoints.WEBHOOKS}/:webhook_id/:webhook_token{Endpoints.MESSAGES}/:message_id"; - var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new { webhook_id, webhook_token, message_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new {webhook_id = webhookId, webhook_token = webhookToken, message_id = messageId }, out var path); var qub = Utilities.GetApiUriBuilderFor(path, this.Discord.Configuration); - if (thread_id != null) - qub.AddParameter("thread_id", thread_id); + if (threadId != null) + qub.AddParameter("thread_id", threadId); var url = qub.Build(); var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.GET, route); var ret = JsonConvert.DeserializeObject(res.Response); ret.Discord = this.Discord; return ret; } /// /// Gets the webhook message async. /// - /// The webhook_id. - /// The webhook_token. - /// The message_id. + /// The webhook_id. + /// The webhook_token. + /// The message_id. /// A Task. - internal Task GetWebhookMessageAsync(ulong webhook_id, string webhook_token, ulong message_id) => - this.GetWebhookMessageAsync(webhook_id, webhook_token, message_id.ToString(), null); + internal Task GetWebhookMessageAsync(ulong webhookId, string webhookToken, ulong messageId) => + this.GetWebhookMessageAsync(webhookId, webhookToken, messageId.ToString(), null); /// /// Gets the webhook message async. /// - /// The webhook_id. - /// The webhook_token. - /// The message_id. - /// The thread_id. + /// The webhook_id. + /// The webhook_token. + /// The message_id. + /// The thread_id. /// A Task. - internal Task GetWebhookMessageAsync(ulong webhook_id, string webhook_token, ulong message_id, ulong thread_id) => - this.GetWebhookMessageAsync(webhook_id, webhook_token, message_id.ToString(), thread_id.ToString()); + internal Task GetWebhookMessageAsync(ulong webhookId, string webhookToken, ulong messageId, ulong threadId) => + this.GetWebhookMessageAsync(webhookId, webhookToken, messageId.ToString(), threadId.ToString()); /// /// Deletes the webhook message async. /// - /// The webhook_id. - /// The webhook_token. - /// The message_id. - /// The thread_id. + /// The webhook_id. + /// The webhook_token. + /// The message_id. + /// The thread_id. /// A Task. - internal async Task DeleteWebhookMessageAsync(ulong webhook_id, string webhook_token, string message_id, string thread_id) + internal async Task DeleteWebhookMessageAsync(ulong webhookId, string webhookToken, string messageId, string threadId) { var route = $"{Endpoints.WEBHOOKS}/:webhook_id/:webhook_token{Endpoints.MESSAGES}/:message_id"; - var bucket = this.Rest.GetBucket(RestRequestMethod.DELETE, route, new { webhook_id, webhook_token, message_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.DELETE, route, new {webhook_id = webhookId, webhook_token = webhookToken, message_id = messageId }, out var path); var qub = Utilities.GetApiUriBuilderFor(path, this.Discord.Configuration); - if (thread_id != null) - qub.AddParameter("thread_id", thread_id); + if (threadId != null) + qub.AddParameter("thread_id", threadId); var url = qub.Build(); await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.DELETE, route); } /// /// Deletes the webhook message async. /// - /// The webhook_id. - /// The webhook_token. - /// The message_id. + /// The webhook_id. + /// The webhook_token. + /// The message_id. /// A Task. - internal Task DeleteWebhookMessageAsync(ulong webhook_id, string webhook_token, ulong message_id) => - this.DeleteWebhookMessageAsync(webhook_id, webhook_token, message_id.ToString(), null); + internal Task DeleteWebhookMessageAsync(ulong webhookId, string webhookToken, ulong messageId) => + this.DeleteWebhookMessageAsync(webhookId, webhookToken, messageId.ToString(), null); /// /// Deletes the webhook message async. /// - /// The webhook_id. - /// The webhook_token. - /// The message_id. - /// The thread_id. + /// The webhook_id. + /// The webhook_token. + /// The message_id. + /// The thread_id. /// A Task. - internal Task DeleteWebhookMessageAsync(ulong webhook_id, string webhook_token, ulong message_id, ulong thread_id) => - this.DeleteWebhookMessageAsync(webhook_id, webhook_token, message_id.ToString(), thread_id.ToString()); + internal Task DeleteWebhookMessageAsync(ulong webhookId, string webhookToken, ulong messageId, ulong threadId) => + this.DeleteWebhookMessageAsync(webhookId, webhookToken, messageId.ToString(), threadId.ToString()); #endregion #region Reactions /// /// Creates the reaction async. /// - /// The channel_id. - /// The message_id. + /// The channel_id. + /// The message_id. /// The emoji. /// A Task. - internal Task CreateReactionAsync(ulong channel_id, ulong message_id, string emoji) + internal Task CreateReactionAsync(ulong channelId, ulong messageId, string emoji) { var route = $"{Endpoints.CHANNELS}/:channel_id{Endpoints.MESSAGES}/:message_id{Endpoints.REACTIONS}/:emoji{Endpoints.ME}"; - var bucket = this.Rest.GetBucket(RestRequestMethod.PUT, route, new { channel_id, message_id, emoji }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.PUT, route, new {channel_id = channelId, message_id = messageId, emoji }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); return this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.PUT, route, ratelimitWaitOverride: this.Discord.Configuration.UseRelativeRatelimit ? null : (double?)0.26); } /// /// Deletes the own reaction async. /// - /// The channel_id. - /// The message_id. + /// The channel_id. + /// The message_id. /// The emoji. /// A Task. - internal Task DeleteOwnReactionAsync(ulong channel_id, ulong message_id, string emoji) + internal Task DeleteOwnReactionAsync(ulong channelId, ulong messageId, string emoji) { var route = $"{Endpoints.CHANNELS}/:channel_id{Endpoints.MESSAGES}/:message_id{Endpoints.REACTIONS}/:emoji{Endpoints.ME}"; - var bucket = this.Rest.GetBucket(RestRequestMethod.DELETE, route, new { channel_id, message_id, emoji }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.DELETE, route, new {channel_id = channelId, message_id = messageId, emoji }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); return this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.DELETE, route, ratelimitWaitOverride: this.Discord.Configuration.UseRelativeRatelimit ? null : (double?)0.26); } /// /// Deletes the user reaction async. /// - /// The channel_id. - /// The message_id. - /// The user_id. + /// The channel_id. + /// The message_id. + /// The user_id. /// The emoji. /// The reason. /// A Task. - internal Task DeleteUserReactionAsync(ulong channel_id, ulong message_id, ulong user_id, string emoji, string reason) + internal Task DeleteUserReactionAsync(ulong channelId, ulong messageId, ulong userId, string emoji, string reason) { var headers = new Dictionary(); if (!string.IsNullOrWhiteSpace(reason)) headers[REASON_HEADER_NAME] = reason; var route = $"{Endpoints.CHANNELS}/:channel_id{Endpoints.MESSAGES}/:message_id{Endpoints.REACTIONS}/:emoji/:user_id"; - var bucket = this.Rest.GetBucket(RestRequestMethod.DELETE, route, new { channel_id, message_id, emoji, user_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.DELETE, route, new {channel_id = channelId, message_id = messageId, emoji, user_id = userId }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); return this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.DELETE, route, headers, ratelimitWaitOverride: this.Discord.Configuration.UseRelativeRatelimit ? null : (double?)0.26); } /// /// Gets the reactions async. /// - /// The channel_id. - /// The message_id. + /// The channel_id. + /// The message_id. /// The emoji. - /// The after_id. + /// The after_id. /// The limit. /// A Task. - internal async Task> GetReactionsAsync(ulong channel_id, ulong message_id, string emoji, ulong? after_id = null, int limit = 25) + internal async Task> GetReactionsAsync(ulong channelId, ulong messageId, string emoji, ulong? afterId = null, int limit = 25) { var urlparams = new Dictionary(); - if (after_id.HasValue) - urlparams["after"] = after_id.Value.ToString(CultureInfo.InvariantCulture); + if (afterId.HasValue) + urlparams["after"] = afterId.Value.ToString(CultureInfo.InvariantCulture); urlparams["limit"] = limit.ToString(CultureInfo.InvariantCulture); var route = $"{Endpoints.CHANNELS}/:channel_id{Endpoints.MESSAGES}/:message_id{Endpoints.REACTIONS}/:emoji"; - var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new { channel_id, message_id, emoji }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new {channel_id = channelId, message_id = messageId, emoji }, out var path); var url = Utilities.GetApiUriFor(path, BuildQueryString(urlparams), this.Discord.Configuration); var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.GET, route).ConfigureAwait(false); - var reacters_raw = JsonConvert.DeserializeObject>(res.Response); + var reactersRaw = JsonConvert.DeserializeObject>(res.Response); var reacters = new List(); - foreach (var xr in reacters_raw) + foreach (var xr in reactersRaw) { var usr = new DiscordUser(xr) { Discord = this.Discord }; usr = this.Discord.UserCache.AddOrUpdate(xr.Id, usr, (id, old) => { old.Username = usr.Username; old.Discriminator = usr.Discriminator; old.AvatarHash = usr.AvatarHash; return old; }); reacters.Add(usr); } return new ReadOnlyCollection(new List(reacters)); } /// /// Deletes the all reactions async. /// - /// The channel_id. - /// The message_id. + /// The channel_id. + /// The message_id. /// The reason. /// A Task. - internal Task DeleteAllReactionsAsync(ulong channel_id, ulong message_id, string reason) + internal Task DeleteAllReactionsAsync(ulong channelId, ulong messageId, string reason) { var headers = new Dictionary(); if (!string.IsNullOrWhiteSpace(reason)) headers[REASON_HEADER_NAME] = reason; var route = $"{Endpoints.CHANNELS}/:channel_id{Endpoints.MESSAGES}/:message_id{Endpoints.REACTIONS}"; - var bucket = this.Rest.GetBucket(RestRequestMethod.DELETE, route, new { channel_id, message_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.DELETE, route, new {channel_id = channelId, message_id = messageId }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); return this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.DELETE, route, headers, ratelimitWaitOverride: this.Discord.Configuration.UseRelativeRatelimit ? null : (double?)0.26); } /// /// Deletes the reactions emoji async. /// - /// The channel_id. - /// The message_id. + /// The channel_id. + /// The message_id. /// The emoji. /// A Task. - internal Task DeleteReactionsEmojiAsync(ulong channel_id, ulong message_id, string emoji) + internal Task DeleteReactionsEmojiAsync(ulong channelId, ulong messageId, string emoji) { var route = $"{Endpoints.CHANNELS}/:channel_id{Endpoints.MESSAGES}/:message_id{Endpoints.REACTIONS}/:emoji"; - var bucket = this.Rest.GetBucket(RestRequestMethod.DELETE, route, new { channel_id, message_id, emoji }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.DELETE, route, new {channel_id = channelId, message_id = messageId, emoji }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); return this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.DELETE, route, ratelimitWaitOverride: this.Discord.Configuration.UseRelativeRatelimit ? null : (double?)0.26); } #endregion #region Threads /// /// Creates the thread with message. /// - /// The channel id to create the thread in. - /// The message id to create the thread from. + /// The channel id to create the thread in. + /// The message id to create the thread from. /// The name of the thread. - /// The auto_archive_duration for the thread. - /// The rate limit per user. + /// The auto_archive_duration for the thread. + /// The rate limit per user. /// The reason. - internal async Task CreateThreadWithMessageAsync(ulong channel_id, ulong message_id, string name, ThreadAutoArchiveDuration auto_archive_duration, int? rate_limit_per_user, string reason = null) + internal async Task CreateThreadWithMessageAsync(ulong channelId, ulong messageId, string name, ThreadAutoArchiveDuration autoArchiveDuration, int? rateLimitPerUser, string reason = null) { var pld = new RestThreadChannelCreatePayload { Name = name, - AutoArchiveDuration = auto_archive_duration, - PerUserRateLimit = rate_limit_per_user + AutoArchiveDuration = autoArchiveDuration, + PerUserRateLimit = rateLimitPerUser }; var headers = Utilities.GetBaseHeaders(); if (!string.IsNullOrWhiteSpace(reason)) headers.Add(REASON_HEADER_NAME, reason); var route = $"{Endpoints.CHANNELS}/:channel_id{Endpoints.MESSAGES}/:message_id{Endpoints.THREADS}"; - var bucket = this.Rest.GetBucket(RestRequestMethod.POST, route, new { channel_id, message_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.POST, route, new {channel_id = channelId, message_id = messageId }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.POST, route, headers, DiscordJson.SerializeObject(pld)); - var thread_channel = JsonConvert.DeserializeObject(res.Response); + var threadChannel = JsonConvert.DeserializeObject(res.Response); - return thread_channel; + return threadChannel; } /// /// Creates the thread without a message. /// - /// The channel id to create the thread in. + /// The channel id to create the thread in. /// The name of the thread. - /// The auto_archive_duration for the thread. + /// The auto_archive_duration for the thread. /// Can be either or . - /// The rate limit per user. + /// The rate limit per user. /// The reason. - internal async Task CreateThreadWithoutMessageAsync(ulong channel_id, string name, ThreadAutoArchiveDuration auto_archive_duration, ChannelType type = ChannelType.PublicThread, int? rate_limit_per_user = null, string reason = null) + internal async Task CreateThreadWithoutMessageAsync(ulong channelId, string name, ThreadAutoArchiveDuration autoArchiveDuration, ChannelType type = ChannelType.PublicThread, int? rateLimitPerUser = null, string reason = null) { var pld = new RestThreadChannelCreatePayload { Name = name, - AutoArchiveDuration = auto_archive_duration, - PerUserRateLimit = rate_limit_per_user, + AutoArchiveDuration = autoArchiveDuration, + PerUserRateLimit = rateLimitPerUser, Type = type }; var headers = Utilities.GetBaseHeaders(); if (!string.IsNullOrWhiteSpace(reason)) headers.Add(REASON_HEADER_NAME, reason); var route = $"{Endpoints.CHANNELS}/:channel_id{Endpoints.THREADS}"; - var bucket = this.Rest.GetBucket(RestRequestMethod.POST, route, new { channel_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.POST, route, new {channel_id = channelId }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.POST, route, headers, DiscordJson.SerializeObject(pld)); - var thread_channel = JsonConvert.DeserializeObject(res.Response); + var threadChannel = JsonConvert.DeserializeObject(res.Response); - return thread_channel; + return threadChannel; } /// /// Gets the thread. /// - /// The thread id. - internal async Task GetThreadAsync(ulong thread_id) + /// The thread id. + internal async Task GetThreadAsync(ulong threadId) { var route = $"{Endpoints.CHANNELS}/:channel_id"; - var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new { thread_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new {thread_id = threadId }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.GET, route).ConfigureAwait(false); var ret = JsonConvert.DeserializeObject(res.Response); ret.Discord = this.Discord; return ret; } /// /// Joins the thread. /// - /// The channel id. - internal async Task JoinThreadAsync(ulong channel_id) + /// The channel id. + internal async Task JoinThreadAsync(ulong channelId) { var route = $"{Endpoints.CHANNELS}/:channel_id{Endpoints.THREAD_MEMBERS}{Endpoints.ME}"; - var bucket = this.Rest.GetBucket(RestRequestMethod.PUT, route, new { channel_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.PUT, route, new {channel_id = channelId }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.PUT, route); } /// /// Leaves the thread. /// - /// The channel id. - internal async Task LeaveThreadAsync(ulong channel_id) + /// The channel id. + internal async Task LeaveThreadAsync(ulong channelId) { var route = $"{Endpoints.CHANNELS}/:channel_id{Endpoints.THREAD_MEMBERS}{Endpoints.ME}"; - var bucket = this.Rest.GetBucket(RestRequestMethod.DELETE, route, new { channel_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.DELETE, route, new {channel_id = channelId }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.DELETE, route); } /// /// Adds a thread member. /// - /// The channel id to add the member to. - /// The user id to add. - internal async Task AddThreadMemberAsync(ulong channel_id, ulong user_id) + /// The channel id to add the member to. + /// The user id to add. + internal async Task AddThreadMemberAsync(ulong channelId, ulong userId) { var route = $"{Endpoints.CHANNELS}/:channel_id{Endpoints.THREAD_MEMBERS}/:user_id"; - var bucket = this.Rest.GetBucket(RestRequestMethod.PUT, route, new { channel_id, user_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.PUT, route, new {channel_id = channelId, user_id = userId }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.PUT, route); } /// /// Gets a thread member. /// - /// The channel id to get the member from. - /// The user id to get. - internal async Task GetThreadMemberAsync(ulong channel_id, ulong user_id) + /// The channel id to get the member from. + /// The user id to get. + internal async Task GetThreadMemberAsync(ulong channelId, ulong userId) { var route = $"{Endpoints.CHANNELS}/:channel_id{Endpoints.THREAD_MEMBERS}/:user_id"; - var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new { channel_id, user_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new {channel_id = channelId, user_id = userId }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.GET, route); - var thread_member = JsonConvert.DeserializeObject(res.Response); + var threadMember = JsonConvert.DeserializeObject(res.Response); - return thread_member; + return threadMember; } /// /// Removes a thread member. /// - /// The channel id to remove the member from. - /// The user id to remove. - internal async Task RemoveThreadMemberAsync(ulong channel_id, ulong user_id) + /// The channel id to remove the member from. + /// The user id to remove. + internal async Task RemoveThreadMemberAsync(ulong channelId, ulong userId) { var route = $"{Endpoints.CHANNELS}/:channel_id{Endpoints.THREAD_MEMBERS}/:user_id"; - var bucket = this.Rest.GetBucket(RestRequestMethod.DELETE, route, new { channel_id, user_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.DELETE, route, new {channel_id = channelId, user_id = userId }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.DELETE, route); } /// /// Gets the thread members. /// - /// The thread id. - internal async Task> GetThreadMembersAsync(ulong thread_id) + /// The thread id. + internal async Task> GetThreadMembersAsync(ulong threadId) { var route = $"{Endpoints.CHANNELS}/:thread_id{Endpoints.THREAD_MEMBERS}"; - var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new { thread_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new {thread_id = threadId }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.GET, route); - var thread_members_raw = JsonConvert.DeserializeObject>(res.Response); + var threadMembersRaw = JsonConvert.DeserializeObject>(res.Response); - return new ReadOnlyCollection(thread_members_raw); + return new ReadOnlyCollection(threadMembersRaw); } /// /// Gets the active threads in a guild. /// - /// The guild id. - internal async Task GetActiveThreadsAsync(ulong guild_id) + /// The guild id. + internal async Task GetActiveThreadsAsync(ulong guildId) { var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.THREADS}{Endpoints.THREAD_ACTIVE}"; - var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new { guild_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new {guild_id = guildId }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.GET, route); - var thread_return = JsonConvert.DeserializeObject(res.Response); + var threadReturn = JsonConvert.DeserializeObject(res.Response); - return thread_return; + return threadReturn; } /// /// Gets the joined private archived threads in a channel. /// - /// The channel id. + /// The channel id. /// Get threads before snowflake. /// Limit the results. - internal async Task GetJoinedPrivateArchivedThreadsAsync(ulong channel_id, ulong? before, int? limit) + internal async Task GetJoinedPrivateArchivedThreadsAsync(ulong channelId, ulong? before, int? limit) { var urlparams = new Dictionary(); if (before != null) urlparams["before"] = before.Value.ToString(CultureInfo.InvariantCulture); if (limit != null && limit > 0) urlparams["limit"] = limit.Value.ToString(CultureInfo.InvariantCulture); var route = $"{Endpoints.CHANNELS}/:channel_id{Endpoints.USERS}{Endpoints.ME}{Endpoints.THREADS}{Endpoints.THREAD_ARCHIVED}{Endpoints.THREAD_PRIVATE}"; - var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new { channel_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new {channel_id = channelId }, out var path); var url = Utilities.GetApiUriFor(path, urlparams.Any() ? BuildQueryString(urlparams) : "", this.Discord.Configuration); var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.GET, route); - var thread_return = JsonConvert.DeserializeObject(res.Response); + var threadReturn = JsonConvert.DeserializeObject(res.Response); - return thread_return; + return threadReturn; } /// /// Gets the public archived threads in a channel. /// - /// The channel id. + /// The channel id. /// Get threads before snowflake. /// Limit the results. - internal async Task GetPublicArchivedThreadsAsync(ulong channel_id, ulong? before, int? limit) + internal async Task GetPublicArchivedThreadsAsync(ulong channelId, ulong? before, int? limit) { var urlparams = new Dictionary(); if (before != null) urlparams["before"] = before.Value.ToString(CultureInfo.InvariantCulture); if (limit != null && limit > 0) urlparams["limit"] = limit.Value.ToString(CultureInfo.InvariantCulture); var route = $"{Endpoints.CHANNELS}/:channel_id{Endpoints.THREADS}{Endpoints.THREAD_ARCHIVED}{Endpoints.THREAD_PUBLIC}"; - var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new { channel_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new {channel_id = channelId }, out var path); var url = Utilities.GetApiUriFor(path, urlparams.Any() ? BuildQueryString(urlparams) : "", this.Discord.Configuration); var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.GET, route); - var thread_return = JsonConvert.DeserializeObject(res.Response); + var threadReturn = JsonConvert.DeserializeObject(res.Response); - return thread_return; + return threadReturn; } /// /// Gets the private archived threads in a channel. /// - /// The channel id. + /// The channel id. /// Get threads before snowflake. /// Limit the results. - internal async Task GetPrivateArchivedThreadsAsync(ulong channel_id, ulong? before, int? limit) + internal async Task GetPrivateArchivedThreadsAsync(ulong channelId, ulong? before, int? limit) { var urlparams = new Dictionary(); if (before != null) urlparams["before"] = before.Value.ToString(CultureInfo.InvariantCulture); if (limit != null && limit > 0) urlparams["limit"] = limit.Value.ToString(CultureInfo.InvariantCulture); var route = $"{Endpoints.CHANNELS}/:channel_id{Endpoints.THREADS}{Endpoints.THREAD_ARCHIVED}{Endpoints.THREAD_PRIVATE}"; - var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new { channel_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new {channel_id = channelId }, out var path); var url = Utilities.GetApiUriFor(path, urlparams.Any() ? BuildQueryString(urlparams) : "", this.Discord.Configuration); var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.GET, route); - var thread_return = JsonConvert.DeserializeObject(res.Response); + var threadReturn = JsonConvert.DeserializeObject(res.Response); - return thread_return; + return threadReturn; } /// /// Modifies a thread. /// - /// The thread to modify. + /// The thread to modify. /// The new name. /// The new locked state. /// The new archived state. /// The new auto archive duration. /// The new per user rate limit. /// The new user invitable state. /// The reason for the modification. - internal Task ModifyThreadAsync(ulong thread_id, string name, Optional locked, Optional archived, Optional autoArchiveDuration, Optional perUserRateLimit, Optional invitable, string reason) + internal Task ModifyThreadAsync(ulong threadId, string name, Optional locked, Optional archived, Optional autoArchiveDuration, Optional perUserRateLimit, Optional invitable, string reason) { var pld = new RestThreadChannelModifyPayload { Name = name, Archived = archived, AutoArchiveDuration = autoArchiveDuration, Locked = locked, PerUserRateLimit = perUserRateLimit, Invitable = invitable }; var headers = Utilities.GetBaseHeaders(); if (!string.IsNullOrWhiteSpace(reason)) headers.Add(REASON_HEADER_NAME, reason); var route = $"{Endpoints.CHANNELS}/:thread_id"; - var bucket = this.Rest.GetBucket(RestRequestMethod.PATCH, route, new { thread_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.PATCH, route, new {thread_id = threadId }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); return this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.PATCH, route, headers, DiscordJson.SerializeObject(pld)); } /// /// Deletes a thread. /// - /// The thread to delete. + /// The thread to delete. /// The reason for deletion. - internal Task DeleteThreadAsync(ulong thread_id, string reason) + internal Task DeleteThreadAsync(ulong threadId, string reason) { var headers = Utilities.GetBaseHeaders(); if (!string.IsNullOrWhiteSpace(reason)) headers.Add(REASON_HEADER_NAME, reason); var route = $"{Endpoints.CHANNELS}/:thread_id"; - var bucket = this.Rest.GetBucket(RestRequestMethod.DELETE, route, new { thread_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.DELETE, route, new {thread_id = threadId }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); return this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.DELETE, route, headers); } #endregion #region Emoji /// /// Gets the guild emojis async. /// - /// The guild_id. + /// The guild_id. /// A Task. - internal async Task> GetGuildEmojisAsync(ulong guild_id) + internal async Task> GetGuildEmojisAsync(ulong guildId) { var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.EMOJIS}"; - var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new { guild_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new {guild_id = guildId }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.GET, route).ConfigureAwait(false); var emojisRaw = JsonConvert.DeserializeObject>(res.Response); - this.Discord.Guilds.TryGetValue(guild_id, out var gld); + this.Discord.Guilds.TryGetValue(guildId, out var gld); var users = new Dictionary(); var emojis = new List(); foreach (var rawEmoji in emojisRaw) { var xge = rawEmoji.ToObject(); xge.Guild = gld; var xtu = rawEmoji["user"]?.ToObject(); if (xtu != null) { if (!users.ContainsKey(xtu.Id)) { var user = gld != null && gld.Members.TryGetValue(xtu.Id, out var member) ? member : new DiscordUser(xtu); users[user.Id] = user; } xge.User = users[xtu.Id]; } emojis.Add(xge); } return new ReadOnlyCollection(emojis); } /// /// Gets the guild emoji async. /// - /// The guild_id. - /// The emoji_id. + /// The guild_id. + /// The emoji_id. /// A Task. - internal async Task GetGuildEmojiAsync(ulong guild_id, ulong emoji_id) + internal async Task GetGuildEmojiAsync(ulong guildId, ulong emojiId) { var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.EMOJIS}/:emoji_id"; - var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new { guild_id, emoji_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new {guild_id = guildId, emoji_id = emojiId }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.GET, route).ConfigureAwait(false); - this.Discord.Guilds.TryGetValue(guild_id, out var gld); + this.Discord.Guilds.TryGetValue(guildId, out var gld); - var emoji_raw = JObject.Parse(res.Response); - var emoji = emoji_raw.ToObject(); + var emojiRaw = JObject.Parse(res.Response); + var emoji = emojiRaw.ToObject(); emoji.Guild = gld; - var xtu = emoji_raw["user"]?.ToObject(); + var xtu = emojiRaw["user"]?.ToObject(); if (xtu != null) emoji.User = gld != null && gld.Members.TryGetValue(xtu.Id, out var member) ? member : new DiscordUser(xtu); return emoji; } /// /// Creates the guild emoji async. /// - /// The guild_id. + /// The guild_id. /// The name. /// The imageb64. /// The roles. /// The reason. /// A Task. - internal async Task CreateGuildEmojiAsync(ulong guild_id, string name, string imageb64, IEnumerable roles, string reason) + internal async Task CreateGuildEmojiAsync(ulong guildId, string name, string imageb64, IEnumerable roles, string reason) { var pld = new RestGuildEmojiCreatePayload { Name = name, ImageB64 = imageb64, Roles = roles?.ToArray() }; var headers = new Dictionary(); if (!string.IsNullOrWhiteSpace(reason)) headers[REASON_HEADER_NAME] = reason; var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.EMOJIS}"; - var bucket = this.Rest.GetBucket(RestRequestMethod.POST, route, new { guild_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.POST, route, new {guild_id = guildId }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.POST, route, headers, DiscordJson.SerializeObject(pld)).ConfigureAwait(false); - this.Discord.Guilds.TryGetValue(guild_id, out var gld); + this.Discord.Guilds.TryGetValue(guildId, out var gld); - var emoji_raw = JObject.Parse(res.Response); - var emoji = emoji_raw.ToObject(); + var emojiRaw = JObject.Parse(res.Response); + var emoji = emojiRaw.ToObject(); emoji.Guild = gld; - var xtu = emoji_raw["user"]?.ToObject(); + var xtu = emojiRaw["user"]?.ToObject(); emoji.User = xtu != null ? gld != null && gld.Members.TryGetValue(xtu.Id, out var member) ? member : new DiscordUser(xtu) : this.Discord.CurrentUser; return emoji; } /// /// Modifies the guild emoji async. /// - /// The guild_id. - /// The emoji_id. + /// The guild_id. + /// The emoji_id. /// The name. /// The roles. /// The reason. /// A Task. - internal async Task ModifyGuildEmojiAsync(ulong guild_id, ulong emoji_id, string name, IEnumerable roles, string reason) + internal async Task ModifyGuildEmojiAsync(ulong guildId, ulong emojiId, string name, IEnumerable roles, string reason) { var pld = new RestGuildEmojiModifyPayload { Name = name, Roles = roles?.ToArray() }; var headers = new Dictionary(); if (!string.IsNullOrWhiteSpace(reason)) headers[REASON_HEADER_NAME] = reason; var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.EMOJIS}/:emoji_id"; - var bucket = this.Rest.GetBucket(RestRequestMethod.PATCH, route, new { guild_id, emoji_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.PATCH, route, new {guild_id = guildId, emoji_id = emojiId }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.PATCH, route, headers, DiscordJson.SerializeObject(pld)).ConfigureAwait(false); - this.Discord.Guilds.TryGetValue(guild_id, out var gld); + this.Discord.Guilds.TryGetValue(guildId, out var gld); - var emoji_raw = JObject.Parse(res.Response); - var emoji = emoji_raw.ToObject(); + var emojiRaw = JObject.Parse(res.Response); + var emoji = emojiRaw.ToObject(); emoji.Guild = gld; - var xtu = emoji_raw["user"]?.ToObject(); + var xtu = emojiRaw["user"]?.ToObject(); if (xtu != null) emoji.User = gld != null && gld.Members.TryGetValue(xtu.Id, out var member) ? member : new DiscordUser(xtu); return emoji; } /// /// Deletes the guild emoji async. /// - /// The guild_id. - /// The emoji_id. + /// The guild_id. + /// The emoji_id. /// The reason. /// A Task. - internal Task DeleteGuildEmojiAsync(ulong guild_id, ulong emoji_id, string reason) + internal Task DeleteGuildEmojiAsync(ulong guildId, ulong emojiId, string reason) { var headers = new Dictionary(); if (!string.IsNullOrWhiteSpace(reason)) headers[REASON_HEADER_NAME] = reason; var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.EMOJIS}/:emoji_id"; - var bucket = this.Rest.GetBucket(RestRequestMethod.DELETE, route, new { guild_id, emoji_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.DELETE, route, new {guild_id = guildId, emoji_id = emojiId }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); return this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.DELETE, route, headers); } #endregion #region Stickers /// /// Gets a sticker. /// - /// The sticker id. - internal async Task GetStickerAsync(ulong sticker_id) + /// The sticker id. + internal async Task GetStickerAsync(ulong stickerId) { var route = $"{Endpoints.STICKERS}/:sticker_id"; - var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new {sticker_id}, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new {sticker_id = stickerId}, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.GET, route).ConfigureAwait(false); var ret = JObject.Parse(res.Response).ToDiscordObject(); ret.Discord = this.Discord; return ret; } /// /// Gets the sticker packs. /// internal async Task> GetStickerPacksAsync() { var route = $"{Endpoints.STICKERPACKS}"; var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new { }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.GET, route).ConfigureAwait(false); var json = JObject.Parse(res.Response)["sticker_packs"] as JArray; var ret = json.ToDiscordObject(); return ret.ToList(); } /// /// Gets the guild stickers. /// - /// The guild id. - internal async Task> GetGuildStickersAsync(ulong guild_id) + /// The guild id. + internal async Task> GetGuildStickersAsync(ulong guildId) { var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.STICKERS}"; - var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new {guild_id}, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new {guild_id = guildId}, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.GET, route).ConfigureAwait(false); var json = JArray.Parse(res.Response); var ret = json.ToDiscordObject(); for (var i = 0; i < ret.Length; i++) { var stkr = ret[i]; stkr.Discord = this.Discord; if (json[i]["user"] is JObject obj) // Null = Missing stickers perm // { var tsr = obj.ToDiscordObject(); var usr = new DiscordUser(tsr) {Discord = this.Discord}; usr = this.Discord.UserCache.AddOrUpdate(tsr.Id, usr, (id, old) => { old.Username = usr.Username; old.Discriminator = usr.Discriminator; old.AvatarHash = usr.AvatarHash; return old; }); stkr.User = usr; } } return ret.ToList(); } /// /// Gets a guild sticker. /// - /// The guild id. - /// The sticker id. - internal async Task GetGuildStickerAsync(ulong guild_id, ulong sticker_id) + /// The guild id. + /// The sticker id. + internal async Task GetGuildStickerAsync(ulong guildId, ulong stickerId) { var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.STICKERS}/:sticker_id"; - var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new {guild_id, sticker_id}, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new {guild_id = guildId, sticker_id = stickerId}, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.GET, route).ConfigureAwait(false); var json = JObject.Parse(res.Response); var ret = json.ToDiscordObject(); if (json["user"] is not null) // Null = Missing stickers perm // { var tsr = json["user"].ToDiscordObject(); var usr = new DiscordUser(tsr) {Discord = this.Discord}; usr = this.Discord.UserCache.AddOrUpdate(tsr.Id, usr, (id, old) => { old.Username = usr.Username; old.Discriminator = usr.Discriminator; old.AvatarHash = usr.AvatarHash; return old; }); ret.User = usr; } ret.Discord = this.Discord; return ret; } /// /// Creates the guild sticker. /// - /// The guild id. + /// The guild id. /// The name. /// The description. /// The tags. /// The file. /// The reason. - internal async Task CreateGuildStickerAsync(ulong guild_id, string name, string description, string tags, DiscordMessageFile file, string reason) + internal async Task CreateGuildStickerAsync(ulong guildId, string name, string description, string tags, DiscordMessageFile file, string reason) { var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.STICKERS}"; - var bucket = this.Rest.GetBucket(RestRequestMethod.POST, route, new {guild_id}, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.POST, route, new {guild_id = guildId}, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); var headers = Utilities.GetBaseHeaders(); if (!string.IsNullOrWhiteSpace(reason)) headers.Add(REASON_HEADER_NAME, reason); var res = await this.DoStickerMultipartAsync(this.Discord, bucket, url, RestRequestMethod.POST, route, headers, file, name, tags, description); var ret = JObject.Parse(res.Response).ToDiscordObject(); ret.Discord = this.Discord; return ret; } /// /// Modifies the guild sticker. /// - /// The guild id. - /// The sticker id. + /// The guild id. + /// The sticker id. /// The name. /// The description. /// The tags. /// The reason. - internal async Task ModifyGuildStickerAsync(ulong guild_id, ulong sticker_id, Optional name, Optional description, Optional tags, string reason) + internal async Task ModifyGuildStickerAsync(ulong guildId, ulong stickerId, Optional name, Optional description, Optional tags, string reason) { var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.STICKERS}/:sticker_id"; - var bucket = this.Rest.GetBucket(RestRequestMethod.PATCH, route, new {guild_id, sticker_id}, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.PATCH, route, new {guild_id = guildId, sticker_id = stickerId}, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); var headers = Utilities.GetBaseHeaders(); if (!string.IsNullOrWhiteSpace(reason)) headers.Add(REASON_HEADER_NAME, reason); var pld = new RestStickerModifyPayload() { Name = name, Description = description, Tags = tags }; var values = new Dictionary { ["payload_json"] = DiscordJson.SerializeObject(pld) }; var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.PATCH, route); var ret = JObject.Parse(res.Response).ToDiscordObject(); ret.Discord = this.Discord; return null; } /// /// Deletes the guild sticker async. /// - /// The guild id. - /// The sticker id. + /// The guild id. + /// The sticker id. /// The reason. - internal async Task DeleteGuildStickerAsync(ulong guild_id, ulong sticker_id, string reason) + internal async Task DeleteGuildStickerAsync(ulong guildId, ulong stickerId, string reason) { var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.STICKERS}/:sticker_id"; - var bucket = this.Rest.GetBucket(RestRequestMethod.DELETE, route, new { guild_id, sticker_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.DELETE, route, new {guild_id = guildId, sticker_id = stickerId }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); var headers = Utilities.GetBaseHeaders(); if (!string.IsNullOrWhiteSpace(reason)) headers.Add(REASON_HEADER_NAME, reason); await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.DELETE, route, headers); } #endregion #region Application Commands /// /// Gets the global application commands async. /// - /// The application_id. + /// The application_id. /// A Task. - internal async Task> GetGlobalApplicationCommandsAsync(ulong application_id) + internal async Task> GetGlobalApplicationCommandsAsync(ulong applicationId) { var route = $"{Endpoints.APPLICATIONS}/:application_id{Endpoints.COMMANDS}"; - var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new { application_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new {application_id = applicationId }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.GET, route); var ret = JsonConvert.DeserializeObject>(res.Response); foreach (var app in ret) app.Discord = this.Discord; return ret.ToList(); } /// /// Bulks the overwrite global application commands async. /// - /// The application_id. + /// The application_id. /// The commands. /// A Task. - internal async Task> BulkOverwriteGlobalApplicationCommandsAsync(ulong application_id, IEnumerable commands) + internal async Task> BulkOverwriteGlobalApplicationCommandsAsync(ulong applicationId, IEnumerable commands) { var pld = new List(); foreach (var command in commands) { pld.Add(new RestApplicationCommandCreatePayload { Type = command.Type, Name = command.Name, Description = command.Description, Options = command.Options, DefaultPermission = command.DefaultPermission, NameLocalizations = command.NameLocalizations?.GetKeyValuePairs(), DescriptionLocalizations = command.DescriptionLocalizations?.GetKeyValuePairs() }); } this.Discord.Logger.LogDebug(DiscordJson.SerializeObject(pld)); var route = $"{Endpoints.APPLICATIONS}/:application_id{Endpoints.COMMANDS}"; - var bucket = this.Rest.GetBucket(RestRequestMethod.PUT, route, new { application_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.PUT, route, new {application_id = applicationId }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.PUT, route, payload: DiscordJson.SerializeObject(pld)); var ret = JsonConvert.DeserializeObject>(res.Response); foreach (var app in ret) app.Discord = this.Discord; return ret.ToList(); } /// /// Creates the global application command async. /// - /// The application_id. + /// The application_id. /// The command. /// A Task. - internal async Task CreateGlobalApplicationCommandAsync(ulong application_id, DiscordApplicationCommand command) + internal async Task CreateGlobalApplicationCommandAsync(ulong applicationId, DiscordApplicationCommand command) { var pld = new RestApplicationCommandCreatePayload { Type = command.Type, Name = command.Name, Description = command.Description, Options = command.Options, DefaultPermission = command.DefaultPermission, NameLocalizations = command.NameLocalizations.GetKeyValuePairs(), DescriptionLocalizations = command.DescriptionLocalizations.GetKeyValuePairs() }; var route = $"{Endpoints.APPLICATIONS}/:application_id{Endpoints.COMMANDS}"; - var bucket = this.Rest.GetBucket(RestRequestMethod.POST, route, new { application_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.POST, route, new {application_id = applicationId }, 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)); var ret = JsonConvert.DeserializeObject(res.Response); ret.Discord = this.Discord; return ret; } /// /// Gets the global application command async. /// - /// The application_id. - /// The command_id. + /// The application_id. + /// The command_id. /// A Task. - internal async Task GetGlobalApplicationCommandAsync(ulong application_id, ulong command_id) + internal async Task GetGlobalApplicationCommandAsync(ulong applicationId, ulong commandId) { var route = $"{Endpoints.APPLICATIONS}/:application_id{Endpoints.COMMANDS}/:command_id"; - var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new { application_id, command_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new {application_id = applicationId, command_id = commandId }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.GET, route); var ret = JsonConvert.DeserializeObject(res.Response); ret.Discord = this.Discord; return ret; } /// /// Edits the global application command async. /// - /// The application_id. - /// The command_id. + /// The application_id. + /// The command_id. /// The name. /// The description. /// The options. - /// The default_permission. - /// The localizations of the name. - /// The localizations of the description. + /// The default_permission. + /// The localizations of the name. + /// The localizations of the description. /// A Task. - internal async Task EditGlobalApplicationCommandAsync(ulong application_id, ulong command_id, Optional name, Optional description, Optional> options, Optional default_permission, Optional name_localization, Optional description_localization) + internal async Task EditGlobalApplicationCommandAsync(ulong applicationId, ulong commandId, Optional name, Optional description, Optional> options, Optional defaultPermission, Optional nameLocalization, Optional descriptionLocalization) { var pld = new RestApplicationCommandEditPayload { Name = name, Description = description, Options = options, - DefaultPermission = default_permission, - NameLocalizations = name_localization.HasValue ? name_localization.Value.GetKeyValuePairs() : null, - DescriptionLocalizations = description_localization.HasValue ? description_localization.Value.GetKeyValuePairs() : null + DefaultPermission = defaultPermission, + NameLocalizations = nameLocalization.HasValue ? nameLocalization.Value.GetKeyValuePairs() : null, + DescriptionLocalizations = descriptionLocalization.HasValue ? descriptionLocalization.Value.GetKeyValuePairs() : null }; var route = $"{Endpoints.APPLICATIONS}/:application_id{Endpoints.COMMANDS}/:command_id"; - var bucket = this.Rest.GetBucket(RestRequestMethod.PATCH, route, new { application_id, command_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.PATCH, route, new {application_id = applicationId, command_id = commandId }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.PATCH, route, payload: DiscordJson.SerializeObject(pld)); var ret = JsonConvert.DeserializeObject(res.Response); ret.Discord = this.Discord; return ret; } /// /// Deletes the global application command async. /// - /// The application_id. - /// The command_id. + /// The application_id. + /// The command_id. /// A Task. - internal async Task DeleteGlobalApplicationCommandAsync(ulong application_id, ulong command_id) + internal async Task DeleteGlobalApplicationCommandAsync(ulong applicationId, ulong commandId) { var route = $"{Endpoints.APPLICATIONS}/:application_id{Endpoints.COMMANDS}/:command_id"; - var bucket = this.Rest.GetBucket(RestRequestMethod.DELETE, route, new { application_id, command_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.DELETE, route, new {application_id = applicationId, command_id = commandId }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.DELETE, route); } /// /// Gets the guild application commands async. /// - /// The application_id. - /// The guild_id. + /// The application_id. + /// The guild_id. /// A Task. - internal async Task> GetGuildApplicationCommandsAsync(ulong application_id, ulong guild_id) + internal async Task> GetGuildApplicationCommandsAsync(ulong applicationId, ulong guildId) { var route = $"{Endpoints.APPLICATIONS}/:application_id{Endpoints.GUILDS}/:guild_id{Endpoints.COMMANDS}"; - var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new { application_id, guild_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new {application_id = applicationId, guild_id = guildId }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.GET, route); var ret = JsonConvert.DeserializeObject>(res.Response); foreach (var app in ret) app.Discord = this.Discord; return ret.ToList(); } /// /// Bulks the overwrite guild application commands async. /// - /// The application_id. - /// The guild_id. + /// The application_id. + /// The guild_id. /// The commands. /// A Task. - internal async Task> BulkOverwriteGuildApplicationCommandsAsync(ulong application_id, ulong guild_id, IEnumerable commands) + internal async Task> BulkOverwriteGuildApplicationCommandsAsync(ulong applicationId, ulong guildId, IEnumerable commands) { var pld = new List(); foreach (var command in commands) { pld.Add(new RestApplicationCommandCreatePayload { Type = command.Type, Name = command.Name, Description = command.Description, Options = command.Options, DefaultPermission = command.DefaultPermission, NameLocalizations = command.NameLocalizations?.GetKeyValuePairs(), DescriptionLocalizations = command.DescriptionLocalizations?.GetKeyValuePairs() }); } this.Discord.Logger.LogDebug(DiscordJson.SerializeObject(pld)); var route = $"{Endpoints.APPLICATIONS}/:application_id{Endpoints.GUILDS}/:guild_id{Endpoints.COMMANDS}"; - var bucket = this.Rest.GetBucket(RestRequestMethod.PUT, route, new { application_id, guild_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.PUT, route, new {application_id = applicationId, guild_id = guildId }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.PUT, route, payload: DiscordJson.SerializeObject(pld)); var ret = JsonConvert.DeserializeObject>(res.Response); foreach (var app in ret) app.Discord = this.Discord; return ret.ToList(); } /// /// Creates the guild application command async. /// - /// The application_id. - /// The guild_id. + /// The application_id. + /// The guild_id. /// The command. /// A Task. - internal async Task CreateGuildApplicationCommandAsync(ulong application_id, ulong guild_id, DiscordApplicationCommand command) + internal async Task CreateGuildApplicationCommandAsync(ulong applicationId, ulong guildId, DiscordApplicationCommand command) { var pld = new RestApplicationCommandCreatePayload { Type = command.Type, Name = command.Name, Description = command.Description, Options = command.Options, DefaultPermission = command.DefaultPermission, NameLocalizations = command.NameLocalizations.GetKeyValuePairs(), DescriptionLocalizations = command.DescriptionLocalizations.GetKeyValuePairs() }; var route = $"{Endpoints.APPLICATIONS}/:application_id{Endpoints.GUILDS}/:guild_id{Endpoints.COMMANDS}"; - var bucket = this.Rest.GetBucket(RestRequestMethod.POST, route, new { application_id, guild_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.POST, route, new {application_id = applicationId, guild_id = guildId }, 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)); var ret = JsonConvert.DeserializeObject(res.Response); ret.Discord = this.Discord; return ret; } /// /// Gets the guild application command async. /// - /// The application_id. - /// The guild_id. - /// The command_id. - internal async Task GetGuildApplicationCommandAsync(ulong application_id, ulong guild_id, ulong command_id) + /// The application_id. + /// The guild_id. + /// The command_id. + internal async Task GetGuildApplicationCommandAsync(ulong applicationId, ulong guildId, ulong commandId) { var route = $"{Endpoints.APPLICATIONS}/:application_id{Endpoints.GUILDS}/:guild_id{Endpoints.COMMANDS}/:command_id"; - var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new { application_id, guild_id, command_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new {application_id = applicationId, guild_id = guildId, command_id = commandId }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.GET, route); var ret = JsonConvert.DeserializeObject(res.Response); ret.Discord = this.Discord; return ret; } /// /// Edits the guild application command async. /// - /// The application_id. - /// The guild_id. - /// The command_id. + /// The application_id. + /// The guild_id. + /// The command_id. /// The name. /// The description. /// The options. - /// The default_permission. - /// The localizations of the name. - /// The localizations of the description. - internal async Task EditGuildApplicationCommandAsync(ulong application_id, ulong guild_id, ulong command_id, Optional name, Optional description, Optional> options, Optional default_permission, Optional name_localization, Optional description_localization) + /// The default_permission. + /// The localizations of the name. + /// The localizations of the description. + internal async Task EditGuildApplicationCommandAsync(ulong applicationId, ulong guildId, ulong commandId, Optional name, Optional description, Optional> options, Optional defaultPermission, Optional nameLocalization, Optional descriptionLocalization) { var pld = new RestApplicationCommandEditPayload { Name = name, Description = description, Options = options, - DefaultPermission = default_permission, - NameLocalizations = name_localization.HasValue ? name_localization.Value.GetKeyValuePairs() : null, - DescriptionLocalizations = description_localization.HasValue ? description_localization.Value.GetKeyValuePairs() : null + DefaultPermission = defaultPermission, + NameLocalizations = nameLocalization.HasValue ? nameLocalization.Value.GetKeyValuePairs() : null, + DescriptionLocalizations = descriptionLocalization.HasValue ? descriptionLocalization.Value.GetKeyValuePairs() : null }; var route = $"{Endpoints.APPLICATIONS}/:application_id{Endpoints.GUILDS}/:guild_id{Endpoints.COMMANDS}/:command_id"; - var bucket = this.Rest.GetBucket(RestRequestMethod.PATCH, route, new { application_id, guild_id, command_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.PATCH, route, new {application_id = applicationId, guild_id = guildId, command_id = commandId }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.PATCH, route, payload: DiscordJson.SerializeObject(pld)); var ret = JsonConvert.DeserializeObject(res.Response); ret.Discord = this.Discord; return ret; } /// /// Deletes the guild application command async. /// - /// The application_id. - /// The guild_id. - /// The command_id. - internal async Task DeleteGuildApplicationCommandAsync(ulong application_id, ulong guild_id, ulong command_id) + /// The application_id. + /// The guild_id. + /// The command_id. + internal async Task DeleteGuildApplicationCommandAsync(ulong applicationId, ulong guildId, ulong commandId) { var route = $"{Endpoints.APPLICATIONS}/:application_id{Endpoints.GUILDS}/:guild_id{Endpoints.COMMANDS}/:command_id"; - var bucket = this.Rest.GetBucket(RestRequestMethod.DELETE, route, new { application_id, guild_id, command_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.DELETE, route, new {application_id = applicationId, guild_id = guildId, command_id = commandId }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.DELETE, route); } /// /// Gets the guild application command permissions. /// - /// The target application id. - /// The target guild id. - internal async Task> GetGuildApplicationCommandPermissionsAsync(ulong application_id, ulong guild_id) + /// The target application id. + /// The target guild id. + internal async Task> GetGuildApplicationCommandPermissionsAsync(ulong applicationId, ulong guildId) { var route = $"{Endpoints.APPLICATIONS}/:application_id{Endpoints.GUILDS}/:guild_id{Endpoints.COMMANDS}{Endpoints.PERMISSIONS}"; - var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new { application_id, guild_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new {application_id = applicationId, guild_id = guildId }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.GET, route); var ret = JsonConvert.DeserializeObject>(res.Response); foreach (var app in ret) app.Discord = this.Discord; return ret.ToList(); } /// /// Gets the application command permission. /// - /// The target application id. - /// The target guild id. - /// The target command id. - internal async Task GetApplicationCommandPermissionAsync(ulong application_id, ulong guild_id, ulong command_id) + /// The target application id. + /// The target guild id. + /// The target command id. + internal async Task GetApplicationCommandPermissionAsync(ulong applicationId, ulong guildId, ulong commandId) { var route = $"{Endpoints.APPLICATIONS}/:application_id{Endpoints.GUILDS}/:guild_id{Endpoints.COMMANDS}/:command_id{Endpoints.PERMISSIONS}"; - var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new { application_id, guild_id, command_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new {application_id = applicationId, guild_id = guildId, command_id = commandId }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.GET, route); var ret = JsonConvert.DeserializeObject(res.Response); ret.Discord = this.Discord; return ret; } /// /// Overwrites the guild application command permissions. /// - /// The target application id. - /// The target guild id. - /// The target command id. + /// The target application id. + /// The target guild id. + /// The target command id. /// Array of permissions. - internal async Task OverwriteGuildApplicationCommandPermissionsAsync(ulong application_id, ulong guild_id, ulong command_id, IEnumerable permissions) + internal async Task OverwriteGuildApplicationCommandPermissionsAsync(ulong applicationId, ulong guildId, ulong commandId, IEnumerable permissions) { var route = $"{Endpoints.APPLICATIONS}/:application_id{Endpoints.GUILDS}/:guild_id{Endpoints.COMMANDS}/:command_id{Endpoints.PERMISSIONS}"; - var bucket = this.Rest.GetBucket(RestRequestMethod.PUT, route, new { application_id, guild_id, command_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.PUT, route, new {application_id = applicationId, guild_id = guildId, command_id = commandId }, out var path); if (permissions.ToArray().Length > 10) throw new NotSupportedException("You can add only up to 10 permission overwrites per command."); var pld = new RestApplicationCommandPermissionEditPayload { Permissions = permissions }; var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.PUT, route, payload: DiscordJson.SerializeObject(pld)); var ret = JsonConvert.DeserializeObject(res.Response); ret.Discord = this.Discord; return ret; } /// /// Bulks overwrite the application command permissions. /// - /// The target application id. - /// The target guild id. - /// - internal async Task> BulkOverwriteApplicationCommandPermissionsAsync(ulong application_id, ulong guild_id, IEnumerable permission_overwrites) + /// The target application id. + /// The target guild id. + /// + internal async Task> BulkOverwriteApplicationCommandPermissionsAsync(ulong applicationId, ulong guildId, IEnumerable permissionOverwrites) { var route = $"{Endpoints.APPLICATIONS}/:application_id{Endpoints.GUILDS}/:guild_id{Endpoints.COMMANDS}{Endpoints.PERMISSIONS}"; - var bucket = this.Rest.GetBucket(RestRequestMethod.PUT, route, new { application_id, guild_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.PUT, route, new {application_id = applicationId, guild_id = guildId }, out var path); var pld = new List(); - foreach (var overwrite in permission_overwrites) + foreach (var overwrite in permissionOverwrites) { if (overwrite.Permissions.Count > 10) throw new NotSupportedException("You can add only up to 10 permission overwrites per command."); pld.Add(new RestGuildApplicationCommandPermissionEditPayload { CommandId = overwrite.Id, Permissions = overwrite.Permissions }); } var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.PUT, route, payload: DiscordJson.SerializeObject(pld.ToArray())); var ret = JsonConvert.DeserializeObject>(res.Response); foreach (var app in ret) app.Discord = this.Discord; return ret.ToList(); } /// /// Creates the interaction response async. /// - /// The interaction_id. - /// The interaction_token. + /// The interaction_id. + /// The interaction_token. /// The type. /// The builder. /// A Task. - internal async Task CreateInteractionResponseAsync(ulong interaction_id, string interaction_token, InteractionResponseType type, DiscordInteractionResponseBuilder builder) + internal async Task CreateInteractionResponseAsync(ulong interactionId, string interactionToken, InteractionResponseType type, DiscordInteractionResponseBuilder builder) { if (builder?.Embeds != null) foreach (var embed in builder.Embeds) if (embed.Timestamp != null) embed.Timestamp = embed.Timestamp.Value.ToUniversalTime(); RestInteractionResponsePayload pld; if (type != InteractionResponseType.AutoCompleteResult) { var data = builder != null ? new DiscordInteractionApplicationCommandCallbackData { Content = builder.Content ?? null, Embeds = builder.Embeds ?? null, - IsTTS = builder.IsTTS, + IsTts = builder.IsTts, Mentions = builder.Mentions ?? null, Flags = builder.IsEphemeral ? MessageFlags.Ephemeral : null, Components = builder.Components ?? null, Choices = null } : null; pld = new RestInteractionResponsePayload { Type = type, Data = data }; if (builder != null && builder.Files != null && builder.Files.Count > 0) { - ulong file_id = 0; + ulong fileId = 0; List attachments = new(); foreach (var file in builder.Files) { DiscordAttachment att = new() { - Id = file_id, + Id = fileId, Discord = this.Discord, Description = file.Description, FileName = file.FileName, FileSize = null }; attachments.Add(att); - file_id++; + fileId++; } pld.Attachments = attachments; pld.Data.Attachments = attachments; } } else { pld = new RestInteractionResponsePayload { Type = type, Data = new DiscordInteractionApplicationCommandCallbackData { Content = null, Embeds = null, - IsTTS = null, + IsTts = null, Mentions = null, Flags = null, Components = null, Choices = builder.Choices, Attachments = null }, Attachments = null }; } var values = new Dictionary(); if (builder != null) - if (!string.IsNullOrEmpty(builder.Content) || builder.Embeds?.Count() > 0 || builder.IsTTS == true || builder.Mentions != null || builder.Files?.Count > 0) + if (!string.IsNullOrEmpty(builder.Content) || builder.Embeds?.Count() > 0 || builder.IsTts == true || builder.Mentions != null || builder.Files?.Count > 0) values["payload_json"] = DiscordJson.SerializeObject(pld); var route = $"{Endpoints.INTERACTIONS}/:interaction_id/:interaction_token{Endpoints.CALLBACK}"; - var bucket = this.Rest.GetBucket(RestRequestMethod.POST, route, new { interaction_id, interaction_token }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.POST, route, new {interaction_id = interactionId, interaction_token = interactionToken }, out var path); var url = Utilities.GetApiUriBuilderFor(path, this.Discord.Configuration).AddParameter("wait", "false").Build(); if (builder != null) { await this.DoMultipartAsync(this.Discord, bucket, url, RestRequestMethod.POST, route, values: values, files: builder.Files); foreach (var file in builder.Files.Where(x => x.ResetPositionTo.HasValue)) { file.Stream.Position = file.ResetPositionTo.Value; } } else { await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.POST, route, payload: DiscordJson.SerializeObject(pld)); } } /// /// Creates the interaction response async. /// - /// The interaction_id. - /// The interaction_token. + /// The interaction_id. + /// The interaction_token. /// The type. /// The builder. /// A Task. - internal async Task CreateInteractionModalResponseAsync(ulong interaction_id, string interaction_token, InteractionResponseType type, DiscordInteractionModalBuilder builder) + internal async Task CreateInteractionModalResponseAsync(ulong interactionId, string interactionToken, InteractionResponseType type, DiscordInteractionModalBuilder builder) { if (builder.ModalComponents.Any(mc => mc.Components.Any(c => c.Type != Enums.ComponentType.InputText))) throw new NotSupportedException("Can't send any other type then Input Text as Modal Component."); var pld = new RestInteractionModalResponsePayload { Type = type, Data = new DiscordInteractionApplicationCommandModalCallbackData { Title = builder.Title, CustomId = builder.CustomId, ModalComponents = builder.ModalComponents } }; var values = new Dictionary(); var route = $"{Endpoints.INTERACTIONS}/:interaction_id/:interaction_token{Endpoints.CALLBACK}"; - var bucket = this.Rest.GetBucket(RestRequestMethod.POST, route, new { interaction_id, interaction_token }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.POST, route, new {interaction_id = interactionId, interaction_token = interactionToken }, out var path); var url = Utilities.GetApiUriBuilderFor(path, this.Discord.Configuration).AddParameter("wait", "true").Build(); await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.POST, route, payload: DiscordJson.SerializeObject(pld)); } /// /// Gets the original interaction response async. /// - /// The application_id. - /// The interaction_token. + /// The application_id. + /// The interaction_token. /// A Task. - internal Task GetOriginalInteractionResponseAsync(ulong application_id, string interaction_token) => - this.GetWebhookMessageAsync(application_id, interaction_token, Endpoints.ORIGINAL, null); + internal Task GetOriginalInteractionResponseAsync(ulong applicationId, string interactionToken) => + this.GetWebhookMessageAsync(applicationId, interactionToken, Endpoints.ORIGINAL, null); /// /// Edits the original interaction response async. /// - /// The application_id. - /// The interaction_token. + /// The application_id. + /// The interaction_token. /// The builder. /// A Task. - internal Task EditOriginalInteractionResponseAsync(ulong application_id, string interaction_token, DiscordWebhookBuilder builder) => - this.EditWebhookMessageAsync(application_id, interaction_token, Endpoints.ORIGINAL, builder, null); + internal Task EditOriginalInteractionResponseAsync(ulong applicationId, string interactionToken, DiscordWebhookBuilder builder) => + this.EditWebhookMessageAsync(applicationId, interactionToken, Endpoints.ORIGINAL, builder, null); /// /// Deletes the original interaction response async. /// - /// The application_id. - /// The interaction_token. + /// The application_id. + /// The interaction_token. /// A Task. - internal Task DeleteOriginalInteractionResponseAsync(ulong application_id, string interaction_token) => - this.DeleteWebhookMessageAsync(application_id, interaction_token, Endpoints.ORIGINAL, null); + internal Task DeleteOriginalInteractionResponseAsync(ulong applicationId, string interactionToken) => + this.DeleteWebhookMessageAsync(applicationId, interactionToken, Endpoints.ORIGINAL, null); /// /// Creates the followup message async. /// - /// The application_id. - /// The interaction_token. + /// The application_id. + /// The interaction_token. /// The builder. /// A Task. - internal async Task CreateFollowupMessageAsync(ulong application_id, string interaction_token, DiscordFollowupMessageBuilder builder) + internal async Task CreateFollowupMessageAsync(ulong applicationId, string interactionToken, DiscordFollowupMessageBuilder builder) { builder.Validate(); if (builder.Embeds != null) foreach (var embed in builder.Embeds) if (embed.Timestamp != null) embed.Timestamp = embed.Timestamp.Value.ToUniversalTime(); var values = new Dictionary(); var pld = new RestFollowupMessageCreatePayload { Content = builder.Content, - IsTTS = builder.IsTTS, + IsTts = builder.IsTts, Embeds = builder.Embeds, Flags = builder.Flags, Components = builder.Components }; if (builder.Files != null && builder.Files.Count > 0) { - ulong file_id = 0; + ulong fileId = 0; List attachments = new(); foreach (var file in builder.Files) { DiscordAttachment att = new() { - Id = file_id, + Id = fileId, Discord = this.Discord, Description = file.Description, FileName = file.FileName, FileSize = null }; attachments.Add(att); - file_id++; + fileId++; } pld.Attachments = attachments; } if (builder.Mentions != null) pld.Mentions = new DiscordMentions(builder.Mentions, builder.Mentions.Any()); - if (!string.IsNullOrEmpty(builder.Content) || builder.Embeds?.Count() > 0 || builder.IsTTS == true || builder.Mentions != null || builder.Files?.Count > 0) + if (!string.IsNullOrEmpty(builder.Content) || builder.Embeds?.Count() > 0 || builder.IsTts == true || builder.Mentions != null || builder.Files?.Count > 0) values["payload_json"] = DiscordJson.SerializeObject(pld); var route = $"{Endpoints.WEBHOOKS}/:application_id/:interaction_token"; - var bucket = this.Rest.GetBucket(RestRequestMethod.POST, route, new { application_id, interaction_token }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.POST, route, new {application_id = applicationId, interaction_token = interactionToken }, out var path); var url = Utilities.GetApiUriBuilderFor(path, this.Discord.Configuration).AddParameter("wait", "true").Build(); var res = await this.DoMultipartAsync(this.Discord, bucket, url, RestRequestMethod.POST, route, values: values, files: builder.Files).ConfigureAwait(false); var ret = JsonConvert.DeserializeObject(res.Response); - foreach (var att in ret._attachments) + foreach (var att in ret.AttachmentsInternal) { att.Discord = this.Discord; } foreach (var file in builder.Files.Where(x => x.ResetPositionTo.HasValue)) { file.Stream.Position = file.ResetPositionTo.Value; } ret.Discord = this.Discord; return ret; } /// /// Gets the followup message async. /// - /// The application_id. - /// The interaction_token. - /// The message_id. + /// The application_id. + /// The interaction_token. + /// The message_id. /// A Task. - internal Task GetFollowupMessageAsync(ulong application_id, string interaction_token, ulong message_id) => - this.GetWebhookMessageAsync(application_id, interaction_token, message_id); + internal Task GetFollowupMessageAsync(ulong applicationId, string interactionToken, ulong messageId) => + this.GetWebhookMessageAsync(applicationId, interactionToken, messageId); /// /// Edits the followup message async. /// - /// The application_id. - /// The interaction_token. - /// The message_id. + /// The application_id. + /// The interaction_token. + /// The message_id. /// The builder. /// A Task. - internal Task EditFollowupMessageAsync(ulong application_id, string interaction_token, ulong message_id, DiscordWebhookBuilder builder) => - this.EditWebhookMessageAsync(application_id, interaction_token, message_id.ToString(), builder, null); + internal Task EditFollowupMessageAsync(ulong applicationId, string interactionToken, ulong messageId, DiscordWebhookBuilder builder) => + this.EditWebhookMessageAsync(applicationId, interactionToken, messageId.ToString(), builder, null); /// /// Deletes the followup message async. /// - /// The application_id. - /// The interaction_token. - /// The message_id. + /// The application_id. + /// The interaction_token. + /// The message_id. /// A Task. - internal Task DeleteFollowupMessageAsync(ulong application_id, string interaction_token, ulong message_id) => - this.DeleteWebhookMessageAsync(application_id, interaction_token, message_id); + internal Task DeleteFollowupMessageAsync(ulong applicationId, string interactionToken, ulong messageId) => + this.DeleteWebhookMessageAsync(applicationId, interactionToken, messageId); #endregion #region Misc /// /// Gets the current application info async. /// /// A Task. internal Task GetCurrentApplicationInfoAsync() => this.GetApplicationInfoAsync("@me"); /// /// Gets the application info async. /// - /// The application_id. + /// The application_id. /// A Task. - internal Task GetApplicationInfoAsync(ulong application_id) - => this.GetApplicationInfoAsync(application_id.ToString(CultureInfo.InvariantCulture)); + internal Task GetApplicationInfoAsync(ulong applicationId) + => this.GetApplicationInfoAsync(applicationId.ToString(CultureInfo.InvariantCulture)); /// /// Gets the application info async. /// - /// The application_id. + /// The application_id. /// A Task. - private async Task GetApplicationInfoAsync(string application_id) + private async Task GetApplicationInfoAsync(string applicationId) { var route = $"{Endpoints.OAUTH2}{Endpoints.APPLICATIONS}/:application_id"; - var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new { application_id }, out var path); + var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new {application_id = applicationId }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.GET, route).ConfigureAwait(false); return JsonConvert.DeserializeObject(res.Response); } /// /// Gets the application assets async. /// /// The application. /// A Task. internal async Task> GetApplicationAssetsAsync(DiscordApplication application) { var route = $"{Endpoints.OAUTH2}{Endpoints.APPLICATIONS}/:application_id{Endpoints.ASSETS}"; var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new { application_id = application.Id }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.GET, route).ConfigureAwait(false); var assets = JsonConvert.DeserializeObject>(res.Response); foreach (var asset in assets) { asset.Discord = application.Discord; asset.Application = application; } return new ReadOnlyCollection(new List(assets)); } /// /// Gets the gateway info async. /// /// A Task. internal async Task GetGatewayInfoAsync() { var headers = Utilities.GetBaseHeaders(); var route = Endpoints.GATEWAY; if (this.Discord.Configuration.TokenType == TokenType.Bot) route += Endpoints.BOT; var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new { }, out var path); var url = Utilities.GetApiUriFor(path, this.Discord.Configuration); var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.GET, route, headers).ConfigureAwait(false); var info = JObject.Parse(res.Response).ToObject(); - info.SessionBucket.ResetAfter = DateTimeOffset.UtcNow + TimeSpan.FromMilliseconds(info.SessionBucket.resetAfter); + info.SessionBucket.ResetAfter = DateTimeOffset.UtcNow + TimeSpan.FromMilliseconds(info.SessionBucket.ResetAfterInternal); return info; } #endregion #region DCS Internals /// /// Gets the DisCatSharp team. /// > internal async Task GetDisCatSharpTeamAsync() { try { var wc = new WebClient(); var dcs = await wc.DownloadStringTaskAsync(new Uri("https://dcs.aitsys.dev/api/devs/")); - var dcs_guild = await wc.DownloadStringTaskAsync(new Uri("https://dcs.aitsys.dev/api/guild/")); + var dcsGuild = await wc.DownloadStringTaskAsync(new Uri("https://dcs.aitsys.dev/api/guild/")); var app = JsonConvert.DeserializeObject(dcs); - var guild = JsonConvert.DeserializeObject(dcs_guild); + var guild = JsonConvert.DeserializeObject(dcsGuild); var dcst = new DisCatSharpTeam { IconHash = app.Team.IconHash, TeamName = app.Team.Name, PrivacyPolicyUrl = app.PrivacyPolicyUrl, TermsOfServiceUrl = app.TermsOfServiceUrl, RepoUrl = "https://github.com/Aiko-IT-Systems/DisCatSharp", DocsUrl = "https://docs.dcs.aitsys.dev", Id = app.Team.Id, BannerHash = guild.BannerHash, LogoHash = guild.IconHash, GuildId = guild.Id, Guild = guild, SupportInvite = await this.GetInviteAsync("discatsharp", true, true, null) }; List team = new(); DisCatSharpTeamMember owner = new(); foreach (var mb in app.Team.Members.OrderBy(m => m.User.Username)) { var tuser = await this.GetUserAsync(mb.User.Id); var user = mb.User; if (mb.User.Id == 856780995629154305) { owner.Id = user.Id; owner.Username = user.Username; owner.Discriminator = user.Discriminator; owner.AvatarHash = user.AvatarHash; owner.BannerHash = tuser.BannerHash; - owner._bannerColor = tuser._bannerColor; + owner.BannerColorInternal = tuser.BannerColorInternal; team.Add(owner); } else { team.Add(new() { Id = user.Id, Username = user.Username, Discriminator = user.Discriminator, AvatarHash = user.AvatarHash, BannerHash = tuser.BannerHash, - _bannerColor = tuser._bannerColor + BannerColorInternal = tuser.BannerColorInternal }); } } dcst.Owner = owner; dcst.Developers = team; return dcst; } catch (Exception ex) { this.Discord.Logger.LogDebug(ex.Message); this.Discord.Logger.LogDebug(ex.StackTrace); return null; } } #endregion } } diff --git a/DisCatSharp/Net/Rest/MultipartWebRequest.cs b/DisCatSharp/Net/Rest/MultipartWebRequest.cs index a5066a5cc..456e95e97 100644 --- a/DisCatSharp/Net/Rest/MultipartWebRequest.cs +++ b/DisCatSharp/Net/Rest/MultipartWebRequest.cs @@ -1,124 +1,124 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System; using System.Collections.Generic; using System.IO; using System.Linq; using DisCatSharp.Entities; namespace DisCatSharp.Net { /// /// Represents a multipart HTTP request. /// internal sealed class MultipartWebRequest : BaseRestRequest { /// /// Gets the dictionary of values attached to this request. /// public IReadOnlyDictionary Values { get; } /// /// Gets the dictionary of files attached to this request. /// public IReadOnlyDictionary Files { get; } /// /// Overwrites the file id start. /// public int? OverwriteFileIdStart { get; } /// /// Initializes a new instance of the class. /// /// The client. /// The bucket. /// The url. /// The method. /// The route. /// The headers. /// The values. /// The files. /// The ratelimit_wait_override. /// The file id start. internal MultipartWebRequest(BaseDiscordClient client, RateLimitBucket bucket, Uri url, RestRequestMethod method, string route, IReadOnlyDictionary headers = null, IReadOnlyDictionary values = null, - IReadOnlyCollection files = null, double? ratelimit_wait_override = null, int? overwrite_file_id_start = null) - : base(client, bucket, url, method, route, headers, ratelimit_wait_override) + IReadOnlyCollection files = null, double? ratelimitWaitOverride = null, int? overwriteFileIdStart = null) + : base(client, bucket, url, method, route, headers, ratelimitWaitOverride) { this.Values = values; - this.OverwriteFileIdStart = overwrite_file_id_start; + this.OverwriteFileIdStart = overwriteFileIdStart; this.Files = files.ToDictionary(x => x.FileName, x => x.Stream); } } /// /// Represents a multipart HTTP request for stickers. /// internal sealed class MultipartStickerWebRequest : BaseRestRequest { /// /// Gets the file. /// public DiscordMessageFile File { get; } /// /// Gets the name. /// public string Name { get; } /// /// Gets the description. /// public string Description { get; } /// /// Gets the tags. /// public string Tags { get; } /// /// Initializes a new instance of the class. /// /// 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. internal MultipartStickerWebRequest(BaseDiscordClient client, RateLimitBucket bucket, Uri url, RestRequestMethod method, string route, IReadOnlyDictionary headers = null, - DiscordMessageFile file = null, string name = "", string tags = "", string description = "", double? ratelimit_wait_override = null) - : base(client, bucket, url, method, route, headers, ratelimit_wait_override) + DiscordMessageFile file = null, string name = "", string tags = "", string description = "", double? ratelimitWaitOverride = null) + : base(client, bucket, url, method, route, headers, ratelimitWaitOverride) { this.File = file; this.Name = name; this.Description = description; this.Tags = tags; } } } diff --git a/DisCatSharp/Net/Rest/RateLimitBucket.cs b/DisCatSharp/Net/Rest/RateLimitBucket.cs index 8693061c2..ec4dff4ba 100644 --- a/DisCatSharp/Net/Rest/RateLimitBucket.cs +++ b/DisCatSharp/Net/Rest/RateLimitBucket.cs @@ -1,291 +1,291 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System; using System.Collections.Concurrent; using System.Threading; using System.Threading.Tasks; namespace DisCatSharp.Net { /// /// Represents a rate limit bucket. /// internal class RateLimitBucket : IEquatable { /// /// Gets the Id of the guild bucket. /// public string GuildId { get; internal set; } /// /// Gets the Id of the channel bucket. /// public string ChannelId { get; internal set; } /// /// Gets the ID of the webhook bucket. /// public string WebhookId { get; internal set; } /// /// Gets the Id of the ratelimit bucket. /// public volatile string BucketId; /// /// Gets or sets the ratelimit hash of this bucket. /// public string Hash { - get => Volatile.Read(ref this._hash); + get => Volatile.Read(ref this.HashInternal); internal set { - this._isUnlimited = value.Contains(_unlimitedHash); + this.IsUnlimited = value.Contains(_unlimitedHash); if (this.BucketId != null && !this.BucketId.StartsWith(value)) { var id = GenerateBucketId(value, this.GuildId, this.ChannelId, this.WebhookId); this.BucketId = id; this.RouteHashes.Add(id); } - Volatile.Write(ref this._hash, value); + Volatile.Write(ref this.HashInternal, value); } } - internal string _hash; + internal string HashInternal; /// /// Gets the past route hashes associated with this bucket. /// public ConcurrentBag RouteHashes { get; } /// /// Gets when this bucket was last called in a request. /// public DateTimeOffset LastAttemptAt { get; internal set; } /// /// Gets the number of uses left before pre-emptive rate limit is triggered. /// public int Remaining - => this._remaining; + => this.RemainingInternal; /// /// Gets the maximum number of uses within a single bucket. /// public int Maximum { get; set; } /// /// Gets the timestamp at which the rate limit resets. /// public DateTimeOffset Reset { get; internal set; } /// /// Gets the time interval to wait before the rate limit resets. /// public TimeSpan? ResetAfter { get; internal set; } = null; /// /// Gets a value indicating whether the ratelimit global. /// public bool IsGlobal { get; internal set; } = false; /// /// Gets the ratelimit scope. /// public string Scope { get; internal set; } = "user"; /// /// Gets the time interval to wait before the rate limit resets as offset /// internal DateTimeOffset ResetAfterOffset { get; set; } - internal volatile int _remaining; + internal volatile int RemainingInternal; /// /// Gets whether this bucket has it's ratelimit determined. /// This will be if the ratelimit is determined. /// - internal volatile bool _isUnlimited; + internal volatile bool IsUnlimited; /// /// If the initial request for this bucket that is deterternining the rate limits is currently executing /// This is a int because booleans can't be accessed atomically /// 0 => False, all other values => True /// - internal volatile int _limitTesting; + internal volatile int LimitTesting; /// /// Task to wait for the rate limit test to finish /// - internal volatile Task _limitTestFinished; + internal volatile Task LimitTestFinished; /// /// If the rate limits have been determined /// - internal volatile bool _limitValid; + internal volatile bool LimitValid; /// /// Rate limit reset in ticks, UTC on the next response after the rate limit has been reset /// - internal long _nextReset; + internal long NextReset; /// /// If the rate limit is currently being reset. /// This is a int because booleans can't be accessed atomically. /// 0 => False, all other values => True /// - internal volatile int _limitResetting; + internal volatile int LimitResetting; private static readonly string _unlimitedHash = "unlimited"; /// /// Initializes a new instance of the class. /// /// The hash. - /// The guild_id. - /// The channel_id. - /// The webhook_id. - internal RateLimitBucket(string hash, string guild_id, string channel_id, string webhook_id) + /// The guild_id. + /// The channel_id. + /// The webhook_id. + internal RateLimitBucket(string hash, string guildId, string channelId, string webhookId) { this.Hash = hash; - this.ChannelId = channel_id; - this.GuildId = guild_id; - this.WebhookId = webhook_id; + this.ChannelId = channelId; + this.GuildId = guildId; + this.WebhookId = webhookId; - this.BucketId = GenerateBucketId(hash, guild_id, channel_id, webhook_id); + this.BucketId = GenerateBucketId(hash, guildId, channelId, webhookId); this.RouteHashes = new ConcurrentBag(); } /// /// Generates an ID for this request bucket. /// /// Hash for this bucket. - /// Guild Id for this bucket. - /// Channel Id for this bucket. - /// Webhook Id for this bucket. + /// Guild Id for this bucket. + /// Channel Id for this bucket. + /// Webhook Id for this bucket. /// Bucket Id. - public static string GenerateBucketId(string hash, string guild_id, string channel_id, string webhook_id) - => $"{hash}:{guild_id}:{channel_id}:{webhook_id}"; + public static string GenerateBucketId(string hash, string guildId, string channelId, string webhookId) + => $"{hash}:{guildId}:{channelId}:{webhookId}"; /// /// Generates the hash key. /// /// The method. /// The route. /// A string. public static string GenerateHashKey(RestRequestMethod method, string route) => $"{method}:{route}"; /// /// Generates the unlimited hash. /// /// The method. /// The route. /// A string. public static string GenerateUnlimitedHash(RestRequestMethod method, string route) => $"{GenerateHashKey(method, route)}:{_unlimitedHash}"; /// /// Returns a string representation of this bucket. /// /// String representation of this bucket. public override string ToString() { var guildId = this.GuildId != string.Empty ? this.GuildId : "guild_id"; var channelId = this.ChannelId != string.Empty ? this.ChannelId : "channel_id"; var webhookId = this.WebhookId != string.Empty ? this.WebhookId : "webhook_id"; return $"{this.Scope} rate limit bucket [{this.Hash}:{guildId}:{channelId}:{webhookId}] [{this.Remaining}/{this.Maximum}] {(this.ResetAfter.HasValue ? this.ResetAfterOffset : this.Reset)}"; } /// /// 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 RateLimitBucket); /// /// Checks whether this is equal to another . /// /// to compare to. /// Whether the is equal to this . public bool Equals(RateLimitBucket e) => e is not null && (ReferenceEquals(this, e) || this.BucketId == e.BucketId); /// /// Gets the hash code for this . /// /// The hash code for this . public override int GetHashCode() => this.BucketId.GetHashCode(); /// /// Sets remaining number of requests to the maximum when the ratelimit is reset /// /// internal async Task TryResetLimitAsync(DateTimeOffset now) { if (this.ResetAfter.HasValue) this.ResetAfter = this.ResetAfterOffset - now; - if (this._nextReset == 0) + if (this.NextReset == 0) return; - if (this._nextReset > now.UtcTicks) + if (this.NextReset > now.UtcTicks) return; - while (Interlocked.CompareExchange(ref this._limitResetting, 1, 0) != 0) + while (Interlocked.CompareExchange(ref this.LimitResetting, 1, 0) != 0) #pragma warning restore 420 await Task.Yield(); - if (this._nextReset != 0) + if (this.NextReset != 0) { - this._remaining = this.Maximum; - this._nextReset = 0; + this.RemainingInternal = this.Maximum; + this.NextReset = 0; } - this._limitResetting = 0; + this.LimitResetting = 0; } /// /// Sets the initial values. /// /// The max. /// The uses left. /// The new reset. internal void SetInitialValues(int max, int usesLeft, DateTimeOffset newReset) { this.Maximum = max; - this._remaining = usesLeft; - this._nextReset = newReset.UtcTicks; + this.RemainingInternal = usesLeft; + this.NextReset = newReset.UtcTicks; - this._limitValid = true; - this._limitTestFinished = null; - this._limitTesting = 0; + this.LimitValid = true; + this.LimitTestFinished = null; + this.LimitTesting = 0; } } } diff --git a/DisCatSharp/Net/Rest/RestClient.cs b/DisCatSharp/Net/Rest/RestClient.cs index c4d910855..47ea711b9 100644 --- a/DisCatSharp/Net/Rest/RestClient.cs +++ b/DisCatSharp/Net/Rest/RestClient.cs @@ -1,860 +1,860 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Net; using System.Net.Http; using System.Net.Http.Headers; using System.Reflection; using System.Text; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using DisCatSharp.Exceptions; using Microsoft.Extensions.Logging; namespace DisCatSharp.Net { /// /// Represents a client used to make REST requests. /// internal sealed class RestClient : IDisposable { /// /// Gets the route argument regex. /// - private static Regex RouteArgumentRegex { get; } = new Regex(@":([a-z_]+)"); + private static Regex s_routeArgumentRegex { get; } = new Regex(@":([a-z_]+)"); /// /// Gets the http client. /// internal HttpClient HttpClient { get; } /// /// Gets the discord client. /// private BaseDiscordClient Discord { get; } /// /// Gets a value indicating whether debug is enabled. /// internal bool Debug { get; set; } /// /// Gets the logger. /// private ILogger Logger { get; } /// /// Gets the routes to hashes. /// private ConcurrentDictionary RoutesToHashes { get; } /// /// Gets the hashes to buckets. /// private ConcurrentDictionary HashesToBuckets { get; } /// /// Gets the request queue. /// private ConcurrentDictionary RequestQueue { get; } /// /// Gets the global rate limit event. /// private AsyncManualResetEvent GlobalRateLimitEvent { get; } /// /// Gets a value indicating whether use reset after. /// private bool UseResetAfter { get; } private CancellationTokenSource _bucketCleanerTokenSource; private readonly TimeSpan _bucketCleanupDelay = TimeSpan.FromSeconds(60); private volatile bool _cleanerRunning; private Task _cleanerTask; private volatile bool _disposed; /// /// Initializes a new instance of the class. /// /// The client. internal RestClient(BaseDiscordClient client) : this(client.Configuration.Proxy, client.Configuration.HttpTimeout, client.Configuration.UseRelativeRatelimit, client.Logger) { this.Discord = client; this.HttpClient.DefaultRequestHeaders.TryAddWithoutValidation("Authorization", Utilities.GetFormattedToken(client)); if (client.Configuration.Override != null) { this.HttpClient.DefaultRequestHeaders.TryAddWithoutValidation("x-super-properties", client.Configuration.Override); } } /// /// Initializes a new instance of the class. /// /// The proxy. /// The timeout. /// If true, use relative ratelimit. /// The logger. internal RestClient(IWebProxy proxy, TimeSpan timeout, bool useRelativeRatelimit, ILogger logger) // This is for meta-clients, such as the webhook client { this.Logger = logger; var httphandler = new HttpClientHandler { UseCookies = false, AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip, UseProxy = proxy != null, Proxy = proxy }; this.HttpClient = new HttpClient(httphandler) { BaseAddress = new Uri(Utilities.GetApiBaseUri(this.Discord?.Configuration)), Timeout = timeout }; this.HttpClient.DefaultRequestHeaders.TryAddWithoutValidation("User-Agent", Utilities.GetUserAgent()); if (this.Discord != null && this.Discord.Configuration != null && this.Discord.Configuration.Override != null) { this.HttpClient.DefaultRequestHeaders.TryAddWithoutValidation("x-super-properties", this.Discord.Configuration.Override); } this.RoutesToHashes = new ConcurrentDictionary(); this.HashesToBuckets = new ConcurrentDictionary(); this.RequestQueue = new ConcurrentDictionary(); this.GlobalRateLimitEvent = new AsyncManualResetEvent(true); this.UseResetAfter = useRelativeRatelimit; } /// /// Gets a bucket. /// /// The method. /// The route. - /// The route paramaters. + /// The route paramaters. /// The url. /// A ratelimit bucket. - public RateLimitBucket GetBucket(RestRequestMethod method, string route, object route_params, out string url) + public RateLimitBucket GetBucket(RestRequestMethod method, string route, object routeParams, out string url) { - var rparams_props = route_params.GetType() + var rparamsProps = routeParams.GetType() .GetTypeInfo() .DeclaredProperties; var rparams = new Dictionary(); - foreach (var xp in rparams_props) + foreach (var xp in rparamsProps) { - var val = xp.GetValue(route_params); + var val = xp.GetValue(routeParams); rparams[xp.Name] = val is string xs ? xs : val is DateTime dt ? dt.ToString("yyyy-MM-ddTHH:mm:sszzz", CultureInfo.InvariantCulture) : val is DateTimeOffset dto ? dto.ToString("yyyy-MM-ddTHH:mm:sszzz", CultureInfo.InvariantCulture) : val is IFormattable xf ? xf.ToString(null, CultureInfo.InvariantCulture) : val.ToString(); } - var guild_id = rparams.ContainsKey("guild_id") ? rparams["guild_id"] : ""; - var channel_id = rparams.ContainsKey("channel_id") ? rparams["channel_id"] : ""; - var webhook_id = rparams.ContainsKey("webhook_id") ? rparams["webhook_id"] : ""; + var guildId = rparams.ContainsKey("guild_id") ? rparams["guild_id"] : ""; + var channelId = rparams.ContainsKey("channel_id") ? rparams["channel_id"] : ""; + var webhookId = rparams.ContainsKey("webhook_id") ? rparams["webhook_id"] : ""; // Create a generic route (minus major params) key // ex: POST:/channels/channel_id/messages var hashKey = RateLimitBucket.GenerateHashKey(method, route); // We check if the hash is present, using our generic route (without major params) // ex: in POST:/channels/channel_id/messages, out 80c17d2f203122d936070c88c8d10f33 // If it doesn't exist, we create an unlimited hash as our initial key in the form of the hash key + the unlimited constant // and assign this to the route to hash cache // ex: this.RoutesToHashes[POST:/channels/channel_id/messages] = POST:/channels/channel_id/messages:unlimited var hash = this.RoutesToHashes.GetOrAdd(hashKey, RateLimitBucket.GenerateUnlimitedHash(method, route)); // Next we use the hash to generate the key to obtain the bucket. // ex: 80c17d2f203122d936070c88c8d10f33:guild_id:506128773926879242:webhook_id // or if unlimited: POST:/channels/channel_id/messages:unlimited:guild_id:506128773926879242:webhook_id - var bucketId = RateLimitBucket.GenerateBucketId(hash, guild_id, channel_id, webhook_id); + var bucketId = RateLimitBucket.GenerateBucketId(hash, guildId, channelId, webhookId); // If it's not in cache, create a new bucket and index it by its bucket id. - var bucket = this.HashesToBuckets.GetOrAdd(bucketId, new RateLimitBucket(hash, guild_id, channel_id, webhook_id)); + var bucket = this.HashesToBuckets.GetOrAdd(bucketId, new RateLimitBucket(hash, guildId, channelId, webhookId)); bucket.LastAttemptAt = DateTimeOffset.UtcNow; // Cache the routes for each bucket so it can be used for GC later. if (!bucket.RouteHashes.Contains(bucketId)) bucket.RouteHashes.Add(bucketId); // Add the current route to the request queue, which indexes the amount // of requests occurring to the bucket id. _ = this.RequestQueue.TryGetValue(bucketId, out var count); // Increment by one atomically due to concurrency this.RequestQueue[bucketId] = Interlocked.Increment(ref count); // Start bucket cleaner if not already running. if (!this._cleanerRunning) { this._cleanerRunning = true; this._bucketCleanerTokenSource = new CancellationTokenSource(); this._cleanerTask = Task.Run(this.CleanupBucketsAsync, this._bucketCleanerTokenSource.Token); this.Logger.LogDebug(LoggerEvents.RestCleaner, "Bucket cleaner task started."); } - url = RouteArgumentRegex.Replace(route, xm => rparams[xm.Groups[1].Value]); + url = s_routeArgumentRegex.Replace(route, xm => rparams[xm.Groups[1].Value]); return bucket; } /// /// Executes the request async. /// /// The request to be executed. public Task ExecuteRequestAsync(BaseRestRequest request) => request == null ? throw new ArgumentNullException(nameof(request)) : this.ExecuteRequestAsync(request, null, null); /// /// Executes the request async. /// This is to allow proper rescheduling of the first request from a bucket. /// /// The request to be executed. /// The bucket. /// The ratelimit task completion source. private async Task ExecuteRequestAsync(BaseRestRequest request, RateLimitBucket bucket, TaskCompletionSource ratelimitTcs) { if (this._disposed) return; HttpResponseMessage res = default; try { await this.GlobalRateLimitEvent.WaitAsync().ConfigureAwait(false); if (bucket == null) bucket = request.RateLimitBucket; if (ratelimitTcs == null) ratelimitTcs = await this.WaitForInitialRateLimit(bucket).ConfigureAwait(false); if (ratelimitTcs == null) // ckeck rate limit only if we are not the probe request { var now = DateTimeOffset.UtcNow; await bucket.TryResetLimitAsync(now).ConfigureAwait(false); // Decrement the remaining number of requests as there can be other concurrent requests before this one finishes and has a chance to update the bucket - if (Interlocked.Decrement(ref bucket._remaining) < 0) + if (Interlocked.Decrement(ref bucket.RemainingInternal) < 0) { this.Logger.LogDebug(LoggerEvents.RatelimitDiag, "Request for {0} is blocked", bucket.ToString()); var delay = bucket.Reset - now; var resetDate = bucket.Reset; if (this.UseResetAfter) { delay = bucket.ResetAfter.Value; resetDate = bucket.ResetAfterOffset; } if (delay < new TimeSpan(-TimeSpan.TicksPerMinute)) { this.Logger.LogError(LoggerEvents.RatelimitDiag, "Failed to retrieve ratelimits - giving up and allowing next request for bucket"); - bucket._remaining = 1; + bucket.RemainingInternal = 1; } if (delay < TimeSpan.Zero) delay = TimeSpan.FromMilliseconds(100); this.Logger.LogWarning(LoggerEvents.RatelimitPreemptive, "Pre-emptive ratelimit triggered - waiting until {0:yyyy-MM-dd HH:mm:ss zzz} ({1:c}).", resetDate, delay); Task.Delay(delay) .ContinueWith(_ => this.ExecuteRequestAsync(request, null, null)) .LogTaskFault(this.Logger, LogLevel.Error, LoggerEvents.RestError, "Error while executing request"); return; } this.Logger.LogDebug(LoggerEvents.RatelimitDiag, "Request for {0} is allowed", bucket.ToString()); } else this.Logger.LogDebug(LoggerEvents.RatelimitDiag, "Initial request for {0} is allowed", bucket.ToString()); var req = this.BuildRequest(request); if (this.Debug) this.Logger.LogTrace(LoggerEvents.Misc, await req.Content.ReadAsStringAsync()); var response = new RestResponse(); try { if (this._disposed) return; res = await this.HttpClient.SendAsync(req, HttpCompletionOption.ResponseContentRead, CancellationToken.None).ConfigureAwait(false); var bts = await res.Content.ReadAsByteArrayAsync().ConfigureAwait(false); var txt = Utilities.UTF8.GetString(bts, 0, bts.Length); this.Logger.LogTrace(LoggerEvents.RestRx, txt); response.Headers = res.Headers.ToDictionary(xh => xh.Key, xh => string.Join("\n", xh.Value), StringComparer.OrdinalIgnoreCase); response.Response = txt; response.ResponseCode = (int)res.StatusCode; } catch (HttpRequestException httpex) { this.Logger.LogError(LoggerEvents.RestError, httpex, "Request to {0} triggered an HttpException", request.Url); request.SetFaulted(httpex); this.FailInitialRateLimitTest(request, ratelimitTcs); return; } this.UpdateBucket(request, response, ratelimitTcs); Exception ex = null; switch (response.ResponseCode) { case 400: case 405: ex = new BadRequestException(request, response); break; case 401: case 403: ex = new UnauthorizedException(request, response); break; case 404: ex = new NotFoundException(request, response); break; case 413: ex = new RequestSizeException(request, response); break; case 429: ex = new RateLimitException(request, response); // check the limit info and requeue this.Handle429(response, out var wait, out var global); if (wait != null) { if (global) { bucket.IsGlobal = true; this.Logger.LogError(LoggerEvents.RatelimitHit, "Global ratelimit hit, cooling down"); try { this.GlobalRateLimitEvent.Reset(); await wait.ConfigureAwait(false); } finally { // we don't want to wait here until all the blocked requests have been run, additionally Set can never throw an exception that could be suppressed here _ = this.GlobalRateLimitEvent.SetAsync(); } this.ExecuteRequestAsync(request, bucket, ratelimitTcs) .LogTaskFault(this.Logger, LogLevel.Error, LoggerEvents.RestError, "Error while retrying request"); } else { this.Logger.LogError(LoggerEvents.RatelimitHit, "Ratelimit hit, requeueing request to {0}", request.Url); await wait.ConfigureAwait(false); this.ExecuteRequestAsync(request, bucket, ratelimitTcs) .LogTaskFault(this.Logger, LogLevel.Error, LoggerEvents.RestError, "Error while retrying request"); } return; } break; case 500: case 502: case 503: case 504: ex = new ServerErrorException(request, response); break; } if (ex != null) request.SetFaulted(ex); else request.SetCompleted(response); } catch (Exception ex) { this.Logger.LogError(LoggerEvents.RestError, ex, "Request to {0} triggered an exception", request.Url); // if something went wrong and we couldn't get rate limits for the first request here, allow the next request to run - if (bucket != null && ratelimitTcs != null && bucket._limitTesting != 0) + if (bucket != null && ratelimitTcs != null && bucket.LimitTesting != 0) this.FailInitialRateLimitTest(request, ratelimitTcs); if (!request.TrySetFaulted(ex)) throw; } finally { res?.Dispose(); // Get and decrement active requests in this bucket by 1. _ = this.RequestQueue.TryGetValue(bucket.BucketId, out var count); this.RequestQueue[bucket.BucketId] = Interlocked.Decrement(ref count); // If it's 0 or less, we can remove the bucket from the active request queue, // along with any of its past routes. if (count <= 0) { foreach (var r in bucket.RouteHashes) { if (this.RequestQueue.ContainsKey(r)) { _ = this.RequestQueue.TryRemove(r, out _); } } } } } /// /// Fails the initial rate limit test. /// /// The request. /// The ratelimit task completion source. /// If true, reset to initial. private void FailInitialRateLimitTest(BaseRestRequest request, TaskCompletionSource ratelimitTcs, bool resetToInitial = false) { if (ratelimitTcs == null && !resetToInitial) return; var bucket = request.RateLimitBucket; - bucket._limitValid = false; - bucket._limitTestFinished = null; - bucket._limitTesting = 0; + bucket.LimitValid = false; + bucket.LimitTestFinished = null; + bucket.LimitTesting = 0; //Reset to initial values. if (resetToInitial) { this.UpdateHashCaches(request, bucket); bucket.Maximum = 0; - bucket._remaining = 0; + bucket.RemainingInternal = 0; return; } // no need to wait on all the potentially waiting tasks _ = Task.Run(() => ratelimitTcs.TrySetResult(false)); } /// /// Waits for the initial rate limit. /// /// The bucket. private async Task> WaitForInitialRateLimit(RateLimitBucket bucket) { - while (!bucket._limitValid) + while (!bucket.LimitValid) { - if (bucket._limitTesting == 0) + if (bucket.LimitTesting == 0) { - if (Interlocked.CompareExchange(ref bucket._limitTesting, 1, 0) == 0) + if (Interlocked.CompareExchange(ref bucket.LimitTesting, 1, 0) == 0) { // if we got here when the first request was just finishing, we must not create the waiter task as it would signel ExecureRequestAsync to bypass rate limiting - if (bucket._limitValid) + if (bucket.LimitValid) return null; // allow exactly one request to go through without having rate limits available var ratelimitsTcs = new TaskCompletionSource(); - bucket._limitTestFinished = ratelimitsTcs.Task; + bucket.LimitTestFinished = ratelimitsTcs.Task; return ratelimitsTcs; } } // it can take a couple of cycles for the task to be allocated, so wait until it happens or we are no longer probing for the limits Task waitTask = null; - while (bucket._limitTesting != 0 && (waitTask = bucket._limitTestFinished) == null) + while (bucket.LimitTesting != 0 && (waitTask = bucket.LimitTestFinished) == null) await Task.Yield(); if (waitTask != null) await waitTask.ConfigureAwait(false); // if the request failed and the response did not have rate limit headers we have allow the next request and wait again, thus this is a loop here } return null; } /// /// Builds the request. /// /// The request. /// A http request message. private HttpRequestMessage BuildRequest(BaseRestRequest request) { var req = new HttpRequestMessage(new HttpMethod(request.Method.ToString()), request.Url); if (request.Headers != null && request.Headers.Any()) foreach (var kvp in request.Headers) req.Headers.Add(kvp.Key, kvp.Value); if (request is RestRequest nmprequest && !string.IsNullOrWhiteSpace(nmprequest.Payload)) { this.Logger.LogTrace(LoggerEvents.RestTx, nmprequest.Payload); req.Content = new StringContent(nmprequest.Payload); req.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json"); } if (request is MultipartWebRequest mprequest) { this.Logger.LogTrace(LoggerEvents.RestTx, ""); var boundary = "---------------------------" + DateTime.Now.Ticks.ToString("x"); req.Headers.Add("Connection", "keep-alive"); req.Headers.Add("Keep-Alive", "600"); var content = new MultipartFormDataContent(boundary); if (mprequest.Values != null && mprequest.Values.Any()) foreach (var kvp in mprequest.Values) content.Add(new StringContent(kvp.Value), kvp.Key); var fileId = mprequest.OverwriteFileIdStart ?? 0; if (mprequest.Files != null && mprequest.Files.Any()) { foreach (var f in mprequest.Files) { var name = $"files[{fileId.ToString(CultureInfo.InvariantCulture)}]"; content.Add(new StreamContent(f.Value), name, f.Key); fileId++; } } req.Content = content; } if (request is MultipartStickerWebRequest mpsrequest) { this.Logger.LogTrace(LoggerEvents.RestTx, ""); var boundary = "---------------------------" + DateTime.Now.Ticks.ToString("x"); req.Headers.Add("Connection", "keep-alive"); req.Headers.Add("Keep-Alive", "600"); var sc = new StreamContent(mpsrequest.File.Stream); if (mpsrequest.File.ContentType != null) sc.Headers.ContentType = new MediaTypeHeaderValue(mpsrequest.File.ContentType); var fileName = mpsrequest.File.FileName; if (mpsrequest.File.FileType != null) fileName += '.' + mpsrequest.File.FileType; var content = new MultipartFormDataContent(boundary) { { new StringContent(mpsrequest.Name), "name" }, { new StringContent(mpsrequest.Tags), "tags" }, { new StringContent(mpsrequest.Description), "description" }, { sc, "file", fileName } }; req.Content = content; } return req; } /// /// Handles the http 429 status. /// /// The response. - /// The wait task. + /// The wait task. /// If true, global. - private void Handle429(RestResponse response, out Task wait_task, out bool global) + private void Handle429(RestResponse response, out Task waitTask, out bool global) { - wait_task = null; + waitTask = null; global = false; if (response.Headers == null) return; var hs = response.Headers; // handle the wait - if (hs.TryGetValue("Retry-After", out var retry_after_raw)) + if (hs.TryGetValue("Retry-After", out var retryAfterRaw)) { - var retry_after = TimeSpan.FromSeconds(int.Parse(retry_after_raw, CultureInfo.InvariantCulture)); - wait_task = Task.Delay(retry_after); + var retryAfter = TimeSpan.FromSeconds(int.Parse(retryAfterRaw, CultureInfo.InvariantCulture)); + waitTask = Task.Delay(retryAfter); } // check if global b1nzy if (hs.TryGetValue("X-RateLimit-Global", out var isglobal) && isglobal.ToLowerInvariant() == "true") { // global global = true; } } /// /// Updates the bucket. /// /// The request. /// The response. /// The ratelimit task completion source. private void UpdateBucket(BaseRestRequest request, RestResponse response, TaskCompletionSource ratelimitTcs) { var bucket = request.RateLimitBucket; if (response.Headers == null) { if (response.ResponseCode != 429) // do not fail when ratelimit was or the next request will be scheduled hitting the rate limit again this.FailInitialRateLimitTest(request, ratelimitTcs); return; } var hs = response.Headers; if (hs.TryGetValue("X-RateLimit-Scope", out var scope)) { bucket.Scope = scope; } if (hs.TryGetValue("X-RateLimit-Global", out var isglobal) && isglobal.ToLowerInvariant() == "true") { if (response.ResponseCode != 429) { bucket.IsGlobal = true; this.FailInitialRateLimitTest(request, ratelimitTcs); } return; } var r1 = hs.TryGetValue("X-RateLimit-Limit", out var usesmax); var r2 = hs.TryGetValue("X-RateLimit-Remaining", out var usesleft); var r3 = hs.TryGetValue("X-RateLimit-Reset", out var reset); var r4 = hs.TryGetValue("X-Ratelimit-Reset-After", out var resetAfter); var r5 = hs.TryGetValue("X-Ratelimit-Bucket", out var hash); if (!r1 || !r2 || !r3 || !r4) { //If the limits were determined before this request, make the bucket initial again. if (response.ResponseCode != 429) this.FailInitialRateLimitTest(request, ratelimitTcs, ratelimitTcs == null); return; } var clienttime = DateTimeOffset.UtcNow; var resettime = new DateTimeOffset(1970, 1, 1, 0, 0, 0, TimeSpan.Zero).AddSeconds(double.Parse(reset, CultureInfo.InvariantCulture)); var servertime = clienttime; - if (hs.TryGetValue("Date", out var raw_date)) - servertime = DateTimeOffset.Parse(raw_date, CultureInfo.InvariantCulture).ToUniversalTime(); + if (hs.TryGetValue("Date", out var rawDate)) + servertime = DateTimeOffset.Parse(rawDate, CultureInfo.InvariantCulture).ToUniversalTime(); var resetdelta = resettime - servertime; //var difference = clienttime - servertime; //if (Math.Abs(difference.TotalSeconds) >= 1) //// this.Logger.LogMessage(LogLevel.DebugBaseDiscordClient.RestEventId, $"Difference between machine and server time: {difference.TotalMilliseconds.ToString("#,##0.00", CultureInfo.InvariantCulture)}ms", DateTime.Now); //else // difference = TimeSpan.Zero; if (request.RateLimitWaitOverride.HasValue) resetdelta = TimeSpan.FromSeconds(request.RateLimitWaitOverride.Value); var newReset = clienttime + resetdelta; if (this.UseResetAfter) { bucket.ResetAfter = TimeSpan.FromSeconds(double.Parse(resetAfter, CultureInfo.InvariantCulture)); newReset = clienttime + bucket.ResetAfter.Value + (request.RateLimitWaitOverride.HasValue ? resetdelta : TimeSpan.Zero); bucket.ResetAfterOffset = newReset; } else bucket.Reset = newReset; var maximum = int.Parse(usesmax, CultureInfo.InvariantCulture); var remaining = int.Parse(usesleft, CultureInfo.InvariantCulture); if (ratelimitTcs != null) { // initial population of the ratelimit data bucket.SetInitialValues(maximum, remaining, newReset); _ = Task.Run(() => ratelimitTcs.TrySetResult(true)); } else { // only update the bucket values if this request was for a newer interval than the one // currently in the bucket, to avoid issues with concurrent requests in one bucket // remaining is reset by TryResetLimit and not the response, just allow that to happen when it is time - if (bucket._nextReset == 0) - bucket._nextReset = newReset.UtcTicks; + if (bucket.NextReset == 0) + bucket.NextReset = newReset.UtcTicks; } this.UpdateHashCaches(request, bucket, hash); } /// /// Updates the hash caches. /// /// The request. /// The bucket. /// The new hash. private void UpdateHashCaches(BaseRestRequest request, RateLimitBucket bucket, string newHash = null) { var hashKey = RateLimitBucket.GenerateHashKey(request.Method, request.Route); if (!this.RoutesToHashes.TryGetValue(hashKey, out var oldHash)) return; // This is an unlimited bucket, which we don't need to keep track of. if (newHash == null) { _ = this.RoutesToHashes.TryRemove(hashKey, out _); _ = this.HashesToBuckets.TryRemove(bucket.BucketId, out _); return; } // Only update the hash once, due to a bug on Discord's end. // This will cause issues if the bucket hashes are dynamically changed from the API while running, // in which case, Dispose will need to be called to clear the caches. - if (bucket._isUnlimited && newHash != oldHash) + if (bucket.IsUnlimited && newHash != oldHash) { this.Logger.LogDebug(LoggerEvents.RestHashMover, "Updating hash in {0}: \"{1}\" -> \"{2}\"", hashKey, oldHash, newHash); var bucketId = RateLimitBucket.GenerateBucketId(newHash, bucket.GuildId, bucket.ChannelId, bucket.WebhookId); _ = this.RoutesToHashes.AddOrUpdate(hashKey, newHash, (key, oldHash) => { bucket.Hash = newHash; var oldBucketId = RateLimitBucket.GenerateBucketId(oldHash, bucket.GuildId, bucket.ChannelId, bucket.WebhookId); // Remove the old unlimited bucket. _ = this.HashesToBuckets.TryRemove(oldBucketId, out _); _ = this.HashesToBuckets.AddOrUpdate(bucketId, bucket, (key, oldBucket) => bucket); return newHash; }); } return; } /// /// Cleanups the buckets. /// private async Task CleanupBucketsAsync() { while (!this._bucketCleanerTokenSource.IsCancellationRequested) { try { await Task.Delay(this._bucketCleanupDelay, this._bucketCleanerTokenSource.Token).ConfigureAwait(false); } catch { } if (this._disposed) return; //Check and clean request queue first in case it wasn't removed properly during requests. foreach (var key in this.RequestQueue.Keys) { var bucket = this.HashesToBuckets.Values.FirstOrDefault(x => x.RouteHashes.Contains(key)); if (bucket == null || (bucket != null && bucket.LastAttemptAt.AddSeconds(5) < DateTimeOffset.UtcNow)) _ = this.RequestQueue.TryRemove(key, out _); } var removedBuckets = 0; StringBuilder bucketIdStrBuilder = default; foreach (var kvp in this.HashesToBuckets) { if (bucketIdStrBuilder == null) bucketIdStrBuilder = new StringBuilder(); var key = kvp.Key; var value = kvp.Value; // Don't remove the bucket if it's currently being handled by the rest client, unless it's an unlimited bucket. - if (this.RequestQueue.ContainsKey(value.BucketId) && !value._isUnlimited) + if (this.RequestQueue.ContainsKey(value.BucketId) && !value.IsUnlimited) continue; var resetOffset = this.UseResetAfter ? value.ResetAfterOffset : value.Reset; // Don't remove the bucket if it's reset date is less than now + the additional wait time, unless it's an unlimited bucket. - if (resetOffset != null && !value._isUnlimited && (resetOffset > DateTimeOffset.UtcNow || DateTimeOffset.UtcNow - resetOffset < this._bucketCleanupDelay)) + if (resetOffset != null && !value.IsUnlimited && (resetOffset > DateTimeOffset.UtcNow || DateTimeOffset.UtcNow - resetOffset < this._bucketCleanupDelay)) continue; _ = this.HashesToBuckets.TryRemove(key, out _); removedBuckets++; bucketIdStrBuilder.Append(value.BucketId + ", "); } if (removedBuckets > 0) this.Logger.LogDebug(LoggerEvents.RestCleaner, "Removed {0} unused bucket{1}: [{2}]", removedBuckets, removedBuckets > 1 ? "s" : string.Empty, bucketIdStrBuilder.ToString().TrimEnd(',', ' ')); if (this.HashesToBuckets.Count == 0) break; } if (!this._bucketCleanerTokenSource.IsCancellationRequested) this._bucketCleanerTokenSource.Cancel(); this._cleanerRunning = false; this.Logger.LogDebug(LoggerEvents.RestCleaner, "Bucket cleaner task stopped."); } ~RestClient() => this.Dispose(); /// /// Disposes the rest client. /// public void Dispose() { if (this._disposed) return; this._disposed = true; this.GlobalRateLimitEvent.Reset(); if (this._bucketCleanerTokenSource?.IsCancellationRequested == false) { this._bucketCleanerTokenSource?.Cancel(); this.Logger.LogDebug(LoggerEvents.RestCleaner, "Bucket cleaner task stopped."); } try { this._cleanerTask?.Dispose(); this._bucketCleanerTokenSource?.Dispose(); this.HttpClient?.Dispose(); } catch { } this.RoutesToHashes.Clear(); this.HashesToBuckets.Clear(); this.RequestQueue.Clear(); } } } diff --git a/DisCatSharp/Net/Rest/RestRequestMethod.cs b/DisCatSharp/Net/Rest/RestRequestMethod.cs index 15fdc8a72..3520f9011 100644 --- a/DisCatSharp/Net/Rest/RestRequestMethod.cs +++ b/DisCatSharp/Net/Rest/RestRequestMethod.cs @@ -1,60 +1,61 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. +// ReSharper disable InconsistentNaming namespace DisCatSharp.Net { /// /// Defines the HTTP method to use for an HTTP request. /// public enum RestRequestMethod : int { /// /// Defines that the request is a GET request. /// GET = 0, /// /// Defines that the request is a POST request. /// POST = 1, /// /// Defines that the request is a DELETE request. /// DELETE = 2, /// /// Defines that the request is a PATCH request. /// PATCH = 3, /// /// Defines that the request is a PUT request. /// PUT = 4, /// /// Defines that the request is a HEAD request. /// HEAD = 5 } } diff --git a/DisCatSharp/Net/Rest/SessionBucket.cs b/DisCatSharp/Net/Rest/SessionBucket.cs index 477f9c4a2..41d8a68bb 100644 --- a/DisCatSharp/Net/Rest/SessionBucket.cs +++ b/DisCatSharp/Net/Rest/SessionBucket.cs @@ -1,71 +1,71 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System; using Newtonsoft.Json; namespace DisCatSharp.Net { /// /// Represents the bucket limits for identifying to Discord. /// This is only relevant for clients that are manually sharding. /// public class SessionBucket { /// /// Gets the total amount of sessions per token. /// [JsonProperty("total")] public int Total { get; internal set; } /// /// Gets the remaining amount of sessions for this token. /// [JsonProperty("remaining")] public int Remaining { get; internal set; } /// /// Gets the datetime when the will reset. /// [JsonIgnore] public DateTimeOffset ResetAfter { get; internal set; } /// /// Gets the maximum amount of shards that can boot concurrently. /// [JsonProperty("max_concurrency")] public int MaxConcurrency { get; internal set; } /// /// Gets the reset after value. /// [JsonProperty("reset_after")] [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "")] - internal int resetAfter { get; set; } + internal int ResetAfterInternal { get; set; } /// /// Returns a readable session bucket string. /// public override string ToString() => $"[{this.Remaining}/{this.Total}] {this.ResetAfter}. {this.MaxConcurrency}x concurrency"; } } diff --git a/DisCatSharp/Net/Udp/DCSUdpClient.cs b/DisCatSharp/Net/Udp/DCSUdpClient.cs index 73334f04e..a7b4ca946 100644 --- a/DisCatSharp/Net/Udp/DCSUdpClient.cs +++ b/DisCatSharp/Net/Udp/DCSUdpClient.cs @@ -1,141 +1,141 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System; using System.Collections.Concurrent; using System.Net.Sockets; using System.Threading; using System.Threading.Tasks; namespace DisCatSharp.Net.Udp { /// /// The default, native-based UDP client implementation. /// - internal class DCSUdpClient : BaseUdpClient + internal class DcsUdpClient : BaseUdpClient { /// /// Gets the client. /// private UdpClient Client { get; set; } /// /// Gets the end point. /// private ConnectionEndpoint EndPoint { get; set; } /// /// Gets the packet queue. /// private BlockingCollection PacketQueue { get; } /// /// Gets the receiver task. /// [System.Diagnostics.CodeAnalysis.SuppressMessage("CodeQuality", "IDE0052:Remove unread private members", Justification = "")] private Task ReceiverTask { get; set; } /// /// Gets the cancellation token source. /// private CancellationTokenSource TokenSource { get; } /// /// Gets the cancellation token. /// private CancellationToken Token => this.TokenSource.Token; /// /// Creates a new UDP client instance. /// - public DCSUdpClient() + public DcsUdpClient() { this.PacketQueue = new BlockingCollection(); this.TokenSource = new CancellationTokenSource(); } /// /// Configures the UDP client. /// /// Endpoint that the client will be communicating with. public override void Setup(ConnectionEndpoint endpoint) { this.EndPoint = endpoint; this.Client = new UdpClient(); this.ReceiverTask = Task.Run(this.ReceiverLoopAsync, this.Token); } /// /// Sends a datagram. /// /// Datagram. /// Length of the datagram. /// public override Task SendAsync(byte[] data, int dataLength) => this.Client.SendAsync(data, dataLength, this.EndPoint.Hostname, this.EndPoint.Port); /// /// Receives a datagram. /// /// The received bytes. public override Task ReceiveAsync() => Task.FromResult(this.PacketQueue.Take(this.Token)); /// /// Closes and disposes the client. /// public override void Close() { this.TokenSource.Cancel(); #if !NETSTANDARD1_3 try { this.Client.Close(); } catch (Exception) { } #endif // dequeue all the packets this.PacketQueue.Dispose(); } /// /// Receivers the loop. /// private async Task ReceiverLoopAsync() { while (!this.Token.IsCancellationRequested) { try { var packet = await this.Client.ReceiveAsync().ConfigureAwait(false); this.PacketQueue.Add(packet.Buffer); } catch (Exception) { } } } /// /// Creates a new instance of . /// public static BaseUdpClient CreateNew() - => new DCSUdpClient(); + => new DcsUdpClient(); } } diff --git a/DisCatSharp/RingBuffer.cs b/DisCatSharp/RingBuffer.cs index 2cd0df9e9..1e042083a 100644 --- a/DisCatSharp/RingBuffer.cs +++ b/DisCatSharp/RingBuffer.cs @@ -1,241 +1,241 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System; using System.Collections; using System.Collections.Generic; using System.Linq; namespace DisCatSharp { /// /// A circular buffer collection. /// /// Type of elements within this ring buffer. public class RingBuffer : ICollection { /// /// Gets the current index of the buffer items. /// public int CurrentIndex { get; protected set; } /// /// Gets the capacity of this ring buffer. /// public int Capacity { get; protected set; } /// /// Gets the number of items in this ring buffer. /// public int Count - => this._reached_end ? this.Capacity : this.CurrentIndex; + => this._reachedEnd ? this.Capacity : this.CurrentIndex; /// /// Gets whether this ring buffer is read-only. /// public bool IsReadOnly => false; /// /// Gets or sets the internal collection of items. /// protected T[] InternalBuffer { get; set; } - private bool _reached_end = false; + private bool _reachedEnd = false; /// /// Creates a new ring buffer with specified size. /// /// Size of the buffer to create. /// public RingBuffer(int size) { if (size <= 0) throw new ArgumentOutOfRangeException(nameof(size), "Size must be positive."); this.CurrentIndex = 0; this.Capacity = size; this.InternalBuffer = new T[this.Capacity]; } /// /// Creates a new ring buffer, filled with specified elements. /// /// Elements to fill the buffer with. /// /// public RingBuffer(IEnumerable elements) : this(elements, 0) { } /// /// Creates a new ring buffer, filled with specified elements, and starting at specified index. /// /// Elements to fill the buffer with. /// Starting element index. /// /// public RingBuffer(IEnumerable elements, int index) { if (elements == null || !elements.Any()) throw new ArgumentException(nameof(elements), "The collection cannot be null or empty."); this.CurrentIndex = index; this.InternalBuffer = elements.ToArray(); this.Capacity = this.InternalBuffer.Length; if (this.CurrentIndex >= this.InternalBuffer.Length || this.CurrentIndex < 0) throw new ArgumentOutOfRangeException(nameof(index), "Index must be less than buffer capacity, and greater than zero."); } /// /// Inserts an item into this ring buffer. /// /// Item to insert. public void Add(T item) { this.InternalBuffer[this.CurrentIndex++] = item; if (this.CurrentIndex == this.Capacity) { this.CurrentIndex = 0; - this._reached_end = true; + this._reachedEnd = true; } } /// /// Gets first item from the buffer that matches the predicate. /// /// Predicate used to find the item. /// Item that matches the predicate, or default value for the type of the items in this ring buffer, if one is not found. /// Whether an item that matches the predicate was found or not. public bool TryGet(Func predicate, out T item) { for (var i = this.CurrentIndex; i < this.InternalBuffer.Length; i++) { if (this.InternalBuffer[i] != null && predicate(this.InternalBuffer[i])) { item = this.InternalBuffer[i]; return true; } } for (var i = 0; i < this.CurrentIndex; i++) { if (this.InternalBuffer[i] != null && predicate(this.InternalBuffer[i])) { item = this.InternalBuffer[i]; return true; } } item = default; return false; } /// /// Clears this ring buffer and resets the current item index. /// public void Clear() { for (var i = 0; i < this.InternalBuffer.Length; i++) this.InternalBuffer[i] = default; this.CurrentIndex = 0; } /// /// Checks whether given item is present in the buffer. This method is not implemented. Use instead. /// /// Item to check for. /// Whether the buffer contains the item. /// public bool Contains(T item) => throw new NotImplementedException("This method is not implemented. Use .Contains(predicate) instead."); /// /// Checks whether given item is present in the buffer using given predicate to find it. /// /// Predicate used to check for the item. /// Whether the buffer contains the item. public bool Contains(Func predicate) => this.InternalBuffer.Any(predicate); /// /// Copies this ring buffer to target array, attempting to maintain the order of items within. /// /// Target array. /// Index starting at which to copy the items to. public void CopyTo(T[] array, int index) { if (array.Length - index < 1) throw new ArgumentException("Target array is too small to contain the elements from this buffer.", nameof(array)); var ci = 0; for (var i = this.CurrentIndex; i < this.InternalBuffer.Length; i++) array[ci++] = this.InternalBuffer[i]; for (var i = 0; i < this.CurrentIndex; i++) array[ci++] = this.InternalBuffer[i]; } /// /// Removes an item from the buffer. This method is not implemented. Use instead. /// /// Item to remove. /// Whether an item was removed or not. public bool Remove(T item) => throw new NotImplementedException("This method is not implemented. Use .Remove(predicate) instead."); /// /// Removes an item from the buffer using given predicate to find it. /// /// Predicate used to find the item. /// Whether an item was removed or not. public bool Remove(Func predicate) { for (var i = 0; i < this.InternalBuffer.Length; i++) { if (this.InternalBuffer[i] != null && predicate(this.InternalBuffer[i])) { this.InternalBuffer[i] = default; return true; } } return false; } /// /// Returns an enumerator for this ring buffer. /// /// Enumerator for this ring buffer. public IEnumerator GetEnumerator() { - return !this._reached_end + return !this._reachedEnd ? this.InternalBuffer.AsEnumerable().GetEnumerator() : this.InternalBuffer.Skip(this.CurrentIndex) .Concat(this.InternalBuffer.Take(this.CurrentIndex)) .GetEnumerator(); } /// /// Returns an enumerator for this ring buffer. /// /// Enumerator for this ring buffer. IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); } } diff --git a/DisCatSharp/Utilities.cs b/DisCatSharp/Utilities.cs index e35e87882..c306ee1c1 100644 --- a/DisCatSharp/Utilities.cs +++ b/DisCatSharp/Utilities.cs @@ -1,465 +1,466 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; using DisCatSharp.Entities; using DisCatSharp.Net; using Microsoft.Extensions.Logging; namespace DisCatSharp { /// /// Various Discord-related utilities. /// public static class Utilities { /// /// Gets the version of the library /// internal static string VersionHeader { get; set; } /// /// Gets or sets the permission strings. /// internal static Dictionary PermissionStrings { get; set; } /// /// Gets the utf8 encoding /// + // ReSharper disable once InconsistentNaming internal static UTF8Encoding UTF8 { get; } = new UTF8Encoding(false); /// /// Initializes a new instance of the class. /// static Utilities() { PermissionStrings = new Dictionary(); var t = typeof(Permissions); var ti = t.GetTypeInfo(); var vals = Enum.GetValues(t).Cast(); foreach (var xv in vals) { var xsv = xv.ToString(); var xmv = ti.DeclaredMembers.FirstOrDefault(xm => xm.Name == xsv); var xav = xmv.GetCustomAttribute(); PermissionStrings[xv] = xav.String; } var a = typeof(DiscordClient).GetTypeInfo().Assembly; var vs = ""; var iv = a.GetCustomAttribute(); if (iv != null) vs = iv.InformationalVersion; else { var v = a.GetName().Version; vs = v.ToString(3); } VersionHeader = $"DiscordBot (https://github.com/Aiko-IT-Systems/DisCatSharp, v{vs})"; } /// /// Gets the api base uri. /// /// The config /// A string. internal static string GetApiBaseUri(DiscordConfiguration config = null) => config == null ? Endpoints.BASE_URI + "9" : config.UseCanary ? Endpoints.CANARY_URI + config.ApiVersion : Endpoints.BASE_URI + config.ApiVersion; /// /// Gets the api uri for. /// /// The path. /// The config /// An Uri. internal static Uri GetApiUriFor(string path, DiscordConfiguration config) => new($"{GetApiBaseUri(config)}{path}"); /// /// Gets the api uri for. /// /// The path. /// The query string. /// The config /// An Uri. internal static Uri GetApiUriFor(string path, string queryString, DiscordConfiguration config) => new($"{GetApiBaseUri(config)}{path}{queryString}"); /// /// Gets the api uri builder for. /// /// The path. /// The config /// A QueryUriBuilder. internal static QueryUriBuilder GetApiUriBuilderFor(string path, DiscordConfiguration config) => new($"{GetApiBaseUri(config)}{path}"); /// /// Gets the formatted token. /// /// The client. /// A string. internal static string GetFormattedToken(BaseDiscordClient client) => GetFormattedToken(client.Configuration); /// /// Gets the formatted token. /// /// The config. /// A string. internal static string GetFormattedToken(DiscordConfiguration config) { return config.TokenType switch { TokenType.Bearer => $"Bearer {config.Token}", TokenType.Bot => $"Bot {config.Token}", _ => throw new ArgumentException("Invalid token type specified.", nameof(config.Token)), }; } /// /// Gets the base headers. /// /// A Dictionary. internal static Dictionary GetBaseHeaders() => new(); /// /// Gets the user agent. /// /// A string. internal static string GetUserAgent() => VersionHeader; /// /// Contains the user mentions. /// /// The message. /// A bool. internal static bool ContainsUserMentions(string message) { var pattern = @"<@(\d+)>"; var regex = new Regex(pattern, RegexOptions.ECMAScript); return regex.IsMatch(message); } /// /// Contains the nickname mentions. /// /// The message. /// A bool. internal static bool ContainsNicknameMentions(string message) { var pattern = @"<@!(\d+)>"; var regex = new Regex(pattern, RegexOptions.ECMAScript); return regex.IsMatch(message); } /// /// Contains the channel mentions. /// /// The message. /// A bool. internal static bool ContainsChannelMentions(string message) { var pattern = @"<#(\d+)>"; var regex = new Regex(pattern, RegexOptions.ECMAScript); return regex.IsMatch(message); } /// /// Contains the role mentions. /// /// The message. /// A bool. internal static bool ContainsRoleMentions(string message) { var pattern = @"<@&(\d+)>"; var regex = new Regex(pattern, RegexOptions.ECMAScript); return regex.IsMatch(message); } /// /// Contains the emojis. /// /// The message. /// A bool. internal static bool ContainsEmojis(string message) { var pattern = @""; var regex = new Regex(pattern, RegexOptions.ECMAScript); return regex.IsMatch(message); } /// /// Gets the user mentions. /// /// The message. /// A list of ulong. internal static IEnumerable GetUserMentions(DiscordMessage message) { var regex = new Regex(@"<@!?(\d+)>", RegexOptions.ECMAScript); var matches = regex.Matches(message.Content); foreach (Match match in matches) yield return ulong.Parse(match.Groups[1].Value, CultureInfo.InvariantCulture); } /// /// Gets the role mentions. /// /// The message. /// A list of ulong. internal static IEnumerable GetRoleMentions(DiscordMessage message) { var regex = new Regex(@"<@&(\d+)>", RegexOptions.ECMAScript); var matches = regex.Matches(message.Content); foreach (Match match in matches) yield return ulong.Parse(match.Groups[1].Value, CultureInfo.InvariantCulture); } /// /// Gets the channel mentions. /// /// The message. /// A list of ulong. internal static IEnumerable GetChannelMentions(DiscordMessage message) { var regex = new Regex(@"<#(\d+)>", RegexOptions.ECMAScript); var matches = regex.Matches(message.Content); foreach (Match match in matches) yield return ulong.Parse(match.Groups[1].Value, CultureInfo.InvariantCulture); } /// /// Gets the emojis. /// /// The message. /// A list of ulong. internal static IEnumerable GetEmojis(DiscordMessage message) { var regex = new Regex(@"", RegexOptions.ECMAScript); var matches = regex.Matches(message.Content); foreach (Match match in matches) yield return ulong.Parse(match.Groups[2].Value, CultureInfo.InvariantCulture); } /// /// Are the valid slash command name. /// /// The name. /// A bool. internal static bool IsValidSlashCommandName(string name) { var regex = new Regex(@"^[\w-]{1,32}$", RegexOptions.ECMAScript); return regex.IsMatch(name); } /// /// Checks the thread auto archive duration feature. /// /// The guild. /// The taad. /// A bool. internal static bool CheckThreadAutoArchiveDurationFeature(DiscordGuild guild, ThreadAutoArchiveDuration taad) { return taad == ThreadAutoArchiveDuration.ThreeDays ? (guild.PremiumTier.HasFlag(PremiumTier.TierOne) || guild.Features.CanSetThreadArchiveDurationThreeDays) : taad != ThreadAutoArchiveDuration.OneWeek || guild.PremiumTier.HasFlag(PremiumTier.TierTwo) || guild.Features.CanSetThreadArchiveDurationSevenDays; } /// /// Checks the thread private feature. /// /// The guild. /// A bool. internal static bool CheckThreadPrivateFeature(DiscordGuild guild) => guild.PremiumTier.HasFlag(PremiumTier.TierTwo) || guild.Features.CanCreatePrivateThreads; /// /// Have the message intents. /// /// The intents. /// A bool. internal static bool HasMessageIntents(DiscordIntents intents) => intents.HasIntent(DiscordIntents.GuildMessages) || intents.HasIntent(DiscordIntents.DirectMessages); /// /// Have the reaction intents. /// /// The intents. /// A bool. internal static bool HasReactionIntents(DiscordIntents intents) => intents.HasIntent(DiscordIntents.GuildMessageReactions) || intents.HasIntent(DiscordIntents.DirectMessageReactions); /// /// Have the typing intents. /// /// The intents. /// A bool. internal static bool HasTypingIntents(DiscordIntents intents) => intents.HasIntent(DiscordIntents.GuildMessageTyping) || intents.HasIntent(DiscordIntents.DirectMessageTyping); // https://discord.com/developers/docs/topics/gateway#sharding-sharding-formula /// /// Gets a shard id from a guild id and total shard count. /// /// The guild id the shard is on. /// The total amount of shards. /// The shard id. public static int GetShardId(ulong guildId, int shardCount) => (int)(guildId >> 22) % shardCount; /// /// Helper method to create a from Unix time seconds for targets that do not support this natively. /// /// Unix time seconds to convert. /// Whether the method should throw on failure. Defaults to true. /// Calculated . public static DateTimeOffset GetDateTimeOffset(long unixTime, bool shouldThrow = true) { try { return DateTimeOffset.FromUnixTimeSeconds(unixTime); } catch (Exception) { if (shouldThrow) throw; return DateTimeOffset.MinValue; } } /// /// Helper method to create a from Unix time milliseconds for targets that do not support this natively. /// /// Unix time milliseconds to convert. /// Whether the method should throw on failure. Defaults to true. /// Calculated . public static DateTimeOffset GetDateTimeOffsetFromMilliseconds(long unixTime, bool shouldThrow = true) { try { return DateTimeOffset.FromUnixTimeMilliseconds(unixTime); } catch (Exception) { if (shouldThrow) throw; return DateTimeOffset.MinValue; } } /// /// Helper method to calculate Unix time seconds from a for targets that do not support this natively. /// /// to calculate Unix time for. /// Calculated Unix time. public static long GetUnixTime(DateTimeOffset dto) => dto.ToUnixTimeMilliseconds(); /// /// Computes a timestamp from a given snowflake. /// /// Snowflake to compute a timestamp from. /// Computed timestamp. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static DateTimeOffset GetSnowflakeTime(this ulong snowflake) - => DiscordClient._discordEpoch.AddMilliseconds(snowflake >> 22); + => DiscordClient.DiscordEpoch.AddMilliseconds(snowflake >> 22); /// /// Converts this into human-readable format. /// /// Permissions enumeration to convert. /// Human-readable permissions. public static string ToPermissionString(this Permissions perm) { if (perm == Permissions.None) return PermissionStrings[perm]; - perm &= PermissionMethods.FULL_PERMS; + perm &= PermissionMethods.FullPerms; var strs = PermissionStrings .Where(xkvp => xkvp.Key != Permissions.None && (perm & xkvp.Key) == xkvp.Key) .Select(xkvp => xkvp.Value); return string.Join(", ", strs.OrderBy(xs => xs)); } /// /// Checks whether this string contains given characters. /// /// String to check. /// Characters to check for. /// Whether the string contained these characters. public static bool Contains(this string str, params char[] characters) { foreach (var xc in str) if (characters.Contains(xc)) return true; return false; } /// /// Logs the task fault. /// /// The task. /// The logger. /// The level. /// The event id. /// The message. internal static void LogTaskFault(this Task task, ILogger logger, LogLevel level, EventId eventId, string message) { if (task == null) throw new ArgumentNullException(nameof(task)); if (logger == null) return; task.ContinueWith(t => logger.Log(level, eventId, t.Exception, message), TaskContinuationOptions.OnlyOnFaulted); } /// /// Deconstructs the. /// /// The kvp. /// The key. /// The value. internal static void Deconstruct(this KeyValuePair kvp, out TKey key, out TValue value) { key = kvp.Key; value = kvp.Value; } } }